diff options
author | Jérémy Zurcher <jeremy@asynk.ch> | 2020-08-05 19:14:26 +0200 |
---|---|---|
committer | Jérémy Zurcher <jeremy@asynk.ch> | 2020-08-05 19:14:26 +0200 |
commit | 94d2e329a4b9c325b32de5bd641e5e1e99a1c8f4 (patch) | |
tree | 1d9f733e12eb8dc27610da0d52674ae1aec9fefc /lib/colonial_twilight/board.rb | |
download | colonial-twilight-94d2e329a4b9c325b32de5bd641e5e1e99a1c8f4.zip colonial-twilight-94d2e329a4b9c325b32de5bd641e5e1e99a1c8f4.tar.gz |
Inital commit
Diffstat (limited to 'lib/colonial_twilight/board.rb')
-rw-r--r-- | lib/colonial_twilight/board.rb | 492 |
1 files changed, 492 insertions, 0 deletions
diff --git a/lib/colonial_twilight/board.rb b/lib/colonial_twilight/board.rb new file mode 100644 index 0000000..3a1679a --- /dev/null +++ b/lib/colonial_twilight/board.rb @@ -0,0 +1,492 @@ +#! /usr/bin/env ruby +# -*- coding: UTF-8 -*- + +require 'json' + +# FIXME : +# - json should not be here +# - has_? +# - can_? +# +# check min/max bases, points, tracks +# +# delegate [syms].each do |s| define_method s delegate.instance_method(s) end +# +# scenario as JSON ?, tools to generate them +# + +module ColonialTwilight + + class Forces + + attr_accessor :algerian_troops, :algerian_police + attr_accessor :french_troops, :french_police + attr_accessor :fln_underground, :fln_active + attr_accessor :fln_bases, :gov_bases + attr_reader :control + + def initialize k + @algerian_troops = 0 + @algerian_police = 0 + @french_troops = 0 + @french_police = 0 + @gov_bases = 0 + @fln_underground = 0 + @fln_active = 0 + @fln_bases = 0 + @max_bases = 2 + @control = :none + rm = nil + case k + when :available + rm = [:@control, :@fln_active] + when :casualties + rm = [:@control, :@fln_active, :@fln_bases] + when :out_of_play + rm = [:@control, :@algerian_troops, :@algerian_police, :@fln_active, :@fln_bases] + when :Country + @max_bases = 3 + rm = [:@control, :@algerian_troops, :@algerian_police, :@french_troops, :@french_police, :@gov_bases] + end + rm.each do |sym| + # maybe remove :sym= instead or set @sym to nil + remove_instance_variable sym + end unless rm.nil? + end + + def to_s + " + #{gov_bases} GOV bases + #{french_troops} french troops + #{french_police} french police + #{algerian_troops} algerian troops + #{algerian_police} algerian police + #{fln_bases} FLN bases + #{fln_underground} underground Guerrillas + #{fln_active} active Guerrillas" + end + + def data + h={} + [:algerian_troops, :algerian_police, :french_troops, :french_police, + :fln_underground, :fln_active, :fln_bases, :gov_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 add t, n=1 + case t + when :french_troops; @french_troops += n + when :french_police; @french_police += n + when :algerian_troops; @algerian_troops += n + when :algerian_police; @algerian_police += n + when :fln_underground; @fln_underground += n + when :fln_active; @fln_active += n + else + raise "unknown force type : #{t}" + end + update_control + end + + def add_base t, n=1 + raise "too much bases in #@name (#{bases} + #{n}) > #@max_bases" if (bases + n) > @max_bases + @gov_bases += n if t == :gov + @fln_bases += n if t == :fln + update_control + end + + private + + def update_control + return if @control.nil? + gov = @algerian_troops + @algerian_police + @french_troops + @french_police + @gov_bases + fln = @fln_underground + @fln_active + @fln_bases + if gov == fln; @control = :none + elsif gov > fln; @control = :GOV + else @control = :FLN + end + end + + end + + class Sector + + MOUNTAIN=1 + COASTAL=2 + BORDER=4 + + attr_reader :wilaya, :sector, :name, :resettled + attr_accessor :pop, :adjacents + attr_accessor :alignment + + def initialize n, w, s, p, attrs=0 + @name = n + @wilaya = w + @sector = s + @pop = p + @attributes = attrs + @descr = "#{self.class.name} #@name #{@wilaya == 0 ? '' : @wilaya}" + (@sector == 0 ? '' : "-#{@sector}") + @terrain = [mountain? ? 'mountain' : nil ,coastal? ? 'coastal' : nil, border? ? 'border' : nil].reject(&:nil?).join '/' + @forces = Forces.new self.class.name.split('::')[-1].to_sym + @alignment = :neutral + @resettled = false + end + + def to_s + "#@descr #@terrain + control : #{@forces.control} + alignment : #@alignment + population : #{@pop}#{@resettled ? ' resettled' : ''} + forces : #{@forces} + adjs : #{@adjacents}" + end + + def data + { :name=>@name, :alignment=>@alignment, :pop=>@pop, :resettled=>@resettled }.merge(@forces.data) + end + + def city?; false; end + def country?; false; end + def border?; (@attributes & BORDER) == BORDER end + def coastal?; (@attributes & COASTAL) == COASTAL end + def mountain?; (@attributes & MOUNTAIN) == MOUNTAIN end + def control; @forces.control; end + def add_gov_base n=1; @forces.add_base :gov, n; end + def add_fln_base n=1; @forces.add_base :fln, n; end + def add_french_troops n=1; @forces.add :french_troops, n; end + def add_french_police n=1; @forces.add :french_police, n; end + def add_algerian_troops n=1; @forces.add :algerian_troops, n; end + def add_algerian_police n=1; @forces.add :algerian_police, n; end + def add_fln_underground n=1; @forces.add :fln_underground, n; end + def add_fln_active n=1; @forces.add :fln_active, n; end + def french_troops; @forces.french_troops end + def french_police; @forces.french_police end + def algerian_troops; @forces.algerian_troops end + def algerian_police; @forces.algerian_police end + def fln_underground; @forces.fln_underground end + def fln_active; @forces.fln_active end + def gov_bases; @forces.gov_bases end + def fln_bases; @forces.fln_bases end + def resettle! + raise "can't resettle a country " if country? + raise "can't resettle a sector with a population > 1" if @pop != 1 + @pop = 0 + @resettled = true + end + + end + + class City < Sector + def initialize n, w, p, attrs=0 + super n, w, 0, p, attrs + end + def city?; true; end + end + + class Country < Sector + + attr_reader :independant + + def initialize n + super n, 0, 0, 1, MOUNTAIN|BORDER|COASTAL + @descr += " #{@independant ? 'Independant' : 'French'}" + end + def add_gov_base n=1; raise "no gov bases allowed in #@name" end + def country?; true; end + end + + + class Board + + FRANCE_TRACK=['A','B','C','D','E'].freeze + + attr_accessor :commitment + attr_accessor :gov_resources, :fln_resources + attr_accessor :support_commitment, :opposition_bases + attr_accessor :resettled_sectors + attr_accessor :france_track, :border_zone_track + + attr_reader :spaces, :names + + def initialize + @names = [] + @spaces = {} + @capabilities = [] + @available = Forces.new :available + @casualties = Forces.new :casualties + @out_of_play = Forces.new :out_of_play + feed + end + + def load scenario + case scenario + when :short; short + when :medium; medium + when :full; full + else raise "unknown scenario : #{scenario}" + end + end + + def sectors + @spaces.select{ |k,s| not s.country? } + end + + def data + h = { } + [:commitment, :gov_resources, :fln_resources, :support_commitment, :opposition_bases, :resettled_sectors, :france_track, :border_zone_track].each do |sym| + h[sym] = send(sym) + end + h[:capabilities] = @capabilities + h[:available] = @available.data + h[:casualties] = @casualties.data + h[:out_of_play] = @out_of_play.data + h[:spaces] = @spaces.inject([])do |a,(k,s)| a << s.data end + h + end + + def to_json + # JSON.pretty_generate(data) + JSON.generate(data) + end + + def save + File.open('save.json','w') do |f| + f.write(JSON.generate(data)) + end + end + + def resettle sector + @spaces[sector].resettle! + @resettled_sectors += 1 + end + + def compute_victory + @opposition_bases = 0 + @support_commitment = @commitment + @spaces.each do |n,s| + @opposition_bases += s.fln_bases + @opposition_bases += s.pop if s.alignment == :oppose + @support_commitment += s.pop if s.alignment == :support + end + end + + private + + def add k, *args + s = k.new *args + # puts s + @names << s.name + @spaces[s.name] = s + end + + def adjacents i, *args + @spaces[@names[i]].adjacents = args + # @spaces[@names[i]].adjacents = args.map { |i| @names[i] } + # puts @spaces[@names[i]] + end + + def feed + mountain=Sector::MOUNTAIN + border=Sector::BORDER + coastal=Sector::COASTAL + add Sector, 'Barika', 'I', 1, 1, mountain # 0 + add Sector, 'Batna', 'I', 2, 0, mountain # 1 + add Sector, 'Biskra', 'I', 3, 0, border # 2 + add Sector, 'Oum El Bouaghi', 'I', 4, 0, mountain # 3 + add Sector, 'Tebessa', 'I', 5, 1, mountain|border # 4 + add Sector, 'Negrine', 'I', 6, 0, mountain|border # 5 + add City, 'Constantine', 'II', 2 # 6 + add Sector, 'Setif', 'II', 1, 1, mountain|coastal # 7 + add Sector, 'Philippeville', 'II', 2, 2, mountain|coastal # 8 + add Sector, 'Souk Ahras', 'II', 3, 2, coastal|border # 9 + add Sector, 'Tizi Ouzou', 'III', 1, 2, mountain|coastal # 10 + add Sector, 'Bordj Bou Arreridj', 'III', 2, 1, mountain # 11 + add Sector, 'Bougie', 'III', 3, 2, mountain|coastal # 12 + add City, 'Algiers', 'IV', 3, coastal # 13 + add Sector, 'Medea', 'IV', 1, 2, mountain|coastal # 14 + add Sector, 'Orleansville', 'IV', 2, 2, mountain|coastal # 15 + add City, 'Oran', 'V', 2, coastal # 16 + add Sector, 'Mecheria', 'V', 1, 0, mountain|border # 17 + add Sector, 'Tlemcen', 'V', 2, 1, border|coastal # 18 + add Sector, 'Sidi Bel Abbes', 'V', 3, 1, coastal # 19 + add Sector, 'Mostaganem', 'V', 4, 2, mountain|coastal # 20 + add Sector, 'Saida', 'V', 5, 0, mountain # 21 + add Sector, 'Mascara', 'V', 6, 0, mountain # 22 + add Sector, 'Tiaret', 'V', 7, 0, mountain # 23 + add Sector, 'Ain Sefra', 'V', 8, 0, border # 24 + add Sector, 'Laghouat', 'V', 9, 0 # 25 + add Sector, 'Sidi Aissa', 'VI', 1, 0, mountain # 26 + add Sector, 'Ain Oussera', 'VI', 2, 1, mountain # 27 + add Country, 'Moroco' # 28 + add Country, 'Tunisia' # 29 + adjacents 0, 1, 2, 3, 7, 8, 11, 19 + adjacents 1, 0, 2, 3, 5 + adjacents 2, 0, 1, 5, 25, 26, 29 + adjacents 3, 0, 1, 4, 5, 8, 9 + adjacents 4, 3, 5, 9, 29 + adjacents 5, 1, 2, 3, 4, 29 + adjacents 6, 7, 8 + adjacents 7, 0, 6, 8, 11, 12 + adjacents 8, 0, 3, 7, 6, 9 + adjacents 9, 3, 4, 8, 29 + adjacents 10, 11, 12, 14 + adjacents 11, 0, 7, 10, 12, 14, 26 + adjacents 12, 7, 10, 11 + adjacents 13, 14 + adjacents 14, 10, 11, 13, 15, 26, 27 + adjacents 15, 14, 20, 23, 27 + adjacents 16, 19 + adjacents 17, 18, 21, 24, 28 + adjacents 18, 17, 19, 21, 28 + adjacents 19, 16, 18, 20, 21, 22 + adjacents 20, 15, 19, 22, 23 + adjacents 21, 17, 18, 19, 22, 24 + adjacents 22, 19, 20, 21, 23, 24 + adjacents 23, 15, 20, 22, 24, 27 + adjacents 24, 17, 21, 22, 23, 25, 27, 28 + adjacents 25, 2, 24, 26, 27 + adjacents 26, 0, 2, 11, 14, 25, 27 + adjacents 27, 14, 15, 23, 24, 25, 26 + adjacents 28, 17, 18, 24 + adjacents 29, 2, 4, 5, 8 + end + + def set_sector i, h, align=nil + s = @spaces[@names[i]] + s.alignment = align unless align.nil? + s.add_gov_base h[:govb] if h.has_key? :govb + s.add_fln_base h[:flnb] if h.has_key? :flnb + s.add_french_troops h[:ft] if h.has_key? :ft + s.add_french_police h[:fp] if h.has_key? :fp + s.add_algerian_troops h[:at] if h.has_key? :at + s.add_algerian_police h[:ap] if h.has_key? :ap + s.add_fln_underground h[:fln] if h.has_key? :fln + # puts s + end + + def short + self.commitment = 15 + self.fln_resources = 15 + self.gov_resources = 20 + self.resettled_sectors = 0 + self.france_track = 4 + self.border_zone_track = 3 + @out_of_play.fln_underground = 5 + @available.gov_bases = 2 + @available.french_police = 4 + @available.fln_bases = 7 + @available.fln_underground = 8 + resettle 'Setif' + resettle 'Tlemcen' + resettle 'Bordj Bou Arreridj' + raise "resettled sectors not counted" if resettled_sectors != 3 + set_sector 0, {:ap=>1, :fln=>1}, :oppose + set_sector 2, {:fp=>1} + set_sector 4, {:ap=>1, :fln=>1}, :oppose + set_sector 5, {:fp=>1} + set_sector 6, {:fp=>1}, :support + set_sector 7, {:fln=>1} + set_sector 8, {:ft=>4, :ap=>1, :govb=>1} + set_sector 9, {:ft=>1, :ap=>1, :govb=>1, :fln=>1, :flnb=>1}, :oppose + set_sector 10, {:fp=>1, :fln=>1, :flnb=>1}, :oppose + set_sector 11, {:fp=>1} + set_sector 12, {:fp=>1, :fln=>1, :flnb=>1}, :oppose + set_sector 13, {:ft=>4, :at=>1, :fp=>1}, :support + set_sector 14, {:at=>1, :govb=>1} + set_sector 15, {:fp=>1, :ap=>1, :fln=>1, :flnb=>1}, :oppose + set_sector 16, {:at=>1, :fp=>1, :ap=>1}, :support + set_sector 17, {:fp=>1, :ap=>1} + set_sector 18, {:fp=>2, :fln=>1} + set_sector 19, {:fp=>1, :govb=>1} + set_sector 20, {:fp=>1} + set_sector 22, {:fp=>1} + set_sector 23, {:fp=>1} + set_sector 24, {:fp=>1} + set_sector 27, {}, :oppose + set_sector 28, {:fln=>4, :flnb=>2} + set_sector 29, {:fln=>5, :flnb=>2} + compute_victory + raise "wrong opposition bases" if @opposition_bases != 19 + raise "wrong support_commitment" if @support_commitment != 22 + end + + def medium + raise 'MEDIUM scenario net implemented yet' + end + + def full + raise 'FULL scenario net implemented yet' + end + + end + +end + +# class ColonialTwilight::Sector +# undef :adjacents= +# end + +if $PROGRAM_NAME == __FILE__ + def check b + # puts '--- Coastal' + # b.spaces.select{ |k,s| s.coastal? }.each { |k,s| puts s.name } + raise "coastal sectors error" if b.spaces.select{ |k,s| s.coastal? }.size != 14 + # puts '--- not Mountain' + # b.spaces.select{ |k,s| not s.mountain? }.each { |k,s| puts s.name } + raise "not moauntain sectors error" if b.spaces.select{ |k,s| not s.mountain? }.size != 9 + # puts '--- Border' + # b.spaces.select{ |k,s| s.border? }.each { |k,s| puts s.name } + raise "border sectors error" if b.spaces.select{ |k,s| s.border? }.size != 9 + # puts '--- City' + # b.spaces.select{ |k,s| s.city? }.each { |k,s| puts s.name } + raise "city sectors error" if b.spaces.select{ |k,s| s.city? }.size != 3 + [[0,11],[1,9],[2,9],[3,1]].each do |p,n| + # puts "--- Population #{p}" + # b.spaces.select{ |k,s| s.pop==p }.each { |k,s| puts s.name } + raise "population #{p} error" if b.spaces.select{ |k,s| s.pop==p}.size != n + end + raise "sectors count wrong" if b.sectors.size != 28 + end + + def check_forces what, b, v + sup, opp, gov, fln = 0, 0, 0, 0 + ft, fp, at, ap, g = 0, 0, 0, 0, 0 + gb, fb = 0, 0 + b.spaces.each do |n,s| + sup += 1 if s.alignment == :support + opp += 1 if s.alignment == :oppose + gov += 1 if s.control == :GOV + fln += 1 if s.control == :FLN + ft += s.french_troops unless s.french_troops.nil? + fp += s.french_police unless s.french_police.nil? + at += s.algerian_troops unless s.algerian_troops.nil? + ap += s.algerian_police unless s.algerian_police.nil? + g += s.fln_underground + gb += s.gov_bases unless s.gov_bases.nil? + fb += s.fln_bases + end + raise "wrong support #{sup} != #{v[0]}" if sup != v[0] + raise "wrong oppose #{opp} != #{v[1]}" if opp != v[1] + raise "wrong GOV control #{gov} != #{v[2]}" if gov != v[2] + raise "wrong FLN control #{fln} != #{v[3]}" if fln != v[3] + raise "wrong french troops #{ft} != #{v[4]}" if ft != v[4] + raise "wrong french police #{fp} != #{v[5]}" if fp != v[5] + raise "wrong algerian troops #{at} != #{v[6]}" if at != v[6] + raise "wrong algerian police #{ap} != #{v[7]}" if ap != v[7] + raise "wrong Guerrillas #{g} != #{v[8]}" if g != v[8] + raise "wrong GOV bases #{gb} != #{v[9]}" if gb != v[9] + raise "wrong FLN bases #{fb} != #{v[10]}" if fb != v[10] + end + + b = ColonialTwilight::Board.new + puts 'check' + check b + b.load :short + check_forces 'short', b, [3, 7, 16, 3, 9, 17, 3, 7, 17, 4, 8] + puts 'ok' +end |