Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/engine/game/g_1862_usa_canada/entities.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ module Entities
type: 'token',
hexes: [],
discount: 80,
owner_type: 'corporation',
owner_type: 'player',
},
],
color: nil,
Expand Down
48 changes: 45 additions & 3 deletions lib/engine/game/g_1862_usa_canada/game.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
require_relative 'entities'
require_relative 'map'
require_relative '../base'
require_relative 'step/buy_sell_par_shares'
require_relative 'step/dividend'
require_relative 'step/token'

module Engine
module Game
Expand Down Expand Up @@ -280,6 +283,45 @@ class Game < Game::Base

GAME_END_CHECK = { bank: :full_or, stock_market: :full_or }.freeze

# ---------------------------------------------------------------------------
# Private company close triggers.
# SOC closes when CPR or UP floats.
# NHSC closes when NYH floats.
# PSC closes when WP pays its first dividend (via on_first_payout!).
# FNY closes when NYC pays its first dividend (via on_first_payout!).
# TOR closes automatically via closed_when_used_up on its tile_lay ability.
# ---------------------------------------------------------------------------
def float_corporation(corporation)
super
on_corporation_floated!(corporation)
end

def on_corporation_floated!(corporation)
case corporation.id
when 'CPR', 'UP'
close_private_if_open!('SOC', "#{corporation.name} floats")
when 'NYH'
close_private_if_open!('NHSC', "#{corporation.name} floats")
end
end

def on_first_payout!(corporation)
case corporation.id
when 'WP'
close_private_if_open!('PSC', "#{corporation.name} pays first dividend")
when 'NYC'
close_private_if_open!('FNY', "#{corporation.name} pays first dividend")
end
end

def close_private_if_open!(sym, reason)
company = companies.find { |c| c.sym == sym && !c.closed? }
return unless company

company.close!
@log << "#{company.name} closes (#{reason})"
end

# ---------------------------------------------------------------------------
# Tile-lay budget override.
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -337,7 +379,7 @@ def stock_round
Engine::Step::DiscardTrain,
Engine::Step::Exchange,
Engine::Step::SpecialTrack,
Engine::Step::BuySellParShares,
G1862UsaCanada::Step::BuySellParShares,
])
end

Expand All @@ -348,9 +390,9 @@ def operating_round(round_num)
Engine::Step::SpecialTrack,
Engine::Step::HomeToken,
Engine::Step::Track,
Engine::Step::Token,
G1862UsaCanada::Step::Token,
Engine::Step::Route,
Engine::Step::Dividend,
G1862UsaCanada::Step::Dividend,
Engine::Step::DiscardTrain,
Engine::Step::BuyTrain,
], round_num: round_num)
Expand Down
27 changes: 27 additions & 0 deletions lib/engine/game/g_1862_usa_canada/step/buy_sell_par_shares.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

require_relative '../../../step/buy_sell_par_shares'

module Engine
module Game
module G1862UsaCanada
module Step
class BuySellParShares < Engine::Step::BuySellParShares
# NHSC gives the buyer NYH's director cert; NYH must be parred at
# exactly $100. Restrict available par prices to that single value.
def get_par_prices(entity, corporation)
return nyh_par_prices if corporation.id == 'NYH'

super
end

private

def nyh_par_prices
[@game.stock_market.par_prices.find { |p| p.price == 100 }].compact
Comment thread
neutronc marked this conversation as resolved.
Outdated
end
end
end
end
end
end
20 changes: 20 additions & 0 deletions lib/engine/game/g_1862_usa_canada/step/dividend.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

require_relative '../../../step/dividend'

module Engine
module Game
module G1862UsaCanada
module Step
class Dividend < Engine::Step::Dividend
def process_dividend(action)
entity = action.entity
first_time = entity.operating_history.none? { |_, info| info.dividend.kind.to_sym == :payout }
super
@game.on_first_payout!(entity) if first_time && action.kind.to_sym == :payout
end
end
end
end
end
end
35 changes: 35 additions & 0 deletions lib/engine/game/g_1862_usa_canada/step/token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

