From 281c6b8093dd540e7baec39bca2458112ac83f13 Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Tue, 18 Jan 2022 21:23:23 +0300 Subject: [PATCH 01/24] 18Scan: fix minor home tokens location --- lib/engine/game/g_18_scan/entities.rb | 4 ++-- lib/engine/game/g_18_scan/game.rb | 21 +++++++++++++++++++ .../game/g_18_scan/step/waterfall_auction.rb | 2 ++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/engine/game/g_18_scan/entities.rb b/lib/engine/game/g_18_scan/entities.rb index 3ca838db4d..d429a393ad 100644 --- a/lib/engine/game/g_18_scan/entities.rb +++ b/lib/engine/game/g_18_scan/entities.rb @@ -136,7 +136,7 @@ module Entities simple_logo: '18_scan/2.alt', tokens: [0, 40], coordinates: 'F11', - city: 0, + city: 1, destination_coordinates: 'B11', color: '#5b74b4', abilities: [ @@ -160,7 +160,7 @@ module Entities simple_logo: '18_scan/3.alt', tokens: [0, 40], coordinates: 'F11', - city: 1, + city: 0, destination_coordinates: 'D7', color: '#5b74b4', abilities: [ diff --git a/lib/engine/game/g_18_scan/game.rb b/lib/engine/game/g_18_scan/game.rb index a89eab0c2e..baf79c4b34 100644 --- a/lib/engine/game/g_18_scan/game.rb +++ b/lib/engine/game/g_18_scan/game.rb @@ -242,6 +242,15 @@ def setup share = sj_share_by_minor(minor.name) share.buyable = false share.counts_for_limit = false + + # Reserve token locations for minors + cities = hex_by_id(minor.coordinates).tile.cities + + if minor.city + cities[minor.city].add_reservation!(minor) + else + cities.first.add_reservation!(minor) + end end end @@ -259,6 +268,18 @@ def stock_round ]) end + def operating_round(round_num) + Round::Operating.new(self, [ + Engine::Step::Bankrupt, + Engine::Step::Track, + Engine::Step::Token, + Engine::Step::Route, + G18Scan::Step::Dividend, + Engine::Step::DiscardTrain, + Engine::Step::BuyTrain, + ], round_num: round_num) + end + def train_limit(entity) super + Array(abilities(entity, :train_limit)).sum(&:increase) end diff --git a/lib/engine/game/g_18_scan/step/waterfall_auction.rb b/lib/engine/game/g_18_scan/step/waterfall_auction.rb index ac493a7bce..eb0680b093 100644 --- a/lib/engine/game/g_18_scan/step/waterfall_auction.rb +++ b/lib/engine/game/g_18_scan/step/waterfall_auction.rb @@ -17,6 +17,8 @@ def buy_company(player, company, price) @game.log << "Minor #{minor.name} floats" minor.owner = player minor.float! + @game.place_home_token(minor) + @game.bank.spend(price, minor) end end end From 40e5643b6615aba2f92af01144e5cc3201ab6fb8 Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Wed, 19 Jan 2022 13:12:33 +0300 Subject: [PATCH 02/24] 18Scan: support buying trains and getting subsidy by minors --- lib/engine/game/g_18_scan/game.rb | 4 ++- lib/engine/game/g_18_scan/step/buy_train.rb | 23 +++++++++++++++++ lib/engine/game/g_18_scan/step/dividend.rb | 28 +++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 lib/engine/game/g_18_scan/step/buy_train.rb create mode 100644 lib/engine/game/g_18_scan/step/dividend.rb diff --git a/lib/engine/game/g_18_scan/game.rb b/lib/engine/game/g_18_scan/game.rb index baf79c4b34..e129880bd7 100644 --- a/lib/engine/game/g_18_scan/game.rb +++ b/lib/engine/game/g_18_scan/game.rb @@ -38,6 +38,8 @@ class Game < Game::Base # Custom constants SJ_NAME = 'SJ' + MINOR_SUBSIDY = 10 + EVENTS_TEXT = Base::EVENTS_TEXT.merge( 'close_minors' => [ 'Minors merge into SJ', @@ -276,7 +278,7 @@ def operating_round(round_num) Engine::Step::Route, G18Scan::Step::Dividend, Engine::Step::DiscardTrain, - Engine::Step::BuyTrain, + G18Scan::Step::BuyTrain, ], round_num: round_num) end diff --git a/lib/engine/game/g_18_scan/step/buy_train.rb b/lib/engine/game/g_18_scan/step/buy_train.rb new file mode 100644 index 0000000000..fbfa15a964 --- /dev/null +++ b/lib/engine/game/g_18_scan/step/buy_train.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require_relative '../../../step/train' + +module Engine + module Game + module G18Scan + module Step + class BuyTrain < Engine::Step::BuyTrain + def can_entity_buy_train? + true + end + + def must_buy_train?(entity) + super unless entity.minor? + + false + end + end + end + end + end +end diff --git a/lib/engine/game/g_18_scan/step/dividend.rb b/lib/engine/game/g_18_scan/step/dividend.rb new file mode 100644 index 0000000000..a832fa4652 --- /dev/null +++ b/lib/engine/game/g_18_scan/step/dividend.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require_relative '../../../step/dividend' +require_relative '../../../step/minor_half_pay' + +module Engine + module Game + module G18Scan + module Step + class Dividend < Engine::Step::Dividend + include Engine::Step::MinorHalfPay + + def withhold(entity, revenue) + return super if entity.corporation? && entity.type != :minor + + { corporation: 0, per_share: @game.class::MINOR_SUBSIDY } + end + + def log_run_payout(entity, kind, revenue, action, payout) + return super if (entity.corporation? && entity.type != :minor) || revenue.positive? + + @log << "#{entity.owner.name} receives subsidy of #{@game.format_currency(payout[:per_share])}" + end + end + end + end + end +end From d9886028a0c799c975faec62998f6d591cf629f4 Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Wed, 19 Jan 2022 13:13:22 +0300 Subject: [PATCH 03/24] 18Scan: support dynamic float percent and switch to full cap --- lib/engine/game/g_18_scan/corporation.rb | 6 ++++++ lib/engine/game/g_18_scan/game.rb | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/engine/game/g_18_scan/corporation.rb b/lib/engine/game/g_18_scan/corporation.rb index e74d55e96b..baf19003f9 100644 --- a/lib/engine/game/g_18_scan/corporation.rb +++ b/lib/engine/game/g_18_scan/corporation.rb @@ -9,6 +9,12 @@ class Corporation < Engine::Corporation def floatable? super && @game.sj && @game.phase.status.include?('sj_unavailable') end + + def floated? + return false unless @floatable + + @floated ||= @ipo_owner.percent_of(self) <= 100 - @game.float_percent + end end end end diff --git a/lib/engine/game/g_18_scan/game.rb b/lib/engine/game/g_18_scan/game.rb index e129880bd7..8583d0ebe6 100644 --- a/lib/engine/game/g_18_scan/game.rb +++ b/lib/engine/game/g_18_scan/game.rb @@ -282,6 +282,26 @@ def operating_round(round_num) ], round_num: round_num) end + def ipo_name + 'Treasury' + end + + def float_percent + return 20 if @phase.status.include?('float_2') + return 30 if @phase.status.include?('float_3') + return 40 if @phase.status.include?('float_4') + + 50 + end + + def event_full_cap! + @corporations.each do |corp| + next if corp.floated? + + corp.capitalization = :full + end + end + def train_limit(entity) super + Array(abilities(entity, :train_limit)).sum(&:increase) end From ebf499bb9436a0dbda8eb449c80d064f9025764b Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Mon, 24 Jan 2022 16:40:22 +0300 Subject: [PATCH 04/24] 18Scan: minor adjustments to game constants --- lib/engine/game/g_18_scan/entities.rb | 3 +-- lib/engine/game/g_18_scan/game.rb | 8 ++++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/engine/game/g_18_scan/entities.rb b/lib/engine/game/g_18_scan/entities.rb index d429a393ad..2d8c141fb1 100644 --- a/lib/engine/game/g_18_scan/entities.rb +++ b/lib/engine/game/g_18_scan/entities.rb @@ -57,7 +57,7 @@ module Entities type: 'tile_lay', hexes: %w[F3], tiles: %w[403 121 584], - when: 'track', + when: 'owning_player_track', owner_type: 'player', count: 1, consume_tile_lay: true, @@ -232,7 +232,6 @@ module Entities float_percent: 50, floatable: false, tokens: [0, 100, 100, 100, 100, 100], - coordinates: 'F3', color: '#3561AE', abilities: [ { diff --git a/lib/engine/game/g_18_scan/game.rb b/lib/engine/game/g_18_scan/game.rb index 8583d0ebe6..24a3a78fa9 100644 --- a/lib/engine/game/g_18_scan/game.rb +++ b/lib/engine/game/g_18_scan/game.rb @@ -35,6 +35,8 @@ class Game < Game::Base CERT_LIMIT = { 2 => 18, 3 => 12, 4 => 9 }.freeze + EBUY_DEPOT_TRAIN_MUST_BE_CHEAPEST = false + # Custom constants SJ_NAME = 'SJ' @@ -98,7 +100,7 @@ class Game < Game::Base train_limit: 4, tiles: [:yellow], operating_rounds: 2, - status: %w[incremental_cap float_2], + status: %w[float_2 incremental_cap], }, { name: '3', @@ -195,7 +197,7 @@ class Game < Game::Base { 'nodes' => ['city'], 'pay' => 3, 'visit' => 3 }, { 'nodes' => ['town'], 'pay' => 3, 'visit' => 3 }, ], - price: 80, + price: 280, }, ], }, @@ -221,6 +223,7 @@ class Game < Game::Base { 'nodes' => ['city'], 'pay' => 5, 'visit' => 5 }, { 'nodes' => ['town'], 'pay' => 0, 'visit' => 99 }, ], + available_on: '5', price: 600, num: 2, }, @@ -230,6 +233,7 @@ class Game < Game::Base { 'nodes' => ['city'], 'pay' => 4, 'visit' => 4, 'multiplier' => 2 }, { 'nodes' => ['town'], 'pay' => 0, 'visit' => 99 }, ], + available_on: '5E', price: 800, num: 2, }, From d90225cce60b4c44f6bf807f8ff4676f97561a58 Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Mon, 24 Jan 2022 16:41:03 +0300 Subject: [PATCH 05/24] 18Scan: fix minor subsidies --- lib/engine/game/g_18_scan/step/dividend.rb | 33 ++++++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/lib/engine/game/g_18_scan/step/dividend.rb b/lib/engine/game/g_18_scan/step/dividend.rb index a832fa4652..6cb1b70897 100644 --- a/lib/engine/game/g_18_scan/step/dividend.rb +++ b/lib/engine/game/g_18_scan/step/dividend.rb @@ -10,16 +10,37 @@ module Step class Dividend < Engine::Step::Dividend include Engine::Step::MinorHalfPay - def withhold(entity, revenue) - return super if entity.corporation? && entity.type != :minor + def process_dividend(action) + # super clears routes so revenue must be calculated beforehand + revenue = @game.routes_revenue(routes) + entity = action.entity - { corporation: 0, per_share: @game.class::MINOR_SUBSIDY } + super + + return if (entity.corporation? && entity.type != :minor) || + revenue.positive? + + @game.bank.spend(@game.class::MINOR_SUBSIDY, entity) + + @log << "#{entity.owner.name} received subsidy of #{@game.format_currency(@game.class::MINOR_SUBSIDY)} from the bank" end - def log_run_payout(entity, kind, revenue, action, payout) - return super if (entity.corporation? && entity.type != :minor) || revenue.positive? + def share_price_change(entity, revenue = 0) + return {} if entity.minor? + + price = entity.share_price.price + + return { share_direction: :left, share_times: 1 } if revenue.zero? + + times = 0 + times = 1 if revenue >= price + times = 2 if revenue >= price * 2 - @log << "#{entity.owner.name} receives subsidy of #{@game.format_currency(payout[:per_share])}" + if times.positive? + { share_direction: :right, share_times: times } + else + {} + end end end end From 625dbc29913e09661b1c13c56c58bf554976d048 Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Mon, 24 Jan 2022 16:41:21 +0300 Subject: [PATCH 06/24] 18Scan: fix floating DSB in ISR --- lib/engine/game/g_18_scan/game.rb | 2 +- .../g_18_scan/step/company_pending_par.rb | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 lib/engine/game/g_18_scan/step/company_pending_par.rb diff --git a/lib/engine/game/g_18_scan/game.rb b/lib/engine/game/g_18_scan/game.rb index 24a3a78fa9..bf8f063d4c 100644 --- a/lib/engine/game/g_18_scan/game.rb +++ b/lib/engine/game/g_18_scan/game.rb @@ -262,7 +262,7 @@ def setup def new_auction_round Round::Auction.new(self, [ - Engine::Step::CompanyPendingPar, + G18Scan::Step::CompanyPendingPar, G18Scan::Step::WaterfallAuction, ]) end diff --git a/lib/engine/game/g_18_scan/step/company_pending_par.rb b/lib/engine/game/g_18_scan/step/company_pending_par.rb new file mode 100644 index 0000000000..5c56c45c74 --- /dev/null +++ b/lib/engine/game/g_18_scan/step/company_pending_par.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require_relative '../../../step/company_pending_par' + +module Engine + module Game + module G18Scan + module Step + class CompanyPendingPar < Engine::Step::CompanyPendingPar + def process_par(action) + corporation = action.corporation + + super + + # Place home token when DSB floats on SJS private buy in ISR + @game.place_home_token(corporation) if corporation.floated? + end + end + end + end + end +end From 7c222b1c747b32077ee00a5a697379991beb5b67 Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Thu, 27 Jan 2022 00:43:09 +0300 Subject: [PATCH 07/24] 18Scan: determine float percentage correctly --- lib/engine/game/g_18_scan/corporation.rb | 12 ++++++++++-- lib/engine/game/g_18_scan/game.rb | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/lib/engine/game/g_18_scan/corporation.rb b/lib/engine/game/g_18_scan/corporation.rb index baf19003f9..f71ccdeb59 100644 --- a/lib/engine/game/g_18_scan/corporation.rb +++ b/lib/engine/game/g_18_scan/corporation.rb @@ -6,8 +6,10 @@ module Engine module Game module G18Scan class Corporation < Engine::Corporation - def floatable? - super && @game.sj && @game.phase.status.include?('sj_unavailable') + def initialize(game, sym:, name:, **opts) + @game = game + + super(sym: sym, name: name, **opts) end def floated? @@ -15,6 +17,12 @@ def floated? @floated ||= @ipo_owner.percent_of(self) <= 100 - @game.float_percent end + + def percent_to_float + return 0 if @floated + + @ipo_owner.percent_of(self) - (100 - @game.float_percent) + end end end end diff --git a/lib/engine/game/g_18_scan/game.rb b/lib/engine/game/g_18_scan/game.rb index bf8f063d4c..164e45fb8a 100644 --- a/lib/engine/game/g_18_scan/game.rb +++ b/lib/engine/game/g_18_scan/game.rb @@ -4,6 +4,7 @@ require_relative '../base' require_relative 'entities' require_relative 'map' +require_relative 'corporation' module Engine module Game @@ -260,6 +261,17 @@ def setup end end + def init_corporations(stock_market) + game_corporations.map do |corporation| + G18Scan::Corporation.new( + self, + min_price: stock_market.par_prices.map(&:price).min, + capitalization: self.class::CAPITALIZATION, + **corporation.merge(corporation_opts), + ) + end + end + def new_auction_round Round::Auction.new(self, [ G18Scan::Step::CompanyPendingPar, @@ -298,6 +310,12 @@ def float_percent 50 end + def float_str(entity) + return 'Floats in phase 5' if entity == sj && !entity.floatable + + super + end + def event_full_cap! @corporations.each do |corp| next if corp.floated? From 1ac11f4650b65ce0043fc0cab4b6db5fbfaf8fed Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Thu, 27 Jan 2022 00:43:39 +0300 Subject: [PATCH 08/24] 18Scan: minor prettifying --- lib/engine/game/g_18_scan/game.rb | 6 ++++-- lib/engine/game/g_18_scan/step/buy_sell_par_shares.rb | 4 +--- lib/engine/game/g_18_scan/step/dividend.rb | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/engine/game/g_18_scan/game.rb b/lib/engine/game/g_18_scan/game.rb index 164e45fb8a..73ef7c6428 100644 --- a/lib/engine/game/g_18_scan/game.rb +++ b/lib/engine/game/g_18_scan/game.rb @@ -41,6 +41,8 @@ class Game < Game::Base # Custom constants SJ_NAME = 'SJ' + SJ_START_PRICE = 100 + MINOR_SUBSIDY = 10 EVENTS_TEXT = Base::EVENTS_TEXT.merge( @@ -138,7 +140,7 @@ class Game < Game::Base train_limit: 2, tiles: %w[yellow green brown], operating_rounds: 2, - status: %w[float_5 full_cap], + status: %w[float_5 full_cap sj_can_float], }, { name: '4D', @@ -146,7 +148,7 @@ class Game < Game::Base train_limit: 2, tiles: %w[yellow green brown], operating_rounds: 2, - status: %w[float_5 full_cap], + status: %w[float_5 full_cap sj_can_float], }, ].freeze diff --git a/lib/engine/game/g_18_scan/step/buy_sell_par_shares.rb b/lib/engine/game/g_18_scan/step/buy_sell_par_shares.rb index 357b5a66ed..0c70825399 100644 --- a/lib/engine/game/g_18_scan/step/buy_sell_par_shares.rb +++ b/lib/engine/game/g_18_scan/step/buy_sell_par_shares.rb @@ -7,13 +7,11 @@ module Game module G18Scan module Step class BuySellParShares < Engine::Step::BuySellParShares - SJ_START_PRICE = 100 - def get_par_prices(entity, corp) return super unless corp == @game.sj @game.stock_market.par_prices.select do |p| - p.price == SJ_START_PRICE && p.price * 2 <= entity.cash + p.price == @game.class::SJ_START_PRICE && p.price * 2 <= entity.cash end end end diff --git a/lib/engine/game/g_18_scan/step/dividend.rb b/lib/engine/game/g_18_scan/step/dividend.rb index 6cb1b70897..eef38cf0c0 100644 --- a/lib/engine/game/g_18_scan/step/dividend.rb +++ b/lib/engine/game/g_18_scan/step/dividend.rb @@ -22,7 +22,7 @@ def process_dividend(action) @game.bank.spend(@game.class::MINOR_SUBSIDY, entity) - @log << "#{entity.owner.name} received subsidy of #{@game.format_currency(@game.class::MINOR_SUBSIDY)} from the bank" + @log << "#{entity.owner.name} receives subsidy of #{@game.format_currency(@game.class::MINOR_SUBSIDY)}" end def share_price_change(entity, revenue = 0) From 2ef5018e97fa8b310d48674eb60903cd1168bfe6 Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Thu, 27 Jan 2022 00:43:50 +0300 Subject: [PATCH 09/24] 18Scan: support 2-player optional rule --- lib/engine/game/g_18_scan/game.rb | 47 +++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/lib/engine/game/g_18_scan/game.rb b/lib/engine/game/g_18_scan/game.rb index 73ef7c6428..709442dd38 100644 --- a/lib/engine/game/g_18_scan/game.rb +++ b/lib/engine/game/g_18_scan/game.rb @@ -274,6 +274,10 @@ def init_corporations(stock_market) end end + def corporation_opts + two_player? && @optional_rules&.include?(:two_player_share_limit) ? { max_ownership_percent: 70 } : {} + end + def new_auction_round Round::Auction.new(self, [ G18Scan::Step::CompanyPendingPar, @@ -292,7 +296,10 @@ def operating_round(round_num) Round::Operating.new(self, [ Engine::Step::Bankrupt, Engine::Step::Track, + # G18Scan::Step::DestinationToken + # G18Scan::Step::DestinationRun Engine::Step::Token, + # G18Scan::Step::BonusToken Engine::Step::Route, G18Scan::Step::Dividend, Engine::Step::DiscardTrain, @@ -318,6 +325,15 @@ def float_str(entity) super end + def event_close_minors! + @minors.each do |minor| + merge_and_close_minor(sj, minor.name) + end + + remove_ability(sj, :no_buy) + sj.floatable = true + end + def event_full_cap! @corporations.each do |corp| next if corp.floated? @@ -339,6 +355,37 @@ def sj_share_by_minor(name) return sj.shares[7] if name == '2' return sj.shares[8] if name == '3' end + + def merge_and_close_minor(entity, id) + company = company_by_id(id) + minor = minor_by_id(id) + share = trade_in_share_by_minor_id(id) + + transfer = minor.cash.positive? ? " that receives #{format_currency(minor.cash)}" : '' + @log << "-- Minor #{minor.name} merges into #{entity.name}#{transfer} --" + + share.buyable = true + @share_pool.buy_shares(minor.player, share, exchange: :free, exchange_price: 0) + + minor.tokens.each do |token| + if token.city.tokened_by?(entity) + new_token = Engine::Token.new(entity) + token.swap!(new_token) + entity.tokens << new_token + else + token.remove + end + end + + minor.spend(minor.cash, entity) if minor.cash.positive? + + minor.trains.each do |train| + buy_train(entity, train, :free) + end + + minor.close! + company.close! + end end end end From cb217ebce4055ad956371fe35c15600ae7d7b7a2 Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Thu, 27 Jan 2022 11:22:54 +0300 Subject: [PATCH 10/24] 18Scan: fix train buying requirement --- lib/engine/game/g_18_scan/step/buy_train.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/engine/game/g_18_scan/step/buy_train.rb b/lib/engine/game/g_18_scan/step/buy_train.rb index fbfa15a964..1b3d77f768 100644 --- a/lib/engine/game/g_18_scan/step/buy_train.rb +++ b/lib/engine/game/g_18_scan/step/buy_train.rb @@ -12,7 +12,7 @@ def can_entity_buy_train? end def must_buy_train?(entity) - super unless entity.minor? + return super unless entity.minor? false end From 7613bc6f277bafe3052c5aaf306f1fcdf6f1cbae Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Fri, 28 Jan 2022 13:24:59 +0300 Subject: [PATCH 11/24] 18Scan: put events to correct places --- lib/engine/game/g_18_scan/game.rb | 36 ++++++++++++------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/lib/engine/game/g_18_scan/game.rb b/lib/engine/game/g_18_scan/game.rb index 709442dd38..e6d35ba36e 100644 --- a/lib/engine/game/g_18_scan/game.rb +++ b/lib/engine/game/g_18_scan/game.rb @@ -47,12 +47,12 @@ class Game < Game::Base EVENTS_TEXT = Base::EVENTS_TEXT.merge( 'close_minors' => [ - 'Minors merge into SJ', + 'SJ merger', 'Minors are closed, transferring all assets to SJ. Minor owners get a 10% SJ share', ], 'full_cap' => [ - 'Full capitalisation', - 'Corporationss receive full capitalisation when started', + 'Full Capitalization', + 'All unfloated corporations will receive full funding on float', ], ).freeze @@ -73,14 +73,6 @@ class Game < Game::Base '50% to float', 'An unstarted corporation needs 50% sold to start for the first time', ], - 'incremental_cap' => [ - 'Incremental capitalization', - 'Corporations receive capitalisation for sold shares when started', - ], - 'full_cap' => [ - 'Full capitalization', - 'Corporations receive full capitalisation when started', - ], 'sj_can_float' => [ 'SJ can float', 'SJ can float if 50% of its shares are sold, receiving K700 from the bank', @@ -103,7 +95,7 @@ class Game < Game::Base train_limit: 4, tiles: [:yellow], operating_rounds: 2, - status: %w[float_2 incremental_cap], + status: %w[float_2], }, { name: '3', @@ -111,7 +103,7 @@ class Game < Game::Base train_limit: 4, tiles: %w[yellow green], operating_rounds: 2, - status: %w[float_3 incremental_cap], + status: %w[float_3], }, { name: '4', @@ -119,7 +111,7 @@ class Game < Game::Base train_limit: 3, tiles: %w[yellow green], operating_rounds: 2, - status: %w[float_4 incremental_cap], + status: %w[float_4], }, { name: '5', @@ -127,12 +119,7 @@ class Game < Game::Base train_limit: 2, tiles: %w[yellow green brown], operating_rounds: 2, - events: [ - { 'type' => 'close_minors' }, - { 'type' => 'close_companies' }, - { 'type' => 'full_cap' }, - ], - status: %w[float_5 full_cap sj_can_float], + status: %w[float_5 sj_can_float], }, { name: '5E', @@ -140,7 +127,7 @@ class Game < Game::Base train_limit: 2, tiles: %w[yellow green brown], operating_rounds: 2, - status: %w[float_5 full_cap sj_can_float], + status: %w[float_5 sj_can_float], }, { name: '4D', @@ -148,7 +135,7 @@ class Game < Game::Base train_limit: 2, tiles: %w[yellow green brown], operating_rounds: 2, - status: %w[float_5 full_cap sj_can_float], + status: %w[float_5 sj_can_float], }, ].freeze @@ -219,6 +206,11 @@ class Game < Game::Base price: 480, }, ], + events: [ + { 'type' => 'close_companies' }, + { 'type' => 'full_cap' }, + { 'type' => 'close_minors' }, + ], }, { name: '5E', From 645e82e802a854a94e5f6e78f132b58c991bcbc2 Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Fri, 28 Jan 2022 13:26:32 +0300 Subject: [PATCH 12/24] 18Scan: fix implementation of SJ merger --- lib/engine/game/g_18_scan/entities.rb | 4 -- lib/engine/game/g_18_scan/game.rb | 72 +++++++++++++++++---------- 2 files changed, 47 insertions(+), 29 deletions(-) diff --git a/lib/engine/game/g_18_scan/entities.rb b/lib/engine/game/g_18_scan/entities.rb index 2d8c141fb1..60bb17316d 100644 --- a/lib/engine/game/g_18_scan/entities.rb +++ b/lib/engine/game/g_18_scan/entities.rb @@ -239,10 +239,6 @@ module Entities description: '+1 train limit', increase: 1, }, - { - type: 'no_buy', - description: 'Cannot float before phase 5', - }, ], }, ].freeze diff --git a/lib/engine/game/g_18_scan/game.rb b/lib/engine/game/g_18_scan/game.rb index e6d35ba36e..3fea7c44d3 100644 --- a/lib/engine/game/g_18_scan/game.rb +++ b/lib/engine/game/g_18_scan/game.rb @@ -235,9 +235,6 @@ class Game < Game::Base ].freeze def setup - # SJ cannot float until phase 5 - sj.floatable = false - # Minors come with a trade-in share of SJ minors.each do |minor| share = sj_share_by_minor(minor.name) @@ -318,12 +315,23 @@ def float_str(entity) end def event_close_minors! - @minors.each do |minor| - merge_and_close_minor(sj, minor.name) - end - - remove_ability(sj, :no_buy) sj.floatable = true + sj.spend(sj.cash, bank) if sj.cash.positive? + + @minors.each { |minor| merge_and_close_minor(minor) } + + @log << "-- Event: #{sj.name} is formed --" + end + + def float_corporation(corporation) + return super unless corporation == sj + + @log << "#{corporation.name} floats" + + initial_cash = corporation.par_price.price * 7 + @bank.spend(initial_cash, corporation) + + @log << "#{corporation.name} receives #{format_currency(initial_cash)}" end def event_full_cap! @@ -331,7 +339,10 @@ def event_full_cap! next if corp.floated? corp.capitalization = :full + corp.spend(corp.cash, bank) if corp.cash.positive? end + + @log << '-- Event: New corporations will be started as full capitalization --' end def train_limit(entity) @@ -343,36 +354,47 @@ def sj end def sj_share_by_minor(name) - return sj.shares[6] if name == '1' - return sj.shares[7] if name == '2' - return sj.shares[8] if name == '3' + @reserved_shares ||= {} + @reserved_shares[name] ||= + case name + when '1' + sj.shares[6] + when '2' + sj.shares[7] + when '3' + sj.shares[8] + end end - def merge_and_close_minor(entity, id) - company = company_by_id(id) - minor = minor_by_id(id) - share = trade_in_share_by_minor_id(id) + def merge_and_close_minor(minor) + company = company_by_id(minor.name) + share = sj_share_by_minor(minor.name) - transfer = minor.cash.positive? ? " that receives #{format_currency(minor.cash)}" : '' - @log << "-- Minor #{minor.name} merges into #{entity.name}#{transfer} --" + msg = "#{minor.name} merges into #{sj.name}" + msg += ' receiving' if minor.cash.positive? || minor.trains.any? + msg += " #{minor.trains.map(&:name).join(', ')}" if minor.trains.any? + msg += ' and' if minor.cash.positive? && minor.trains.any? + msg += " #{format_currency(minor.cash)}" if minor.cash.positive? + @log << msg + # Award reserved share share.buyable = true @share_pool.buy_shares(minor.player, share, exchange: :free, exchange_price: 0) + # Transfer tokens minor.tokens.each do |token| - if token.city.tokened_by?(entity) - new_token = Engine::Token.new(entity) - token.swap!(new_token) - entity.tokens << new_token + if !token.hex || token.hex.tile.cities.any? { |c| c.tokened_by?(sj) } + token.remove! else - token.remove + token.swap!(sj.next_token) end end - minor.spend(minor.cash, entity) if minor.cash.positive? + minor.spend(minor.cash, sj) if minor.cash.positive? - minor.trains.each do |train| - buy_train(entity, train, :free) + # Transfer trains + minor.trains.dup.each do |train| + buy_train(sj, train, :free) end minor.close! From a31ff1276a75d12cb284d14d1c6e96d46f1ecf72 Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Fri, 28 Jan 2022 13:27:07 +0300 Subject: [PATCH 13/24] 18Scan: fix map hexes issues --- lib/engine/game/g_18_scan/map.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/engine/game/g_18_scan/map.rb b/lib/engine/game/g_18_scan/map.rb index db6195495a..81e9896fa2 100644 --- a/lib/engine/game/g_18_scan/map.rb +++ b/lib/engine/game/g_18_scan/map.rb @@ -74,6 +74,7 @@ module Map 'F15' => 'Tampere', 'G2' => 'Stettin', 'G4' => 'Malmö', + 'G12' => 'Ferry crossing', 'G14' => 'Helsinki', 'G16' => 'Lahti', 'H13' => 'Tallin', @@ -116,8 +117,7 @@ module Map ['E4'] => 'city=revenue:30;border=edge:3,type:impassable,color:blue;border=edge:4,type:impassable,color:blue;border=edge:5,type:impassable,color:blue', ['E12'] => 'border=edge:4,type:impassable,color:blue;border=edge:5,type:impassable,color:blue', - # TODO: Copenhagen - ['F3'] => 'city=revenue:30,loc:4.5;town=revenue:0,loc:1.5;upgrade=cost:40,terrain:water', + ['F3'] => 'city=revenue:30,loc:4.5;town=revenue:0,loc:1.5;upgrade=cost:40,terrain:water;label=COP', ['F13'] => 'city=revenue:30;border=edge:1,type:impassable,color:blue;border=edge:2,type:impassable,color:blue', @@ -128,7 +128,7 @@ module Map ['G14'] => 'city=revenue:30;label=Y', }, gray: { - ['G12'] => 'path=a:2,b:3,track:narrow', + ['G12'] => 'path=a:2,b:3', }, }.freeze end From 0b0259c329d2f0e9bc3e1ed18dd26783f6bedafc Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Fri, 28 Jan 2022 23:35:34 +0300 Subject: [PATCH 14/24] 18Scan: reorder game class methods for readability --- lib/engine/game/g_18_scan/game.rb | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/engine/game/g_18_scan/game.rb b/lib/engine/game/g_18_scan/game.rb index 3fea7c44d3..3b8c0e6207 100644 --- a/lib/engine/game/g_18_scan/game.rb +++ b/lib/engine/game/g_18_scan/game.rb @@ -300,6 +300,10 @@ def ipo_name 'Treasury' end + def sj + @sj ||= corporation_by_id('SJ') + end + def float_percent return 20 if @phase.status.include?('float_2') return 30 if @phase.status.include?('float_3') @@ -314,15 +318,6 @@ def float_str(entity) super end - def event_close_minors! - sj.floatable = true - sj.spend(sj.cash, bank) if sj.cash.positive? - - @minors.each { |minor| merge_and_close_minor(minor) } - - @log << "-- Event: #{sj.name} is formed --" - end - def float_corporation(corporation) return super unless corporation == sj @@ -345,12 +340,17 @@ def event_full_cap! @log << '-- Event: New corporations will be started as full capitalization --' end - def train_limit(entity) - super + Array(abilities(entity, :train_limit)).sum(&:increase) + def event_close_minors! + sj.floatable = true + sj.spend(sj.cash, bank) if sj.cash.positive? + + @minors.each { |minor| merge_and_close_minor(minor) } + + @log << "-- Event: #{sj.name} is formed --" end - def sj - @sj ||= corporation_by_id('SJ') + def train_limit(entity) + super + Array(abilities(entity, :train_limit)).sum(&:increase) end def sj_share_by_minor(name) From 65433b7155f21ef7f70827cd689efab64ad997b3 Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Fri, 28 Jan 2022 23:38:04 +0300 Subject: [PATCH 15/24] 18Scan: add bonus revenue for company tokens --- lib/engine/game/g_18_scan/entities.rb | 16 +++++++++++-- lib/engine/game/g_18_scan/game.rb | 34 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/lib/engine/game/g_18_scan/entities.rb b/lib/engine/game/g_18_scan/entities.rb index 60bb17316d..a4ac4bf745 100644 --- a/lib/engine/game/g_18_scan/entities.rb +++ b/lib/engine/game/g_18_scan/entities.rb @@ -18,7 +18,13 @@ module Entities abilities: [ { type: 'no_buy' }, { type: 'shares', shares: 'VR_1' }, - # TODO: Two +20 bonus tokens for ferry + # TODO: Ferry token svg + { + type: 'assign_corporation', + when: 'owning_player_token', + count: 2, + owner_type: 'player', + }, ], color: nil, }, @@ -36,7 +42,13 @@ module Entities abilities: [ { type: 'no_buy' }, { type: 'shares', shares: 'S&NJ_1' }, - # TODO: +50 bonus token for Kiruna + # TODO: Ore mine token svg + { + type: 'assign_corporation', + when: 'owning_player_token', + count: 1, + owner_type: 'player', + }, ], color: nil, }, diff --git a/lib/engine/game/g_18_scan/game.rb b/lib/engine/game/g_18_scan/game.rb index 3b8c0e6207..36777b7d16 100644 --- a/lib/engine/game/g_18_scan/game.rb +++ b/lib/engine/game/g_18_scan/game.rb @@ -304,6 +304,22 @@ def sj @sj ||= corporation_by_id('SJ') end + def mine + @mine ||= company_by_id('Mine') + end + + def mine_included(route) + route.corporation.assigned?(mine.id) && route.hexes.find { |h| h.id == 'A20' } + end + + def ferry + @ferry ||= company_by_id('Ferry') + end + + def ferry_included(route) + route.corporation.assigned?(ferry.id) && route.hexes.find { |h| h.id == 'L7' } + end + def float_percent return 20 if @phase.status.include?('float_2') return 30 if @phase.status.include?('float_3') @@ -329,6 +345,24 @@ def float_corporation(corporation) @log << "#{corporation.name} receives #{format_currency(initial_cash)}" end + def revenue_for(route, stops) + revenue = super + + revenue += 20 if ferry_included(route) + revenue += 50 if mine_included(route) + + revenue + end + + def revenue_str(route) + str = super + + str += ' + Stockholm-Åbo Ferry' if ferry_included(route) + str += ' + Lapland Ore Mine' if mine_included(route) + + str + end + def event_full_cap! @corporations.each do |corp| next if corp.floated? From 0ec254d67bfbacce0f9d3c34eb16bfbf981723e5 Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Mon, 31 Jan 2022 11:40:26 +0300 Subject: [PATCH 16/24] 18Scan: allow unusual tile upgrades --- lib/engine/game/g_18_scan/game.rb | 23 +++++++++++++++- lib/engine/game/g_18_scan/step/track.rb | 35 +++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 lib/engine/game/g_18_scan/step/track.rb diff --git a/lib/engine/game/g_18_scan/game.rb b/lib/engine/game/g_18_scan/game.rb index 36777b7d16..71adfcf457 100644 --- a/lib/engine/game/g_18_scan/game.rb +++ b/lib/engine/game/g_18_scan/game.rb @@ -284,7 +284,7 @@ def stock_round def operating_round(round_num) Round::Operating.new(self, [ Engine::Step::Bankrupt, - Engine::Step::Track, + G18Scan::Step::Track, # G18Scan::Step::DestinationToken # G18Scan::Step::DestinationRun Engine::Step::Token, @@ -363,6 +363,27 @@ def revenue_str(route) str end + def upgrades_to?(from, to, _special = false, selected_company: nil) + # Y cities are same as plain in yellow + return to.name == '5' if from.color == :white && from.label.to_s == 'Y' + + # Copenhagen + return to.name == '121' if from.name == '403' + return to.name == '584' if from.name == '121' + + # Oslo + return to.name == '623' if from.name == '622' && from.hex.name == 'D7' + + # Helsinki, Stockholm + return to.name == '582' if from.name == '622' && %w[G14 F11].include?(from.hex.name) + + super + end + + def copenhagen_dit_upgrade(from, to) + from.name == '403' && to.name == '121' + end + def event_full_cap! @corporations.each do |corp| next if corp.floated? diff --git a/lib/engine/game/g_18_scan/step/track.rb b/lib/engine/game/g_18_scan/step/track.rb new file mode 100644 index 0000000000..560fc6998e --- /dev/null +++ b/lib/engine/game/g_18_scan/step/track.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require_relative '../../../step/track' + +module Engine + module Game + module G18Scan + module Step + class Track < Engine::Step::Track + def old_exits_are_preserved(old_paths, new_paths) + old_exits = old_paths.flat_map(&:exits).uniq + new_exits = new_paths.flat_map(&:exits).uniq + + (old_exits - new_exits).empty? + end + + def legal_tile_rotation?(entity, hex, tile) + return super unless @game.copenhagen_dit_upgrade(hex.tile, tile) + + # simplified version of super + old_paths = hex.tile.paths + + new_paths = tile.paths + new_exits = tile.exits + + # substituted path check: allow dit/city -> city/city upgrade + new_exits.all? { |edge| hex.neighbors[edge] } && + !(new_exits & hex_neighbors(entity, hex)).empty? && + old_exits_are_preserved(old_paths, new_paths) + end + end + end + end + end +end From 32aa95d5da78309ec9814c5d9250d472f93d03a8 Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Mon, 7 Feb 2022 13:03:33 +0300 Subject: [PATCH 17/24] 18Scan: show minors in spreadsheet --- lib/engine/game/g_18_scan/game.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/engine/game/g_18_scan/game.rb b/lib/engine/game/g_18_scan/game.rb index 71adfcf457..810fba0086 100644 --- a/lib/engine/game/g_18_scan/game.rb +++ b/lib/engine/game/g_18_scan/game.rb @@ -267,6 +267,10 @@ def corporation_opts two_player? && @optional_rules&.include?(:two_player_share_limit) ? { max_ownership_percent: 70 } : {} end + def all_corporations + minors + corporations + end + def new_auction_round Round::Auction.new(self, [ G18Scan::Step::CompanyPendingPar, From 6e9d95548293261b6b15f08f0d57f77ae3f48d68 Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Tue, 8 Feb 2022 14:55:05 +0300 Subject: [PATCH 18/24] 18Scan: Display all train limits in game info --- lib/engine/game/g_18_scan/entities.rb | 15 ++++++++------- lib/engine/game/g_18_scan/game.rb | 16 ++++++---------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/lib/engine/game/g_18_scan/entities.rb b/lib/engine/game/g_18_scan/entities.rb index a4ac4bf745..3902ab884e 100644 --- a/lib/engine/game/g_18_scan/entities.rb +++ b/lib/engine/game/g_18_scan/entities.rb @@ -123,6 +123,7 @@ module Entities name: 'Södra Stambanan', logo: '18_scan/1', simple_logo: '18_scan/1.alt', + type: 'minor', tokens: [0, 40], coordinates: 'G4', destination_coordinates: 'B11', @@ -146,6 +147,7 @@ module Entities name: 'Nordvärsta Stambanan', logo: '18_scan/2', simple_logo: '18_scan/2.alt', + type: 'minor', tokens: [0, 40], coordinates: 'F11', city: 1, @@ -170,6 +172,7 @@ module Entities name: 'Västra Stambanan', logo: '18_scan/3', simple_logo: '18_scan/3.alt', + type: 'minor', tokens: [0, 40], coordinates: 'F11', city: 0, @@ -195,6 +198,7 @@ module Entities { sym: 'DSB', name: 'Danske Statsbaner', + type: 'major', logo: '18_scan/DSB', simple_logo: '18_scan/DSB.alt', float_percent: 20, @@ -206,6 +210,7 @@ module Entities { sym: 'S&NJ', name: 'Sveriges & Norges Järnvägar', + type: 'major', logo: '18_scan/SNJ', simple_logo: '18_scan/SNJ.alt', float_percent: 20, @@ -217,6 +222,7 @@ module Entities { sym: 'NSB', name: 'Norges Statsbaner', + type: 'major', logo: '18_scan/NSB', simple_logo: '18_scan/NSB.alt', float_percent: 20, @@ -228,6 +234,7 @@ module Entities { sym: 'VR', name: 'Valtionraurariet', + type: 'major', logo: '18_scan/VR', simple_logo: '18_scan/VR.alt', float_percent: 20, @@ -241,17 +248,11 @@ module Entities name: 'Statens Järnvägar', logo: '18_scan/SJ', simple_logo: '18_scan/SJ.alt', + type: 'national', float_percent: 50, floatable: false, tokens: [0, 100, 100, 100, 100, 100], color: '#3561AE', - abilities: [ - { - type: 'train_limit', - description: '+1 train limit', - increase: 1, - }, - ], }, ].freeze end diff --git a/lib/engine/game/g_18_scan/game.rb b/lib/engine/game/g_18_scan/game.rb index 810fba0086..d81c2b32fe 100644 --- a/lib/engine/game/g_18_scan/game.rb +++ b/lib/engine/game/g_18_scan/game.rb @@ -92,7 +92,7 @@ class Game < Game::Base PHASES = [ { name: '2', - train_limit: 4, + train_limit: { minor: 2, major: 4 }, tiles: [:yellow], operating_rounds: 2, status: %w[float_2], @@ -100,7 +100,7 @@ class Game < Game::Base { name: '3', on: '3', - train_limit: 4, + train_limit: { minor: 2, major: 4 }, tiles: %w[yellow green], operating_rounds: 2, status: %w[float_3], @@ -108,7 +108,7 @@ class Game < Game::Base { name: '4', on: '4', - train_limit: 3, + train_limit: { minor: 2, major: 3 }, tiles: %w[yellow green], operating_rounds: 2, status: %w[float_4], @@ -116,7 +116,7 @@ class Game < Game::Base { name: '5', on: '5', - train_limit: 2, + train_limit: { national: 3, major: 2 }, tiles: %w[yellow green brown], operating_rounds: 2, status: %w[float_5 sj_can_float], @@ -124,7 +124,7 @@ class Game < Game::Base { name: '5E', on: '5E', - train_limit: 2, + train_limit: { national: 3, major: 2 }, tiles: %w[yellow green brown], operating_rounds: 2, status: %w[float_5 sj_can_float], @@ -132,7 +132,7 @@ class Game < Game::Base { name: '4D', on: '4D', - train_limit: 2, + train_limit: { national: 3, major: 2 }, tiles: %w[yellow green brown], operating_rounds: 2, status: %w[float_5 sj_can_float], @@ -408,10 +408,6 @@ def event_close_minors! @log << "-- Event: #{sj.name} is formed --" end - def train_limit(entity) - super + Array(abilities(entity, :train_limit)).sum(&:increase) - end - def sj_share_by_minor(name) @reserved_shares ||= {} @reserved_shares[name] ||= From 6c1cefc3e874f593f14f0982a0ed6097b4436e8d Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Wed, 16 Feb 2022 11:48:37 +0300 Subject: [PATCH 19/24] 18Scan: fix selling configuration --- lib/engine/game/g_18_scan/game.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/engine/game/g_18_scan/game.rb b/lib/engine/game/g_18_scan/game.rb index d81c2b32fe..b63afbe063 100644 --- a/lib/engine/game/g_18_scan/game.rb +++ b/lib/engine/game/g_18_scan/game.rb @@ -26,6 +26,8 @@ class Game < Game::Base CAPITALIZATION = :incremental + MUST_SELL_IN_BLOCKS = true + SELL_AFTER = :operate SELL_BUY_ORDER = :sell_buy From 857a461deef3bb37c81c914f1ab182ddb64dfcb6 Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Wed, 16 Feb 2022 11:49:04 +0300 Subject: [PATCH 20/24] 18Scan: limit running to non-tokened offboards --- lib/engine/game/g_18_scan/game.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/engine/game/g_18_scan/game.rb b/lib/engine/game/g_18_scan/game.rb index b63afbe063..8c1c225ec6 100644 --- a/lib/engine/game/g_18_scan/game.rb +++ b/lib/engine/game/g_18_scan/game.rb @@ -369,6 +369,18 @@ def revenue_str(route) str end + def check_connected(route, corporation) + super(route, corporation) + + route.visited_stops.each do |node| + # Cannot rely on offboard? because all of them are cities + next unless node.tile.color == :red && node.city? + next if node.tokened_by?(corporation) + + raise GameError, 'Can only run to tokened offboards' + end + end + def upgrades_to?(from, to, _special = false, selected_company: nil) # Y cities are same as plain in yellow return to.name == '5' if from.color == :white && from.label.to_s == 'Y' From 9e057af3705bfc722299ec251126ec6c7608940c Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Wed, 16 Feb 2022 15:58:06 +0300 Subject: [PATCH 21/24] Move step to buy one train of type per OR to game core --- lib/engine/game/g_1836_jr30/game.rb | 2 +- lib/engine/game/g_1836_jr30/step/buy_train.rb | 34 ------------------- lib/engine/game/g_18_scan/step/buy_train.rb | 2 +- lib/engine/step/buy_single_train_of_type.rb | 31 +++++++++++++++++ 4 files changed, 33 insertions(+), 36 deletions(-) delete mode 100644 lib/engine/game/g_1836_jr30/step/buy_train.rb create mode 100644 lib/engine/step/buy_single_train_of_type.rb diff --git a/lib/engine/game/g_1836_jr30/game.rb b/lib/engine/game/g_1836_jr30/game.rb index bf7c9a4d90..2ee1596cc8 100644 --- a/lib/engine/game/g_1836_jr30/game.rb +++ b/lib/engine/game/g_1836_jr30/game.rb @@ -453,7 +453,7 @@ def operating_round(round_num) Engine::Step::Route, Engine::Step::Dividend, Engine::Step::DiscardTrain, - G1836Jr30::Step::BuyTrain, + Engine::Step::BuySingleTrainOfType, [Engine::Step::BuyCompany, { blocks: true }], ], round_num: round_num) end diff --git a/lib/engine/game/g_1836_jr30/step/buy_train.rb b/lib/engine/game/g_1836_jr30/step/buy_train.rb deleted file mode 100644 index cd7bdf6e1c..0000000000 --- a/lib/engine/game/g_1836_jr30/step/buy_train.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../../step/buy_train' - -module Engine - module Game - module G1836Jr30 - module Step - class BuyTrain < Engine::Step::BuyTrain - def buyable_trains(entity) - super.reject { |x| x.from_depot? && @depot_trains_bought.include?(x.sym) } - end - - def setup - super - @depot_trains_bought = [] - end - - def process_buy_train(action) - # Since the train won't be in the depot after being bought store the state now. - from_depot = action.train.from_depot? - super - - return unless from_depot - - @depot_trains_bought << action.train.sym - - pass! if buyable_trains(action.entity).empty? - end - end - end - end - end -end diff --git a/lib/engine/game/g_18_scan/step/buy_train.rb b/lib/engine/game/g_18_scan/step/buy_train.rb index 1b3d77f768..4492e4aee7 100644 --- a/lib/engine/game/g_18_scan/step/buy_train.rb +++ b/lib/engine/game/g_18_scan/step/buy_train.rb @@ -6,7 +6,7 @@ module Engine module Game module G18Scan module Step - class BuyTrain < Engine::Step::BuyTrain + class BuyTrain < Engine::Step::BuySingleTrainOfType def can_entity_buy_train? true end diff --git a/lib/engine/step/buy_single_train_of_type.rb b/lib/engine/step/buy_single_train_of_type.rb new file mode 100644 index 0000000000..30db5f2275 --- /dev/null +++ b/lib/engine/step/buy_single_train_of_type.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require_relative 'buy_train' + +module Engine + module Step + class BuySingleTrainOfType < BuyTrain + def setup + super + @depot_trains_bought = [] + end + + def buyable_trains(entity) + super.reject { |x| x.from_depot? && @depot_trains_bought.include?(x.sym) } + end + + def process_buy_train(action) + # Since the train won't be in the depot after being bought store the state now. + from_depot = action.train.from_depot? + + super + + return unless from_depot + + @depot_trains_bought << action.train.sym + + pass! if buyable_trains(action.entity).empty? + end + end + end +end From d27929d681f2618615269faa53ae885eb318b590 Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Wed, 16 Feb 2022 16:41:35 +0300 Subject: [PATCH 22/24] 18Scan: fix upgrading yellow Y tiles --- lib/engine/game/g_18_scan/step/track.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/engine/game/g_18_scan/step/track.rb b/lib/engine/game/g_18_scan/step/track.rb index 560fc6998e..3d38bbe74a 100644 --- a/lib/engine/game/g_18_scan/step/track.rb +++ b/lib/engine/game/g_18_scan/step/track.rb @@ -28,6 +28,19 @@ def legal_tile_rotation?(entity, hex, tile) !(new_exits & hex_neighbors(entity, hex)).empty? && old_exits_are_preserved(old_paths, new_paths) end + + # preserve label on Y cities because they use unlabeled yellow tiles + def process_lay_tile(action) + old_tile = action.hex.tile + + super + + return unless old_tile.label.to_s == 'Y' + + old_tile.label = nil if old_tile.color == :yellow + + action.tile.label = 'Y' if action.tile.color == :yellow + end end end end From 467b910c141a7a8d79af06d7a00f145b8345b7a0 Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Fri, 18 Feb 2022 11:37:30 +0300 Subject: [PATCH 23/24] 18Scan: fix code review remarks --- lib/engine/game/g_18_scan/game.rb | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/engine/game/g_18_scan/game.rb b/lib/engine/game/g_18_scan/game.rb index 8c1c225ec6..b51342b5d0 100644 --- a/lib/engine/game/g_18_scan/game.rb +++ b/lib/engine/game/g_18_scan/game.rb @@ -315,7 +315,7 @@ def mine end def mine_included(route) - route.corporation.assigned?(mine.id) && route.hexes.find { |h| h.id == 'A20' } + route.corporation.assigned?(mine.id) && route.hexes.any? { |h| h.id == 'A20' } end def ferry @@ -323,7 +323,7 @@ def ferry end def ferry_included(route) - route.corporation.assigned?(ferry.id) && route.hexes.find { |h| h.id == 'L7' } + route.corporation.assigned?(ferry.id) && route.hexes.any? { |h| h.id == 'L7' } end def float_percent @@ -374,10 +374,9 @@ def check_connected(route, corporation) route.visited_stops.each do |node| # Cannot rely on offboard? because all of them are cities - next unless node.tile.color == :red && node.city? - next if node.tokened_by?(corporation) - - raise GameError, 'Can only run to tokened offboards' + if node.tile.color == :red && node.city? && !node.tokened_by?(corporation) + raise GameError, 'Can only run to tokened offboards' + end end end @@ -451,7 +450,7 @@ def merge_and_close_minor(minor) @share_pool.buy_shares(minor.player, share, exchange: :free, exchange_price: 0) # Transfer tokens - minor.tokens.each do |token| + minor.tokens.dup.each do |token| if !token.hex || token.hex.tile.cities.any? { |c| c.tokened_by?(sj) } token.remove! else From 9707408a754968b6ee8361c8a0aa8f90d5db163f Mon Sep 17 00:00:00 2001 From: Aleksandr Vansach Date: Fri, 18 Feb 2022 12:11:34 +0300 Subject: [PATCH 24/24] 18Scan: move dynamic float percentage configuration to events --- lib/engine/game/g_18_scan/corporation.rb | 29 -------- lib/engine/game/g_18_scan/game.rb | 84 ++++++++++++------------ 2 files changed, 43 insertions(+), 70 deletions(-) delete mode 100644 lib/engine/game/g_18_scan/corporation.rb diff --git a/lib/engine/game/g_18_scan/corporation.rb b/lib/engine/game/g_18_scan/corporation.rb deleted file mode 100644 index f71ccdeb59..0000000000 --- a/lib/engine/game/g_18_scan/corporation.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../corporation' - -module Engine - module Game - module G18Scan - class Corporation < Engine::Corporation - def initialize(game, sym:, name:, **opts) - @game = game - - super(sym: sym, name: name, **opts) - end - - def floated? - return false unless @floatable - - @floated ||= @ipo_owner.percent_of(self) <= 100 - @game.float_percent - end - - def percent_to_float - return 0 if @floated - - @ipo_owner.percent_of(self) - (100 - @game.float_percent) - end - end - end - end -end diff --git a/lib/engine/game/g_18_scan/game.rb b/lib/engine/game/g_18_scan/game.rb index b51342b5d0..6c9e907853 100644 --- a/lib/engine/game/g_18_scan/game.rb +++ b/lib/engine/game/g_18_scan/game.rb @@ -4,7 +4,6 @@ require_relative '../base' require_relative 'entities' require_relative 'map' -require_relative 'corporation' module Engine module Game @@ -48,21 +47,6 @@ class Game < Game::Base MINOR_SUBSIDY = 10 EVENTS_TEXT = Base::EVENTS_TEXT.merge( - 'close_minors' => [ - 'SJ merger', - 'Minors are closed, transferring all assets to SJ. Minor owners get a 10% SJ share', - ], - 'full_cap' => [ - 'Full Capitalization', - 'All unfloated corporations will receive full funding on float', - ], - ).freeze - - STATUS_TEXT = { - 'float_2' => [ - '20% to float', - 'An unstarted corporation needs 20% sold to start for the first time', - ], 'float_3' => [ '30% to float', 'An unstarted corporation needs 30% sold to start for the first time', @@ -75,6 +59,17 @@ class Game < Game::Base '50% to float', 'An unstarted corporation needs 50% sold to start for the first time', ], + 'close_minors' => [ + 'SJ merger', + 'Minors are closed, transferring all assets to SJ. Minor owners get a 10% SJ share', + ], + 'full_cap' => [ + 'Full Capitalization', + 'All unfloated corporations will receive full funding on float', + ], + ).freeze + + STATUS_TEXT = { 'sj_can_float' => [ 'SJ can float', 'SJ can float if 50% of its shares are sold, receiving K700 from the bank', @@ -97,7 +92,6 @@ class Game < Game::Base train_limit: { minor: 2, major: 4 }, tiles: [:yellow], operating_rounds: 2, - status: %w[float_2], }, { name: '3', @@ -105,7 +99,6 @@ class Game < Game::Base train_limit: { minor: 2, major: 4 }, tiles: %w[yellow green], operating_rounds: 2, - status: %w[float_3], }, { name: '4', @@ -113,7 +106,6 @@ class Game < Game::Base train_limit: { minor: 2, major: 3 }, tiles: %w[yellow green], operating_rounds: 2, - status: %w[float_4], }, { name: '5', @@ -121,7 +113,7 @@ class Game < Game::Base train_limit: { national: 3, major: 2 }, tiles: %w[yellow green brown], operating_rounds: 2, - status: %w[float_5 sj_can_float], + status: %w[sj_can_float], }, { name: '5E', @@ -129,7 +121,7 @@ class Game < Game::Base train_limit: { national: 3, major: 2 }, tiles: %w[yellow green brown], operating_rounds: 2, - status: %w[float_5 sj_can_float], + status: %w[sj_can_float], }, { name: '4D', @@ -137,7 +129,7 @@ class Game < Game::Base train_limit: { national: 3, major: 2 }, tiles: %w[yellow green brown], operating_rounds: 2, - status: %w[float_5 sj_can_float], + status: %w[sj_can_float], }, ].freeze @@ -175,6 +167,9 @@ class Game < Game::Base price: 180, }, ], + events: [ + { 'type' => 'float_3' }, + ], }, { name: '4', @@ -192,6 +187,9 @@ class Game < Game::Base price: 280, }, ], + events: [ + { 'type' => 'float_4' }, + ], }, { name: '5', @@ -209,6 +207,7 @@ class Game < Game::Base }, ], events: [ + { 'type' => 'float_5' }, { 'type' => 'close_companies' }, { 'type' => 'full_cap' }, { 'type' => 'close_minors' }, @@ -254,17 +253,6 @@ def setup end end - def init_corporations(stock_market) - game_corporations.map do |corporation| - G18Scan::Corporation.new( - self, - min_price: stock_market.par_prices.map(&:price).min, - capitalization: self.class::CAPITALIZATION, - **corporation.merge(corporation_opts), - ) - end - end - def corporation_opts two_player? && @optional_rules&.include?(:two_player_share_limit) ? { max_ownership_percent: 70 } : {} end @@ -326,14 +314,6 @@ def ferry_included(route) route.corporation.assigned?(ferry.id) && route.hexes.any? { |h| h.id == 'L7' } end - def float_percent - return 20 if @phase.status.include?('float_2') - return 30 if @phase.status.include?('float_3') - return 40 if @phase.status.include?('float_4') - - 50 - end - def float_str(entity) return 'Floats in phase 5' if entity == sj && !entity.floatable @@ -401,6 +381,28 @@ def copenhagen_dit_upgrade(from, to) from.name == '403' && to.name == '121' end + def update_float_percent(percent) + @corporations.each do |corporation| + next if corporation.floated? || corporation == sj + + corporation.float_percent = percent + end + + @log << "-- Event: New corporations need #{percent}% shares sold to float --" + end + + def event_float_3! + update_float_percent(30) + end + + def event_float_4! + update_float_percent(40) + end + + def event_float_5! + update_float_percent(50) + end + def event_full_cap! @corporations.each do |corp| next if corp.floated?