diff options
| author | Jérémy Zurcher <jeremy@asynk.ch> | 2026-03-11 15:16:47 +0100 |
|---|---|---|
| committer | Jérémy Zurcher <jeremy@asynk.ch> | 2026-03-11 15:16:47 +0100 |
| commit | 9458b6413e3609e12f563dcb321d493b5f317017 (patch) | |
| tree | 0e8bb866639541ee1e5b9c669b964054d9da8aa6 /lib/colonial_twilight/fln_bot/fln_rally.rb | |
| parent | 96eba00d76af7fe662bc7f26d8962ada3bfa15b2 (diff) | |
| download | colonial-twilight-9458b6413e3609e12f563dcb321d493b5f317017.zip colonial-twilight-9458b6413e3609e12f563dcb321d493b5f317017.tar.gz | |
update FlnBot infrastructure
Diffstat (limited to 'lib/colonial_twilight/fln_bot/fln_rally.rb')
| -rw-r--r-- | lib/colonial_twilight/fln_bot/fln_rally.rb | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/lib/colonial_twilight/fln_bot/fln_rally.rb b/lib/colonial_twilight/fln_bot/fln_rally.rb new file mode 100644 index 0000000..0e91534 --- /dev/null +++ b/lib/colonial_twilight/fln_bot/fln_rally.rb @@ -0,0 +1,190 @@ +# frozen_string_literal: true + +module ColonialTwilight + module FLNBotRally + def rally + # return false if !available_resources.positive? && !extort + + @reserved_to_agitate = 0 + # max 6 spaces + max_selected = (limited_op_only? ? 1 : 6) + # max 2/3 resources unless starts with < 9 resources + max_resources = (@board.fln_resources < 9 ? 0 : @board.fln_resources * 2 / 3) + max_cost = -> { max_resources.zero? ? 0 : max_resources - @turn.cost } + + stop_cond = if max_resources.zero? + -> { @turn.selected_spaces >= max_selected } + else + -> { @turn.selected_spaces >= max_selected || (@turn.cost + @reserved_to_agitate) >= max_resources } + end + stop_cond_base = -> { !available_fln_bases? || stop_cond.call } + + loop do + break unless _place_base_in(_rally(1, stop_cond_base, ->(s) { may_rally_1_in?(s) })) + end + + loop do + break unless _place_base_in(_rally(2, stop_cond_base, ->(s) { may_rally_2_in?(s) })) + end + + loop do + break unless _place_fln_in(_rally(3, stop_cond, ->(s) { may_rally_3_in?(s) }, priority: 3)) + end + + _shift_france_track unless stop_cond.call + + loop do + break unless _place_fln_in(_rally(5, stop_cond, ->(s) { may_rally_5_in?(s) }, priority: 5)) + end + + unless stop_cond.call + printd(' rally 6') + filter = ->(s) { may_rally_6_in?(s, @turn.operation_selected?(s)) } + space = _rally_one_space(filter, priority: 6, reselect: true) + if _reserve_to_agitate_in?(space, max_cost.call) + agitate_in = space + _place_fln_in(space, to_agitate_in: space) unless @turn.operation_selected?(space) + end + end + + 2.times do + break unless _place_fln_in(_rally(7, stop_cond, ->(s) { may_rally_7_in?(s) }, priority: 7)) + end + + 2.times do + break unless _place_fln_in(_rally(8, stop_cond, ->(s) { may_rally_8_in?(s) }, priority: 8)) + end + + if agitate_in.nil? + printd ' rally 9' + filter = ->(s) { may_rally_9_in?(s) && (@turn.operation_selected?(s) || @turn.selected_spaces < max_selected) } + spaces = rally_9_priority(@board.search(&filter), max_cost.call) { |s| @turn.operation_selected?(s) }.shuffle + while (space = spaces.pop) + if @turn.operation_selected?(space) + agitate_in = space + elsif _reserve_to_agitate_in?(space, max_cost.call) && _place_fln_in(space, to_agitate_in: space) + agitate_in = space + end + break unless agitate_in.nil? + end + end + _agitate_in(agitate_in, max_cost.call) + + @turn.operation_done? + end + + def _rally(num, stop_cond, filter, priority: nil, reselect: false) + return nil if stop_cond.call + + printd(" rally #{num}") + return nil if (space = _rally_one_space(filter, priority: priority, reselect: reselect)).nil? + + printd(" -> #{space.name}") + extort unless available_resources.positive? + + available_resources.positive? ? space : nil + end + + def _rally_one_space(filter, priority: nil, reselect: false) + spaces = @board.search(&filter) + spaces = spaces.reject(&@turn.method('operation_selected?')) unless reselect + spaces = _place_priority(spaces, priority) unless priority.nil? + spaces.sample + end + + def _place_priority(spaces, priority) + return spaces if spaces.size < 2 + + spaces = case priority + when 3 then rally_3_priority(spaces) + when 5 then rally_5_priority(spaces) + when 6 then rally_6_priority(spaces) + when 7 then rally_7_priority(spaces) + else spaces + end + place_guerrillas_priority(spaces) + end + + def _place_base_in(space) + return false if space.nil? + + printd " => _place_base_in : #{space.name}" + a, u = (n = space.fln_active) >= 2 ? [2, 0] : [n, 2 - n] + apply_action @turn.operation_in(:rally, space, 1) + .transfer_to(:available, :fln_active, a) + .transfer_to(:available, :fln_underground, u) + .transfer_from(:available, :fln_base) + end + + def _place_fln_in(space, to_agitate_in: nil) + return false if space.nil? + + printd " => _place_fln_in : #{space.name}" + return false if (steps = place_guerrillas_in(space)).empty? + + apply_action @turn.operation_in(:rally, space, 1, to_agitate_in: to_agitate_in).transfer_steps(steps) + end + + def _shift_france_track + printd(' rally 4') + return false if @board.france_track.zero? + + extort unless available_resources.positive? + apply_action @turn.operation_in(:rally, :france_track, 1).shift(1) + end + + def _agitate_in(space, max_cost) + return if space.nil? + + printd " => _agitate_in : #{space.name}" + terror = space.terror + oppose = space.oppose? ? 0 : 1 + if @reserved_to_agitate.positive? + terror = terror > @reserved_to_agitate ? @reserved_to_agitate : terror + oppose = 0 if terror == @reserved_to_agitate + return apply_action @turn.agitate_in(space, terror, oppose) + end + + if max_cost.positive? && (cost = (terror + oppose)) > max_cost + terror -= (cost - oppose - max_cost) + oppose = 0 + end + return if terror.zero? + + if (cost = terror + oppose) < available_resources + return apply_action @turn.agitate_in(space, terror, oppose) + end + + max_rcs = available_resources + extortable.size + if cost > max_rcs + terror -= (cost - oppose - max_rcs) + oppose = 0 + end + return if terror.zero? + + ((terror + oppose) - available_resources).times { extort(to_agitate_in: space) } + apply_action @turn.agitate_in(space, terror, oppose) + end + + def _reserve_to_agitate_in?(space, max_cost) + return false if space.nil? + + printd " => _reserve_to_agitate_in : #{space.name}" + cost = (rally_cost = (@turn.operation_selected?(space) ? 0 : 1)) + (agitate_cost = max_agitate_cost(space)) + agitate_cost -= (cost - max_cost) if max_cost.positive? && cost > max_cost + return false unless agitate_cost.positive? + + if (cost = (rally_cost + agitate_cost)) < available_resources + @reserved_to_agitate = agitate_cost + return true + end + max_rcs = available_resources + extortable.size + agitate_cost -= (cost - max_rcs) if cost > max_rcs + return false unless agitate_cost.positive? + + ((rally_cost + agitate_cost) - available_resources).times { extort(to_agitate_in: space) } + @reserved_to_agitate = agitate_cost + true + end + end +end |
