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 | |
download | colonial-twilight-94d2e329a4b9c325b32de5bd641e5e1e99a1c8f4.zip colonial-twilight-94d2e329a4b9c325b32de5bd641e5e1e99a1c8f4.tar.gz |
Inital commit
Diffstat (limited to 'lib/colonial_twilight')
-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 |
7 files changed, 990 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 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 |