diff options
Diffstat (limited to 'lib/colonial_twilight/board')
| -rw-r--r-- | lib/colonial_twilight/board/forces.rb | 153 | ||||
| -rw-r--r-- | lib/colonial_twilight/board/scenario.rb | 67 | ||||
| -rw-r--r-- | lib/colonial_twilight/board/setup.rb | 87 | ||||
| -rw-r--r-- | lib/colonial_twilight/board/spaces.rb | 229 |
4 files changed, 536 insertions, 0 deletions
diff --git a/lib/colonial_twilight/board/forces.rb b/lib/colonial_twilight/board/forces.rb new file mode 100644 index 0000000..ecf27df --- /dev/null +++ b/lib/colonial_twilight/board/forces.rb @@ -0,0 +1,153 @@ +# frozen_string_literal: true + +# rubocop:disable Style/AccessorGrouping +# rubocop:disable Style/ParallelAssignment + +module ColonialTwilight + class Forces + attr_reader :name + attr_reader :algerian_troops, :algerian_police + attr_reader :french_troops, :french_police + attr_reader :fln_underground, :fln_active + attr_reader :fln_bases, :gov_bases + attr_reader :max_bases, :control + + def initialize(sym) + @name = sym + @algerian_troops, @algerian_police = 0, 0 + @french_troops, @french_police, @gov_bases = 0, 0, 0 + @fln_underground, @fln_active, @fln_bases = 0, 0, 0 + @max_bases = nil + @control = :uncontrolled + @max_bases = 2 if %i[Country Sector].include?(sym) + _variables_to_remove(sym)&.each do |s| + instance_variable_set(s, nil) + end + end + + def init(data) + data.each { |k, v| add(k, v) } + end + + private + + def _variables_to_remove(sym) + case sym + when :available then %i[@control @fln_active] + when :casualties then %i[@control @fln_active @fln_bases] + when :out_of_play then %i[@control @algerian_troops @algerian_police @fln_active @fln_bases] + when :Country then %i[@control @algerian_troops @algerian_police @french_troops @french_police] + when :City then nil + when :Sector then nil + end + end + + public + + def inspect + "GOV bases: #{gov_bases} + french troops: #{french_troops} + french police: #{french_police} + algerian troops: #{algerian_troops} + algerian police: #{algerian_police} + FLN bases: #{fln_bases} + underground Guerrillas: #{fln_underground} + active Guerrillas: #{fln_active}" + end + alias to_s inspect + + def data + h = {} + %i[algerian_troops algerian_police french_troops french_police gov_bases + fln_underground fln_active fln_bases control].each do |sym| + h[sym] = send(sym) unless send(sym).nil? + end + h + end + + def bases + (@gov_bases || 0) + (@fln_bases || 0) + end + + def fln + guerrillas + (@fln_bases || 0) + end + + def guerrillas + (@fln_underground || 0) + (@fln_active || 0) + end + + def gov + gov_cubes + (@gov_bases || 0) + end + + def gov_cubes + french_cubes + algerian_cubes + end + + def french_cubes + (@french_troops || 0) + (@french_police || 0) + end + + def algerian_cubes + (@algerian_troops || 0) + (@algerian_police || 0) + end + + def troops + (@french_troops || 0) + (@algerian_troops || 0) + end + + def police + (@french_police || 0) + (@algerian_police || 0) + end + + def add(type, num = 1) + type = :fln_underground if name == :available && type == :fln_active + case type + when :french_troops then @french_troops += num + when :french_police then @french_police += num + when :algerian_troops then @algerian_troops += num + when :algerian_police then @algerian_police += num + when :fln_underground then @fln_underground += num + when :fln_active then @fln_active += num + when :gov_bases then add_base(type, num) + when :fln_bases then add_base(type, num) + else + raise "unknown force type : #{type}" + end + update_control + end + + def activate(num) + raise "cannot activate #{num}" if @fln_underground < num + + @fln_underground -= num + @fln_active += num + end + + private + + def add_base(type, num = 1) + if !@max_bases.nil? && (bases + num) > @max_bases + raise "too much bases in #{@name} (#{bases} + #{num}) > #{@max_bases}" + end + + @gov_bases += num if type == :gov_bases + @fln_bases += num if type == :fln_bases + end + + def update_control + return nil if @control.nil? + + ctr = @control + @control = ( + case gov <=> fln + when 0 then :uncontrolled + when 1 then :GOV + when -1 then :FLN + end + ) + @control != ctr + end + end +end diff --git a/lib/colonial_twilight/board/scenario.rb b/lib/colonial_twilight/board/scenario.rb new file mode 100644 index 0000000..a0f5edf --- /dev/null +++ b/lib/colonial_twilight/board/scenario.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module ColonialTwilight + module Scenario + def resettle(name) + by_name(name).resettle! + end + + def set_space(idx, opts, align = nil) + s = @spaces[idx] + s.alignment = align unless align.nil? + %i[gov_bases fln_bases french_troops french_police algerian_troops algerian_police + fln_underground].each { |sym| s.add(sym, opts[sym]) if opts.key?(sym) } + end + + def short_scenario + @opposition_bases.v = 19 + @support_commitment.v = 22 + @commitment.v = 15 + @fln_resources.v = 15 + @gov_resources.v = 20 + @france_track.v = 4 + @border_zone_track.v = 3 + @out_of_play.init(fln_underground: 5) + @available.init(gov_bases: 2, french_police: 4, fln_bases: 7, fln_underground: 8) + resettle 'Setif' + resettle 'Tlemcen' + resettle 'Bordj Bou Arreridj' + + set_space 0, { algerian_police: 1, fln_underground: 1 }, :oppose + set_space 2, { french_police: 1 } + set_space 4, { algerian_police: 1, fln_underground: 1 }, :oppose + set_space 5, { french_police: 1 } + set_space 6, { french_police: 1 }, :support + set_space 7, { fln_underground: 1 } + set_space 8, { french_troops: 4, algerian_police: 1, gov_bases: 1 } + set_space 9, { french_troops: 1, algerian_police: 1, gov_bases: 1, fln_underground: 1, fln_bases: 1 }, :oppose + set_space 10, { french_police: 1, fln_underground: 1, fln_bases: 1 }, :oppose + set_space 11, { french_police: 1 } + set_space 12, { french_police: 1, fln_underground: 1, fln_bases: 1 }, :oppose + set_space 13, { french_troops: 4, algerian_troops: 1, french_police: 1 }, :support + set_space 14, { algerian_troops: 1, gov_bases: 1 } + set_space 15, { french_police: 1, algerian_police: 1, fln_underground: 1, fln_bases: 1 }, :oppose + set_space 16, { algerian_troops: 1, french_police: 1, algerian_police: 1 }, :support + set_space 17, { french_police: 1, algerian_police: 1 } + set_space 18, { french_police: 2, fln_underground: 1 } + set_space 19, { french_police: 1, gov_bases: 1 } + set_space 20, { french_police: 1 } + set_space 22, { french_police: 1 } + set_space 23, { french_police: 1 } + set_space 24, { french_police: 1 } + set_space 27, {}, :oppose + set_space 28, { fln_underground: 4, fln_bases: 2 } + set_space 29, { fln_underground: 5, fln_bases: 2 } + spaces[28].independent! + spaces[29].independent! + end + + def medium_scenario + raise 'MEDIUM scenario net implemented yet' + end + + def full_scenario + raise 'FULL scenario net implemented yet' + end + end +end diff --git a/lib/colonial_twilight/board/setup.rb b/lib/colonial_twilight/board/setup.rb new file mode 100644 index 0000000..110a674 --- /dev/null +++ b/lib/colonial_twilight/board/setup.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +module ColonialTwilight + module Setup + def setup + set_spaces + set_adjacents + end + + def add(kls, *args) + @spaces << kls.new(*args) + end + + def set_spaces + mountain = Sector::MOUNTAIN + border = Sector::BORDER + coastal = Sector::COASTAL + add Sector, 'Barika', 'I', 1, 1, mountain + add Sector, 'Batna', 'I', 2, 0, mountain + add Sector, 'Biskra', 'I', 3, 0, border + add Sector, 'Oum El Bouaghi', 'I', 4, 0, mountain + add Sector, 'Tebessa', 'I', 5, 1, mountain | border + add Sector, 'Negrine', 'I', 6, 0, mountain | border + add City, 'Constantine', 'II', 2 + add Sector, 'Setif', 'II', 1, 1, mountain | coastal + add Sector, 'Philippeville', 'II', 2, 2, mountain | coastal + add Sector, 'Souk Ahras', 'II', 3, 2, coastal | border + add Sector, 'Tizi Ouzou', 'III', 1, 2, mountain | coastal + add Sector, 'Bordj Bou Arreridj', 'III', 2, 1, mountain + add Sector, 'Bougie', 'III', 3, 2, mountain | coastal + add City, 'Algiers', 'IV', 3, coastal + add Sector, 'Medea', 'IV', 1, 2, mountain | coastal + add Sector, 'Orleansville', 'IV', 2, 2, mountain | coastal + add City, 'Oran', 'V', 2, coastal + add Sector, 'Mecheria', 'V', 1, 0, mountain | border + add Sector, 'Tlemcen', 'V', 2, 1, border | coastal + add Sector, 'Sidi Bel Abbes', 'V', 3, 1, coastal + add Sector, 'Mostaganem', 'V', 4, 2, mountain | coastal + add Sector, 'Saida', 'V', 5, 0, mountain + add Sector, 'Mascara', 'V', 6, 0, mountain + add Sector, 'Tiaret', 'V', 7, 0, mountain + add Sector, 'Ain Sefra', 'V', 8, 0, border + add Sector, 'Laghouat', 'V', 9, 0 + add Sector, 'Sidi Aissa', 'VI', 1, 0, mountain + add Sector, 'Ain Oussera', 'VI', 2, 1, mountain + add Country, 'Morocco' + add Country, 'Tunisia' + end + + def adjacents(name, *args) + by_name(name).adjacents = args + end + + def set_adjacents + adjacents 'Barika', 1, 2, 3, 7, 8, 11, 26 + adjacents 'Batna', 0, 2, 3, 5 + adjacents 'Biskra', 0, 1, 5, 25, 26, 29 + adjacents 'Oum El Bouaghi', 0, 1, 4, 5, 8, 9 + adjacents 'Tebessa', 3, 5, 9, 29 + adjacents 'Negrine', 1, 2, 3, 4, 29 + adjacents 'Constantine', 7, 8 + adjacents 'Setif', 0, 6, 8, 11, 12 + adjacents 'Philippeville', 0, 3, 7, 6, 9 + adjacents 'Souk Ahras', 3, 4, 8, 29 + adjacents 'Tizi Ouzou', 11, 12, 14 + adjacents 'Bordj Bou Arreridj', 0, 7, 10, 12, 14, 26 + adjacents 'Bougie', 7, 10, 11 + adjacents 'Algiers', 14 + adjacents 'Medea', 10, 11, 13, 15, 26, 27 + adjacents 'Orleansville', 14, 20, 23, 27 + adjacents 'Oran', 19 + adjacents 'Mecheria', 18, 21, 24, 28 + adjacents 'Tlemcen', 17, 19, 21, 28 + adjacents 'Sidi Bel Abbes', 16, 18, 20, 21, 22 + adjacents 'Mostaganem', 15, 19, 22, 23 + adjacents 'Saida', 17, 18, 19, 22, 24 + adjacents 'Mascara', 19, 20, 21, 23, 24 + adjacents 'Tiaret', 15, 20, 22, 24, 27 + adjacents 'Ain Sefra', 17, 21, 22, 23, 25, 27, 28 + adjacents 'Laghouat', 2, 24, 26, 27 + adjacents 'Sidi Aissa', 0, 2, 11, 14, 25, 27 + adjacents 'Ain Oussera', 14, 15, 23, 24, 25, 26 + adjacents 'Morocco', 17, 18, 24 + adjacents 'Tunisia', 2, 4, 5, 9 + end + end +end diff --git a/lib/colonial_twilight/board/spaces.rb b/lib/colonial_twilight/board/spaces.rb new file mode 100644 index 0000000..fad3170 --- /dev/null +++ b/lib/colonial_twilight/board/spaces.rb @@ -0,0 +1,229 @@ +# frozen_string_literal: true + +require_relative 'forces' + +module ColonialTwilight + class Track + attr_accessor :v + + def initialize(max) + @v = 0 + @max = max + end + + def shift(val) + @v += val + raise "out of track #{@v}" if @v.negative? || @v > @max + + @v + end + + def clamp(val) + @v = (@v + val).clamp(0, @max) + end + + def data + @v + end + end + + class Box < Forces + end + + class Sector + MOUNTAIN = 1 + COASTAL = 2 + BORDER = 4 + + attr_reader :wilaya, :sector, :name, :resettled + attr_accessor :pop, :terror, :adjacents, :alignment + + def initialize(name, wilaya, sector, pop, attrs = 0) + @name = name + @wilaya = wilaya + @sector = sector + @pop = pop + @attrs = attrs + @alignment = :neutral + @resettled = false + @terror = 0 + @forces = Forces.new self.class.name.split('::')[-1].to_sym + _compute_strings + end + + private + + def _compute_strings + @terrain = %i[mountain coastal border].map { |s| send("#{s}?") ? s : nil }.reject(&:nil?).join('/') + @descr = "#{@name} #{self.class.name.split('::')[-1]}#{number}" + end + + def number + return '' if @wilaya.nil? && @sector.nil? + + @descr = "(#{@wilaya}-#{@sector})" + end + + public + + def to_s + @name + end + + def inspect + "\n#{@descr} : #{@terrain} + population : #{@pop}#{@resettled ? ' resettled' : ''} + control : #{control} + alignment : #{@alignment} + terror : #{@terror} + #{@forces} + adjs : #{@adjacents}" + end + + def data + { name: @name, alignment: @alignment, terror: @terror, pop: @pop, resettled: @resettled }.merge(@forces.data) + end + + %i[gov gov_bases gov_cubes french_cubes algerian_cubes troops police + french_troops french_police algerian_troops algerian_police + fln fln_bases guerrillas fln_underground fln_active max_bases control].each do |sym| + define_method(sym) { @forces.send(sym) } + end + + def sector? + true + end + + def city? + false + end + + def country? + false + end + + def border? + (@attrs & BORDER) == BORDER + end + + def coastal? + (@attrs & COASTAL) == COASTAL + end + + def mountain? + (@attrs & MOUNTAIN) == MOUNTAIN + end + + def terror? + @terror.positive? + end + + def support? + @alignment == :support + end + + def oppose? + @alignment == :oppose + end + + def neutral? + @alignment == :neutral + end + + def uncontrolled? + control == :uncontrolled + end + + def fln_control? + control == :FLN + end + + def gov_control? + control == :GOV + end + + def add(type, num = 1) + @forces.add(type, num) + end + + def shift_terror(num = 1) + raise 'terror cant be negative' if @terror.zero? && num.negative? + + @terror += num + end + + def resettled? + @resettled + end + + def resettle! + raise 'cannot resettle a country' if country? + raise 'cannot resettle a sector with a population =! 1' if @pop != 1 + + @pop = 0 + @resettled = true + end + + def shift(towards) + if towards == :oppose + raise 'cannot shift towards oppose' if oppose? + + @alignment = (support? ? :neutral : :oppose) + elsif towards == :support + raise 'cannot shift towards support' if support? + + @alignment = (oppose? ? :neutral : :support) + else + raise "unknown shift direction : #{towards}" + end + end + + def activate(num) + @forces.activate(num) + end + end + + class City < Sector + def initialize(name, wilaya, pop, attrs = 0) + super name, wilaya, 0, pop, attrs + end + + def sector? + false + end + + def city? + true + end + end + + # if independent, FLN may Rally, March and Extort in these Countries, + # but their Population is never counted in the total Opposition + class Country < Sector + attr_reader :independent + + def initialize(name) + super(name, nil, nil, 1, MOUNTAIN | BORDER | COASTAL) + @independent = false + @descr += ' : French' + end + + def sector? + false + end + + def country? + true + end + + def independent? + @independent + end + + def independent! + @independent = true + @descr.gsub!(/French/, 'Independent') + @independent + end + end +end |
