diff --git a/lib/engine/game/g_18_pa/entities.rb b/lib/engine/game/g_18_pa/entities.rb index 0cb4d71756..1e22ca19ba 100644 --- a/lib/engine/game/g_18_pa/entities.rb +++ b/lib/engine/game/g_18_pa/entities.rb @@ -229,11 +229,10 @@ def company_header(_company) color: '#000000', }, { - float_percent: 50, + float_percent: 40, sym: 'B&O', name: 'Baltimore & Ohio Railroad', - logo: '18_chesapeake/BO', - simple_logo: '1830/BO.alt', + logo: '18_pa/b&o', tokens: [0, 40], shares: [40, 20, 20, 20], type: 'five_share', @@ -242,81 +241,75 @@ def company_header(_company) color: '#025aaa', }, { - float_percent: 20, - name: 'Boston and Albany Railroad', + float_percent: 40, sym: 'B&A', - logo: '18_ny/ba', - simple_logo: '18_ny/ba.alt', + name: 'Boston and Albany Railroad', + logo: '18_pa/b&a', tokens: [0, 40], shares: [40, 20, 20, 20], type: 'five_share', coordinates: 'D27', + city: 0, abilities: [{ type: 'assign_hexes', hexes: ['D17'], count: 1 }], color: '#E21F27', }, { - float_percent: 50, + float_percent: 40, sym: 'CNJ', name: 'Central Railroad of New Jersey', - logo: '', - simple_logo: '', + logo: '18_pa/cnj', tokens: [0, 40], shares: [40, 20, 20, 20], type: 'five_share', coordinates: 'H17', city: 0, - color: :'#ADD8E6', + color: '#ADD8E6', text_color: 'black', }, { - float_percent: 50, + float_percent: 40, sym: 'ERIE', name: 'Erie Railroad', - logo: '1846/ERIE', - simple_logo: '1830/ERIE.alt', + logo: '18_pa/erie', tokens: [0, 40], shares: [40, 20, 20, 20], type: 'five_share', coordinates: 'H17', city: 1, abilities: [{ type: 'assign_hexes', hexes: ['C2'], count: 1 }], - color: :'#FFF500', - text_color: 'black', + color: '#FFA500', }, { - float_percent: 50, + float_percent: 40, name: 'New York, New Haven, & Hartford Railroad', sym: 'NH', - logo: '18_ny/nynh', - simple_logo: '18_ny/nynh.alt', + logo: '18_pa/nynh', tokens: [0, 40], shares: [40, 20, 20, 20], type: 'five_share', coordinates: 'G22', - color: '#E96B21', + color: '#32763f', }, { - float_percent: 50, + float_percent: 40, sym: 'PRR', name: 'Pennsylvania Railroad', - logo: '18_chesapeake/PRR', - simple_logo: '1830/PRR.alt', + logo: '18_pa/prr', tokens: [0, 40], shares: [40, 20, 20, 20], type: 'five_share', coordinates: 'I8', abilities: [{ type: 'assign_hexes', hexes: ['I2'], count: 1 }], - color: '#32763f', + color: '#7b352a', }, { - float_percent: 50, + float_percent: 40, sym: 'NYC', name: 'New York Central System', - logo: '1830/NYC', - simple_logo: '1830/NYC.alt', - tokens: [100, 100, 100], + logo: '18_pa/nyc', + tokens: [0, 0, 0, 100, 100, 100], shares: [20, 10, 10, 10, 10, 10, 10, 10, 10], - color: :'#474548', + color: '#000000', }, ].freeze end diff --git a/lib/engine/game/g_18_pa/game.rb b/lib/engine/game/g_18_pa/game.rb index 5323b1e89d..5293145509 100644 --- a/lib/engine/game/g_18_pa/game.rb +++ b/lib/engine/game/g_18_pa/game.rb @@ -27,12 +27,11 @@ class Game < Game::Base CURRENCY_FORMAT_STR = '$%s' MUST_BUY_TRAIN = :always - EBUY_DEPOT_TRAIN_MUST_BE_CHEAPEST = true + EBUY_DEPOT_TRAIN_MUST_BE_CHEAPEST = false SELL_AFTER = :operate SOLD_SHARES_DESTINATION = :corporation MARKET_SHARE_LIMIT = 80 # percent EBUY_FROM_OTHERS = :never - TILE_LAYS = [{ lay: true, upgrade: true }, { lay: :not_if_upgraded, upgrade: false }].freeze BANK_CASH = 8_000 @@ -66,7 +65,6 @@ class Game < Game::Base train_limit: 3, tiles: %i[yellow green], operating_rounds: 2, - status: %i['may_convert_acquire'], }, { name: '5', @@ -74,7 +72,6 @@ class Game < Game::Base train_limit: 2, tiles: %i[yellow green brown], operating_rounds: 2, - status: %i['may_convert_acquire'], }, { name: '3D', @@ -125,6 +122,7 @@ class Game < Game::Base { 'nodes' => ['town'], 'pay' => 99, 'visit' => 99 }], price: 500, num: 99, + # events: [{ 'type' => 'convert_2r_trains' }], }, # The 2R trains are reserved for corps which buy in Minors 4-9 { @@ -145,23 +143,89 @@ class Game < Game::Base }, ].freeze + SCRANTON_HEX = 'G12' + SCRANTON_MARKER_ICON = 'mine' + SCRANTON_MARKER_COST = 40 + DOUBLING_TOKEN_CORPS = %w[B&A ERIE PRR].freeze + MINOR_UPGRADES = %w[yellow green].freeze + + def new_auction_round + Engine::Round::Auction.new(self, [ + Engine::Step::SelectionAuction, + ]) + end + + def stock_round + Engine::Round::Stock.new(self, [ + Engine::Step::DiscardTrain, + Engine::Step::Exchange, + Engine::Step::SpecialTrack, + Engine::Step::BuySellParShares, + ]) + end + def operating_round(round_num) Round::Operating.new(self, [ Engine::Step::Bankrupt, - Engine::Step::Exchange, - Engine::Step::SpecialTrack, Engine::Step::SpecialToken, - Engine::Step::BuyCompany, - Engine::Step::HomeToken, - Engine::Step::Track, + G18PA::Step::Track, Engine::Step::Token, Engine::Step::Route, - Engine::Step::Dividend, + G18PA::Step::Dividend, Engine::Step::DiscardTrain, Engine::Step::BuyTrain, - [Engine::Step::BuyCompany, { blocks: true }], ], round_num: round_num) end + + def setup + @scranton_marker_ability = Engine::Ability::Description.new(type: 'description', description: 'Scranton Token') + + # place the home station for all corporations and minors except NYC. + @corporations.each do |corporation| + next if corporation.id == 'NYC' + + tile = hex_by_id(corporation.coordinates).tile + tile.cities[corporation.city || 0].place_token(corporation, corporation.tokens.first, free: true) + end + end + + def scranton_marker_available? + hex_by_id(SCRANTON_HEX).tile.icons.any? { |icon| icon.name == SCRANTON_MARKER_ICON } + end + + def scranton_marker?(entity) + return false if !entity.corporation? || entity.type == :minor + + !scranton_markers(entity).empty? + end + + def scranton_markers(entity) + entity.all_abilities.select { |ability| ability.description == @scranton_marker_ability.description } + end + + def connected_to_scranton?(entity) + graph.reachable_hexes(entity).include?(hex_by_id(SCRANTON_HEX)) + end + + def can_buy_scranton_marker?(entity) + return false if !entity.corporation? || entity.type == :minor + + scranton_marker_available? && + !scranton_marker?(entity) && + buying_power(entity) >= SCRANTON_MARKER_COST && + connected_to_scranton?(entity) + end + + def buy_scranton_marker(entity) + return unless can_buy_scranton_marker?(entity) + + entity.spend(SCRANTON_MARKER_COST, @bank) + entity.add_ability(@scranton_marker_ability.dup) + @log << "#{entity.name} buys a Scranton bonus token for $#{SCRANTON_MARKER_COST}." + + tile_icons = hex_by_id(SCRANTON_HEX).tile.icons + tile_icons.delete_at(tile_icons.index { |icon| icon.name == SCRANTON_MARKER_ICON }) + end end end end diff --git a/lib/engine/game/g_18_pa/map.rb b/lib/engine/game/g_18_pa/map.rb index 5b9dbf6285..1cae75d135 100644 --- a/lib/engine/game/g_18_pa/map.rb +++ b/lib/engine/game/g_18_pa/map.rb @@ -5,6 +5,121 @@ module Game module G18PA module Map TILES = { + # yellow + '4' => 8, + '8' => 'unlimited', + '9' => 'unlimited', + '58' => 8, + + # green + '15' => 5, + '80' => 2, + '81' => 4, + '82' => 7, + '83' => 7, + '141' => 4, + '142' => 4, + '143' => 3, + 'X10' => { + 'count' => 1, + 'color' => 'green', + 'code' => 'city=revenue:30;city=revenue:30;city=revenue:30;path=a:0,b:_0;path=a:2,b:_1;path=a:1,b:_2;path=a:4,b:_2;'\ + 'upgrade=cost:40,terrain:water;label=EWR', + }, + 'X11' => { + 'count' => 1, + 'color' => 'green', + 'code' => 'city=revenue:40;city=revenue:40;city=revenue:40;path=a:1,b:_0;path=a:2,b:_1;path=a:3,b:_1;path=a:4,b:_2;'\ + 'upgrade=cost:40,terrain:water;label=NYC', + }, + 'X12' => { + 'count' => 1, + 'color' => 'green', + 'code' => 'city=revenue:40;city=revenue:40;city=revenue:40;path=a:0,b:_0;path=a:3,b:_0;path=a:2,b:_1;path=a:5,b:_1;'\ + 'path=a:1,b:_2;path=a:4,b:_2;label=PHI', + }, + 'X13' => { + 'count' => 1, + 'color' => 'green', + 'code' => 'city=revenue:30;city=revenue:30;city=revenue:30;path=a:1,b:_0;path=a:0,b:_1;path=a:3,b:_1;path=a:5,b:_2;'\ + 'label=BOS', + }, + 'X14' => { + 'count' => 1, + 'color' => 'green', + 'code' => 'city=revenue:40,slots:2;path=a:0,b:_0;path=a:1,b:_0;path=a:2,b:_0;path=a:3,b:_0;path=a:4,b:_0;label=BAL', + }, + 'X15' => { + 'count' => 1, + 'color' => 'green', + 'code' => 'city=revenue:40,slots:2;path=a:0,b:_0;path=a:2,b:_0;path=a:3,b:_0;path=a:4,b:_0;path=a:5,b:_0;label=BUF', + }, + + # brown + 'X20' => { + 'count' => 1, + 'color' => 'brown', + 'code' => 'city=revenue:40,slots:3;path=a:0,b:_0;path=a:1,b:_0;path=a:2,b:_0;path=a:4,b:_0;label=EWR', + }, + 'X21' => { + 'count' => 1, + 'color' => 'brown', + 'code' => 'city=revenue:60,slots:3;path=a:1,b:_0;path=a:2,b:_0;path=a:3,b:_0;path=a:4,b:_0;label=NYC', + }, + 'X22' => { + 'count' => 1, + 'color' => 'brown', + 'code' => 'city=revenue:60,slots:3;path=a:0,b:_0;path=a:1,b:_0;path=a:2,b:_0;path=a:3,b:_0;path=a:4,b:_0;'\ + 'path=a:5,b:_0;label=PHI', + }, + 'X23' => { + 'count' => 1, + 'color' => 'brown', + 'code' => 'city=revenue:40,slots:3;path=a:0,b:_0;path=a:1,b:_0;path=a:3,b:_0;path=a:5,b:_0;label=BOS', + }, + 'X24' => { + 'count' => 1, + 'color' => 'brown', + 'code' => 'city=revenue:50,slots:3;path=a:0,b:_0;path=a:1,b:_0;path=a:2,b:_0;path=a:3,b:_0;path=a:4,b:_0;label=BAL', + }, + 'X25' => { + 'count' => 1, + 'color' => 'brown', + 'code' => 'city=revenue:50,slots:3;path=a:0,b:_0;path=a:2,b:_0;path=a:3,b:_0;path=a:4,b:_0;path=a:5,b:_0;label=BUF', + }, + + # gray + 'X30' => { + 'count' => 1, + 'color' => 'gray', + 'code' => 'city=revenue:60,slots:3;path=a:0,b:_0;path=a:1,b:_0;path=a:2,b:_0;path=a:4,b:_0;label=EWR', + }, + 'X31' => { + 'count' => 1, + 'color' => 'gray', + 'code' => 'city=revenue:80,slots:4;path=a:1,b:_0;path=a:2,b:_0;path=a:3,b:_0;path=a:4,b:_0;label=NYC', + }, + 'X32' => { + 'count' => 1, + 'color' => 'gray', + 'code' => 'city=revenue:70,slots:3;path=a:0,b:_0;path=a:1,b:_0;path=a:2,b:_0;path=a:3,b:_0;path=a:4,b:_0;'\ + 'path=a:5,b:_0;label=PHI', + }, + 'X33' => { + 'count' => 1, + 'color' => 'gray', + 'code' => 'city=revenue:60,slots:3;path=a:0,b:_0;path=a:1,b:_0;path=a:3,b:_0;path=a:5,b:_0;label=BOS', + }, + 'X34' => { + 'count' => 1, + 'color' => 'gray', + 'code' => 'city=revenue:60,slots:3;path=a:0,b:_0;path=a:1,b:_0;path=a:2,b:_0;path=a:3,b:_0;path=a:4,b:_0;label=BAL', + }, + 'X35' => { + 'count' => 1, + 'color' => 'gray', + 'code' => 'city=revenue:60,slots:3;path=a:0,b:_0;path=a:2,b:_0;path=a:3,b:_0;path=a:4,b:_0;path=a:5,b:_0;label=BUF', + }, }.freeze LOCATION_NAMES = { @@ -34,6 +149,7 @@ module Map 'I16' => 'Trenton', 'I18' => 'Long Beach', 'J13' => 'Philadelphia', + 'J21' => 'Bonus per Token on Route', 'K2' => 'Columbus', 'K10' => 'Baltimore', 'K16' => 'Atlantic City', @@ -41,11 +157,19 @@ module Map 'M8' => 'Washington', }.freeze + MAJOR_TILE_LAYS = [{ lay: true, upgrade: true }, { lay: :not_if_upgraded, upgrade: false }].freeze + MINOR_TILE_LAYS = [{ lay: true, upgrade: true }].freeze + + def tile_lays(entity) + entity.type == :minor ? MINOR_TILE_LAYS : MAJOR_TILE_LAYS + end + HEXES = { red: { ['D1'] => 'offboard=revenue:yellow_0|green_30|brown_60;path=a:3,b:_0', - ['I2'] => 'offboard=revenue:yellow_30|green_40|brown_60;path=a:4,b:_0', - ['K2'] => 'offboard=revenue:yellow_30|green_40|brown_60;path=a:4,b:_0', + ['I2'] => 'city=revenue:yellow_30|green_40|brown_60;path=a:4,b:_0', + ['J21'] => 'offboard=revenue:yellow_0|green_10|brown_20|gray_30', + ['K2'] => 'city=revenue:yellow_30|green_40|brown_60;path=a:4,b:_0', ['M8'] => 'city=revenue:yellow_10|green_20|brown_60;path=a:2,b:_0;path=a:3,b:_0', }, gray: { @@ -54,7 +178,8 @@ module Map ['E22'] => 'town=revenue:10,loc:5.5;town=revenue:10,loc:2.5;path=a:0,b:_0;path=a:5,b:_0;path=a:2,b:_1;'\ 'path=a:3,b:_1;path=a:_0,b:_1', ['E28'] => 'path=a:0,b:2', - ['G12'] => 'town=revenue:10;path=a:0,b:_0;path=a:1,b:_0;path=a:2,b:_0;path=a:3,b:_0;path=a:4,b:_0;path=a:5,b:_0', + ['G12'] => 'town=revenue:10;path=a:0,b:_0;path=a:1,b:_0;path=a:2,b:_0;path=a:3,b:_0;path=a:4,b:_0;path=a:5,b:_0;'\ + 'icon=image:mine;icon=image:mine', ['I18'] => 'town=revenue:10;path=a:0,b:_0;path=a:1,b:_0', ['L15'] => 'town=revenue:10;path=a:2,b:_0;path=a:3,b:_0', }, @@ -116,7 +241,7 @@ module Map ['F17'] => 'border=edge:0,type:water,cost:20;border=edge:1,type:water,cost:20;border=edge:2,type:water,cost:20', ['F21'] => 'border=edge:4,type:water,cost:20', ['F23'] => 'border=edge:0,type:water,cost:20;border=edge:1,type:water,cost:20', - ['G16'] => 'border=edge:3,type:water,cost:20;border=edge:4,type:impassable', + ['G16'] => 'border=edge:3,type:water,cost:20;border=edge:4,type:impassable;stub=edge:5', ['G18'] => 'town=revenue:0;stub=edge:5;border=edge:0,type:impassable;border=edge:1,type:impassable;'\ 'border=edge:4,type:impassable', ['G20'] => 'town=revenue:0;stub=edge:0;border=edge:1,type:impassable;border=edge:5,type:impassable;'\ diff --git a/lib/engine/game/g_18_pa/step/dividend.rb b/lib/engine/game/g_18_pa/step/dividend.rb new file mode 100644 index 0000000000..6bfb98ee6a --- /dev/null +++ b/lib/engine/game/g_18_pa/step/dividend.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require_relative '../../../step/dividend' +require_relative '../../../step/minor_half_pay' + +module Engine + module Game + module G18PA + module Step + class Dividend < Engine::Step::Dividend + include Engine::Step::MinorHalfPay + + def payout(entity, revenue) + return super if entity.corporation? && entity.type != :minor + + amount = revenue / 2 + { corporation: 0, per_share: amount } + end + + def share_price_change(entity, revenue = 0) + return {} if entity.type == :minor + + super + end + end + end + end + end +end diff --git a/lib/engine/game/g_18_pa/step/track.rb b/lib/engine/game/g_18_pa/step/track.rb new file mode 100644 index 0000000000..e3ca49c8c5 --- /dev/null +++ b/lib/engine/game/g_18_pa/step/track.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_relative '../../../step/base' + +module Engine + module Game + module G18PA + module Step + class Track < Engine::Step::Base + def potential_tile_colors(entity, hex) + return @game.class::MINOR_UPGRADES if entity.corporation? && + entity.type == :minor && + @game.phase.name != '2' + + super + end + end + end + end + end +end diff --git a/public/logos/18_pa/b&a.svg b/public/logos/18_pa/b&a.svg new file mode 100644 index 0000000000..210eb150bb --- /dev/null +++ b/public/logos/18_pa/b&a.svg @@ -0,0 +1 @@ +B&A \ No newline at end of file diff --git a/public/logos/18_pa/b&o.svg b/public/logos/18_pa/b&o.svg new file mode 100644 index 0000000000..155ed4ac89 --- /dev/null +++ b/public/logos/18_pa/b&o.svg @@ -0,0 +1 @@ +B&O \ No newline at end of file diff --git a/public/logos/18_pa/cnj.svg b/public/logos/18_pa/cnj.svg new file mode 100644 index 0000000000..f9babea907 --- /dev/null +++ b/public/logos/18_pa/cnj.svg @@ -0,0 +1 @@ +CNJ \ No newline at end of file diff --git a/public/logos/18_pa/erie.svg b/public/logos/18_pa/erie.svg new file mode 100644 index 0000000000..e485608279 --- /dev/null +++ b/public/logos/18_pa/erie.svg @@ -0,0 +1 @@ +ERIE \ No newline at end of file diff --git a/public/logos/18_pa/nyc.svg b/public/logos/18_pa/nyc.svg new file mode 100644 index 0000000000..c35df5ac8b --- /dev/null +++ b/public/logos/18_pa/nyc.svg @@ -0,0 +1 @@ +NYC \ No newline at end of file diff --git a/public/logos/18_pa/nynh.svg b/public/logos/18_pa/nynh.svg new file mode 100644 index 0000000000..eb66f2ac57 --- /dev/null +++ b/public/logos/18_pa/nynh.svg @@ -0,0 +1 @@ +NH \ No newline at end of file diff --git a/public/logos/18_pa/prr.svg b/public/logos/18_pa/prr.svg new file mode 100644 index 0000000000..887f759041 --- /dev/null +++ b/public/logos/18_pa/prr.svg @@ -0,0 +1 @@ +PRR \ No newline at end of file