summaryrefslogtreecommitdiffstats
path: root/lib/colonial_twilight/fln_bot_rules.rb
blob: e9b9e795566dba78ec9161b4af271c0d266f0a18 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#! /usr/bin/env ruby
# frozen_string_literal: true

module ColonialTwilight
  module FLNBotRules
    def dbg(msg, ret)
      return if @debug.zero?

      case @debug
      when 1 then puts "  #{msg} : YES" if ret
      else puts "  #{msg} : #{ret ? 'YES' : 'NO'}"
      end
    end

    def pass?
      # if resources = 0 && Op Limited as only choice
      r = @board.fln_resources.zero? && limited_op_only?
      dbg 'PASS', r
      r
    end

    def terror1?
      # unless any FLN base is (pop 0 && 0 FLN underground or pop 1+ && 1- FLN underground)
      r = !@board.has do |s|
        s.fln_bases.positive? &&
          ((s.pop.zero? && s.fln_underground.zero?) || (!s.pop.zero? && s.fln_underground < 2))
      end
      dbg 'TERROR 1', r
      r
    end

    def terror2?
      # if GOV is first eligible && will be second eligible
      r = !first_eligible? && will_be_next_first_eligible?
      dbg 'TERROR 2', r
      r
    end

    def rally1?
      # rally would place bases (first 2 bullets)
      # 4+ or (3+ FLN and no GOV (unless limited_op_only))
      r = @board.available_fln_bases.positive? && @board.has do |s|
        may_add_base_in?(s) && (rally_2_in?(s) || rally_1_in?(s))
      end
      dbg 'RALLY 1', r
      r
    end

    def rally2?
      # if #FLN bases * 2 > #FLN at FLN bases + 1d6/2
      a = @board.count(&:fln_bases) * 2
      b = @board.count { |s| s.fln_bases.zero? ? 0 : s.fln_cubes }
      r = a > (b + d6 / 2)
      dbg 'RALLY 2', r
      r
    end

    # Rally

    def may_add_base_in?(space)
      r = space.fln_cubes > 2 && (space.fln_bases < (space.country? ? space.max_bases : 1))
      dbg "  may_add_base : #{space.name}", r
      r
    end

    def rally_1_in?(space)
      # 3+ FLN and no GOV (unless limited_op_only))
      r = space.fln_cubes >= 3 && (limited_op_only? ? true : space.gov_cubes.zero?)
      dbg "  rally_1_in : #{space.name}", r
      r
    end

    def rally_2_in?(space)
      # 4+ FLN
      r = space.fln_cubes >= 4
      dbg "  rally_2_in : #{space.name}", r
      r
    end

    def rally_3_in?(space)
      # at FLN bases, with 2- FLN underground or 0 fln_undergroud in country or 0 pop
      r = !space.fln_bases.zero? &&
          (space.country? || space.pop.zero? ? space.fln_underground.zero? : space.fln_underground < 2)
      dbg "  rally_3_in : #{space.name}", r
      r
    end

    def rally_3_priority(spaces)
      # Algeria -> with cubes -> pop 1+ -> least FLN underground
      l0 = (l0 = spaces.reject(&:country?)).empty? ? spaces : l0
      l1 = (l1 = l0.select { |s| s.gov_cubes.positive? }).empty? ? l0 : l1
      l2 = (l2 = l1.select { |s| s.pop.positive? }).empty? ? l1 : l2
      l2.min { |a, b| a.fln_underground <=> b.fln_underground }
    end

    def rally_5_in?(space)
      # non-city at support with 0 FLN underground
      r = !space.city? && space.support? && space.fln_underground.zero?
      dbg "  rally_5_in : #{space.name}", r
      r
    end

    def rally_6_in?(space)
      # 2+ pop to agitate
      r = space.pop > 1 && may_agitate_in?(space)
      dbg "  rally_6_in : #{space.name}", r
      r
    end

    def rally_8_in?(space)
      r = !space.fln_cubes.zero? && space.fln_bases.zero?
      dbg "  rally_8_in : #{space.name}", r
      r
    end

    # 8.1.2 - Procedure Guidelines
    def fln_to_place?
      @board.available_fln_underground.positive? || !place_from(@board.spaces).nil?
    end

    # 1) place: outofplay -> available | bases -> cubes if choice
    # 2) place: underground first unless from map then place active first flipped as underground
    # 3) march: underground -> active, unless march would activate then move active first

    def place_in(spaces)
      # 4) support -> with friendly pieces -> random
      l0 = (l0 = spaces.select(&:support?)).empty? ? spaces : l0
      l1 = (l1 = l0.select { |s| s.fln_cubes.positive? }).empty? ? l0 : l1
      (l1.empty? ? @board.spaces : l1).sample
    end

    def place_from(spaces)
      # 5) active only, leave 2 cubes at base or support, most cubes first
      l = spaces.select do |s|
        s.fln_active.positive? && (s.support? || s.fln_bases.positive? ? s.fln_cubes > 2 : true)
      end
      return nil if l.empty?

      v = l.max { |a, b| a.fln_cubes <=> b.fln_cubes }.fln_cubes
      l.select { |s| s.fln_cubes == v }.sample
    end

    def remove_from(space, num = 1)
      # 6) remove active -> underground -> base
      h = {}
      num -= h[:fln_active] = (s = space.fln_active) >= num ? num : s
      num -= h[:fln_underground] = (s = space.fln_underground) >= num ? num : s
      h[:fln_bases] = (s = space.fln_bases) >= num ? num : s
      h
    end

    def remove_gov(num = 1)
      # 7) map -> availabe (base -> french -> algerian; troops -> police)
    end

      # 8) reduce : commitment -> support -> france track -> gov resource
      # 9) shift : support -> oppose | best combined; remove terror only if also shift
      # 10) random
  end
end