From d75f430c7ee9e40b53ef1932456c5482296129b6 Mon Sep 17 00:00:00 2001 From: bena Date: Wed, 27 Jul 2022 16:06:30 +0300 Subject: [PATCH] implement 18esp --- assets/app/view/game/combined_trains.rb | 171 ++++++++++++++++++ assets/app/view/game/round/operating.rb | 2 + lib/engine/action/combined_trains.rb | 34 ++++ lib/engine/game/g_18_esp/entities.rb | 118 +++++++++++- lib/engine/game/g_18_esp/game.rb | 119 +++++++++++- lib/engine/game/g_18_esp/map.rb | 69 +++++-- lib/engine/game/g_18_esp/meta.rb | 8 + .../game/g_18_esp/step/combined_trains.rb | 78 ++++++++ lib/engine/game/g_18_esp/step/home_token.rb | 3 +- lib/engine/game/g_18_esp/step/track.rb | 2 +- lib/engine/game/g_18_esp/step/tracker.rb | 1 + public/logos/18_esp/AVT.svg | 1 + public/logos/18_esp/CA.svg | 1 + public/logos/18_esp/CSE.svg | 1 + public/logos/18_esp/FDC.svg | 1 + public/logos/18_esp/GSSR.svg | 1 + public/logos/18_esp/MH.svg | 1 + public/logos/18_esp/SFVA.svg | 1 + 18 files changed, 583 insertions(+), 29 deletions(-) create mode 100644 assets/app/view/game/combined_trains.rb create mode 100644 lib/engine/action/combined_trains.rb create mode 100644 lib/engine/game/g_18_esp/step/combined_trains.rb create mode 100644 public/logos/18_esp/AVT.svg create mode 100644 public/logos/18_esp/CA.svg create mode 100644 public/logos/18_esp/CSE.svg create mode 100644 public/logos/18_esp/FDC.svg create mode 100644 public/logos/18_esp/GSSR.svg create mode 100644 public/logos/18_esp/MH.svg create mode 100644 public/logos/18_esp/SFVA.svg diff --git a/assets/app/view/game/combined_trains.rb b/assets/app/view/game/combined_trains.rb new file mode 100644 index 0000000000..ef40b1276b --- /dev/null +++ b/assets/app/view/game/combined_trains.rb @@ -0,0 +1,171 @@ +# frozen_string_literal: true + +require 'view/game/actionable' + +module View + module Game + class CombinedTrains < Snabberb::Component + include Actionable + include Lib::Settings + + needs :selected_trains, store: true, default: {} + + def render + @step = @game.active_step + current_entity = @game.round.current_entity + base_trains = @game.combined_base_trains_candidates(current_entity) + obsolete_trains = @game.combined_obsolete_trains_candidates(current_entity) + + rendered_base_trains = base_trains.flat_map do |train| + onclick = lambda do + @selected_trains[train] = !@selected_trains[train] + store(:selected_trains, @selected_trains, skip: false) + end + + style = { + border: 'solid 1px', + display: 'inline-block', + cursor: 'pointer', + margin: '0.1rem 0rem', + padding: '3px 6px', + minWidth: '1.5rem', + textAlign: 'center', + whiteSpace: 'nowrap', + } + + bg_color = route_prop(1, :color) + style[:backgroundColor] = @selected_trains[train] ? bg_color : color_for(:bg) + style[:color] = contrast_on(bg_color) + + [ + h(:tr, + [h('td.middle', [ + h(:div, { style: style, on: { click: onclick } }, train.name), + ])]), + ] + end + rendered_obsolete_trains = obsolete_trains.flat_map do |train| + train.variants.flat_map do |_name, variant| + onclick = lambda do + train.variant = variant[:name] + @selected_trains[train] = !@selected_trains[train] + store(:selected_trains, @selected_trains, skip: false) + end + + children = [] + + style = { + border: 'solid 1px', + display: 'inline-block', + cursor: 'pointer', + margin: '0.1rem 0rem', + padding: '3px 6px', + minWidth: '1.5rem', + textAlign: 'center', + whiteSpace: 'nowrap', + } + + bg_color = route_prop(1, :color) + style[:backgroundColor] = @selected_trains[train] && train.name == variant[:name] ? bg_color : color_for(:bg) + style[:color] = contrast_on(bg_color) + + td_props = { style: { paddingRight: '0.8rem' } } + children << h('td.right.middle', td_props, @game.format_currency(variant[:price] * 2)) + [ + h(:tr, + [h('td.middle', [ + h(:div, { style: style, on: { click: onclick } }, variant[:name]), + ]), *children]), + ] + end + end + + div_props = { + key: 'combined_trains', + hook: { + destroy: -> { cleanup }, + }, + } + table_props = { + style: { + marginTop: '0.5rem', + textAlign: 'left', + }, + } + + description = 'Each turn, trains may be added together to run as a single longer train for that turn.' + + h(:div, div_props, [ + h(:h3, 'Combined Trains'), + h('div.small_font', description), + h(:table, table_props, [ + h(:thead, [ + h(:tr, [ + h(:th, 'Base Train'), + ]), + ]), + h(:tbody, rendered_base_trains), + h(:thead, [ + h(:tr, [ + h(:th, 'Obsoslete Train'), + h(:th, 'Cost'), + ]), + ]), + h(:tbody, rendered_obsolete_trains), + ]), + actions, + ].compact) + end + + def cleanup(skip: true) + store(:selected_trains, {}, skip: skip) + end + + def actions + selected_count = 0 + cities = 0 + towns = 0 + + trains = @selected_trains.map do |train, selected| + next unless selected + next if train.is_a?(String) + + selected_count += 1 + + c, t = @game.distance(train) + cities += c + towns += t + + train + end.compact + + disabled = trains.size < 2 || trains.first.track_type == trains.last.track_type + submit_txt = disabled ? 'Select trains' : "Form #{cities}+#{towns} train" + base, variant = trains.partition { |t| t.owner == @game.current_entity } + variant = variant.first + base = base.first + + submit = lambda do + process_action(Engine::Action::CombinedTrains.new(@game.current_entity, base: base, additional_train: variant, + additional_train_variant: variant.name)) + cleanup + end + + reset = lambda do + cleanup(skip: false) + end + + submit_style = { + minWidth: '6.5rem', + marginTop: '1rem', + padding: '0.2rem 0.5rem', + } + + h(:div, { style: { overflow: 'auto', marginBottom: '1rem' } }, [ + h(:button, { attrs: { disabled: disabled }, style: submit_style, on: { click: submit } }, submit_txt), + h(:button, { style: submit_style, on: { click: reset } }, 'Reset'), + ]) + end + end + end +end diff --git a/assets/app/view/game/round/operating.rb b/assets/app/view/game/round/operating.rb index 3c2e9ccb82..c7c699c20b 100644 --- a/assets/app/view/game/round/operating.rb +++ b/assets/app/view/game/round/operating.rb @@ -19,6 +19,7 @@ require 'view/game/route_selector' require 'view/game/cash_crisis' require 'view/game/double_head_trains' +require 'view/game/combined_trains' require 'view/game/buy_token' module View @@ -54,6 +55,7 @@ def render left << h(SwitchTrains) if @current_actions.include?('switch_trains') left << h(ReassignTrains) if @current_actions.include?('reassign_trains') left << h(DoubleHeadTrains) if @current_actions.include?('double_head_trains') + left << h(CombinedTrains) if @current_actions.include?('combined_trains') left << h(Choose) if @current_actions.include?('choose') left << h(BuyToken, entity: entity) if @current_actions.include?('buy_token') diff --git a/lib/engine/action/combined_trains.rb b/lib/engine/action/combined_trains.rb new file mode 100644 index 0000000000..4ee3bea02d --- /dev/null +++ b/lib/engine/action/combined_trains.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require_relative 'base' + +module Engine + module Action + class CombinedTrains < Base + attr_reader :base, :additional_train, :additional_train_variant + + def initialize(entity, base:, additional_train:, additional_train_variant:) + super(entity) + @base = base + @additional_train = additional_train + @additional_train_variant = additional_train_variant + end + + def self.h_to_args(h, game) + { + base: game.train_by_id(h['base']), + additional_train_variant: h['additional_train_variant'], + additional_train: game.train_by_id(h['additional_train']), + } + end + + def args_to_h + { + 'base' => base&.id, + 'additional_train' => additional_train&.id, + 'additional_train_variant' => additional_train_variant, + } + end + end + end +end diff --git a/lib/engine/game/g_18_esp/entities.rb b/lib/engine/game/g_18_esp/entities.rb index 7ca6dd40fe..b9207d5cdd 100644 --- a/lib/engine/game/g_18_esp/entities.rb +++ b/lib/engine/game/g_18_esp/entities.rb @@ -113,6 +113,15 @@ module Entities color: nil, abilities: [{ type: 'shares', shares: 'CRB_1' }], }, + { + sym: 'P6', + name: 'Ferrocarril Vasco-Navarro', + value: 160, + revenue: 20, + desc: 'It provides a 10% certificate from the Southern company CRB.', + color: nil, + abilities: [{ type: 'shares', shares: 'random_share' }], + }, { sym: 'P7', name: 'Ferrocarril de Carreño', @@ -125,7 +134,6 @@ module Entities }, ].freeze - # corporations with different properties in 1st Edition CORPORATIONS = [ { float_percent: 40, @@ -342,6 +350,114 @@ module Entities startable: true, }, ].freeze + + EXTRA_CORPORATIONS = [ + { + float_percent: 40, + sym: 'SFVA', + name: 'Sociedad General de Ferrocarriles Vasco Asturiana', + logo: '18_esp/SFVA', + coordinates: 'D6', + color: '#75151E', + max_ownership_percent: 60, + tokens: [0, 50, 50, 50, 50], + type: 'major', + destination: 'C1', + }, + { + float_percent: 40, + sym: 'FDC', + name: 'Ferrocarril del Cantábrico', + logo: '18_esp/FDC', + coordinates: 'I5', + color: '#5D9B9B', + max_ownership_percent: 60, + tokens: [0, 50, 50, 50, 50], + type: 'major', + destination: 'G5', + }, + { + float_percent: 40, + sym: 'GSSR', + name: 'Great Southern of Spain Railway Company Limited', + logo: '18_esp/GSSR', + coordinates: 'I29', + city: 0, + max_ownership_percent: 60, + tokens: [0, 50, 50, 50, 50, 50], + color: '#6A5F31', + type: 'major', + destination: 'F32', + }, + { + float_percent: 40, + sym: 'AVT', + name: 'Sociedad de los Ferrocarriles de Almansa a Valencia y Tarragona', + logo: '18_esp/AVT', + coordinates: 'K25', + city: 0, + max_ownership_percent: 60, + tokens: [0, 50, 50, 50, 50, 50], + color: '#7DCCE5', + type: 'major', + destination: 'L22', + }, + { + float_percent: 40, + sym: 'TBF', + name: 'Compañía de los Ferrocarriles de Tarragona a Barcelona y Francia', + logo: '18_esp/TBF', + coordinates: 'L22', + city: 0, + max_ownership_percent: 60, + tokens: [0, 50, 50, 50, 50, 50], + color: '#F4A900', + type: 'major', + destination: 'N18', + }, + + { + sym: 'MH', + name: 'Ferrocarril de Madrid a Hendaya', + logo: '18_esp/MH', + coordinates: 'E21', + color: '#4C2F27', + tokens: [0], + type: 'minor', + shares: [100], + float_percent: 100, + max_ownership_percent: 100, + startable: true, + }, + + { + sym: 'CA', + name: 'Compañía del Ferrocarril Central de Aragón', + logo: '18_esp/CA', + coordinates: 'E21', + color: '#2E3A23', + tokens: [0], + type: 'minor', + shares: [100], + float_percent: 100, + max_ownership_percent: 100, + startable: true, + }, + { + sym: 'CSE', + name: 'Compañía de los Caminos de Hierro del Sur de España ', + logo: '18_esp/CSE', + coordinates: 'E21', + color: '#2E3A23', + tokens: [0], + type: 'minor', + shares: [100], + float_percent: 100, + max_ownership_percent: 100, + startable: true, + }, + + ].freeze end end end diff --git a/lib/engine/game/g_18_esp/game.rb b/lib/engine/game/g_18_esp/game.rb index 9531d8ec24..c56bb99a38 100644 --- a/lib/engine/game/g_18_esp/game.rb +++ b/lib/engine/game/g_18_esp/game.rb @@ -19,7 +19,7 @@ class Game < Game::Base attr_reader :can_build_mountain_pass, :can_buy_trains, :minors_stop_operating - attr_accessor :player_debts, :luxury_carriages_count + attr_accessor :player_debts, :combined_trains, :luxury_carriages_count CURRENCY_FORMAT_STR = '₧%d' @@ -31,7 +31,7 @@ class Game < Game::Base STARTING_CASH = { 3 => 860, 4 => 650, 5 => 520, 6 => 440 }.freeze - NORTH_CORPS = %w[FdSB FdLR CFEA CFLG].freeze + NORTH_CORPS = %w[FdSB FdLR CFEA CFLG SFVA FDC].freeze TRACK_RESTRICTION = :permissive @@ -65,6 +65,8 @@ class Game < Game::Base P5_HEX = 'H12' + DOUBLE_HEX = %w[D6 C31 J20].freeze + P5_DISCOUNT = 40 BASE_MINE_BONUS = { yellow: 30, green: 20, brown: 10, gray: 0 }.freeze @@ -225,7 +227,8 @@ class Game < Game::Base }, ], events: [{ 'type' => 'close_companies' }, - { 'type' => 'minors_stop_operating' }], + { 'type' => 'minors_stop_operating' }, + { 'type' => 'float_60' }], }, { name: '6', @@ -241,7 +244,6 @@ class Game < Game::Base price: 600, }, ], - events: [{ 'type' => 'float_60' }], }, { @@ -301,6 +303,7 @@ def new_auction_round def stock_round G18ESP::Round::Stock.new(self, [ + G18ESP::Step::Acquire, Engine::Step::DiscardTrain, G18ESP::Step::BuySellParShares, ]) @@ -321,6 +324,7 @@ def operating_round(round_num) Engine::Step::DiscardTrain, G18ESP::Step::Acquire, G18ESP::Step::BuyTrain, + G18ESP::Step::CombinedTrains, [G18ESP::Step::BuyCarriageOrCompany, { blocks: true }], ], round_num: round_num) end @@ -363,6 +367,7 @@ def setup @luxury_carriages_count = 4 @opened_mountain_passes = [] + @combined_trains = {} # Initialize the player depts, if player have to take an emergency loan init_player_debts @@ -380,6 +385,34 @@ def setup end end + def setup_corporations + minors, majors = @corporations.partition { |corporation| corporation.type == :minor } + north_majors, south_majors = majors.partition { |corporation| north_corp?(corporation) } + remove_corps = north_majors.sort_by { rand }.take(2) + south_majors.sort_by { rand }.take(3) + minors.sort_by do + rand + end.take(3) + @log << "Removing #{remove_corps.map(&:name).join(', ')}" + remove_corps.each do |c| + @corporations.delete(c) + hex = @hexes.find { |h| h.id == c.coordinates } + hex.tile.cities[c.city || 0].remove_reservation!(c) + hex.tile.cities[c.city || 0].remove_tokens! + c.close! + end + end + + def setup_companies + remove_company_name = @corporations.none? { |c| c.name == 'CRB' } ? 'Zafra - Huelva' : 'Ferrocarril Vasco-Navarro' + remove_company = @companies.find { |c| c.name == remove_company_name } + @log << "Removing #{remove_company_name}" + @companies.delete(remove_company) + end + + def setup_preround + setup_corporations unless core + setup_companies + end + def setup_company_price(mulitplier) @companies.each { |company| company.max_price = company.value * mulitplier } end @@ -405,6 +438,7 @@ def find_and_remove_train_for_minor(train_id, buyable = true) def init_company_abilities northern_corps = @corporations.select { |c| north_corp?(c) } random_corporation = northern_corps[rand % northern_corps.size] + another_random_corporation = northern_corps[rand % northern_corps.size] @companies.each do |company| next unless (ability = abilities(company, :shares)) @@ -423,6 +457,12 @@ def init_company_abilities when: 'bought_train', corporation: random_corporation.name, )) + when 'random_share' + share = another_random_corporation.shares.find { |s| !s.president } + real_shares << share + company.desc = "It provides a 10% certificate from a random corporation. \ + The random corporation in this game is #{another_random_corporation.name}." + @log << "#{company.name} comes with a #{share.percent}% share of #{another_random_corporation.name}" else real_shares << share_by_id(share) end @@ -581,7 +621,16 @@ def status_array(corporation) goal_status << ['Takeover'] if !north_corp?(corporation) && !corporation.taken_over_minor && !corporation.full_cap goal_status = [] if goal_status.length == 1 - goal_status + + train_status = corporation.trains.map do |train| + next unless combined_trains[train] + + "Combined train #{train.name}: #{combined_trains[train]}" + end + train_status = [] if train_status.length.zero? + status = goal_status + train_status + status = nil if status.length.zero? + status end def company_status_str(company) @@ -698,9 +747,22 @@ def check_distance(route, visits) 'Minors can not run to offboard locations' end + if combined_trains.include?(route.train) + raise GameError, 'Combined train must run through a montain pass' if route.hexes.none? do |hex| + mountain_pass_token_hex?(hex) + end + + north_stops = route.stops.count { |st| north_hex?(st.hex) && !mountain_pass_token_hex?(st.hex) } + south_stops = route.stops.count { |st| !north_hex?(st.hex) && !mountain_pass_token_hex?(st.hex) } + + raise GameError, 'Combined train must stop at both maps' if !north_stops.positive? || !south_stops.positive? + + end + raise GameError, 'Route can only use one mountain pass' if route.hexes.count { |hex| mountain_pass_token_hex?(hex) } > 1 - raise GameError, 'Route visits same hex twice' if route.hexes.size != route.hexes.uniq.size + hexes = route.hexes.reject { |h| self.class::DOUBLE_HEX.include?(h.name) } + raise GameError, 'Route visits same hex twice' if hexes.size != hexes.uniq.size if route.train.id == '2P-0' && !@perm2_ran_aranjuez && route.hexes.none? do |hex| hex.id == ARANJUEZ_HEX @@ -1081,7 +1143,7 @@ def spend_compensation(amount, from, to) from.spend(amount, to) else difference = amount - from.cash - from.spend(from.cash, to) + from.spend(from.cash, to) if from.cash.positive? if to != from take_player_loan(from.owner, differnce - from.owner.cash) unless from.owner.cash >= difference from.owner.spend(difference, to) @@ -1130,7 +1192,8 @@ def delete_token_mz(minor) city = token.city yellow_green = city.tile.color == :yellow || city.tile.color == :green if !yellow_green - city.delete_token!(token) + delete_slot = city.slots > 4 ? city.slots : false + city.delete_token!(token, remove_slot: delete_slot) # add mza reservation if mza not tokened in madrid yet mza_token = city.tokens.compact.find { |t| t.corporation == mza } city.add_reservation!(mza) unless mza_token @@ -1185,6 +1248,46 @@ def move_assets(survivor, nonsurvivor) @log << "Moved assets from #{nonsurvivor.name} to #{survivor.name}" end + + def combined_base_trains_candidates(corporation) + return unless corporation + + corporation.trains.reject { |t| combined_trains.key?(t) || t.name == '2P' } + end + + def combined_obsolete_trains_candidates(corporation) + return unless corporation + + rusted_trains = @depot.trains.select do |train| + train.rusted && corporation.cash >= 2 * train.price + end + + rusted_trains.uniq { |train| train.variants.keys[0] } + end + + def update_trains_cache + update_cache(:trains) + end + + def distance(train) + if train.distance.is_a?(Numeric) + [train.distance, 0] + else + cities = train.distance[1]['pay'] + towns = train.distance[0]['pay'] + [cities, towns] + end + end + + def core + @optional_rules&.include?(:core) + end + + def game_corporations + corps = self.class::CORPORATIONS + corps += self.class::EXTRA_CORPORATIONS unless core + corps + end end end end diff --git a/lib/engine/game/g_18_esp/map.rb b/lib/engine/game/g_18_esp/map.rb index 069bd2db3b..827a52c850 100644 --- a/lib/engine/game/g_18_esp/map.rb +++ b/lib/engine/game/g_18_esp/map.rb @@ -4,7 +4,7 @@ module Engine module Game module G18ESP module Map - MINE_HEXES = %w[C5 C9 E9 E11 E19 G9 H6 I7 C23 G17 G21 D18 D32 E31 H30 I23 D8 E7 B30 F30 I21].freeze + MINE_HEXES = %w[C5 C9 E9 E19 G9 H6 I7 C23 G17 G21 D18 D32 E31 H30 I23 D8 E7 B30 F30 I21 J24].freeze LAYOUT = :flat TILES = { '3' => 5, @@ -46,12 +46,41 @@ module Map 'code' => 'city=revenue:30;path=a:0,b:_0,track:narrow;path=a:3,b:_0,track:narrow;label=Y', }, - '235' => 3, - 'L113' => { - 'count' => 3, + 'L129' => { + 'count' => 1, + 'color' => 'yellow', + 'code' => + 'city=revenue:30;city=revenue:30;path=a:0,b:_0;path=a:1,b:_1;label=OO', + }, + 'L130' => { + 'count' => 1, 'color' => 'yellow', 'code' => - 'city=revenue:30;city=revenue:30;path=a:0,b:_0,track:narrow;label=OO', + 'city=revenue:30;city=revenue:30;path=a:0,b:_0;path=a:2,b:_1;label=OO', + }, + 'L131' => { + 'count' => 1, + 'color' => 'yellow', + 'code' => + 'city=revenue:30;city=revenue:30;path=a:0,b:_0;path=a:3,b:_1;label=OO', + }, + 'L132' => { + 'count' => 1, + 'color' => 'yellow', + 'code' => + 'city=revenue:30;city=revenue:30;path=a:0,b:_0,track:narrow;path=a:1,b:_1,track:narrow;label=OO', + }, + 'L133' => { + 'count' => 1, + 'color' => 'yellow', + 'code' => + 'city=revenue:30;city=revenue:30;path=a:0,b:_0,track:narrow;path=a:2,b:_1,track:narrow;label=OO', + }, + 'L134' => { + 'count' => 1, + 'color' => 'yellow', + 'code' => + 'city=revenue:30;city=revenue:30;path=a:0,b:_0,track:narrow;path=a:3,b:_1,track:narrow;label=OO', }, 'L79' => { 'count' => 1, @@ -459,7 +488,9 @@ module Map %w[201 L80], %w[202 L81], %w[621 L82], - %w[235 L113], + %w[L129 L132], + %w[L130 L133], + %w[L131 L134], %w[L79], %w[L89 L92], %w[L90 L93], @@ -616,11 +647,11 @@ module Map %w[C1] => 'halt=revenue:yellow_40|green_30|brown_20|gray_20;path=a:0,b:_0,track:dual', %w[E1] => 'halt=revenue:yellow_20|green_30|brown_40|gray_50;path=a:0,b:_0,track:dual', %w[K3] => 'halt=revenue:green_30|brown_40|gray_50;path=a:0,b:_0,track:dual', - %w[L26] => 'halt=revenue:green_30|brown_50|gray_60;path=a:2,b:_0,track:dual;label=E', - %w[K31] => 'halt=revenue:green_30|brown_30|gray_40;path=a:2,b:_0,track:dual;label=E', + %w[L26] => 'halt=revenue:yellow_20|green_30|brown_50|gray_60;path=a:2,b:_0,track:dual;label=E', + %w[I33 K31] => 'halt=revenue:yellow_20|green_30|brown_30|gray_40;path=a:2,b:_0,track:dual;label=E', %w[F34] => 'halt=revenue:green_30|brown_30|gray_40;path=a:2,b:_0,track:dual', %w[B34] => 'halt=revenue:yellow_20|green_30|brown_30|gray_40;path=a:4,b:_0,track:dual', - %w[N22] => 'halt=revenue:green_30|brown_50|gray_60;path=a:2,b:_0,track:dual;label=E', + %w[N22] => 'halt=revenue:yellow_20|green_30|brown_50|gray_60;path=a:2,b:_0,track:dual;label=E', }, red: { %w[A5] => @@ -657,22 +688,25 @@ module Map ['K5'] => 'city=revenue:30;path=a:1,b:_0,track:narrow;path=a:2,b:_0,track:narrow;label=Y;' \ 'icon=image:18_esp/FdSB,sticky:1;icon=image:18_esp/FdLR,sticky:1;'\ 'icon=image:anchor', + + ['I5'] => 'city=revenue:30,slots:2;path=a:2,b:_0,track:narrow;path=a:4,b:_0,track:narrow', ['F24'] => 'city=revenue:30;city=revenue:30;city=revenue:30;path=a:0,b:_0;path=a:1,b:_0;' \ 'path=a:2,b:_1;path=a:4,b:_2;path=a:0,b:_2;label=M', - ['M21'] => 'city=revenue:30;city=revenue:30;path=a:1,b:_0;path=a:2,b:_1;label=B;'\ + ['M21'] => 'city=revenue:30;city=revenue:30;path=a:1,b:_0;path=a:2,b:_1;path=a:5,b:_1;label=B;'\ 'icon=image:anchor', }, white: { - %w[B6 B8 B28 C19 D34 H26 I27 I31 J26 K27 L4 L20] => '', + %w[B6 B8 B28 C19 D34 H26 I27 J26 K27 L4 L20] => '', %w[A3 B4 D2 H4 J4] => 'town=revenue:0', - %w[C33] => 'town=revenue:0;icon=image:anchor', + %w[C33] => 'city=revenue:0;icon=image:anchor', %w[G5] => 'town=revenue:0;icon=image:18_esp/CFEA,sticky:1;border=edge:0,type:impassable', - %w[B10 D4 F18 F32 I5 K19] => 'city=revenue:0', + %w[B10 D4 F18 F32 K19] => 'city=revenue:0', ['E3'] => 'city=revenue:0;icon=image:anchor;label=Y', %w[E33] => 'city=revenue:0;'\ 'label=Y;icon=image:anchor', %w[K25] => 'city=revenue:0;label=Y;icon=image:anchor', - %w[C5 C9 E9 G9 I7 C23 G21 D32 E31 I23] => 'halt=symbol:⚒,route:mandatory;upgrade=cost:30,terrain:mine', + ['H32'] => 'city=revenue:0;icon=image:anchor;upgrade=cost:30,terrain:mountain', + %w[C5 C9 E9 G9 I7 C23 G21 D32 E31 I23 I31] => 'halt=symbol:⚒,route:mandatory;upgrade=cost:30,terrain:mine', %w[H6] => 'halt=symbol:⚒,route:mandatory;upgrade=cost:30,terrain:mine;border=edge:1,type:impassable', %w[H30] => 'halt=symbol:⚒,route:mandatory;upgrade=cost:30,terrain:mine;border=edge:2,type:impassable', %w[E7] => 'halt=symbol:⚒,route:mandatory;town=revenue:0;upgrade=cost:30,terrain:mine;icon=image:18_esp/CFLG,sticky:1', @@ -687,7 +721,7 @@ module Map %w[K21] => 'upgrade=cost:20,terrain:river', %w[L18 M19] => 'upgrade=cost:80,terrain:mountain', %w[G7] => 'upgrade=cost:40,terrain:mountain;border=edge:3,type:impassable;border=edge:4,type:impassable', - %w[H22 H24 J22] => 'upgrade=cost:60,terrain:mountain', + %w[H22 H24 J22] => 'upgrade=cost:30,terrain:mountain', %w[E11 F8 H10 J8 K7 L6 D10] => 'upgrade=cost:30,terrain:mountain', %w[G29] => 'upgrade=cost:60,terrain:mountain;border=edge:5,type:impassable', %w[J6] => 'town=revenue:0;upgrade=cost:30,terrain:mountain', @@ -696,8 +730,8 @@ module Map %w[D20] => 'town=revenue:0;upgrade=cost:10,terrain:river', %w[C25] => 'city=revenue:0;upgrade=cost:10,terrain:river;' \ 'icon=image:18_esp/MCP,sticky:1', - %w[E29 I29] => 'city=revenue:0;upgrade=cost:10,terrain:river', - %w[J28] => 'city=revenue:0;upgrade=cost:10,terrain:river;icon=image:18_esp/MZA,sticky:1;label=Y', + %w[E29 J28] => 'city=revenue:0;upgrade=cost:10,terrain:river', + %w[I29] => 'city=revenue:0;upgrade=cost:10,terrain:river;icon=image:18_esp/MZA,sticky:1;label=Y', %w[F20 D24 D26 D30 H18 I19 G25] => 'upgrade=cost:10,terrain:river', %w[B30 D8 D18 E19 F30 G17 I21 J24] => 'halt=symbol:⚒,route:mandatory;town=revenue:0;upgrade=cost:30,terrain:mine', %w[C7 C11 E23 E25 F10 F22 G19 H20 I9 I25 G33] => 'upgrade=cost:40,terrain:mountain', @@ -717,7 +751,6 @@ module Map 'path=a:1,b:_0;path=a:2,b:_0;path=a:4,b:_0;path=a:5,b:_0', ['G27'] => 'city=revenue:30,slots:2;path=a:1,b:_0;path=a:2,b:_0;path=a:4,b:_0;path=a:5,b:_0', %w[J30] => 'town=revenue:20;path=a:1,b:_0;path=a:2,b:_0;path=a:3,b:_0;path=a:5,b:_0', - ['H32'] => 'town=revenue:30;path=a:1,b:_0;path=a:3,b:_0;path=a:4,b:_0', }, orange: { ['D12'] => 'city=revenue:0,slots:2;path=a:3,b:_0,track:dual;path=a:0,b:_0,track:dual;label=100;icon=image:18_esp/50', diff --git a/lib/engine/game/g_18_esp/meta.rb b/lib/engine/game/g_18_esp/meta.rb index aef3f506a2..ebdf2bda9b 100644 --- a/lib/engine/game/g_18_esp/meta.rb +++ b/lib/engine/game/g_18_esp/meta.rb @@ -18,6 +18,14 @@ module Meta GAME_INFO_URL = '' PLAYER_RANGE = [3, 6].freeze + + OPTIONAL_RULES = [ + { + sym: :core, + short_name: 'Core game', + desc: 'Core game without a variable setup', + }, + ].freeze end end end diff --git a/lib/engine/game/g_18_esp/step/combined_trains.rb b/lib/engine/game/g_18_esp/step/combined_trains.rb new file mode 100644 index 0000000000..e91de15fff --- /dev/null +++ b/lib/engine/game/g_18_esp/step/combined_trains.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require_relative '../../../step/base' + +module Engine + module Game + module G18ESP + module Step + class CombinedTrains < Engine::Step::Base + ACTIONS = %w[combined_trains pass].freeze + + def description + 'Combine Trains' + end + + def actions(entity) + return [] unless entity == current_entity + return [] unless entity.corporation? + return [] if entity.type == :minor + return [] unless @game.combined_obsolete_trains_candidates(entity).size.positive? + return [] unless @game.combined_base_trains_candidates(entity).size.positive? + + ACTIONS + end + + def process_combined_trains(action) + base = action.base + additional_train = action.additional_train + variant = action.additional_train_variant + corporation = action.entity + additional_train.variant = variant + + joined_trains = "#{base.name}, #{additional_train.name}" + create_combined_train!(base, additional_train) + + cost = additional_train.price * 2 + + corporation.spend(cost, @game.bank) + + @log << "#{corporation.name} forms "\ + "#{base.name} train by combining trains: #{joined_trains} for #{@game.format_currency(cost)}" + end + + def create_combined_train!(base, additional_train) + # combined train's ID is formed by combining the the IDs of the + # given trains + distance, name = combined_distance_and_name(base, additional_train) + + # simplify name to C+t form, after id is set via sym + base.name = name + base.distance = distance + base.track_type = :all + + @game.update_trains_cache + + @game.combined_trains << base + end + + def combined_distance_and_name(base, additional_train) + c, t = @game.distance(base) + c1, t1 = @game.distance(additional_train) + cities = c + c1 + towns = t + t1 + + distance = [ + { 'nodes' => %w[town halt], 'pay' => towns, 'visit' => towns }, + { 'nodes' => %w[city offboard town halt], 'pay' => cities, 'visit' => cities }, + ] + + name = "#{cities}+#{towns}C" + + [distance, name] + end + end + end + end + end +end diff --git a/lib/engine/game/g_18_esp/step/home_token.rb b/lib/engine/game/g_18_esp/step/home_token.rb index cb3f1c6123..e72fe25c80 100644 --- a/lib/engine/game/g_18_esp/step/home_token.rb +++ b/lib/engine/game/g_18_esp/step/home_token.rb @@ -29,7 +29,8 @@ def process_place_token(action) action.city, token, connected: false, - extra_action: true + extra_action: true, + check_tokenable: false ) @round.pending_tokens.shift action.entity.goal_reached!(:destination) if @game.check_for_destination_connection(action.entity) diff --git a/lib/engine/game/g_18_esp/step/track.rb b/lib/engine/game/g_18_esp/step/track.rb index 054999b706..7d4cf020fe 100644 --- a/lib/engine/game/g_18_esp/step/track.rb +++ b/lib/engine/game/g_18_esp/step/track.rb @@ -27,7 +27,7 @@ def process_place_token(action) end def pay_token_cost(entity, cost, city) - return super if !@game.mountain_pass?(city.hex) || city.tokens.compact.size == 1 + return super if !@game.mountain_pass_token_hex?(city.hex) || city.tokens.compact.size == 1 first_corp = city.tokens.first.corporation extra_cost = cost - @game.class::MOUNTAIN_SECOND_TOKEN_COST diff --git a/lib/engine/game/g_18_esp/step/tracker.rb b/lib/engine/game/g_18_esp/step/tracker.rb index 13a0c0a523..7f4d742a17 100644 --- a/lib/engine/game/g_18_esp/step/tracker.rb +++ b/lib/engine/game/g_18_esp/step/tracker.rb @@ -16,6 +16,7 @@ def setup def lay_tile_action(action) hex = action.hex + old_tile = hex.tile super if @game.mine_hex?(action.hex) && old_tile.color == :white diff --git a/public/logos/18_esp/AVT.svg b/public/logos/18_esp/AVT.svg new file mode 100644 index 0000000000..370a28ec38 --- /dev/null +++ b/public/logos/18_esp/AVT.svg @@ -0,0 +1 @@ +AVT \ No newline at end of file diff --git a/public/logos/18_esp/CA.svg b/public/logos/18_esp/CA.svg new file mode 100644 index 0000000000..8e18fdd140 --- /dev/null +++ b/public/logos/18_esp/CA.svg @@ -0,0 +1 @@ +CA \ No newline at end of file diff --git a/public/logos/18_esp/CSE.svg b/public/logos/18_esp/CSE.svg new file mode 100644 index 0000000000..04b99fc828 --- /dev/null +++ b/public/logos/18_esp/CSE.svg @@ -0,0 +1 @@ +CSE \ No newline at end of file diff --git a/public/logos/18_esp/FDC.svg b/public/logos/18_esp/FDC.svg new file mode 100644 index 0000000000..a227c15fa1 --- /dev/null +++ b/public/logos/18_esp/FDC.svg @@ -0,0 +1 @@ +FDC \ No newline at end of file diff --git a/public/logos/18_esp/GSSR.svg b/public/logos/18_esp/GSSR.svg new file mode 100644 index 0000000000..57f21e0ce0 --- /dev/null +++ b/public/logos/18_esp/GSSR.svg @@ -0,0 +1 @@ +GSSR \ No newline at end of file diff --git a/public/logos/18_esp/MH.svg b/public/logos/18_esp/MH.svg new file mode 100644 index 0000000000..ebc7518b78 --- /dev/null +++ b/public/logos/18_esp/MH.svg @@ -0,0 +1 @@ +MH \ No newline at end of file diff --git a/public/logos/18_esp/SFVA.svg b/public/logos/18_esp/SFVA.svg new file mode 100644 index 0000000000..5f6976b809 --- /dev/null +++ b/public/logos/18_esp/SFVA.svg @@ -0,0 +1 @@ +SFVA \ No newline at end of file