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 @@
+
\ 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 @@
+
\ 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 @@
+
\ 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 @@
+
\ 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 @@
+
\ 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 @@
+
\ 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 @@
+
\ No newline at end of file