From a8be09ab04e4c29e6ce032dffcde0c134cc74382 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Zurcher?= <jeremy@asynk.ch>
Date: Mon, 13 Nov 2023 10:14:49 +0100
Subject: FLNBotRules : add attack && ambush && specs

---
 lib/colonial_twilight/fln_bot_rules.rb | 31 +++++++++++
 lib/colonial_twilight/fln_rules.rb     |  2 +-
 spec/fln_bot_rules_spec.rb             | 97 ++++++++++++++++++++++++++++++++++
 3 files changed, 129 insertions(+), 1 deletion(-)

diff --git a/lib/colonial_twilight/fln_bot_rules.rb b/lib/colonial_twilight/fln_bot_rules.rb
index 818f0bf..8f0f37a 100644
--- a/lib/colonial_twilight/fln_bot_rules.rb
+++ b/lib/colonial_twilight/fln_bot_rules.rb
@@ -218,6 +218,37 @@ module ColonialTwilight
         (de_gaule && space.sector? && space.troops.positive? && space.police.positive? && space.gov_control?)
     end
 
+    # Attack
+
+    def may_attack_1_in?(space)
+      # attack will remove 1+ GOV piece, do not expose a base
+      r = may_attack_in?(space) && space.guerrillas > 5 && space.fln_bases.zero?
+      dbg "  may_attack_1_in : #{space.name}", r
+      r
+    end
+
+    def may_ambush_1_in?(space)
+      # do not expose a base
+      r = may_ambush_in?(space) && (space.fln_bases.zero? ? true : space.guerrillas > 1)
+      dbg "  may_attack_1_in : #{space.name}", r
+      r
+    end
+
+    def may_attack_2_in?(space)
+      # 4+ guerrillas, do not expose a base
+      r = may_attack_in?(space) && space.guerrillas > 3 && space.fln_bases.zero?
+      dbg "  may_attack_2_in : #{space.name}", r
+      r
+    end
+
+    def attack_priority(spaces)
+      # GOV bases -> French Troops -> French Police -> most pieces
+      f = _filter(spaces) { |s| s.gov_bases.positive? }
+      f = _filter(f) { |s| s.french_troops.positive? }
+      f = _filter(f) { |s| s.french_police.positive? }
+      _max(f, :gov)
+    end
+
     # 8.1.2 - Procedure Guidelines
 
     def _filter(spaces, &block)
diff --git a/lib/colonial_twilight/fln_rules.rb b/lib/colonial_twilight/fln_rules.rb
index 9e3bddd..f8e6c40 100644
--- a/lib/colonial_twilight/fln_rules.rb
+++ b/lib/colonial_twilight/fln_rules.rb
@@ -68,7 +68,7 @@ module ColonialTwilight
 
     # Ambush 4.3.3
     def may_ambush_in?(space)
-      may_attack_in?(space)
+      may_attack_in?(space) && space.fln_underground.positive?
     end
 
     def ambush_spaces(board)
diff --git a/spec/fln_bot_rules_spec.rb b/spec/fln_bot_rules_spec.rb
index 25ded7c..6140f68 100644
--- a/spec/fln_bot_rules_spec.rb
+++ b/spec/fln_bot_rules_spec.rb
@@ -652,6 +652,103 @@ describe ColonialTwilight::FLNBotRules do
     end
   end
 
