summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJérémy Zurcher <jeremy@asynk.ch>2026-03-15 21:42:14 +0100
committerJérémy Zurcher <jeremy@asynk.ch>2026-03-15 21:42:14 +0100
commitf0c2066e3ffffe3212658313cd6e30d85028412c (patch)
tree7fffcf8fcde438d1a565e154a52909fa6c054832
parente4e09f936d38a89082f40354fdf451ad875baffa (diff)
downloadcolonial-twilight-f0c2066e3ffffe3212658313cd6e30d85028412c.zip
colonial-twilight-f0c2066e3ffffe3212658313cd6e30d85028412c.tar.gz
implement FLN action & operations
-rw-r--r--lib/colonial_twilight/actions/action.rb2
-rw-r--r--lib/colonial_twilight/actions/fln/agitate.rb2
-rw-r--r--lib/colonial_twilight/actions/fln/ambush.rb35
-rw-r--r--lib/colonial_twilight/actions/fln/attack.rb57
-rw-r--r--lib/colonial_twilight/actions/fln/extort.rb35
-rw-r--r--lib/colonial_twilight/actions/fln/march.rb62
-rw-r--r--lib/colonial_twilight/actions/fln/oas.rb33
-rw-r--r--lib/colonial_twilight/actions/fln/rally.rb2
-rw-r--r--lib/colonial_twilight/actions/fln/subvert.rb62
-rw-r--r--lib/colonial_twilight/actions/fln/terror.rb33
-rw-r--r--spec/fln_actions_spec.rb297
11 files changed, 617 insertions, 3 deletions
diff --git a/lib/colonial_twilight/actions/action.rb b/lib/colonial_twilight/actions/action.rb
index 3be8483..4b7ea6c 100644
--- a/lib/colonial_twilight/actions/action.rb
+++ b/lib/colonial_twilight/actions/action.rb
@@ -67,7 +67,7 @@ module ColonialTwilight
end
def available_modes(_space)
- nil
+ {}
end
def possible_spaces(board)
diff --git a/lib/colonial_twilight/actions/fln/agitate.rb b/lib/colonial_twilight/actions/fln/agitate.rb
index fd0e287..8434662 100644
--- a/lib/colonial_twilight/actions/fln/agitate.rb
+++ b/lib/colonial_twilight/actions/fln/agitate.rb
@@ -34,7 +34,7 @@ module ColonialTwilight
# with Base and or Control && terror or shift to oppose possible
def applicable?(space)
- Rally.applicable?(space) &&
+ Rally.applicable?(space) && !space.country? &&
(space.fln_bases.positive? || space.fln_control?) && (space.terror.positive? || !space.oppose?)
end
diff --git a/lib/colonial_twilight/actions/fln/ambush.rb b/lib/colonial_twilight/actions/fln/ambush.rb
new file mode 100644
index 0000000..9756c55
--- /dev/null
+++ b/lib/colonial_twilight/actions/fln/ambush.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require_relative 'fln_action'
+
+module ColonialTwilight
+ module Actions
+ module FLN
+ # Ambush 4.3.3 : max 2
+ class Ambush < FlnAction
+ def initialize(space)
+ super(space, {}, cost: 0)
+ end
+
+ # Activate only 1 Underground Guerrilla
+ # Remove 1 Government piece (Police first, then Troops, then Base) into casualties
+ # No Attrition
+ # -1 Commitment per French Base removed
+ def apply!(board)
+ raise NotImplementedError
+ end
+
+ class << self
+ def special?
+ true
+ end
+
+ # any space where an Attack is occurring with Underground Guerrillas.
+ def applicable?(space)
+ Attack.applicable?(space) && space.fln_underground.positive?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/colonial_twilight/actions/fln/attack.rb b/lib/colonial_twilight/actions/fln/attack.rb
new file mode 100644
index 0000000..298f28e
--- /dev/null
+++ b/lib/colonial_twilight/actions/fln/attack.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require_relative 'fln_action'
+require_relative 'ambush'
+
+module ColonialTwilight
+ module Actions
+ module FLN
+ # Attack 3.3.3
+ class Attack < FlnAction
+ def initialize(space)
+ super(space, {}, cost: 1)
+ @ambush = nil
+ end
+
+ # can be combined with Ambush (replaces Attack procedure)
+ def ambush!
+ raise 'ambush! called twice' unless @ambush.nil?
+
+ @ambush = Ambush.new(space)
+ self
+ end
+
+
+ # def validate!
+ # super
+ # raise 'select conduct mode' unless mode.key?(:conduct)
+ # end
+
+ # Activate all Guerrillas in the space
+ # Roll 1d6: if result is less than or equal to number of Guerrillas
+ # remove up to 2 Gov pieces (Police first, then Troops, then Base) into casualties
+ # If result is a 1, add 1 underground Guerrillas
+ # Attrition (not Ambush) : for each French piece removed : remove 1 FLN Guerrillas (alternate available / casualties)
+ # -1 Commitment per French Base removed
+ def apply!(board)
+ raise NotImplementedError
+ end
+
+ class << self
+ def op?
+ true
+ end
+
+ # any space with FLN cubes and Gov pieces
+ def applicable?(space)
+ space.guerrillas.positive? && space.gov.positive?
+ end
+
+ # def available_modes(_space)
+ # { conduct: 1 }
+ # end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/colonial_twilight/actions/fln/extort.rb b/lib/colonial_twilight/actions/fln/extort.rb
new file mode 100644
index 0000000..58b4e18
--- /dev/null
+++ b/lib/colonial_twilight/actions/fln/extort.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require_relative 'fln_action'
+
+module ColonialTwilight
+ module Actions
+ module FLN
+ # Extort 4.3.1
+ class Extort < FlnAction
+ def initialize(space, mode)
+ super(space, mode, cost: 0)
+ end
+
+ # flip 1 Underground Guerrilla to Active
+ # add 1 Resources to FLN track per space.
+ def apply!(board)
+ raise NotImplementedError
+ end
+
+ class << self
+ def special?
+ true
+ end
+
+ # any space with Population, Underground Guerrillas, and FLN Control
+ # also Morocco/Tunisia if Independent and have Underground Guerrillas
+ def applicable?(space)
+ space.fln_underground.positive? &&
+ (space.country? ? space.independent? : space.pop.positive? && space.fln_control?)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/colonial_twilight/actions/fln/march.rb b/lib/colonial_twilight/actions/fln/march.rb
new file mode 100644
index 0000000..e738733
--- /dev/null
+++ b/lib/colonial_twilight/actions/fln/march.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require_relative 'fln_action'
+
+module ColonialTwilight
+ module Actions
+ module FLN
+ # March 3.3.2
+ class March < FlnAction
+ def initialize(space, mode)
+ super(space, mode, cost: 1)
+ end
+
+ def cost
+ # Cost is 1 Resource per destination space moved into
+ mode.keys.size
+ end
+
+ def validate!
+ super
+ raise 'select at least 1 destination' if mode.empty?
+
+ total_moved = mode.values.sum
+ raise "total moved #{total_moved} exceeds available #{space.guerrillas}" if total_moved > space.guerrillas
+ end
+
+ # move any number of Guerrillas to adjacent spaces as a group
+ def apply!(board)
+ raise NotImplementedError
+ end
+
+ class << self
+ def op?
+ true
+ end
+
+ # any space with FLN cubes
+ def applicable?(space)
+ space.guerrillas.positive?
+ end
+
+ def available_modes(space)
+ space.adjacents.to_h { |idx| [idx, space.guerrillas] }
+ end
+
+ # the group must stop if moving across a Wilaya border or an International border
+ def must_stop?(space_from, space_to)
+ space_from.wilaya != space_to.wilaya || space_from.country? || space_to.country?
+ end
+
+ # if destination is at Support: activate group if moved FLN + Gov cubes > 3
+ # when crossing International border: activate group if moved FLN + Gov cubes + Border level > 3
+ def must_activate?(board, space_from, space_to, num = 1)
+ international = space_from.country? || space_to.country?
+ (international || space_to.support?) &&
+ (num + space_to.gov_cubes + (international ? board.border_zone_track : 0)) > 3
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/colonial_twilight/actions/fln/oas.rb b/lib/colonial_twilight/actions/fln/oas.rb
new file mode 100644
index 0000000..6d38e0f
--- /dev/null
+++ b/lib/colonial_twilight/actions/fln/oas.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require_relative 'fln_action'
+
+module ColonialTwilight
+ module Actions
+ module FLN
+ # OAS 5.3.1
+ class Oas < FlnAction
+ def initialize(space, mode)
+ super(space, mode, cost: 0)
+ end
+
+ # add 1 Terror, set to Neutral
+ # GOV lose Commitment equal to Population, FLN lose Resources equal to twice Population.
+ def apply!(board)
+ raise NotImplementedError
+ end
+
+ class << self
+ def special?
+ true
+ end
+
+ # 1 populated space with no Terror not Country.
+ def applicable?(space)
+ !space.country? && space.pop.positive? && space.terror.zero?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/colonial_twilight/actions/fln/rally.rb b/lib/colonial_twilight/actions/fln/rally.rb
index c9ed489..bfa7ab2 100644
--- a/lib/colonial_twilight/actions/fln/rally.rb
+++ b/lib/colonial_twilight/actions/fln/rally.rb
@@ -9,7 +9,7 @@ module ColonialTwilight
# Rally 3.3.1
class Rally < FlnAction
def initialize(space, mode)
- super(space, mode)
+ super(space, mode, cost: 1)
@agitate = nil
end
diff --git a/lib/colonial_twilight/actions/fln/subvert.rb b/lib/colonial_twilight/actions/fln/subvert.rb
new file mode 100644
index 0000000..a6ec252
--- /dev/null
+++ b/lib/colonial_twilight/actions/fln/subvert.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require_relative 'fln_action'
+
+module ColonialTwilight
+ module Actions
+ module FLN
+ # Subvert 4.3.2
+ class Subvert < FlnAction
+ def initialize(space, mode)
+ super(space, mode, cost: 0)
+ @second = nil
+ end
+
+ def validate!
+ super
+ return if @second.nil?
+
+ p = (mode[:remove_police] || 0) + (@second.mode[:remove_police] || 0)
+ t = (mode[:remove_troops] || 0) + (@second.mode[:remove_troops] || 0)
+ raise "remove #{p} police + #{t} troops > 2" if p + t > 2
+ end
+
+ def subvert!(space2, mode2)
+ raise 'subvert! called twice' unless @second.nil?
+ raise 'cannot subvert! after replace algerian police' if mode.key?(:replace_police)
+ raise 'cannot subvert! with replace algerian police' if mode2.key?(:replace_police)
+
+ @second = Subvert.new(space2, mode2)
+ validate!
+ end
+
+ # remove 2 Algerian cubes into Available, among selected spaces
+ # or remove 1 Algerian Police and replace it with 1 Underground Guerrilla from Available.
+ def apply!(board)
+ raise NotImplementedError
+ end
+
+ class << self
+ def special?
+ true
+ end
+
+ # spaces with Underground Guerrillas and Algerian cubes.
+ def applicable?(space)
+ space.fln_underground.positive? && space.algerian_cubes.positive?
+ end
+
+ def available_modes(space)
+ modes = {}
+ if space.algerian_police.positive?
+ modes[:replace_police] = 1
+ modes[:remove_police] = space.algerian_police > 1 ? 2 : 1
+ end
+ modes[:remove_troops] = space.algerian_troops > 1 ? 2 : 1 if space.algerian_troops.positive?
+ modes
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/colonial_twilight/actions/fln/terror.rb b/lib/colonial_twilight/actions/fln/terror.rb
new file mode 100644
index 0000000..b812bb4
--- /dev/null
+++ b/lib/colonial_twilight/actions/fln/terror.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require_relative 'fln_action'
+
+module ColonialTwilight
+ module Actions
+ module FLN
+ # Terror 3.3.4
+ class Terror < FlnAction
+ def initialize(space, mode)
+ super(space, mode, cost: 1)
+ end
+
+ # flip 1 Underground Guerrilla to Active.
+ # place 1 Terror marker if none (max 12 on the map), set to Neutral
+ def apply!(board)
+ raise NotImplementedError
+ end
+
+ class << self
+ def op?
+ true
+ end
+
+ # Populated not Resettled space with Underground Guerrillas.
+ def applicable?(space)
+ !space.country? && space.pop.positive? && space.fln_underground.positive? # && !space.resettled?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/fln_actions_spec.rb b/spec/fln_actions_spec.rb
new file mode 100644
index 0000000..5301e72
--- /dev/null
+++ b/spec/fln_actions_spec.rb
@@ -0,0 +1,297 @@
+# frozen_string_literal: true
+
+require './lib/colonial_twilight/actions/fln/rally'
+require './lib/colonial_twilight/actions/fln/agitate'
+require './lib/colonial_twilight/actions/fln/attack'
+require './lib/colonial_twilight/actions/fln/march'
+require './lib/colonial_twilight/actions/fln/terror'
+require './lib/colonial_twilight/actions/fln/extort'
+require './lib/colonial_twilight/actions/fln/subvert'
+require './lib/colonial_twilight/actions/fln/ambush'
+require './lib/colonial_twilight/actions/fln/oas'
+require './lib/colonial_twilight/board'
+require './spec/mock_board'
+
+describe ColonialTwilight::Actions::FLN do
+ before do
+ @board = ColonialTwilight::Board.new
+ end
+
+ describe 'Rally' do
+ let(:action_class) { ColonialTwilight::Actions::FLN::Rally }
+
+ it 'collects spaces where operation can be conducted' do
+ # all but countries
+ expect(action_class.possible_spaces(@board).size).to eq(28)
+ end
+
+ it 'collects spaces where operation can be conducted' do
+ @board.load :short
+ # 25 sectors + 2 countries
+ expect(action_class.possible_spaces(@board).size).to eq(27)
+ end
+
+ it 'applicable? France track' do
+ t = Track.new(5, 'France track')
+ expect(action_class.applicable?(t)).to be true
+ t = Track.new(5, 'France ')
+ expect(action_class.applicable?(t)).to be false
+ end
+
+ it 'applicable? sector' do
+ a = Sector.new
+ expect(action_class.applicable?(a)).to be true
+ end
+
+ it 'applicable? in city not at support' do
+ a = Sector.new({ name: 'city', support: false })
+ expect(action_class.applicable?(a)).to be true
+ end
+
+ it 'not applicable? in city at support' do
+ a = Sector.new({ name: 'city', support: true })
+ expect(action_class.applicable?(a)).to be false
+ end
+
+ it 'applicable? in independent country' do
+ a = Sector.new({ name: 'country', independent: true })
+ expect(action_class.applicable?(a)).to be true
+ end
+
+ it 'not applicable? not in not independent country' do
+ a = Sector.new({ name: 'country', independent: false })
+ expect(action_class.applicable?(a)).to be false
+ end
+
+ it 'may place 1 guerrillas' do
+ a = Sector.new({ pop: 3 })
+ modes = action_class.available_modes(a)
+ expect(modes.keys.size).to eq(1)
+ expect(modes[:place_guerilla]).to eq(1)
+ end
+
+ it 'may place pop + base guerrillas' do
+ a = Sector.new({ pop: 3, fln_bases: 2 })
+ modes = action_class.available_modes(a)
+ expect(modes.keys.size).to eq(1)
+ expect(modes[:place_guerilla]).to eq(5)
+ end
+
+ it 'may flip underground guerillas' do
+ a = Sector.new({ fln_active: 3, fln_underground: 2, fln_bases: 1 })
+ modes = action_class.available_modes(a)
+ expect(modes.keys.size).to eq(3)
+ expect(modes[:place_base]).to eq(1)
+ expect(modes[:place_guerilla]).to eq(1)
+ expect(modes[:underground]).to eq(3)
+ end
+
+ it 'may create action within available modes' do
+ a = Sector.new({ fln_active: 3, fln_underground: 2, fln_bases: 1 })
+ expect { action_class.new(a, { underground: 3 }) }.not_to raise_error
+ expect(action_class.new(a, { underground: 3 }).cost).to eq(1)
+ end
+
+ it 'may not create action within a not applicable space' do
+ a = Sector.new({ name: 'city', support: true })
+ expect { action_class.new(a, {}) }.to raise_error(/not applicable/)
+ end
+
+ it 'may not create action within higher value' do
+ a = Sector.new({ fln_active: 3, fln_underground: 2, fln_bases: 1 })
+ expect { action_class.new(a, { underground: 4 }) }.to raise_error(/value:/)
+ end
+
+ it 'may create action within available modes' do
+ a = Sector.new({ fln_active: 3, fln_underground: 2, fln_bases: 1 })
+ expect { action_class.new(a, { wrong: 3 }) }.to raise_error(/mode:/)
+ end
+
+ it 'may agitate and compute cost' do
+ a = Sector.new({ fln_active: 3, fln_underground: 2, fln_bases: 1, terror: 2 })
+ expect { action_class.new(a, { underground: 3 }).agitate!({ remove_terror: 1 }) }.not_to raise_error
+ expect(action_class.new(a, { underground: 3 }).agitate!({ remove_terror: 1 }).cost).to eq(2)
+ expect(action_class.new(a, { underground: 3 }).agitate!({ remove_terror: 2 }).cost).to eq(3)
+ end
+
+ it 'may not agitate if not applicable' do
+ a = Sector.new({ fln_active: 3, fln_underground: 2, fln_bases: 1, oppose: true })
+ expect { action_class.new(a, { underground: 3 }).agitate!({ remove_terror: 1 }) }.to raise_error(/not applicable/)
+ end
+
+ it 'may not agitate twice' do
+ a = Sector.new({ fln_active: 3, fln_underground: 2, fln_bases: 1, terror: 1 })
+ expect { action_class.new(a, { underground: 3 }).agitate!({ remove_terror: 1 }).agitate!({ remove_terror: 1 }) }.to raise_error(/agitate! called/)
+ end
+ end
+
+ describe 'Attack' do
+ let(:action_class) { ColonialTwilight::Actions::FLN::Attack }
+
+ it 'is applicable where FLN and GOV are present' do
+ a = Sector.new(fln_active: 1, gov_cubes: 1)
+ expect(action_class.applicable?(a)).to be true
+ end
+
+ it 'is not applicable without FLN' do
+ a = Sector.new(fln_active: 0, gov_cubes: 1)
+ expect(action_class.applicable?(a)).to be false
+ end
+
+ it 'is not applicable without GOV' do
+ a = Sector.new(fln_active: 1, gov_cubes: 0)
+ expect(action_class.applicable?(a)).to be false
+ end
+
+ it 'has no modes' do
+ a = Sector.new(fln_active: 1, gov_cubes: 1)
+ expect(action_class.available_modes(a).empty?).to be true
+ end
+
+ it 'can have an ambush' do
+ a = Sector.new(fln_active: 1, fln_underground: 1, gov_cubes: 1)
+ action = action_class.new(a)
+ expect { action.ambush! }.not_to raise_error
+ end
+ end
+
+ describe 'March' do
+ let(:action_class) { ColonialTwilight::Actions::FLN::March }
+
+ it 'is applicable where FLN guerrillas are present' do
+ a = Sector.new(fln_active: 1)
+ expect(action_class.applicable?(a)).to be true
+ end
+
+ it 'is not applicable without FLN guerrillas' do
+ a = Sector.new(fln_active: 0)
+ expect(action_class.applicable?(a)).to be false
+ end
+
+ it 'calculates cost based on destinations' do
+ a = Sector.new(fln_active: 5)
+ allow(a).to receive(:adjacents).and_return([1, 2, 3])
+ action = action_class.new(a, { 1 => 2, 2 => 3 })
+ expect(action.cost).to eq(2)
+ end
+ end
+
+ describe 'Terror' do
+ let(:action_class) { ColonialTwilight::Actions::FLN::Terror }
+
+ it 'is applicable in populated space with underground FLN' do
+ a = Sector.new(pop: 1, fln_underground: 1)
+ expect(action_class.applicable?(a)).to be true
+ end
+
+ it 'is not applicable in unpopulated space' do
+ a = Sector.new(pop: 0, fln_underground: 1)
+ expect(action_class.applicable?(a)).to be false
+ end
+
+ it 'is not applicable without underground FLN' do
+ a = Sector.new(pop: 1, fln_underground: 0)
+ expect(action_class.applicable?(a)).to be false
+ end
+
+ it 'is not applicable in countries' do
+ a = Sector.new(name: 'country', pop: 1, fln_underground: 1)
+ expect(action_class.applicable?(a)).to be false
+ end
+
+ it 'has no mode' do
+ a = Sector.new(pop: 1, fln_underground: 1)
+ expect(action_class.available_modes(a).empty?).to be true
+ end
+ end
+
+ describe 'Extort' do
+ let(:action_class) { ColonialTwilight::Actions::FLN::Extort }
+
+ it 'is applicable in populated space with underground FLN and FLN control' do
+ a = Sector.new(pop: 1, fln_underground: 1, fln_active: 2, gov_cubes: 0)
+ expect(action_class.applicable?(a)).to be true
+ end
+
+ it 'is applicable in independent countries with underground FLN' do
+ a = Sector.new(name: 'country', independent: true, fln_underground: 1)
+ expect(action_class.applicable?(a)).to be true
+ end
+
+ it 'is not applicable without FLN control in sector' do
+ a = Sector.new(pop: 1, fln_underground: 1, fln_active: 0, gov_cubes: 2)
+ expect(action_class.applicable?(a)).to be false
+ end
+ end
+
+ describe 'Subvert' do
+ let(:action_class) { ColonialTwilight::Actions::FLN::Subvert }
+
+ it 'is applicable with underground FLN and Algerian cubes' do
+ a = Sector.new(fln_underground: 1, algerian_police: 1)
+ expect(action_class.applicable?(a)).to be true
+ end
+
+ it 'provides replace_police and remove_police modes' do
+ a = Sector.new(fln_underground: 1, algerian_police: 2)
+ modes = action_class.available_modes(a)
+ expect(modes[:replace_police]).to eq(1)
+ expect(modes[:remove_police]).to eq(2)
+ expect(modes[:remove_troops]).to be_nil
+ end
+
+ it 'provides replace_police and remove_troops modes' do
+ a = Sector.new(fln_underground: 1, algerian_troops: 2)
+ modes = action_class.available_modes(a)
+ expect(modes[:replace_police]).to be_nil
+ expect(modes[:remove_police]).to be_nil
+ expect(modes[:remove_troops]).to eq(2)
+ end
+
+ it 'may chain subvert in 2 spaces' do
+ a = Sector.new(fln_underground: 1, algerian_police: 2)
+ b = Sector.new(fln_underground: 1, algerian_troops: 1)
+ act = action_class.new(a, { remove_police: 1 })
+ expect { act.subvert!(b, { remove_troops: 1 }) }.to_not raise_error
+ expect { act.subvert!(b, { remove_troops: 2 }) }.to raise_error(/subvert! called/)
+ act = action_class.new(a, { remove_police: 1 })
+ b = Sector.new(fln_underground: 1, algerian_police: 2)
+ expect { act.subvert!(b, { remove_police: 2 }) }.to raise_error(/remove 3 police/)
+ b = Sector.new(fln_underground: 1, algerian_troops: 2)
+ act = action_class.new(a, { remove_police: 1 })
+ expect { act.subvert!(b, { remove_troops: 2 }) }.to raise_error(/remove 1 police/)
+ end
+
+ it 'cannot chain after replace_police' do
+ a = Sector.new(fln_underground: 1, algerian_police: 2)
+ b = Sector.new(fln_underground: 1, algerian_troops: 1)
+ act = action_class.new(a, { replace_police: 1 })
+ expect { act.subvert!(b, {}) }.to raise_error(/cannot subvert! after/)
+ act = action_class.new(a, { remove_police: 1 })
+ expect { act.subvert!(b, { replace_police: 1 }) }.to raise_error(/cannot subvert! with/)
+ end
+ end
+
+ describe 'Ambush' do
+ let(:action_class) { ColonialTwilight::Actions::FLN::Ambush }
+
+ it 'is applicable with underground FLN and GOV present' do
+ a = Sector.new(fln_underground: 1, gov_cubes: 1)
+ expect(action_class.applicable?(a)).to be true
+ end
+ end
+
+ describe 'OAS' do
+ let(:action_class) { ColonialTwilight::Actions::FLN::Oas }
+
+ it 'is applicable in populated space with no terror and not a country' do
+ a = Sector.new(pop: 1, terror: 0)
+ expect(action_class.applicable?(a)).to be true
+ end
+
+ it 'is not applicable if terror present' do
+ a = Sector.new(pop: 1, terror: 1)
+ expect(action_class.applicable?(a)).to be false
+ end
+ end
+end