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 | |
| download | colonial-twilight-94d2e329a4b9c325b32de5bd641e5e1e99a1c8f4.zip colonial-twilight-94d2e329a4b9c325b32de5bd641e5e1e99a1c8f4.tar.gz | |
Inital commit
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/colonial_twilight.rb | 10 | ||||
| -rw-r--r-- | lib/colonial_twilight/board.rb | 492 | ||||
| -rw-r--r-- | lib/colonial_twilight/cards.rb | 128 | ||||
| -rw-r--r-- | lib/colonial_twilight/cli.rb | 136 | ||||
| -rw-r--r-- | lib/colonial_twilight/colorized_string.rb | 85 | ||||
| -rw-r--r-- | lib/colonial_twilight/fln_bot.rb | 21 | ||||
| -rw-r--r-- | lib/colonial_twilight/game.rb | 101 | ||||
| -rw-r--r-- | lib/colonial_twilight/player.rb | 27 | 
8 files changed, 1000 insertions, 0 deletions
| diff --git a/lib/colonial_twilight.rb b/lib/colonial_twilight.rb new file mode 100644 index 0000000..5785241 --- /dev/null +++ b/lib/colonial_twilight.rb @@ -0,0 +1,10 @@ +#! /usr/bin/env ruby +# -*- coding: UTF-8 -*- + +module ColonialTwilight +    MAJOR       = 0 +    MINOR       = 1 +    REVISION    = 0 +    VERSION = [MAJOR,MINOR,REVISION].join '.' +end + 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 diff --git a/lib/colonial_twilight/cards.rb b/lib/colonial_twilight/cards.rb new file mode 100644 index 0000000..4bc7a9f --- /dev/null +++ b/lib/colonial_twilight/cards.rb @@ -0,0 +1,128 @@ +#! /usr/bin/env ruby +# -*- coding: UTF-8 -*- + +module ColonialTwilight + +  CARD_SINGLE=1 +  CARD_FLN_MARKED=2 +  CARD_ALWAYS_PLAY=4 + +  class Card +    attr_reader :num, :title +    def initialize n, t, attr, a0=nil, a1=nil +      @num = n +      @title = t +      @attributes = attr +      @a0 = a0 +      @a1 = a1 +    end +    def dual?; @attributes & CARD_SINGLE == 0 end +    def single?; @attributes & CARD_SINGLE == CARD_SINGLE end +    def flnmarked?; @attributes & CARD_FLN_MARKED == CARD_FLN_MARKED end +    def alwaysplay?; @attributes & CARD_ALWAYS_PLAY == CARD_ALWAYS_PLAY end +    def check +      # @attributes.each do |attr| raise "unknown attribute : #{attr}" if attr not in ATTRS end +      puts single? +      puts dual? +      puts flnmarked? +      puts alwaysplay? +    end +  end + +  class CardAction +    def initialize t, c +      @txt = t +      @condition=c +    end +  end + +  class Deck +    attr_reader :cards +    def initialize +      @cards = {} +      add_card 1, 'Quadrillage', 0, CardAction.new('Place up to all French Police in Available in up to 3 spaces', {:what=>:french_police,:from=>:available}) +    end + +    def pull n; @cards[n] end + +    private + +    def add_card num, title, attrs, action +      @cards[num] = Card.new num, title, attrs +      @cards[num].check +    end + +  end + +end + + # 'Balky Conscripts' + # 'Leadership Snatch' + # 'Oil & Gas Discoveries' + # 'Peace of the Brave' + # 'Factionalism' + # '5th Bureau' + # 'Cross-border air strike' + # 'Beni-Oui-Oui' + # 'Moudjahidine' + # 'Bananes' + # 'Ventilos' + # 'SAS' + # 'Protest in Paris' + # 'Jean-Paul Sarte' + # 'NATO' + # 'Commandos' + # 'Torture' + # 'General Strike' + # 'Sauve qui peut' + # 'United Nations Resolution' + # 'The Government of USA is Convinced...' + # 'Diplomatic Leanings' + # 'Economic Development' + # 'Purge' + # 'Casbah' + # 'Covert Movement' + # 'Atrocities and Reprisals' + # 'The Call Up' + # 'Change in Tactics' + # 'Intimidation' + # 'Teleb the Bomb-maker' + # 'Overkill' + # 'Elections' + # 'Napalm' + # 'Assassination' + # 'Integration' + # 'Economic Crisis in France' + # 'Retreat into Djebel' + # 'Strategic Movement' + # 'Egypt' + # 'Czech Arms Deal' + # 'Refugees' + # 'Paranoia' + # 'Challe Plan' + # 'Moghazni' + # 'Third Force' + # 'Ultras' + # 'Factional Plot' + # 'Bleuite' + # 'Stripey Hole' + # 'Cabinet Shuffle' + # 'Population Control' + # 'Operation 744' + # 'Development' + # 'Hardened Attitudes' + # 'Peace Talks' + # 'Army in Waiting' + # 'Bandung Conference' + # 'Soummam Conference' + # 'Morocco and Tunisia Independent' + # 'Suez Crisis' + # 'OAS' + # 'Mobilization' + # 'Recall De Gaulle' + # "Coup d'etat" + # "Propaganda!" + # "Propaganda!" + # "Propaganda!" + # "Propaganda!" + # "Propaganda!" diff --git a/lib/colonial_twilight/cli.rb b/lib/colonial_twilight/cli.rb new file mode 100644 index 0000000..d5a5c53 --- /dev/null +++ b/lib/colonial_twilight/cli.rb @@ -0,0 +1,136 @@ +#! /usr/bin/env ruby +# -*- coding: UTF-8 -*- + +require 'colonial_twilight' +require 'colonial_twilight/colorized_string' +require 'colonial_twilight/game' + +module ColonialTwilight + +  class Cli + +    def initialize options +      @options = options +      @game = ColonialTwilight::Game.new options +    end + +    def start +      logo +      ret = [] +      ret << chose('Choose a scenario', @game.scenarios) { |s| a = s.split(':'); a[0] = a[0].yellow; a.join(':') } +      exit(0) if ret[-1] < 0 +      ret << chose('Choose a ruleset', @game.rules) { |s| a = s.split('-'); a[0] = a[0].yellow; a.join('-') } +      exit(0) if ret[-1] < 0 +      @game.start self, *ret +    end + +    def logo +      clear_screen +      puts ('  ____      _             _       _   _____          _ _ _       _     _    '+ +            "\n"' / ___|___ | | ___  _ __ (_) __ _| | |_   _|_      _(_) (_) __ _| |__ | |_  ' + +            "\n"'| |   / _ \| |/ _ \| \'_ \| |/ _` | |   | | \ \ /\ / / | | |/ _` | \'_ \| __| ' + +            "\n"'| |__| (_) | | (_) | | | | | (_| | |   | |  \ V  V /| | | | (_| | | | | |_  ' + +            "\n"' \____\___/|_|\___/|_| |_|_|\__,_|_|   |_|   \_/\_/ |_|_|_|\__, |_| |_|\__| ' + +            "\n"'                                                           |___/            ').white.bold.on_light_green +      puts "version : #{ColonialTwilight::VERSION.red}".black.on_white +    end + +    def clear_screen +      puts String::CLS +    end + +    PS = { :FLN => 'FLN'.red, :GOV => 'Government'.red } + +    def turn_start turn, first, second +      clear_screen if @options.clearscreen +      puts +      puts ("=" * 80).white.bold.on_light_green +      puts " Turn : #{turn.to_s.red} ".black.on_white + "\t First Eligible  : #{PS[first.faction]} ".black.on_white +      puts "\t\t Second Eligible : #{PS[second.faction]} ".black.on_white +    end + +    def pull_card max +      puts +      printf "Enter the current #{'card number'.yellow} : " +      while true +        s = gets.chomp +        if s.to_i.to_s == s.to_s +          ret = s.to_i +          return ret if ret < max +        end +        puts "\t\t\t\t'#{s}' is not valid, must be one of [1..#{max}]" +        printf "\t$ " +      end +    end + +    def show_card card +      puts +      puts "Current event card : ##{card.num.to_s.yellow} #{card.title.red}" +    end + +    def player p, first +      puts +      clear_screen if @options.clearscreen +      puts +      puts " #{PS[p.faction]} is #{first ? 'First Eligible' : 'Second Eligible'}".black.on_white +    end + +    def chose prompt, list, quit=false +      puts +      puts " => #{prompt.yellow}:" +      puts ('-'*(prompt.size + 5)).white.bold +      list.each_with_index do |el, i| +        puts "\t#{(i+1).to_s.bold}) : #{block_given? ? yield(el): el}" +      end +      puts "\tq) : Quit" if quit + +      printf "\t$ " +      ret = -1 +      while true +        s = gets.chomp +        return -1 if s == 'q' +        if s.to_i.to_s == s.to_s +          ret = s.to_i +          return ret - 1 if ret >= 1 and ret <= list.length +        end +        puts "\t\t\t\t'#{s}' is not valid, must be one of [1..#{list.length}]" +        printf "\t$ " +      end +    end + +    YES=['y','yes'] +    NO=['n','no'] +    def ask prompt, default=nil +      puts +      c = (default.nil? ? 'y/n' : (default ? 'Y/n' : 'y/N')) +      printf " => #{prompt.yellow} (#{c}) ? " +      while true +        ret = gets.chomp.downcase +        return true if YES.include? ret +        return false if NO.include? ret +        return default if not default.nil? +        puts "\t\t\t\t'#{ret}' is not valid, (y/n) ?" +        printf "\t$ " +      end +    end + +  end + +end + +if $PROGRAM_NAME == __FILE__ +  io = ColonialTwilight::Cli.new +  io.logo +  puts +  l = ['Short: 1960-1962: The End Game','Medium: 1957-1962: Midgame Development','Full: 1955-1962: Algerie Francaise!'] +  ret = io.chose('Choose a scenario', l) { |s| a = s.split(':'); a[0] = a[0].yellow; a.join(':') } +  puts l[ret] +  ret = io.ask 'Are you sure' +  puts ret +  ret = io.ask 'Are you sure', true +  puts ret +  ret = io.ask 'Are you sure', false +  puts ret +  puts +  io.turn_start(1, [:FLN, :GOV]) +end diff --git a/lib/colonial_twilight/colorized_string.rb b/lib/colonial_twilight/colorized_string.rb new file mode 100644 index 0000000..6f1d55b --- /dev/null +++ b/lib/colonial_twilight/colorized_string.rb @@ -0,0 +1,85 @@ +#! /usr/bin/env ruby +# -*- coding: UTF-8 -*- + +class String + +  CLS="\033[0;0f\033\[2J".freeze + +  @color_codes = { +    :black   => 0, :light_black    => 60, +    :red     => 1, :light_red      => 61, +    :green   => 2, :light_green    => 62, +    :yellow  => 3, :light_yellow   => 63, +    :blue    => 4, :light_blue     => 64, +    :magenta => 5, :light_magenta  => 65, +    :cyan    => 6, :light_cyan     => 66, +    :white   => 7, :light_white    => 67, +    :default => 9 +  } +  @color_codes.default=9 +  @color_modes = { +    :default   => 0, # Turn off all attributes +    :bold      => 1, # Set bold mode +    :italic    => 3, # Set italic mode +    :underline => 4, # Set underline mode +    :blink     => 5, # Set blink mode +    :swap      => 7, # Exchange foreground and background colors +    :hide      => 8  # Hide text (foreground color would be the same as background) +  } +  @color_modes.default=0 +  @syms = [:fg, :bg, :mode] + +  class << self +    attr_reader :color_codes, :color_modes, :syms +    def create_methods +      color_codes.keys.each do |cc| +        next if cc == :default +        define_method cc do colorize(:fg=>cc) end +        define_method "on_#{cc}" do colorize(:bg=>cc) end +      end +      color_modes.keys.each do |cc| +        next if cc == :default +        define_method cc do colorize(:mode=>cc) end +      end + +    end +  end +  create_methods + +  START="\033[".freeze +  RESET="\033[0m".freeze +  START_RE=/^\033\[([0-9;]+)m/ +  RESET_RE=/(?<!^)\033\[0m(?!$)/ + +  def colorize h +    code = h.inject([]) { |a,(k,v)| a<<resolve(k,v) if self.class.syms.include? k; a }.join(';') +    return code if code.empty? +    s = ( +      if self =~ START_RE # merge with existing escape sequence +        prev = /(?<!^)\033\[#{$1}m(?!$)/ +        code = START + $1 + ';' + code + 'm' +        self.sub(START_RE, code) +      else +        prev = RESET_RE +        code = START + code + 'm' +        code + self +      end +    ) +    s.gsub!(prev, code) +    s+= RESET unless s[-4..] == RESET +    s +  end + +  private + +  def resolve k, v +    return self.class.color_codes[v] + 30 if k == :fg +    return self.class.color_codes[v] + 40 if k == :bg +    return self.class.color_modes[v] if k == :mode +  end + +end + +if $PROGRAM_NAME == __FILE__ +  puts "RED >> #{"blue".colorize(:fg=>:blue,:bg=>nil)} #{"green".colorize(:fg=>nil).on_green} << DER".white.on_red.underline +end diff --git a/lib/colonial_twilight/fln_bot.rb b/lib/colonial_twilight/fln_bot.rb new file mode 100644 index 0000000..9944507 --- /dev/null +++ b/lib/colonial_twilight/fln_bot.rb @@ -0,0 +1,21 @@ +#! /usr/bin/env ruby +# -*- coding: UTF-8 -*- + +module ColonialTwilight + +  class FLNBot + +    attr_reader :faction + +    def initialize game, faction +      @game = game +      @faction = faction +    end + +    def play possible_actions +      puts 'FLNBot.play' #FIXME +    end + +  end + +end diff --git a/lib/colonial_twilight/game.rb b/lib/colonial_twilight/game.rb new file mode 100644 index 0000000..43d6318 --- /dev/null +++ b/lib/colonial_twilight/game.rb @@ -0,0 +1,101 @@ +#! /usr/bin/env ruby +# -*- coding: UTF-8 -*- + +require 'colonial_twilight/board' +require 'colonial_twilight/cards' +require 'colonial_twilight/player' +require 'colonial_twilight/fln_bot' + +module ColonialTwilight + +  class Game + +    @scenarios = ['Short:  1960-1962: The End Game', +                  'Medium: 1957-1962: Midgame Development', +                  'Full:   1955-1962: Algérie Francaise!'].freeze +    @rules = ['Standard Rules      - No Support Phase in final Propaganda round', +              'Optional Rule 8.5.1 - Conduct Support Phase in final Propaganda round'].freeze +    @states = { +      :event        => 'Event:                        execute the Event card', +      :ope_special  => 'Operation & Special Activity: conduct an Operation in any number of spaces with a Special Activity', +      :ope_only     => 'Operation Only:               conduct an Operation in any number of spaces without a Special Activity', +      :ope_limited  => 'Limited Operation:            conduct an Operation in 1 space without a Special Activity', +      :pass         => 'Pass:                         increase your Resources' +    }.freeze +    class << self +      attr_reader :scenarios, :rules, :states, :cards +    end +    def rules; Game.rules end +    def scenarios; Game.scenarios end +    def possible_actions used=nil +      ks = Game.states.keys +      if not used.nil? +        if used == :event +          ks.delete :event +          ks.delete :ope_only +          ks.delete :ope_limited +        elsif used == :ope_special +          ks.delete :ope_special +          ks.delete :ope_only +        elsif used == :ope_limited +          ks.delete :ope_limited +          ks.delete :event +        elsif used == :ope_only +          ks.delete :ope_only +          ks.delete :event +          ks.delete :ope_special +        end +      end +      Game.states.select { |k,v| ks.include? k } +    end + +    attr_reader :scenario, :ruleset, :board, :ui, :cards +    def initialize options +      @options = options +      @board = ColonialTwilight::Board.new +      @deck = ColonialTwilight::Deck.new +    end + +    def start ui, s, rs +      @ui = ui +      @ruleset = rs +      @scenario = s +      @board.load [:short, :medium, :long][s] +      @max_card = 71 +      @turn = 1 +      @cards = [] +      @actions = [] +      @players = [FLNBot.new(self, :FLN), Player.new(self, :GOV)] +      play +    end + +    def play +      while true +        ui.turn_start @turn, *@players +        c = ui.pull_card @max_card +        @cards << @deck.pull(1) # FIXME +        ui.show_card @cards[-1] + +        continue? @players[0].instance_of? FLNBot +        ui.player @players[0], true +        @actions[0] = @players[0].play possible_actions + +        continue? @players[1].instance_of? FLNBot +        ui.player @players[1], false +        @actions[1] = @players[1].play possible_actions @actions[0] + +        @cards.shift if @cards.length > 2 +        @turn += 1 +        # TURN END ... +      end +    end + +    def continue? bot +      l = bot ? ["FLN :\t\tlet the FLN bot play", "Pivotal Event:\tplay a Pivotal Event"] : ["Play:\t\tplay your turn"] +      ret = ui.chose('Next action', l, true) { |s| a = s.split(':'); a[0] = a[0].yellow; a.join(':') } +      exit(0) if ret < 0 +    end + +  end + +end diff --git a/lib/colonial_twilight/player.rb b/lib/colonial_twilight/player.rb new file mode 100644 index 0000000..16e0b04 --- /dev/null +++ b/lib/colonial_twilight/player.rb @@ -0,0 +1,27 @@ +#! /usr/bin/env ruby +# -*- coding: UTF-8 -*- + +module ColonialTwilight + +  class Player + +    attr_reader :faction + +    def initialize game, faction +      @game = game +      @faction = faction +    end + +    def to_s +      @faction.to_s +    end + +    def play possible_actions +      action = @game.ui.chose( 'Choose an action', possible_actions.values) { |s| a = s.split(':'); a[0] = a[0].yellow; a.join(':') } +      puts 'Player.play' # FIXME +      return action +    end + +  end + +end | 