+  describe 'Attack' do
+    it 'may attack 1' do
+      a = Sector.new(gov_cubes: 1, fln_underground: 2, fln_active: 4)
+      expect(@rules.may_attack_1_in?(a)).to be true
+    end
+
+    it 'may attack 1 but no gov' do
+      a = Sector.new(fln_underground: 2, fln_active: 4)
+      expect(@rules.may_attack_1_in?(a)).to be false
+    end
+
+    it 'may attack 1 but not enough fln' do
+      a = Sector.new(gov_cubes: 1, fln_underground: 2, fln_active: 3)
+      expect(@rules.may_attack_1_in?(a)).to be false
+    end
+
+    it 'may attack 1 but fln base' do
+      a = Sector.new(gov_cubes: 1, fln_underground: 2, fln_active: 4, fln_bases: 1)
+      expect(@rules.may_attack_1_in?(a)).to be false
+    end
+
+    it 'may attack 2' do
+      a = Sector.new(gov_cubes: 1, fln_underground: 2, fln_active: 2)
+      expect(@rules.may_attack_2_in?(a)).to be true
+    end
+
+    it 'may attack 2 but no gov' do
+      a = Sector.new(fln_underground: 2, fln_active: 2)
+      expect(@rules.may_attack_2_in?(a)).to be false
+    end
+
+    it 'may attack 2 but not enough fln' do
+      a = Sector.new(gov_cubes: 1, fln_underground: 2, fln_active: 1)
+      expect(@rules.may_attack_2_in?(a)).to be false
+    end
+
+    it 'may attack 2 but fln base' do
+      a = Sector.new(gov_cubes: 1, fln_underground: 2, fln_active: 2, fln_bases: 1)
+      expect(@rules.may_attack_2_in?(a)).to be false
+    end
+
+    it 'may ambush 1 in' do
+      a = Sector.new(gov_cubes: 1, fln_underground: 1)
+      expect(@rules.may_ambush_1_in?(a)).to be true
+    end
+
+    it 'may ambush 1 in but no underground' do
+      a = Sector.new(gov_cubes: 1, fln_active: 1)
+      expect(@rules.may_ambush_1_in?(a)).to be false
+    end
+
+    it 'may ambush 1 in but bases' do
+      a = Sector.new(gov_cubes: 1, fln_underground: 1, fln_bases: 1)
+      expect(@rules.may_ambush_1_in?(a)).to be false
+    end
+
+    it 'may ambush 1 in bases and enough guerillas' do
+      a = Sector.new(gov_cubes: 1, fln_underground: 1, fln_active: 1, fln_bases: 1)
+      expect(@rules.may_ambush_1_in?(a)).to be true
+    end
+
+    it 'attack priority at bases' do
+      a = Sector.new(gov_bases: 1)
+      b = Sector.new(gov_bases: 2)
+      c = Sector.new(gov_bases: 1)
+      expect(@rules.attack_priority([a, b, c])[0]).to be b
+    end
+
+    it 'attack priority french troops' do
+      a = Sector.new(gov_bases: 2)
+      b = Sector.new(gov_bases: 2, french_troops: 2)
+      c = Sector.new(gov_bases: 2, french_troops: 1)
+      expect(@rules.attack_priority([a, b, c])[0]).to be b
+    end
+
+    it 'attack priority french police' do
+      a = Sector.new(gov_bases: 2, french_troops: 2)
+      b = Sector.new(gov_bases: 2, french_troops: 2, french_police: 2)
+      c = Sector.new(gov_bases: 2, french_troops: 2, french_police: 1)
+      expect(@rules.attack_priority([a, b, c])[0]).to be b
+    end
+
+    it 'attack priority gov most pieces, gov base' do
+      a = Sector.new(gov_bases: 2, french_troops: 2, french_police: 2)
+      b = Sector.new(gov_bases: 3, french_troops: 2, french_police: 2)
+      c = Sector.new(gov_bases: 2, french_troops: 2, french_police: 2)
+      expect(@rules.attack_priority([a, b, c])[0]).to be b
+    end
+
+    it 'attack priority gov most pieces, french troops' do
+      a = Sector.new(gov_bases: 2, french_troops: 2, french_police: 2)
+      b = Sector.new(gov_bases: 2, french_troops: 3, french_police: 2)
+      c = Sector.new(gov_bases: 2, french_troops: 2, french_police: 2)
+      expect(@rules.attack_priority([a, b, c])[0]).to be b
+    end
+  end
+
   describe '8.1.2 Procedure Guidelines' do
     it 'placeable_guerrillas?' do
       expect(@rules.placeable_guerrillas?).to be false
-- 
cgit v1.1-2-g2b99