diff --git a/assets/app/view/welcome.rb b/assets/app/view/welcome.rb index fb22c3f21d..fc4dc83ad7 100644 --- a/assets/app/view/welcome.rb +++ b/assets/app/view/welcome.rb @@ -16,7 +16,8 @@ def render def render_notification message = <<~MESSAGE -
1880 Romania is now in alpha.
+1880 Romania is now in alpha. + Including 2-player Transilvania variant.
1824 is now in beta.
diff --git a/lib/engine/game/g_1880_romania/meta.rb b/lib/engine/game/g_1880_romania/meta.rb index 2ea1fe035b..4436b43072 100644 --- a/lib/engine/game/g_1880_romania/meta.rb +++ b/lib/engine/game/g_1880_romania/meta.rb @@ -19,6 +19,15 @@ module Meta GAME_TITLE = '1880 Romania' PLAYER_RANGE = [3, 6].freeze + + GAME_VARIANTS = [ + { + sym: :transilvania, + name: 'Transilvania', + title: '1880 Romania Transilvania', + desc: 'Alternate map for 2 players, shorter game', + }, + ].freeze end end end diff --git a/lib/engine/game/g_1880_romania_transilvania.rb b/lib/engine/game/g_1880_romania_transilvania.rb new file mode 100644 index 0000000000..28382e384c --- /dev/null +++ b/lib/engine/game/g_1880_romania_transilvania.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Engine + module Game + module G1880RomaniaTransilvania + end + end +end diff --git a/lib/engine/game/g_1880_romania_transilvania/game.rb b/lib/engine/game/g_1880_romania_transilvania/game.rb new file mode 100644 index 0000000000..4335305ae9 --- /dev/null +++ b/lib/engine/game/g_1880_romania_transilvania/game.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +require_relative 'meta' +require_relative '../g_1880_romania/game' +require_relative 'map' +require_relative '../g_1880_romania/entities' + +module Engine + module Game + module G1880RomaniaTransilvania + class Game < G1880Romania::Game + include_meta(G1880RomaniaTransilvania::Meta) + include Map + + CERT_LIMIT = { 2 => 11 }.freeze + + STARTING_CASH = { 2 => 350 }.freeze + + def game_companies + companies = COMPANIES.map(&:dup) + kept_companies = %w[P1 P3 P5 P8] + companies.select { |c| kept_companies.include?(c[:sym]) } + end + + def game_minors + minors = MINORS.map(&:dup) + + kept_minors = %w[1 4 5] + coordinates = { + '1' => 'D2', + '4' => 'B8', + '5' => 'J4', + }.freeze + minors + .select { |m| kept_minors.include?(m[:sym]) } + .each { |m| m[:coordinates] = coordinates[m[:sym]] } + end + + def game_corporations + corporations = CORPORATIONS.map(&:dup) + kept_corporations = %w[BR CR SZ TR] + coordinates = { + 'BR' => 'D6', + 'CR' => 'E3', + 'SZ' => 'G1', + 'TR' => 'L6', + }.freeze + corporations + .select { |c| kept_corporations.include?(c[:sym]) } + .each { |m| m[:coordinates] = coordinates[m[:sym]] } + end + + def game_trains + unless @train_games + @train_games = super.map(&:dup) + t_2, t_2p2, t_3, t_3p3, t_4, t_4p4, t_6, t_6e, t_8, t_8e, t_2r = @train_games + t_2[:num] = 6 + t_2p2[:num] = 3 + t_2p2[:events] = [] + t_3[:num] = 3 + t_3p3[:num] = 2 + t_3p3[:events] = [{ 'type' => 'communist_takeover' }] + t_4[:num] = 2 + t_4p4[:num] = 2 + t_6[:num] = 2 + t_6e[:num] = 1 + t_6e[:events] = [{ 'type' => 'signal_end_game', 'when' => 1 }] + t_8[:num] = 1 + t_8e[:num] = 'unlimited' + t_2r[:num] = 6 + end + @train_games + end + + def par_chart + @par_chart ||= + share_prices.sort_by { |sp| -sp.price }.to_h { |sp| [sp, [nil, nil]] } + end + + def setup + super + + @dummy ||= Company.new( + name: 'Dummy Company', + sym: 'DUMMY', + value: 0, + ) + @dummy.close! + end + + # P2 not used in this variant + def consortiu + @dummy + end + + # P4 not used in this variant + def danube_port + @dummy + end + + # P6 not used in this variant + def malaxa + @dummy + end + + # P7 not used in this variant + def rocket + @dummy + end + end + end + end +end diff --git a/lib/engine/game/g_1880_romania_transilvania/map.rb b/lib/engine/game/g_1880_romania_transilvania/map.rb new file mode 100644 index 0000000000..e1457d56dd --- /dev/null +++ b/lib/engine/game/g_1880_romania_transilvania/map.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require_relative '../g_1880_romania/map' + +module Engine + module Game + module G1880RomaniaTransilvania + module Map + include G1880Romania::Map + + LAYOUT = :pointy + AXES = { x: :letter, y: :number }.freeze + + LOCATION_NAMES = { + 'G1' => 'Satu Mare', + 'K1' => 'Sighetu Marmației', + 'Q1' => 'Czernowitz', + 'D2' => 'Viena / Budapešta', + 'J2' => 'Baia-Mare', + 'P2' => 'Rădăuți', + 'E3' => 'Oradea', + 'G3' => 'Margita / Simleu', + 'I3' => 'Zalău', + 'K3' => 'Dej', + 'K7' => 'Istanbul', + 'M3' => 'Bistrița', + 'J4' => 'Cluj-Napoca', + 'L4' => 'Târgu Mureș', + 'D19' => 'Bacău', + 'A5' => 'Sinnicolau Mare', + 'C5' => 'Arad', + 'K5' => 'Turda', + 'M5' => 'Mediaș', + 'D6' => 'Timișoara', + 'H6' => 'Deva / Hunedoara', + 'J6' => 'Alba Iulia', + 'L6' => 'Sibiu', + 'N6' => 'Făgăraș', + 'P6' => 'Brașov', + 'E7' => 'Reșița', + 'B8' => 'Belgrad', + 'D8' => 'Oravița / Moldova Veche', + }.freeze + + HEXES = { + white: { + # no cities or towns + %w[I1 F2 H2 D4 F4 E5 B6 F6 C7 F8] => '', + %w[L2 N2 O3 G5 I5 G7] => 'upgrade=cost:40,terrain:mountain', + %w[H4 N4 I7] => 'upgrade=cost:30,terrain:mountain', + ['O5'] => 'upgrade=cost:20,terrain:mountain', + + # town + ['K1'] => 'town=revenue:0;upgrade=cost:40,terrain:mountain', + %w[M3 N6] => 'town=revenue:0;upgrade=cost:30,terrain:mountain', + %w[K3 K5 M5] => 'town=revenue:0;upgrade=cost:10,terrain:mountain', + + # double towns + ['G3'] => 'town=revenue:0;town=revenue:0', + ['D8'] => 'town=revenue:0;town=revenue:0;icon=image:port', + ['H6'] => 'town=revenue:0;town=revenue:0;upgrade=cost:20,terrain:mountain', + + # city + %w[E3 J4 L4 C5 D6] => 'city=revenue:0', + ['G1'] => 'city=revenue:0;label=T', + ['L6'] => 'city=revenue:0;upgrade=cost:20,terrain:mountain', + + # town and city + %w[J2 I3] => 'town=revenue:0;city=revenue:0', + ['J6'] => 'town=revenue:0;city=revenue:0;upgrade=cost:20,terrain:mountain', + ['E7'] => 'town=revenue:0;city=revenue:0;upgrade=cost:40,terrain:mountain', + }, + gray: { + ['P2'] => 'town=revenue:20;path=a:0,b:_0;path=a:1,b:_0;path=a:3,b:_0', + ['A5'] => 'town=revenue:20;path=a:4,b:_0;path=a:5,b:_0', + ['P6'] => 'city=revenue:yellow_20|green_30|brown_40|gray_50;path=a:1,b:_0;path=a:2,b:_0', + }, + red: { + ['D2'] => 'city=revenue:yellow_20|green_30|brown_50|gray_70;path=a:4,b:_0,terminal:1;path=a:5,b:_0,terminal:1', + ['B8'] => 'city=revenue:yellow_20|green_30|brown_40|gray_60;path=a:3,b:_0,terminal:1;path=a:4,b:_0,terminal:1', + ['Q1'] => 'city=revenue:yellow_30|green_40|brown_50|gray_60;path=a:0,b:_0,terminal:1', + ['K7'] => 'offboard=revenue:yellow_10|green_20|brown_40|gray_50,hide:1,groups:Istanbul;'\ + 'path=a:3,b:_0,terminal:1;border=edge:4', + ['M7'] => 'offboard=revenue:yellow_10|green_30|brown_40|gray_50,groups:Istanbul;path=a:2,b:_0,terminal:1;'\ + 'border=edge:1', + }, + }.freeze + end + end + end +end diff --git a/lib/engine/game/g_1880_romania_transilvania/meta.rb b/lib/engine/game/g_1880_romania_transilvania/meta.rb new file mode 100644 index 0000000000..ce116d337e --- /dev/null +++ b/lib/engine/game/g_1880_romania_transilvania/meta.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require_relative '../meta' +require_relative '../g_1880_romania/meta' + +module Engine + module Game + module G1880RomaniaTransilvania + module Meta + include Game::Meta + include G1880Romania::Meta + + DEPENDS_ON = '1880 Romania' + + DEV_STAGE = :alpha + + GAME_IS_VARIANT_OF = G1880Romania::Meta + GAME_INFO_URL = 'https://github.com/tobymao/18xx/wiki/1880-Romania-Transilvania-map'.freeze + GAME_TITLE = '1880 Romania Transilvania'.freeze + + PLAYER_RANGE = [2, 2].freeze + OPTIONAL_RULES = [].freeze + end + end + end +end diff --git a/public/fixtures/1880RomaniaTransilvania/1880_romania_transilvania_game_end_final_phase.json b/public/fixtures/1880RomaniaTransilvania/1880_romania_transilvania_game_end_final_phase.json new file mode 100644 index 0000000000..8ea8487b1a --- /dev/null +++ b/public/fixtures/1880RomaniaTransilvania/1880_romania_transilvania_game_end_final_phase.json @@ -0,0 +1 @@ +{"status":"finished","actions":[{"type":"bid","entity":0,"entity_type":"player","id":1,"created_at":1778333726,"company":"P1","price":20},{"type":"pass","entity":1,"entity_type":"player","id":2,"created_at":1778333730},{"type":"bid","entity":1,"entity_type":"player","id":3,"created_at":1778333733,"company":"P3","price":40},{"type":"pass","entity":0,"entity_type":"player","id":4,"created_at":1778333735},{"type":"bid","entity":0,"entity_type":"player","id":5,"created_at":1778333739,"company":"P5","price":70},{"type":"pass","entity":1,"entity_type":"player","id":6,"created_at":1778333740},{"type":"bid","entity":1,"entity_type":"player","id":7,"created_at":1778333742,"company":"P8","price":160},{"type":"pass","entity":0,"entity_type":"player","id":8,"created_at":1778333743},{"type":"par","entity":1,"entity_type":"player","id":9,"created_at":1778333874,"corporation":"TR","share_price":"80,5,3","slot":0},{"type":"bid","entity":1,"entity_type":"player","id":10,"created_at":1778333894,"minor":"5","price":0},{"type":"bid","entity":0,"entity_type":"player","id":11,"created_at":1778333903,"minor":"1","price":0},{"type":"buy_shares","entity":1,"entity_type":"player","id":12,"created_at":1778335622,"shares":["TR_2"],"percent":10},{"type":"par","entity":0,"entity_type":"player","id":13,"created_at":1778335688,"corporation":"BR","share_price":"90,3,3","slot":1},{"type":"choose","entity":0,"entity_type":"player","id":14,"created_at":1778335692,"choice":"ABC"},{"type":"undo","entity":0,"entity_type":"player","id":15,"created_at":1778335713},{"type":"undo","entity":0,"entity_type":"player","id":16,"created_at":1778335715},{"type":"undo","entity":0,"entity_type":"player","id":17,"created_at":1778335717},{"type":"undo","entity":1,"entity_type":"player","id":18,"created_at":1778335770},{"type":"bid","entity":0,"entity_type":"player","id":19,"created_at":1778335787,"minor":"1","price":0},{"type":"pass","entity":1,"entity_type":"player","id":20,"created_at":1778335801},{"type":"undo","entity":0,"entity_type":"player","id":21,"created_at":1778335816,"action_id":9},{"type":"bid","entity":1,"entity_type":"player","id":22,"created_at":1778335829,"minor":"5","price":0},{"type":"bid","entity":0,"entity_type":"player","id":23,"created_at":1778335836,"minor":"1","price":0},{"type":"buy_shares","entity":1,"entity_type":"player","id":24,"created_at":1778335842,"shares":["TR_2"],"percent":10},{"type":"par","entity":0,"entity_type":"player","id":25,"created_at":1778335854,"corporation":"BR","share_price":"80,5,3","slot":1},{"type":"choose","entity":0,"entity_type":"player","id":26,"created_at":1778335857,"choice":30},{"type":"choose","entity":0,"entity_type":"player","id":27,"created_at":1778335859,"choice":"AB"},{"type":"lay_tile","entity":"1","entity_type":"minor","id":28,"created_at":1778335885,"hex":"E3","tile":"6-0","rotation":2},{"type":"run_routes","entity":"1","entity_type":"minor","id":29,"created_at":1778337174,"auto_actions":[{"type":"pass","entity":"1","entity_type":"minor","created_at":1778337174}],"routes":[{"train":"2-0","connections":[["D2","E3"]],"hexes":["D2","E3"],"revenue":40,"revenue_str":"D2-E3","nodes":["D2-0","E3-0"]}],"extra_revenue":0,"subsidy":0},{"type":"lay_tile","entity":"5","entity_type":"minor","id":30,"created_at":1778337192,"auto_actions":[{"type":"pass","entity":"5","entity_type":"minor","created_at":1778337192}],"hex":"J4","tile":"6-1","rotation":2},{"type":"lay_tile","entity":"TR","entity_type":"corporation","id":31,"created_at":1778337226,"hex":"L6","tile":"6-2","rotation":5},{"type":"buy_train","entity":"TR","entity_type":"corporation","id":32,"created_at":1778337232,"train":"2-0","price":100,"variant":"2"},{"type":"buy_train","entity":"TR","entity_type":"corporation","id":33,"created_at":1778337238,"train":"2-1","price":100,"variant":"2"},{"type":"pass","entity":"TR","entity_type":"corporation","id":34,"created_at":1778337240},{"type":"lay_tile","entity":"BR","entity_type":"corporation","id":35,"created_at":1778337249,"hex":"D6","tile":"5-0","rotation":0},{"type":"buy_train","entity":"BR","entity_type":"corporation","id":36,"created_at":1778337256,"train":"2-2","price":100,"variant":"2"},{"type":"pass","entity":"BR","entity_type":"corporation","id":37,"created_at":1778337258},{"type":"lay_tile","entity":"1","entity_type":"minor","id":38,"created_at":1778337266,"hex":"G3","tile":"8857-0","rotation":0},{"type":"run_routes","entity":"1","entity_type":"minor","id":39,"created_at":1778337271,"auto_actions":[{"type":"pass","entity":"1","entity_type":"minor","created_at":1778337271}],"routes":[{"train":"2-3","connections":[["D2","E3"]],"hexes":["D2","E3"],"revenue":40,"revenue_str":"D2-E3","nodes":["D2-0","E3-0"]}],"extra_revenue":0,"subsidy":0},{"type":"lay_tile","entity":"5","entity_type":"minor","id":40,"created_at":1778337279,"hex":"L4","tile":"6-3","rotation":5},{"type":"run_routes","entity":"5","entity_type":"minor","id":41,"created_at":1778337282,"auto_actions":[{"type":"pass","entity":"5","entity_type":"minor","created_at":1778337282}],"routes":[{"train":"2-3","connections":[["J4","L4"]],"hexes":["J4","L4"],"revenue":40,"revenue_str":"J4-L4","nodes":["J4-0","L4-0"]}],"extra_revenue":0,"subsidy":0},{"type":"lay_tile","entity":"TR","entity_type":"corporation","id":42,"created_at":1778337289,"hex":"J6","tile":"57-0","rotation":1},{"type":"pass","entity":"TR","entity_type":"corporation","id":43,"created_at":1778337293},{"type":"run_routes","entity":"TR","entity_type":"corporation","id":44,"created_at":1778337296,"routes":[{"train":"2-1","connections":[["L6","J6"]],"hexes":["L6","J6"],"revenue":40,"revenue_str":"L6-J6","nodes":["L6-0","J6-0"]},{"train":"2-0","connections":[["L6","M7"]],"hexes":["L6","M7"],"revenue":30,"revenue_str":"L6-M7","nodes":["L6-0","M7-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"TR","entity_type":"corporation","id":45,"created_at":1778337298,"kind":"payout"},{"type":"pass","entity":"TR","entity_type":"corporation","id":46,"created_at":1778337301},{"type":"lay_tile","entity":"BR","entity_type":"corporation","id":47,"created_at":1778337308,"hex":"B6","tile":"8-0","rotation":2},{"type":"run_routes","entity":"BR","entity_type":"corporation","id":48,"created_at":1778337313,"routes":[{"train":"2-2","connections":[["D6","B6","A5"]],"hexes":["D6","A5"],"revenue":40,"revenue_str":"D6-A5","nodes":["D6-0","A5-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"BR","entity_type":"corporation","id":49,"created_at":1778337322,"kind":"payout"},{"type":"pass","entity":"BR","entity_type":"corporation","id":50,"created_at":1778337325},{"type":"buy_shares","entity":1,"entity_type":"player","id":51,"created_at":1778337335,"shares":["TR_3"],"percent":10},{"type":"pass","entity":1,"entity_type":"player","id":52,"created_at":1778337347},{"type":"buy_train","entity":"BR","entity_type":"corporation","id":53,"created_at":1778337350,"train":"2+2-0","price":180,"variant":"2+2"},{"type":"lay_tile","entity":"1","entity_type":"minor","id":54,"created_at":1778337362,"hex":"I3","tile":"8851-0","rotation":1},{"type":"run_routes","entity":"1","entity_type":"minor","id":55,"created_at":1778337369,"auto_actions":[{"type":"pass","entity":"1","entity_type":"minor","created_at":1778337369}],"routes":[{"train":"2+2-1","connections":[["D2","E3"],["E3","G3"],["G3","I3"]],"hexes":["D2","E3","G3","I3"],"revenue":80,"revenue_str":"D2-E3-G3-I3","nodes":["D2-0","E3-0","G3-0","I3-0"]}],"extra_revenue":0,"subsidy":0},{"type":"lay_tile","entity":"5","entity_type":"minor","id":56,"created_at":1778337378,"hex":"M5","tile":"8851-1","rotation":2},{"type":"run_routes","entity":"5","entity_type":"minor","id":57,"created_at":1778337381,"auto_actions":[{"type":"pass","entity":"5","entity_type":"minor","created_at":1778337381}],"routes":[{"train":"2+2-1","connections":[["G3","I3"],["I3","J4"],["J4","L4"]],"hexes":["G3","I3","J4","L4"],"revenue":80,"revenue_str":"G3-I3-J4-L4","nodes":["G3-0","I3-0","J4-0","L4-0"]}],"extra_revenue":0,"subsidy":0},{"type":"lay_tile","entity":"TR","entity_type":"corporation","id":58,"created_at":1778337394,"hex":"H6","tile":"8855-0","rotation":1},{"type":"pass","entity":"TR","entity_type":"corporation","id":59,"created_at":1778337400},{"type":"run_routes","entity":"TR","entity_type":"corporation","id":60,"created_at":1778337410,"routes":[{"train":"2-1","connections":[["L6","J6"]],"hexes":["L6","J6"],"revenue":40,"revenue_str":"L6-J6","nodes":["L6-0","J6-0"]},{"train":"2-0","connections":[["L6","M7"]],"hexes":["L6","M7"],"revenue":30,"revenue_str":"L6-M7","nodes":["L6-0","M7-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"TR","entity_type":"corporation","id":61,"created_at":1778337412,"kind":"payout"},{"type":"pass","entity":"TR","entity_type":"corporation","id":62,"created_at":1779482154},{"type":"lay_tile","entity":"BR","entity_type":"corporation","id":63,"created_at":1779482217,"hex":"C5","tile":"6-4","rotation":1},{"type":"undo","entity":"BR","entity_type":"corporation","id":64,"created_at":1779482227},{"type":"lay_tile","entity":"BR","entity_type":"corporation","id":65,"created_at":1779482248,"hex":"C5","tile":"6-4","rotation":1},{"type":"place_token","entity":"BR","entity_type":"corporation","id":66,"created_at":1779482249,"city":"6-4-0","slot":0,"tokener":"BR"},{"type":"run_routes","entity":"BR","entity_type":"corporation","id":67,"created_at":1779482256,"routes":[{"train":"2+2-0","connections":[["D6","B6","A5"]],"hexes":["D6","A5"],"revenue":40,"revenue_str":"D6-A5","nodes":["D6-0","A5-0"]},{"train":"2-2","connections":[["C5","A5"]],"hexes":["C5","A5"],"revenue":40,"revenue_str":"C5-A5","nodes":["C5-0","A5-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"BR","entity_type":"corporation","id":68,"created_at":1779482259,"kind":"payout"},{"type":"buy_shares","entity":0,"entity_type":"player","id":69,"created_at":1779482281,"shares":["BR_2"],"percent":10},{"type":"pass","entity":1,"entity_type":"player","id":70,"created_at":1779482286},{"type":"pass","entity":0,"entity_type":"player","id":71,"created_at":1779482294},{"type":"lay_tile","entity":"1","entity_type":"minor","id":72,"created_at":1779482305,"hex":"F2","tile":"8-1","rotation":1},{"type":"run_routes","entity":"1","entity_type":"minor","id":73,"created_at":1779482312,"auto_actions":[{"type":"pass","entity":"1","entity_type":"minor","created_at":1779482312}],"routes":[{"train":"3-0","connections":[["D2","E3"],["E3","G3"]],"hexes":["D2","E3","G3"],"revenue":60,"revenue_str":"D2-E3-G3","nodes":["D2-0","E3-0","G3-0"]}],"extra_revenue":0,"subsidy":0},{"type":"pass","entity":"5","entity_type":"minor","id":74,"created_at":1779482347},{"type":"run_routes","entity":"5","entity_type":"minor","id":75,"created_at":1779482350,"auto_actions":[{"type":"pass","entity":"5","entity_type":"minor","created_at":1779482350}],"routes":[{"train":"3-0","connections":[["I3","J4"],["J4","L4"]],"hexes":["I3","J4","L4"],"revenue":60,"revenue_str":"I3-J4-L4","nodes":["I3-0","J4-0","L4-0"]}],"extra_revenue":0,"subsidy":0},{"type":"lay_tile","entity":"TR","entity_type":"corporation","id":76,"created_at":1779482363,"hex":"F6","tile":"8-2","rotation":4},{"type":"pass","entity":"TR","entity_type":"corporation","id":77,"created_at":1779482374},{"type":"run_routes","entity":"TR","entity_type":"corporation","id":78,"created_at":1779482381,"routes":[{"train":"2-1","connections":[["L6","J6"]],"hexes":["L6","J6"],"revenue":40,"revenue_str":"L6-J6","nodes":["L6-0","J6-0"]},{"train":"2-0","connections":[["L6","M7"]],"hexes":["L6","M7"],"revenue":30,"revenue_str":"L6-M7","nodes":["L6-0","M7-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"TR","entity_type":"corporation","id":79,"created_at":1779482394,"kind":"payout"},{"type":"buy_train","entity":"TR","entity_type":"corporation","id":80,"created_at":1779482400,"train":"3-0","price":180,"variant":"3"},{"type":"pass","entity":"TR","entity_type":"corporation","id":81,"created_at":1779482405},{"type":"lay_tile","entity":"BR","entity_type":"corporation","id":82,"created_at":1779482417,"hex":"D4","tile":"9-0","rotation":0},{"type":"lay_tile","entity":"BR","entity_type":"corporation","id":83,"created_at":1779482422,"hex":"C7","tile":"9-1","rotation":0},{"type":"pass","entity":"BR","entity_type":"corporation","id":84,"created_at":1779482434},{"type":"run_routes","entity":"BR","entity_type":"corporation","id":85,"created_at":1779482438,"routes":[{"train":"2+2-0","connections":[["B8","C7","D6"],["D6","B6","A5"]],"hexes":["B8","D6","A5"],"revenue":70,"revenue_str":"B8-D6-A5","nodes":["B8-0","D6-0","A5-0"]},{"train":"2-2","connections":[["C5","A5"]],"hexes":["C5","A5"],"revenue":40,"revenue_str":"C5-A5","nodes":["C5-0","A5-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"BR","entity_type":"corporation","id":86,"created_at":1779482442,"kind":"payout"},{"type":"pass","entity":"BR","entity_type":"corporation","id":87,"created_at":1779482446},{"type":"lay_tile","entity":"1","entity_type":"minor","id":88,"created_at":1779482454,"hex":"E3","tile":"619-0","rotation":0},{"type":"run_routes","entity":"1","entity_type":"minor","id":89,"created_at":1779482459,"auto_actions":[{"type":"destination_connection","entity":"1","entity_type":"minor","created_at":1779482459}],"routes":[{"train":"3-1","connections":[["D2","E3"],["E3","G3"]],"hexes":["D2","E3","G3"],"revenue":80,"revenue_str":"D2-E3-G3","nodes":["D2-0","E3-0","G3-0"]}],"extra_revenue":0,"subsidy":0},{"type":"choose","entity":"1","entity_type":"minor","id":90,"created_at":1779482465,"choice":"L300 to BR treasury"},{"type":"choose","entity":"BR","entity_type":"corporation","id":91,"created_at":1779482471,"choice":"Replace"},{"type":"lay_tile","entity":"5","entity_type":"minor","id":92,"created_at":1779482492,"hex":"J4","tile":"619-1","rotation":0},{"type":"run_routes","entity":"5","entity_type":"minor","id":93,"created_at":1779482500,"auto_actions":[{"type":"pass","entity":"5","entity_type":"minor","created_at":1779482500}],"routes":[{"train":"3-1","connections":[["I3","J4"],["J4","L4"]],"hexes":["I3","J4","L4"],"revenue":70,"revenue_str":"I3-J4-L4","nodes":["I3-0","J4-0","L4-0"]}],"extra_revenue":0,"subsidy":0},{"type":"lay_tile","entity":"TR","entity_type":"corporation","id":94,"created_at":1779482511,"hex":"L6","tile":"619-2","rotation":1},{"type":"place_token","entity":"TR","entity_type":"corporation","id":95,"created_at":1779482527,"city":"619-0-0","slot":1,"tokener":"TR"},{"type":"run_routes","entity":"TR","entity_type":"corporation","id":96,"created_at":1779482537,"routes":[{"train":"3-0","connections":[["D2","E3"],["E3","G3"]],"hexes":["D2","E3","G3"],"revenue":80,"revenue_str":"D2-E3-G3","nodes":["D2-0","E3-0","G3-0"]},{"train":"2-1","connections":[["L6","M7"]],"hexes":["L6","M7"],"revenue":60,"revenue_str":"L6-M7","nodes":["L6-0","M7-0"]},{"train":"2-0","connections":[["L6","M5"]],"hexes":["L6","M5"],"revenue":50,"revenue_str":"L6-M5","nodes":["L6-0","M5-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"TR","entity_type":"corporation","id":97,"created_at":1779482541,"kind":"payout"},{"type":"pass","entity":"TR","entity_type":"corporation","id":98,"created_at":1779482546},{"type":"buy_shares","entity":1,"entity_type":"player","id":99,"created_at":1779482565,"shares":["TR_4"],"percent":10},{"type":"buy_shares","entity":0,"entity_type":"player","id":100,"created_at":1779482570,"shares":["BR_4"],"percent":10},{"type":"buy_shares","entity":1,"entity_type":"player","id":101,"created_at":1779482577,"shares":["TR_5"],"percent":10},{"type":"pass","entity":0,"entity_type":"player","id":102,"created_at":1779482580},{"type":"pass","entity":1,"entity_type":"player","id":103,"created_at":1779482583},{"type":"buy_train","entity":"TR","entity_type":"corporation","id":104,"created_at":1779482602,"train":"3+3-0","price":300,"variant":"3+3"},{"type":"discard_train","entity":"TR","entity_type":"corporation","id":105,"created_at":1779482611,"train":"2-0"},{"type":"lay_tile","entity":"BR","entity_type":"corporation","id":106,"created_at":1779482633,"hex":"G1","tile":"447-0","rotation":0},{"type":"lay_tile","entity":"BR","entity_type":"corporation","id":107,"created_at":1779482638,"hex":"I1","tile":"8-3","rotation":5},{"type":"run_routes","entity":"BR","entity_type":"corporation","id":108,"created_at":1779482648,"routes":[{"train":"2+2-0","connections":[["D2","E3"],["E3","G3"],["G3","I3"]],"hexes":["D2","E3","G3","I3"],"revenue":100,"revenue_str":"D2-E3-G3-I3","nodes":["D2-0","E3-0","G3-0","I3-0"]},{"train":"2-2","connections":[["D2","F2","G1"]],"hexes":["D2","G1"],"revenue":60,"revenue_str":"D2-G1","nodes":["D2-0","G1-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"BR","entity_type":"corporation","id":109,"created_at":1779482674,"kind":"payout"},{"type":"buy_train","entity":"BR","entity_type":"corporation","id":110,"created_at":1779482677,"train":"3+3-1","price":300,"variant":"3+3"},{"type":"choose_ability","entity":"P1","entity_type":"company","id":111,"created_at":1779482694,"choice":"player"},{"type":"buy_shares","entity":0,"entity_type":"player","id":112,"created_at":1779482697,"shares":["BR_5"],"percent":10},{"type":"buy_shares","entity":0,"entity_type":"player","id":113,"created_at":1779482708,"auto_actions":[{"type":"destination_connection","entity":"5","entity_type":"minor","created_at":1779482708}],"shares":["BR_6"],"percent":10},{"type":"choose","entity":"5","entity_type":"minor","id":114,"created_at":1779482713,"choice":"L250 to TR treasury"},{"type":"choose","entity":"TR","entity_type":"corporation","id":115,"created_at":1779482748,"choice":"Discard"},{"type":"lay_tile","entity":"TR","entity_type":"corporation","id":116,"created_at":1779482766,"hex":"E7","tile":"5-1","rotation":2},{"type":"lay_tile","entity":"TR","entity_type":"corporation","id":117,"created_at":1779482787,"hex":"K3","tile":"8851-2","rotation":2},{"type":"pass","entity":"TR","entity_type":"corporation","id":118,"created_at":1779568570},{"type":"run_routes","entity":"TR","entity_type":"corporation","id":119,"created_at":1779568611,"routes":[{"train":"3+3-0","connections":[["D2","E3"],["E3","G3"],["G3","I3"],["I3","J4"],["J4","K3"]],"hexes":["D2","E3","G3","I3","J4","K3"],"revenue":150,"revenue_str":"D2-E3-G3-I3-J4-K3","nodes":["D2-0","E3-0","G3-0","I3-0","J4-0","K3-0"]},{"train":"3-0","connections":[["M7","L6"],["L6","M5"]],"hexes":["M7","L6","M5"],"revenue":80,"revenue_str":"M7-L6-M5","nodes":["M7-0","L6-0","M5-0"]},{"train":"2-1","connections":[["L6","J6"]],"hexes":["L6","J6"],"revenue":50,"revenue_str":"L6-J6","nodes":["L6-0","J6-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"TR","entity_type":"corporation","id":120,"created_at":1779568612,"kind":"withhold"},{"type":"lay_tile","entity":"BR","entity_type":"corporation","id":121,"created_at":1779568639,"hex":"D6","tile":"15-0","rotation":5},{"type":"run_routes","entity":"BR","entity_type":"corporation","id":122,"created_at":1779568654,"routes":[{"train":"3+3-1","connections":[["D2","E3"],["E3","G3"],["G3","I3"],["I3","J4"],["J4","K3"]],"hexes":["D2","E3","G3","I3","J4","K3"],"revenue":150,"revenue_str":"D2-E3-G3-I3-J4-K3","nodes":["D2-0","E3-0","G3-0","I3-0","J4-0","K3-0"]},{"train":"2+2-0","connections":[["A5","B6","D6"],["D6","E7"],["E7","F6","H6"]],"hexes":["A5","D6","E7","H6"],"revenue":90,"revenue_str":"A5-D6-E7-H6","nodes":["A5-0","D6-0","E7-0","H6-0"]},{"train":"2-2","connections":[["D2","F2","G1"]],"hexes":["D2","G1"],"revenue":60,"revenue_str":"D2-G1","nodes":["D2-0","G1-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"BR","entity_type":"corporation","id":123,"created_at":1779568656,"kind":"withhold"},{"type":"buy_train","entity":"BR","entity_type":"corporation","id":124,"created_at":1779568667,"train":"4+4-0","price":600,"variant":"4+4"},{"type":"lay_tile","entity":"TR","entity_type":"corporation","id":125,"created_at":1779568702,"hex":"F2","tile":"23-0","rotation":3},{"type":"pass","entity":"TR","entity_type":"corporation","id":126,"created_at":1779568708},{"type":"run_routes","entity":"TR","entity_type":"corporation","id":127,"created_at":1779568717,"routes":[{"train":"3+3-0","connections":[["D2","E3"],["E3","G3"],["G3","I3"],["I3","J4"],["J4","K3"]],"hexes":["D2","E3","G3","I3","J4","K3"],"revenue":170,"revenue_str":"D2-E3-G3-I3-J4-K3","nodes":["D2-0","E3-0","G3-0","I3-0","J4-0","K3-0"]},{"train":"3-0","connections":[["M7","L6"],["L6","M5"]],"hexes":["M7","L6","M5"],"revenue":90,"revenue_str":"M7-L6-M5","nodes":["M7-0","L6-0","M5-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"TR","entity_type":"corporation","id":128,"created_at":1779568720,"kind":"payout"},{"type":"buy_train","entity":"TR","entity_type":"corporation","id":129,"created_at":1779568731,"train":"4+4-1","price":600,"variant":"4+4"},{"type":"buy_shares","entity":1,"entity_type":"player","id":130,"created_at":1779568751,"shares":["TR_6"],"percent":10},{"type":"pass","entity":0,"entity_type":"player","id":131,"created_at":1779568758},{"type":"buy_shares","entity":1,"entity_type":"player","id":132,"created_at":1779568762,"shares":["TR_7"],"percent":10},{"type":"pass","entity":0,"entity_type":"player","id":133,"created_at":1779568765},{"type":"pass","entity":1,"entity_type":"player","id":134,"created_at":1779568766},{"type":"run_routes","entity":"BR","entity_type":"corporation","id":135,"created_at":1779568772,"routes":[{"train":"4+4-0","connections":[["A5","B6","D6"],["D6","E7"],["E7","F6","H6"],["H6","J6"],["J6","L6"],["L6","M5"]],"hexes":["A5","D6","E7","H6","J6","L6","M5"],"revenue":160,"revenue_str":"A5-D6-E7-H6-J6-L6-M5","nodes":["A5-0","D6-0","E7-0","H6-0","J6-0","L6-0","M5-0"]},{"train":"3+3-1","connections":[["D2","E3"],["E3","G3"],["G3","I3"],["I3","J4"],["J4","K3"]],"hexes":["D2","E3","G3","I3","J4","K3"],"revenue":170,"revenue_str":"D2-E3-G3-I3-J4-K3","nodes":["D2-0","E3-0","G3-0","I3-0","J4-0","K3-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"BR","entity_type":"corporation","id":136,"created_at":1779568775,"kind":"payout"},{"type":"lay_tile","entity":"TR","entity_type":"corporation","id":137,"created_at":1779568793,"hex":"G1","tile":"405-0","rotation":5},{"type":"run_routes","entity":"TR","entity_type":"corporation","id":138,"created_at":1779568800,"routes":[{"train":"4+4-1","connections":[["M5","L6"],["L6","J6"],["J6","H6"],["H6","F6","E7"],["E7","D6"],["D6","B6","A5"]],"hexes":["M5","L6","J6","H6","E7","D6","A5"],"revenue":160,"revenue_str":"M5-L6-J6-H6-E7-D6-A5","nodes":["M5-0","L6-0","J6-0","H6-0","E7-0","D6-0","A5-0"]},{"train":"3+3-0","connections":[["D2","E3"],["E3","G3"],["G3","I3"],["I3","J4"],["J4","K3"]],"hexes":["D2","E3","G3","I3","J4","K3"],"revenue":170,"revenue_str":"D2-E3-G3-I3-J4-K3","nodes":["D2-0","E3-0","G3-0","I3-0","J4-0","K3-0"]},{"train":"3-0","connections":[["G1","F2","E3"],["E3","D4","C5"]],"hexes":["G1","E3","C5"],"revenue":90,"revenue_str":"G1-E3-C5","nodes":["G1-0","E3-0","C5-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"TR","entity_type":"corporation","id":139,"created_at":1779568801,"kind":"payout"},{"type":"buy_shares","entity":0,"entity_type":"player","id":140,"created_at":1779568889,"shares":["BR_7"],"percent":10},{"type":"par","entity":1,"entity_type":"player","id":141,"created_at":1779568929,"corporation":"CR","share_price":"70,7,3","slot":0},{"type":"choose","entity":1,"entity_type":"player","id":142,"created_at":1779568932,"choice":40},{"type":"choose","entity":1,"entity_type":"player","id":143,"created_at":1779568935,"choice":"D"},{"type":"buy_shares","entity":0,"entity_type":"player","id":144,"created_at":1779568948,"shares":["BR_8"],"percent":10},{"type":"buy_shares","entity":1,"entity_type":"player","id":145,"created_at":1779568967,"shares":["CR_1"],"percent":10},{"type":"buy_shares","entity":0,"entity_type":"player","id":146,"created_at":1779568979,"shares":["CR_2"],"percent":10},{"type":"undo","entity":1,"entity_type":"player","id":147,"created_at":1779569015},{"type":"buy_shares","entity":0,"entity_type":"player","id":148,"created_at":1779569024,"shares":["CR_2"],"percent":10},{"type":"buy_shares","entity":1,"entity_type":"player","id":149,"created_at":1779569051,"shares":["CR_3"],"percent":10},{"type":"pass","entity":0,"entity_type":"player","id":150,"created_at":1779569323},{"type":"pass","entity":1,"entity_type":"player","id":151,"created_at":1779569324},{"type":"run_routes","entity":"BR","entity_type":"corporation","id":152,"created_at":1779569347,"routes":[{"train":"4+4-0","connections":[["A5","B6","D6"],["D6","E7"],["E7","F6","H6"],["H6","J6"],["J6","L6"],["L6","M5"]],"hexes":["A5","D6","E7","H6","J6","L6","M5"],"revenue":160,"revenue_str":"A5-D6-E7-H6-J6-L6-M5","nodes":["A5-0","D6-0","E7-0","H6-0","J6-0","L6-0","M5-0"]},{"train":"3+3-1","connections":[["D2","F2","G1"]],"hexes":["D2","G1"],"revenue":110,"revenue_str":"D2-G1","nodes":["D2-0","G1-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"BR","entity_type":"corporation","id":153,"created_at":1779569349,"kind":"payout"},{"type":"lay_tile","entity":"CR","entity_type":"corporation","id":154,"created_at":1779569376,"hex":"J2","tile":"57-1","rotation":2},{"type":"pass","entity":"CR","entity_type":"corporation","id":155,"created_at":1779569432},{"type":"place_token","entity":"CR","entity_type":"corporation","id":156,"created_at":1779569440,"city":"57-1-0","slot":0,"tokener":"CR"},{"type":"undo","entity":"CR","entity_type":"corporation","id":157,"created_at":1779569461},{"type":"pass","entity":"CR","entity_type":"corporation","id":158,"created_at":1779569466},{"type":"buy_train","entity":"CR","entity_type":"corporation","id":159,"created_at":1779569468,"train":"6E-0","price":700,"variant":"6E"},{"type":"undo","entity":0,"entity_type":"player","id":160,"created_at":1779569478},{"type":"buy_train","entity":"CR","entity_type":"corporation","id":161,"created_at":1779569496,"train":"3+3-0","price":20},{"type":"pass","entity":"CR","entity_type":"corporation","id":162,"created_at":1779569540},{"type":"run_routes","entity":"TR","entity_type":"corporation","id":163,"created_at":1779569561,"routes":[{"train":"4+4-1","connections":[["D2","E3"],["E3","G3"],["G3","I3"],["I3","J4"],["J4","L4"],["L4","M5"]],"hexes":["D2","E3","G3","I3","J4","L4","M5"],"revenue":210,"revenue_str":"D2-E3-G3-I3-J4-L4-M5","nodes":["D2-0","E3-0","G3-0","I3-0","J4-0","L4-0","M5-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"TR","entity_type":"corporation","id":164,"created_at":1779569568,"kind":"payout"},{"type":"pass","entity":"TR","entity_type":"corporation","id":165,"created_at":1779569575},{"type":"buy_shares","entity":0,"entity_type":"player","id":166,"created_at":1779569614,"shares":["TR_8"],"percent":10},{"type":"pass","entity":1,"entity_type":"player","id":167,"created_at":1779569627},{"type":"buy_shares","entity":0,"entity_type":"player","id":168,"created_at":1779569631,"shares":["CR_4"],"percent":10},{"type":"pass","entity":1,"entity_type":"player","id":169,"created_at":1779569634},{"type":"pass","entity":0,"entity_type":"player","id":170,"created_at":1779569640},{"type":"run_routes","entity":"BR","entity_type":"corporation","id":171,"created_at":1779569658,"routes":[{"train":"4+4-0","connections":[["D2","F2","G1"],["G1","I1","J2"],["J2","K3"],["K3","J4"],["J4","I3"],["I3","G3"]],"hexes":["D2","G1","J2","K3","J4","I3","G3"],"revenue":220,"revenue_str":"D2-G1-J2-K3-J4-I3-G3","nodes":["D2-0","G1-0","J2-0","K3-0","J4-0","I3-0","G3-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"BR","entity_type":"corporation","id":172,"created_at":1779569662,"kind":"payout"},{"type":"lay_tile","entity":"CR","entity_type":"corporation","id":173,"created_at":1779569683,"hex":"G1","tile":"497-0","rotation":4},{"type":"pass","entity":"CR","entity_type":"corporation","id":174,"created_at":1779569692},{"type":"buy_train","entity":"CR","entity_type":"corporation","id":175,"created_at":1779569697,"train":"8-0","price":800,"variant":"8"},{"type":"run_routes","entity":"TR","entity_type":"corporation","id":176,"created_at":1779569721,"routes":[{"train":"4+4-1","connections":[["D2","E3"],["E3","G3"],["G3","I3"],["I3","J4"],["J4","L4"],["L4","M5"]],"hexes":["D2","E3","G3","I3","J4","L4","M5"],"revenue":210,"revenue_str":"D2-E3-G3-I3-J4-L4-M5","nodes":["D2-0","E3-0","G3-0","I3-0","J4-0","L4-0","M5-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"TR","entity_type":"corporation","id":177,"created_at":1779569722,"kind":"payout"},{"type":"pass","entity":"TR","entity_type":"corporation","id":178,"created_at":1779569725},{"type":"run_routes","entity":"BR","entity_type":"corporation","id":179,"created_at":1779569730,"routes":[{"train":"4+4-0","connections":[["D2","F2","G1"],["G1","I1","J2"],["J2","K3"],["K3","J4"],["J4","I3"],["I3","G3"]],"hexes":["D2","G1","J2","K3","J4","I3","G3"],"revenue":230,"revenue_str":"D2-G1-J2-K3-J4-I3-G3","nodes":["D2-0","G1-0","J2-0","K3-0","J4-0","I3-0","G3-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"BR","entity_type":"corporation","id":180,"created_at":1779569733,"kind":"payout"},{"type":"lay_tile","entity":"CR","entity_type":"corporation","id":181,"created_at":1779569757,"hex":"C5","tile":"619-3","rotation":1},{"type":"run_routes","entity":"CR","entity_type":"corporation","id":182,"created_at":1779569769,"routes":[{"train":"8-0","connections":[["B8","C7","D6"],["D6","C5"],["C5","D4","E3"],["E3","F2","G1"],["G1","I1","J2"],["J2","K3"],["K3","J4"]],"hexes":["B8","D6","C5","E3","G1","J2","K3","J4"],"revenue":270,"revenue_str":"B8-D6-C5-E3-G1-J2-K3-J4","nodes":["B8-0","D6-0","C5-0","E3-0","G1-0","J2-0","K3-0","J4-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"CR","entity_type":"corporation","id":183,"created_at":1779569780,"kind":"payout"},{"type":"run_routes","entity":"TR","entity_type":"corporation","id":184,"created_at":1779569785,"routes":[{"train":"4+4-1","connections":[["B8","C7","D6"],["D6","B6","A5"],["A5","C5"],["C5","D4","E3"],["E3","G3"],["G3","I3"]],"hexes":["B8","D6","A5","C5","E3","G3","I3"],"revenue":210,"revenue_str":"B8-D6-A5-C5-E3-G3-I3","nodes":["B8-0","D6-0","A5-0","C5-0","E3-0","G3-0","I3-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"TR","entity_type":"corporation","id":185,"created_at":1779569787,"kind":"payout"},{"type":"pass","entity":"TR","entity_type":"corporation","id":186,"created_at":1779569793},{"type":"run_routes","entity":"BR","entity_type":"corporation","id":187,"created_at":1779569799,"routes":[{"train":"4+4-0","connections":[["D2","F2","G1"],["G1","I1","J2"],["J2","K3"],["K3","J4"],["J4","I3"],["I3","G3"]],"hexes":["D2","G1","J2","K3","J4","I3","G3"],"revenue":230,"revenue_str":"D2-G1-J2-K3-J4-I3-G3","nodes":["D2-0","G1-0","J2-0","K3-0","J4-0","I3-0","G3-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"BR","entity_type":"corporation","id":188,"created_at":1779569800,"kind":"payout"},{"type":"lay_tile","entity":"CR","entity_type":"corporation","id":189,"created_at":1779569823,"hex":"J4","tile":"63-0","rotation":0},{"type":"run_routes","entity":"CR","entity_type":"corporation","id":190,"created_at":1779569827,"routes":[{"train":"8-0","connections":[["B8","C7","D6"],["D6","C5"],["C5","D4","E3"],["E3","F2","G1"],["G1","I1","J2"],["J2","K3"],["K3","J4"]],"hexes":["B8","D6","C5","E3","G1","J2","K3","J4"],"revenue":280,"revenue_str":"B8-D6-C5-E3-G1-J2-K3-J4","nodes":["B8-0","D6-0","C5-0","E3-0","G1-0","J2-0","K3-0","J4-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"CR","entity_type":"corporation","id":191,"created_at":1779569828,"kind":"payout"},{"type":"run_routes","entity":"TR","entity_type":"corporation","id":192,"created_at":1779569896,"routes":[{"train":"4+4-1","connections":[["D2","E3"],["E3","G3"],["G3","I3"],["I3","J4"],["J4","L4"],["L4","M5"]],"hexes":["D2","E3","G3","I3","J4","L4","M5"],"revenue":220,"revenue_str":"D2-E3-G3-I3-J4-L4-M5","nodes":["D2-0","E3-0","G3-0","I3-0","J4-0","L4-0","M5-0"]}],"extra_revenue":0,"subsidy":0},{"type":"dividend","entity":"TR","entity_type":"corporation","id":193,"created_at":1779569898,"kind":"payout"},{"type":"pass","entity":"TR","entity_type":"corporation","id":194,"created_at":1779569919}],"id":"hs_ebwckqvc_1778333698","players":[{"name":"Player 1","id":0},{"name":"Player 2","id":1}],"title":"1880 Romania Transilvania","description":"","min_players":"2","max_players":"2","settings":{"optional_rules":[],"seed":""},"mode":"hotseat","user":{"id":0,"name":"You"},"created_at":"2026-05-22","loaded":true,"result":{"0":3014,"1":3383},"manually_ended":false,"game_end_reason":"final_train","turn":9,"round":"Operating Round","acting":[1,0],"updated_at":1779569919} \ No newline at end of file diff --git a/spec/lib/engine/game/g_1880_romania_transilvania/game_spec.rb b/spec/lib/engine/game/g_1880_romania_transilvania/game_spec.rb new file mode 100644 index 0000000000..166411dac4 --- /dev/null +++ b/spec/lib/engine/game/g_1880_romania_transilvania/game_spec.rb @@ -0,0 +1,205 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module Engine + describe Game::G1880RomaniaTransilvania::Game do + let(:players) { %w[Alice Bob] } + let(:game) { Game::G1880RomaniaTransilvania::Game.new(players) } + + it 'initialises without error' do + expect(game).to be_a(described_class) + end + + it 'has the correct player range' do + expect(described_class::PLAYER_RANGE).to eq([2, 2]) + end + + it 'has the correct game title' do + expect(described_class::GAME_TITLE).to eq('1880 Romania Transilvania') + end + + it 'has the correct development stage' do + expect(described_class::DEV_STAGE).to eq(:alpha) + end + + it 'depends on 1880 Romania' do + expect(described_class::DEPENDS_ON).to eq('1880 Romania') + end + + it 'has the correct starting cash for 2 players' do + expect(described_class::STARTING_CASH).to eq({ 2 => 350 }) + end + + it 'has the correct cert limit for 2 players' do + expect(described_class::CERT_LIMIT).to eq({ 2 => 11 }) + end + + describe 'game initialization' do + it 'has the correct number of companies' do + expect(game.companies.size).to eq(4) + end + + it 'has the correct company symbols' do + company_ids = game.companies.map(&:id) + expect(company_ids).to contain_exactly('P1', 'P3', 'P5', 'P8') + end + + it 'has the correct number of minors' do + expect(game.minors.size).to eq(3) + end + + it 'has the correct minor ids' do + minor_ids = game.minors.map(&:id) + expect(minor_ids).to contain_exactly('1', '4', '5') + end + + it 'has the correct minor coordinates' do + minor_coords = game.minors.to_h { |m| [m.id, m.coordinates] } + expect(minor_coords['1']).to eq('D2') + expect(minor_coords['4']).to eq('B8') + expect(minor_coords['5']).to eq('J4') + end + + it 'has the correct number of corporations' do + expect(game.corporations.size).to eq(4) + end + + it 'has the correct corporation ids' do + corp_ids = game.corporations.map(&:id) + expect(corp_ids).to contain_exactly('BR', 'CR', 'SZ', 'TR') + end + + it 'has the correct corporation coordinates' do + corp_coords = game.corporations.to_h { |c| [c.id, c.coordinates] } + expect(corp_coords['BR']).to eq('D6') + expect(corp_coords['CR']).to eq('E3') + expect(corp_coords['SZ']).to eq('G1') + expect(corp_coords['TR']).to eq('L6') + end + end + + describe 'train configuration' do + it 'has the correct number of each train type' do + train_counts = game.depot.trains.each_with_object({}) do |train, hash| + hash[train.name] = hash.fetch(train.name, 0) + 1 + end + expect(train_counts['2']).to eq(6) + expect(train_counts['2+2']).to eq(3) + expect(train_counts['3']).to eq(3) + expect(train_counts['3+3']).to eq(2) + expect(train_counts['4']).to eq(2) + expect(train_counts['4+4']).to eq(2) + expect(train_counts['6']).to eq(2) + expect(train_counts['6E']).to eq(1) + expect(train_counts['8']).to eq(1) + expect(train_counts['2R']).to eq(6) + end + + it 'has 2+2 train without open_borders event' do + t_2p2 = game.depot.trains.find { |t| t.name == '2+2' } + expect(t_2p2).not_to be_nil + expect(t_2p2.events).to eq([]) + end + + it 'has 3+3 train with communist_takeover event' do + t_3p3 = game.depot.trains.find { |t| t.name == '3+3' } + expect(t_3p3).not_to be_nil + expect(t_3p3.events).to eq([{ 'type' => 'communist_takeover' }]) + end + + it 'has 6E train with signal_end_game event' do + t_6e = game.depot.trains.find { |t| t.name == '6E' } + expect(t_6e).not_to be_nil + expect(t_6e.events).to eq([{ 'type' => 'signal_end_game', 'when' => 1 }]) + end + end + + describe 'par chart' do + it 'returns a hash of share prices to par values' do + expect(game.par_chart).to be_a(Hash) + end + + it 'has entries for all share prices' do + expect(game.par_chart.size).to eq(game.share_prices.size) + end + + it 'has nil values for par chart' do + game.par_chart.each do |_sp, par_values| + expect(par_values).to eq([nil, nil]) + end + end + end + + describe 'dummy company for unused privates' do + it 'returns dummy company for consortiu (P2)' do + expect(game.consortiu).to be_a(Engine::Company) + expect(game.consortiu.id).to eq('DUMMY') + expect(game.consortiu.name).to eq('Dummy Company') + end + + it 'returns dummy company for danube_port (P4)' do + expect(game.danube_port).to be_a(Engine::Company) + expect(game.danube_port.id).to eq('DUMMY') + end + + it 'returns dummy company for malaxa (P6)' do + expect(game.malaxa).to be_a(Engine::Company) + expect(game.malaxa.id).to eq('DUMMY') + end + + it 'returns dummy company for rocket (P7)' do + expect(game.rocket).to be_a(Engine::Company) + expect(game.rocket.id).to eq('DUMMY') + end + + it 'dummy company is closed' do + expect(game.consortiu.closed?).to be true + end + + it 'all dummy methods return the same instance' do + expect(game.consortiu).to eq(game.danube_port) + expect(game.danube_port).to eq(game.malaxa) + expect(game.malaxa).to eq(game.rocket) + end + end + + describe 'location names' do + it 'has the correct location names from map' do + expect(Game::G1880RomaniaTransilvania::Map::LOCATION_NAMES['G1']).to eq('Satu Mare') + expect(Game::G1880RomaniaTransilvania::Map::LOCATION_NAMES['E3']).to eq('Oradea') + expect(Game::G1880RomaniaTransilvania::Map::LOCATION_NAMES['J4']).to eq('Cluj-Napoca') + expect(Game::G1880RomaniaTransilvania::Map::LOCATION_NAMES['L6']).to eq('Sibiu') + expect(Game::G1880RomaniaTransilvania::Map::LOCATION_NAMES['D2']).to eq('Viena / Budapešta') + end + end + + describe 'map configuration' do + it 'uses pointy layout' do + expect(Game::G1880RomaniaTransilvania::Map::LAYOUT).to eq(:pointy) + end + + it 'has the correct axes configuration' do + expect(Game::G1880RomaniaTransilvania::Map::AXES).to eq({ x: :letter, y: :number }) + end + + it 'has hex definitions' do + expect(Game::G1880RomaniaTransilvania::Map::HEXES).to be_a(Hash) + expect(Game::G1880RomaniaTransilvania::Map::HEXES[:white]).to be_a(Hash) + expect(Game::G1880RomaniaTransilvania::Map::HEXES[:gray]).to be_a(Hash) + expect(Game::G1880RomaniaTransilvania::Map::HEXES[:red]).to be_a(Hash) + end + + it 'has offboard locations' do + hexes = Game::G1880RomaniaTransilvania::Map::HEXES + expect(hexes[:red][['K7']]).to include('offboard') + expect(hexes[:red][['M7']]).to include('offboard') + end + + it 'has Istanbul offboard with correct revenue' do + hexes = Game::G1880RomaniaTransilvania::Map::HEXES + expect(hexes[:red][['K7']]).to include('offboard=revenue:yellow_10|green_20|brown_40|gray_50,hide:1,groups:Istanbul') + end + end + end +end