require_relative '../../../step/token'

module Engine
module Game
module G1862UsaCanada
module Step
class Token < Engine::Step::Token
# GHU (Bahnhoflizenz) gives the director's corporation an $80 discount
# on station token placement (minimum $0). The discount is auto-applied
# whenever the operating corporation's director holds GHU.
def process_place_token(action)
apply_ghu_discount!(action.entity, action.token)
super
end

private

def ghu_company
@game.companies.find { |c| c.sym == 'GHU' && !c.closed? }
end

def apply_ghu_discount!(corporation, token)
ghu = ghu_company
return unless ghu&.owner == corporation.owner

discount = ghu.abilities.find { |a| a.type == :token }&.discount.to_i
token.price = [token.price - discount, 0].max
end
end
end
end
end
end
80 changes: 80 additions & 0 deletions spec/lib/engine/game/g_1862_usa_canada/game_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,86 @@ module Engine
expect(described_class::SELL_BUY_ORDER).to eq(:sell_buy)
end

describe 'private close triggers' do
let(:soc) { game.companies.find { |c| c.sym == 'SOC' } }
let(:nhsc) { game.companies.find { |c| c.sym == 'NHSC' } }
let(:psc) { game.companies.find { |c| c.sym == 'PSC' } }
let(:fny) { game.companies.find { |c| c.sym == 'FNY' } }
let(:cpr) { game.corporation_by_id('CPR') }
let(:up) { game.corporation_by_id('UP') }
let(:nyh) { game.corporation_by_id('NYH') }
let(:wp) { game.corporation_by_id('WP') }
let(:nyc) { game.corporation_by_id('NYC') }

it 'SOC closes when CPR floats' do
game.on_corporation_floated!(cpr)
expect(soc.closed?).to be true
end

it 'SOC closes when UP floats' do
game.on_corporation_floated!(up)
expect(soc.closed?).to be true
end

it 'NHSC closes when NYH floats' do
game.on_corporation_floated!(nyh)
expect(nhsc.closed?).to be true
end

it 'SOC does not close when NYH floats' do
game.on_corporation_floated!(nyh)
expect(soc.closed?).to be false
end

it 'PSC closes on first WP payout' do
game.on_first_payout!(wp)
expect(psc.closed?).to be true
end

it 'FNY closes on first NYC payout' do
game.on_first_payout!(nyc)
expect(fny.closed?).to be true
end

it 'FNY does not close when WP pays first dividend' do
game.on_first_payout!(wp)
expect(fny.closed?).to be false
end
end

describe 'GHU token discount' do
let(:ghu) { game.companies.find { |c| c.sym == 'GHU' } }

it 'GHU ability has player owner_type' do
ability = ghu.abilities.find { |a| a.type == :token }
expect(ability.owner_type).to eq(:player)
end

it 'GHU discount is $80' do
ability = ghu.abilities.find { |a| a.type == :token }
expect(ability.discount).to eq(80)
end
end

describe 'NYH par price restriction' do
let(:nyh) { game.corporation_by_id('NYH') }
let(:step) { game.stock_round.active_step }

it 'NYH par is restricted to $100' do
game.companies.each { |c| c.owner = game.players.first }
prices = step.get_par_prices(game.players.first, nyh).map(&:price)
expect(prices).to eq([100])
end

it 'other corps have multiple par prices available' do
game.companies.each { |c| c.owner = game.players.first }
[game.corporation_by_id('NYC'), game.corporation_by_id('CP')].each do |corp|
prices = step.get_par_prices(game.players.first, corp)
expect(prices.size).to be > 1
end
end
end

describe 'tile-lay budget' do
it 'phase 2: single yellow tile only' do
lays = game.tile_lays(game.corporations.first)
Expand Down
Loading