diff --git a/lib/engine/game/g_18_ka.rb b/lib/engine/game/g_18_ka.rb
new file mode 100644
index 0000000000..b707cb791c
--- /dev/null
+++ b/lib/engine/game/g_18_ka.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module Engine
+ module Game
+ module G18KA
+ end
+ end
+end
diff --git a/lib/engine/game/g_18_ka/game.rb b/lib/engine/game/g_18_ka/game.rb
new file mode 100644
index 0000000000..ed4004d6b2
--- /dev/null
+++ b/lib/engine/game/g_18_ka/game.rb
@@ -0,0 +1,933 @@
+# frozen_string_literal: true
+
+require_relative 'meta'
+require_relative '../base'
+require_relative '../../game_error'
+require_relative '../g_1856/game'
+
+module Engine
+ module Game
+ module G18KA
+ class Game < G1856::Game
+ include_meta(G18KA::Meta)
+
+ CURRENCY_FORMAT_STR = '%dc'
+
+ BANK_CASH = 12_000
+
+ CERT_LIMIT = { 2 => 20, 3 => 13, 4 => 10 }.freeze
+ def cert_limit
+ # cert limit isn't dynamic in 1836jr56
+ CERT_LIMIT[@players.size]
+ end
+
+ STARTING_CASH = { 3 => 560, 4 => 420, 5 => 336, 6 => 280, 7 => 240 }.freeze
+
+ LAYOUT = :flat
+ # AXES = { x: :letter, y: :number }.freeze
+
+ # colors
+ OL_LIGHT_GREEN = '#7bb137'
+ WM_CYAN = '#37b2e2'
+ KM_ORANGE = '#eb6f0e'
+ THARSIS_BROWN = '#881a1e'
+ NM_NAVY = '#004d95'
+ PMC_WHITE = '#FFF'
+
+ NATIONAL_ = '#AA4444'
+ ATLAS_VIOLET = '#8811CC'
+ MARTIAN_RED = '#a1251b'
+ MOUNTAIN_ENGINEER = '#daa'
+ WATER_ENGINEER = '#cef'
+ IPM_MAIN = '#ccc'
+ JOVIAN_ORANGE = '#C99039'
+ ICTN_GREEN = '#228855'
+ MWM_BLUE = '#118'
+ CAN_RED = '#FF0000'
+ TITAN_RED = '#c43'
+ ICY_WATER = '#b0e1eb'
+ ASTEROID_GRAY = '#777'
+ # TILE_TYPE = :lawson
+ TILES = {
+
+ '3' => 5,
+ '4' => 5,
+ '58' => 5,
+
+ '5' => 4,
+ '6' => 4,
+ '57' => 8,
+
+ '7' => 8,
+ '8' => 16,
+ '9' => 16,
+
+ '59' => 2,
+
+ '80' => 5,
+ '81' => 5,
+ '82' => 5,
+ '83' => 5,
+
+ '141' => 5,
+ '142' => 5,
+ '143' => 5,
+ '144' => 5,
+
+ '147' => 5,
+ '146' => 5,
+ '145' => 5,
+
+ '64' => 1,
+ '65' => 1,
+ '66' => 1,
+ '67' => 1,
+ '68' => 1,
+
+ '544' => 5,
+ '545' => 5,
+ '546' => 5,
+
+ '14' => 5,
+ '15' => 5,
+ '619' => 5,
+
+ '125' => 10,
+
+ '51' => 5,
+ '915' => 5,
+
+ '60' => 5, #
+ # OO tiles
+ 'OO1' => {
+ 'count' => 1,
+ 'color' => 'gray',
+ 'code' => 'city=revenue:60,slots:3;label=OO;'\
+ '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',
+ },
+ 'OO2' => {
+ 'count' => 1,
+ 'color' => 'gray',
+ 'code' => 'city=revenue:70,loc:0;city=revenue:70,loc:3;label=OO;'\
+ 'path=a:0,b:_0;path=a:1,b:_0;path=a:4,b:_0;'\
+ 'path=a:2,b:_1;path=a:3,b:_1;path=a:5,b:_1',
+
+ },
+ # NNNY
+ 'NNNY1' => {
+ 'count' => 1,
+ 'color' => 'green',
+ 'code' =>
+ 'city=revenue:40;city=revenue:40;city=revenue:40;town=revenue:20;label=NNNY;'\
+ 'path=a:1,b:_0;path=a:_0,b:_3;path=a:3,b:_1;path=a:_1,b:_3;path=a:5,b:_2;path=a:_2,b:_3;',
+ },
+ 'NNNY2' => {
+ 'count' => 1,
+ 'color' => 'brown',
+ 'code' =>
+ 'city=revenue:60;city=revenue:60;city=revenue:60;town=revenue:30,loc:center;label=NNNY;'\
+ 'path=a:1,b:_0;path=a:_0,b:_3;path=a:3,b:_1;path=a:_1,b:_3;path=a:5,b:_2;path=a:_2,b:_3;path=a:2,b:_3',
+ },
+ 'NNNY3' => {
+ 'count' => 1,
+ 'color' => 'gray',
+ 'code' =>
+ 'city=revenue:80;city=revenue:80;city=revenue:80;town=revenue:40,loc:center;label=NNNY;'\
+ 'path=a:1,b:_0;path=a:_0,b:_3;path=a:3,b:_1;path=a:_1,b:_3;path=a:5,b:_2;path=a:_2,b:_3;'\
+ 'path=a:2,b:_3;path=a:4,b:_3',
+ },
+ # Capitol Tiles
+ 'CAP1' => {
+ 'count' => 1,
+ 'color' => 'green',
+ 'code' =>
+ 'city=revenue:60,loc:1.5;city=revenue:60,loc:4.5;label=C;'\
+ 'path=a:1,b:_0;path=a:2,b:_0;path=a:4,b:_1;path=a:5,b:_1',
+ },
+ 'CAP2' => {
+ 'count' => 1,
+ 'color' => 'brown',
+ 'code' =>
+ 'city=revenue:80;city=revenue:80;city=revenue:40;label=C;'\
+ 'path=a:1,b:_0;path=a:2,b:_0;path=a:4,b:_1;path=a:5,b:_1;'\
+ 'path=a:0,b:_2;path=a:3,b:_2',
+ },
+ 'CAP3' => {
+ 'count' => 1,
+ 'color' => 'gray',
+ 'code' =>
+ 'city=revenue:100;city=revenue:100;city=revenue:50,slots:2;label=C;'\
+ 'path=a:1,b:_0;path=a:2,b:_0;path=a:4,b:_1;path=a:5,b:_1;'\
+ 'path=a:0,b:_2;path=a:3,b:_2',
+ },
+ # Farm
+ 'FARM1' => {
+ 'count' => 1,
+ 'color' => 'green',
+ 'code' =>
+ 'city=revenue:40;label=F;path=a:0,b:_0;path=a:2,b:_0',
+ },
+ 'FARM2' => {
+ 'count' => 1,
+ 'color' => 'brown',
+ 'code' =>
+ 'city=revenue:50,slots:2;label=F;path=a:0,b:_0;path=a:2,b:_0;path=a:4,b:_0',
+ },
+ 'FARM3' => {
+ 'count' => 1,
+ 'color' => 'gray',
+ 'code' =>
+ 'city=revenue:60,slots:3;label=F;path=a:0,b:_0;path=a:2,b:_0;path=a:4,b:_0;path=a:5,b:_0',
+ },
+ # Space elevator
+ 'SE1' => {
+ 'count' => 1,
+ 'color' => 'green',
+ 'code' =>
+ 'city=revenue:50;label=SE;path=a:0,b:_0;path=a:2,b:_0;path=a:4,b:_0',
+ },
+ 'SE2' => {
+ 'count' => 1,
+ 'color' => 'brown',
+ 'code' =>
+ 'city=revenue:70,slots:2;label=SE;path=a:0,b:_0;path=a:2,b:_0;path=a:4,b:_0;path=a:5,b:_0',
+ },
+ 'SE3' => {
+ 'count' => 1,
+ 'color' => 'gray',
+ 'code' =>
+ 'city=revenue:90,slots:3;label=SE;path=a:0,b:_0;path=a:2,b:_0;path=a:4,b:_0;path=a:5,b:_0;path=a:3,b:_0',
+ },
+ # Port tile tech tree
+ 'PORT1' => {
+ 'count' => 4,
+ 'color' => 'yellow',
+ 'code' => 'city=revenue:30;label=P;path=a:0,b:_0;path=a:2,b:_0',
+ },
+ 'PORT2a' => {
+ 'count' => 1,
+ 'color' => 'green',
+ 'code' => 'city=revenue:40,slots:2;label=P;path=a:0,b:_0;path=a:2,b:_0;path=a:1,b:_0;path=a:3,b:_0',
+ },
+ 'PORT2b' => {
+ 'count' => 1,
+ 'color' => 'green',
+ 'code' => 'city=revenue:40;label=P;path=a:0,b:_0;path=a:2,b:_0;path=a:1,b:_0',
+ },
+ 'PORT2c' => {
+ 'count' => 1,
+ 'color' => 'green',
+ 'code' => 'city=revenue:40;label=P;path=a:0,b:_0;path=a:2,b:_0;path=a:3,b:_0',
+ },
+ 'PORT2d' => {
+ 'count' => 1,
+ 'color' => 'green',
+ 'code' => 'city=revenue:40;label=P;path=a:0,b:_0;path=a:2,b:_0;path=a:5,b:_0',
+ },
+ 'PORT3' => {
+ 'count' => 4,
+ 'color' => 'brown',
+ 'code' => 'city=revenue:50,slots:2;label=P;path=a:0,b:_0;path=a:2,b:_0;path=a:1,b:_0;path=a:3,b:_0',
+ },
+ 'PORT4a' => {
+ 'count' => 3,
+ 'color' => 'gray',
+ 'code' => 'city=revenue:60,slots:3;label=P;path=a:0,b:_0;path=a:2,b:_0;path=a:1,b:_0;path=a:3,b:_0',
+ },
+ 'PORT4b' => {
+ 'count' => 3,
+ 'color' => 'gray',
+ 'code' => 'city=revenue:70,slots:2;label=P;path=a:0,b:_0;path=a:2,b:_0;path=a:1,b:_0;path=a:3,b:_0',
+ },
+ }.freeze
+
+ LOCATION_NAMES = {
+ 'E4' => 'Arcadian Bay',
+ 'I4' => 'Borealis Harbor',
+ 'M8' => 'Chryse Gulf',
+ 'I10' => 'Deimos Down',
+ 'C12' => 'Olympus Mons',
+ 'F13' => 'Ascreus',
+ 'N15' => 'Chryse Harbor',
+ 'A16' => 'Elysium',
+ 'E16' => 'Pavis',
+ 'D19' => 'Arsia',
+ 'N21' => 'Hellas Sea',
+ 'E24' => 'Southern Mines',
+
+ }.freeze
+
+ PHASES = [
+ {
+ name: '2',
+ train_limit: 4,
+ tiles: [:yellow],
+ status: %w[escrow facing_2],
+ operating_rounds: 1,
+ },
+ {
+ name: "2'",
+ on: "2+'",
+ train_limit: 4,
+ tiles: [:yellow],
+ status: %w[escrow facing_3],
+ operating_rounds: 1,
+ },
+ {
+ name: '3',
+ on: '3',
+ train_limit: 4,
+ tiles: %i[yellow green],
+ operating_rounds: 2,
+ status: %w[escrow facing_3 can_buy_companies],
+ },
+ {
+ name: "3'",
+ on: "3+'",
+ train_limit: 4,
+ tiles: %i[yellow green],
+ operating_rounds: 2,
+ status: %w[escrow facing_4 can_buy_companies],
+ },
+ {
+ name: '4',
+ on: '4',
+ train_limit: 3,
+ tiles: %i[yellow green],
+ operating_rounds: 2,
+ status: %w[escrow facing_4 can_buy_companies],
+ },
+ {
+ name: "4'",
+ on: "4+'",
+ train_limit: 3,
+ tiles: %i[yellow green],
+ operating_rounds: 2,
+ status: %w[incremental facing_5 can_buy_companies],
+ },
+ {
+ name: '5',
+ on: '5',
+ train_limit: 2,
+ tiles: %i[yellow green brown],
+ status: %w[incremental facing_5],
+ operating_rounds: 3,
+ },
+ {
+ name: "5'",
+ on: "5'",
+ train_limit: 2,
+ tiles: %i[yellow green brown],
+ status: %w[fullcap facing_6],
+ operating_rounds: 3,
+ },
+ {
+ name: '6',
+ on: '6',
+ train_limit: 2,
+ tiles: %i[yellow green brown gray],
+ status: %w[fullcap facing_6 upgradable_towns],
+ operating_rounds: 3,
+ },
+ {
+ name: '8',
+ on: '8',
+ train_limit: 2,
+ tiles: %i[yellow green brown gray black],
+ status: %w[fullcap facing_6 upgradable_towns],
+ operating_rounds: 3,
+ },
+ ].freeze
+
+ TRAINS = [{ name: '2', distance: 2, price: 100, rusts_on: '4', num: 4 },
+ {
+ name: '2+1',
+ distance: [{ 'nodes' => %w[city offboard], 'pay' => 2, 'visit' => 2 },
+ { 'nodes' => ['town'], 'pay' => 1, 'visit' => 1 }],
+ price: 125,
+ obsolete_on: '4',
+ num: 1,
+ },
+ {
+ name: '2+1`',
+ distance: [{ 'nodes' => %w[city offboard], 'pay' => 2, 'visit' => 2 },
+ { 'nodes' => ['town'], 'pay' => 1, 'visit' => 1 }],
+ price: 125,
+ obsolete_on: '4',
+ num: 1,
+ },
+ { name: '3', distance: 3, price: 225, rusts_on: '6', num: 3 },
+ {
+ name: '3+1',
+ distance: [{ 'nodes' => %w[city offboard], 'pay' => 3, 'visit' => 3 },
+ { 'nodes' => ['town'], 'pay' => 1, 'visit' => 1 }],
+ price: 250,
+ obsolete_on: '6',
+ num: 1,
+ },
+ {
+ name: "3+1'",
+ distance: [{ 'nodes' => %w[city offboard], 'pay' => 3, 'visit' => 3 },
+ { 'nodes' => ['town'], 'pay' => 1, 'visit' => 1 }],
+ price: 250,
+ obsolete_on: '6',
+ num: 1,
+ },
+ { name: '4', distance: 4, price: 350, rusts_on: '8', num: 2 },
+ {
+ name: '4+1',
+ distance: [{ 'nodes' => %w[city offboard], 'pay' => 4, 'visit' => 4 },
+ { 'nodes' => ['town'], 'pay' => 1, 'visit' => 1 }],
+ price: 375,
+ obsolete_on: '8', # also D
+ num: 1,
+ },
+ {
+ name: "4+1'",
+ distance: [{ 'nodes' => %w[city offboard], 'pay' => 4, 'visit' => 4 },
+ { 'nodes' => ['town'], 'pay' => 1, 'visit' => 1 }],
+ price: 375,
+ obsolete_on: '8', # also D
+ num: 1,
+ },
+ {
+ name: '5',
+ distance: 5,
+ price: 550,
+ num: 1,
+ events: [{ 'type' => 'close_companies' }],
+ },
+ {
+ name: "5'",
+ distance: 5,
+ price: 550,
+ num: 1,
+ events: [{ 'type' => 'no_more_incremental_corps' }],
+ },
+ {
+ name: '6',
+ distance: 6,
+ price: 700,
+ num: 2,
+ events: [{ 'type' => 'nationalization' }, { 'type' => 'remove_tokens' }],
+ },
+ {
+ name: '8',
+ distance: 8,
+ price: 1000,
+ num: 16,
+ available_on: '6',
+ discount: { '4' => 350, "4'" => 350, '5' => 350, "5'" => 350, '6' => 350 },
+ variants: [
+ {
+ name: 'D',
+ distance: 999,
+ price: 1250,
+ available_on: '6',
+ discount: { '4' => 350, "4'" => 350, '5' => 350, "5'" => 350, '6' => 350 },
+ },
+ ],
+ }].freeze
+
+ COMPANIES = [
+ {
+ name: 'Platinum Credit Card',
+ value: 10,
+ revenue: 0,
+ desc: 'The owning corporation can have 1 additional outstanding loan, beyond the usual limit',
+ sym: 'A',
+ },
+ {
+ name: 'Deimos Drilling',
+ value: 20,
+ revenue: 5,
+ desc: 'The owning corporation gets 3 mountain Engineers and 1 mining Right. Other corporations can buy '\
+ 'mining Rights from this corporation for 50c',
+ sym: 'B',
+ },
+ {
+ name: 'Cosmic Corn Company',
+ value: 30,
+ revenue: 10,
+ desc: 'The owning corporation may place the Farm (F) tile on an equitorial hex as a bonus disconnected '\
+ 'tile lay. In exchange for closing this private the corporation may token it for free',
+ sym: 'C',
+ },
+ {
+ name: 'Bob\'s Better Bridges',
+ value: 40,
+ revenue: 10,
+ desc: 'This private comes with 2 water Engineers and 2 Harbor Rights. Other corporations can buy harbor '\
+ 'Rights from this corporation for 50c',
+ sym: 'D',
+ },
+ {
+ name: 'Planetary Espresso',
+ value: 50,
+ revenue: 10,
+ desc: 'The owning corporation may close this company to upgrade the NNNY hex/tile for free and the owning '\
+ 'corporation may also place a token on the NNNY hex for free, even without a connection',
+ sym: 'E',
+ },
+ {
+ name: 'Happy Happy Harbor',
+ value: 60,
+ revenue: 15,
+ desc: 'This private comes with 1 water Engineer and 2 Harbor Rights. When bought by a corporation, '\
+ 'the owning corporation may lay a second harbor marker on an harbor hex, doubling the harbor right bonus '\
+ 'for that hex. Other coproraitons can buy harbor rights from this corporation for 50c',
+ sym: 'F',
+ },
+ {
+ name: 'Capitol Contract',
+ value: 70,
+ revenue: 15,
+ desc: 'The owning corporation may place the Capitol (C) tile on an equitorial hex as a bonus disconnected '\
+ 'tile lay. In exchange for closing this private the corporation may token it for free',
+ sym: 'G',
+ },
+ {
+ name: 'Mole People',
+ value: 80,
+ revenue: 20,
+ desc: 'The private comes with 1 mining Engineer and 2 mining Rights. THe owning corporation may close '\
+ 'this private to place a mining token in an empty, connected station as a neutral token. Other '\
+ 'coprorations can buy mining rights from this corproation for 50c',
+ sym: 'H',
+ },
+ {
+ name: 'Space Elevator',
+ value: 90,
+ revenue: 20,
+ desc: 'The owning corporation may place the Space Elevator (SE) tile on an equitorial hex as a bonus '\
+ 'disconnected tile lay. In exchange for closing this private the corporation may token it for free',
+ sym: 'I',
+ },
+ {
+ name: 'Protoype Maglev',
+ value: 100,
+ revenue: 25,
+ desc: 'Comes with a 2T. The 2T train may be sold at any time (including SR) to any corporation '\
+ '(whose president\'s share has been bought) for any price from 1c up to 100c with consent from the '\
+ 'buyer and seller. The buying company may offer IPO shares as part of the sale counting at market price '\
+ 'with no monetary compensation to the selling corporation. The private is closed at the '\
+ 'end of the initial private auction',
+ sym: 'J',
+ },
+ {
+ name: 'UPF Bribes',
+ value: 125,
+ revenue: 30,
+ desc: 'This private can be exchanged as a buy action during any stock round to buy an IPO share of a '\
+ 'started corporation for free, with no compensation to the corporation',
+ sym: 'K',
+ },
+ {
+ name: 'Underground Connections',
+ value: 150,
+ revenue: 35,
+ desc: 'The owning corporation can close this company to repalce an existing token with a token of the '\
+ 'corporation, with exceptions. This private may not be sold to a corporation. In a stock round this '\
+ 'private can be exchanged to buy the presidency of a corporation with this private as '\
+ 'the only compensation to the corporation',
+ sym: 'L',
+ },
+ ].freeze
+
+ ASSIGNMENT_TOKENS = {
+ 'RdP' => '/icons/1846/sc_token.svg',
+ }.freeze
+ PORT_HEXES = %w[A9 B8 B10 D6 E5 E11 F4 F10 G7 H2 H4 H6 H10 I3 I9 J6 J8 K11].freeze
+ CORPORATIONS = [
+ # Tier 1
+ {
+ sym: 'OL',
+ name: 'Olympean Lines',
+ logo: '18_ka/OL',
+ simple_logo: '18_ka/OL.alt',
+ tokens: [0, 40, 80, 120],
+ coordinates: 'A12',
+ color: OL_LIGHT_GREEN,
+ text_color: 'black',
+ },
+ {
+ sym: 'AF',
+ name: 'Atlas Freight',
+ logo: '18_ka/AF',
+ simple_logo: '18_ka/AF.alt',
+ tokens: [0, 40, 80, 120],
+ coordinates: 'G8',
+ color: ATLAS_VIOLET,
+ text_color: 'white',
+ },
+ {
+ sym: 'KM',
+ name: 'Kasei Magway',
+ logo: '18_ka/KM',
+ simple_logo: '18_ka/KM.alt',
+ tokens: [0, 40, 80, 120],
+ coordinates: 'M20',
+ color: KM_ORANGE,
+ text_color: 'black',
+ },
+ {
+ sym: 'WM',
+ name: 'Whitney Magway',
+ logo: '18_ka/WM',
+ simple_logo: '18_ka/WM.alt',
+ tokens: [0, 40, 80, 120],
+ coordinates: 'K14',
+ color: WM_CYAN,
+ text_color: 'black',
+ },
+
+ # Tier 2
+ {
+ sym: 'C9S',
+ name: 'Cloud Nine Shipping',
+ logo: '18_ka/C9S',
+ simple_logo: '18_ka/C9S.alt',
+ tokens: [0, 40, 70, 100, 130],
+ coordinates: 'M12',
+ color: ICY_WATER,
+ text_color: 'black',
+ },
+ {
+ sym: 'TL',
+ name: 'Tharsis Lines',
+ logo: '18_ka/TL',
+ simple_logo: '18_ka/TL.alt',
+ tokens: [0, 40, 70, 100, 130],
+ coordinates: 'E10',
+ color: THARSIS_BROWN,
+ text_color: 'white',
+ },
+ {
+ sym: 'NM',
+ name: 'Noctis Magways',
+ logo: '18_ka/NM',
+ simple_logo: '18_ka/NM.alt',
+ tokens: [0, 40, 70, 100, 130],
+ coordinates: 'I19',
+ color: NM_NAVY,
+ text_color: 'white',
+ },
+ {
+ sym: 'ATN',
+ name: 'Asteroid Transportation Network',
+ logo: '18_ka/ATN',
+ simple_logo: '18_ka/ATN.alt',
+ tokens: [0, 40, 70, 100, 130],
+ coordinates: 'D7',
+ color: ASTEROID_GRAY,
+ text_color: 'white',
+ },
+ {
+ sym: 'PMC',
+ name: 'Polar Mining Corporation',
+ logo: '18_ka/PMC',
+ simple_logo: '18_ka/PMC.alt',
+ tokens: [0, 40, 70, 100, 130],
+ coordinates: 'L9',
+ color: PMC_WHITE,
+ text_color: 'black',
+ },
+
+ # Tier 3
+ {
+ sym: 'CMM',
+ name: 'Canadian Martian Magway',
+ logo: '18_ka/CMM',
+ simple_logo: '18_ka/CMM.alt',
+ tokens: [0, 40, 60, 80, 100, 120],
+ coordinates: 'F19',
+ color: CAN_RED,
+ text_color: 'black',
+ },
+ {
+ sym: 'IPM',
+ name: 'Inner Planets Magway',
+ logo: '18_ka/IPM',
+ simple_logo: '18_ka/IPM.alt',
+ tokens: [0, 40, 60, 80, 100, 120],
+ coordinates: 'C20',
+ color: IPM_MAIN,
+ text_color: 'black',
+ },
+ {
+ sym: 'TiTaN',
+ name: 'Tharsis Transportation Network',
+ logo: '18_ka/TiTaN',
+ simple_logo: '18_ka/TiTaN.alt',
+ tokens: [0, 40, 60, 80, 100, 120],
+ coordinates: 'F15',
+ color: TITAN_RED,
+ text_color: 'white',
+ },
+ {
+ sym: 'ICTN',
+ name: 'Indo-Chinese Transportation Network',
+ logo: '18_ka/ICTN',
+ simple_logo: '18_ka/ICTN.alt',
+ tokens: [0, 40, 60, 80, 100, 120],
+ coordinates: 'H13',
+ color: ICTN_GREEN,
+ text_color: 'white',
+ },
+ {
+ sym: 'JA',
+ name: 'Jovian Alliance',
+ logo: '18_ka/JA',
+ simple_logo: '18_ka/JA.alt',
+ tokens: [0, 40, 60, 80, 100, 120],
+ coordinates: 'H6',
+ color: JOVIAN_ORANGE,
+ text_color: 'black',
+ },
+ {
+ sym: 'MWM',
+ name: 'Milky Way Mining',
+ logo: '18_ka/MWM',
+ simple_logo: '18_ka/MWM.alt',
+ tokens: [0, 40, 60, 80, 100, 120],
+ coordinates: 'I8',
+ color: MWM_BLUE,
+ text_color: 'white',
+ },
+
+ {
+ sym: 'MGM',
+ logo: '18_ka/MGM',
+ simple_logo: '18_ka/MGM.alt',
+ name: 'Martian Global Magway',
+ tokens: [],
+ color: NATIONAL_,
+ text_color: 'white',
+ abilities: [
+ {
+ type: 'train_buy',
+ description: 'Inter train buy/sell at face value',
+ face_value: true,
+ },
+ {
+ type: 'train_limit',
+ increase: 99,
+ description: '3 train limit',
+ },
+ {
+ type: 'borrow_train',
+ train_types: %w[8 D],
+ description: 'May borrow a train when trainless*',
+ },
+ ],
+ reservation_color: nil,
+ },
+ ].freeze
+ # TODO: Make location name optional and refactor into 1856
+ def create_destinations(destinations)
+ @destinations = {}
+ destinations.each do |corp, dest|
+ dest_arr = Array(dest)
+ d_goals = Array(dest_arr.first)
+ d_start = dest_arr.size > 1 ? dest_arr.last : corporation_by_id(corp).coordinates
+ dest_arr.each do |d|
+ # Array(d).first allows us to treat 'E5' or %[O2 N3] identically
+ hex_by_id(Array(d).first).original_tile.icons << Part::Icon.new(icon_path(corp))
+ end
+ @destinations[corp] = [d_start, d_goals].freeze
+ end
+ end
+ DESTINATIONS = {
+ 'AF' => 'I14',
+ 'KM' => 'K16',
+ 'OL' => 'E16',
+ 'WM' => %w[N21 N23],
+
+ 'ATN' => 'I10',
+ 'C9S' => 'H11',
+ 'TL' => 'F15',
+ 'NM' => 'N15',
+ 'PMC' => 'M16',
+
+ 'JA' => 'M8',
+ 'IPM' => 'C12',
+ 'ICTN' => 'G20',
+ 'CMM' => 'K20',
+ 'MWM' => %w[D5 E4],
+ 'TiTaN' => %w[E24 F25],
+ }.freeze
+
+ # These cities upgrade to the common BarrieLondon green tile,
+ # but upgrade to specialized brown tiles
+ BARRIE_HEX = 'I3'
+ LONDON_HEX = 'H6'
+ HAMILTON_HEX = 'E5'
+ HEXES = {
+ gray: {
+ ['A10'] => 'path=a:0,b:4',
+ ['A14'] => 'path=a:5,b:3',
+ ['C22'] => 'path=a:4,b:3',
+ ['J21'] => 'path=a:2,b:4',
+ },
+ white: {
+ %w[B17 C8 D15 E6 E8 E12 E20 E22 F5 F17 G22 H7 H15 H19 I6 I20 J13 J15 K10 L13 L15 L21] => 'blank',
+ %w[B15 C18 C20 D7 E10 F23 F19 G6 G18 H13 J19 K14 K20 L9] => 'city=revenue:0',
+ %w[H17 I18 J17 K18 L11 M10] => 'upgrade=cost:40,terrain:water',
+ %w[L19] => 'town=revenue:0;upgrade=cost:40,terrain:water',
+ %w[B19 J5 B9 D21 F21 F7 G12 H21 I14 K16 K12 L7 M14] => 'town=revenue:0',
+ %w[C10 B13 D11 E14 D13 D17 H9 J9 I12 G14 D17 E18 M22] => 'upgrade=cost:30,terrain:mountain',
+ %w[F15 I8 H11 J11] => 'city=revenue:0;upgrade=cost:30,terrain:mountain',
+ %w[K8 F9] => 'city=revenue:0;upgrade=cost:20,terrain:swamp',
+ %w[J7 K6 G10 F11] => 'upgrade=cost:20,terrain:swamp',
+ %w[B11 C14] => 'town=revenue:0;upgrade=cost:30,terrain:mountain',
+ %w[H5 A12 M12 M16 M20] => 'city=revenue:0;label=P',
+ %w[D19 E16 F13] =>
+ 'town=revenue:0;upgrade=cost:60,terrain:mountain;icon=image:18_ka/plus_20,sticky:1',
+ },
+ red: {
+ ['C12'] => 'offboard=revenue:yellow_30|brown_60|black_70;path=a:5,b:_0,terminal:1;'\
+ 'path=a:0,b:_0,terminal:1;path=a:1,b:_0,terminal:1;path=a:2,b:_0,terminal:1;path=a:3,b:_0,terminal:1;'\
+ 'path=a:4,b:_0,terminal:1',
+ ['I10'] => 'offboard=revenue:yellow_40|brown_30|black_20;path=a:5,b:_0,terminal:1;'\
+ 'path=a:1,b:_0,terminal:1;path=a:3,b:_0,terminal:1;icon=image:18_ka/mine,sticky:1',
+ ['D5'] =>
+ 'offboard=revenue:yellow_30|brown_40|black_50,hide:1,groups:Arcadian Bay;'\
+ 'path=a:0,b:_0,terminal:1;path=a:5,b:_0,terminal:1;border=edge:4',
+ ['E4'] =>
+ 'offboard=revenue:yellow_30|brown_40|black_50,groups:Arcadian Bay;'\
+ 'path=a:0,b:_0,terminal:1;border=edge:1',
+ ['A16'] =>
+ 'offboard=revenue:yellow_30|brown_40|black_60,hide:1,groups:Elysium;'\
+ 'path=a:5,b:_0,terminal:1;path=a:4,b:_0,terminal:1;border=edge:0',
+ ['A18'] =>
+ 'offboard=revenue:yellow_30|brown_40|black_60,groups:Elysium;'\
+ 'path=a:5,b:_0,terminal:1;path=a:4,b:_0,terminal:1;border=edge:3',
+ ['E24'] =>
+ 'offboard=revenue:yellow_40|brown_50|black_20,hide:1,groups:Southern Mines;'\
+ 'path=a:3,b:_0,terminal:1;path=a:4,b:_0,terminal:1;border=edge:5',
+ ['F25'] =>
+ 'offboard=revenue:yellow_40|brown_50|black_20,groups:Southern Mines;'\
+ 'path=a:3,b:_0,terminal:1;border=edge:2;icon=image:18_ka/mine,sticky:1',
+ ['N21'] =>
+ 'offboard=revenue:yellow_30|brown_40|black_50,hide:1,groups:Hellas Sea;'\
+ 'path=a:1,b:_0,terminal:1;path=a:2,b:_0,terminal:1;border=edge:0',
+ ['N23'] =>
+ 'offboard=revenue:yellow_30|brown_40|black_50,groups:Hellas Sea;'\
+ 'path=a:2,b:_0,terminal:1;border=edge:3',
+ },
+ blue: {
+ %w[A8] => 'border=edge:4',
+ %w[B7] => 'border=edge:4;border=edge:1',
+ %w[C6] => 'border=edge:1',
+ %w[F3] => 'border=edge:5',
+ %w[G4] => 'border=edge:2;border=edge:4',
+ %w[H3 J3] => 'border=edge:1;border=edge:5',
+ %w[K4 L5] => 'border=edge:2;border=edge:5',
+ ['I4'] => 'town=revenue:yellow_10|brown_20|black_30;path=a:1,b:_0;path=a:0,b:_0;path=a:5,b:_0;'\
+ 'border=edge:2;border=edge:4;icon=image:port,sticky:1',
+ %w[M6] => 'border=edge:2;border=edge:0',
+ ['M8'] => 'offboard=revenue:yellow_20|brown_30|black_40;path=a:1,b:_0,terminal:1;path=a:2,b:_0,terminal:1;'\
+ 'border=edge:3;border=edge:5;icon=image:port,sticky:1',
+ %w[N9] => 'border=edge:2;border=edge:0',
+ %w[N11 N13] => 'border=edge:3;border=edge:0',
+ ['N15'] => 'offboard=revenue:yellow_20|brown_30|black_60;path=a:1,b:_0,terminal:1;'\
+ 'path=a:2,b:_0,terminal:1;border=edge:3;border=edge:0;icon=image:port,sticky:1',
+ ['N17'] => 'border=edge:3;border=edge:1;border=edge:0',
+ ['M18'] => 'border=edge:4;border=edge:5',
+ ['N19'] => 'border=edge:2;border=edge:3',
+ },
+ yellow: {
+ ['L17'] =>
+ 'city=revenue:20;town=revenue:20;path=a:2,b:_0;path=a:_0,b:_1;label=NNNY;upgrade=cost:40,terrain:water',
+ %w[D9 G8 G20] => 'city=revenue:0;city=revenue:0;label=OO',
+ %w[C16 G16] => 'city=revenue:0;label=E;',
+ %w[I16] => 'city=revenue:0;label=E;upgrade=cost:40,terrain:water',
+ },
+ }.freeze
+
+ LAKE_HEXES = [].freeze
+
+ SELL_BUY_ORDER = :sell_buy_sell
+ TRACK_RESTRICTION = :permissive
+ TILE_RESERVATION_BLOCKS_OTHERS = true
+ def national
+ @national ||= corporation_by_id('MGM')
+ end
+
+ def port
+ @port ||= company_by_id('RdP')
+ end
+
+ def company_bought(company, entity) end
+
+ def tunnel
+ raise GameError, "'tunnel' Should not be used"
+ end
+
+ def bridge
+ raise GameError, "'bridge' Should not be used"
+ end
+
+ def wsrc
+ raise GameError, "'wsrc' Should not be used"
+ end
+
+ def setup
+ @straight_city ||= @tiles.find { |t| t.name == '57' }
+ @sharp_city ||= @tiles.find { |t| t.name == '5' }
+ @gentle_city ||= @tiles.find { |t| t.name == '6' }
+
+ @straight_track ||= @tiles.find { |t| t.name == '9' }
+ @sharp_track ||= @tiles.find { |t| t.name == '7' }
+ @gentle_track ||= @tiles.find { |t| t.name == '8' }
+
+ @x_city ||= @tiles.find { |t| t.name == '14' }
+ @k_city ||= @tiles.find { |t| t.name == '15' }
+
+ @brown_london ||= @tiles.find { |t| t.name == '126' }
+ @brown_barrie ||= @tiles.find { |t| t.name == '127' }
+
+ @gray_hamilton ||= @tiles.find { |t| t.name == '123' }
+
+ @post_nationalization = false
+ @nationalization_train_discard_trigger = false
+ @national_formed = false
+
+ @pre_national_percent_by_player = {}
+ @pre_national_market_percent = 0
+
+ @pre_national_market_prices = {}
+ @nationalized_corps = []
+
+ @bankrupted = false
+
+ # Is the president of the national a "false" president?
+ # A false president gets the presidency with only one share; in this case the president gets
+ # the full president's certificate but is obligated to buy up to the full presidency in the
+ # following SR unless a different player becomes rightfully president during share exchange
+ # It is impossible for someone who didn't become president in
+ # exchange (1 share tops) to steal the presidency in the SR because
+ # they'd have to buy 2 shares in one action which is a no-no
+ # nil: Presidency not awarded yet at all
+ # not-nl: 1-share false presidency has been awarded to the player (value of var)
+ @false_national_president = nil
+
+ # CGR flags
+ @national_ever_owned_permanent = false
+
+ # Corp -> Borrowed Train
+ @borrowed_trains = {}
+ create_destinations(DESTINATIONS)
+ national.add_ability(self.class::NATIONAL_IMMOBILE_SHARE_PRICE_ABILITY)
+ national.add_ability(self.class::NATIONAL_FORCED_WITHHOLD_ABILITY)
+ end
+
+ def icon_path(corp)
+ super if corp == national
+
+ "../logos/18_ka/#{corp}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/engine/game/g_18_ka/meta.rb b/lib/engine/game/g_18_ka/meta.rb
new file mode 100644
index 0000000000..06b8f1df0d
--- /dev/null
+++ b/lib/engine/game/g_18_ka/meta.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require_relative '../meta'
+
+module Engine
+ module Game
+ module G18KA
+ module Meta
+ include Game::Meta
+
+ DEV_STAGE = :prealpha
+ PROTOTYPE = true
+ DEPENDS_ON = '1856'
+
+ GAME_DESIGNER = 'Matthew Gilzinger'
+ GAME_INFO_URL = ''
+ GAME_LOCATION = 'Tharsis, Mars'
+ GAME_RULES_URL = ''
+
+ PLAYER_RANGE = [3, 7].freeze
+ end
+ end
+ end
+end
diff --git a/public/icons/18_ka/mine.svg b/public/icons/18_ka/mine.svg
new file mode 100644
index 0000000000..1f247be8b5
--- /dev/null
+++ b/public/icons/18_ka/mine.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/icons/18_ka/plus_20.svg b/public/icons/18_ka/plus_20.svg
new file mode 100644
index 0000000000..629f245794
--- /dev/null
+++ b/public/icons/18_ka/plus_20.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/18_ka/AF.alt.svg b/public/logos/18_ka/AF.alt.svg
new file mode 100644
index 0000000000..b123b2e0ba
--- /dev/null
+++ b/public/logos/18_ka/AF.alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/18_ka/AF.svg b/public/logos/18_ka/AF.svg
new file mode 100644
index 0000000000..36bb94b682
--- /dev/null
+++ b/public/logos/18_ka/AF.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/public/logos/18_ka/ATN.alt.svg b/public/logos/18_ka/ATN.alt.svg
new file mode 100644
index 0000000000..3349b34052
--- /dev/null
+++ b/public/logos/18_ka/ATN.alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/18_ka/ATN.svg b/public/logos/18_ka/ATN.svg
new file mode 100644
index 0000000000..1721423b19
--- /dev/null
+++ b/public/logos/18_ka/ATN.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/public/logos/18_ka/C9S.alt.svg b/public/logos/18_ka/C9S.alt.svg
new file mode 100644
index 0000000000..317817c6d2
--- /dev/null
+++ b/public/logos/18_ka/C9S.alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/18_ka/C9S.svg b/public/logos/18_ka/C9S.svg
new file mode 100644
index 0000000000..aad1c2d183
--- /dev/null
+++ b/public/logos/18_ka/C9S.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/public/logos/18_ka/CMM.alt.svg b/public/logos/18_ka/CMM.alt.svg
new file mode 100644
index 0000000000..d40407d8d0
--- /dev/null
+++ b/public/logos/18_ka/CMM.alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/18_ka/CMM.svg b/public/logos/18_ka/CMM.svg
new file mode 100644
index 0000000000..91a15105ca
--- /dev/null
+++ b/public/logos/18_ka/CMM.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/public/logos/18_ka/ICTN.alt.svg b/public/logos/18_ka/ICTN.alt.svg
new file mode 100644
index 0000000000..ebbb72da03
--- /dev/null
+++ b/public/logos/18_ka/ICTN.alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/18_ka/ICTN.svg b/public/logos/18_ka/ICTN.svg
new file mode 100644
index 0000000000..a61a3f73dc
--- /dev/null
+++ b/public/logos/18_ka/ICTN.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/public/logos/18_ka/IPM.alt.svg b/public/logos/18_ka/IPM.alt.svg
new file mode 100644
index 0000000000..1681dd7bda
--- /dev/null
+++ b/public/logos/18_ka/IPM.alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/18_ka/IPM.svg b/public/logos/18_ka/IPM.svg
new file mode 100644
index 0000000000..2fa5e07d00
--- /dev/null
+++ b/public/logos/18_ka/IPM.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/public/logos/18_ka/JA.alt.svg b/public/logos/18_ka/JA.alt.svg
new file mode 100644
index 0000000000..467fbe7be4
--- /dev/null
+++ b/public/logos/18_ka/JA.alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/18_ka/JA.svg b/public/logos/18_ka/JA.svg
new file mode 100644
index 0000000000..da772a92db
--- /dev/null
+++ b/public/logos/18_ka/JA.svg
@@ -0,0 +1,20 @@
+
+
diff --git a/public/logos/18_ka/KM.alt.svg b/public/logos/18_ka/KM.alt.svg
new file mode 100644
index 0000000000..e8e30bf0ca
--- /dev/null
+++ b/public/logos/18_ka/KM.alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/18_ka/KM.svg b/public/logos/18_ka/KM.svg
new file mode 100644
index 0000000000..41aa244000
--- /dev/null
+++ b/public/logos/18_ka/KM.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/public/logos/18_ka/MGM.alt.svg b/public/logos/18_ka/MGM.alt.svg
new file mode 100644
index 0000000000..d21e4ce4f3
--- /dev/null
+++ b/public/logos/18_ka/MGM.alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/18_ka/MGM.svg b/public/logos/18_ka/MGM.svg
new file mode 100644
index 0000000000..f7210ead64
--- /dev/null
+++ b/public/logos/18_ka/MGM.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/public/logos/18_ka/MWM.alt.svg b/public/logos/18_ka/MWM.alt.svg
new file mode 100644
index 0000000000..c5ed565f33
--- /dev/null
+++ b/public/logos/18_ka/MWM.alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/18_ka/MWM.svg b/public/logos/18_ka/MWM.svg
new file mode 100644
index 0000000000..298853c37c
--- /dev/null
+++ b/public/logos/18_ka/MWM.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/public/logos/18_ka/NM.alt.svg b/public/logos/18_ka/NM.alt.svg
new file mode 100644
index 0000000000..58318e0ac2
--- /dev/null
+++ b/public/logos/18_ka/NM.alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/18_ka/NM.svg b/public/logos/18_ka/NM.svg
new file mode 100644
index 0000000000..a89ae15514
--- /dev/null
+++ b/public/logos/18_ka/NM.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/public/logos/18_ka/OL.alt.svg b/public/logos/18_ka/OL.alt.svg
new file mode 100644
index 0000000000..ec5840729e
--- /dev/null
+++ b/public/logos/18_ka/OL.alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/18_ka/OL.svg b/public/logos/18_ka/OL.svg
new file mode 100644
index 0000000000..f370039b47
--- /dev/null
+++ b/public/logos/18_ka/OL.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/public/logos/18_ka/PMC.alt.svg b/public/logos/18_ka/PMC.alt.svg
new file mode 100644
index 0000000000..bef66b8633
--- /dev/null
+++ b/public/logos/18_ka/PMC.alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/18_ka/PMC.svg b/public/logos/18_ka/PMC.svg
new file mode 100644
index 0000000000..78dd13b9ce
--- /dev/null
+++ b/public/logos/18_ka/PMC.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/public/logos/18_ka/TL.alt.svg b/public/logos/18_ka/TL.alt.svg
new file mode 100644
index 0000000000..a43b8e297b
--- /dev/null
+++ b/public/logos/18_ka/TL.alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/18_ka/TL.svg b/public/logos/18_ka/TL.svg
new file mode 100644
index 0000000000..5502404e12
--- /dev/null
+++ b/public/logos/18_ka/TL.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/public/logos/18_ka/TiTaN.alt.svg b/public/logos/18_ka/TiTaN.alt.svg
new file mode 100644
index 0000000000..f73b3c463b
--- /dev/null
+++ b/public/logos/18_ka/TiTaN.alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/18_ka/TiTaN.svg b/public/logos/18_ka/TiTaN.svg
new file mode 100644
index 0000000000..3966461548
--- /dev/null
+++ b/public/logos/18_ka/TiTaN.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/public/logos/18_ka/WM.alt.svg b/public/logos/18_ka/WM.alt.svg
new file mode 100644
index 0000000000..9bfeefa91c
--- /dev/null
+++ b/public/logos/18_ka/WM.alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/logos/18_ka/WM.svg b/public/logos/18_ka/WM.svg
new file mode 100644
index 0000000000..dd61c87401
--- /dev/null
+++ b/public/logos/18_ka/WM.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/public/logos/18_ka/mine.svg b/public/logos/18_ka/mine.svg
new file mode 100644
index 0000000000..f517f22535
--- /dev/null
+++ b/public/logos/18_ka/mine.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/public/logos/18_ka/plus_twenty.alt.svg b/public/logos/18_ka/plus_twenty.alt.svg
new file mode 100644
index 0000000000..c474b42bd2
--- /dev/null
+++ b/public/logos/18_ka/plus_twenty.alt.svg
@@ -0,0 +1,4 @@
+
+
++20
+
diff --git a/public/logos/18_ka/port.svg b/public/logos/18_ka/port.svg
new file mode 100644
index 0000000000..1b47a55109
--- /dev/null
+++ b/public/logos/18_ka/port.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/public/logos/18_ka/template.svg b/public/logos/18_ka/template.svg
new file mode 100644
index 0000000000..67cbe1cd4e
--- /dev/null
+++ b/public/logos/18_ka/template.svg
@@ -0,0 +1,3 @@
+
+
+