diff options
Diffstat (limited to 'lib/colonial_twilight/fln_bot.rb')
| -rw-r--r-- | lib/colonial_twilight/fln_bot.rb | 375 |
1 files changed, 20 insertions, 355 deletions
diff --git a/lib/colonial_twilight/fln_bot.rb b/lib/colonial_twilight/fln_bot.rb index 1db4564..4e4cbf2 100644 --- a/lib/colonial_twilight/fln_bot.rb +++ b/lib/colonial_twilight/fln_bot.rb @@ -1,20 +1,36 @@ # frozen_string_literal: true -require 'colonial_twilight/player' -require 'colonial_twilight/fln_rules' -require 'colonial_twilight/fln_bot_rules' +require_relative 'player' +require_relative 'fln_bot/fln_rules' +require_relative 'fln_bot/fln_bot_rules' + +require_relative 'fln_bot/fln_attack' +require_relative 'fln_bot/fln_extort' +require_relative 'fln_bot/fln_march' +require_relative 'fln_bot/fln_pass' +require_relative 'fln_bot/fln_rally' +require_relative 'fln_bot/fln_subvert' +require_relative 'fln_bot/fln_terror' module ColonialTwilight class FLNBot < Player include ColonialTwilight::FLNRules include ColonialTwilight::FLNBotRules - include ColonialTwilight::FLNRalyRules + include ColonialTwilight::FLNRallyRules include ColonialTwilight::FLNExtortRules include ColonialTwilight::FLNSubvertRules include ColonialTwilight::FLNTerrorRules include ColonialTwilight::FLNAttackRules include ColonialTwilight::FLNGuidelines + include ColonialTwilight::FLNBotAttack + include ColonialTwilight::FLNBotExtort + include ColonialTwilight::FLNBotMarch + include ColonialTwilight::FLNBotPass + include ColonialTwilight::FLNBotRally + include ColonialTwilight::FLNBotSubvert + include ColonialTwilight::FLNBotTerror + def play_turn(prev_action, possible_actions) init_turn prev_action, possible_actions _start_turn @@ -50,356 +66,5 @@ module ColonialTwilight # FIXME: the next Propaganda Card will be the last one of the game true end - - # PASS ##################################################################### - - def pass - apply_action @turn.pass(1) - end - - # EXTORT ################################################################### - - def extort(except: nil, to_agitate_in: nil) - return false if available_resources > 4 - return false unless @turn.may_special_activity?(:extort) - return false if (space = extort_priority(extortable(except: except)).sample).nil? - - apply_action @turn.special_activity_in(:extort, space, -1, to_agitate_in: to_agitate_in).extort - end - - def extortable(except: nil) - @board.search { |s| may_extort_0_in?(s) }.reject { |s| @turn.special_activity_selected?(s) || s == except } - end - - # TERROR ################################################################### - - def terror - # return false if !available_resources.positive? && !extort - return false if event_playable? && event_more_effective_than_terror? - - until (space = terror_1_priority(@board.search { |s| may_terror_1_in?(s) }).sample).nil? - exc = space.fln_underground == 1 ? space : nil - break if !available_resources.positive? && !extort(except: exc) - - apply_action @turn.operation_in(:terror, space, 1).terror - end - - if last_campaign? - until (space = @board.search { |s| may_terror_2_in?(s) }.sample).nil? - exc = space.fln_underground == 1 ? space : nil - break if !available_resources.positive? && !extort(except: exc) - - apply_action @turn.operation_in(:terror, space, 1).terror - end - end - - @turn.operation_done? - end - - # ATTACK ################################################################### - - def attack - # return false if !available_resources.positive? && !extort - - n = 2 - ambush_cond = ->(s) { n.positive? && @turn.may_special_activity?(:ambush) && may_ambush_1_in?(s) } - cond = ->(s) { may_attack_1_in?(s) || ambush_cond.call(s) } - until (space = attack_priority(@board.search(&cond)).sample).nil? - break if !available_resources.positive? && !extort - - _apply_attack(space, ambush_cond.call(space)) - n -= 1 - end - - until (space = attack_priority(@board.search { |s| may_attack_2_in?(s) }).sample).nil? - break if !available_resources.positive? && !extort - - _apply_attack(space, ambush_cond.call(space)) - n -= 1 - end - - @turn.operation_done? - end - - def _apply_attack(space, ambush) - apply_action ambush ? _ambush(space) : _attack(space) - end - - def _ambush(space) - action = @turn.special_activity_in(:ambush, space, 1).activate(1) - casualties = _casualties(space, action, 1) - _attrition(action, casualties) - end - - def _attack(space) - action = @turn.operation_in(:attack, space, 1).activate(space.fln_underground) - return action if (d = d6) > space.guerrillas - - casualties = _casualties(space, action, 2) - _attrition(action, casualties) - action.transfer_from(place_from, :fln_underground) if d == 1 - action - end - - def _casualties(space, action, casualties) - num = 0 - CASUALTIES_PRIORITY.each do |sym| - next unless (n = space.send(sym)).positive? - - casualties -= (n = (n > casualties ? casualties : n)) - num += n - action.transfer_to(:casualties, sym, n) - action.shift(:commitment, -1) if sym == :gov_bases - break if casualties.zero? - end - num - end - - def _attrition(action, casualties) - action.transfer_to(:available, :fln_active, (casualties + 1) / 2) - .transfer_to(:casualties, :fln_active, casualties / 2) - end - - # SUBVERT ################################################################## - - def subvert - return false if (spaces = subvert_spaces(@board)).empty? - - n = 2 - while n.positive? - printd(' subvert 1') - break if (space = subvert_1_priority(spaces.select { |s| may_subvert_1_in?(s, n) }).sample).nil? - - n -= space.algerian_cubes - apply_action _subvert_remove(space, space.algerian_police, space.algerian_troops) - spaces.delete(space) - end - return true if n.zero? || spaces.empty? - - if n == 2 && placeable_guerrillas? - printd(' subvert 2') - unless (space = spaces.select { |s| may_subvert_2_in?(s) }.sample).nil? - apply_action _subvert_replace(space, pick_guerrillas_from) - return true - end - end - return false if n == 2 && !@turn.operation_done? - - spaces.shuffle! - while n.positive? && !(space = spaces.pop).nil? - printd(' subvert 3') - n -= (p = (p = space.algerian_police) > n ? n : p) - n -= (t = (t = space.algerian_troops) > n ? n : t) - apply_action _subvert_remove(space, p, t) - end - n != 2 - end - - def _subvert_remove(space, police, troops) - @turn.special_activity_in(:subvert, space, 0) - .transfer_to(:available, :algerian_police, police) - .transfer_to(:available, :algerian_troops, troops) - end - - def _subvert_replace(space, place_from) - @turn.special_activity_in(:subvert, space, 0) - .transfer_to(:available, :algerian_police, 1) - .transfer_from(place_from, :fln_underground, 1) - end - - # RALLY #################################################################### - - 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 - - # MARCH# ################################################################### - - def march - return false if event_playable? && event_more_effective_than_terror? - - # FIXME - end end end |
