summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cards.js328
-rw-r--r--data.js1155
-rw-r--r--play.html1
-rw-r--r--play.js131
-rw-r--r--rules.js1248
5 files changed, 1669 insertions, 1194 deletions
diff --git a/cards.js b/cards.js
deleted file mode 100644
index cdcbd3b..0000000
--- a/cards.js
+++ /dev/null
@@ -1,328 +0,0 @@
-const CARD_3_OPS = { title: "3 OPS", type: 'ops', count: 3 }
-const CARD_2_OPS = { title: "2 OPS", type: 'ops', count: 2 }
-const CARD_1_OPS = { title: "1 OPS", type: 'ops', count: 1 }
-
-const CARDS = {
- 1:CARD_3_OPS, 2:CARD_3_OPS, 3:CARD_3_OPS, 4:CARD_3_OPS, 5:CARD_3_OPS,
- 6:CARD_3_OPS, 7:CARD_3_OPS, 8:CARD_3_OPS, 9:CARD_3_OPS, 10:CARD_3_OPS,
- 11:CARD_3_OPS, 12:CARD_3_OPS, 13:CARD_3_OPS, 14:CARD_3_OPS, 15:CARD_3_OPS,
- 16:CARD_3_OPS, 17:CARD_3_OPS, 18:CARD_3_OPS, 19:CARD_3_OPS, 20:CARD_3_OPS,
- 21:CARD_3_OPS, 22:CARD_3_OPS,
-
- 23:CARD_2_OPS, 24:CARD_2_OPS, 25:CARD_2_OPS, 26:CARD_2_OPS, 27:CARD_2_OPS,
- 28:CARD_2_OPS, 29:CARD_2_OPS, 30:CARD_2_OPS, 31:CARD_2_OPS, 32:CARD_2_OPS,
- 33:CARD_2_OPS, 34:CARD_2_OPS, 35:CARD_2_OPS, 36:CARD_2_OPS, 37:CARD_2_OPS,
- 38:CARD_2_OPS, 39:CARD_2_OPS, 40:CARD_2_OPS, 41:CARD_2_OPS, 42:CARD_2_OPS,
- 43:CARD_2_OPS, 44:CARD_2_OPS,
-
- 45:CARD_1_OPS, 46:CARD_1_OPS, 47:CARD_1_OPS, 48:CARD_1_OPS, 49:CARD_1_OPS,
- 50:CARD_1_OPS, 51:CARD_1_OPS, 52:CARD_1_OPS, 53:CARD_1_OPS, 54:CARD_1_OPS,
- 55:CARD_1_OPS, 56:CARD_1_OPS, 57:CARD_1_OPS, 58:CARD_1_OPS, 59:CARD_1_OPS,
- 60:CARD_1_OPS, 61:CARD_1_OPS, 62:CARD_1_OPS, 63:CARD_1_OPS, 64:CARD_1_OPS,
- 65:CARD_1_OPS, 66:CARD_1_OPS,
-
- 67: {
- title: "Minor Campaign",
- type: 'campaign',
- count: 2
- },
-
- 68: {
- title: "Minor Campaign",
- type: 'campaign',
- count: 2
- },
-
- 69: {
- title: "Minor Campaign",
- type: 'campaign',
- count: 2
- },
-
- 70: {
- title: "Major Campaign",
- type: 'campaign',
- count: 3
- },
-
- 71: {
- title: "North's Government Falls - The War Ends in 1779",
- type: 'mandatory-event',
- event: 'the_war_ends',
- year: 1779
- },
-
- 72: {
- title: "North's Government Falls - The War Ends in 1780",
- type: 'mandatory-event',
- event: 'the_war_ends',
- year: 1780
- },
-
- 73: {
- title: "North's Government Falls - The War Ends in 1781",
- type: 'mandatory-event',
- event: 'the_war_ends',
- year: 1781
- },
-
- 74: {
- title: "North's Government Falls - The War Ends in 1782",
- type: 'mandatory-event',
- event: 'the_war_ends',
- year: 1782
- },
-
- 75: {
- title: "North's Government Falls - The War Ends in 1783",
- type: 'mandatory-event',
- event: 'the_war_ends',
- year: 1783
- },
-
- 76: {
- title: "Henry Know Continental Artillery Commander",
- type: 'american-battle',
- },
-
- 77: {
- title: "Jane McCrea Indian Atrocity Sparks Outrage",
- type: 'american-event',
- event: 'place_american_pc',
- count: 2,
- once: true,
- },
-
- 78: {
- title: "Iroquois Uprising!",
- type: 'british-event',
- event: 'remove_american_pc_from_non_port',
- where: [ 'NH', 'NY', 'PA' ],
- count: 2,
- },
-
- 79: {
- title: "Joseph Brant Leads an Iroquois Raid",
- type: 'british-event',
- event: 'remove_american_pc_from_non_port',
- where: [ 'NH', 'NY', 'PA' ],
- count: 2,
- },
-
- 80: {
- title: "Lt. Colonel Simcoe's Queen's Rangers",
- type: 'british-event',
- event: 'remove_american_pc_within_two_spaces_of_a_british_general',
- count: 2,
- },
-
- 81: {
- title: "D'Estaing Sails for the Caribbean",
- type: 'british-event',
- when: 'after_french_alliance',
- event: 'remove_french_navy',
- },
-
- 82: {
- title: "Banastre Tarleton's Waxhaws Massacre",
- type: 'british-event-or-battle',
- event: 'remove_american_cu',
- },
-
- 83: {
- title: "Lord George Germaine Offers Royal Amnesty",
- type: 'british-event',
- event: 'remove_american_pc',
- count: 2,
- },
-
- 84: {
- title: "George Rogers Clark Leads a Western Offensive",
- type: 'american-event',
- event: 'remove_random_british_card',
- },
-
- 85: {
- title: "Don Bernardo Galvez Captures Pensacola",
- type: 'american-event',
- when: 'european_war_in_effect',
- event: 'remove_british_cu',
- count: 2,
- },
-
- 86: {
- title: "Baron von Steuben Trains the Continental Army",
- type: 'american-event',
- event: 'baron_von_steuben_trains_the_continental_army',
- },
-
- 87: {
- title: "Lord North Offers a Royal Amnesty",
- type: 'british-event',
- event: 'remove_american_pc',
- count: 4,
- once: true,
- },
-
- 88: {
- title: "The Swamp Fox, Francis Marion",
- type: 'american-event',
- event: 'remove_british_pc_from',
- where: [ 'NC', 'SC', 'GA' ],
- count: 2,
- },
-
- 89: {
- title: "The Gamecock: Thomas Sumter",
- type: 'american-event',
- event: 'remove_british_pc_from',
- where: [ 'NC', 'SC', 'GA' ],
- count: 2,
- },
-
- 90: {
- title: "Josiah Martin Rallies North Carolina Loyalists",
- type: 'british-event',
- event: 'remove_american_pc_from',
- where: [ 'NC' ],
- count: 2,
- },
-
- 91: {
- title: 'Thomas Paine Publishes Pamphlets "Common Sense" and "The American Crisis"',
- type: 'american-event',
- event: 'place_american_pc_in',
- where: [ 'NH', 'NY', 'MA', 'CT', 'RI', 'PA', 'NJ', 'MD', 'DE', 'VA', 'NC', 'SC', 'GA' ],
- count: 3,
- once: true,
- },
-
- 92: {
- title: "Nathan Hale, American Martyr",
- type: 'american-event',
- event: 'place_american_pc',
- count: 2,
- once: true,
- },
-
- 93: {
- title: "John Glover's Marblehead Regiment",
- type: 'american-event',
- event: 'john_glovers_marblehead_regiment',
- },
-
- 94: {
- title: "Pennsylvania and New Jersey Line Mutinies",
- type: 'british-event',
- event: 'pennsylvania_and_new_jersey_line_mutinies',
- },
-
- 95: {
- title: "William Pitt Urges Peace Talks",
- type: 'british-event',
- when: 'before_french_alliance',
- event: 'remove_american_pc',
- count: 2,
- reshuffle: 'if_played',
- },
-
- 96: {
- title: "Hortelez et Cie: Clandestine French and Spanish Aid",
- type: 'american-event',
- when: 'before_french_alliance',
- event: 'advance_french_alliance',
- reshuffle: 'if_discarded',
- count: 2,
- },
-
- 97: {
- title: "Admiral Suffren Wins a Naval Victory",
- type: 'american-event',
- when: 'after_french_alliance',
- event: 'remove_random_british_card',
- },
-
- 98: {
- title: '"Mad" Anthony Wayne',
- type: 'american-battle',
- },
-
- 99: {
- title: "Declaration of Independence",
- type: 'mandatory-event',
- event: 'declaration_of_independence',
- once: true,
- reshuffle: 'if_played',
- tournament: true,
- },
-
- 100: {
- title: "Benedict Arnold's Treason Undermines the Patriot Cause",
- type: 'british-battle',
- event: 'remove_benedict_arnold',
- once: true,
- },
-
- 101: {
- title: "Benjamin Franklin: Minister to France",
- type: 'mandatory-event',
- event: 'advance_french_alliance',
- count: 4,
- once: true,
- tournament: true,
- },
-
- 102: {
- title: "Admiral Rodney Captures St. Eustatius",
- type: 'british-event',
- event: 'remove_random_american_card',
- when: 'european_war_in_effect',
- once: true,
- },
-
- 103: {
- title: "Thaddeus Kosciuszco Constructs Engineering Works",
- type: 'american-battle',
- },
-
- 104: {
- title: "Light Horse Harry Lee",
- type: 'american-battle',
- },
-
- 105: {
- title: "Morgan's Riflemen",
- type: 'american-battle',
- },
-
- 106: {
- title: "John Paul Jones' Shipping Raids",
- type: 'american-event',
- event: 'remove_random_british_card',
- },
-
- 107: {
- title: "British Light Infantry",
- type: 'british-battle',
- },
-
- 108: {
- title: "Lord Sandwich Coastal Raids",
- type: 'british-event',
- event: 'lord_sandwich_coastal_raids',
- },
-
- 109: {
- title: "Edward Bancroft, British Double Agent",
- type: 'british-event',
- event: 'remove_random_american_card',
- },
-
- 110: {
- title: "Hessian Infantry Bayonet Charge",
- type: 'british-battle',
- },
-}
-
-if (typeof module == 'object')
- module.exports = CARDS;
diff --git a/data.js b/data.js
index 7e5c8f4..011d0fd 100644
--- a/data.js
+++ b/data.js
@@ -1,360 +1,823 @@
"use strict"
-let BOXES = {}
-let BLOCKADE = {}
-let COLONIES = {}
-let SPACES = {}
-let PATH_INDEX = {}
-let PATH_NAME
-let PATH_TYPE
-
-const GENERALS = {
- Arnold: { owner: "American", strategy: 1, battle: 3, agility: 2, bonus: false },
- Gates: { owner: "American", strategy: 2, battle: 2, agility: 1, bonus: false },
- Greene: { owner: "American", strategy: 1, battle: 4, agility: 2, bonus: true },
- Lafayette: { owner: "American", strategy: 1, battle: 2, agility: 1, bonus: false },
- Lee: { owner: "American", strategy: 2, battle: 1, agility: 1, bonus: false },
- Lincoln: { owner: "American", strategy: 2, battle: 1, agility: 1, bonus: false },
- Washington: { owner: "American", strategy: 1, battle: 5, agility: 2, bonus: true },
-
- Burgoyne: { owner: "British", strategy: 2, battle: 2, agility: 1, bonus: false },
- Carleton: { owner: "British", strategy: 3, battle: 3, agility: 2, bonus: false },
- Clinton: { owner: "British", strategy: 3, battle: 4, agility: 2, bonus: false },
- Cornwallis: { owner: "British", strategy: 2, battle: 4, agility: 2, bonus: false },
- Howe: { owner: "British", strategy: 3, battle: 6, agility: 3, bonus: false },
-
- Rochambeau: { owner: "French", strategy: 2, battle: 4, agility: 2, bonus: false },
+var data
+
+;(function () {
+
+function array_insert(array, index, item) {
+ for (let i = array.length; i > index; --i)
+ array[i] = array[i - 1]
+ array[index] = item
}
-function getEdge(A, B) {
- if (A > B)
- return PATHS[B + "/" + A]
- return PATHS[A + "/" + B]
+function set_add(set, item) {
+ let a = 0
+ let b = set.length - 1
+ while (a <= b) {
+ let m = (a + b) >> 1
+ let x = set[m]
+ if (item < x)
+ b = m - 1
+ else if (item > x)
+ a = m + 1
+ else
+ return
+ }
+ array_insert(set, a, item)
}
-;(function () {
- let info = {}
+function make_index_from_list(list) {
+ let map = {}
+ for (let i = 0; i < list.length; ++i)
+ map[list[i]] = i
+ return map
+}
- function box(category, A, x, y) {
- x += 40
- y += 36
- BOXES[A] = { name: A, category: category, x: x, y: y }
- }
+const BOXES = {}
- function space(colony, name, x, y, type) {
- x += 40
- y += 36
- if (type == "winter-quarters") {
- x += 1
- y += 1
- }
- if (!(colony in COLONIES))
- COLONIES[colony] = []
- COLONIES[colony].push(name)
- SPACES[name] = { name: name, colony: colony, type: type, port: false, x: x, y: y, exits: [] }
- }
+// === GENERALS ===
- function line(A, B, type) {
- if (A > B) {
- let C = B
- B = A
- A = C
- }
- let id = A + "/" + B
- SPACES[A].exits.push(B)
- SPACES[B].exits.push(A)
- info[id] = type
- if (!(A in PATH_INDEX))
- PATH_INDEX[A] = {}
- if (!(B in PATH_INDEX))
- PATH_INDEX[B] = {}
- PATH_INDEX[A][B] = -1
- PATH_INDEX[B][A] = -1
- }
+const generals = [
+ { name: "Arnold", owner: "American", strategy: 1, battle: 3, agility: 2, bonus: false },
+ { name: "Gates", owner: "American", strategy: 2, battle: 2, agility: 1, bonus: false },
+ { name: "Greene", owner: "American", strategy: 1, battle: 4, agility: 2, bonus: true },
+ { name: "Lafayette", owner: "American", strategy: 1, battle: 2, agility: 1, bonus: false },
+ { name: "Lee", owner: "American", strategy: 2, battle: 1, agility: 1, bonus: false },
+ { name: "Lincoln", owner: "American", strategy: 2, battle: 1, agility: 1, bonus: false },
+ { name: "Washington", owner: "American", strategy: 1, battle: 5, agility: 2, bonus: true },
- space("CA", "Fort Detroit", 55, 673, "regular-space")
- space("CA", "Montreal", 1000, 159, "fortified-port")
- space("CA", "Quebec", 1122, 57, "fortified-port")
- space("CT", "Hartford", 1183, 749, "winter-quarters")
- space("CT", "New Haven", 1088, 736, "regular-space")
- space("DE", "Wilmington DE", 885, 1079, "winter-quarters")
- space("GA", "Augusta", 140, 2060, "regular-space")
- space("GA", "Savannah", 227, 2271, "regular-space")
- space("GA", "St. Mary's", 180, 2394, "regular-space")
- space("MA", "Barnstable", 1394, 705, "regular-space")
- space("MA", "Boston", 1327, 592, "winter-quarters")
- space("MA", "Falmouth", 1334, 410, "regular-space")
- space("MA", "Lexington Concord", 1218, 628, "regular-space")
- space("MA", "Springfield", 1095, 624, "winter-quarters")
- space("MD", "Baltimore", 769, 1105, "winter-quarters")
- space("MD", "Fort Cumberland", 446, 1098, "regular-space")
- space("MD", "Frederick Town", 646, 1097, "regular-space")
- space("NC", "Charlotte", 215, 1653, "regular-space")
- space("NC", "Gilbert Town", 123, 1568, "regular-space")
- space("NC", "Hillsboro", 484, 1582, "regular-space")
- space("NC", "New Bern", 764, 1670, "regular-space")
- space("NC", "Salem", 308, 1561, "regular-space")
- space("NC", "Wake (Raleigh)", 606, 1671, "regular-space")
- space("NC", "Wilmington NC", 676, 1816, "regular-space")
- space("NH", "Brattleboro", 1102, 486, "regular-space")
- space("NH", "Concord", 1214, 460, "regular-space")
- space("NH", "Norwich", 1125, 342, "winter-quarters")
- space("NJ", "Monmouth", 998, 1074, "regular-space")
- space("NJ", "Morristown", 864, 831, "regular-space")
- space("NJ", "New Brunswick", 961, 942, "regular-space")
- space("NY", "Albany", 965, 626, "winter-quarters")
- space("NY", "Fort Niagara", 461, 526, "regular-space")
- space("NY", "Fort Stanwix", 877, 483, "winter-quarters")
- space("NY", "Genesee", 668, 582, "regular-space")
- space("NY", "Long Island", 1147, 868, "regular-space")
- space("NY", "New York", 1024, 838, "winter-quarters")
- space("NY", "Oswego", 768, 424, "regular-space")
- space("NY", "Saratoga", 992, 496, "regular-space")
- space("NY", "Ticonderoga", 993, 355, "winter-quarters")
- space("NY", "Westchester", 955, 747, "regular-space")
- space("PA", "Bassett Town", 147, 984, "regular-space")
- space("PA", "Harrisburg", 555, 880, "regular-space")
- space("PA", "Philadelphia", 846, 961, "fortified-port")
- space("PA", "Pittsburgh", 439, 958, "winter-quarters")
- space("PA", "Reading", 684, 900, "regular-space")
- space("PA", "Wyoming Valley", 685, 747, "winter-quarters")
- space("PA", "York", 589, 992, "regular-space")
- space("RI", "Newport", 1289, 736, "winter-quarters")
- space("SC", "Camden", 346, 1773, "regular-space")
- space("SC", "Charleston", 444, 2050, "fortified-port")
- space("SC", "Cheraw", 496, 1795, "regular-space")
- space("SC", "Eutaw Springs", 404, 1905, "regular-space")
- space("SC", "Fort Prince George", 74, 1773, "regular-space")
- space("SC", "Georgetown", 574, 1915, "regular-space")
- space("SC", "Ninety Six", 222, 1932, "regular-space")
- space("VA", "Abingdon", 61, 1451, "regular-space")
- space("VA", "Alexandria", 653, 1217, "winter-quarters")
- space("VA", "Charlottesville", 434, 1257, "winter-quarters")
- space("VA", "Fincastle", 349, 1402, "regular-space")
- space("VA", "Fort Chiswell", 212, 1408, "regular-space")
- space("VA", "Lynch's Ferry", 486, 1444, "regular-space")
- space("VA", "Norfolk", 781, 1450, "winter-quarters")
- space("VA", "Petersburg", 634, 1446, "regular-space")
- space("VA", "Point Pleasant", 132, 1218, "regular-space")
- space("VA", "Richmond", 608, 1323, "winter-quarters")
- space("VA", "Yorktown", 767, 1317, "regular-space")
-
- box("SEA", "Sea1", 1277, 65, "blockade")
- box("SEA", "Sea2", 1469, 514, "blockade")
- box("SEA", "Sea3", 1213, 981, "blockade")
- box("SEA", "Sea4", 1058, 1174, "blockade")
- box("SEA", "Sea5", 947, 1395, "blockade")
- box("SEA", "Sea6", 637, 2027, "blockade")
- box("SEA", "Sea7", 359, 2358, "blockade")
-
- box("HOLDING", "Continental Congress Dispersed", 545, 350)
- box("HOLDING", "Captured Generals", 1463, 80)
- box("HOLDING", "British Leader Reinforcements", 1425, 1745)
- box("HOLDING", "American Leader Reinforcements", 400, 310)
- box("HOLDING", "French Reinforcements", 150, 340)
-
- box("ALLIANCE", "French Alliance Track 0", 898, 2240)
- box("ALLIANCE", "French Alliance Track 1", 964, 2240)
- box("ALLIANCE", "French Alliance Track 2", 1030, 2240)
- box("ALLIANCE", "French Alliance Track 3", 1096, 2240)
- box("ALLIANCE", "French Alliance Track 4", 1162, 2240)
- box("ALLIANCE", "French Alliance Track 5", 1228, 2240)
- box("ALLIANCE", "French Alliance Track 6", 1294, 2240)
- box("ALLIANCE", "French Alliance Track 7", 1360, 2240)
- box("ALLIANCE", "French Alliance Track 8", 1426, 2240)
- box("ALLIANCE", "French Alliance Track 9", 1492, 2240)
-
- box("TURN", "Game Turn 1775", 898, 2370)
- box("TURN", "Game Turn 1776", 964, 2370)
- box("TURN", "Game Turn 1777", 1030, 2370)
- box("TURN", "Game Turn 1778", 1096, 2370)
- box("TURN", "Game Turn 1779", 1162, 2370)
- box("TURN", "Game Turn 1780", 1228, 2370)
- box("TURN", "Game Turn 1781", 1294, 2370)
- box("TURN", "Game Turn 1782", 1360, 2370)
- box("TURN", "Game Turn 1783", 1426, 2370)
-
- box("CONTROL", "CA", 1376, 967)
- box("CONTROL", "NH", 1458, 1006)
- box("CONTROL", "NY", 1375, 1081)
- box("CONTROL", "MA", 1491, 1081)
- box("CONTROL", "CT", 1455, 1156)
- box("CONTROL", "RI", 1533, 1156)
- box("CONTROL", "PA", 1299, 1194)
- box("CONTROL", "NJ", 1376, 1189)
- box("CONTROL", "MD", 1299, 1266)
- box("CONTROL", "DE", 1376, 1266)
- box("CONTROL", "VA", 1241, 1337)
- box("CONTROL", "NC", 1260, 1407)
- box("CONTROL", "SC", 1212, 1479)
- box("CONTROL", "GA", 1164, 1550)
-
- function wilderness(A, B) {
- line(A, B, "wilderness")
- }
- function path(A, B) {
- line(A, B, "path")
- }
- function sea(space, zone) {
- SPACES[space].port = true
- BLOCKADE[space] = zone
- }
+ { name: "Rochambeau", owner: "French", strategy: 2, battle: 4, agility: 2, bonus: false },
- wilderness("Quebec", "Falmouth")
- wilderness("Fort Detroit", "Bassett Town")
- wilderness("Bassett Town", "Point Pleasant")
-
- path("Quebec", "Montreal")
- path("Montreal", "Ticonderoga")
- path("Montreal", "Oswego")
- path("Oswego", "Fort Niagara")
- path("Oswego", "Fort Stanwix")
- path("Fort Niagara", "Fort Detroit")
- path("Fort Niagara", "Genesee")
- path("Ticonderoga", "Norwich")
- path("Ticonderoga", "Saratoga")
- path("Norwich", "Brattleboro")
- path("Norwich", "Concord")
- path("Concord", "Falmouth")
- path("Concord", "Brattleboro")
- path("Saratoga", "Brattleboro")
- path("Saratoga", "Albany")
- path("Brattleboro", "Springfield")
- path("Fort Stanwix", "Albany")
- path("Albany", "Springfield")
- path("Lexington Concord", "Springfield")
- path("Lexington Concord", "Boston")
- path("Falmouth", "Boston")
- path("Barnstable", "Boston")
- path("Newport", "Boston")
- path("Newport", "Hartford")
- path("Springfield", "Hartford")
- path("New Haven", "Hartford")
- path("New Haven", "New York")
- path("New Haven", "Westchester")
- path("New York", "Westchester")
- path("New York", "Long Island")
- path("Newport", "Lexington Concord")
- path("Albany", "Westchester")
- path("Fort Stanwix", "Genesee")
- path("Genesee", "Wyoming Valley")
- path("Fort Niagara", "Pittsburgh")
- path("Morristown", "Westchester")
- path("Morristown", "Wyoming Valley")
- path("Morristown", "Reading")
- path("Morristown", "New Brunswick")
- path("Morristown", "New York")
- path("New York", "New Brunswick")
- path("Monmouth", "New Brunswick")
- path("Philadelphia", "New Brunswick")
- path("Monmouth", "Philadelphia")
- path("Reading", "Philadelphia")
- path("Wyoming Valley", "Reading")
- path("Wilmington DE", "Philadelphia")
- path("Wilmington DE", "Baltimore")
- path("Bassett Town", "Pittsburgh")
- path("Fort Cumberland", "Pittsburgh")
- path("Harrisburg", "Pittsburgh")
- path("Harrisburg", "York")
- path("Reading", "York")
- path("Harrisburg", "Reading")
- path("Frederick Town", "York")
- path("Frederick Town", "Baltimore")
- path("Frederick Town", "Alexandria")
- path("Frederick Town", "Charlottesville")
- path("Frederick Town", "Fort Cumberland")
- path("Richmond", "Alexandria")
- path("Richmond", "Yorktown")
- path("Richmond", "Petersburg")
- path("Richmond", "Lynch's Ferry")
- path("Richmond", "Charlottesville")
- path("Richmond", "Norfolk")
- path("Fincastle", "Charlottesville")
- path("Fincastle", "Lynch's Ferry")
- path("Fincastle", "Fort Chiswell")
- path("Point Pleasant", "Fort Chiswell")
- path("Abingdon", "Fort Chiswell")
- path("Abingdon", "Gilbert Town")
- path("Fort Prince George", "Gilbert Town")
- path("Charlotte", "Gilbert Town")
- path("Charlotte", "Salem")
- path("Hillsboro", "Salem")
- path("Cheraw", "Salem")
- path("Lynch's Ferry", "Salem")
- path("Charlotte", "Camden")
- path("Abingdon", "Fort Prince George")
- path("New Bern", "Norfolk")
- path("Petersburg", "Norfolk")
- path("Petersburg", "Wake (Raleigh)")
- path("New Bern", "Wake (Raleigh)")
- path("Hillsboro", "Wake (Raleigh)")
- path("Petersburg", "Hillsboro")
- path("Petersburg", "Lynch's Ferry")
- path("Wilmington NC", "Wake (Raleigh)")
- path("Wilmington NC", "Cheraw")
- path("Wilmington NC", "Georgetown")
- path("Cheraw", "Georgetown")
- path("Cheraw", "Camden")
- path("Ninety Six", "Fort Prince George")
- path("Ninety Six", "Camden")
- path("Ninety Six", "Augusta")
- path("Eutaw Springs", "Camden")
- path("Eutaw Springs", "Charleston")
- path("Georgetown", "Charleston")
- path("Savannah", "Charleston")
- path("Alexandria", "Baltimore")
- path("Savannah", "Augusta")
- path("Savannah", "St. Mary's")
- path("Charlottesville", "Lynch's Ferry")
-
- sea("Quebec", "Sea1")
- sea("Montreal", "Sea1")
-
- sea("Falmouth", "Sea2")
- sea("Boston", "Sea2")
- sea("Barnstable", "Sea2")
- sea("Newport", "Sea2")
-
- sea("New Haven", "Sea3")
- sea("New York", "Sea3")
- sea("Long Island", "Sea3")
-
- sea("Philadelphia", "Sea4")
- sea("Wilmington DE", "Sea4")
-
- sea("Baltimore", "Sea5")
- sea("Alexandria", "Sea5")
- sea("Yorktown", "Sea5")
- sea("Norfolk", "Sea5")
-
- sea("New Bern", "Sea6")
- sea("Wilmington NC", "Sea6")
- sea("Charleston", "Sea6")
-
- sea("Savannah", "Sea7")
- sea("St. Mary's", "Sea7")
-
- PATH_NAME = Object.keys(info).sort()
- PATH_TYPE = []
- for (let id of PATH_NAME)
- PATH_TYPE.push(info[id])
- for (let a in PATH_INDEX) {
- for (let b in PATH_INDEX[a]) {
- let id = a < b ? a + "/" + b : b + "/" + a
- PATH_INDEX[a][b] = PATH_NAME.indexOf(id)
- }
- }
-})()
+ { name: "Burgoyne", owner: "British", strategy: 2, battle: 2, agility: 1, bonus: false },
+ { name: "Carleton", owner: "British", strategy: 3, battle: 3, agility: 2, bonus: false },
+ { name: "Clinton", owner: "British", strategy: 3, battle: 4, agility: 2, bonus: false },
+ { name: "Cornwallis", owner: "British", strategy: 2, battle: 4, agility: 2, bonus: false },
+ { name: "Howe", owner: "British", strategy: 3, battle: 6, agility: 3, bonus: false },
+]
+
+const general_index = make_index_from_list(generals.map(x=>x.name))
+
+// === SPACES ===
+
+const spaces = []
+const space_index = {}
-if (typeof module == "object") {
- module.exports = {
- GENERALS: GENERALS,
- BOXES: BOXES,
- COLONIES: COLONIES,
- SPACES: SPACES,
- PATH_NAME: PATH_NAME,
- PATH_TYPE: PATH_TYPE,
- PATH_INDEX: PATH_INDEX,
- BLOCKADE,
+const colony_name = [ "Canada", "NH", "NY", "MA", "CT", "RI", "PA", "NJ", "MD", "DE", "VA", "NC", "SC", "GA" ]
+const colony_spaces = colony_name.map(x => [])
+
+const Canada = 0
+const NH = 1
+const NY = 2
+const MA = 3
+const CT = 4
+const RI = 5
+const PA = 6
+const NJ = 7
+const MD = 8
+const DE = 9
+const VA = 10
+const NC = 11
+const SC = 12
+const GA = 13
+
+function box(category, A, x, y) {
+ x += 40
+ y += 36
+ BOXES[A] = { name: A, category: category, x: x, y: y }
+}
+
+function space(colony, name, x, y, type) {
+ x += 40
+ y += 36
+ if (type === "winter-quarters") {
+ x += 1
+ y += 1
}
+
+ let ix = spaces.length
+ space_index[name] = ix
+ spaces.push({ name: name, colony, type, port: 0, path: [], wilderness: [], adjacent: [], x, y })
+ set_add(colony_spaces[colony], ix)
+}
+
+function not_space(type, name, x, y) {
+ // box that's not a space
+ x += 40
+ y += 36
+ let ix = spaces.length
+ space_index[name] = ix
+ spaces.push({ name, type, x, y })
+}
+
+function line(A, B, type) {
+ A = space_index[A]
+ B = space_index[B]
+ set_add(spaces[A].adjacent, B)
+ set_add(spaces[B].adjacent, A)
+ set_add(spaces[A][type], B)
+ set_add(spaces[B][type], A)
+}
+
+space(Canada, "Fort Detroit", 55, 673, "regular-space")
+space(Canada, "Montreal", 1000, 159, "fortified-port")
+space(Canada, "Quebec", 1122, 57, "fortified-port")
+space(CT, "Hartford", 1183, 749, "winter-quarters")
+space(CT, "New Haven", 1088, 736, "regular-space")
+space(DE, "Wilmington DE", 885, 1079, "winter-quarters")
+space(GA, "Augusta", 140, 2060, "regular-space")
+space(GA, "Savannah", 227, 2271, "regular-space")
+space(GA, "St. Mary's", 180, 2394, "regular-space")
+space(MA, "Barnstable", 1394, 705, "regular-space")
+space(MA, "Boston", 1327, 592, "winter-quarters")
+space(MA, "Falmouth", 1334, 410, "regular-space")
+space(MA, "Lexington Concord", 1218, 628, "regular-space")
+space(MA, "Springfield", 1095, 624, "winter-quarters")
+space(MD, "Baltimore", 769, 1105, "winter-quarters")
+space(MD, "Fort Cumberland", 446, 1098, "regular-space")
+space(MD, "Frederick Town", 646, 1097, "regular-space")
+space(NC, "Charlotte", 215, 1653, "regular-space")
+space(NC, "Gilbert Town", 123, 1568, "regular-space")
+space(NC, "Hillsboro", 484, 1582, "regular-space")
+space(NC, "New Bern", 764, 1670, "regular-space")
+space(NC, "Salem", 308, 1561, "regular-space")
+space(NC, "Wake (Raleigh)", 606, 1671, "regular-space")
+space(NC, "Wilmington NC", 676, 1816, "regular-space")
+space(NH, "Brattleboro", 1102, 486, "regular-space")
+space(NH, "Concord", 1214, 460, "regular-space")
+space(NH, "Norwich", 1125, 342, "winter-quarters")
+space(NJ, "Monmouth", 998, 1074, "regular-space")
+space(NJ, "Morristown", 864, 831, "regular-space")
+space(NJ, "New Brunswick", 961, 942, "regular-space")
+space(NY, "Albany", 965, 626, "winter-quarters")
+space(NY, "Fort Niagara", 461, 526, "regular-space")
+space(NY, "Fort Stanwix", 877, 483, "winter-quarters")
+space(NY, "Genesee", 668, 582, "regular-space")
+space(NY, "Long Island", 1147, 868, "regular-space")
+space(NY, "New York", 1024, 838, "winter-quarters")
+space(NY, "Oswego", 768, 424, "regular-space")
+space(NY, "Saratoga", 992, 496, "regular-space")
+space(NY, "Ticonderoga", 993, 355, "winter-quarters")
+space(NY, "Westchester", 955, 747, "regular-space")
+space(PA, "Bassett Town", 147, 984, "regular-space")
+space(PA, "Harrisburg", 555, 880, "regular-space")
+space(PA, "Philadelphia", 846, 961, "fortified-port")
+space(PA, "Pittsburgh", 439, 958, "winter-quarters")
+space(PA, "Reading", 684, 900, "regular-space")
+space(PA, "Wyoming Valley", 685, 747, "winter-quarters")
+space(PA, "York", 589, 992, "regular-space")
+space(RI, "Newport", 1289, 736, "winter-quarters")
+space(SC, "Camden", 346, 1773, "regular-space")
+space(SC, "Charleston", 444, 2050, "fortified-port")
+space(SC, "Cheraw", 496, 1795, "regular-space")
+space(SC, "Eutaw Springs", 404, 1905, "regular-space")
+space(SC, "Fort Prince George", 74, 1773, "regular-space")
+space(SC, "Georgetown", 574, 1915, "regular-space")
+space(SC, "Ninety Six", 222, 1932, "regular-space")
+space(VA, "Abingdon", 61, 1451, "regular-space")
+space(VA, "Alexandria", 653, 1217, "winter-quarters")
+space(VA, "Charlottesville", 434, 1257, "winter-quarters")
+space(VA, "Fincastle", 349, 1402, "regular-space")
+space(VA, "Fort Chiswell", 212, 1408, "regular-space")
+space(VA, "Lynch's Ferry", 486, 1444, "regular-space")
+space(VA, "Norfolk", 781, 1450, "winter-quarters")
+space(VA, "Petersburg", 634, 1446, "regular-space")
+space(VA, "Point Pleasant", 132, 1218, "regular-space")
+space(VA, "Richmond", 608, 1323, "winter-quarters")
+space(VA, "Yorktown", 767, 1317, "regular-space")
+
+not_space("box", "Continental Congress Dispersed", 545, 350)
+not_space("box", "Captured Generals", 1463, 80)
+not_space("box", "British Leader Reinforcements", 1425, 1745)
+not_space("box", "American Leader Reinforcements", 400, 310)
+not_space("box", "French Reinforcements", 150, 340)
+
+box("SEA", "Sea1", 1277, 65, "blockade")
+box("SEA", "Sea2", 1469, 514, "blockade")
+box("SEA", "Sea3", 1213, 981, "blockade")
+box("SEA", "Sea4", 1058, 1174, "blockade")
+box("SEA", "Sea5", 947, 1395, "blockade")
+box("SEA", "Sea6", 637, 2027, "blockade")
+box("SEA", "Sea7", 359, 2358, "blockade")
+
+box("ALLIANCE", "French Alliance Track 0", 898, 2240)
+box("ALLIANCE", "French Alliance Track 1", 964, 2240)
+box("ALLIANCE", "French Alliance Track 2", 1030, 2240)
+box("ALLIANCE", "French Alliance Track 3", 1096, 2240)
+box("ALLIANCE", "French Alliance Track 4", 1162, 2240)
+box("ALLIANCE", "French Alliance Track 5", 1228, 2240)
+box("ALLIANCE", "French Alliance Track 6", 1294, 2240)
+box("ALLIANCE", "French Alliance Track 7", 1360, 2240)
+box("ALLIANCE", "French Alliance Track 8", 1426, 2240)
+box("ALLIANCE", "French Alliance Track 9", 1492, 2240)
+
+box("TURN", "Game Turn 1775", 898, 2370)
+box("TURN", "Game Turn 1776", 964, 2370)
+box("TURN", "Game Turn 1777", 1030, 2370)
+box("TURN", "Game Turn 1778", 1096, 2370)
+box("TURN", "Game Turn 1779", 1162, 2370)
+box("TURN", "Game Turn 1780", 1228, 2370)
+box("TURN", "Game Turn 1781", 1294, 2370)
+box("TURN", "Game Turn 1782", 1360, 2370)
+box("TURN", "Game Turn 1783", 1426, 2370)
+
+box("CONTROL", "Canada", 1376, 967)
+box("CONTROL", "NH", 1458, 1006)
+box("CONTROL", "NY", 1375, 1081)
+box("CONTROL", "MA", 1491, 1081)
+box("CONTROL", "CT", 1455, 1156)
+box("CONTROL", "RI", 1533, 1156)
+box("CONTROL", "PA", 1299, 1194)
+box("CONTROL", "NJ", 1376, 1189)
+box("CONTROL", "MD", 1299, 1266)
+box("CONTROL", "DE", 1376, 1266)
+box("CONTROL", "VA", 1241, 1337)
+box("CONTROL", "NC", 1260, 1407)
+box("CONTROL", "SC", 1212, 1479)
+box("CONTROL", "GA", 1164, 1550)
+
+function wilderness(A, B) {
+ line(A, B, "wilderness")
+}
+
+function path(A, B) {
+ line(A, B, "path")
+}
+
+function sea(space, zone) {
+ spaces[space_index[space]].port = zone
+}
+
+wilderness("Quebec", "Falmouth")
+wilderness("Fort Detroit", "Bassett Town")
+wilderness("Bassett Town", "Point Pleasant")
+
+path("Quebec", "Montreal")
+path("Montreal", "Ticonderoga")
+path("Montreal", "Oswego")
+path("Oswego", "Fort Niagara")
+path("Oswego", "Fort Stanwix")
+path("Fort Niagara", "Fort Detroit")
+path("Fort Niagara", "Genesee")
+path("Ticonderoga", "Norwich")
+path("Ticonderoga", "Saratoga")
+path("Norwich", "Brattleboro")
+path("Norwich", "Concord")
+path("Concord", "Falmouth")
+path("Concord", "Brattleboro")
+path("Saratoga", "Brattleboro")
+path("Saratoga", "Albany")
+path("Brattleboro", "Springfield")
+path("Fort Stanwix", "Albany")
+path("Albany", "Springfield")
+path("Lexington Concord", "Springfield")
+path("Lexington Concord", "Boston")
+path("Falmouth", "Boston")
+path("Barnstable", "Boston")
+path("Newport", "Boston")
+path("Newport", "Hartford")
+path("Springfield", "Hartford")
+path("New Haven", "Hartford")
+path("New Haven", "New York")
+path("New Haven", "Westchester")
+path("New York", "Westchester")
+path("New York", "Long Island")
+path("Newport", "Lexington Concord")
+path("Albany", "Westchester")
+path("Fort Stanwix", "Genesee")
+path("Genesee", "Wyoming Valley")
+path("Fort Niagara", "Pittsburgh")
+path("Morristown", "Westchester")
+path("Morristown", "Wyoming Valley")
+path("Morristown", "Reading")
+path("Morristown", "New Brunswick")
+path("Morristown", "New York")
+path("New York", "New Brunswick")
+path("Monmouth", "New Brunswick")
+path("Philadelphia", "New Brunswick")
+path("Monmouth", "Philadelphia")
+path("Reading", "Philadelphia")
+path("Wyoming Valley", "Reading")
+path("Wilmington DE", "Philadelphia")
+path("Wilmington DE", "Baltimore")
+path("Bassett Town", "Pittsburgh")
+path("Fort Cumberland", "Pittsburgh")
+path("Harrisburg", "Pittsburgh")
+path("Harrisburg", "York")
+path("Reading", "York")
+path("Harrisburg", "Reading")
+path("Frederick Town", "York")
+path("Frederick Town", "Baltimore")
+path("Frederick Town", "Alexandria")
+path("Frederick Town", "Charlottesville")
+path("Frederick Town", "Fort Cumberland")
+path("Richmond", "Alexandria")
+path("Richmond", "Yorktown")
+path("Richmond", "Petersburg")
+path("Richmond", "Lynch's Ferry")
+path("Richmond", "Charlottesville")
+path("Richmond", "Norfolk")
+path("Fincastle", "Charlottesville")
+path("Fincastle", "Lynch's Ferry")
+path("Fincastle", "Fort Chiswell")
+path("Point Pleasant", "Fort Chiswell")
+path("Abingdon", "Fort Chiswell")
+path("Abingdon", "Gilbert Town")
+path("Fort Prince George", "Gilbert Town")
+path("Charlotte", "Gilbert Town")
+path("Charlotte", "Salem")
+path("Hillsboro", "Salem")
+path("Cheraw", "Salem")
+path("Lynch's Ferry", "Salem")
+path("Charlotte", "Camden")
+path("Abingdon", "Fort Prince George")
+path("New Bern", "Norfolk")
+path("Petersburg", "Norfolk")
+path("Petersburg", "Wake (Raleigh)")
+path("New Bern", "Wake (Raleigh)")
+path("Hillsboro", "Wake (Raleigh)")
+path("Petersburg", "Hillsboro")
+path("Petersburg", "Lynch's Ferry")
+path("Wilmington NC", "Wake (Raleigh)")
+path("Wilmington NC", "Cheraw")
+path("Wilmington NC", "Georgetown")
+path("Cheraw", "Georgetown")
+path("Cheraw", "Camden")
+path("Ninety Six", "Fort Prince George")
+path("Ninety Six", "Camden")
+path("Ninety Six", "Augusta")
+path("Eutaw Springs", "Camden")
+path("Eutaw Springs", "Charleston")
+path("Georgetown", "Charleston")
+path("Savannah", "Charleston")
+path("Alexandria", "Baltimore")
+path("Savannah", "Augusta")
+path("Savannah", "St. Mary's")
+path("Charlottesville", "Lynch's Ferry")
+
+sea("Quebec", 1)
+sea("Montreal", 1)
+
+sea("Falmouth", 2)
+sea("Boston", 2)
+sea("Barnstable", 2)
+sea("Newport", 2)
+
+sea("New Haven", 3)
+sea("New York", 3)
+sea("Long Island", 3)
+
+sea("Philadelphia", 4)
+sea("Wilmington DE", 4)
+
+sea("Baltimore", 5)
+sea("Alexandria", 5)
+sea("Yorktown", 5)
+sea("Norfolk", 5)
+
+sea("New Bern", 6)
+sea("Wilmington NC", 6)
+sea("Charleston", 6)
+
+sea("Savannah", 7)
+sea("St. Mary's", 7)
+
+// === CARDS ===
+
+const CARD_3_OPS = { title: "3 OPS", type: "ops", count: 3 }
+const CARD_2_OPS = { title: "2 OPS", type: "ops", count: 2 }
+const CARD_1_OPS = { title: "1 OPS", type: "ops", count: 1 }
+
+const cards = [
+ null,
+
+ CARD_3_OPS,
+ CARD_3_OPS,
+ CARD_3_OPS,
+ CARD_3_OPS,
+ CARD_3_OPS,
+ CARD_3_OPS,
+ CARD_3_OPS,
+ CARD_3_OPS,
+ CARD_3_OPS,
+ CARD_3_OPS,
+ CARD_3_OPS,
+ CARD_3_OPS,
+ CARD_3_OPS,
+ CARD_3_OPS,
+ CARD_3_OPS,
+ CARD_3_OPS,
+ CARD_3_OPS,
+ CARD_3_OPS,
+ CARD_3_OPS,
+ CARD_3_OPS,
+ CARD_3_OPS,
+ CARD_3_OPS,
+
+ CARD_2_OPS,
+ CARD_2_OPS,
+ CARD_2_OPS,
+ CARD_2_OPS,
+ CARD_2_OPS,
+ CARD_2_OPS,
+ CARD_2_OPS,
+ CARD_2_OPS,
+ CARD_2_OPS,
+ CARD_2_OPS,
+ CARD_2_OPS,
+ CARD_2_OPS,
+ CARD_2_OPS,
+ CARD_2_OPS,
+ CARD_2_OPS,
+ CARD_2_OPS,
+ CARD_2_OPS,
+ CARD_2_OPS,
+ CARD_2_OPS,
+ CARD_2_OPS,
+ CARD_2_OPS,
+ CARD_2_OPS,
+
+ CARD_1_OPS,
+ CARD_1_OPS,
+ CARD_1_OPS,
+ CARD_1_OPS,
+ CARD_1_OPS,
+ CARD_1_OPS,
+ CARD_1_OPS,
+ CARD_1_OPS,
+ CARD_1_OPS,
+ CARD_1_OPS,
+ CARD_1_OPS,
+ CARD_1_OPS,
+ CARD_1_OPS,
+ CARD_1_OPS,
+ CARD_1_OPS,
+ CARD_1_OPS,
+ CARD_1_OPS,
+ CARD_1_OPS,
+ CARD_1_OPS,
+ CARD_1_OPS,
+ CARD_1_OPS,
+ CARD_1_OPS,
+
+ /* 67 */
+ {
+ title: "Minor Campaign",
+ type: "campaign",
+ count: 2,
+ },
+
+ /* 68 */
+ {
+ title: "Minor Campaign",
+ type: "campaign",
+ count: 2,
+ },
+
+ /* 69 */
+ {
+ title: "Minor Campaign",
+ type: "campaign",
+ count: 2,
+ },
+
+ /* 70 */
+ {
+ title: "Major Campaign",
+ type: "campaign",
+ count: 3,
+ },
+
+ /* 71 */
+ {
+ title: "North's Government Falls - The War Ends in 1779",
+ type: "mandatory-event",
+ event: "the_war_ends",
+ year: 1779,
+ },
+
+ /* 72 */
+ {
+ title: "North's Government Falls - The War Ends in 1780",
+ type: "mandatory-event",
+ event: "the_war_ends",
+ year: 1780,
+ },
+
+ /* 73 */
+ {
+ title: "North's Government Falls - The War Ends in 1781",
+ type: "mandatory-event",
+ event: "the_war_ends",
+ year: 1781,
+ },
+
+ /* 74 */
+ {
+ title: "North's Government Falls - The War Ends in 1782",
+ type: "mandatory-event",
+ event: "the_war_ends",
+ year: 1782,
+ },
+
+ /* 75 */
+ {
+ title: "North's Government Falls - The War Ends in 1783",
+ type: "mandatory-event",
+ event: "the_war_ends",
+ year: 1783,
+ },
+
+ /* 76 */
+ {
+ title: "Henry Know Continental Artillery Commander",
+ type: "american-battle",
+ },
+
+ /* 77 */
+ {
+ title: "Jane McCrea Indian Atrocity Sparks Outrage",
+ type: "american-event",
+ event: "place_american_pc",
+ count: 2,
+ once: true,
+ },
+
+ /* 78 */
+ {
+ title: "Iroquois Uprising!",
+ type: "british-event",
+ event: "remove_american_pc_from_non_port",
+ where: [ NH, NY, PA ],
+ count: 2,
+ },
+
+ /* 79 */
+ {
+ title: "Joseph Brant Leads an Iroquois Raid",
+ type: "british-event",
+ event: "remove_american_pc_from_non_port",
+ where: [ NH, NY, PA ],
+ count: 2,
+ },
+
+ /* 80 */
+ {
+ title: "Lt. Colonel Simcoe's Queen's Rangers",
+ type: "british-event",
+ event: "remove_american_pc_within_two_spaces_of_a_british_general",
+ count: 2,
+ },
+
+ /* 81 */
+ {
+ title: "D'Estaing Sails for the Caribbean",
+ type: "british-event",
+ when: "after_french_alliance",
+ event: "remove_french_navy",
+ },
+
+ /* 82 */
+ {
+ title: "Banastre Tarleton's Waxhaws Massacre",
+ type: "british-event-or-battle",
+ event: "remove_american_cu",
+ },
+
+ /* 83 */
+ {
+ title: "Lord George Germaine Offers Royal Amnesty",
+ type: "british-event",
+ event: "remove_american_pc",
+ count: 2,
+ },
+
+ /* 84 */
+ {
+ title: "George Rogers Clark Leads a Western Offensive",
+ type: "american-event",
+ event: "remove_random_british_card",
+ },
+
+ /* 85 */
+ {
+ title: "Don Bernardo Galvez Captures Pensacola",
+ type: "american-event",
+ when: "european_war_in_effect",
+ event: "remove_british_cu",
+ count: 2,
+ },
+
+ /* 86 */
+ {
+ title: "Baron von Steuben Trains the Continental Army",
+ type: "american-event",
+ event: "baron_von_steuben_trains_the_continental_army",
+ },
+
+ /* 87 */
+ {
+ title: "Lord North Offers a Royal Amnesty",
+ type: "british-event",
+ event: "remove_american_pc",
+ count: 4,
+ once: true,
+ },
+
+ /* 88 */
+ {
+ title: "The Swamp Fox, Francis Marion",
+ type: "american-event",
+ event: "remove_british_pc_from",
+ where: [ NC, SC, GA ],
+ count: 2,
+ },
+
+ /* 89 */
+ {
+ title: "The Gamecock: Thomas Sumter",
+ type: "american-event",
+ event: "remove_british_pc_from",
+ where: [ NC, SC, GA ],
+ count: 2,
+ },
+
+ /* 90 */
+ {
+ title: "Josiah Martin Rallies North Carolina Loyalists",
+ type: "british-event",
+ event: "remove_american_pc_from",
+ where: [ NC ],
+ count: 2,
+ },
+
+ /* 91 */
+ {
+ title: 'Thomas Paine Publishes Pamphlets "Common Sense" and "The American Crisis"',
+ type: "american-event",
+ event: "place_american_pc_in",
+ where: [ NH, NY, MA, CT, RI, PA, NJ, MD, DE, VA, NC, SC, GA ],
+ count: 3,
+ once: true,
+ },
+
+ /* 92 */
+ {
+ title: "Nathan Hale, American Martyr",
+ type: "american-event",
+ event: "place_american_pc",
+ count: 2,
+ once: true,
+ },
+
+ /* 93 */
+ {
+ title: "John Glover's Marblehead Regiment",
+ type: "american-event",
+ event: "john_glovers_marblehead_regiment",
+ },
+
+ /* 94 */
+ {
+ title: "Pennsylvania and New Jersey Line Mutinies",
+ type: "british-event",
+ event: "pennsylvania_and_new_jersey_line_mutinies",
+ },
+
+ /* 95 */
+ {
+ title: "William Pitt Urges Peace Talks",
+ type: "british-event",
+ when: "before_french_alliance",
+ event: "remove_american_pc",
+ count: 2,
+ reshuffle: "if_played",
+ },
+
+ /* 96 */
+ {
+ title: "Hortelez et Cie: Clandestine French and Spanish Aid",
+ type: "american-event",
+ when: "before_french_alliance",
+ event: "advance_french_alliance",
+ reshuffle: "if_discarded",
+ count: 2,
+ },
+
+ /* 97 */
+ {
+ title: "Admiral Suffren Wins a Naval Victory",
+ type: "american-event",
+ when: "after_french_alliance",
+ event: "remove_random_british_card",
+ },
+
+ /* 98 */
+ {
+ title: '"Mad" Anthony Wayne',
+ type: "american-battle",
+ },
+
+ /* 99 */
+ {
+ title: "Declaration of Independence",
+ type: "mandatory-event",
+ event: "declaration_of_independence",
+ once: true,
+ reshuffle: "if_played",
+ tournament: true,
+ },
+
+ /* 100 */
+ {
+ title: "Benedict Arnold's Treason Undermines the Patriot Cause",
+ type: "british-battle",
+ event: "remove_benedict_arnold",
+ once: true,
+ },
+
+ /* 101 */
+ {
+ title: "Benjamin Franklin: Minister to France",
+ type: "mandatory-event",
+ event: "advance_french_alliance",
+ count: 4,
+ once: true,
+ tournament: true,
+ },
+
+ /* 102 */
+ {
+ title: "Admiral Rodney Captures St. Eustatius",
+ type: "british-event",
+ event: "remove_random_american_card",
+ when: "european_war_in_effect",
+ once: true,
+ },
+
+ /* 103 */
+ {
+ title: "Thaddeus Kosciuszco Constructs Engineering Works",
+ type: "american-battle",
+ },
+
+ /* 104 */
+ {
+ title: "Light Horse Harry Lee",
+ type: "american-battle",
+ },
+
+ /* 105 */
+ {
+ title: "Morgan's Riflemen",
+ type: "american-battle",
+ },
+
+ /* 106 */
+ {
+ title: "John Paul Jones' Shipping Raids",
+ type: "american-event",
+ event: "remove_random_british_card",
+ },
+
+ /* 107 */
+ {
+ title: "British Light Infantry",
+ type: "british-battle",
+ },
+
+ /* 108 */
+ {
+ title: "Lord Sandwich Coastal Raids",
+ type: "british-event",
+ event: "lord_sandwich_coastal_raids",
+ },
+
+ /* 109 */
+ {
+ title: "Edward Bancroft, British Double Agent",
+ type: "british-event",
+ event: "remove_random_american_card",
+ },
+
+ /* 110 */
+ {
+ title: "Hessian Infantry Bayonet Charge",
+ type: "british-battle",
+ },
+]
+
+data = {
+ BOXES,
+ cards,
+ general_index,
+ generals,
+ space_index,
+ spaces,
+ colony_name,
+ colony_spaces,
}
+
+})()
+
+if (typeof module === "object")
+ module.exports = data
diff --git a/play.html b/play.html
index 2e8a385..53c7d5c 100644
--- a/play.html
+++ b/play.html
@@ -11,7 +11,6 @@
<link rel="stylesheet" href="play.css">
<script defer src="/common/client.js"></script>
<script defer src="data.js"></script>
-<script defer src="cards.js"></script>
<script defer src="play.js"></script>
</script>
</head>
diff --git a/play.js b/play.js
index 5b75f51..98646cb 100644
--- a/play.js
+++ b/play.js
@@ -1,20 +1,32 @@
"use strict"
-const THE_13_COLONIES = [ "NH", "NY", "MA", "CT", "RI", "PA", "NJ", "MD", "DE", "VA", "NC", "SC", "GA" ]
-const ALL_COLONIES = THE_13_COLONIES.concat([ "CA" ])
-const AMERICAN_GENERALS = [ "Arnold", "Gates", "Greene", "Lafayette", "Lee", "Lincoln", "Washington" ]
-const CONTINENTAL_CONGRESS_DISPERSED = "Continental Congress Dispersed"
+const PC_NONE = 0
+const PC_BRITISH = 1
+const PC_AMERICAN = 2
+
+const GENERALS = data.generals
+const CARDS = data.cards
+const SPACES = data.spaces
+const COLONIES = data.colony_spaces
+const BOXES = data.BOXES
+
+const CONTINENTAL_CONGRESS_DISPERSED = data.space_index["Continental Congress Dispersed"]
+const BLOCKADE_ZONES = [ "Sea1", "Sea2", "Sea3", "Sea4", "Sea5", "Sea6", "Sea7" ]
const BRITISH = "British"
const AMERICAN = "American"
-const BLOCKADE_ZONES = [ "Sea1", "Sea2", "Sea3", "Sea4", "Sea5", "Sea6", "Sea7" ]
+
+const space_count = 66
+const general_count = data.generals.length
let ui = {
cards: {},
- spaces: {},
- generals: {},
- control: {},
- blockade: {},
+ spaces: [],
+ generals: [],
+ pc: [],
cu: [],
+ control: [],
+
+ blockade: {},
}
function on_focus_card_tip(card_number) {
@@ -43,6 +55,7 @@ function on_log(text) {
text = text.replace(/#(\d+)/g,
'<span class="tip" onmouseenter="on_focus_card_tip($1)" onmouseleave="on_blur_card_tip()">$&</span>')
+ // text = text.replace(/%(\d+)/g, sub_space_name)
if (text.match(/^\.h1 /)) {
p.className = "h1"
@@ -70,12 +83,12 @@ function onHoverCard(X) {
}
function onFocusNode(evt) {
- let space = SPACES[evt.target.id]
+ let space = SPACES[evt.target.my_id]
document.getElementById("status").textContent = space.name
}
function onBlurNode(evt) {
- let space = SPACES[evt.target.id]
+ let space = SPACES[evt.target.my_id]
document.getElementById("status").textContent = ""
}
@@ -91,7 +104,7 @@ function build_marker(container, id, x, y, w, h, classList) {
e.classList.add("marker")
for (let c of classList)
e.classList.add(c)
- e.setAttribute("id", id)
+ e.my_id = id
e.style.left = ((x - w / 2) | 0) + "px"
e.style.top = ((y - h / 2) | 0) + "px"
document.getElementById(container).appendChild(e)
@@ -99,7 +112,11 @@ function build_marker(container, id, x, y, w, h, classList) {
}
function update_marker(e, space) {
- let box = SPACES[space] || BOXES[space]
+ let box
+ if (typeof space === "number")
+ box = SPACES[space]
+ else
+ box = BOXES[space]
e.style.left = ((box.x - e.foo.w / 2) | 0) + "px"
e.style.top = ((box.y - e.foo.h / 2) | 0) + "px"
}
@@ -143,17 +160,17 @@ function build_map() {
return e
}
- for (let name in SPACES) {
- let space = SPACES[name]
+ for (let i = 0; i < SPACES.length; ++i) {
+ let space = SPACES[i]
if (space.colony != null) {
- let e = buildNode(space.type, space.x, space.y, SPACES[name].colony)
- e.setAttribute("id", name)
+ let e = buildNode(space.type, space.x, space.y, space.colony)
+ e.my_id = i
e.addEventListener("mouseenter", onFocusNode)
e.addEventListener("mouseleave", onBlurNode)
e.addEventListener("click", on_space)
- ui.spaces[name] = e
+ ui.spaces[i] = e
document.getElementById("spaces").appendChild(e)
- build_marker("pc", name + "-pc", space.x, space.y, 67, 58.5, [ "pc" ])
+ ui.pc[i] = build_marker("pc", name + "-pc", space.x, space.y, 67, 58.5, [ "pc" ])
}
}
@@ -182,8 +199,8 @@ function build_map() {
ui.french_navy = build_marker(
"markers",
"FrenchNavy",
- BOXES["French Reinforcements"].x - 130 / 2 - 10,
- BOXES["French Reinforcements"].y - 32,
+ SPACES[data.space_index["French Reinforcements"]].x - 130 / 2 - 10,
+ SPACES[data.space_index["French Reinforcements"]].y - 32,
126 / 2,
252 / 2,
[ "french-navy" ]
@@ -192,15 +209,16 @@ function build_map() {
ui.congress = build_marker(
"markers",
"Congress",
- SPACES["Philadelphia"].x,
- SPACES["Philadelphia"].y,
+ SPACES[data.space_index["Philadelphia"]].x,
+ SPACES[data.space_index["Philadelphia"]].y,
113 / 2,
113 / 2,
[ "congress" ]
)
- for (let c in COLONIES) {
- ui.control[c] = build_marker("markers", "control_" + c, BOXES[c].x, BOXES[c].y, 38 + 8, 38 + 8, [ "control" ])
+ for (let c = 0; c <= 13; ++c) {
+ let name = data.colony_name[c]
+ ui.control[c] = build_marker("markers", "control_" + name, BOXES[name].x, BOXES[name].y, 38 + 8, 38 + 8, [ "control" ])
}
for (let c = 1; c <= 110; ++c) {
@@ -208,9 +226,10 @@ function build_map() {
ui.cards[c].addEventListener("click", on_card)
}
- for (let g in GENERALS) {
+ for (let g = 0; g < general_count; ++g) {
let color = GENERALS[g].owner.toLowerCase()
- ui.generals[g] = build_marker("generals", g, 0, 0, 126 / 2, 252 / 2, [ "general", color, g, "offmap" ])
+ let name = GENERALS[g].name
+ ui.generals[g] = build_marker("generals", g, 0, 0, 126 / 2, 252 / 2, [ "general", color, name, "offmap" ])
ui.generals[g].addEventListener("click", on_general)
}
}
@@ -237,40 +256,43 @@ function update_units() {
else
ui.french_alliance.classList.remove("european-war")
- if (view.french_navy == "French Reinforcements") {
- let x = BOXES["French Reinforcements"].x - 130 / 2 - 10
- let y = BOXES["French Reinforcements"].y - 32
+ if (view.french_navy < 0) {
+ let x = SPACES[data.space_index["French Reinforcements"]].x - 130 / 2 - 10
+ let y = SPACES[data.space_index["French Reinforcements"]].y - 32
let w = 126 / 2
let h = 252 / 2
ui.french_navy.style.left = ((x - w / 2) | 0) + "px"
ui.french_navy.style.top = ((y - h / 2) | 0) + "px"
} else {
- update_marker(ui.french_navy, view.french_navy)
+ let s
+ if (view.french_navy > 1700)
+ s = "Game Turn "+ view.french_navy
+ else
+ s = "Sea "+ view.french_navy
+ update_marker(ui.french_navy, s)
}
- for (let space in SPACES) {
+ for (let space = 0; space < space_count; ++space) {
let space_pc = view.pc[space]
- let e = document.getElementById(space + "-pc")
- if (e) {
- if (space_pc === BRITISH) {
- e.classList.remove("american")
- e.classList.add("british")
- } else if (space_pc === AMERICAN) {
- e.classList.add("american")
- e.classList.remove("british")
- } else {
- e.classList.remove("american")
- e.classList.remove("british")
- }
+ let e = ui.pc[space]
+ if (space_pc === PC_BRITISH) {
+ e.classList.remove("american")
+ e.classList.add("british")
+ } else if (space_pc === PC_AMERICAN) {
+ e.classList.add("american")
+ e.classList.remove("british")
+ } else {
+ e.classList.remove("american")
+ e.classList.remove("british")
}
}
- for (let c in COLONIES) {
+ for (let c = 0; c <= 13; ++c) {
let control = 0
for (let space of COLONIES[c]) {
- if (view.pc[space] == BRITISH)
+ if (view.pc[space] == PC_BRITISH)
--control
- else if (view.pc[space] == AMERICAN)
+ else if (view.pc[space] == PC_AMERICAN)
++control
}
if (control < 0)
@@ -284,8 +306,8 @@ function update_units() {
let offset = {}
for (let g in GENERALS) {
let e = ui.generals[g]
- let unit = view.generals[g]
- let space = SPACES[unit.location] || BOXES[unit.location]
+ let loc = view.generals[g]
+ let space = SPACES[loc] || BOXES[loc]
if (space) {
let o = offset[space.name] | 0
update_marker_xy(e, space.x + o * generalX, space.y + o * generalY - 32)
@@ -320,7 +342,7 @@ build_map()
function player_info(player, nc, nq) {
let info = ""
if (player == AMERICAN) {
- if (view.pennsylvania_and_new_jersey_line_mutinies || view.congress == CONTINENTAL_CONGRESS_DISPERSED)
+ if (view.pennsylvania_and_new_jersey_line_mutinies || view.congress === CONTINENTAL_CONGRESS_DISPERSED)
info += "\u{1f6ab} "
}
if (nq > 0)
@@ -390,10 +412,10 @@ function on_update() {
ui.cards[c].classList.remove("show")
}
- for (let space in SPACES)
+ for (let space = 0; space < space_count; ++space)
ui.spaces[space].classList.remove("enabled")
- for (let general in GENERALS)
+ for (let general = 0; general < general_count; ++general)
ui.generals[general].classList.remove("enabled")
for (let zone of BLOCKADE_ZONES)
@@ -404,6 +426,7 @@ function on_update() {
if (player != view.active)
return
+ if (view.actions)
for (let action of Object.keys(view.actions)) {
let args = view.actions[action]
switch (action) {
@@ -518,7 +541,7 @@ function get_action_from_arg(x) {
function on_space(evt) {
if (view.actions) {
- let space = evt.target.id
+ let space = evt.target.my_id
let action = get_action_from_arg(space)
if (action)
send_action(action, space)
@@ -527,7 +550,7 @@ function on_space(evt) {
function on_general(evt) {
if (view.actions) {
- let general = evt.target.id
+ let general = evt.target.my_id
let action = get_action_from_arg(general)
if (action)
send_action(action, general)
diff --git a/rules.js b/rules.js
index 02dc94b..f7fc222 100644
--- a/rules.js
+++ b/rules.js
@@ -7,47 +7,77 @@ exports.scenarios = [ "Historical" ]
exports.roles = [ "American", "British" ]
-const CARDS = require("./cards")
-const DATA = require("./data")
-const SPACES = DATA.SPACES
-const COLONIES = DATA.COLONIES
-const GENERALS = DATA.GENERALS
-const BLOCKADE = DATA.BLOCKADE
-const PATH_INDEX = DATA.PATH_INDEX
-const PATH_NAME = DATA.PATH_NAME
-const PATH_TYPE = DATA.PATH_TYPE
+const data = require("./data")
+
+const CARDS = data.cards
+const SPACES = data.spaces
+const COLONIES = data.colony_spaces
+const GENERALS = data.generals
+
+const Canada = 0
+const NH = 1
+const NY = 2
+const MA = 3
+const CT = 4
+const RI = 5
+const PA = 6
+const NJ = 7
+const MD = 8
+const DE = 9
+const VA = 10
+const NC = 11
+const SC = 12
+const GA = 13
const BRITISH = "British"
const AMERICAN = "American"
const FRENCH = "French"
-const BRITISH_GENERALS = [ "Burgoyne", "Carleton", "Clinton", "Cornwallis", "Howe" ]
-const AMERICAN_GENERALS = [ "Arnold", "Gates", "Greene", "Lafayette", "Lee", "Lincoln", "Washington", "Rochambeau" ]
-const WASHINGTON = "Washington"
-const ROCHAMBEAU = "Rochambeau"
-const ARNOLD = "Arnold"
-
-const CAPTURED_GENERALS = "Captured Generals"
-const CONTINENTAL_CONGRESS_DISPERSED = "Continental Congress Dispersed"
-const BRITISH_REINFORCEMENTS = "British Leader Reinforcements"
-const AMERICAN_REINFORCEMENTS = "American Leader Reinforcements"
-const FRENCH_REINFORCEMENTS = "French Reinforcements"
-const TURN_TRACK = {
- 1775: "Game Turn 1775",
- 1776: "Game Turn 1776",
- 1777: "Game Turn 1777",
- 1778: "Game Turn 1778",
- 1779: "Game Turn 1779",
- 1780: "Game Turn 1780",
- 1781: "Game Turn 1781",
- 1782: "Game Turn 1782",
- 1783: "Game Turn 1783",
-}
-
-const FALMOUTH_QUEBEC = "Falmouth/Quebec"
-
-const THE_13_COLONIES = [ "NH", "NY", "MA", "CT", "RI", "PA", "NJ", "MD", "DE", "VA", "NC", "SC", "GA" ]
-const SOUTH_OF_WINTER_ATTRITION_LINE = [ "NC", "SC", "GA" ]
+const PC_NONE = 0
+const PC_BRITISH = 1
+const PC_AMERICAN = 2
+
+const AMERICAN_GENERALS = [ 0, 1, 2, 3, 4, 5, 6, 7 ]
+const BRITISH_GENERALS = [ 8, 9, 10, 11, 12 ]
+
+const NOBODY = -1
+const ARNOLD = data.general_index["Arnold"]
+const BURGOYNE = data.general_index["Burgoyne"]
+const CLINTON = data.general_index["Clinton"]
+const CORNWALLIS = data.general_index["Cornwallis"]
+const GATES = data.general_index["Gates"]
+const GREENE = data.general_index["Greene"]
+const LAFAYETTE = data.general_index["Lafayette"]
+const LEE = data.general_index["Lee"]
+const LINCOLN = data.general_index["Lincoln"]
+const ROCHAMBEAU = data.general_index["Rochambeau"]
+const WASHINGTON = data.general_index["Washington"]
+const CARLETON = data.general_index["Carleton"]
+const HOWE = data.general_index["Howe"]
+
+const NOWHERE = -1
+const BOSTON = data.space_index["Boston"]
+const CHARLESTON = data.space_index["Charleston"]
+const FORT_DETROIT = data.space_index["Fort Detroit"]
+const GILBERT_TOWN = data.space_index["Gilbert Town"]
+const LEXINGTON_CONCORD = data.space_index["Lexington Concord"]
+const MONTREAL = data.space_index["Montreal"]
+const NEWPORT = data.space_index["Newport"]
+const NINETY_SIX = data.space_index["Ninety Six"]
+const NORFOLK = data.space_index["Norfolk"]
+const PHILADELPHIA = data.space_index["Philadelphia"]
+const QUEBEC = data.space_index["Quebec"]
+const WILMINGTON_NC = data.space_index["Wilmington NC"]
+
+const CAPTURED_GENERALS = data.space_index["Captured Generals"]
+const CONTINENTAL_CONGRESS_DISPERSED = data.space_index["Continental Congress Dispersed"]
+const BRITISH_REINFORCEMENTS = data.space_index["British Leader Reinforcements"]
+const AMERICAN_REINFORCEMENTS = data.space_index["American Leader Reinforcements"]
+const FRENCH_REINFORCEMENTS = data.space_index["French Reinforcements"]
+const ELIMINATED = data.spaces.length
+
+const THE_13_COLONIES = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 ]
+const SOUTH_OF_WINTER_ATTRITION_LINE = [ 11, 12, 13 ]
const CAMPAIGN_CARDS = [ 67, 68, 69, 70 ]
const DECLARATION_OF_INDEPENDENCE = 99
@@ -55,9 +85,10 @@ const BARON_VON_STEUBEN = 86
const WAR_ENDS_1779 = 71
const BENJAMIN_FRANKLIN = 101
-const ENEMY = { American: BRITISH, British: AMERICAN }
+const space_count = 66
+const all_spaces = new Array(space_count).fill(0).map((_,i)=>i)
-const default_options = {}
+const ENEMY = { American: BRITISH, British: AMERICAN }
let states = {}
let events = {}
@@ -65,107 +96,28 @@ let events = {}
let game
let view
-function random(n) {
- return (game.seed = (game.seed * 200105) % 34359738337) % n
-}
-
-function logbr() {
- if (game.log.length > 0 && game.log[game.log.length - 1] !== "")
- game.log.push("")
-}
-
-function log(s) {
- game.log.push(s)
-}
-
-function logp(s) {
- game.log.push(game.active[0] + " " + s)
-}
-
-function object_copy(original) {
- if (Array.isArray(original)) {
- let n = original.length
- let copy = new Array(n)
- for (let i = 0; i < n; ++i) {
- let v = original[i]
- if (typeof v === "object" && v !== null)
- copy[i] = object_copy(v)
- else
- copy[i] = v
- }
- return copy
- } else {
- let copy = {}
- for (let i in original) {
- let v = original[i]
- if (typeof v === "object" && v !== null)
- copy[i] = object_copy(v)
- else
- copy[i] = v
- }
- return copy
- }
-}
-
-function clear_undo() {
- if (game.undo) {
- game.undo.length = 0
- }
-}
-
-function push_undo() {
- if (game.undo) {
- let copy = {}
- for (let k in game) {
- let v = game[k]
- if (k === "undo")
- continue
- else if (k === "log")
- v = v.length
- else if (typeof v === "object" && v !== null)
- v = object_copy(v)
- copy[k] = v
- }
- game.undo.push(copy)
- }
-}
-
-function pop_undo() {
- if (game.undo) {
- let save_log = game.log
- let save_undo = game.undo
- game = save_undo.pop()
- save_log.length = game.log
- game.log = save_log
- game.undo = save_undo
- }
-}
-
-function remove_from_array(array, item) {
- let i = array.indexOf(item)
- if (i >= 0)
- array.splice(i, 1)
-}
-
function setup_game(seed) {
game = {
seed: seed,
year: 1775,
- congress: "Philadelphia",
+ congress: data.space_index["Philadelphia"],
french_alliance: 0,
french_alliance_triggered: false,
european_war: false,
- french_navy: FRENCH_REINFORCEMENTS,
+ french_navy: -1,
regulars: true,
war_ends: 0,
played_british_reinforcements: 0,
played_american_reinforcements: [],
- pc: {},
- generals: {},
- moved: {},
+ pc: new Array(space_count).fill(PC_NONE),
+ generals: new Array(general_count).fill(NOWHERE),
+ moved: [],
cu: [],
- control: {},
+
+ // TODO: compute on the fly
+ control: [],
+
deck: create_deck(),
discard: [],
@@ -182,22 +134,13 @@ function setup_game(seed) {
actions: [],
}
- function spawn_unit(owner, location, pc, cu, name) {
+ function spawn_unit(owner, location, pc, cu, general=NOBODY) {
if (pc)
- game.pc[location] = owner
- if (name) {
- game.generals[name] = {
- location: location,
- }
- }
- if (cu > 0) {
- game.cu.push({
- owner: owner,
- location: location,
- count: cu,
- moved: 0,
- })
- }
+ set_space_pc(location, pc)
+ if (general !== NOBODY)
+ set_general_location(general, location)
+ if (cu > 0)
+ spawn_cu(owner, location, count)
}
function british(place, pc, cu, ld) {
@@ -210,31 +153,31 @@ function setup_game(seed) {
spawn_unit(FRENCH, place, pc, cu, ld)
}
- british("Quebec", true, 2, "Carleton")
- british("Montreal", true)
- british("Fort Detroit", true, 1)
- british("Boston", true, 5, "Howe")
- british("Norfolk", true)
- british("Gilbert Town", true)
- british("Wilmington NC", true)
- british("Ninety Six", true)
+ british(QUEBEC, PC_BRITISH, 2, CARLETON)
+ british(MONTREAL, PC_BRITISH)
+ british(FORT_DETROIT, PC_BRITISH, 1)
+ british(BOSTON, PC_BRITISH, 5, HOWE)
+ british(NORFOLK, PC_BRITISH)
+ british(GILBERT_TOWN, PC_BRITISH)
+ british(WILMINGTON_NC, PC_BRITISH)
+ british(NINETY_SIX, PC_BRITISH)
- american("Lexington Concord", true, 5, "Washington")
- american("Newport", false, 2, "Greene")
- american("Charleston", true, 2)
- american("Philadelphia", true)
+ american(LEXINGTON_CONCORD, PC_AMERICAN, 5, WASHINGTON)
+ american(NEWPORT, PC_NONE, 2, GREENE)
+ american(CHARLESTON, PC_AMERICAN, 2)
+ american(PHILADELPHIA, PC_AMERICAN)
- british(BRITISH_REINFORCEMENTS, false, 0, "Burgoyne")
- british(BRITISH_REINFORCEMENTS, false, 0, "Clinton")
- british(BRITISH_REINFORCEMENTS, false, 0, "Cornwallis")
+ british(BRITISH_REINFORCEMENTS, PC_NONE, 0, BURGOYNE)
+ british(BRITISH_REINFORCEMENTS, PC_NONE, 0, CLINTON)
+ british(BRITISH_REINFORCEMENTS, PC_NONE, 0, CORNWALLIS)
- american(AMERICAN_REINFORCEMENTS, false, 0, "Arnold")
- american(AMERICAN_REINFORCEMENTS, false, 0, "Lincoln")
- american(AMERICAN_REINFORCEMENTS, false, 0, "Gates")
- american(AMERICAN_REINFORCEMENTS, false, 0, "Lee")
- american(AMERICAN_REINFORCEMENTS, false, 0, "Lafayette")
+ american(AMERICAN_REINFORCEMENTS, PC_NONE, 0, ARNOLD)
+ american(AMERICAN_REINFORCEMENTS, PC_NONE, 0, LINCOLN)
+ american(AMERICAN_REINFORCEMENTS, PC_NONE, 0, GATES)
+ american(AMERICAN_REINFORCEMENTS, PC_NONE, 0, LEE)
+ american(AMERICAN_REINFORCEMENTS, PC_NONE, 0, LAFAYETTE)
- french(FRENCH_REINFORCEMENTS, false, 5, "Rochambeau")
+ french(FRENCH_REINFORCEMENTS, PC_NONE, 5, ROCHAMBEAU)
goto_committees_of_correspondence()
}
@@ -275,7 +218,7 @@ function deal_card() {
function last_discard() {
if (game.discard.length > 0)
return game.discard[game.discard.length - 1]
- return null
+ return 0
}
function active_hand() {
@@ -289,7 +232,7 @@ function play_card(c, reason) {
log(game.active[0] + " played #" + c)
if (CARDS[c].reshuffle === "if_played")
game.reshuffle = true
- remove_from_array(active_hand(), c)
+ array_remove_item(active_hand(), c)
game.last_played = c
if (!CARDS[c].once)
game.discard.push(c)
@@ -298,7 +241,7 @@ function play_card(c, reason) {
}
function discard_card_from_hand(hand, c) {
- remove_from_array(hand, c)
+ array_remove_item(hand, c)
game.discard.push(c)
if (CARDS[c].reshuffle === "if_discarded")
game.reshuffle = true
@@ -345,7 +288,7 @@ function can_play_reinforcements() {
if (game.played_british_reinforcements === 0) {
let n = count_british_cu(BRITISH_REINFORCEMENTS)
for (let g of BRITISH_GENERALS)
- if (game.generals[g].location === BRITISH_REINFORCEMENTS)
+ if (is_general_at_location(g, BRITISH_REINFORCEMENTS))
++n
return n > 0
}
@@ -356,14 +299,17 @@ function can_play_reinforcements() {
return false
}
+function is_map_space(s) {
+ return s >= 0 && s <= 65
+}
+
function is_port(where) {
return SPACES[where].port
}
function is_non_blockaded_port(where) {
- if (SPACES[where].port && BLOCKADE[where] !== game.french_navy)
- return true
- return false
+ let port = SPACES[where].port
+ return (port > 0 && port !== game.french_navy)
}
function is_fortified_port(where) {
@@ -376,7 +322,7 @@ function is_continental_congress_dispersed() {
function is_winter_quarter_space(where) {
let colony = SPACES[where].colony
- if (colony === "GA" || colony === "SC" || colony === "NC")
+ if (set_has(SOUTH_OF_WINTER_ATTRITION_LINE, colony))
return true // south of winter attrition line
let type = SPACES[where].type
if (type === "winter-quarters" || type === "fortified-port")
@@ -393,11 +339,11 @@ function allowed_to_place_american_pc() {
}
function is_british_militia(space) {
- return game.control[SPACES[space].colony] === BRITISH
+ return game.control[SPACES[space].colony] === PC_BRITISH
}
function is_american_militia(space) {
- return game.control[SPACES[space].colony] === AMERICAN
+ return game.control[SPACES[space].colony] === PC_AMERICAN
}
function is_american_winter_offensive() {
@@ -408,16 +354,20 @@ function is_american_winter_offensive() {
/* PC */
+function set_space_pc(space, pc) {
+ game.pc[space] = pc
+}
+
function has_no_pc(space) {
- return game.pc[space] !== BRITISH && game.pc[space] !== AMERICAN
+ return game.pc[space] === PC_NONE
}
function has_british_pc(space) {
- return game.pc[space] === BRITISH
+ return game.pc[space] === PC_BRITISH
}
function has_american_pc(space) {
- return game.pc[space] === AMERICAN
+ return game.pc[space] === PC_AMERICAN
}
function has_enemy_pc(space) {
@@ -428,11 +378,11 @@ function has_enemy_pc(space) {
}
function is_adjacent_to_british_pc(a) {
- for (let b of SPACES[a].exits)
+ for (let b of SPACES[a].adjacent)
if (has_british_pc(b))
return true
if (SPACES[a].port) {
- for (let b in SPACES) {
+ for (let b of all_spaces) {
if (SPACES[b].port)
if (has_british_pc(b))
return true
@@ -442,7 +392,7 @@ function is_adjacent_to_british_pc(a) {
}
function is_adjacent_to_american_pc(a) {
- for (let b of SPACES[a].exits)
+ for (let b of SPACES[a].adjacent)
if (has_american_pc(b))
return true
return false
@@ -451,13 +401,13 @@ function is_adjacent_to_american_pc(a) {
function place_british_pc(space) {
logp("placed PC in " + space)
if (game.british_pc_space_list)
- remove_from_array(game.british_pc_space_list, space)
- game.pc[space] = BRITISH
+ array_remove_item(game.british_pc_space_list, space)
+ set_space_pc(space, PC_BRITISH)
}
function place_american_pc(space) {
logp("placed PC in " + space)
- game.pc[space] = AMERICAN
+ set_space_pc(space, PC_AMERICAN)
}
function remove_pc(space) {
@@ -465,7 +415,7 @@ function remove_pc(space) {
logp("removed PC in " + space)
else
logp("removed PC in " + space)
- game.pc[space] = undefined
+ set_space_pc(space, PC_NONE)
}
function flip_pc(space) {
@@ -473,24 +423,27 @@ function flip_pc(space) {
logp("flipped PC in " + space)
else
logp("flipped PC in " + space)
- game.pc[space] = ENEMY[game.pc[space]]
+ if (has_british_pc(space))
+ set_space_pc(space, PC_AMERICAN)
+ else
+ set_space_pc(space, PC_BRITISH)
}
function update_colony_control() {
- for (let c in COLONIES) {
+ for (let c = 0; c <= 13; ++c) {
let control = 0
for (let space of COLONIES[c]) {
- if (game.pc[space] === BRITISH)
+ if (has_british_pc(space))
--control
- else if (game.pc[space] === AMERICAN)
+ else if (has_american_pc(space))
++control
}
if (control < 0)
- game.control[c] = BRITISH
+ game.control[c] = PC_BRITISH
else if (control > 0)
- game.control[c] = AMERICAN
+ game.control[c] = PC_AMERICAN
else
- game.control[c] = undefined
+ game.control[c] = PC_NONE
}
}
@@ -505,6 +458,11 @@ function find_cu(owner, space) {
return null
}
+function reset_moved_cu() {
+ for (let cu of game.cu)
+ cu.moved = 0
+}
+
function find_british_cu(space) {
return find_cu(BRITISH, space)
}
@@ -605,7 +563,7 @@ function remove_cu(owner, where, count) {
let cu = find_cu(owner, where)
if (count >= cu.count) {
let i = game.cu.indexOf(cu)
- remove_from_array(game.cu, cu)
+ array_remove_item(game.cu, cu)
} else {
cu.count -= count
}
@@ -655,38 +613,54 @@ function move_british_cu(from, to, count) {
/* GENERALS */
+function location_of_general(g) {
+ return game.generals[g]
+}
+
+function set_general_location(g, s) {
+ game.generals[g] = s
+}
+
+function is_general_at_location(g, s) {
+ return location_of_general(g) === s
+}
+
function is_general_on_map(g) {
- switch (game.generals[g].location) {
- case null: /* killed */
- case CAPTURED_GENERALS:
- case BRITISH_REINFORCEMENTS:
- case AMERICAN_REINFORCEMENTS:
- case FRENCH_REINFORCEMENTS:
- return false
- }
- return true
+ return is_map_space(location_of_general(g))
+}
+
+function has_general_moved(g) {
+ return set_has(game.moved, g)
+}
+
+function set_general_moved(g) {
+ set_add(game.moved, g)
+}
+
+function reset_moved_generals() {
+ set_clear(game.moved)
}
function find_british_general(where) {
for (let general of BRITISH_GENERALS)
- if (game.generals[general].location === where)
+ if (is_general_at_location(general, where))
return general
- return null
+ return NOBODY
}
function find_american_or_french_general(where) {
for (let general of AMERICAN_GENERALS)
- if (game.generals[general].location === where)
+ if (is_general_at_location(general, where))
return general
- return null
+ return NOBODY
}
function has_british_general(where) {
- return find_british_general(where) !== null
+ return find_british_general(where) !== NOBODY
}
function has_american_or_french_general(where) {
- return find_american_or_french_general(where) !== null
+ return find_american_or_french_general(where) !== NOBODY
}
function has_enemy_general(where) {
@@ -704,7 +678,7 @@ function count_friendly_generals(where) {
list = AMERICAN_GENERALS
let count = 0
for (let g of list)
- if (location_of_general(g) === where)
+ if (is_general_at_location(g, where))
++count
return count
}
@@ -754,11 +728,11 @@ function can_activate_american_general(c) {
}
function move_general(who, where) {
- game.generals[who].location = where
+ set_general_location(who, where)
}
function capture_washington() {
- game.generals[WASHINGTON].location = null
+ set_general_location(WASHINGTON, ELIMINATED)
if (!game.french_alliance_triggered) {
game.french_alliance -= 3
@@ -792,9 +766,9 @@ function capture_enemy_general(where) {
}
function remove_benedict_arnold() {
- if (game.generals[ARNOLD].location) {
+ if (!is_general_at_location(ARNOLD, ELIMINATED)) {
log("Removed Arnold from the game!")
- game.generals[ARNOLD].location = null
+ set_general_location(ARNOLD, ELIMINATED)
}
}
@@ -830,10 +804,10 @@ function has_no_american_unit(where) {
function place_british_reinforcements(who, count, where) {
let already_there = find_british_general(where)
- if (who && already_there) {
+ if (who !== NOBODY && already_there !== NOBODY) {
move_general(already_there, BRITISH_REINFORCEMENTS)
}
- if (who) {
+ if (who !== NOBODY) {
logp("reinforced " + where + " with " + who)
move_general(who, where)
}
@@ -848,15 +822,16 @@ function place_british_reinforcements(who, count, where) {
}
function place_american_reinforcements(who, count, where) {
+console.log("PLACE AM", who, count, where)
let already_there = find_american_or_french_general(where)
- if (who && already_there) {
+ if (who !== NOBODY && already_there !== NOBODY) {
// Never replace Washington
if (already_there === WASHINGTON)
- who = null
+ who = NOBODY
else
move_general(already_there, AMERICAN_REINFORCEMENTS)
}
- if (who) {
+ if (who !== NOBODY) {
logp("reinforced " + where + " with " + who)
move_general(who, where)
}
@@ -868,14 +843,14 @@ function place_american_reinforcements(who, count, where) {
function place_french_reinforcements(who, where) {
let already_there = find_american_or_french_general(where)
- if (who && already_there) {
+ if (who !== NOBODY && already_there !== NOBODY) {
// Never replace Washington
if (already_there === WASHINGTON)
- who = null
+ who = NOBODY
else
move_general(already_there, AMERICAN_REINFORCEMENTS)
}
- if (who) {
+ if (who !== NOBODY) {
logp("reinforced " + where + " with " + who)
move_general(who, where)
}
@@ -884,10 +859,6 @@ function place_french_reinforcements(who, where) {
move_cu(FRENCH, AMERICAN_REINFORCEMENTS, where, count_french_cu(AMERICAN_REINFORCEMENTS))
}
-function location_of_general(g) {
- return game.generals[g].location
-}
-
function pickup_max_british_cu(where) {
game.carry_british = count_unmoved_british_cu(where)
if (game.carry_british > 5)
@@ -1017,7 +988,7 @@ function gen_remove_british_pc_from(list_of_colonies) {
}
function gen_remove_american_pc() {
- for (let space in SPACES) {
+ for (let space of all_spaces) {
if (has_american_pc(space) && has_no_american_unit(space)) {
gen_action("remove_pc", space)
}
@@ -1047,26 +1018,26 @@ function gen_remove_american_pc_from_non_port(list_of_colonies) {
}
function gen_remove_american_pc_within_two_spaces_of_a_british_general() {
- let candidates = {}
+ let candidates = []
for (let g of BRITISH_GENERALS) {
- let a = game.generals[g].location
- if (a in SPACES) {
- candidates[a] = true
- for (let b of SPACES[a].exits) {
- candidates[b] = true
- for (let c of SPACES[b].exits) {
- candidates[c] = true
+ let a = location_of_general(g)
+ if (is_map_space(a)) {
+ set_add(candidates, a)
+ for (let b of SPACES[a].adjacent) {
+ set_add(candidates, b)
+ for (let c of SPACES[b].adjacent) {
+ set_add(candidates, c)
}
}
}
}
- for (let space in candidates)
+ for (let space of candidates)
if (has_american_pc(space) && has_no_american_unit(space))
gen_action("remove_pc", space)
}
function gen_place_american_pc() {
- for (let space in SPACES) {
+ for (let space of all_spaces) {
if (has_no_pc(space) && has_no_british_playing_piece(space)) {
gen_action("place_american_pc", space)
}
@@ -1095,7 +1066,7 @@ function goto_committees_of_correspondence() {
states.committees_of_correspondence = {
inactive: "Committees of Correspondence",
- prompt: function (current) {
+ prompt(current) {
view.prompt =
"Committees of Correspondence: Place 1 PC marker in each of the 13 colonies. " + game.coc.length + " left."
if (game.coc.length > 0)
@@ -1103,13 +1074,13 @@ states.committees_of_correspondence = {
else
gen_pass()
},
- place_american_pc: function (space) {
+ place_american_pc(space) {
push_undo()
let colony = SPACES[space].colony
- remove_from_array(game.coc, colony)
+ array_remove_item(game.coc, colony)
place_american_pc(space)
},
- pass: function () {
+ pass() {
clear_undo()
goto_for_the_king()
},
@@ -1128,19 +1099,19 @@ function goto_for_the_king() {
states.for_the_king = {
inactive: "For the King",
- prompt: function (current) {
+ prompt(current) {
view.prompt = "For the King: Place 3 PC markers. " + game.count + " left."
if (game.count > 0)
gen_british_pc_ops()
else
gen_pass()
},
- place_british_pc: function (space) {
+ place_british_pc(space) {
push_undo()
place_british_pc(space)
--game.count
},
- pass: function () {
+ pass() {
clear_undo()
gen_british_pc_ops_end()
goto_start_year()
@@ -1152,7 +1123,7 @@ states.for_the_king = {
function automatic_victory() {
let n_american = 0
let n_british = 0
- for (let space in SPACES) {
+ for (let space of all_spaces) {
n_american += count_french_cu(space) + count_american_cu(space)
if (SPACES[space].colony !== "CA")
n_british += count_british_cu(space)
@@ -1183,10 +1154,10 @@ function goto_start_year() {
// Prisoner exchange
for (let g of BRITISH_GENERALS)
- if (game.generals[g].location === CAPTURED_GENERALS)
+ if (is_general_at_location(g, CAPTURED_GENERALS))
move_general(g, BRITISH_REINFORCEMENTS)
for (let g of AMERICAN_GENERALS)
- if (game.generals[g].location === CAPTURED_GENERALS)
+ if (is_general_at_location(g, CAPTURED_GENERALS))
move_general(g, AMERICAN_REINFORCEMENTS)
switch (game.year) {
@@ -1246,7 +1217,7 @@ function goto_start_year() {
}
states.british_declare_first = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Declare yourself as the first player by playing a campaign card?"
gen_pass()
for (let c of CAMPAIGN_CARDS) {
@@ -1255,13 +1226,13 @@ states.british_declare_first = {
}
}
},
- card_campaign: function (c) {
+ card_campaign(c) {
delete game.congress_was_dispersed
logp("went first by playing a campaign card")
game.active = BRITISH
goto_campaign(c)
},
- pass: function () {
+ pass() {
if (game.congress_was_dispersed)
game.active = BRITISH
else
@@ -1272,16 +1243,16 @@ states.british_declare_first = {
}
states.choose_first_player = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Choose who will play the first strategy card."
gen_action("american_first")
gen_action("british_first")
},
- american_first: function (c) {
+ american_first(c) {
logp("went first")
goto_strategy_phase(AMERICAN)
},
- british_first: function (c) {
+ british_first(c) {
logp("went first")
goto_strategy_phase(BRITISH)
},
@@ -1302,42 +1273,42 @@ function goto_strategy_phase(new_active) {
states.strategy_phase = {
inactive: "strategy phase",
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Play a strategy card."
gen_strategy_plays(active_hand())
},
- card_campaign: function (c) {
+ card_campaign(c) {
game.did_discard_event = false
clear_queue()
goto_campaign(c)
},
- card_play_event: function (c) {
+ card_play_event(c) {
push_undo()
game.did_discard_event = false
clear_queue()
do_event(c)
},
- card_discard_event: function (c) {
+ card_discard_event(c) {
push_undo()
game.did_discard_event = true
clear_queue()
discard_card(c, "PC action")
game.state = "discard_event_pc_action"
},
- card_ops_pc: function (c) {
+ card_ops_pc(c) {
push_undo()
game.did_discard_event = false
clear_queue()
play_card(c, "for PC")
goto_ops_pc(CARDS[c].count)
},
- card_ops_reinforcements: function (c) {
+ card_ops_reinforcements(c) {
push_undo()
game.did_discard_event = false
clear_queue()
goto_ops_reinforcements(c)
},
- card_ops_queue: function (c) {
+ card_ops_queue(c) {
game.did_discard_event = false
play_card(c, "to queue")
if (game.active === BRITISH)
@@ -1346,12 +1317,12 @@ states.strategy_phase = {
game.a_queue += CARDS[c].count
end_strategy_card()
},
- card_ops_general: function (c) {
+ card_ops_general(c) {
push_undo()
game.did_discard_event = false
goto_ops_general(c)
},
- exchange_for_discard: function (c) {
+ exchange_for_discard(c) {
game.did_discard_event = false
let d = game.discard.pop()
discard_card(c)
@@ -1380,7 +1351,7 @@ function end_strategy_card() {
if (!game.french_alliance_triggered && game.french_alliance === 9) {
log("The French signed an alliance with the Americans!")
game.french_alliance_triggered = true
- if (game.french_navy === FRENCH_REINFORCEMENTS) {
+ if (game.french_navy === -1) {
game.save_active = game.active
game.active = AMERICAN
game.state = "place_french_navy_trigger"
@@ -1388,9 +1359,8 @@ function end_strategy_card() {
}
}
- game.moved = {}
- for (let cu of game.cu)
- cu.moved = 0
+ reset_moved_generals()
+ reset_moved_cu()
goto_strategy_phase(ENEMY[game.active])
@@ -1401,6 +1371,7 @@ function end_strategy_card() {
if (hand.length === 0)
return goto_winter_attrition_phase()
}
+
}
function clear_queue() {
@@ -1455,7 +1426,7 @@ function gen_strategy_plays(hand) {
/* DISCARD EVENT CARD FOR PC ACTION */
states.discard_event_pc_action = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Place, flip, or remove PC marker."
gen_pass()
if (game.active === BRITISH)
@@ -1463,29 +1434,29 @@ states.discard_event_pc_action = {
else
gen_american_discard_event_pc_action()
},
- place_british_pc: function (space) {
+ place_british_pc(space) {
place_british_pc(space)
end_strategy_card()
},
- place_american_pc: function (space) {
+ place_american_pc(space) {
place_american_pc(space)
end_strategy_card()
},
- remove_pc: function (space) {
+ remove_pc(space) {
remove_pc(space)
end_strategy_card()
},
- flip_pc: function (space) {
+ flip_pc(space) {
flip_pc(space)
end_strategy_card()
},
- pass: function () {
+ pass() {
end_strategy_card()
},
}
function gen_british_discard_event_pc_action() {
- for (let space in SPACES) {
+ for (let space of all_spaces) {
if (is_adjacent_to_british_pc(space)) {
if (has_no_pc(space) && has_no_american_unit(space))
gen_action("place_british_pc", space)
@@ -1498,7 +1469,7 @@ function gen_british_discard_event_pc_action() {
}
function gen_american_discard_event_pc_action() {
- for (let space in SPACES) {
+ for (let space of all_spaces) {
if (is_adjacent_to_american_pc(space)) {
if (has_no_pc(space) && has_no_british_cu(space)) {
if (allowed_to_place_american_pc())
@@ -1522,7 +1493,7 @@ function goto_ops_pc(count) {
}
states.ops_pc = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Place or flip PC markers. " + game.count + " left."
gen_pass()
if (game.count > 0) {
@@ -1532,22 +1503,22 @@ states.ops_pc = {
gen_american_pc_ops()
}
},
- place_british_pc: function (space) {
+ place_british_pc(space) {
push_undo()
place_british_pc(space)
--game.count
},
- place_american_pc: function (space) {
+ place_american_pc(space) {
push_undo()
place_american_pc(space)
--game.count
},
- flip_pc: function (space) {
+ flip_pc(space) {
push_undo()
flip_pc(space)
--game.count
},
- pass: function () {
+ pass() {
if (game.active === BRITISH)
gen_british_pc_ops_end()
end_strategy_card()
@@ -1556,7 +1527,7 @@ states.ops_pc = {
function gen_british_pc_ops_start() {
game.british_pc_space_list = []
- for (let space in SPACES) {
+ for (let space of all_spaces) {
if (has_no_pc(space) && has_no_american_unit(space)) {
if (is_adjacent_to_british_pc(space))
game.british_pc_space_list.push(space)
@@ -1567,7 +1538,7 @@ function gen_british_pc_ops_start() {
function gen_british_pc_ops() {
for (let space of game.british_pc_space_list)
gen_action("place_british_pc", space)
- for (let space in SPACES) {
+ for (let space of all_spaces) {
if (has_british_army(space)) {
if (has_no_pc(space))
gen_action("place_british_pc", space)
@@ -1582,7 +1553,7 @@ function gen_british_pc_ops_end(space) {
}
function gen_american_pc_ops() {
- for (let space in SPACES) {
+ for (let space of all_spaces) {
if (has_no_pc(space) && has_no_british_cu(space)) {
if (allowed_to_place_american_pc())
gen_action("place_american_pc", space)
@@ -1609,86 +1580,85 @@ function goto_ops_reinforcements(c) {
}
states.ops_british_reinforcements_who = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Reinforcements: choose an available general or pass to bring only CU."
view.prompt += " Carrying " + game.count + " British CU."
gen_pass()
gen_british_reinforcements_who()
},
- drop_british_cu: function () {
+ drop_british_cu() {
--game.count
},
- pickup_british_cu: function () {
+ pickup_british_cu() {
++game.count
},
- select_general: function (g) {
+ select_general(g) {
push_undo()
game.state = "ops_british_reinforcements_where"
game.who = g
},
- pass: function () {
+ pass() {
push_undo()
game.state = "ops_british_reinforcements_where"
- game.who = null
+ game.who = NOBODY
},
}
states.ops_british_reinforcements_where = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Reinforcements: choose a port space."
view.prompt += " Carrying " + game.count + " British CU."
gen_british_reinforcements_where()
},
- drop_british_cu: function () {
+ drop_british_cu() {
--game.count
},
- pickup_british_cu: function () {
+ pickup_british_cu() {
++game.count
},
- place_reinforcements: function (space) {
+ place_reinforcements(space) {
place_british_reinforcements(game.who, game.count, space)
end_strategy_card()
- game.who = null
+ game.who = NOBODY
},
}
states.ops_american_reinforcements_who = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Reinforcements: choose an available general or pass to bring only CU."
gen_pass()
gen_american_reinforcements_who()
},
- select_general: function (g) {
+ select_general(g) {
push_undo()
game.state = "ops_american_reinforcements_where"
game.who = g
},
- pass: function () {
+ pass() {
push_undo()
game.state = "ops_american_reinforcements_where"
- game.who = null
+ game.who = NOBODY
},
}
states.ops_american_reinforcements_where = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Reinforcements: choose a space."
gen_american_reinforcements_where(game.who)
},
- place_reinforcements: function (space) {
+ place_reinforcements(space) {
if (game.who === ROCHAMBEAU)
place_french_reinforcements(game.who, space)
else
place_american_reinforcements(game.who, game.count, space)
end_strategy_card()
- game.who = null
+ game.who = NOBODY
},
}
function gen_british_reinforcements_who() {
for (let g of BRITISH_GENERALS) {
- let general = game.generals[g]
- if (general.location === BRITISH_REINFORCEMENTS) {
+ if (is_general_at_location(g, BRITISH_REINFORCEMENTS)) {
gen_action("select_general", g)
}
}
@@ -1699,7 +1669,7 @@ function gen_british_reinforcements_who() {
}
function gen_british_reinforcements_where() {
- for (let space in SPACES) {
+ for (let space of all_spaces) {
if (is_non_blockaded_port(space))
if (!has_american_or_french_cu(space) && !has_american_pc(space))
gen_action("place_reinforcements", space)
@@ -1712,15 +1682,14 @@ function gen_british_reinforcements_where() {
function gen_american_reinforcements_who() {
for (let g of AMERICAN_GENERALS) {
- let general = game.generals[g]
- if (general.location === AMERICAN_REINFORCEMENTS) {
+ if (is_general_at_location(g, AMERICAN_REINFORCEMENTS)) {
gen_action("select_general", g)
}
}
}
function gen_american_reinforcements_where(general) {
- for (let space in SPACES) {
+ for (let space of all_spaces) {
if (!has_british_cu(space) && !has_british_pc(space)) {
if (general === ROCHAMBEAU) {
if (SPACES[space].port)
@@ -1747,7 +1716,7 @@ function goto_ops_general(c) {
}
states.ops_general_who = {
- prompt: function (current) {
+ prompt(current) {
if (game.campaign && game.landing_party)
view.prompt = "Campaign: Activate a general or use a landing party. " + game.campaign + " left."
else if (game.campaign)
@@ -1759,21 +1728,21 @@ states.ops_general_who = {
gen_activate_general()
gen_pass()
},
- place_british_pc: function (where) {
+ place_british_pc(where) {
game.landing_party = 0
place_british_pc(where)
end_strategy_card()
},
- flip_pc: function (where) {
+ flip_pc(where) {
game.landing_party = 0
flip_pc(where)
end_strategy_card()
},
- select_general: function (g) {
+ select_general(g) {
push_undo()
goto_ops_general_move(g, false)
},
- pass: function () {
+ pass() {
if (game.campaign > 0)
game.campaign = 0
end_strategy_card()
@@ -1781,7 +1750,7 @@ states.ops_general_who = {
}
function gen_landing_party() {
- for (let space in SPACES) {
+ for (let space of all_spaces) {
if (!is_fortified_port(space) && is_non_blockaded_port(space)) {
if (has_american_pc(space) && has_no_american_unit(space))
gen_action("flip_pc", space)
@@ -1800,13 +1769,13 @@ function gen_activate_general() {
function gen_activate_british_general() {
for (let g of BRITISH_GENERALS)
- if (is_general_on_map(g) && GENERALS[g].strategy <= game.count && !game.moved[g])
+ if (is_general_on_map(g) && GENERALS[g].strategy <= game.count && !has_general_moved(g))
gen_action("select_general", g)
}
function gen_activate_american_general() {
for (let g of AMERICAN_GENERALS)
- if (is_general_on_map(g) && GENERALS[g].strategy <= game.count && !game.moved[g])
+ if (is_general_on_map(g) && GENERALS[g].strategy <= game.count && !has_general_moved(g))
gen_action("select_general", g)
}
@@ -1816,11 +1785,11 @@ function goto_remove_general(where) {
}
states.remove_general = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Remove a general to the reinforcements box."
gen_remove_general()
},
- select_general: function (g) {
+ select_general(g) {
if (game.active === BRITISH)
move_general(g, BRITISH_REINFORCEMENTS)
else
@@ -1834,11 +1803,11 @@ function goto_remove_general_after_intercept() {
}
states.remove_general_after_intercept = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Remove a general to the reinforcements box."
gen_remove_general()
},
- select_general: function (g) {
+ select_general(g) {
if (game.active === BRITISH)
move_general(g, BRITISH_REINFORCEMENTS)
else
@@ -1853,11 +1822,11 @@ function goto_remove_general_after_retreat(where) {
}
states.remove_general_after_retreat = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Remove a general to the reinforcements box."
gen_remove_general()
},
- select_general: function (g) {
+ select_general(g) {
if (game.active === BRITISH)
move_general(g, BRITISH_REINFORCEMENTS)
else
@@ -1875,14 +1844,14 @@ function gen_remove_general() {
function gen_remove_british_general() {
for (let g of BRITISH_GENERALS)
- if (location_of_general(g) === game.where)
+ if (is_general_at_location(g, game.where))
gen_action("select_general", g)
}
function gen_remove_american_general() {
for (let g of AMERICAN_GENERALS)
if (g !== WASHINGTON)
- if (location_of_general(g) === game.where)
+ if (is_general_at_location(g, game.where))
gen_action("select_general", g)
}
@@ -1909,7 +1878,7 @@ function goto_ops_general_move(g, marblehead) {
}
states.ops_general_move = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Move " + game.who + " with "
if (game.carry_british > 0) {
view.prompt += game.carry_british + " British CU."
@@ -1940,45 +1909,45 @@ states.ops_general_move = {
gen_move_general()
},
- pickup_british_cu: function () {
+ pickup_british_cu() {
++game.carry_british
},
- pickup_american_cu: function () {
+ pickup_american_cu() {
++game.carry_american
},
- pickup_french_cu: function () {
+ pickup_french_cu() {
++game.carry_french
},
- drop_british_cu: function () {
+ drop_british_cu() {
push_undo()
--game.carry_british
- if (game.moved[game.who])
+ if (has_general_moved(game.who))
mark_moved_cu(BRITISH, location_of_general(game.who), 1)
},
- drop_american_cu: function () {
+ drop_american_cu() {
push_undo()
--game.carry_american
- if (game.moved[game.who])
+ if (has_general_moved(game.who))
mark_moved_cu(AMERICAN, location_of_general(game.who), 1)
},
- drop_french_cu: function () {
+ drop_french_cu() {
push_undo()
--game.carry_french
- if (game.moved[game.who])
+ if (has_general_moved(game.who))
mark_moved_cu(FRENCH, location_of_general(game.who), 1)
},
- move: function (to) {
+ move(to) {
push_undo()
- game.moved[game.who] = 1
+ set_general_moved(game.who)
let from = location_of_general(game.who)
let cu = game.carry_british + game.carry_american + game.carry_french
let intercept = false
if (game.active === BRITISH) {
- let is_sea_move = path_type(from, to) === undefined
+ let is_sea_move = path_type(from, to) === "sea"
if (has_american_pc(to) && cu > 0 && !is_sea_move && !has_british_cu(to))
intercept = can_intercept_to(to)
}
@@ -2002,7 +1971,7 @@ states.ops_general_move = {
else
resume_moving(from, to)
},
- pass: function () {
+ pass() {
clear_undo()
let where = location_of_general(game.who)
end_move()
@@ -2021,10 +1990,10 @@ function resume_moving(from, to) {
}
function can_intercept_to(to) {
- for (let space of SPACES[to].exits) {
+ for (let space of SPACES[to].adjacent) {
if (has_american_army(space)) {
let g = find_american_or_french_general(space)
- if (g && !game.moved[g])
+ if (g && !has_general_moved(g))
return true
}
}
@@ -2032,10 +2001,10 @@ function can_intercept_to(to) {
}
function gen_intercept() {
- for (let space of SPACES[game.where].exits) {
+ for (let space of SPACES[game.where].adjacent) {
if (has_american_army(space)) {
let g = find_american_or_french_general(space)
- if (g && !game.moved[g])
+ if (g && !has_general_moved(g))
gen_action("select_general", g)
}
}
@@ -2044,7 +2013,7 @@ function gen_intercept() {
function goto_intercept(from, where) {
clear_undo()
game.save_who = game.who
- game.who = null
+ game.who = NOBODY
game.from = from
game.where = where
game.active = AMERICAN
@@ -2052,13 +2021,13 @@ function goto_intercept(from, where) {
}
states.intercept = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Intercept " + game.save_who + " in " + game.where + "?"
gen_pass()
gen_intercept()
},
- select_general: function (g) {
- game.moved[g] = 1
+ select_general(g) {
+ set_general_moved(g)
let die = roll_d6()
if (die <= GENERALS[g].agility) {
log(g + " intercepted (" + die + " <= " + GENERALS[g].agility + ")")
@@ -2085,7 +2054,7 @@ states.intercept = {
end_intercept()
}
},
- pass: function () {
+ pass() {
end_intercept()
},
}
@@ -2101,12 +2070,12 @@ function end_intercept() {
function end_move() {
let where = location_of_general(game.who)
- if (game.moved[game.who]) {
+ if (has_general_moved(game.who)) {
mark_moved_cu(BRITISH, where, game.carry_british)
mark_moved_cu(AMERICAN, where, game.carry_american)
mark_moved_cu(FRENCH, where, game.carry_french)
}
- game.who = null
+ game.who = NOBODY
delete game.mobility
delete game.carry_british
delete game.carry_american
@@ -2114,11 +2083,11 @@ function end_move() {
}
function path_type(from, to) {
- return PATH_TYPE[PATH_INDEX[from][to]]
-}
-
-function path_name(from, to) {
- return PATH_NAME[PATH_INDEX[from][to]]
+ if (set_has(SPACES[from].path, to))
+ return "path"
+ if (set_has(SPACES[from].wilderness, to))
+ return "wilderness"
+ return "sea"
}
function gen_carry_cu() {
@@ -2143,22 +2112,24 @@ function gen_carry_cu() {
function movement_cost(from, to) {
switch (path_type(from, to)) {
- case undefined:
+ case "sea":
return 4 /* must be a sea connection if no direct path */
case "wilderness":
return 3
- default:
+ case "path":
return 1
+ default:
+ throw "IMPOSSIBLE"
}
}
function gen_move_general() {
let from = location_of_general(game.who)
let alone = game.carry_british + game.carry_american + game.carry_french === 0
- for (let to of SPACES[from].exits) {
+ for (let to of SPACES[from].adjacent) {
let mp = 1
if (path_type(from, to) === "wilderness") {
- if (path_name(from, to) === FALMOUTH_QUEBEC)
+ if ((from === QUEBEC && to === FALMOUTH) || (to === QUEBEC && from === FALMOUTH))
if (game.who !== ARNOLD)
continue
mp = 3
@@ -2184,7 +2155,7 @@ function gen_move_general() {
}
if (game.active === BRITISH && game.count === 4) {
if (is_non_blockaded_port(from)) {
- for (let to in SPACES) {
+ for (let to of all_spaces) {
if (to !== from) {
if (is_non_blockaded_port(to)) {
if (!has_american_pc(to) && !has_american_or_french_cu(to)) {
@@ -2218,7 +2189,7 @@ events.the_war_ends = function (c, card) {
logp("played #" + c)
log("The war will end in " + card.year)
game.last_played = c
- remove_from_array(active_hand(), c)
+ array_remove_item(active_hand(), c)
if (game.war_ends)
game.discard.push(WAR_ENDS_1779 + game.war_ends - 1779)
game.war_ends = card.year
@@ -2284,7 +2255,7 @@ events.advance_french_alliance = function (c, card) {
events.remove_french_navy = function (c, card) {
play_card(c)
- game.french_navy = TURN_TRACK[game.year + 1]
+ game.french_navy = game.year + 1
end_strategy_card()
}
@@ -2296,19 +2267,19 @@ events.remove_british_pc_from = function (c, card) {
}
states.remove_british_pc_from = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Remove British PC markers from " + game.where.join(", ") + ". " + game.count + " left."
gen_pass()
gen_remove_british_pc_from(game.where)
},
- remove_pc: function (where) {
+ remove_pc(where) {
remove_pc(where)
if (--game.count === 0) {
delete game.where
end_strategy_card()
}
},
- pass: function () {
+ pass() {
delete game.where
end_strategy_card()
},
@@ -2321,18 +2292,18 @@ events.remove_american_pc = function (c, card) {
}
states.remove_american_pc = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Remove American PC markers. " + game.count + " left."
gen_pass()
gen_remove_american_pc()
},
- remove_pc: function (where) {
+ remove_pc(where) {
remove_pc(where)
if (--game.count === 0) {
end_strategy_card()
}
},
- pass: function () {
+ pass() {
end_strategy_card()
},
}
@@ -2345,19 +2316,19 @@ events.remove_american_pc_from = function (c, card) {
}
states.remove_american_pc_from = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Remove American PC markers from " + game.where.join(", ") + ". " + game.count + " left."
gen_pass()
gen_remove_american_pc_from(game.where)
},
- remove_pc: function (where) {
+ remove_pc(where) {
remove_pc(where)
if (--game.count === 0) {
delete game.where
end_strategy_card()
}
},
- pass: function () {
+ pass() {
delete game.where
end_strategy_card()
},
@@ -2371,20 +2342,20 @@ events.remove_american_pc_from_non_port = function (c, card) {
}
states.remove_american_pc_from_non_port = {
- prompt: function (current) {
+ prompt(current) {
view.prompt =
"Remove American PC markers from non-Port space in " + game.where.join(", ") + ". " + game.count + " left."
gen_pass()
gen_remove_american_pc_from_non_port(game.where)
},
- remove_pc: function (where) {
+ remove_pc(where) {
remove_pc(where)
if (--game.count === 0) {
delete game.where
end_strategy_card()
}
},
- pass: function () {
+ pass() {
delete game.where
end_strategy_card()
},
@@ -2397,19 +2368,19 @@ events.remove_american_pc_within_two_spaces_of_a_british_general = function (c,
}
states.remove_american_pc_within_two_spaces_of_a_british_general = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Remove American PC markers within two spaces of a British general. " + game.count + " left."
gen_pass()
gen_remove_american_pc_within_two_spaces_of_a_british_general()
},
- remove_pc: function (where) {
+ remove_pc(where) {
remove_pc(where)
if (--game.count === 0) {
delete game.where
end_strategy_card()
}
},
- pass: function () {
+ pass() {
delete game.where
end_strategy_card()
},
@@ -2422,18 +2393,18 @@ events.place_american_pc = function (c, card) {
}
states.place_american_pc = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Place American PC markers. " + game.count + " left."
gen_pass()
gen_place_american_pc()
},
- place_american_pc: function (where) {
+ place_american_pc(where) {
place_american_pc(where)
if (--game.count === 0) {
end_strategy_card()
}
},
- pass: function () {
+ pass() {
end_strategy_card()
},
}
@@ -2446,19 +2417,19 @@ events.place_american_pc_in = function (c, card) {
}
states.place_american_pc_in = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Place American PC markers in " + game.where.join(", ") + ". " + game.count + " left."
gen_pass()
gen_place_american_pc_in(game.where)
},
- place_american_pc: function (where) {
+ place_american_pc(where) {
place_american_pc(where)
if (--game.count === 0) {
delete game.where
end_strategy_card()
}
},
- pass: function () {
+ pass() {
delete game.where
end_strategy_card()
},
@@ -2468,32 +2439,32 @@ events.lord_sandwich_coastal_raids = function (c, card) {
play_card(c)
game.state = "lord_sandwich_coastal_raids"
game.count = 2
- game.where = null
+ game.where = NOWHERE
}
states.lord_sandwich_coastal_raids = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Remove two or flip one American PC in a port space."
gen_pass()
gen_lord_sandwich_coastal_raids(game.where)
},
- place_british_pc: function (where) {
+ place_british_pc(where) {
place_british_pc(where)
end_strategy_card()
},
- remove_pc: function (where) {
+ remove_pc(where) {
game.where = where
remove_pc(where)
if (--game.count === 0)
end_strategy_card()
},
- pass: function () {
+ pass() {
end_strategy_card()
},
}
function gen_lord_sandwich_coastal_raids(first_removed) {
- for (let space in SPACES) {
+ for (let space of all_spaces) {
if (SPACES[space].port)
if (has_american_pc(space) && has_no_american_unit(space))
gen_action("remove_pc", space)
@@ -2508,23 +2479,23 @@ events.remove_american_cu = function (c, card) {
}
states.remove_american_cu = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Remove one American CU from any space."
gen_pass()
gen_remove_american_cu()
},
- remove_cu: function (where) {
+ remove_cu(where) {
let cu = find_american_cu(where) || find_french_cu(where)
remove_cu(cu.owner, where, 1)
end_strategy_card()
},
- pass: function () {
+ pass() {
end_strategy_card()
},
}
function gen_remove_american_cu() {
- for (let space in SPACES) {
+ for (let space of all_spaces) {
if (has_american_or_french_cu(space))
gen_action("remove_cu", space)
}
@@ -2537,24 +2508,24 @@ events.remove_british_cu = function (c, card) {
}
states.remove_british_cu = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Remove " + game.count + " British CU from any space."
gen_pass()
gen_remove_british_cu()
},
- remove_cu: function (where) {
+ remove_cu(where) {
let cu = find_british_cu(where)
remove_cu(cu.owner, where, 1)
if (--game.count === 0)
end_strategy_card()
},
- pass: function () {
+ pass() {
end_strategy_card()
},
}
function gen_remove_british_cu() {
- for (let space in SPACES) {
+ for (let space of all_spaces) {
if (has_british_cu(space))
gen_action("remove_cu", space)
}
@@ -2565,29 +2536,29 @@ events.pennsylvania_and_new_jersey_line_mutinies = function (c, card) {
game.pennsylvania_and_new_jersey_line_mutinies = true
game.state = "pennsylvania_and_new_jersey_line_mutinies"
game.count = 2
- game.where = null
+ game.where = NOWHERE
}
states.pennsylvania_and_new_jersey_line_mutinies = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Remove two American CUs from the map, one each from two different spaces."
gen_pass()
gen_pennsylvania_and_new_jersey_line_mutinies(game.where)
},
- remove_cu: function (where) {
+ remove_cu(where) {
let cu = find_american_cu(where) || find_french_cu(where)
remove_cu(cu.owner, where, 1)
game.where = where
if (--game.count === 0)
end_strategy_card()
},
- pass: function () {
+ pass() {
end_strategy_card()
},
}
function gen_pennsylvania_and_new_jersey_line_mutinies(first_removed) {
- for (let space in SPACES) {
+ for (let space of all_spaces) {
if (has_american_or_french_cu(space))
if (space !== first_removed)
gen_action("remove_cu", space)
@@ -2601,11 +2572,11 @@ events.john_glovers_marblehead_regiment = function (c, card) {
}
states.john_glovers_marblehead_regiment_who = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Activate an American general."
gen_activate_general()
},
- select_general: function (g) {
+ select_general(g) {
goto_ops_general_move(g, true)
},
}
@@ -2619,20 +2590,20 @@ events.declaration_of_independence = function (c, card) {
}
states.declaration_of_independence = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Declaration of Independence: Place 1 PC marker in each of the 13 colonies. "
view.prompt += game.doi.length + " left."
gen_pass()
gen_place_american_pc_in(game.doi)
},
- place_american_pc: function (space) {
+ place_american_pc(space) {
let colony = SPACES[space].colony
- remove_from_array(game.doi, colony)
+ array_remove_item(game.doi, colony)
place_american_pc(space)
if (game.doi.length === 0)
end_declaration_of_independence()
},
- pass: function () {
+ pass() {
end_declaration_of_independence()
},
}
@@ -2656,18 +2627,18 @@ function goto_george_washington_captured() {
}
states.george_washington_captured = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "George Washington is captured! Remove American PC markers. " + game.count + " left."
gen_pass()
gen_remove_american_pc()
},
- remove_pc: function (where) {
+ remove_pc(where) {
remove_pc(where)
if (--game.count === 0) {
end_george_washington_captured()
}
},
- pass: function () {
+ pass() {
end_george_washington_captured()
},
}
@@ -2697,7 +2668,7 @@ function can_retreat_before_battle(where) {
return false
// can't retreat if attempted (successful or not) interception!
let g = find_american_or_french_general(where)
- if (g && !game.moved[g])
+ if (g && !has_general_moved(g))
return true
return false
}
@@ -2723,12 +2694,12 @@ function goto_retreat_before_battle() {
}
states.retreat_before_battle = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Attempt retreat before battle?"
gen_pass()
gen_defender_retreat()
},
- move: function (to) {
+ move(to) {
let agility = GENERALS[game.who].agility
if (GENERALS[game.who].bonus)
agility += 2
@@ -2743,7 +2714,7 @@ states.retreat_before_battle = {
end_retreat_before_battle()
}
},
- pass: function () {
+ pass() {
end_retreat_before_battle()
},
}
@@ -2759,11 +2730,11 @@ function goto_remove_general_after_retreat_before_battle(to) {
}
states.remove_general_after_retreat_before_battle = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Remove a general to the reinforcements box."
gen_remove_general()
},
- select_general: function (g) {
+ select_general(g) {
if (game.active === BRITISH)
move_general(g, BRITISH_REINFORCEMENTS)
else
@@ -2808,7 +2779,7 @@ function can_attacker_retreat() {
function gen_defender_retreat() {
let from = game.where
- for (let to of SPACES[from].exits) {
+ for (let to of SPACES[from].adjacent) {
if (can_defender_retreat(to))
gen_action("move", to)
}
@@ -2823,7 +2794,7 @@ function gen_defender_retreat() {
}
}
if (can_sea_retreat) {
- for (let to in SPACES) {
+ for (let to of all_spaces) {
if (to !== from && is_non_blockaded_port(to)) {
if (!has_american_pc(to) && !has_american_or_french_cu(to)) {
gen_action("move", to)
@@ -2840,7 +2811,7 @@ function gen_attacker_retreat() {
}
function end_retreat_before_battle() {
- game.who = null
+ game.who = NOBODY
goto_play_attacker_battle_card()
}
@@ -2850,12 +2821,12 @@ function goto_play_attacker_battle_card() {
}
states.play_attacker_battle_card = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Attack: Play or discard event for DRM."
gen_pass()
gen_battle_card()
},
- card_battle_play: function (c) {
+ card_battle_play(c) {
play_card(c, "for +2 DRM")
if (game.active === BRITISH) {
if (CARDS[c].event === "remove_benedict_arnold")
@@ -2868,7 +2839,7 @@ states.play_attacker_battle_card = {
}
goto_play_defender_battle_card()
},
- card_battle_discard: function (c) {
+ card_battle_discard(c) {
discard_card(c, "for +1 DRM")
if (game.active === BRITISH) {
game.b_draw_after_battle = true
@@ -2879,7 +2850,7 @@ states.play_attacker_battle_card = {
}
goto_play_defender_battle_card()
},
- pass: function () {
+ pass() {
goto_play_defender_battle_card()
},
}
@@ -2890,12 +2861,12 @@ function goto_play_defender_battle_card() {
}
states.play_defender_battle_card = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Defend: Play or discard event for DRM."
gen_pass()
gen_battle_card()
},
- card_battle_play: function (c) {
+ card_battle_play(c) {
play_card(c, "for +2 DRM")
if (game.active === BRITISH) {
if (CARDS[c].event === "remove_benedict_arnold")
@@ -2908,7 +2879,7 @@ states.play_defender_battle_card = {
}
resolve_battle()
},
- card_battle_discard: function (c) {
+ card_battle_discard(c) {
discard_card(c, "for +1 DRM")
if (game.active === BRITISH) {
game.b_draw_after_battle = true
@@ -2919,7 +2890,7 @@ states.play_defender_battle_card = {
}
resolve_battle()
},
- pass: function () {
+ pass() {
resolve_battle()
},
}
@@ -3005,7 +2976,7 @@ function apply_british_combat_losses(max) {
cu.count -= max
return max
}
- remove_from_array(game.cu, cu)
+ array_remove_item(game.cu, cu)
return cu.count
}
@@ -3016,7 +2987,7 @@ function apply_american_combat_losses(max) {
cu.count -= max
return max
}
- remove_from_array(game.cu, cu)
+ array_remove_item(game.cu, cu)
return cu.count
}
return 0
@@ -3029,7 +3000,7 @@ function apply_french_combat_losses(max) {
cu.count -= max
return max
}
- remove_from_array(game.cu, cu)
+ array_remove_item(game.cu, cu)
return cu.count
}
return 0
@@ -3176,11 +3147,11 @@ function resolve_battle() {
function goto_retreat_after_battle(victor) {
if (victor === BRITISH) {
game.who = find_american_or_french_general(game.where)
- if (game.who === null && count_american_and_french_cu(game.where) === 0)
+ if (game.who === NOBODY && count_american_and_french_cu(game.where) === 0)
return end_battle()
} else {
game.who = find_british_general(game.where)
- if (game.who === null && count_british_cu(game.where) === 0)
+ if (game.who === NOBODY && count_british_cu(game.where) === 0)
return end_battle()
}
game.active = ENEMY[victor]
@@ -3188,7 +3159,7 @@ function goto_retreat_after_battle(victor) {
}
states.retreat_after_battle = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Retreat after battle."
gen_action("surrender")
if (game.active === game.attacker)
@@ -3196,7 +3167,7 @@ states.retreat_after_battle = {
else
gen_defender_retreat()
},
- move: function (to) {
+ move(to) {
logp("retreated to " + to)
if (game.active === BRITISH)
retreat_british_army(game.where, to)
@@ -3207,7 +3178,7 @@ states.retreat_after_battle = {
else
end_battle()
},
- surrender: function () {
+ surrender() {
// End battle here, so if Washington is captured we can handle the interrupt state.
let active = game.active
let where = game.where
@@ -3244,8 +3215,8 @@ function end_battle() {
delete game.attack_from
delete game.british_losses
delete game.attacker
- game.where = null
- game.who = null
+ game.where = NOWHERE
+ game.who = NOBODY
end_strategy_card()
}
@@ -3270,12 +3241,12 @@ function goto_winter_attrition_phase() {
logbr()
log("Winter Attrition")
- for (let space in SPACES) {
+ for (let space of all_spaces) {
let wq = is_winter_quarter_space(space)
let n_british = count_british_cu(space)
let n_american = count_american_cu(space)
let n_french = count_french_cu(space)
- let has_washington = game.generals[WASHINGTON].location === space
+ let has_washington = is_general_at_location(WASHINGTON, space)
if (n_british === 1 && !wq)
apply_single_winter_attrition(BRITISH, space, has_british_general(space))
@@ -3323,7 +3294,7 @@ function goto_winter_attrition_phase() {
}
function goto_french_naval_phase() {
- if (game.french_navy !== FRENCH_REINFORCEMENTS) {
+ if (game.french_navy !== -1) {
game.active = AMERICAN
game.state = "place_french_navy"
} else {
@@ -3342,11 +3313,11 @@ function gen_place_french_navy() {
}
states.place_french_navy_trigger = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Place the French Navy in a blockade zone."
gen_place_french_navy()
},
- place_navy: function (zone) {
+ place_navy(zone) {
logp("placed French Navy.")
game.french_navy = zone
game.active = game.save_active
@@ -3356,11 +3327,11 @@ states.place_french_navy_trigger = {
}
states.place_french_navy = {
- prompt: function (current) {
+ prompt(current) {
view.prompt = "Place the French Navy in a blockade zone."
gen_place_french_navy()
},
- place_navy: function (zone) {
+ place_navy(zone) {
logp("placed French Navy.")
game.french_navy = zone
goto_political_control_phase()
@@ -3368,7 +3339,7 @@ states.place_french_navy = {
}
function place_pc_markers_segment() {
- for (let space in SPACES) {
+ for (let space of all_spaces) {
if (has_american_army(space)) {
if (has_no_pc(space))
place_american_pc(space)
@@ -3423,22 +3394,24 @@ function is_british_pc_path(space) {
}
function spread_american_path(seen, from) {
- for (let to of SPACES[from].exits) {
- if (to in seen)
+ // TODO: BFS
+ for (let to of SPACES[from].adjacent) {
+ if (set_has(seen, to))
continue
if (is_american_pc_path(to)) {
- seen[to] = 1
+ set_add(seen, to)
spread_american_path(seen, to)
}
}
}
function spread_british_path(seen, from) {
- for (let to of SPACES[from].exits) {
- if (to in seen)
+ // TODO: BFS
+ for (let to of SPACES[from].adjacent) {
+ if (set_has(seen, to))
continue
if (is_british_pc_path(to)) {
- seen[to] = 1
+ set_add(seen, to)
spread_british_path(seen, to)
}
}
@@ -3446,35 +3419,35 @@ function spread_british_path(seen, from) {
function remove_isolated_american_pc_segment() {
log("Removed isolated American PC")
- let seen = {}
- for (let space in SPACES) {
+ let seen = []
+ for (let space of all_spaces) {
if (is_american_pc_root(space)) {
- seen[space] = 1
+ set_add(seen, space)
spread_american_path(seen, space)
}
}
- for (let space in SPACES)
- if (has_american_pc(space) && !seen[space])
+ for (let space of all_spaces)
+ if (has_american_pc(space) && !set_has(seen, space))
remove_pc(space)
}
function remove_isolated_british_pc_segment() {
log("Removed isolated British PC")
- let seen = {}
- for (let space in SPACES) {
+ let seen = []
+ for (let space of all_spaces) {
if (is_british_pc_root(space)) {
- seen[space] = 1
+ set_add(seen, space)
spread_british_path(seen, space)
}
}
- for (let space in SPACES)
- if (has_british_pc(space) && !seen[space])
+ for (let space of all_spaces)
+ if (has_british_pc(space) && !set_has(seen, space))
remove_pc(space)
}
function gen_place_continental_congress() {
let n = 0
- for (let space in SPACES) {
+ for (let space of all_spaces) {
if (SPACES[space].colony !== "CA") {
if (has_american_pc(space) && has_no_british_playing_piece(space)) {
gen_action("place_continental_congress", space)
@@ -3495,21 +3468,22 @@ function goto_political_control_phase() {
}
states.return_continental_congress = {
- prompt: function () {
+ prompt() {
view.prompt = "Return Continental Congress to a space in the 13 colonies."
if (gen_place_continental_congress() === 0)
gen_pass()
},
- place_continental_congress: function (where) {
+ place_continental_congress(where) {
game.congress = where
goto_political_control_phase_2()
},
- pass: function () {
+ pass() {
goto_political_control_phase_2()
},
}
function goto_political_control_phase_2() {
+ // TODO: manually place and remove
place_pc_markers_segment()
remove_isolated_american_pc_segment()
remove_isolated_british_pc_segment()
@@ -3517,18 +3491,18 @@ function goto_political_control_phase_2() {
}
states.european_war = {
- prompt: function () {
+ prompt() {
view.prompt = "European War: Remove 2 British CU from any spaces. " + game.count + " left."
gen_pass()
gen_remove_british_cu()
},
- remove_cu: function (where) {
+ remove_cu(where) {
let cu = find_british_cu(where)
remove_cu(BRITISH, where, 1)
if (--game.count === 0)
goto_end_phase()
},
- pass: function () {
+ pass() {
goto_end_phase()
},
}
@@ -3537,8 +3511,8 @@ function norths_government_falls() {
update_colony_control()
let n_american = 0
- for (let c in COLONIES)
- if (game.control[c] === AMERICAN)
+ for (let c = 0; c <= 13; ++C)
+ if (game.control[c] === PC_AMERICAN)
++n_american
if (n_american >= 7)
@@ -3573,7 +3547,7 @@ function goto_end_phase() {
}
states.game_over = {
- prompt: function () {
+ prompt() {
view.prompt = game.victory
},
}
@@ -3659,3 +3633,347 @@ exports.view = function (state, current) {
return view
}
+
+
+// === COMMON LIBRARY ===
+
+function log(s) {
+ game.log.push(s)
+}
+
+function logp(s) {
+ game.log.push(game.active + " " + s)
+}
+
+function logbr(s) {
+ if (game.log.length > 0 && game.log[game.log.length - 1] !== "")
+ game.log.push("")
+}
+
+function clear_undo() {
+ if (game.undo) {
+ game.undo.length = 0
+ }
+}
+
+function push_undo() {
+ if (game.undo) {
+ let copy = {}
+ for (let k in game) {
+ let v = game[k]
+ if (k === "undo")
+ continue
+ else if (k === "log")
+ v = v.length
+ else if (typeof v === "object" && v !== null)
+ v = object_copy(v)
+ copy[k] = v
+ }
+ game.undo.push(copy)
+ }
+}
+
+function pop_undo() {
+ if (game.undo) {
+ let save_log = game.log
+ let save_undo = game.undo
+ game = save_undo.pop()
+ save_log.length = game.log
+ game.log = save_log
+ game.undo = save_undo
+ }
+}
+
+function random(range) {
+ // An MLCG using integer arithmetic with doubles.
+ // https://www.ams.org/journals/mcom/1999-68-225/S0025-5718-99-00996-5/S0025-5718-99-00996-5.pdf
+ // m = 2**35 − 31
+ return (game.seed = game.seed * 200105 % 34359738337) % range
+}
+
+function random_bigint(range) {
+ // Largest MLCG that will fit its state in a double.
+ // Uses BigInt for arithmetic, so is an order of magnitude slower.
+ // https://www.ams.org/journals/mcom/1999-68-225/S0025-5718-99-00996-5/S0025-5718-99-00996-5.pdf
+ // m = 2**53 - 111
+ return (game.seed = Number(BigInt(game.seed) * 5667072534355537n % 9007199254740881n)) % range
+}
+
+function shuffle(list) {
+ // Fisher-Yates shuffle
+ for (let i = list.length - 1; i > 0; --i) {
+ let j = random(i + 1)
+ let tmp = list[j]
+ list[j] = list[i]
+ list[i] = tmp
+ }
+}
+
+function shuffle_bigint(list) {
+ // Fisher-Yates shuffle
+ for (let i = list.length - 1; i > 0; --i) {
+ let j = random_bigint(i + 1)
+ let tmp = list[j]
+ list[j] = list[i]
+ list[i] = tmp
+ }
+}
+
+// Fast deep copy for objects without cycles
+function object_copy(original) {
+ if (Array.isArray(original)) {
+ let n = original.length
+ let copy = new Array(n)
+ for (let i = 0; i < n; ++i) {
+ let v = original[i]
+ if (typeof v === "object" && v !== null)
+ copy[i] = object_copy(v)
+ else
+ copy[i] = v
+ }
+ return copy
+ } else {
+ let copy = {}
+ for (let i in original) {
+ let v = original[i]
+ if (typeof v === "object" && v !== null)
+ copy[i] = object_copy(v)
+ else
+ copy[i] = v
+ }
+ return copy
+ }
+}
+
+// Array remove and insert (faster than splice)
+
+function array_remove(array, index) {
+ let n = array.length
+ for (let i = index + 1; i < n; ++i)
+ array[i - 1] = array[i]
+ array.length = n - 1
+}
+
+function array_remove_item(array, item) {
+ let n = array.length
+ for (let i = 0; i < n; ++i)
+ if (array[i] === item)
+ return array_remove(array, i)
+}
+
+function array_insert(array, index, item) {
+ for (let i = array.length; i > index; --i)
+ array[i] = array[i - 1]
+ array[index] = item
+}
+
+function array_remove_pair(array, index) {
+ let n = array.length
+ for (let i = index + 2; i < n; ++i)
+ array[i - 2] = array[i]
+ array.length = n - 2
+}
+
+function array_insert_pair(array, index, key, value) {
+ for (let i = array.length; i > index; i -= 2) {
+ array[i] = array[i-2]
+ array[i+1] = array[i-1]
+ }
+ array[index] = key
+ array[index+1] = value
+}
+
+// Set as plain sorted array
+
+function set_clear(set) {
+ set.length = 0
+}
+
+function set_has(set, item) {
+ let a = 0
+ let b = set.length - 1
+ while (a <= b) {
+ let m = (a + b) >> 1
+ let x = set[m]
+ if (item < x)
+ b = m - 1
+ else if (item > x)
+ a = m + 1
+ else
+ return true
+ }
+ return false
+}
+
+function set_add(set, item) {
+ let a = 0
+ let b = set.length - 1
+ while (a <= b) {
+ let m = (a + b) >> 1
+ let x = set[m]
+ if (item < x)
+ b = m - 1
+ else if (item > x)
+ a = m + 1
+ else
+ return
+ }
+ array_insert(set, a, item)
+}
+
+function set_delete(set, item) {
+ let a = 0
+ let b = set.length - 1
+ while (a <= b) {
+ let m = (a + b) >> 1
+ let x = set[m]
+ if (item < x)
+ b = m - 1
+ else if (item > x)
+ a = m + 1
+ else {
+ array_remove(set, m)
+ return
+ }
+ }
+}
+
+function set_toggle(set, item) {
+ let a = 0
+ let b = set.length - 1
+ while (a <= b) {
+ let m = (a + b) >> 1
+ let x = set[m]
+ if (item < x)
+ b = m - 1
+ else if (item > x)
+ a = m + 1
+ else {
+ array_remove(set, m)
+ return
+ }
+ }
+ array_insert(set, a, item)
+}
+
+// Map as plain sorted array of key/value pairs
+
+function map_clear(map) {
+ map.length = 0
+}
+
+function map_has(map, key) {
+ let a = 0
+ let b = (map.length >> 1) - 1
+ while (a <= b) {
+ let m = (a + b) >> 1
+ let x = map[m<<1]
+ if (key < x)
+ b = m - 1
+ else if (key > x)
+ a = m + 1
+ else
+ return true
+ }
+ return false
+}
+
+function map_get(map, key, missing) {
+ let a = 0
+ let b = (map.length >> 1) - 1
+ while (a <= b) {
+ let m = (a + b) >> 1
+ let x = map[m<<1]
+ if (key < x)
+ b = m - 1
+ else if (key > x)
+ a = m + 1
+ else
+ return map[(m<<1)+1]
+ }
+ return missing
+}
+
+function map_set(map, key, value) {
+ let a = 0
+ let b = (map.length >> 1) - 1
+ while (a <= b) {
+ let m = (a + b) >> 1
+ let x = map[m<<1]
+ if (key < x)
+ b = m - 1
+ else if (key > x)
+ a = m + 1
+ else {
+ map[(m<<1)+1] = value
+ return
+ }
+ }
+ array_insert_pair(map, a<<1, key, value)
+}
+
+function map_delete(map, key) {
+ let a = 0
+ let b = (map.length >> 1) - 1
+ while (a <= b) {
+ let m = (a + b) >> 1
+ let x = map[m<<1]
+ if (key < x)
+ b = m - 1
+ else if (key > x)
+ a = m + 1
+ else {
+ array_remove_pair(map, m<<1)
+ return
+ }
+ }
+}
+
+function object_diff(a, b) {
+ if (a === b)
+ return false
+ if (a !== null && b !== null && typeof a === "object" && typeof b === "object") {
+ if (Array.isArray(a)) {
+ if (!Array.isArray(b))
+ return true
+ let a_length = a.length
+ if (b.length !== a_length)
+ return true
+ for (let i = 0; i < a_length; ++i)
+ if (object_diff(a[i], b[i]))
+ return true
+ return false
+ }
+ for (let key in a)
+ if (object_diff(a[key], b[key]))
+ return true
+ for (let key in b)
+ if (!(key in a))
+ return true
+ return false
+ }
+ return true
+}
+
+// same as Object.groupBy
+function object_group_by(items, callback) {
+ let groups = {}
+ if (typeof callback === "function") {
+ for (let item of items) {
+ let key = callback(item)
+ if (key in groups)
+ groups[key].push(item)
+ else
+ groups[key] = [ item ]
+ }
+ } else {
+ for (let item of items) {
+ let key = item[callback]
+ if (key in groups)
+ groups[key].push(item)
+ else
+ groups[key] = [ item ]
+ }
+ }
+ return groups
+}