From 388f7ed20139f4638dc42122d4b693828b444ee4 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Sun, 2 Oct 2022 22:55:24 +0200 Subject: Optimize representation. --- data.js | 757 ++++++++++++++++++++++++++++++++++----------------------------- play.css | 9 + play.js | 705 ++++++++++++++++++++++++++++++---------------------------- rules.js | 448 +++++++++++++++++++++---------------- 4 files changed, 1043 insertions(+), 876 deletions(-) diff --git a/data.js b/data.js index 01229e9..10e0480 100644 --- a/data.js +++ b/data.js @@ -1,52 +1,4 @@ -"use strict"; - -let AREAS = { - "Ireland":{"x":120,"y":475}, - "Isle of Man":{"x":360,"y":525}, - "Scotland":{"x":635,"y":180}, - "Northumbria":{"x":885,"y":280}, - "Cumbria":{"x":680,"y":405}, - "North Yorks":{"x":890,"y":535}, - "East Yorks":{"x":1120,"y":545}, - "South Yorks":{"x":985,"y":710}, - "Lancaster":{"x":750,"y":690}, - "Caernarvon":{"x":480,"y":890}, - "Chester":{"x":720,"y":900}, - "Derby":{"x":960,"y":880}, - "Lincoln":{"x":1210,"y":820}, - "Pembroke":{"x":340,"y":1220}, - "Powys":{"x":565,"y":1100}, - "Hereford":{"x":715,"y":1125}, - "Warwick":{"x":890,"y":1090}, - "Leicester":{"x":1080,"y":1055}, - "Rutland":{"x":1265,"y":1060}, - "East Anglia":{"x":1505,"y":1040}, - "Glamorgan":{"x":570,"y":1330}, - "Gloucester":{"x":840,"y":1300}, - "Oxford":{"x":1035,"y":1290}, - "Middlesex":{"x":1235,"y":1305}, - "Essex":{"x":1440,"y":1255}, - "Somerset":{"x":750,"y":1510}, - "Wilts":{"x":920,"y":1460}, - "Sussex":{"x":1140,"y":1550}, - "Kent":{"x":1415,"y":1490}, - "Cornwall":{"x":400,"y":1660}, - "Dorset":{"x":810,"y":1640}, - "France":{"x":225,"y":160}, - "Calais":{"x":1465,"y":1795}, - "Irish Sea":{"x":280,"y":685}, - "North Sea":{"x":1425,"y":460}, - "English Channel":{"x":915,"y":1820}, - "Pool":{x:0,y:0}, - "Minor":{x:0,y:0}, - "LPool":{x:1688-50,y:50}, - "LMinor":{x:1688-50-210,y:50}, - "YPool":{x:50,y:1950-50}, - "YMinor":{x:50+210,y:1950-50}, -} - -let BORDERS = {}; -let BLOCKS = {}; +"use strict" const CARDS = { 1: { name: "Force March", event: "force_march", actions: 1, image: "card_force_march" }, @@ -74,337 +26,444 @@ const CARDS = { 23: { name: "a 2", actions: 2, image: "card_2" }, 24: { name: "a 2", actions: 2, image: "card_2" }, 25: { name: "a 2", actions: 2, image: "card_2" }, -}; - -(function () { - for (let a in AREAS) { - AREAS[a].exits = []; - AREAS[a].shields = []; - AREAS[a].wrap = 3; - AREAS[a].layout_axis = 'X'; - AREAS[a].layout_major = 0.5; - AREAS[a].layout_minor = 0.5; +} + +let BORDERS = {} + +let BLOCKS = [] +let block_index = {} + +let AREAS = [] +let area_index = {} + +function init_areas() { + let AREA_XY = { + "Ireland":{"x":120,"y":475}, + "Isle of Man":{"x":360,"y":525}, + "Scotland":{"x":635,"y":180}, + "Northumbria":{"x":885,"y":280}, + "Cumbria":{"x":680,"y":405}, + "North Yorks":{"x":890,"y":535}, + "East Yorks":{"x":1120,"y":545}, + "South Yorks":{"x":985,"y":710}, + "Lancaster":{"x":750,"y":690}, + "Caernarvon":{"x":480,"y":890}, + "Chester":{"x":720,"y":900}, + "Derby":{"x":960,"y":880}, + "Lincoln":{"x":1210,"y":820}, + "Pembroke":{"x":340,"y":1220}, + "Powys":{"x":565,"y":1100}, + "Hereford":{"x":715,"y":1125}, + "Warwick":{"x":890,"y":1090}, + "Leicester":{"x":1080,"y":1055}, + "Rutland":{"x":1265,"y":1060}, + "East Anglia":{"x":1505,"y":1040}, + "Glamorgan":{"x":570,"y":1330}, + "Gloucester":{"x":840,"y":1300}, + "Oxford":{"x":1035,"y":1290}, + "Middlesex":{"x":1235,"y":1305}, + "Essex":{"x":1440,"y":1255}, + "Somerset":{"x":750,"y":1510}, + "Wilts":{"x":920,"y":1460}, + "Sussex":{"x":1140,"y":1550}, + "Kent":{"x":1415,"y":1490}, + "Cornwall":{"x":400,"y":1660}, + "Dorset":{"x":810,"y":1640}, + "France":{"x":225,"y":160}, + "Calais":{"x":1465,"y":1795}, + "Irish Sea":{"x":280,"y":685}, + "North Sea":{"x":1425,"y":460}, + "English Channel":{"x":915,"y":1820}, + + "Dead":{x:0,y:0}, + "Pool":{x:0,y:0}, + "Minor":{x:0,y:0}, + "LPool":{x:1688-50,y:50}, + "LMinor":{x:1688-50-210,y:50}, + "YPool":{x:50,y:1950-50}, + "YMinor":{x:50+210,y:1950-50}, } - function border(a, b, type) { - if (a > b) [a, b] = [b, a]; - let id = a + "/" + b; - BORDERS[id] = type; - AREAS[a].exits.push(b); - AREAS[b].exits.push(a); + let AREA_CITY = { + "Somerset": "Bristol", + "Warwick": "Coventry", + "Middlesex": "London", + "Northumbria": "Newcastle", + "East Anglia": "Norwich", + "Wilts": "Salisbury", + "South Yorks": "York", } - function yellow(A,B) { border(A,B,"major"); } - function blue(A,B) { border(A,B,"river"); } - function red(A,B) { border(A,B,"minor"); } - function sea(A,B,major) { border(A,B,"sea"); if (major) AREAS[B].major_port = true; } + let AREA_CATHEDRAL = { + "Kent": "Canterbury", + "South Yorks": "York", + } + + let AREA_CROWN = [ + "Cumbria", + "South Yorks", + "Caernarvon", + "Chester", + "Derby", + "Pembroke", + "Warwick", + "Gloucester", + "Middlesex", + "Sussex", + "Cornwall", + ] function layout(a, wrap, axis, major, minor) { - AREAS[a].wrap = wrap; - AREAS[a].layout_axis = axis; - AREAS[a].layout_major = (1 - major) / 2; - AREAS[a].layout_minor = (1 - minor) / 2; + area_index[a] = AREAS.length + AREAS.push({ + name: a, + city: AREA_CITY[a] || null, + cathedral: AREA_CATHEDRAL[a] || null, + crown: AREA_CROWN.includes(a), + major_port: false, + shields: [], + exits: [], + layout: { + x: AREA_XY[a].x, + y: AREA_XY[a].y, + wrap, + axis, + major: (1 - major) / 2, + minor: (1 - minor) / 2, + }, + }) } - layout("LPool", 13, 'Y', 1, -1); - layout("LMinor", 5, 'X', -1, 0); - layout("YPool", 13, 'Y', -1, 1); - layout("YMinor", 5, 'X', 1, 0); - - layout("France", 4, 'X', 0, 0); - layout("Calais", 4, 'X', 0, 0); - - layout("Ireland", 3, 'Y', -1, -1); - layout("Scotland", 3, 'X', -1, -1); - layout("Northumbria", 4, 'Y', 0, 0); - layout("Rutland", 4, 'Y', 0, 0); - layout("Leicester", 4, 'Y', 0, 0); - - layout("North Sea", 10, 'Y', 1, 0); - layout("Irish Sea", 10, 'Y', 1, 0); - layout("English Channel", 10, 'X', 0, 0); - - layout("Cornwall", 4, 'X', 0, 0); - layout("Dorset", 4, 'X', 0, 0); - layout("Sussex", 4, 'X', 0, 0); - layout("Kent", 4, 'X', 0, 0); - layout("Somerset", 4, 'X', -1, -1); - - layout("East Anglia", 4, 'X', 0, 0); - layout("Powys", 4, 'Y', 0, 0); - layout("Hereford", 4, 'Y', 0, 0); - layout("Oxford", 3, 'Y', 0, 0); - - layout("Derby", 4, 'X', 0, 0); - layout("Caernarvon", 4, 'X', 0, 0); - layout("Essex", 3, 'X', 0, 0); - layout("Cumbria", 4, 'X', 0, 0); - layout("Glamorgan", 4, 'X', 0, 0); - layout("Pembroke", 4, 'X', 0, -1); - - red("Scotland", "Cumbria"); - red("Scotland", "Northumbria"); - red("Cumbria", "Northumbria"); - red("Cumbria", "North Yorks"); - red("Cumbria", "Lancaster"); - blue("Northumbria", "North Yorks"); - blue("Northumbria", "East Yorks"); - - yellow("North Yorks", "East Yorks"); - yellow("North Yorks", "South Yorks"); - red("North Yorks", "Lancaster"); - blue("East Yorks", "South Yorks"); - red("Lancaster", "South Yorks"); - blue("Lancaster", "Chester"); - red("Lancaster", "Derby"); - yellow("South Yorks", "Derby"); - blue("South Yorks", "Lincoln"); - - blue("Caernarvon", "Chester"); - red("Caernarvon", "Powys"); - red("Caernarvon", "Pembroke"); - yellow("Chester", "Powys"); - yellow("Chester", "Derby"); - blue("Chester", "Hereford"); - yellow("Chester", "Warwick"); - blue("Derby", "Warwick"); - blue("Derby", "Leicester"); - blue("Derby", "Lincoln"); - yellow("Lincoln", "Leicester"); - blue("Lincoln", "Rutland"); - - red("Pembroke", "Powys"); - yellow("Pembroke", "Glamorgan"); - red("Powys", "Hereford"); - blue("Powys", "Glamorgan"); - blue("Hereford", "Warwick"); - blue("Hereford", "Gloucester"); - blue("Hereford", "Glamorgan"); - yellow("Warwick", "Leicester"); - blue("Warwick", "Oxford"); - blue("Warwick", "Gloucester"); - yellow("Leicester", "Rutland"); - blue("Leicester", "Essex"); - yellow("Leicester", "Middlesex"); - yellow("Leicester", "Oxford"); - blue("Rutland", "East Anglia"); - blue("Rutland", "Essex"); - yellow("East Anglia", "Essex"); - - yellow("Gloucester", "Oxford"); - blue("Gloucester", "Wilts"); - yellow("Gloucester", "Somerset"); - yellow("Oxford", "Middlesex"); - blue("Oxford", "Wilts"); - blue("Oxford", "Sussex"); - blue("Middlesex", "Sussex"); - blue("Middlesex", "Kent"); - yellow("Middlesex", "Essex"); - - yellow("Cornwall", "Somerset"); - yellow("Cornwall", "Dorset"); - yellow("Somerset", "Wilts"); - yellow("Somerset", "Dorset"); - yellow("Wilts", "Dorset"); - yellow("Wilts", "Sussex"); - blue("Sussex", "Dorset"); - yellow("Sussex", "Kent"); - - sea("Irish Sea", "Ireland", true); - sea("Irish Sea", "Isle of Man"); - sea("Irish Sea", "Scotland", true); - sea("Irish Sea", "Cumbria"); - sea("Irish Sea", "Lancaster"); - sea("Irish Sea", "Chester", true); - sea("Irish Sea", "Caernarvon"); - sea("Irish Sea", "Pembroke"); - sea("Irish Sea", "Glamorgan", true); - sea("Irish Sea", "Somerset", true); - sea("Irish Sea", "Cornwall", true); - - sea("North Sea", "Scotland", true); - sea("North Sea", "Northumbria", true); - sea("North Sea", "East Yorks", true); - sea("North Sea", "Lincoln"); - sea("North Sea", "Rutland"); - sea("North Sea", "East Anglia", true); - sea("North Sea", "Essex"); - sea("North Sea", "Middlesex", true); - sea("North Sea", "Kent", true); - - sea("English Channel", "Cornwall", true); - sea("English Channel", "Dorset"); - sea("English Channel", "Sussex", true); - sea("English Channel", "Kent", true); - - sea("English Channel", "Calais", true); - sea("North Sea", "Calais", true); - - sea("English Channel", "France", true); - sea("Irish Sea", "France", true); - - AREAS["Somerset"].city = "Bristol"; - AREAS["Warwick"].city = "Coventry"; - AREAS["Middlesex"].city = "London"; - AREAS["Northumbria"].city = "Newcastle"; - AREAS["East Anglia"].city = "Norwich"; - AREAS["Wilts"].city = "Salisbury"; - AREAS["South Yorks"].city = "York"; - - AREAS["Kent"].cathedral = "Canterbury"; - AREAS["South Yorks"].cathedral = "York"; - - AREAS["Cumbria"].crown = true; - AREAS["South Yorks"].crown = true; - AREAS["Caernarvon"].crown = true; - AREAS["Chester"].crown = true; - AREAS["Derby"].crown = true; - AREAS["Pembroke"].crown = true; - AREAS["Warwick"].crown = true; - AREAS["Gloucester"].crown = true; - AREAS["Middlesex"].crown = true; - AREAS["Sussex"].crown = true; - AREAS["Cornwall"].crown = true; + // pools + layout("Dead", 3, 'X', 0, 0) + layout("Pool", 3, 'X', 0, 0) + layout("Minor", 3, 'X', 0, 0) + + // seas + layout("English Channel", 10, 'X', 0, 0) + layout("Irish Sea", 10, 'Y', 1, 0) + layout("North Sea", 10, 'Y', 1, 0) + + // exile areas + layout("Calais", 4, 'X', 0, 0) + layout("France", 4, 'X', 0, 0) + layout("Ireland", 3, 'Y', -1, -1) + layout("Scotland", 3, 'X', -1, -1) + + layout("Caernarvon", 4, 'X', 0, 0) + layout("Chester", 3, 'X', 0, 0) + layout("Cornwall", 4, 'X', 0, 0) + layout("Cumbria", 4, 'X', 0, 0) + layout("Derby", 4, 'X', 0, 0) + layout("Dorset", 4, 'X', 0, 0) + layout("East Anglia", 4, 'X', 0, 0) + layout("East Yorks", 3, 'X', 0, 0) + layout("Essex", 3, 'X', 0, 0) + layout("Glamorgan", 4, 'X', 0, 0) + layout("Gloucester", 3, 'X', 0, 0) + layout("Hereford", 4, 'Y', 0, 0) + layout("Isle of Man", 3, 'X', 0, 0) + layout("Kent", 4, 'X', 0, 0) + layout("Lancaster", 3, 'X', 0, 0) + layout("Leicester", 4, 'Y', 0, 0) + layout("Lincoln", 3, 'X', 0, 0) + layout("Middlesex", 3, 'X', 0, 0) + layout("North Yorks", 3, 'X', 0, 0) + layout("Northumbria", 4, 'Y', 0, 0) + layout("Oxford", 3, 'Y', 0, 0) + layout("Pembroke", 4, 'X', 0, -1) + layout("Powys", 4, 'Y', 0, 0) + layout("Rutland", 4, 'Y', 0, 0) + layout("Somerset", 4, 'X', -1, -1) + layout("South Yorks", 3, 'X', 0, 0) + layout("Sussex", 4, 'X', 0, 0) + layout("Warwick", 3, 'X', 0, 0) + layout("Wilts", 3, 'X', 0, 0) + + // only used for layout in ui + layout("LPool", 13, 'Y', 1, -1) + layout("LMinor", 5, 'X', -1, 0) + layout("YPool", 13, 'Y', -1, 1) + layout("YMinor", 5, 'X', 1, 0) +} + +function init_borders() { + function border(a, b, type) { + a = area_index[a] + b = area_index[b] + let id = (a < b) ? a * 100 + b : b * 100 + a + BORDERS[id] = type + AREAS[a].exits.push(b) + AREAS[b].exits.push(a) + } + function yellow(A,B) { border(A,B,"major"); } + function blue(A,B) { border(A,B,"river"); } + function red(A,B) { border(A,B,"minor"); } + function sea(A,B,major) { border(A,B,"sea"); if (major) AREAS[area_index[B]].major_port = true; } + + red("Scotland", "Cumbria") + red("Scotland", "Northumbria") + red("Cumbria", "Northumbria") + red("Cumbria", "North Yorks") + red("Cumbria", "Lancaster") + blue("Northumbria", "North Yorks") + blue("Northumbria", "East Yorks") + + yellow("North Yorks", "East Yorks") + yellow("North Yorks", "South Yorks") + red("North Yorks", "Lancaster") + blue("East Yorks", "South Yorks") + red("Lancaster", "South Yorks") + blue("Lancaster", "Chester") + red("Lancaster", "Derby") + yellow("South Yorks", "Derby") + blue("South Yorks", "Lincoln") + + blue("Caernarvon", "Chester") + red("Caernarvon", "Powys") + red("Caernarvon", "Pembroke") + yellow("Chester", "Powys") + yellow("Chester", "Derby") + blue("Chester", "Hereford") + yellow("Chester", "Warwick") + blue("Derby", "Warwick") + blue("Derby", "Leicester") + blue("Derby", "Lincoln") + yellow("Lincoln", "Leicester") + blue("Lincoln", "Rutland") + + red("Pembroke", "Powys") + yellow("Pembroke", "Glamorgan") + red("Powys", "Hereford") + blue("Powys", "Glamorgan") + blue("Hereford", "Warwick") + blue("Hereford", "Gloucester") + blue("Hereford", "Glamorgan") + yellow("Warwick", "Leicester") + blue("Warwick", "Oxford") + blue("Warwick", "Gloucester") + yellow("Leicester", "Rutland") + blue("Leicester", "Essex") + yellow("Leicester", "Middlesex") + yellow("Leicester", "Oxford") + blue("Rutland", "East Anglia") + blue("Rutland", "Essex") + yellow("East Anglia", "Essex") + + yellow("Gloucester", "Oxford") + blue("Gloucester", "Wilts") + yellow("Gloucester", "Somerset") + yellow("Oxford", "Middlesex") + blue("Oxford", "Wilts") + blue("Oxford", "Sussex") + blue("Middlesex", "Sussex") + blue("Middlesex", "Kent") + yellow("Middlesex", "Essex") + + yellow("Cornwall", "Somerset") + yellow("Cornwall", "Dorset") + yellow("Somerset", "Wilts") + yellow("Somerset", "Dorset") + yellow("Wilts", "Dorset") + yellow("Wilts", "Sussex") + blue("Sussex", "Dorset") + yellow("Sussex", "Kent") + + sea("Irish Sea", "Ireland", true) + sea("Irish Sea", "Isle of Man") + sea("Irish Sea", "Scotland", true) + sea("Irish Sea", "Cumbria") + sea("Irish Sea", "Lancaster") + sea("Irish Sea", "Chester", true) + sea("Irish Sea", "Caernarvon") + sea("Irish Sea", "Pembroke") + sea("Irish Sea", "Glamorgan", true) + sea("Irish Sea", "Somerset", true) + sea("Irish Sea", "Cornwall", true) + + sea("North Sea", "Scotland", true) + sea("North Sea", "Northumbria", true) + sea("North Sea", "East Yorks", true) + sea("North Sea", "Lincoln") + sea("North Sea", "Rutland") + sea("North Sea", "East Anglia", true) + sea("North Sea", "Essex") + sea("North Sea", "Middlesex", true) + sea("North Sea", "Kent", true) + + sea("English Channel", "Cornwall", true) + sea("English Channel", "Dorset") + sea("English Channel", "Sussex", true) + sea("English Channel", "Kent", true) + + sea("English Channel", "Calais", true) + sea("North Sea", "Calais", true) + + sea("English Channel", "France", true) + sea("Irish Sea", "France", true) + + for (let a of AREAS) + a.exits.sort((a,b)=>a-b) +} + +function init_blocks() { function block(image, owner, type, name, steps, combat, loyalty, extra, extra2) { - let id = name; - let enemy = null; + let id = name + let enemy = null if (name === "Bombard") - id = name + "/" + owner[0]; + id = name + "/" + owner[0] if (loyalty) { - id = name + "/" + owner[0]; + id = name + "/" + owner[0] if (owner === "York") - enemy = name + "/L"; + enemy = name + "/L" else - enemy = name + "/Y"; + enemy = name + "/Y" } - if (id in BLOCKS) - throw new Error("Duplicate block: " + id); - BLOCKS[id] = { - type: type, - owner: owner, + if (id in block_index) + throw new Error("Duplicate block: " + id) + let i = block_index[id] = BLOCKS.length + BLOCKS.push({ + id: id, name: name, + owner: owner, + type: type, shield: name, steps: steps, combat: combat, + initiative: combat[0], + fire_power: combat[1] | 0, + loyalty: loyalty || 0, + enemy: enemy || -1, + heir: 0, + home: null, image: image, - }; - if (loyalty) - BLOCKS[id].loyalty = loyalty; - if (enemy) - BLOCKS[id].enemy = enemy; + }) if (extra) { if (type === 'heir') { - BLOCKS[id].heir = extra; - BLOCKS[id].shield = extra2; + BLOCKS[i].heir = extra + if (extra2) + BLOCKS[i].shield = extra2 } if (type === 'church' || type === 'levies') - BLOCKS[id].home = extra; + BLOCKS[i].home = extra if (type === 'nobles') - BLOCKS[id].shield = extra; + BLOCKS[i].shield = extra } + + // console.log(`const B_${id.toUpperCase().replace(/[ \/]/g, "_").replace(/[()]/g, "")} = block_index["${id}"]`) } - block(11, "York", "heir", "York", 4, "B3", 0, 1); - block(12, "York", "heir", "March", 3, "A3", 0, 2); - block(13, "York", "heir", "Rutland", 3, "B1", 0, 3); - block(14, "York", "heir", "Clarence", 3, "B2", 1, 4); - block(15, "York", "heir", "Gloucester", 3, "B3", 0, 5); - - block(16, "York", "nobles", "Essex", 3, "B1", 0); - block(17, "York", "nobles", "Hastings", 3, "B2", 0); - block(21, "York", "nobles", "Herbert", 3, "A2", 0); - block(22, "York", "nobles", "Worcester", 2, "B2", 0); - block(23, "York", "nobles", "Suffolk", 3, "B2", 0); - block(24, "York", "nobles", "Norfolk", 4, "B2", 0); - block(25, "York", "nobles", "Buckingham", 4, "B2", 1); - block(26, "York", "nobles", "Exeter", 3, "A1", 1); - block(27, "York", "nobles", "Rivers", 2, "B2", 2); - block(31, "York", "nobles", "Northumberland", 4, "B3", 1); - block(32, "York", "nobles", "Shrewsbury", 3, "A1", 1); - block(33, "York", "nobles", "Stanley", 4, "B2", 1); - block(34, "York", "nobles", "Arundel", 3, "B2", 0); - block(35, "York", "nobles", "Warwick", 4, "B3", 3); - block(36, "York", "nobles", "Kent", 3, "A2", 2); - block(37, "York", "nobles", "Salisbury", 3, "B2", 2); - block(41, "York", "nobles", "Westmoreland", 3, "B2", 2); - - block(51, "York", "mercenaries", "Irish Mercenary", 4, "B2", 0); - block(52, "York", "mercenaries", "Burgundian Mercenary", 3, "A3", 0); - block(53, "York", "mercenaries", "Calais Mercenary", 3, "B4", 0); - block(46, "York", "church", "Canterbury (church)", 3, "C1", 2, "Canterbury"); - block(47, "York", "church", "York (church)", 3, "C2", 1, "York"); - block(42, "York", "levies", "London (levy)", 4, "C3", 0, "London"); - block(43, "York", "levies", "Norwich (levy)", 4, "C2", 0, "Norwich"); - block(44, "York", "levies", "Salisbury (levy)", 4, "C2", 0, "Salisbury"); - block(45, "York", "bombard", "Bombard", 3, "D3", 0); - - block(91, "Lancaster", "heir", "Henry VI", 4, "B2", 0, 1); - block(92, "Lancaster", "heir", "Prince Edward", 3, "B1", 0, 2); - block(93, "Lancaster", "heir", "Exeter", 3, "A1", 2, 3, "Exeter"); - block(94, "Lancaster", "heir", "Somerset", 3, "A2", 0, 4, "Somerset"); - block(95, "Lancaster", "heir", "Richmond", 3, "B2", 0, 5, "Richmond"); - - block(67, "Lancaster", "nobles", "Westmoreland", 3, "B2", 2); - block(71, "Lancaster", "nobles", "Northumberland", 4, "B3", 2); - block(72, "Lancaster", "nobles", "Shrewsbury", 3, "A1", 2); - block(73, "Lancaster", "nobles", "Stanley", 4, "B2", 1); - block(75, "Lancaster", "nobles", "Warwick", 4, "B3", 3); - block(76, "Lancaster", "nobles", "Kent", 3, "A2", 2); - block(77, "Lancaster", "nobles", "Salisbury", 3, "B2", 2); - block(81, "Lancaster", "nobles", "Oxford", 3, "A2", 0); - block(82, "Lancaster", "nobles", "Pembroke", 3, "B2", 0); - block(83, "Lancaster", "nobles", "Devon", 2, "B2", 0); - block(84, "Lancaster", "nobles", "Beaumont", 3, "B2", 0); - block(85, "Lancaster", "nobles", "Buckingham", 4, "B2", 1); - block(86, "Lancaster", "nobles", "Clarence", 3, "B2", 1); - block(87, "Lancaster", "nobles", "Rivers", 2, "B2", 1); - block(96, "Lancaster", "nobles", "Clifford", 3, "A2", 0); - block(97, "Lancaster", "nobles", "Wiltshire", 3, "B2", 0); - - block(55, "Lancaster", "mercenaries", "Scots Mercenary", 4, "B3"); - block(56, "Lancaster", "mercenaries", "Welsh Mercenary", 3, "A2"); - block(57, "Lancaster", "mercenaries", "French Mercenary", 4, "B3"); - block(61, "Lancaster", "church", "Canterbury (church)", 3, "C1", 1, "Canterbury"); - block(62, "Lancaster", "church", "York (church)", 3, "C2", 2, "York"); - block(63, "Lancaster", "bombard", "Bombard", 3, "D3"); - block(64, "Lancaster", "levies", "Coventry (levy)", 4, "C2", 0, "Coventry"); - block(65, "Lancaster", "levies", "Bristol (levy)", 4, "C2", 0, "Bristol"); - block(66, "Lancaster", "levies", "Newcastle (levy)", 4, "C3", 0, "Newcastle"); - block(74, "Lancaster", "levies", "York (levy)", 4, "C3", 0, "York"); - - block(54, "Rebel", "rebel", "Rebel", 4, "A2"); + block(11, "York", "heir", "York", 4, "B3", 0, 1) + block(12, "York", "heir", "March", 3, "A3", 0, 2) + block(13, "York", "heir", "Rutland", 3, "B1", 0, 3) + block(14, "York", "heir", "Clarence", 3, "B2", 1, 4) + block(15, "York", "heir", "Gloucester", 3, "B3", 0, 5) + + block(16, "York", "nobles", "Essex", 3, "B1", 0) + block(17, "York", "nobles", "Hastings", 3, "B2", 0) + block(21, "York", "nobles", "Herbert", 3, "A2", 0) + block(22, "York", "nobles", "Worcester", 2, "B2", 0) + block(23, "York", "nobles", "Suffolk", 3, "B2", 0) + block(24, "York", "nobles", "Norfolk", 4, "B2", 0) + block(25, "York", "nobles", "Buckingham", 4, "B2", 1) + block(26, "York", "nobles", "Exeter", 3, "A1", 1) + block(27, "York", "nobles", "Rivers", 2, "B2", 2) + block(31, "York", "nobles", "Northumberland", 4, "B3", 1) + block(32, "York", "nobles", "Shrewsbury", 3, "A1", 1) + block(33, "York", "nobles", "Stanley", 4, "B2", 1) + block(34, "York", "nobles", "Arundel", 3, "B2", 0) + block(35, "York", "nobles", "Warwick", 4, "B3", 3) + block(36, "York", "nobles", "Kent", 3, "A2", 2) + block(37, "York", "nobles", "Salisbury", 3, "B2", 2) + block(41, "York", "nobles", "Westmoreland", 3, "B2", 2) + + block(51, "York", "mercenaries", "Irish Mercenary", 4, "B2", 0) + block(52, "York", "mercenaries", "Burgundian Mercenary", 3, "A3", 0) + block(53, "York", "mercenaries", "Calais Mercenary", 3, "B4", 0) + block(46, "York", "church", "Canterbury (church)", 3, "C1", 2, "Canterbury") + block(47, "York", "church", "York (church)", 3, "C2", 1, "York") + block(42, "York", "levies", "London (levy)", 4, "C3", 0, "London") + block(43, "York", "levies", "Norwich (levy)", 4, "C2", 0, "Norwich") + block(44, "York", "levies", "Salisbury (levy)", 4, "C2", 0, "Salisbury") + block(45, "York", "bombard", "Bombard", 3, "D3", 0) + + block(91, "Lancaster", "heir", "Henry VI", 4, "B2", 0, 1) + block(92, "Lancaster", "heir", "Prince Edward", 3, "B1", 0, 2) + block(93, "Lancaster", "heir", "Exeter", 3, "A1", 2, 3, "Exeter") + block(94, "Lancaster", "heir", "Somerset", 3, "A2", 0, 4, "Somerset") + block(95, "Lancaster", "heir", "Richmond", 3, "B2", 0, 5, "Richmond") + + block(67, "Lancaster", "nobles", "Westmoreland", 3, "B2", 2) + block(71, "Lancaster", "nobles", "Northumberland", 4, "B3", 2) + block(72, "Lancaster", "nobles", "Shrewsbury", 3, "A1", 2) + block(73, "Lancaster", "nobles", "Stanley", 4, "B2", 1) + block(75, "Lancaster", "nobles", "Warwick", 4, "B3", 3) + block(76, "Lancaster", "nobles", "Kent", 3, "A2", 2) + block(77, "Lancaster", "nobles", "Salisbury", 3, "B2", 2) + block(81, "Lancaster", "nobles", "Oxford", 3, "A2", 0) + block(82, "Lancaster", "nobles", "Pembroke", 3, "B2", 0) + block(83, "Lancaster", "nobles", "Devon", 2, "B2", 0) + block(84, "Lancaster", "nobles", "Beaumont", 3, "B2", 0) + block(85, "Lancaster", "nobles", "Buckingham", 4, "B2", 1) + block(86, "Lancaster", "nobles", "Clarence", 3, "B2", 1) + block(87, "Lancaster", "nobles", "Rivers", 2, "B2", 1) + block(96, "Lancaster", "nobles", "Clifford", 3, "A2", 0) + block(97, "Lancaster", "nobles", "Wiltshire", 3, "B2", 0) + + block(55, "Lancaster", "mercenaries", "Scots Mercenary", 4, "B3") + block(56, "Lancaster", "mercenaries", "Welsh Mercenary", 3, "A2") + block(57, "Lancaster", "mercenaries", "French Mercenary", 4, "B3") + block(61, "Lancaster", "church", "Canterbury (church)", 3, "C1", 1, "Canterbury") + block(62, "Lancaster", "church", "York (church)", 3, "C2", 2, "York") + block(63, "Lancaster", "bombard", "Bombard", 3, "D3") + block(64, "Lancaster", "levies", "Coventry (levy)", 4, "C2", 0, "Coventry") + block(65, "Lancaster", "levies", "Bristol (levy)", 4, "C2", 0, "Bristol") + block(66, "Lancaster", "levies", "Newcastle (levy)", 4, "C3", 0, "Newcastle") + block(74, "Lancaster", "levies", "York (levy)", 4, "C3", 0, "York") + + block(54, "Rebel", "rebel", "Rebel", 4, "A2") + + for (let b of BLOCKS) + if (b.enemy !== -1) + b.enemy = block_index[b.enemy] +} +function init_shields() { function shields(area, list) { - AREAS[area].shields = list; + AREAS[area_index[area]].shields = list } - shields("Isle of Man", ["Stanley"]); - shields("Northumbria", ["Northumberland", "Westmoreland"]); - shields("Cumbria", ["Northumberland", "Clifford"]); - shields("North Yorks", ["Salisbury", "Clifford"]); - shields("East Yorks", ["Kent", "Salisbury", "Northumberland"]); - shields("South Yorks", ["York", "Shrewsbury"]); - shields("Lancaster", ["Lancaster", "Stanley"]); - shields("Caernarvon", ["Norfolk"]); - shields("Lincoln", ["Lancaster", "Beaumont"]); - shields("Pembroke", ["Richmond", "Pembroke"]); - shields("Hereford", ["York"]); - shields("Warwick", ["Buckingham", "Warwick"]); - shields("Leicester", ["Hastings", "Rivers"]); - shields("Rutland", ["York", "Worcester"]); - shields("East Anglia", ["Norfolk", "Suffolk"]); - shields("Glamorgan", ["Buckingham", "Norfolk", "Herbert", "Warwick"]); - shields("Oxford", ["Suffolk"]); - shields("Essex", ["Oxford", "Essex"]); - shields("Wilts", ["Wiltshire"]); - shields("Sussex", ["Arundel"]); - shields("Kent", ["Buckingham"]); - shields("Cornwall", ["Devon", "Exeter"]); - shields("Dorset", ["Somerset"]); - shields("Calais", ["Warwick"]); - -})(); + shields("Isle of Man", ["Stanley"]) + shields("Northumbria", ["Northumberland", "Westmoreland"]) + shields("Cumbria", ["Northumberland", "Clifford"]) + shields("North Yorks", ["Salisbury", "Clifford"]) + shields("East Yorks", ["Kent", "Salisbury", "Northumberland"]) + shields("South Yorks", ["York", "Shrewsbury"]) + shields("Lancaster", ["Lancaster", "Stanley"]) + shields("Caernarvon", ["Norfolk"]) + shields("Lincoln", ["Lancaster", "Beaumont"]) + shields("Pembroke", ["Richmond", "Pembroke"]) + shields("Hereford", ["York"]) + shields("Warwick", ["Buckingham", "Warwick"]) + shields("Leicester", ["Hastings", "Rivers"]) + shields("Rutland", ["York", "Worcester"]) + shields("East Anglia", ["Norfolk", "Suffolk"]) + shields("Glamorgan", ["Buckingham", "Norfolk", "Herbert", "Warwick"]) + shields("Oxford", ["Suffolk"]) + shields("Essex", ["Oxford", "Essex"]) + shields("Wilts", ["Wiltshire"]) + shields("Sussex", ["Arundel"]) + shields("Kent", ["Buckingham"]) + shields("Cornwall", ["Devon", "Exeter"]) + shields("Dorset", ["Somerset"]) + shields("Calais", ["Warwick"]) +} + +init_areas() +init_borders() +init_blocks() +init_shields() if (typeof module !== 'undefined') - module.exports = { CARDS, BLOCKS, AREAS, BORDERS } + module.exports = { CARDS, BLOCKS, AREAS, BORDERS, block_index, area_index } diff --git a/play.css b/play.css index c9251be..5cd3648 100644 --- a/play.css +++ b/play.css @@ -15,6 +15,7 @@ header.your_turn { background-color: orange; } #log .Y { background-color: gainsboro; } #log .h3 { background-color: lightsteelblue; } #log .h4 { font-style: italic; text-decoration: underline; } +#log .tip:hover { text-decoration: underline; cursor: pointer; } .hand { margin: 25px; @@ -123,6 +124,14 @@ header.your_turn { background-color: orange; } #map svg path.area.where.highlight { stroke: white; } +#map svg path.area.tip { + opacity: 1; + stroke-opacity: 1; + stroke: yellow; + stroke-width: 40; + stroke-dasharray: 80 40; + fill: none; +} #map.hide_blocks #blocks { visibility: hidden; diff --git a/play.js b/play.js index ccac1cb..b236266 100644 --- a/play.js +++ b/play.js @@ -1,19 +1,47 @@ -"use strict"; +"use strict" -const LANCASTER = "Lancaster"; -const YORK = "York"; -const REBEL = "Rebel"; +const LANCASTER = "Lancaster" +const YORK = "York" const ENEMY = { York: "Lancaster", Lancaster: "York" } -const POOL = "Pool"; -const DEAD = "Dead"; -const MINOR = "Minor"; - -const NOBODY = -1 const NOWHERE = 0 +const POOL = 1 +const MINOR = 2 -const KING_TEXT = "\u2756"; -const PRETENDER_TEXT = ""; +const L_POOL = area_index["LPool"] +const Y_POOL = area_index["YPool"] +const L_MINOR = area_index["LMinor"] +const Y_MINOR = area_index["YMinor"] + +const NOBODY = -1 +const B_YORK = block_index["York"] +const B_MARCH = block_index["March"] +const B_RUTLAND = block_index["Rutland"] +const B_CLARENCE_Y = block_index["Clarence/Y"] +const B_GLOUCESTER = block_index["Gloucester"] +const B_EXETER_Y = block_index["Exeter/Y"] +const B_WARWICK_Y = block_index["Warwick/Y"] +const B_KENT_Y = block_index["Kent/Y"] +const B_SALISBURY_Y = block_index["Salisbury/Y"] +const B_IRISH_MERCENARY = block_index["Irish Mercenary"] +const B_BURGUNDIAN_MERCENARY = block_index["Burgundian Mercenary"] +const B_CALAIS_MERCENARY = block_index["Calais Mercenary"] +const B_HENRY_VI = block_index["Henry VI"] +const B_PRINCE_EDWARD = block_index["Prince Edward"] +const B_EXETER_L = block_index["Exeter/L"] +const B_SOMERSET = block_index["Somerset"] +const B_RICHMOND = block_index["Richmond"] +const B_WARWICK_L = block_index["Warwick/L"] +const B_KENT_L = block_index["Kent/L"] +const B_SALISBURY_L = block_index["Salisbury/L"] +const B_CLARENCE_L = block_index["Clarence/L"] +const B_SCOTS_MERCENARY = block_index["Scots Mercenary"] +const B_WELSH_MERCENARY = block_index["Welsh Mercenary"] +const B_FRENCH_MERCENARY = block_index["French Mercenary"] +const B_REBEL = block_index["Rebel"] + +const KING_TEXT = "\u2756" +const PRETENDER_TEXT = "" const LONG_NAME = { "Somerset": "Duke of Somerset", @@ -49,7 +77,7 @@ const LONG_NAME = { } function toggle_blocks() { - document.getElementById("map").classList.toggle("hide_blocks"); + document.getElementById("map").classList.toggle("hide_blocks") } let ui = { @@ -62,6 +90,9 @@ let ui = { present: new Set(), } +for (let a of AREAS) + a.nbname = a.name.replace(/ /g, "\xa0") + function on_focus_space_tip(x) { ui.areas[x].classList.add("tip") } @@ -76,7 +107,7 @@ function on_click_space_tip(x) { function sub_space_name(match, p1, offset, string) { let x = p1 | 0 - let n = AREAS[x].name + let n = AREAS[x].nbname return `${n}` } @@ -115,158 +146,158 @@ function on_log(text) { function is_known_block(b) { if (view.game_over && player === 'Observer') - return true; - return block_owner(b) === player; + return true + return block_owner(b) === player } function on_focus_area(evt) { - let where = evt.target.area; - let text = where; + let where = evt.target.area + let text = AREAS[where].name if (AREAS[where].city) - text += " (" + AREAS[where].city + ")"; + text += " (" + AREAS[where].city + ")" if (AREAS[where].crown) - text += " - Crown"; // " \u2655"; + text += " - Crown" // " \u2655" if (where === "South Yorks" || where === "Kent") - text += " - Church"; // " -" \u2657"; + text += " - Church" // " -" \u2657" if (AREAS[where].major_port) - text += " - Port"; + text += " - Port" if (AREAS[where].shields.length > 0) - text += " - " + AREAS[where].shields.join(", "); - document.getElementById("status").textContent = text; + text += " - " + AREAS[where].shields.join(", ") + document.getElementById("status").textContent = text } function on_blur_area(evt) { - document.getElementById("status").textContent = ""; + document.getElementById("status").textContent = "" } function on_click_area(evt) { - let where = evt.target.area; - send_action('area', where); + let where = evt.target.area + send_action('area', where) } -const STEP_TEXT = [ 0, "I", "II", "III", "IIII" ]; -const HEIR_TEXT = [ 0, '\u00b9', '\u00b2', '\u00b3', '\u2074', '\u2075' ]; +const STEP_TEXT = [ 0, "I", "II", "III", "IIII" ] +const HEIR_TEXT = [ 0, '\u00b9', '\u00b2', '\u00b3', '\u2074', '\u2075' ] function block_name(who) { - if (who === NOBODY) return "Nobody"; - let name = BLOCKS[who].name; - let long_name = LONG_NAME[name]; - return long_name ? long_name : name; + if (who === NOBODY) return "Nobody" + let name = BLOCKS[who].name + let long_name = LONG_NAME[name] + return long_name ? long_name : name } function block_owner(who) { - if (who === REBEL) { + if (who === B_REBEL) { if (view.pretender !== NOBODY) - return BLOCKS[view.pretender].owner; + return BLOCKS[view.pretender].owner if (view.king !== NOBODY) - return ENEMY[BLOCKS[view.king].owner]; - return YORK; + return ENEMY[BLOCKS[view.king].owner] + return YORK } - return BLOCKS[who].owner; + return BLOCKS[who].owner } function on_focus_map_block(evt) { - let b = evt.target.block; + let b = evt.target.block if (is_known_block(b)) { - let s = BLOCKS[b].steps; - let text = block_name(b) + " "; + let s = BLOCKS[b].steps + let text = block_name(b) + " " if (BLOCKS[b].type === 'heir') - text += "H" + HEIR_TEXT[BLOCKS[b].heir] + "-"; + text += "H" + HEIR_TEXT[BLOCKS[b].heir] + "-" if (BLOCKS[b].loyalty) - text += BLOCKS[b].loyalty + "-"; + text += BLOCKS[b].loyalty + "-" else if (BLOCKS[b].type === 'nobles') - text += "\u2740-"; - text += STEP_TEXT[s] + "-" + BLOCKS[b].combat; - document.getElementById("status").textContent = text; + text += "\u2740-" + text += STEP_TEXT[s] + "-" + BLOCKS[b].combat + document.getElementById("status").textContent = text } else { - let owner = block_owner(b); - if (b === REBEL) - owner = "Rebel"; - document.getElementById("status").textContent = owner; + let owner = block_owner(b) + if (b === B_REBEL) + owner = "Rebel" + document.getElementById("status").textContent = owner } } function on_blur_map_block(evt) { - document.getElementById("status").textContent = ""; + document.getElementById("status").textContent = "" } function on_click_map_block(evt) { - let b = evt.target.block; + let b = evt.target.block if (!view.battle) - send_action('block', b); + send_action('block', b) } function on_focus_battle_block(evt) { - let b = evt.target.block; - let msg = block_name(b); + let b = evt.target.block + let msg = block_name(b) if (view.battle.LR.includes(b)) - msg = "Lancaster Reserve"; + msg = "Lancaster Reserve" if (view.battle.YR.includes(b)) - msg = "York Reserve"; + msg = "York Reserve" if (view.actions && view.actions.battle_fire && view.actions.battle_fire.includes(b)) - msg = "Fire with " + msg; + msg = "Fire with " + msg else if (view.actions && view.actions.battle_retreat && view.actions.battle_retreat.includes(b)) - msg = "Retreat with " + msg; + msg = "Retreat with " + msg else if (view.actions && view.actions.battle_charge && view.actions.battle_charge.includes(b)) - msg = "Charge " + msg; + msg = "Charge " + msg else if (view.actions && view.actions.battle_treachery && view.actions.battle_treachery.includes(b)) - msg = "Attempt treachery on " + msg; + msg = "Attempt treachery on " + msg else if (view.actions && view.actions.battle_hit && view.actions.battle_hit.includes(b)) - msg = "Take hit on " + msg; + msg = "Take hit on " + msg - document.getElementById("status").textContent = msg; + document.getElementById("status").textContent = msg } function on_blur_battle_block(evt) { - document.getElementById("status").textContent = ""; + document.getElementById("status").textContent = "" } function on_click_battle_block(evt) { - let b = evt.target.block; - send_action('block', b); + let b = evt.target.block + send_action('block', b) } function on_focus_battle_fire(evt) { document.getElementById("status").textContent = - "Fire with " + block_name(evt.target.block); + "Fire with " + block_name(evt.target.block) } function on_focus_battle_retreat(evt) { document.getElementById("status").textContent = - "Retreat with " + block_name(evt.target.block); + "Retreat with " + block_name(evt.target.block) } function on_focus_battle_pass(evt) { document.getElementById("status").textContent = - "Pass with " + block_name(evt.target.block); + "Pass with " + block_name(evt.target.block) } function on_focus_battle_hit(evt) { document.getElementById("status").textContent = - "Take hit on " + block_name(evt.target.block); + "Take hit on " + block_name(evt.target.block) } function on_focus_battle_charge(evt) { if (block_owner(evt.target.block) === view.active) document.getElementById("status").textContent = - "Charge with " + block_name(evt.target.block); + "Charge with " + block_name(evt.target.block) else document.getElementById("status").textContent = - "Charge " + block_name(evt.target.block); + "Charge " + block_name(evt.target.block) } function on_focus_battle_treachery(evt) { if (block_owner(evt.target.block) === view.active) document.getElementById("status").textContent = - "Attempt treachery with " + block_name(evt.target.block); + "Attempt treachery with " + block_name(evt.target.block) else document.getElementById("status").textContent = - "Attempt treachery on " + block_name(evt.target.block); + "Attempt treachery on " + block_name(evt.target.block) } function on_blur_battle_button(evt) { - document.getElementById("status").textContent = ""; + document.getElementById("status").textContent = "" } function on_click_battle_hit(evt) { send_action('battle_hit', evt.target.block); } @@ -277,468 +308,478 @@ function on_click_battle_treachery(evt) { send_action('battle_treachery', evt.ta function on_click_battle_pass(evt) { if (window.confirm("Are you sure that you want to PASS with " + block_name(evt.target.block) + "?")) - send_action('battle_pass', evt.target.block); + send_action('battle_pass', evt.target.block) } function on_click_card(evt) { - let c = evt.target.id.split("+")[1] | 0; - send_action('play', c); + let c = evt.target.id.split("+")[1] | 0 + send_action('play', c) } function build_battle_button(menu, b, c, click, enter, img_src) { - let img = new Image(); - img.draggable = false; - img.classList.add("action"); - img.classList.add(c); - img.setAttribute("src", img_src); - img.addEventListener("click", click); - img.addEventListener("mouseenter", enter); - img.addEventListener("mouseleave", on_blur_battle_button); - img.block = b; - menu.appendChild(img); + let img = new Image() + img.draggable = false + img.classList.add("action") + img.classList.add(c) + img.setAttribute("src", img_src) + img.addEventListener("click", click) + img.addEventListener("mouseenter", enter) + img.addEventListener("mouseleave", on_blur_battle_button) + img.block = b + menu.appendChild(img) } function build_battle_block(b, block) { - let element = document.createElement("div"); - element.classList.add("block"); - element.classList.add("known"); - element.classList.add(BLOCKS[b].owner); - element.classList.add("block_" + block.image); - element.addEventListener("mouseenter", on_focus_battle_block); - element.addEventListener("mouseleave", on_blur_battle_block); - element.addEventListener("click", on_click_battle_block); - element.block = b; - ui.battle_block[b] = element; - - let menu_list = document.createElement("div"); - menu_list.classList.add("battle_menu_list"); + let element = document.createElement("div") + element.classList.add("block") + element.classList.add("known") + element.classList.add(BLOCKS[b].owner) + element.classList.add("block_" + block.image) + element.addEventListener("mouseenter", on_focus_battle_block) + element.addEventListener("mouseleave", on_blur_battle_block) + element.addEventListener("click", on_click_battle_block) + element.block = b + ui.battle_block[b] = element + + let menu_list = document.createElement("div") + menu_list.classList.add("battle_menu_list") build_battle_button(menu_list, b, "treachery", on_click_battle_treachery, on_focus_battle_treachery, - "/images/rose.svg"); + "/images/rose.svg") build_battle_button(menu_list, b, "charge", on_click_battle_charge, on_focus_battle_charge, - "/images/mounted-knight.svg"); + "/images/mounted-knight.svg") build_battle_button(menu_list, b, "hit", on_click_battle_hit, on_focus_battle_hit, - "/images/cross-mark.svg"); + "/images/cross-mark.svg") - // menu_list.appendChild(document.createElement("br")); + // menu_list.appendChild(document.createElement("br")) build_battle_button(menu_list, b, "fire", on_click_battle_fire, on_focus_battle_fire, - "/images/pointy-sword.svg"); + "/images/pointy-sword.svg") build_battle_button(menu_list, b, "retreat", on_click_battle_retreat, on_focus_battle_retreat, - "/images/flying-flag.svg"); + "/images/flying-flag.svg") build_battle_button(menu_list, b, "pass", on_click_battle_pass, on_focus_battle_pass, - "/images/sands-of-time.svg"); + "/images/sands-of-time.svg") - let menu = document.createElement("div"); - menu.classList.add("battle_menu"); - menu.appendChild(element); - menu.appendChild(menu_list); - menu.block = b; - ui.battle_menu[b] = menu; + let menu = document.createElement("div") + menu.classList.add("battle_menu") + menu.appendChild(element) + menu.appendChild(menu_list) + menu.block = b + ui.battle_menu[b] = menu } function build_map_block(b, block) { - let element = document.createElement("div"); - element.classList.add("block"); - element.classList.add("known"); - element.classList.add(BLOCKS[b].owner); - element.classList.add("block_" + block.image); - element.addEventListener("mouseenter", on_focus_map_block); - element.addEventListener("mouseleave", on_blur_map_block); - element.addEventListener("click", on_click_map_block); - element.block = b; - ui.blocks[b] = element; + let element = document.createElement("div") + element.classList.add("block") + element.classList.add("known") + element.classList.add(BLOCKS[b].owner) + element.classList.add("block_" + block.image) + element.addEventListener("mouseenter", on_focus_map_block) + element.addEventListener("mouseleave", on_blur_map_block) + element.addEventListener("click", on_click_map_block) + element.block = b + ui.blocks[b] = element } function build_map() { - let element; + let element - ui.blocks_element = document.getElementById("blocks"); - ui.offmap_element = document.getElementById("offmap"); + ui.blocks_element = document.getElementById("blocks") + ui.offmap_element = document.getElementById("offmap") for (let c = 1; c <= 25; ++c) { - ui.cards[c] = document.getElementById("card+"+c); - ui.cards[c].addEventListener("click", on_click_card); + ui.cards[c] = document.getElementById("card+"+c) + ui.cards[c].addEventListener("click", on_click_card) } for (let c = 1; c <= 7; ++c) - ui.card_backs[c] = document.getElementById("back+"+c); + ui.card_backs[c] = document.getElementById("back+"+c) - for (let name in AREAS) { - let area = AREAS[name]; - element = document.getElementById("svgmap").getElementById("area_"+name.replace(/ /g, "_")); + for (let area = 0; area < AREAS.length; ++area) { + let name = AREAS[area].name + element = document.getElementById("svgmap").getElementById("area_"+name.replace(/ /g, "_")) if (element) { - element.area = name; - element.addEventListener("mouseenter", on_focus_area); - element.addEventListener("mouseleave", on_blur_area); - element.addEventListener("click", on_click_area); - ui.areas[name] = element; + element.area = area + element.addEventListener("mouseenter", on_focus_area) + element.addEventListener("mouseleave", on_blur_area) + element.addEventListener("click", on_click_area) + ui.areas[area] = element } } - for (let b in BLOCKS) { - let block = BLOCKS[b]; - build_battle_block(b, block); - build_map_block(b, block); + for (let b = 0; b < BLOCKS.length; ++b) { + let block = BLOCKS[b] + build_battle_block(b, block) + build_map_block(b, block) } } function update_steps(b, steps, element) { - element.classList.remove("r1"); - element.classList.remove("r2"); - element.classList.remove("r3"); - element.classList.add("r"+(BLOCKS[b].steps - steps)); + element.classList.remove("r1") + element.classList.remove("r2") + element.classList.remove("r3") + element.classList.add("r"+(BLOCKS[b].steps - steps)) +} + +function compare_layout_blocks(a, b) { + let ad = view.dead.includes(a.block) + let bd = view.dead.includes(b.block) + if (ad && !bd) return 1 + if (bd && !ad) return -1 + return a.block - b.block } function layout_blocks(area, secret, known) { - let wrap = AREAS[area].wrap; - let s = secret.length; - let k = known.length; - let n = s + k; - let row, rows = []; - let i = 0; + secret.sort(compare_layout_blocks) + known.sort(compare_layout_blocks) + + let wrap = AREAS[area].layout.wrap + let s = secret.length + let k = known.length + let n = s + k + let row, rows = [] + let i = 0 function new_line() { - rows.push(row = []); - i = 0; + rows.push(row = []) + i = 0 } - new_line(); + new_line() while (secret.length > 0) { if (i === wrap) - new_line(); - row.push(secret.shift()); - ++i; + new_line() + row.push(secret.shift()) + ++i } // Break early if secret and known fit in exactly two rows, and more than three blocks total if (s > 0 && s <= wrap && k > 0 && k <= wrap && n > 3) - new_line(); + new_line() while (known.length > 0) { if (i === wrap) - new_line(); - row.push(known.shift()); - ++i; + new_line() + row.push(known.shift()) + ++i } - if (AREAS[area].layout_minor > 0.5) - rows.reverse(); + if (AREAS[area].layout.minor > 0.5) + rows.reverse() for (let j = 0; j < rows.length; ++j) for (i = 0; i < rows[j].length; ++i) - position_block(area, j, rows.length, i, rows[j].length, rows[j][i]); + position_block(area, j, rows.length, i, rows[j].length, rows[j][i]) } function position_block(area, row, n_rows, col, n_cols, element) { - let space = AREAS[area]; - let block_size = 60+6; - let padding = 4; - let offset = block_size + padding; - let row_size = (n_rows-1) * offset; - let col_size = (n_cols-1) * offset; - let x = space.x - block_size/2; - let y = space.y - block_size/2; - - if (space.layout_axis === 'X') { - x -= col_size * space.layout_major; - y -= row_size * space.layout_minor; - x += col * offset; - y += row * offset; + let space = AREAS[area] + let block_size = 60+6 + let padding = 4 + let offset = block_size + padding + let row_size = (n_rows-1) * offset + let col_size = (n_cols-1) * offset + let x = space.layout.x - block_size/2 + let y = space.layout.y - block_size/2 + + if (space.layout.axis === 'X') { + x -= col_size * space.layout.major + y -= row_size * space.layout.minor + x += col * offset + y += row * offset } else { - y -= col_size * space.layout_major; - x -= row_size * space.layout_minor; - y += col * offset; - x += row * offset; + y -= col_size * space.layout.major + x -= row_size * space.layout.minor + y += col * offset + x += row * offset } - element.style.left = (x|0)+"px"; - element.style.top = (y|0)+"px"; + element.style.left = (x|0)+"px" + element.style.top = (y|0)+"px" } function show_block(element) { if (element.parentElement !== ui.blocks_element) - ui.blocks_element.appendChild(element); + ui.blocks_element.appendChild(element) } function hide_block(element) { if (element.parentElement !== ui.offmap_element) - ui.offmap_element.appendChild(element); + ui.offmap_element.appendChild(element) } function is_dead(who) { - return view.location[who] === NOWHERE; + return view.location[who] === NOWHERE } function is_perma_dead(who) { - if (BLOCKS[who].loyalty === undefined) - return true; + if (BLOCKS[who].loyalty === 0) + return true switch (who) { - case "Warwick/Y": return is_dead("Warwick/L") && is_dead("Warwick/Y"); - case "Kent/Y": return is_dead("Kent/L") && is_dead("Kent/Y"); - case "Salisbury/Y": return is_dead("Salisbury/L") && is_dead("Salisbury/Y"); - case "Clarence/Y": return is_dead("Clarence/L") && is_dead("Clarence/Y"); - case "Exeter/L": return is_dead("Exeter/L") && is_dead("Exeter/Y"); + case B_WARWICK_Y: return is_dead(B_WARWICK_Y) && is_dead(B_WARWICK_L) + case B_KENT_Y: return is_dead(B_KENT_Y) && is_dead(B_KENT_L) + case B_SALISBURY_Y: return is_dead(B_SALISBURY_Y) && is_dead(B_SALISBURY_L) + case B_CLARENCE_Y: return is_dead(B_CLARENCE_Y) && is_dead(B_CLARENCE_L) + case B_EXETER_L: return is_dead(B_EXETER_Y) && is_dead(B_EXETER_L) } - return false; + return false } function update_map() { - let overflow = { Lancaster: [], York: [], Rebel: [] }; - let layout = {}; + let overflow = { Lancaster: [], York: [], Rebel: [] } + let layout = {} document.getElementById("turn_info").textContent = "Campaign " + view.campaign + "\nKing: " + block_name(view.king) + - "\nPretender: " + block_name(view.pretender); - - layout[DEAD] = { Lancaster: [], York: [] }; - for (let area in AREAS) - layout[area] = { Lancaster: [], York: [] }; - - for (let b in view.location) { - let info = BLOCKS[b]; - let element = ui.blocks[b]; - let area = view.location[b]; - let moved = view.moved[b] ? " moved" : ""; - let image = " block_" + info.image; - let steps = " r" + (info.steps - view.steps[b]); - let known = is_known_block(b); - - // perma-dead nobles - if (area === NOWHERE && is_perma_dead(b)) { - area = DEAD; - moved = " moved"; - known = 1; - steps = ""; - } - - if (area !== NOWHERE) { + "\nPretender: " + block_name(view.pretender) + + for (let area = 0; area < AREAS.length; ++area) + layout[area] = { Lancaster: [], York: [] } + + for (let b = 0; b < BLOCKS.length; ++b) { + let info = BLOCKS[b] + let element = ui.blocks[b] + let area = view.location[b] + let moved = view.moved.includes(b) ? " moved" : "" + let image = " block_" + info.image + let steps = " r" + (info.steps - view.steps[b]) + let known = is_known_block(b) + + if (area !== NOWHERE || is_perma_dead(b)) { + // perma-dead nobles + if (area === NOWHERE || view.dead.includes(b)) { + moved = " moved" + known = 1 + steps = "" + } if (known) { - element.classList = info.owner + " known block" + image + steps + moved; + element.classList = info.owner + " known block" + image + steps + moved } else { - element.classList = info.owner + " block" + moved; + element.classList = info.owner + " block" + moved } + if (block_owner(b) === LANCASTER) - layout[area].Lancaster.push(element); + layout[area].Lancaster.push(element) else - layout[area].York.push(element); - show_block(element); + layout[area].York.push(element) + + show_block(element) } else { - hide_block(element); + hide_block(element) } } - for (let area in AREAS) { + for (let area = 1; area < AREAS.length; ++area) { if (area === POOL) { - layout_blocks("LPool", layout[POOL].Lancaster, layout[DEAD].Lancaster); - layout_blocks("YPool", layout[POOL].York, layout[DEAD].York); + layout_blocks(L_POOL, layout[POOL].Lancaster, layout[NOWHERE].Lancaster) + layout_blocks(Y_POOL, layout[POOL].York, layout[NOWHERE].York) } else if (area === MINOR) { - layout_blocks("LMinor", layout[area].Lancaster, []); - layout_blocks("YMinor", layout[area].York, []); + layout_blocks(L_MINOR, layout[area].Lancaster, []) + layout_blocks(Y_MINOR, layout[area].York, []) } else { - layout_blocks(area, layout[area].Lancaster, layout[area].York); + layout_blocks(area, layout[area].Lancaster, layout[area].York) } } - for (let area in AREAS) { + for (let area = 0; area < AREAS.length; ++area) { if (ui.areas[area]) { - ui.areas[area].classList.remove('highlight'); - ui.areas[area].classList.remove('where'); + ui.areas[area].classList.remove('highlight') + ui.areas[area].classList.remove('where') } } if (view.actions && view.actions.area) for (let area of view.actions.area) - ui.areas[area].classList.add('highlight'); + ui.areas[area].classList.add('highlight') if (view.where !== NOWHERE) - ui.areas[view.where].classList.add('where'); + ui.areas[view.where].classList.add('where') - for (let b in BLOCKS) { - ui.blocks[b].classList.remove('highlight'); - ui.blocks[b].classList.remove('selected'); + for (let b = 0; b < BLOCKS.length; ++b) { + ui.blocks[b].classList.remove('highlight') + ui.blocks[b].classList.remove('selected') } if (!view.battle) { if (view.actions && view.actions.block) for (let b of view.actions.block) - ui.blocks[b].classList.add('highlight'); + ui.blocks[b].classList.add('highlight') if (view.who !== NOBODY) - ui.blocks[view.who].classList.add('selected'); + ui.blocks[view.who].classList.add('selected') } } function update_cards() { - let cards = view.hand; + let cards = view.hand for (let c = 1; c <= 25; ++c) { - ui.cards[c].classList.remove('enabled'); + ui.cards[c].classList.remove('enabled') if (cards && cards.includes(c)) - ui.cards[c].classList.add('show'); + ui.cards[c].classList.add('show') else - ui.cards[c].classList.remove('show'); + ui.cards[c].classList.remove('show') } - let n = view.hand.length; + let n = view.hand.length for (let c = 1; c <= 7; ++c) if (c <= n && player === 'Observer') - ui.card_backs[c].classList.add("show"); + ui.card_backs[c].classList.add("show") else - ui.card_backs[c].classList.remove("show"); + ui.card_backs[c].classList.remove("show") if (view.actions && view.actions.play) { for (let c of view.actions.play) - ui.cards[c].classList.add('enabled'); + ui.cards[c].classList.add('enabled') } if (!view.l_card) - document.getElementById("lancaster_card").className = "show card card_back"; + document.getElementById("lancaster_card").className = "show card card_back" else - document.getElementById("lancaster_card").className = "show card " + CARDS[view.l_card].image; + document.getElementById("lancaster_card").className = "show card " + CARDS[view.l_card].image if (!view.y_card) - document.getElementById("york_card").className = "show card card_back"; + document.getElementById("york_card").className = "show card card_back" else - document.getElementById("york_card").className = "show card " + CARDS[view.y_card].image; + document.getElementById("york_card").className = "show card " + CARDS[view.y_card].image } function compare_blocks(a, b) { - let aa = BLOCKS[a].combat; - let bb = BLOCKS[b].combat; + let aa = BLOCKS[a].combat + let bb = BLOCKS[b].combat // Bombard - if (aa === "D3" && view.battle.round <= 1) aa = "A3"; - if (bb === "D3" && view.battle.round <= 1) bb = "A3"; + if (aa === "D3" && view.battle.round <= 1) aa = "A3" + if (bb === "D3" && view.battle.round <= 1) bb = "A3" if (aa === bb) - return (a < b) ? -1 : (a > b) ? 1 : 0; - return (aa < bb) ? -1 : (aa > bb) ? 1 : 0; + return (a < b) ? -1 : (a > b) ? 1 : 0 + return (aa < bb) ? -1 : (aa > bb) ? 1 : 0 } function sort_battle_row(root) { - let swapped; - let children = root.children; + let swapped + let children = root.children do { - swapped = false; + swapped = false for (let i = 1; i < children.length; ++i) { if (compare_blocks(children[i-1].block, children[i].block) > 0) { - children[i].after(children[i-1]); - swapped = true; + children[i].after(children[i-1]) + swapped = true } } - } while (swapped); + } while (swapped) } function update_battle() { function fill_cell(name, list, reserve) { - let cell = window[name]; + let cell = window[name] - ui.present.clear(); + ui.present.clear() for (let block of list) { - ui.present.add(block); + ui.present.add(block) if (!cell.contains(ui.battle_menu[block])) - cell.appendChild(ui.battle_menu[block]); + cell.appendChild(ui.battle_menu[block]) if (block === view.who) - ui.battle_block[block].classList.add("selected"); + ui.battle_block[block].classList.add("selected") else - ui.battle_block[block].classList.remove("selected"); + ui.battle_block[block].classList.remove("selected") - ui.battle_block[block].classList.remove("highlight"); - ui.battle_menu[block].classList.remove('hit'); - ui.battle_menu[block].classList.remove('fire'); - ui.battle_menu[block].classList.remove('retreat'); - ui.battle_menu[block].classList.remove('pass'); - ui.battle_menu[block].classList.remove('charge'); - ui.battle_menu[block].classList.remove('treachery'); + ui.battle_block[block].classList.remove("highlight") + ui.battle_menu[block].classList.remove('hit') + ui.battle_menu[block].classList.remove('fire') + ui.battle_menu[block].classList.remove('retreat') + ui.battle_menu[block].classList.remove('pass') + ui.battle_menu[block].classList.remove('charge') + ui.battle_menu[block].classList.remove('treachery') if (view.actions && view.actions.block && view.actions.block.includes(block)) - ui.battle_block[block].classList.add("highlight"); + ui.battle_block[block].classList.add("highlight") if (view.actions && view.actions.battle_fire && view.actions.battle_fire.includes(block)) - ui.battle_menu[block].classList.add('fire'); + ui.battle_menu[block].classList.add('fire') if (view.actions && view.actions.battle_retreat && view.actions.battle_retreat.includes(block)) - ui.battle_menu[block].classList.add('retreat'); + ui.battle_menu[block].classList.add('retreat') if (view.actions && view.actions.battle_pass && view.actions.battle_pass.includes(block)) - ui.battle_menu[block].classList.add('pass'); + ui.battle_menu[block].classList.add('pass') if (view.actions && view.actions.battle_hit && view.actions.battle_hit.includes(block)) - ui.battle_menu[block].classList.add('hit'); + ui.battle_menu[block].classList.add('hit') if (view.actions && view.actions.battle_charge && view.actions.battle_charge.includes(block)) - ui.battle_menu[block].classList.add('charge'); + ui.battle_menu[block].classList.add('charge') if (view.actions && view.actions.battle_treachery && view.actions.battle_treachery.includes(block)) - ui.battle_menu[block].classList.add('treachery'); + ui.battle_menu[block].classList.add('treachery') - update_steps(block, view.steps[block], ui.battle_block[block]); + update_steps(block, view.steps[block], ui.battle_block[block]) if (reserve) - ui.battle_block[block].classList.add("secret"); + ui.battle_block[block].classList.add("secret") else - ui.battle_block[block].classList.remove("secret"); - if (view.moved[block]) - ui.battle_block[block].classList.add("moved"); + ui.battle_block[block].classList.remove("secret") + if (view.moved.includes(block)) + ui.battle_block[block].classList.add("moved") else - ui.battle_block[block].classList.remove("moved"); + ui.battle_block[block].classList.remove("moved") if (reserve) - ui.battle_block[block].classList.remove("known"); + ui.battle_block[block].classList.remove("known") else - ui.battle_block[block].classList.add("known"); + ui.battle_block[block].classList.add("known") } - for (let b in BLOCKS) { + for (let b = 0; b < BLOCKS.length; ++b) { if (!ui.present.has(b)) { if (cell.contains(ui.battle_menu[b])) - cell.removeChild(ui.battle_menu[b]); + cell.removeChild(ui.battle_menu[b]) } } - sort_battle_row(cell); + sort_battle_row(cell) } if (player === LANCASTER) { - fill_cell("FR", view.battle.LR, true); - fill_cell("FF", view.battle.LF, false); - fill_cell("EF", view.battle.YF, false); - fill_cell("ER", view.battle.YR, true); + fill_cell("FR", view.battle.LR, true) + fill_cell("FF", view.battle.LF, false) + fill_cell("EF", view.battle.YF, false) + fill_cell("ER", view.battle.YR, true) } else { - fill_cell("ER", view.battle.LR, true); - fill_cell("EF", view.battle.LF, false); - fill_cell("FF", view.battle.YF, false); - fill_cell("FR", view.battle.YR, true); + fill_cell("ER", view.battle.LR, true) + fill_cell("EF", view.battle.LF, false) + fill_cell("FF", view.battle.YF, false) + fill_cell("FR", view.battle.YR, true) } } function on_update() { - let king = block_owner(view.king); - document.getElementById("lancaster_vp").textContent = (king === LANCASTER ? KING_TEXT : PRETENDER_TEXT); - document.getElementById("york_vp").textContent = (king === YORK ? KING_TEXT : PRETENDER_TEXT); - - action_button("eliminate", "Eliminate"); - action_button("execute_clarence", "Execute Clarence"); - action_button("execute_exeter", "Execute Exeter"); - action_button("end_action_phase", "End action phase"); - action_button("end_supply_phase", "End supply phase"); - action_button("end_political_turn", "End political turn"); - action_button("end_exile_limits", "End exile limits"); - action_button("end_regroup", "End regroup"); - action_button("end_retreat", "End retreat"); - action_button("pass", "Pass"); - action_button("undo", "Undo"); - - update_cards(); - update_map(); + let king = block_owner(view.king) + document.getElementById("lancaster_vp").textContent = (king === LANCASTER ? KING_TEXT : PRETENDER_TEXT) + document.getElementById("york_vp").textContent = (king === YORK ? KING_TEXT : PRETENDER_TEXT) + + action_button("eliminate", "Eliminate") + action_button("execute_clarence", "Execute Clarence") + action_button("execute_exeter", "Execute Exeter") + action_button("end_action_phase", "End action phase") + action_button("end_supply_phase", "End supply phase") + action_button("end_political_turn", "End political turn") + action_button("end_exile_limits", "End exile limits") + action_button("end_regroup", "End regroup") + action_button("end_retreat", "End retreat") + action_button("pass", "Pass") + action_button("undo", "Undo") + + update_cards() + update_map() if (view.battle) { - document.getElementById("battle_header").textContent = view.battle.title; - document.getElementById("battle_message").textContent = view.battle.flash; - document.getElementById("battle").classList.add("show"); - update_battle(); + document.getElementById("battle_header").textContent = view.battle.title + document.getElementById("battle_message").textContent = view.battle.flash + document.getElementById("battle").classList.add("show") + update_battle() } else { - document.getElementById("battle").classList.remove("show"); + document.getElementById("battle").classList.remove("show") } } -build_map(); +build_map() -drag_element_with_mouse("#battle", "#battle_header"); -scroll_with_middle_mouse("main", 2); +drag_element_with_mouse("#battle", "#battle_header") +scroll_with_middle_mouse("main", 2) diff --git a/rules.js b/rules.js index 8a5e58f..aa1e069 100644 --- a/rules.js +++ b/rules.js @@ -13,24 +13,75 @@ exports.scenarios = [ exports.roles = [ "York", "Lancaster" ] -const { CARDS, BLOCKS, AREAS, BORDERS } = require('./data') +const { CARDS, BLOCKS, AREAS, BORDERS, block_index, area_index } = require('./data') -delete AREAS.LPool -delete AREAS.YPool -delete AREAS.LMinor -delete AREAS.YMinor +const first_area = 6 // first real area (skip pools and seas) +const last_area = AREAS.length - 4 // skip layout areas at end +const block_count = BLOCKS.length + +// roles const LANCASTER = "Lancaster" const YORK = "York" -const REBEL = "Rebel" const ENEMY = { Lancaster: "York", York: "Lancaster" } const OBSERVER = "Observer" const BOTH = "Both" -const POOL = "Pool" -const MINOR = "Minor" -const NOBODY = -1 +// areas const NOWHERE = 0 +const POOL = 1 +const MINOR = 2 + +const IRISH_SEA = area_index["Irish Sea"] +const NORTH_SEA = area_index["North Sea"] +const ENGLISH_CHANNEL = area_index["English Channel"] + +const IRELAND = area_index["Ireland"] +const SCOTLAND = area_index["Scotland"] +const FRANCE = area_index["France"] +const CALAIS = area_index["Calais"] + +const CAERNARVON = area_index["Caernarvon"] +const PEMBROKE = area_index["Pembroke"] +const POWYS = area_index["Powys"] +const GLAMORGAN = area_index["Glamorgan"] + +const CORNWALL = area_index["Cornwall"] +const EAST_YORKS = area_index["East Yorks"] +const HEREFORD = area_index["Hereford"] +const ISLE_OF_MAN = area_index["Isle of Man"] +const MIDDLESEX = area_index["Middlesex"] +const NORTH_YORKS = area_index["North Yorks"] +const RUTLAND = area_index["Rutland"] +const SOUTH_YORKS = area_index["South Yorks"] + +// blocks +const NOBODY = -1 +const B_YORK = block_index["York"] +const B_MARCH = block_index["March"] +const B_RUTLAND = block_index["Rutland"] +const B_CLARENCE_Y = block_index["Clarence/Y"] +const B_GLOUCESTER = block_index["Gloucester"] +const B_EXETER_Y = block_index["Exeter/Y"] +const B_WARWICK_Y = block_index["Warwick/Y"] +const B_KENT_Y = block_index["Kent/Y"] +const B_SALISBURY_Y = block_index["Salisbury/Y"] +const B_IRISH_MERCENARY = block_index["Irish Mercenary"] +const B_BURGUNDIAN_MERCENARY = block_index["Burgundian Mercenary"] +const B_CALAIS_MERCENARY = block_index["Calais Mercenary"] +const B_HENRY_VI = block_index["Henry VI"] +const B_PRINCE_EDWARD = block_index["Prince Edward"] +const B_EXETER_L = block_index["Exeter/L"] +const B_SOMERSET = block_index["Somerset"] +const B_RICHMOND = block_index["Richmond"] +const B_WARWICK_L = block_index["Warwick/L"] +const B_KENT_L = block_index["Kent/L"] +const B_SALISBURY_L = block_index["Salisbury/L"] +const B_CLARENCE_L = block_index["Clarence/L"] +const B_SCOTS_MERCENARY = block_index["Scots Mercenary"] +const B_WELSH_MERCENARY = block_index["Welsh Mercenary"] +const B_FRENCH_MERCENARY = block_index["French Mercenary"] +const B_REBEL = block_index["Rebel"] // serif cirled numbers const DIE_HIT = [ 0, '\u2776', '\u2777', '\u2778', '\u2779', '\u277A', '\u277B' ] @@ -39,10 +90,6 @@ const DIE_MISS = [ 0, '\u2460', '\u2461', '\u2462', '\u2463', '\u2464', '\u2465' const ATTACK_MARK = "*" const RESERVE_MARK = "" -// List for fast iteration -const BLOCKLIST = Object.keys(BLOCKS) -const AREALIST = Object.keys(AREAS) - let states = {} let game = null @@ -75,14 +122,14 @@ function log(s) { } function log_move_start(from) { - game.turn_buf = [ from ] + game.turn_buf = [ "#" + from ] } function log_move_continue(to, mark) { if (mark) - game.turn_buf.push(to + mark) + game.turn_buf.push("#" + to + mark) else - game.turn_buf.push(to) + game.turn_buf.push("#" + to) } function log_move_end() { @@ -198,9 +245,7 @@ function is_royal_heir(who) { } function is_dead(who) { - if (who in BLOCKS) - return game.location[who] === NOWHERE - return game.location[who+"/L"] === NOWHERE && game.location[who+"/Y"] === NOWHERE + return game.location[who] === NOWHERE } function is_shield_area_for(where, who, combat) { @@ -208,22 +253,22 @@ function is_shield_area_for(where, who, combat) { let needle = BLOCKS[who].shield // Nevilles going to exile in Calais - if (where === "Calais") { - if (who === "Warwick/L" || who === "Kent/L" || who === "Salisbury/L") + if (where === CALAIS) { + if (who === B_WARWICK_L || who === B_KENT_L || who === B_SALISBURY_L) return false - if (count_blocks_exclude_mercenaries("Calais") < 4) { - if (who === "Kent/Y") - return is_area_friendly_to("East Yorks", LANCASTER) - if (who === "Salisbury/Y") - return is_area_friendly_to("North Yorks", LANCASTER) + if (count_blocks_exclude_mercenaries(CALAIS) < 4) { + if (who === B_KENT_Y) + return is_area_friendly_to(EAST_YORKS, LANCASTER) + if (who === B_SALISBURY_Y) + return is_area_friendly_to(NORTH_YORKS, LANCASTER) } } // Exeter and Clarence as enemy nobles - if (who === "Exeter/Y") - return where === "Cornwall" - if (who === "Clarence/L") - return (where === "South Yorks" || where === "Rutland" || where === "Hereford") + if (who === B_EXETER_Y) + return where === CORNWALL + if (who === B_CLARENCE_L) + return (where === SOUTH_YORKS || where === RUTLAND || where === HEREFORD) // Everyone can always use their own shield if (haystack && haystack.includes(needle)) @@ -231,11 +276,11 @@ function is_shield_area_for(where, who, combat) { // Nevilles can use each other's shields if their owner is dead if (is_neville(who)) { - if (is_dead("Warwick") && haystack.includes("Warwick")) + if (is_dead(B_WARWICK_L) && is_dead(B_WARWICK_Y) && haystack.includes("Warwick")) return true - if (is_dead("Kent") && haystack.includes("Kent")) + if (is_dead(B_KENT_L) && is_dead(B_KENT_Y) && haystack.includes("Kent")) return true - if (is_dead("Salisbury") && haystack.includes("Salisbury")) + if (is_dead(B_SALISBURY_L) && is_dead(B_SALISBURY_Y) && haystack.includes("Salisbury")) return true } @@ -250,11 +295,11 @@ function is_shield_area_for(where, who, combat) { let available = false if (haystack.includes("Lancaster")) available = true - if (is_dead("Exeter") && haystack.includes("Exeter")) + if (is_dead(B_EXETER_L) && is_dead(B_EXETER_Y) && haystack.includes("Exeter")) available = true - if (is_dead("Somerset") && haystack.includes("Somerset")) + if (is_dead(B_SOMERSET) && haystack.includes("Somerset")) available = true - if (is_dead("Richmond") && haystack.includes("Richmond")) + if (is_dead(B_RICHMOND) && haystack.includes("Richmond")) available = true if (available) return !combat || find_senior_heir_in_area(LANCASTER, where) === who @@ -295,21 +340,21 @@ function is_home_for(where, who) { } function is_available_home_for(where, who) { - if (who === "Clarence/L") + if (who === B_CLARENCE_L) return is_home_for(where, who) && is_vacant_area(where) return is_home_for(where, who) && is_friendly_or_vacant_area(where) } function count_available_homes(who) { let count = 0 - for (let where of AREALIST) + for (let where = first_area; where <= last_area; ++where) if (is_available_home_for(where, who)) ++count return count } function available_home(who) { - for (let where of AREALIST) + for (let where = first_area; where <= last_area; ++where) if (is_available_home_for(where, who)) return where } @@ -324,7 +369,7 @@ function go_home_if_possible(who) { let home = available_home(who) if (game.location[who] !== home) { game.location[who] = home - game.turn_log.push([block_name(who), game.location[who]]) // TODO: "Home"? + game.turn_log.push([block_name(who), "#" + game.location[who]]) // TODO: "Home"? } } else { return true @@ -336,8 +381,7 @@ function go_home_if_possible(who) { function is_on_map_not_in_exile_or_man(who) { let where = game.location[who] return where !== NOWHERE && where !== MINOR && where !== POOL && - where !== "Isle of Man" && - !is_exile_area(where) + where !== ISLE_OF_MAN && !is_exile_area(where) } function is_land_area(where) { @@ -353,12 +397,12 @@ function is_area_friendly_to(where, owner) { } function is_london_friendly_to(owner) { - return is_area_friendly_to("Middlesex", owner) + return is_area_friendly_to(MIDDLESEX, owner) } function count_lancaster_nobles_and_heirs() { let count = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (block_owner(b) === LANCASTER && (block_type(b) === 'nobles' || block_type(b) === 'church' || block_type(b) === 'heir')) if (is_on_map_not_in_exile_or_man(b)) @@ -370,7 +414,7 @@ function count_lancaster_nobles_and_heirs() { function count_york_nobles_and_heirs() { let count = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (block_owner(b) === YORK && (block_type(b) === 'nobles' || block_type(b) === 'church' || block_type(b) === 'heir')) if (is_on_map_not_in_exile_or_man(b)) @@ -393,7 +437,7 @@ function block_home(who) { } function block_owner(who) { - if (who === REBEL) { + if (who === B_REBEL) { if (game.pretender !== NOBODY) return block_owner(game.pretender) else if (game.king !== NOBODY) @@ -407,11 +451,11 @@ function block_owner(who) { function block_initiative(who) { if (block_type(who) === 'bombard') return game.battle_round <= 1 ? 'A' : 'D' - return BLOCKS[who].combat[0] + return BLOCKS[who].initiative } function block_printed_fire_power(who) { - return BLOCKS[who].combat[1] | 0 + return BLOCKS[who].fire_power } function block_fire_power(who, where) { @@ -427,7 +471,7 @@ function block_fire_power(who, where) { ++combat if (is_levy(who) && block_home(who) === has_city(where)) ++combat - if (who === "Welsh Mercenary" && is_wales(where)) + if (who === B_WELSH_MERCENARY && is_wales(where)) ++combat } return combat @@ -471,7 +515,7 @@ function block_loyalty(source, target) { if (target_name === "Northumberland" || target_name === "Westmoreland") return 0 } - return BLOCKS[target].loyalty | 0 + return BLOCKS[target].loyalty } function can_defect(source, target) { @@ -483,11 +527,11 @@ function can_defect(source, target) { function can_attempt_treason_event() { if (game.treason === game.attacker[game.where]) { - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (is_defender(b) && can_defect(NOBODY, b)) return true } else { - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (is_attacker(b) && can_defect(NOBODY, b)) return true } @@ -497,14 +541,14 @@ function can_attempt_treason_event() { function treachery_tag(who) { if (who === game.king) return 'King' if (who === game.pretender) return 'Pretender' - if (who === "Warwick/L" || who === "Warwick/Y") return 'Warwick' + if (who === B_WARWICK_L || who === B_WARWICK_Y) return 'Warwick' return game.active } function can_attempt_treachery(who) { let once = treachery_tag(who) if (set_has(game.battle_list, who) && !set_has(game.treachery, once)) { - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (game.active === game.attacker[game.where]) { if (is_defender(b) && can_defect(who, b)) return true @@ -538,7 +582,7 @@ function is_block_alive(b) { } function border_id(a, b) { - return (a < b) ? a + "/" + b : b + "/" + a + return (a < b) ? a * 100 + b : b * 100 + a } function border_was_last_used_by_enemy(from, to) { @@ -564,7 +608,7 @@ function reset_border_limits() { function count_friendly(where) { let p = game.active let count = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (game.location[b] === where && block_owner(b) === p && !set_has(game.dead, b)) ++count return count @@ -573,7 +617,7 @@ function count_friendly(where) { function count_enemy(where) { let p = ENEMY[game.active] let count = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (game.location[b] === where && block_owner(b) === p && !set_has(game.dead, b)) ++count return count @@ -582,7 +626,7 @@ function count_enemy(where) { function count_enemy_excluding_reserves(where) { let p = ENEMY[game.active] let count = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (game.location[b] === where && block_owner(b) === p) if (!set_has(game.reserves, b)) ++count @@ -612,19 +656,19 @@ function is_major_port(where) { } function is_sea_area(where) { - return where === 'Irish Sea' || where === 'North Sea' || where === 'English Channel' + return where === IRISH_SEA || where === NORTH_SEA || where === ENGLISH_CHANNEL } function is_wales(where) { - return where === "Caernarvon" || where === "Pembroke" || where === "Powys" || where === "Glamorgan" + return where === CAERNARVON || where === PEMBROKE || where === POWYS || where === GLAMORGAN } function is_lancaster_exile_area(where) { - return where === "France" || where === "Scotland" + return where === FRANCE || where === SCOTLAND } function is_york_exile_area(where) { - return where === "Calais" || where === "Ireland" + return where === CALAIS || where === IRELAND } function is_exile_area(where) { @@ -644,7 +688,7 @@ function is_pretender_exile_area(where) { } function can_recruit_to(who, to) { - if (who === "Welsh Mercenary") + if (who === B_WELSH_MERCENARY) return is_wales(to) && is_friendly_or_vacant_area(to) switch (block_type(who)) { case 'heir': @@ -678,14 +722,14 @@ function can_recruit(who) { if (game.active === game.piracy) return false if (can_activate(who) && game.location[who] === POOL) - for (let to of AREALIST) + for (let to = first_area; to <= last_area; ++to) if (can_recruit_to(who, to)) return true return false } function have_contested_areas() { - for (let where of AREALIST) + for (let where = first_area; where <= last_area; ++where) if (is_area_on_map(where) && is_contested_area(where)) return true return false @@ -697,7 +741,7 @@ function count_pinning(where) { function count_pinned(where) { let count = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (game.location[b] === where && block_owner(b) === game.active) if (!set_has(game.reserves, b)) ++count @@ -717,7 +761,7 @@ function can_block_sea_move_to(who, from, to) { return false if (game.active === game.force_march) return false - if (who === REBEL || who === "Scots Mercenary" || who === "Welsh Mercenary") + if (who === B_REBEL || who === B_SCOTS_MERCENARY || who === B_WELSH_MERCENARY) return false if (block_type(who) === 'bombard' || block_type(who) === 'levies') return false @@ -863,7 +907,7 @@ function can_block_muster_via(who, from, next, muster) { if (next === muster) return true if (border_type(from, next) !== 'minor') { - if (AREAS[next].exits.includes(muster)) + if (set_has(AREAS[next].exits, muster)) if (can_block_land_move_to(who, next, muster)) return true } @@ -886,7 +930,7 @@ function can_block_muster(who, muster) { } function can_muster_to(muster) { - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (can_block_muster(b, muster)) return true return false @@ -924,12 +968,12 @@ function disband(who) { function check_instant_victory() { // Check Clarence/Y and Exeter/L specifically (they're not heirs if converted) - if (is_dead("York") && is_dead("March") && is_dead("Rutland") && is_dead("Clarence/Y") && is_dead("Gloucester")) { + if (is_dead(B_YORK) && is_dead(B_MARCH) && is_dead(B_RUTLAND) && is_dead(B_CLARENCE_Y) && is_dead(B_GLOUCESTER)) { log("All York heirs were eliminated!") game.victory = "Lancaster won by eliminating all enemy heirs!" game.result = LANCASTER } - if (is_dead("Henry VI") && is_dead("Prince Edward") && is_dead("Exeter/L") && is_dead("Somerset") && is_dead("Richmond")) { + if (is_dead(B_HENRY_VI) && is_dead(B_PRINCE_EDWARD) && is_dead(B_EXETER_L) && is_dead(B_SOMERSET) && is_dead(B_RICHMOND)) { log("All Lancaster heirs were eliminated!") game.victory = "York won by eliminating all enemy heirs!" game.result = YORK @@ -939,12 +983,12 @@ function check_instant_victory() { function eliminate_block(who) { log(block_name(who) + " was eliminated.") game.flash += " " + block_name(who) + " was eliminated." - if (who === "Exeter/Y") { + if (who === B_EXETER_Y) { game.location[who] = NOWHERE ++game.killed_heirs[LANCASTER] return check_instant_victory() } - if (who === "Clarence/L") { + if (who === B_CLARENCE_L) { game.location[who] = NOWHERE ++game.killed_heirs[YORK] return check_instant_victory() @@ -963,12 +1007,12 @@ function eliminate_block(who) { } if (is_mercenary(who)) { switch (who) { - case "Welsh Mercenary": game.location[who] = POOL; break - case "Irish Mercenary": game.location[who] = "Ireland"; break - case "Burgundian Mercenary": game.location[who] = "Calais"; break - case "Calais Mercenary": game.location[who] = "Calais"; break - case "Scots Mercenary": game.location[who] = "Scotland"; break - case "French Mercenary": game.location[who] = "France"; break + case B_WELSH_MERCENARY: game.location[who] = POOL; break + case B_IRISH_MERCENARY: game.location[who] = IRELAND; break + case B_BURGUNDIAN_MERCENARY: game.location[who] = CALAIS; break + case B_CALAIS_MERCENARY: game.location[who] = CALAIS; break + case B_SCOTS_MERCENARY: game.location[who] = SCOTLAND; break + case B_FRENCH_MERCENARY: game.location[who] = FRANCE; break } game.steps[who] = block_max_steps(who) set_add(game.dead, who) @@ -989,7 +1033,7 @@ function reduce_block(who) { function count_attackers() { let count = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (is_attacker(b)) ++count return count @@ -997,7 +1041,7 @@ function count_attackers() { function count_defenders() { let count = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (is_defender(b)) ++count return count @@ -1005,7 +1049,7 @@ function count_defenders() { function count_blocks_exclude_mercenaries(where) { let count = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (game.location[b] === where && !is_mercenary(b) && !(game.reduced && set_has(game.reduced, b))) ++count return count @@ -1013,32 +1057,32 @@ function count_blocks_exclude_mercenaries(where) { function count_blocks(where) { let count = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (game.location[b] === where && !(game.reduced && set_has(game.reduced, b))) ++count return count } function add_blocks_exclude_mercenaries(list, where) { - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (game.location[b] === where && !is_mercenary(b) && !(game.reduced && set_has(game.reduced, b))) set_add(list, b) } function add_blocks(list, where) { - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (game.location[b] === where && !(game.reduced && set_has(game.reduced, b))) set_add(list, b) } function check_supply_penalty() { game.supply = [] - for (let where of AREALIST) { + for (let where = first_area; where <= last_area; ++where) { if (is_friendly_area(where)) { - if (where === "Calais" || where === "France") { + if (where === CALAIS || where === FRANCE) { if (count_blocks_exclude_mercenaries(where) > 4) add_blocks_exclude_mercenaries(game.supply, where) - } else if (where === "Ireland" || where === "Scotland") { + } else if (where === IRELAND || where === SCOTLAND) { if (count_blocks_exclude_mercenaries(where) > 2) add_blocks_exclude_mercenaries(game.supply, where) } else if (has_city(where)) { @@ -1055,12 +1099,12 @@ function check_supply_penalty() { function check_exile_limits() { game.exiles = [] - for (let where of AREALIST) { + for (let where = first_area; where <= last_area; ++where) { if (is_friendly_area(where)) { - if (where === "Calais" || where === "France") { + if (where === CALAIS || where === FRANCE) { if (count_blocks_exclude_mercenaries(where) > 4) add_blocks_exclude_mercenaries(game.exiles, where) - } else if (where === "Ireland" || where === "Scotland") { + } else if (where === IRELAND || where === SCOTLAND) { if (count_blocks_exclude_mercenaries(where) > 2) add_blocks_exclude_mercenaries(game.exiles, where) } @@ -1075,20 +1119,20 @@ function check_exile_limits() { // SETUP function find_block(owner, name) { - if (name in BLOCKS) - return name + if (name in block_index) + return block_index[name] name = name + "/" + owner[0] - if (name in BLOCKS) - return name + if (name in block_index) + return block_index[name] throw new Error("Block not found: " + name) } function deploy(who, where) { if (where === "Enemy") return - if (!(where in AREAS)) + if (!(where in area_index)) throw new Error("Area not found: " + where) - game.location[who] = where + game.location[who] = area_index[where] game.steps[who] = BLOCKS[who].steps } @@ -1101,7 +1145,7 @@ function deploy_york(name, where) { } function reset_blocks() { - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { game.location[b] = NOWHERE game.steps[b] = block_max_steps(b) } @@ -1112,8 +1156,8 @@ function setup_game() { game.campaign = 1 game.end_campaign = 3 - game.pretender = "York" - game.king = "Henry VI" + game.pretender = B_YORK + game.king = B_HENRY_VI deploy_lancaster("Henry VI", "Middlesex") deploy_lancaster("Somerset", "Dorset") @@ -1186,8 +1230,8 @@ function setup_kingmaker() { game.campaign = 2 game.end_campaign = 2 - game.pretender = "Henry VI" - game.king = "March" + game.pretender = B_HENRY_VI + game.king = B_MARCH deploy_york("March", "Middlesex") deploy_york("Gloucester", "South Yorks") @@ -1242,7 +1286,7 @@ function setup_kingmaker() { deploy_lancaster("Canterbury (church)", "Enemy") // Prisoner! - set_add(game.dead, "Henry VI") + set_add(game.dead, B_HENRY_VI) } function setup_richard_iii() { @@ -1250,8 +1294,8 @@ function setup_richard_iii() { game.campaign = 3 game.end_campaign = 3 - game.pretender = "Richmond" - game.king = "Gloucester" + game.pretender = B_RICHMOND + game.king = B_GLOUCESTER deploy_york("Gloucester", "Middlesex") deploy_york("Norfolk", "East Anglia") @@ -1298,11 +1342,11 @@ function setup_richard_iii() { // Kingmaker scenario special rule function free_henry_vi() { - if (set_has(game.dead, "Henry VI")) { - if ((game.active === LANCASTER && is_friendly_area("Middlesex")) || - (game.active === YORK && is_enemy_area("Middlesex"))) { + if (set_has(game.dead, B_HENRY_VI)) { + if ((game.active === LANCASTER && is_friendly_area(MIDDLESEX)) || + (game.active === YORK && is_enemy_area(MIDDLESEX))) { log("Henry VI rescued!") - set_delete(game.dead, "Henry VI") + set_delete(game.dead, B_HENRY_VI) } } } @@ -1312,6 +1356,7 @@ function free_henry_vi() { function start_campaign() { logbr() log(".h1 Campaign " + game.campaign) + logbr() // TODO: Use board game mulligan rules instead of automatically redealing? do { @@ -1328,6 +1373,7 @@ function start_game_turn() { logbr() log(".h1 Turn " + game.turn + " of campaign " + game.campaign + ".") + logbr() // Reset movement and attack tracking state reset_border_limits() @@ -1451,6 +1497,7 @@ function reveal_cards() { function start_player_turn() { logbr() log(".h2 " + game.active) + reset_border_limits() let lc = CARDS[game.l_card] let yc = CARDS[game.y_card] @@ -1517,7 +1564,7 @@ states.plague_event = { return view.prompt = "Plague: Waiting for " + game.active + " to choose a city." view.prompt = "Plague: Choose an enemy city area." gen_action(view, 'pass') - for (let where of AREALIST) + for (let where = first_area; where <= last_area; ++where) if (is_enemy_area(where) && has_city(where)) gen_action(view, 'area', where) }, @@ -1525,7 +1572,7 @@ states.plague_event = { log("Plague ravaged " + has_city(where) + "!") game.where = where game.plague = [] - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (game.location[b] === where) set_add(game.plague, b) game.active = ENEMY[game.active] @@ -1550,6 +1597,7 @@ states.apply_plague = { if (game.plague.length === 0) { delete game.plague game.active = ENEMY[game.active] + game.where = NOWHERE end_player_turn() } }, @@ -1568,7 +1616,7 @@ states.muster_event = { view.prompt = "Muster: Choose one friendly or vacant muster area." gen_action_undo(view) gen_action(view, 'end_action_phase') - for (let where of AREALIST) { + for (let where = first_area; where <= last_area; ++where) { if (is_friendly_or_vacant_area(where)) if (can_muster_to(where)) gen_action(view, 'area', where) @@ -1594,7 +1642,7 @@ states.muster_who = { view.prompt = "Muster: Move blocks to the designated muster area." gen_action_undo(view) gen_action(view, 'end_action_phase') - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (can_block_muster(b, game.where)) gen_action(view, 'block', b) }, @@ -1728,7 +1776,7 @@ states.action_phase = { gen_action_undo(view) gen_action(view, 'end_action_phase') - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { let from = game.location[b] if (can_recruit(b)) { if (game.moves > 0) @@ -1768,11 +1816,13 @@ states.action_phase = { if (game.moves > 0 && game.turn_log.length === 0 && game.recruit_log.length === 0) logp("did nothing.") - if (game.turn_log.length > 0) + if (game.turn_log.length > 0) { print_turn_log(game.active + " moved:") + } game.turn_log = game.recruit_log - if (game.turn_log.length > 0) + if (game.turn_log.length > 0) { print_turn_log(game.active + " recruited:") + } game.turn_log = null game.recruit_log = null @@ -1790,12 +1840,12 @@ states.recruit_where = { view.prompt = "Recruit " + block_name(game.who) + " where?" gen_action_undo(view) gen_action(view, 'block', game.who) - for (let to of AREALIST) + for (let to = first_area; to <= last_area; ++to) if (can_recruit_to(game.who, to)) gen_action(view, 'area', to) }, area: function (to) { - game.recruit_log.push([to]) + game.recruit_log.push(["#" + to]) --game.moves game.location[game.who] = to set_add(game.moved, game.who) @@ -1929,7 +1979,7 @@ function end_move() { if (game.distance > 0) { log_move_end() if (!set_has(game.activated, game.origin)) { - logp("activated " + game.origin + ".") + logp("activated #" + game.origin + ".") set_add(game.activated, game.origin) game.moves -- } @@ -1963,7 +2013,7 @@ states.battle_phase = { if (is_inactive_player(current)) return view.prompt = "Waiting for " + game.active + " to choose a battle." view.prompt = "Choose the next battle to fight!" - for (let where of AREALIST) + for (let where = first_area; where <= last_area; ++where) if (is_area_on_map(where) && is_contested_area(where)) gen_action(view, 'area', where) }, @@ -2000,7 +2050,7 @@ function resume_battle() { function end_battle() { if (game.turn_log && game.turn_log.length > 0) - print_turn_log("Retreated from " + game.where + ":") + print_turn_log("Retreated from #" + game.where + ":") free_henry_vi() game.flash = "" game.battle_round = 0 @@ -2018,7 +2068,7 @@ states.treason_event = { return view.prompt = "Treason: Waiting for " + game.active + " to choose a target." view.prompt = "Treason: Choose a target or pass." gen_action(view, 'pass') - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (game.active === game.attacker[game.where]) { if (is_defender(b) && can_defect(NOBODY, b)) { gen_action(view, 'battle_treachery', b) @@ -2051,7 +2101,7 @@ states.treason_event = { } function bring_on_reserves(owner, moved) { - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (block_owner(b) === owner && game.location[b] === game.where) { set_delete(game.reserves, b) if (moved) @@ -2065,10 +2115,10 @@ function bring_on_reserves(owner, moved) { function start_battle_round() { if (++game.battle_round <= 4) { if (game.turn_log && game.turn_log.length > 0) - print_turn_log("Retreated from " + game.where + ":") + print_turn_log("Retreated from #" + game.where + ":") game.turn_log = [] - log("~ Battle Round " + game.battle_round + " ~") + log(".h4 Battle Round " + game.battle_round) reset_border_limits() set_clear(game.moved) @@ -2107,7 +2157,7 @@ function pump_battle_round() { function filter_battle_blocks(ci, is_candidate) { let output = null - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (is_candidate(b) && !set_has(game.moved, b) && !set_has(game.dead, b)) { if (block_initiative(b) === ci) { if (!output) @@ -2286,7 +2336,7 @@ function can_block_fire(who) { function find_minor_heir(owner) { let candidate = NOBODY - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (block_owner(b) === owner && block_type(b) === 'heir' && game.location[b] === MINOR) if (candidate === NOBODY || BLOCKS[b].heir < BLOCKS[candidate].heir) candidate = b @@ -2296,7 +2346,7 @@ function find_minor_heir(owner) { function find_senior_heir(owner) { let candidate = NOBODY - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (block_owner(b) === owner && block_type(b) === 'heir' && !is_dead(b)) if (candidate === NOBODY || BLOCKS[b].heir < BLOCKS[candidate].heir) candidate = b @@ -2305,7 +2355,7 @@ function find_senior_heir(owner) { function find_next_king(owner) { let candidate = NOBODY - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (block_owner(b) === owner && block_type(b) === 'heir' && game.location[b] !== NOWHERE) if (candidate === NOBODY || BLOCKS[b].heir < BLOCKS[candidate].heir) candidate = b @@ -2314,7 +2364,7 @@ function find_next_king(owner) { function find_senior_heir_in_area(owner, where) { let candidate = NOBODY - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (block_owner(b) === owner && block_type(b) === 'heir' && game.location[b] === where) { if (is_battle_reserve(b)) continue @@ -2376,10 +2426,10 @@ states.battle_round = { gen_action(view, 'battle_treachery', game.king) if (can_attempt_treachery(game.pretender)) gen_action(view, 'battle_treachery', game.pretender) - if (can_attempt_treachery("Warwick/L")) - gen_action(view, 'battle_treachery', "Warwick/L") - if (can_attempt_treachery("Warwick/Y")) - gen_action(view, 'battle_treachery', "Warwick/Y") + if (can_attempt_treachery(B_WARWICK_L)) + gen_action(view, 'battle_treachery', B_WARWICK_L) + if (can_attempt_treachery(B_WARWICK_Y)) + gen_action(view, 'battle_treachery', B_WARWICK_Y) }, battle_fire: function (who) { fire_with_block(who) @@ -2417,7 +2467,7 @@ states.battle_charge = { return view.prompt = "Heir Charge: Waiting for " + game.active + " to choose a target." view.prompt = "Heir Charge: Choose a target." gen_action(view, 'undo') - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (game.active === game.attacker[game.where]) { if (is_defender(b)) { gen_action(view, 'battle_charge', b) @@ -2449,7 +2499,7 @@ states.battle_treachery = { return view.prompt = "Treachery: Waiting for " + game.active + " to choose a target." view.prompt = "Treachery: Choose a target." gen_action(view, 'undo') - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (game.active === game.attacker[game.where]) { if (is_defender(b) && can_defect(game.who, b)) { gen_action(view, 'battle_treachery', b) @@ -2511,11 +2561,11 @@ function apply_hit(who) { function list_victims(p) { let is_candidate = (p === game.attacker[game.where]) ? is_attacker : is_defender let max = 0 - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (is_candidate(b) && game.steps[b] > max) max = game.steps[b] let list = [] - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (is_candidate(b) && game.steps[b] === max) set_add(list, b) return list @@ -2565,7 +2615,7 @@ states.retreat_in_battle = { } else { game.flash = block_name(game.who) + " retreated." log_battle(game.flash) - game.turn_log.push([game.active, to]) + game.turn_log.push([game.active, "#" + to]) use_border(game.where, to) game.location[game.who] = to resume_battle() @@ -2598,7 +2648,7 @@ states.sea_retreat_to = { }, area: function (to) { let sea = game.location[game.who] - game.turn_log.push([game.active, sea, to]) + game.turn_log.push([game.active, "#" + sea, "#" + to]) game.flash = block_name(game.who) + " retreated." log_battle(game.flash) game.location[game.who] = to @@ -2619,7 +2669,7 @@ function goto_regroup() { game.active = game.attacker[game.where] if (is_enemy_area(game.where)) game.active = ENEMY[game.active] - log(game.active + " won the battle in " + game.where + "!") + log(game.active + " won the battle in #" + game.where + "!") game.state = 'regroup' game.turn_log = [] clear_undo() @@ -2632,7 +2682,7 @@ states.regroup = { view.prompt = "Regroup: Choose an army to move." gen_action_undo(view) gen_action(view, 'end_regroup') - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (game.location[b] === game.where) { if (game.active === game.piracy) { if (set_has(game.is_pirate, b)) @@ -2687,7 +2737,7 @@ states.regroup_to = { game.location[game.who] = to game.state = 'sea_regroup_to' } else { - game.turn_log.push([game.where, to]) + game.turn_log.push(["#"+game.where, "#"+to]) move_block(game.who, game.where, to) game.who = NOBODY game.state = 'regroup' @@ -2736,10 +2786,10 @@ function goto_supply_phase() { } function goto_execute_clarence() { - if (is_block_alive("Clarence/L")) { + if (is_block_alive(B_CLARENCE_L)) { game.active = LANCASTER game.state = 'execute_clarence' - game.who = "Clarence/L" + game.who = B_CLARENCE_L } else { goto_execute_exeter() } @@ -2755,7 +2805,7 @@ states.execute_clarence = { }, execute_clarence: function () { logp("executed Clarence.") - eliminate_block("Clarence/L") + eliminate_block(B_CLARENCE_L) game.who = NOBODY if (game.result) return goto_game_over() @@ -2768,10 +2818,10 @@ states.execute_clarence = { } function goto_execute_exeter() { - if (is_block_alive("Exeter/Y")) { + if (is_block_alive(B_EXETER_Y)) { game.active = YORK game.state = 'execute_exeter' - game.who = "Exeter/Y" + game.who = B_EXETER_Y } else { goto_enter_pretender_heir() } @@ -2787,7 +2837,7 @@ states.execute_exeter = { }, execute_exeter: function () { logp("executed Exeter.") - eliminate_block("Exeter/Y") + eliminate_block(B_EXETER_Y) game.who = NOBODY if (game.result) return goto_game_over() @@ -2815,7 +2865,7 @@ states.enter_pretender_heir = { if (is_inactive_player(current)) return view.prompt = "Waiting for " + game.active + " to enter pretender heirs." view.prompt = "Death of an Heir: Enter " + block_name(game.who) + " in an exile area." - for (let where of AREALIST) + for (let where = first_area; where <= last_area; ++where) if (is_pretender_exile_area(where)) gen_action(view, 'area', where) }, @@ -2856,7 +2906,7 @@ states.supply_limits_pretender = { }, block: function (who) { push_undo() - game.turn_log.push([game.location[who]]) + game.turn_log.push(["#" + game.location[who]]) set_add(game.reduced, who) reduce_block(who) check_supply_penalty() @@ -2890,7 +2940,7 @@ states.enter_royal_heir = { return view.prompt = "Waiting for " + game.active + " to enter royal heirs." view.prompt = "Death of an Heir: Enter " + block_name(game.who) + " in a Crown area." let can_enter = false - for (let where of AREALIST) { + for (let where = first_area; where <= last_area; ++where) { if (is_crown_area(where) && is_friendly_or_vacant_area(where)) { gen_action(view, 'area', where) can_enter = true @@ -2940,7 +2990,7 @@ states.supply_limits_king = { }, block: function (who) { push_undo() - game.turn_log.push([game.location[who]]) + game.turn_log.push(["#" + game.location[who]]) set_add(game.reduced, who) reduce_block(who) check_supply_penalty() @@ -2967,45 +3017,45 @@ function goto_political_turn() { game.turn_log = [] // Levies disband - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (!is_land_area(game.location[b])) continue switch (block_type(b)) { case 'bombard': case 'levies': case 'rebel': - game.turn_log.push([game.location[b]]) + game.turn_log.push(["#" + game.location[b]]) disband(b) break case 'mercenaries': switch (b) { - case "Welsh Mercenary": - game.turn_log.push([game.location[b]]) + case B_WELSH_MERCENARY: + game.turn_log.push(["#" + game.location[b]]) disband(b) break - case "Irish Mercenary": - if (game.location[b] !== "Ireland") { - game.turn_log.push([game.location[b], "Ireland"]) - game.location[b] = "Ireland" + case B_IRISH_MERCENARY: + if (game.location[b] !== IRELAND) { + game.turn_log.push(["#" + game.location[b], "#" + IRELAND]) + game.location[b] = IRELAND } break - case "Burgundian Mercenary": - case "Calais Mercenary": - if (game.location[b] !== "Calais") { - game.turn_log.push([game.location[b], "Calais"]) - game.location[b] = "Calais" + case B_BURGUNDIAN_MERCENARY: + case B_CALAIS_MERCENARY: + if (game.location[b] !== CALAIS) { + game.turn_log.push(["#" + game.location[b], "#" + CALAIS]) + game.location[b] = CALAIS } break - case "Scots Mercenary": - if (game.location[b] !== "Scotland") { - game.turn_log.push([game.location[b], "Scotland"]) - game.location[b] = "Scotland" + case B_SCOTS_MERCENARY: + if (game.location[b] !== SCOTLAND) { + game.turn_log.push(["#" + game.location[b], "#" + SCOTLAND]) + game.location[b] = SCOTLAND } break - case "French Mercenary": - if (game.location[b] !== "France") { - game.turn_log.push([game.location[b], "France"]) - game.location[b] = "France" + case B_FRENCH_MERCENARY: + if (game.location[b] !== FRANCE) { + game.turn_log.push(["#" + game.location[b], "#" + FRANCE]) + game.location[b] = FRANCE } break } @@ -3024,13 +3074,13 @@ function goto_political_turn() { if (l_count > y_count && block_owner(game.king) === YORK) { game.king = find_senior_heir(LANCASTER) game.pretender = find_senior_heir(YORK) - log(game.king + " usurped the throne!") + log(block_name(game.king) + " usurped the throne!") } else if (y_count > l_count && block_owner(game.king) === LANCASTER) { game.king = find_senior_heir(YORK) game.pretender = find_senior_heir(LANCASTER) - log(game.king + " usurped the throne!") + log(block_name(game.king) + " usurped the throne!") } else { - log(game.king + " remained king.") + log(block_name(game.king) + " remained king.") } // Game ends after last Usurpation check @@ -3048,7 +3098,7 @@ function goto_pretender_goes_home() { game.state = 'pretender_goes_home' game.turn_log = [] let choices = false - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (block_owner(b) === game.active && is_block_on_map(b)) if (go_home_if_possible(b)) choices = true @@ -3066,7 +3116,7 @@ states.pretender_goes_home = { return view.prompt = "Waiting for the Pretender to go to exile." gen_action_undo(view) let done = true - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (block_owner(b) === game.active && is_block_on_map(b) && !set_has(game.moved, b)) { if (!is_in_exile(b)) { if (is_heir(b)) { @@ -3083,7 +3133,7 @@ states.pretender_goes_home = { } if (done) { view.prompt = "Pretender Goes Home: You may move nobles to another home." - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (block_owner(b) === game.active && is_block_on_map(b) && !set_has(game.moved, b)) { if (!is_in_exile(b)) { if (is_at_home(b)) { @@ -3121,7 +3171,7 @@ states.pretender_goes_home_to = { else view.prompt = "Pretender Goes Home: Move " + block_name(game.who) + " to home." gen_action(view, 'block', game.who) - for (let where of AREALIST) { + for (let where = first_area; where <= last_area; ++where) { if (where !== game.location[game.who]) { if (is_heir(game.who)) { if (is_friendly_exile_area(where)) @@ -3134,9 +3184,9 @@ states.pretender_goes_home_to = { }, area: function (to) { if (is_exile_area(to)) - game.turn_log.push([block_name(game.who), to]) // TODO: "Exile"? + game.turn_log.push([block_name(game.who), "#" + to]) // TODO: "Exile"? else - game.turn_log.push([block_name(game.who), to]) // TODO: "Home"? + game.turn_log.push([block_name(game.who), "#" + to]) // TODO: "Home"? set_add(game.moved, game.who) game.location[game.who] = to game.who = NOBODY @@ -3171,7 +3221,7 @@ states.exile_limits_pretender = { block: function (who) { push_undo() let where = game.location[who] - logp("disbanded in " + where + ".") + logp("disbanded in #" + where + ".") game.exiles = game.exiles.filter(b => game.location[b] !== where) disband(who) }, @@ -3188,7 +3238,7 @@ function goto_king_goes_home() { game.state = 'king_goes_home' game.turn_log = [] let choices = false - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (block_owner(b) === game.active && is_block_on_map(b)) if (go_home_if_possible(b)) choices = true @@ -3206,7 +3256,7 @@ states.king_goes_home = { return view.prompt = "Waiting for the King to go home." gen_action_undo(view) let done = true - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (block_owner(b) === game.active && is_block_on_map(b) && !set_has(game.moved, b)) { if (!is_in_exile(b)) { if (!is_at_home(b)) { @@ -3220,7 +3270,7 @@ states.king_goes_home = { } if (done) { view.prompt = "King Goes Home: You may move nobles and heirs to another home." - for (let b of BLOCKLIST) { + for (let b = 0; b < block_count; ++b) { if (block_owner(b) === game.active && is_block_on_map(b) && !set_has(game.moved, b)) { if (!is_in_exile(b)) { if (is_at_home(b)) { @@ -3255,13 +3305,13 @@ states.king_goes_home_to = { return view.prompt = "Waiting for the King to go home." view.prompt = "King Goes Home: Move " + block_name(game.who) + " to home." gen_action(view, 'block', game.who) - for (let where of AREALIST) + for (let where = first_area; where <= last_area; ++where) if (where !== game.location[game.who]) if (is_available_home_for(where, game.who)) gen_action(view, 'area', where) }, area: function (to) { - game.turn_log.push([block_name(game.who), to]) // TODO: "Home"? + game.turn_log.push([block_name(game.who), "#" + to]) // TODO: "Home"? set_add(game.moved, game.who) game.location[game.who] = to game.who = NOBODY @@ -3296,7 +3346,7 @@ states.exile_limits_king = { block: function (who) { push_undo() let where = game.location[who] - logp("disbanded in " + where + ".") + logp("disbanded in #" + where + ".") game.exiles = game.exiles.filter(b => game.location[b] !== where) disband(who) }, @@ -3309,7 +3359,7 @@ states.exile_limits_king = { function end_political_turn() { // Campaign reset set_clear(game.dead) - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) game.steps[b] = block_max_steps(b) ++game.campaign @@ -3343,11 +3393,11 @@ function make_battle_view() { flash: game.flash } - battle.title = game.attacker[game.where] + " attacks " + game.where + battle.title = game.attacker[game.where] + " attacks " + AREAS[game.where].name battle.title += " \u2014 round " + game.battle_round + " of 4" function fill_cell(cell, owner, fn) { - for (let b of BLOCKLIST) + for (let b = 0; b < block_count; ++b) if (game.location[b] === game.where & block_owner(b) === owner && !set_has(game.dead, b) && fn(b)) cell.push(b) } @@ -3366,16 +3416,20 @@ exports.setup = function (seed, scenario, options) { log: [], undo: [], + state: null, active: null, + turn: 0, moves: 0, who: NOBODY, where: NOWHERE, show_cards: false, killed_heirs: { Lancaster: 0, York: 0 }, + king: NOBODY, + pretender: NOBODY, - location: {}, - steps: {}, + location: [], + steps: [], moved: [], dead: [], reserves: [], @@ -3399,7 +3453,10 @@ exports.setup = function (seed, scenario, options) { else throw new Error("Unknown scenario:", scenario) + logbr() log(".h1 " + scenario) + logbr() + start_campaign() return game } @@ -3453,6 +3510,7 @@ exports.view = function(state, current) { location: game.location, steps: game.steps, moved: game.moved, + dead: game.dead, battle: null, prompt: null, } -- cgit v1.2.3