From 48726dd19ad8dde11a6172f30b5071987b7d09b5 Mon Sep 17 00:00:00 2001 From: Frans Bongers Date: Mon, 2 Dec 2024 21:46:43 +0100 Subject: add tableaus and icon data --- data.js | 92 +++++++- data.ts | 92 +++++++- land-and-freedom.css | 53 ++++- land-and-freedom.scss | 65 +++++- play.html | 19 +- play.js | 37 ++- play.ts | 56 +++-- rules.js | 464 ++++++++++++++++++++++---------------- rules.ts | 609 +++++++++++++++++++++++++++++++------------------- types.d.ts | 37 ++- 10 files changed, 1064 insertions(+), 460 deletions(-) diff --git a/data.js b/data.js index c41aee7..edf0e36 100644 --- a/data.js +++ b/data.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.PLAYER_WITH_MOST_HERO_POINTS = exports.ON = exports.OFF = exports.TEAMWORK_BONUS = exports.MORALE_BONUS = exports.FOREIGN_AID = exports.SOVIET_SUPPORT = exports.GOVERNMENT = exports.COLLECTIVIZATION = exports.LIBERTY = void 0; +exports.TRASH = exports.TOWARDS_CENTER = exports.SELF = exports.PLAYER_WITH_MOST_HERO_POINTS = exports.OTHER_PLAYERS = exports.ON = exports.OFF = exports.TEAMWORK_BONUS = exports.MORALE_BONUS = exports.FOREIGN_AID = exports.SOVIET_SUPPORT = exports.GOVERNMENT = exports.COLLECTIVIZATION = exports.CLOSEST_TO_VICTORY = exports.CLOSEST_TO_DEFEAT = exports.LIBERTY = exports.ANY = void 0; const LIBERTY = 0; exports.LIBERTY = LIBERTY; const COLLECTIVIZATION = 1; @@ -26,13 +26,20 @@ const MADRID = 'm'; const NORTHERN = 'n'; const SOUTHERN = 's'; const CLOSEST_TO_DEFEAT = 'd'; +exports.CLOSEST_TO_DEFEAT = CLOSEST_TO_DEFEAT; const CLOSEST_TO_VICTORY = 'v'; +exports.CLOSEST_TO_VICTORY = CLOSEST_TO_VICTORY; const TOWARDS_CENTER = 10; +exports.TOWARDS_CENTER = TOWARDS_CENTER; const AWAY_FROM_CENTER = 11; const ANY = 'any'; +exports.ANY = ANY; const SELF = 'self'; +exports.SELF = SELF; const OTHER_PLAYERS = 'other'; +exports.OTHER_PLAYERS = OTHER_PLAYERS; const TRASH = 'trash'; +exports.TRASH = TRASH; function create_effect(type, target, value) { return { type, @@ -49,6 +56,7 @@ const data = { create_effect('track', FOREIGN_AID, 2), create_effect('track', SOVIET_SUPPORT, -1), ], + icons: ['foreign_aid', 'add_to_front', 'd_soviet_support'], strength: 1, title: 'CLANDESTINE FRENCH ARMS', type: 'pc', @@ -60,6 +68,7 @@ const data = { create_effect('front', ANY, 3), create_effect('track', LIBERTY, -2), ], + icons: ['foreign_aid', 'add_to_front', 'd_liberty'], strength: 2, title: 'POPULAR ARMY OF THE REPUBLIC', type: 'pc', @@ -70,6 +79,7 @@ const data = { create_effect('front', CLOSEST_TO_DEFEAT, 3), create_effect('draw_card', SELF, 1), ], + icons: ['add_to_front', 'government', 'draw_card'], strength: 2, title: 'MEXICAN GUNS', type: 'pc', @@ -81,6 +91,7 @@ const data = { create_effect('track', FOREIGN_AID, 1), create_effect('swap_card_tableau_hand', ANY, 1), ], + icons: ['add_to_front', 'foreign_aid'], strength: 1, title: 'BATTLE OF GUADALAJARA', type: 'pc', @@ -93,6 +104,7 @@ const data = { create_effect('draw_card', SELF, 3), create_effect('draw_card', OTHER_PLAYERS, 1), ], + icons: ['teamwork_on', 'add_to_front', 'draw_card'], strength: 1, title: '"SI ME OUIERES ESCRIBIR"', type: 'pc', @@ -104,6 +116,7 @@ const data = { create_effect('track', GOVERNMENT, 1), create_effect('draw_card', SELF, 1), ], + icons: ['teamwork_on', 'add_to_front', 'government', 'draw_card'], strength: 2, title: 'XYZ LINE', type: 'pc', @@ -117,6 +130,12 @@ const data = { create_effect('track', GOVERNMENT, 2), create_effect('hero_points', SELF, 1), ], + icons: [ + 'add_to_front', + 'foreign_aid', + 'd_collectivization', + 'government', + ], strength: 3, title: 'INDALECIO PRIETO', type: 'pc', @@ -127,6 +146,7 @@ const data = { create_effect('bonus', ANY, ON), create_effect('front', ANY, 3), ], + icons: ['teamwork_on', 'add_to_front'], strength: 1, title: "PEOPLE'S OLYMPIAD", type: 'pc', @@ -139,6 +159,12 @@ const data = { create_effect('track', SOVIET_SUPPORT, -2), create_effect('draw_card', SELF, 3), ], + icons: [ + 'd_liberty', + 'd_collectivization', + 'd_soviet_support', + 'draw_card', + ], strength: 1, title: 'FOUR ANARCHIST MINISTERS', type: 'pc', @@ -149,6 +175,7 @@ const data = { create_effect('track', FOREIGN_AID, 4), create_effect('add_to_tableau', SELF, 1), ], + icons: ['foreign_aid', 'draw_card'], strength: 1, title: 'GUERNICA', type: 'pc', @@ -160,6 +187,7 @@ const data = { create_effect('remove_blank_marker', ANY, 1), create_effect('track', FOREIGN_AID, 2), ], + icons: ['add_to_front', 'foreign_aid'], strength: 1, title: 'ERNEST HEMINGWAY', type: 'pc', @@ -170,6 +198,7 @@ const data = { create_effect('track', SOVIET_SUPPORT, -3), create_effect('track', COLLECTIVIZATION, -3), ], + icons: ['add_to_front', 'd_soviet_support', 'd_collectivization'], strength: 1, title: 'HUESCA OFFENSIVE', type: 'pc', @@ -182,6 +211,7 @@ const data = { create_effect('track', GOVERNMENT, 2), create_effect('hero_points', SELF, 1), ], + icons: ['foreign_aid', 'd_soviet_support', 'government'], strength: 1, title: 'PABLO NERUDA', type: 'pc', @@ -193,6 +223,7 @@ const data = { create_effect('track', COLLECTIVIZATION, -1), create_effect('track', GOVERNMENT, 1), ], + icons: ['add_to_front', 'd_collectivization', 'government'], strength: 1, title: 'EUSKO GUDAROSTEA', type: 'pc', @@ -203,6 +234,7 @@ const data = { create_effect('track', FOREIGN_AID, 2), create_effect('return_card', TRASH, 1), ], + icons: ['d_liberty', 'd_collectivization', 'foreign_aid', 'draw_card'], strength: 2, title: 'JUAN NEGRÍN', type: 'pc', @@ -210,6 +242,7 @@ const data = { { id: 16, effects: [], + icons: ['add_to_front', 'foreign_aid'], strength: 1, title: 'PUBLICIZE FASCIST WAR CRIMES', type: 'pc', @@ -220,6 +253,7 @@ const data = { create_effect('track', FOREIGN_AID, 1), create_effect('track', GOVERNMENT, 1), ], + icons: ['foreign_aid', 'government', 'd_collectivization'], strength: 1, title: 'AGRARIAN REFORM', type: 'pc', @@ -231,6 +265,7 @@ const data = { create_effect('track', GOVERNMENT, 2), create_effect('draw_card', SELF, 2), ], + icons: ['d_collectivization', 'government', 'draw_card'], strength: 1, title: 'IMPOSE FACTORY MANAGERS', type: 'pc', @@ -242,6 +277,7 @@ const data = { create_effect('track', GOVERNMENT, -1), create_effect('return_card', TRASH, 1), ], + icons: ['add_to_front', 'government', 'draw_card'], strength: 2, title: '¡NO PASARÁN!', type: 'pc', @@ -252,6 +288,7 @@ const data = { create_effect('bonus', ANY, ON), create_effect('track', SOVIET_SUPPORT, 2), ], + icons: ['teamwork_on', 'soviet_support', 'add_to_front'], strength: 2, title: 'RUSSIAN FIGHTERS', type: 'pc', @@ -264,6 +301,7 @@ const data = { create_effect('track', COLLECTIVIZATION, -2), create_effect('hero_points', SELF, 1), ], + icons: ['add_to_front', 'd_liberty', 'd_collectivization'], strength: 1, title: 'ENRIQUE LÍSTER', type: 'pc', @@ -274,6 +312,7 @@ const data = { create_effect('track', LIBERTY, -2), create_effect('track', SOVIET_SUPPORT, 1), ], + icons: ['d_liberty', 'soviet_support', 'government'], strength: 2, title: 'LARGO CABALLERO', type: 'pc', @@ -284,6 +323,7 @@ const data = { create_effect('track', FOREIGN_AID, -2), create_effect('draw_card', SELF, 2), ], + icons: ['add_to_front', 'd_foreign_aid', 'draw_card'], strength: 2, title: 'SOVIET TANKS', type: 'pc', @@ -297,6 +337,7 @@ const data = { create_effect('track', GOVERNMENT, -2), create_effect('hero_points', SELF, 1), ], + icons: ['add_to_front', 'soviet_support', 'd_liberty', 'government'], strength: 3, title: 'DOLORES IBÁRRURI', type: 'pc', @@ -309,6 +350,7 @@ const data = { create_effect('draw_card', SELF, 3), create_effect('draw_card', OTHER_PLAYERS, 1), ], + icons: ['teamwork_on', 'add_to_front', 'draw_card'], strength: 1, title: 'PAUL ROBESON', type: 'pc', @@ -319,6 +361,7 @@ const data = { create_effect('track', LIBERTY, -2), create_effect('track', GOVERNMENT, -1), ], + icons: ['add_to_front', 'd_liberty', 'government'], strength: 1, title: 'MADRID DEFENSE COUNCIL', type: 'pc', @@ -331,6 +374,7 @@ const data = { create_effect('track', FOREIGN_AID, -1), create_effect('draw_card', SELF, 1), ], + icons: ['soviet_support', 'd_foreign_aid', 'draw_card'], strength: 1, title: "STALIN GETS THE REPUBLIC'S GOLD", type: 'pc', @@ -343,6 +387,7 @@ const data = { create_effect('track', FOREIGN_AID, -1), create_effect('draw_card', SELF, 1), ], + icons: ['teamwork_on', 'add_to_front', 'd_foreign_aid', 'draw_card'], strength: 1, title: 'INTERNATIONAL BRIGADES', type: 'pc', @@ -352,6 +397,7 @@ const data = { effects: [ create_effect('track', GOVERNMENT, -2), ], + icons: ['government', 'd_liberty'], strength: 1, title: 'BAN WOMEN FROM THE FRONT', type: 'pc', @@ -363,6 +409,7 @@ const data = { create_effect('track', SOVIET_SUPPORT, 2), create_effect('track', FOREIGN_AID, -3), ], + icons: ['add_to_front', 'soviet_support', 'd_foreign_aid'], strength: 1, title: 'ABRAHAM LINCOLN BRIGADE', type: 'pc', @@ -374,6 +421,7 @@ const data = { create_effect('track', SOVIET_SUPPORT, 3), create_effect('track', GOVERNMENT, -2), ], + icons: ['soviet_support', 'government'], strength: 2, title: 'OUTLAW THE POUM', type: 'pc', @@ -384,6 +432,12 @@ const data = { create_effect('front', ANY, 1), create_effect('track', SOVIET_SUPPORT, 1), ], + icons: [ + 'add_to_front', + 'soviet_support', + 'd_liberty', + 'd_collectivization', + ], strength: 1, title: 'DISBAND THE CONTROL PATROLS', type: 'pc', @@ -396,6 +450,7 @@ const data = { create_effect('track', LIBERTY, -4), create_effect('swap_card_tableau_hand', ANY, 1), ], + icons: ['soviet_support', 'd_liberty'], strength: 1, title: 'MAY DAYS', type: 'pc', @@ -407,6 +462,7 @@ const data = { create_effect('track', GOVERNMENT, -1), create_effect('draw_card', SELF, 1), ], + icons: ['add_to_front', 'government', 'draw_card'], strength: 1, title: 'FIFTH REGIMENT', type: 'pc', @@ -417,6 +473,7 @@ const data = { create_effect('front', ANY, 2), create_effect('draw_card', SELF, 2), ], + icons: ['add_to_front', 'soviet_support', 'draw_card'], strength: 1, title: 'THÄLMANN BATTALION', type: 'pc', @@ -429,6 +486,7 @@ const data = { create_effect('draw_card', SELF, 2), create_effect('add_to_tableau', ANY, 1), ], + icons: ['soviet_support', 'd_collectivization', 'draw_card'], strength: 1, title: 'DE-COLLECTIVIZE AGRICULTURE', type: 'pc', @@ -442,6 +500,12 @@ const data = { create_effect('track', GOVERNMENT, TOWARDS_CENTER), create_effect('hero_points', SELF, 1), ], + icons: [ + 'add_to_front', + 'collectivization', + 'liberty', + 'government_to_center', + ], strength: 3, title: 'BUENAVENTURA DURRUTI', type: 'pc', @@ -454,6 +518,7 @@ const data = { create_effect('draw_card', SELF, 1), create_effect('swap_card_tableau_hand', ANY, 1), ], + icons: ['liberty', 'd_soviet_support', 'draw_card'], strength: 2, title: 'MUJERES LIBRES', type: 'pc', @@ -464,6 +529,7 @@ const data = { create_effect('track', COLLECTIVIZATION, 1), create_effect('draw_card', SELF, 2), ], + icons: ['add_to_front', 'collectivization', 'draw_card'], strength: 1, title: 'IRON COLUMN', type: 'pc', @@ -475,6 +541,7 @@ const data = { create_effect('track', COLLECTIVIZATION, 2), create_effect('draw_card', SELF, 1), ], + icons: ['add_to_front', 'collectivization', 'draw_card'], strength: 2, title: 'ASTURIAN MINERS', type: 'pc', @@ -486,6 +553,7 @@ const data = { create_effect('track', COLLECTIVIZATION, 3), create_effect('return_card', TRASH, 1), ], + icons: ['teamwork_on', 'collectivization', 'draw_card'], strength: 2, title: 'CNT-FAI', type: 'pc', @@ -497,6 +565,7 @@ const data = { create_effect('front', ARAGON, 2), create_effect('front', MADRID, 1), ], + icons: ['liberty', 'add_to_front'], strength: 2, title: 'DURRUTI COLUMN', type: 'pc', @@ -510,6 +579,7 @@ const data = { create_effect('draw_card', SELF, 3), create_effect('draw_card', OTHER_PLAYERS, 1), ], + icons: ['teamwork_on', 'add_to_front', 'd_soviet_support', 'draw_card'], strength: 1, title: 'GEORGE ORWELL', type: 'pc', @@ -521,6 +591,7 @@ const data = { create_effect('track', LIBERTY, 1), create_effect('track', FOREIGN_AID, -1), ], + icons: ['add_to_front', 'liberty', 'd_foreign_aid'], strength: 1, title: 'F.I.J.L.', type: 'pc', @@ -529,8 +600,9 @@ const data = { id: 45, effects: [ create_effect('front', ANY, 2), - create_effect('track', FOREIGN_AID, -3) + create_effect('track', FOREIGN_AID, -3), ], + icons: ['add_to_front', 'collectivization', 'd_foreign_aid'], strength: 1, title: 'ARM THE UNIONS', type: 'pc', @@ -541,6 +613,7 @@ const data = { create_effect('bonus', ANY, ON), create_effect('track', LIBERTY, 2), ], + icons: ['teamwork_on', 'liberty', 'add_to_front'], strength: 1, title: 'GUERRILLAS', type: 'pc', @@ -553,6 +626,7 @@ const data = { create_effect('track', GOVERNMENT, TOWARDS_CENTER), create_effect('draw_card', SELF, 1), ], + icons: ['liberty', 'government_to_center', 'draw_card'], strength: 1, title: 'RADICAL EDUCATION', type: 'pc', @@ -564,6 +638,7 @@ const data = { create_effect('track', SOVIET_SUPPORT, -2), create_effect('draw_card', SELF, 3), ], + icons: ['add_to_front', 'd_soviet_support', 'draw_card'], strength: 1, title: 'MATTEOTTI BATTALION', type: 'pc', @@ -574,6 +649,7 @@ const data = { create_effect('track', COLLECTIVIZATION, 4), create_effect('draw_card', SELF, 1), ], + icons: ['collectivization', 'draw_card'], strength: 1, title: 'COLLECTIVIZE AGRICULTURE', type: 'pc', @@ -584,6 +660,7 @@ const data = { create_effect('track', COLLECTIVIZATION, 1), create_effect('track', FOREIGN_AID, -1), ], + icons: ['collectivization', 'add_to_front', 'd_foreign_aid'], strength: 1, title: 'ARMORED VEHICLES', type: 'pc', @@ -595,6 +672,7 @@ const data = { create_effect('track', FOREIGN_AID, -2), create_effect('track', GOVERNMENT, TOWARDS_CENTER), ], + icons: ['collectivization', 'd_foreign_aid', 'government_to_center'], strength: 1, title: 'INDUSTRIAL DEMOCRACY', type: 'pc', @@ -605,6 +683,12 @@ const data = { create_effect('front', ANY, 1), create_effect('track', GOVERNMENT, TOWARDS_CENTER), ], + icons: [ + 'add_to_front', + 'liberty', + 'collectivization', + 'government_to_center', + ], strength: 2, title: 'AFFINITY GROUPS', type: 'pc', @@ -615,6 +699,7 @@ const data = { create_effect('track', LIBERTY, 1), create_effect('hero_points', SELF, 1), ], + icons: ['liberty', 'add_to_front'], strength: 1, title: 'GENDER-INCLUSIVE MILITIA', type: 'pc', @@ -625,6 +710,7 @@ const data = { create_effect('track', SOVIET_SUPPORT, -1), create_effect('add_to_tableau', ANY, 1), ], + icons: ['liberty', 'd_soviet_support', 'draw_card'], strength: 1, title: 'FEDERICA MONTSENY', type: 'pc', @@ -711,7 +797,7 @@ const data = { effects: [ create_effect('attack', SOUTHERN, -5), create_effect('bonus', MORALE_BONUS, OFF), - create_effect('hero_points', PLAYER_WITH_MOST_HERO_POINTS, -1), + create_effect('track', LIBERTY, -1), ], title: 'AIRLIFT OF THE ARMY OF AFRICA', type: 'ec', diff --git a/data.ts b/data.ts index d2d2172..6facc50 100644 --- a/data.ts +++ b/data.ts @@ -30,7 +30,10 @@ const OTHER_PLAYERS = 'other'; const TRASH = 'trash'; export { + ANY, LIBERTY, + CLOSEST_TO_DEFEAT, + CLOSEST_TO_VICTORY, COLLECTIVIZATION, GOVERNMENT, SOVIET_SUPPORT, @@ -39,7 +42,11 @@ export { TEAMWORK_BONUS, OFF, ON, + OTHER_PLAYERS, PLAYER_WITH_MOST_HERO_POINTS, + SELF, + TOWARDS_CENTER, + TRASH, }; function create_effect( @@ -64,6 +71,7 @@ const data: StaticData = { // create_effect('track', FOREIGN_AID, 2), // Conditional support create_effect('track', SOVIET_SUPPORT, -1), ], + icons: ['foreign_aid', 'add_to_front', 'd_soviet_support'], strength: 1, title: 'CLANDESTINE FRENCH ARMS', type: 'pc', @@ -75,6 +83,7 @@ const data: StaticData = { create_effect('front', ANY, 3), create_effect('track', LIBERTY, -2), ], + icons: ['foreign_aid', 'add_to_front', 'd_liberty'], strength: 2, title: 'POPULAR ARMY OF THE REPUBLIC', type: 'pc', @@ -86,6 +95,7 @@ const data: StaticData = { // create_effect('track', FOREIGN_AID, 2), // conditional effect create_effect('draw_card', SELF, 1), ], + icons: ['add_to_front', 'government', 'draw_card'], strength: 2, title: 'MEXICAN GUNS', type: 'pc', @@ -97,6 +107,7 @@ const data: StaticData = { create_effect('track', FOREIGN_AID, 1), create_effect('swap_card_tableau_hand', ANY, 1), ], + icons: ['add_to_front', 'foreign_aid'], strength: 1, title: 'BATTLE OF GUADALAJARA', type: 'pc', @@ -109,6 +120,7 @@ const data: StaticData = { create_effect('draw_card', SELF, 3), create_effect('draw_card', OTHER_PLAYERS, 1), ], + icons: ['teamwork_on', 'add_to_front', 'draw_card'], strength: 1, title: '"SI ME OUIERES ESCRIBIR"', type: 'pc', @@ -121,6 +133,7 @@ const data: StaticData = { create_effect('track', GOVERNMENT, 1), create_effect('draw_card', SELF, 1), ], + icons: ['teamwork_on', 'add_to_front', 'government', 'draw_card'], strength: 2, title: 'XYZ LINE', type: 'pc', @@ -134,6 +147,12 @@ const data: StaticData = { create_effect('track', GOVERNMENT, 2), create_effect('hero_points', SELF, 1), ], + icons: [ + 'add_to_front', + 'foreign_aid', + 'd_collectivization', + 'government', + ], strength: 3, title: 'INDALECIO PRIETO', type: 'pc', @@ -144,6 +163,7 @@ const data: StaticData = { create_effect('bonus', ANY, ON), create_effect('front', ANY, 3), ], + icons: ['teamwork_on', 'add_to_front'], strength: 1, title: "PEOPLE'S OLYMPIAD", type: 'pc', @@ -156,6 +176,12 @@ const data: StaticData = { create_effect('track', SOVIET_SUPPORT, -2), create_effect('draw_card', SELF, 3), ], + icons: [ + 'd_liberty', + 'd_collectivization', + 'd_soviet_support', + 'draw_card', + ], strength: 1, title: 'FOUR ANARCHIST MINISTERS', type: 'pc', @@ -167,6 +193,7 @@ const data: StaticData = { // create_effect('track', FOREIGN_AID, 3) // conditional draw cards create_effect('add_to_tableau', SELF, 1), ], + icons: ['foreign_aid', 'draw_card'], strength: 1, title: 'GUERNICA', type: 'pc', @@ -178,6 +205,7 @@ const data: StaticData = { create_effect('remove_blank_marker', ANY, 1), create_effect('track', FOREIGN_AID, 2), ], + icons: ['add_to_front', 'foreign_aid'], strength: 1, title: 'ERNEST HEMINGWAY', type: 'pc', @@ -189,6 +217,7 @@ const data: StaticData = { create_effect('track', SOVIET_SUPPORT, -3), create_effect('track', COLLECTIVIZATION, -3), ], + icons: ['add_to_front', 'd_soviet_support', 'd_collectivization'], strength: 1, title: 'HUESCA OFFENSIVE', type: 'pc', @@ -201,6 +230,7 @@ const data: StaticData = { create_effect('track', GOVERNMENT, 2), create_effect('hero_points', SELF, 1), ], + icons: ['foreign_aid', 'd_soviet_support', 'government'], strength: 1, title: 'PABLO NERUDA', type: 'pc', @@ -212,6 +242,7 @@ const data: StaticData = { create_effect('track', COLLECTIVIZATION, -1), create_effect('track', GOVERNMENT, 1), ], + icons: ['add_to_front', 'd_collectivization', 'government'], strength: 1, title: 'EUSKO GUDAROSTEA', type: 'pc', @@ -223,6 +254,7 @@ const data: StaticData = { create_effect('track', FOREIGN_AID, 2), create_effect('return_card', TRASH, 1), ], + icons: ['d_liberty', 'd_collectivization', 'foreign_aid', 'draw_card'], strength: 2, title: 'JUAN NEGRÍN', type: 'pc', @@ -233,6 +265,7 @@ const data: StaticData = { // create_effect('front', ANY, -2) // move attacks // create_effect('track', FOREIGN_AID, 4) // conditional ], + icons: ['add_to_front', 'foreign_aid'], strength: 1, title: 'PUBLICIZE FASCIST WAR CRIMES', type: 'pc', @@ -244,6 +277,7 @@ const data: StaticData = { create_effect('track', GOVERNMENT, 1), // create_effect('track', COLLECTIVIZATION,-4) // conditional ], + icons: ['foreign_aid', 'government', 'd_collectivization'], strength: 1, title: 'AGRARIAN REFORM', type: 'pc', @@ -255,6 +289,7 @@ const data: StaticData = { create_effect('track', GOVERNMENT, 2), create_effect('draw_card', SELF, 2), ], + icons: ['d_collectivization', 'government', 'draw_card'], strength: 1, title: 'IMPOSE FACTORY MANAGERS', type: 'pc', @@ -266,6 +301,7 @@ const data: StaticData = { create_effect('track', GOVERNMENT, -1), create_effect('return_card', TRASH, 1), ], + icons: ['add_to_front', 'government', 'draw_card'], strength: 2, title: '¡NO PASARÁN!', type: 'pc', @@ -277,6 +313,7 @@ const data: StaticData = { create_effect('track', SOVIET_SUPPORT, 2), // create_effect('front') // conditional on track ], + icons: ['teamwork_on', 'soviet_support', 'add_to_front'], strength: 2, title: 'RUSSIAN FIGHTERS', type: 'pc', @@ -289,6 +326,7 @@ const data: StaticData = { create_effect('track', COLLECTIVIZATION, -2), create_effect('hero_points', SELF, 1), ], + icons: ['add_to_front', 'd_liberty', 'd_collectivization'], strength: 1, title: 'ENRIQUE LÍSTER', type: 'pc', @@ -300,6 +338,7 @@ const data: StaticData = { create_effect('track', SOVIET_SUPPORT, 1), // create_effect('track', GOVERNMENT, -2) // conditional ], + icons: ['d_liberty', 'soviet_support', 'government'], strength: 2, title: 'LARGO CABALLERO', type: 'pc', @@ -311,6 +350,7 @@ const data: StaticData = { create_effect('track', FOREIGN_AID, -2), create_effect('draw_card', SELF, 2), ], + icons: ['add_to_front', 'd_foreign_aid', 'draw_card'], strength: 2, title: 'SOVIET TANKS', type: 'pc', @@ -324,6 +364,7 @@ const data: StaticData = { create_effect('track', GOVERNMENT, -2), create_effect('hero_points', SELF, 1), ], + icons: ['add_to_front', 'soviet_support', 'd_liberty', 'government'], strength: 3, title: 'DOLORES IBÁRRURI', type: 'pc', @@ -336,6 +377,7 @@ const data: StaticData = { create_effect('draw_card', SELF, 3), create_effect('draw_card', OTHER_PLAYERS, 1), ], + icons: ['teamwork_on', 'add_to_front', 'draw_card'], strength: 1, title: 'PAUL ROBESON', type: 'pc', @@ -347,6 +389,7 @@ const data: StaticData = { create_effect('track', LIBERTY, -2), create_effect('track', GOVERNMENT, -1), ], + icons: ['add_to_front', 'd_liberty', 'government'], strength: 1, title: 'MADRID DEFENSE COUNCIL', type: 'pc', @@ -359,6 +402,7 @@ const data: StaticData = { create_effect('track', FOREIGN_AID, -1), create_effect('draw_card', SELF, 1), ], + icons: ['soviet_support', 'd_foreign_aid', 'draw_card'], strength: 1, title: "STALIN GETS THE REPUBLIC'S GOLD", type: 'pc', @@ -371,6 +415,7 @@ const data: StaticData = { create_effect('track', FOREIGN_AID, -1), create_effect('draw_card', SELF, 1), ], + icons: ['teamwork_on', 'add_to_front', 'd_foreign_aid', 'draw_card'], strength: 1, title: 'INTERNATIONAL BRIGADES', type: 'pc', @@ -381,6 +426,7 @@ const data: StaticData = { create_effect('track', GOVERNMENT, -2), // create_effect('track', LIBERTY, -3) // conditional ], + icons: ['government', 'd_liberty'], strength: 1, title: 'BAN WOMEN FROM THE FRONT', type: 'pc', @@ -392,6 +438,7 @@ const data: StaticData = { create_effect('track', SOVIET_SUPPORT, 2), create_effect('track', FOREIGN_AID, -3), ], + icons: ['add_to_front', 'soviet_support', 'd_foreign_aid'], strength: 1, title: 'ABRAHAM LINCOLN BRIGADE', type: 'pc', @@ -403,6 +450,7 @@ const data: StaticData = { create_effect('track', SOVIET_SUPPORT, 3), create_effect('track', GOVERNMENT, -2), ], + icons: ['soviet_support', 'government'], strength: 2, title: 'OUTLAW THE POUM', type: 'pc', @@ -415,6 +463,12 @@ const data: StaticData = { create_effect('track', SOVIET_SUPPORT, 1), // create_effect('track') // choose one of two tracks ], + icons: [ + 'add_to_front', + 'soviet_support', + 'd_liberty', + 'd_collectivization', + ], strength: 1, title: 'DISBAND THE CONTROL PATROLS', type: 'pc', @@ -427,6 +481,7 @@ const data: StaticData = { create_effect('track', LIBERTY, -4), create_effect('swap_card_tableau_hand', ANY, 1), ], + icons: ['soviet_support', 'd_liberty'], strength: 1, title: 'MAY DAYS', type: 'pc', @@ -438,6 +493,7 @@ const data: StaticData = { create_effect('track', GOVERNMENT, -1), create_effect('draw_card', SELF, 1), ], + icons: ['add_to_front', 'government', 'draw_card'], strength: 1, title: 'FIFTH REGIMENT', type: 'pc', @@ -449,6 +505,7 @@ const data: StaticData = { // create_effect('track', SOVIET_SUPPORT,2) // conditional create_effect('draw_card', SELF, 2), ], + icons: ['add_to_front', 'soviet_support', 'draw_card'], strength: 1, title: 'THÄLMANN BATTALION', type: 'pc', @@ -461,6 +518,7 @@ const data: StaticData = { create_effect('draw_card', SELF, 2), create_effect('add_to_tableau', ANY, 1), ], + icons: ['soviet_support', 'd_collectivization', 'draw_card'], strength: 1, title: 'DE-COLLECTIVIZE AGRICULTURE', type: 'pc', @@ -469,11 +527,17 @@ const data: StaticData = { id: 37, effects: [ create_effect('front', ANY, 1), - create_effect('track', COLLECTIVIZATION,2), + create_effect('track', COLLECTIVIZATION, 2), create_effect('track', LIBERTY, 2), create_effect('track', GOVERNMENT, TOWARDS_CENTER), create_effect('hero_points', SELF, 1), ], + icons: [ + 'add_to_front', + 'collectivization', + 'liberty', + 'government_to_center', + ], strength: 3, title: 'BUENAVENTURA DURRUTI', type: 'pc', @@ -486,6 +550,7 @@ const data: StaticData = { create_effect('draw_card', SELF, 1), create_effect('swap_card_tableau_hand', ANY, 1), ], + icons: ['liberty', 'd_soviet_support', 'draw_card'], strength: 2, title: 'MUJERES LIBRES', type: 'pc', @@ -497,6 +562,7 @@ const data: StaticData = { create_effect('track', COLLECTIVIZATION, 1), create_effect('draw_card', SELF, 2), ], + icons: ['add_to_front', 'collectivization', 'draw_card'], strength: 1, title: 'IRON COLUMN', type: 'pc', @@ -508,6 +574,7 @@ const data: StaticData = { create_effect('track', COLLECTIVIZATION, 2), create_effect('draw_card', SELF, 1), ], + icons: ['add_to_front', 'collectivization', 'draw_card'], strength: 2, title: 'ASTURIAN MINERS', type: 'pc', @@ -519,6 +586,7 @@ const data: StaticData = { create_effect('track', COLLECTIVIZATION, 3), create_effect('return_card', TRASH, 1), ], + icons: ['teamwork_on', 'collectivization', 'draw_card'], strength: 2, title: 'CNT-FAI', type: 'pc', @@ -531,6 +599,7 @@ const data: StaticData = { create_effect('front', MADRID, 1), // create_effect('ability') // activate ability ], + icons: ['liberty', 'add_to_front'], strength: 2, title: 'DURRUTI COLUMN', type: 'pc', @@ -544,6 +613,7 @@ const data: StaticData = { create_effect('draw_card', SELF, 3), create_effect('draw_card', OTHER_PLAYERS, 1), ], + icons: ['teamwork_on', 'add_to_front', 'd_soviet_support', 'draw_card'], strength: 1, title: 'GEORGE ORWELL', type: 'pc', @@ -556,6 +626,7 @@ const data: StaticData = { create_effect('track', LIBERTY, 1), create_effect('track', FOREIGN_AID, -1), ], + icons: ['add_to_front', 'liberty', 'd_foreign_aid'], strength: 1, title: 'F.I.J.L.', type: 'pc', @@ -565,8 +636,9 @@ const data: StaticData = { effects: [ create_effect('front', ANY, 2), // create_effect('track', COLLECTIVIZATION, 1) // conditional - create_effect('track', FOREIGN_AID, -3) + create_effect('track', FOREIGN_AID, -3), ], + icons: ['add_to_front', 'collectivization', 'd_foreign_aid'], strength: 1, title: 'ARM THE UNIONS', type: 'pc', @@ -578,6 +650,7 @@ const data: StaticData = { create_effect('track', LIBERTY, 2), // create_effect('special') // Peek at Fascist cards ], + icons: ['teamwork_on', 'liberty', 'add_to_front'], strength: 1, title: 'GUERRILLAS', type: 'pc', @@ -590,6 +663,7 @@ const data: StaticData = { create_effect('track', GOVERNMENT, TOWARDS_CENTER), create_effect('draw_card', SELF, 1), ], + icons: ['liberty', 'government_to_center', 'draw_card'], strength: 1, title: 'RADICAL EDUCATION', type: 'pc', @@ -601,6 +675,7 @@ const data: StaticData = { create_effect('track', SOVIET_SUPPORT, -2), create_effect('draw_card', SELF, 3), ], + icons: ['add_to_front', 'd_soviet_support', 'draw_card'], strength: 1, title: 'MATTEOTTI BATTALION', type: 'pc', @@ -611,6 +686,7 @@ const data: StaticData = { create_effect('track', COLLECTIVIZATION, 4), create_effect('draw_card', SELF, 1), ], + icons: ['collectivization', 'draw_card'], strength: 1, title: 'COLLECTIVIZE AGRICULTURE', type: 'pc', @@ -622,6 +698,7 @@ const data: StaticData = { // create_effect('front', ARAGON, 3) // conditional create_effect('track', FOREIGN_AID, -1), ], + icons: ['collectivization', 'add_to_front', 'd_foreign_aid'], strength: 1, title: 'ARMORED VEHICLES', type: 'pc', @@ -633,6 +710,7 @@ const data: StaticData = { create_effect('track', FOREIGN_AID, -2), create_effect('track', GOVERNMENT, TOWARDS_CENTER), ], + icons: ['collectivization', 'd_foreign_aid', 'government_to_center'], strength: 1, title: 'INDUSTRIAL DEMOCRACY', type: 'pc', @@ -644,6 +722,12 @@ const data: StaticData = { // create_effect('track') // choose create_effect('track', GOVERNMENT, TOWARDS_CENTER), ], + icons: [ + 'add_to_front', + 'liberty', + 'collectivization', + 'government_to_center', + ], strength: 2, title: 'AFFINITY GROUPS', type: 'pc', @@ -655,6 +739,7 @@ const data: StaticData = { // create_effect('front', ANY, 3) // conditional create_effect('hero_points', SELF, 1), ], + icons: ['liberty', 'add_to_front'], strength: 1, title: 'GENDER-INCLUSIVE MILITIA', type: 'pc', @@ -666,6 +751,7 @@ const data: StaticData = { create_effect('track', SOVIET_SUPPORT, -1), create_effect('add_to_tableau', ANY, 1), ], + icons: ['liberty', 'd_soviet_support', 'draw_card'], strength: 1, title: 'FEDERICA MONTSENY', type: 'pc', @@ -752,7 +838,7 @@ const data: StaticData = { effects: [ create_effect('attack', SOUTHERN, -5), create_effect('bonus', MORALE_BONUS, OFF), - create_effect('hero_points', PLAYER_WITH_MOST_HERO_POINTS, -1), + create_effect('track', LIBERTY, -1), ], title: 'AIRLIFT OF THE ARMY OF AFRICA', type: 'ec', diff --git a/land-and-freedom.css b/land-and-freedom.css index 00fbfd0..9452199 100644 --- a/land-and-freedom.css +++ b/land-and-freedom.css @@ -1,5 +1,5 @@ main { - background-color: darkolivegreen; + background-color: #7B904B; } /* MAP */ @@ -26,13 +26,39 @@ main { background-image: url(images/map100.png); } } -#hand { +.panel { + min-width: 1271px; + max-width: 1271px; + background-color: #58641D; + margin: 12px auto; + box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.2666666667); + border: 2px solid #333; +} + +.panel_body { display: flex; - flex-direction: row; - gap: 8px; - margin-top: 16px; - padding: 16px; justify-content: center; + flex-wrap: wrap; + padding: 12px; + gap: 12px; + min-height: 260px; +} + +.panel_header { + color: white; + user-select: none; + font-weight: bold; + text-align: center; + border-bottom: 2px solid #333; + background-color: red; +} + +#hand_header { + background-color: #273B09; +} + +.panel_header[data-faction-id=a] { + background: linear-gradient(45deg, black 50%, red 0); } .front { @@ -321,6 +347,17 @@ main { height: 180px; } +.blank_marker { + box-sizing: border-box; + position: absolute; + background-color: red; + width: 29px; + height: 29px; + border-radius: 50%; + box-shadow: 0 0 0 1px #333; + margin-top: 1px; +} + .standee { box-sizing: border-box; position: absolute; @@ -333,14 +370,17 @@ main { transition-property: top, left; transition-duration: 700ms; transition-timing-function: ease; + z-index: 1; } +.bonus.action, .card.action, .front.action, .standee.action { box-shadow: 0 0 0 3px white; } +.bonus.action:hover, .card.action:hover, .card.selected, .front.action:hover, @@ -375,6 +415,7 @@ main { height: 54px; background-size: cover; background-repeat: no-repeat; + border-radius: 50%; } .bonus[data-bonus-id="0"][data-bonus-on="0"] { diff --git a/land-and-freedom.scss b/land-and-freedom.scss index 5dd9325..3602c26 100644 --- a/land-and-freedom.scss +++ b/land-and-freedom.scss @@ -6,7 +6,8 @@ $selected-color: yellow; //blue; main { // background-color: rgb(213, 196, 131); - background-color: darkolivegreen; + // background-color: darkolivegreen; + background-color: #7B904B;; } /* MAP */ @@ -43,15 +44,52 @@ main { } } -#hand { +.panel { + min-width: 1271px; + max-width: 1271px; + // background-color: hsl(34, 10%, 35%); + background-color: #58641D; + margin: 12px auto; + box-shadow: 1px 2px 4px #0004; + border: 2px solid #333; +} + +.panel_body { display: flex; - flex-direction: row; - gap: 8px; - margin-top: 16px; - padding: 16px; justify-content: center; + flex-wrap: wrap; + padding: 12px; + gap: 12px; + min-height: 260px; +} + +.panel_header { + color: white; + user-select: none; + font-weight: bold; + text-align: center; + border-bottom: 2px solid #333; + background-color: red; } +#hand_header { + background-color: #273B09; +} + +.panel_header[data-faction-id="a"] { + background: linear-gradient(45deg, black 50%, red 0); +} + +// #hand, +// .tableau { +// display: flex; +// flex-direction: row; +// gap: 8px; +// margin-top: 16px; +// padding: 16px; +// justify-content: center; +// } + .front { position: absolute; box-sizing: border-box; @@ -102,6 +140,17 @@ main { height: 180px; } +.blank_marker { + box-sizing: border-box; + position: absolute; + background-color: red; + width: 29px; + height: 29px; + border-radius: 50%; + box-shadow: 0 0 0 1px #333; + margin-top: 1px; +} + .standee { box-sizing: border-box; position: absolute; @@ -114,15 +163,18 @@ main { transition-property: top, left; transition-duration: 700ms; transition-timing-function: ease; + z-index: 1; // opacity: 0.6; } +.bonus.action, .card.action, .front.action, .standee.action { box-shadow: 0 0 0 3px $selectable-color; } +.bonus.action:hover, .card.action:hover, .card.selected, .front.action:hover, @@ -147,6 +199,7 @@ main { height: 54px; background-size: cover; background-repeat: no-repeat; + border-radius: 50%; } @for $i from 0 through 1 { diff --git a/play.html b/play.html index 0dfdfe0..04e8168 100644 --- a/play.html +++ b/play.html @@ -43,11 +43,28 @@
+
-
+ +
+
Hand
+
+
+
+
Anarchist
+
+
+
+
Communist
+
+
+
+
Moderate
+
+
diff --git a/play.js b/play.js index c654730..7607bc4 100644 --- a/play.js +++ b/play.js @@ -1,14 +1,24 @@ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); const BONUSES_COUNT = 2; -const CARD_COUNT = 62; +const CARD_COUNT = 63; const MEDAILLONS_COUNT = 5; const STANDEES_COUNT = 5; +const TRACK_COUNT = 5; +const TRACK_LENGTH = 11; +const FACTIONS = ['a', 'c', 'm']; let ui = { map: document.getElementById('map'), + markers: document.getElementById('markers'), hand: document.getElementById('hand'), current_events: document.getElementById('current_events'), + tableaus: { + a: document.getElementById('tableau_a'), + c: document.getElementById('tableau_c'), + m: document.getElementById('tableau_m'), + }, tracks: document.getElementById('tracks'), + blank_markers: [[], [], [], [], []], bonuses: [], fronts: {}, frontValues: {}, @@ -147,6 +157,13 @@ function on_init() { on_init_once = true; console.log('ui', ui); console.log('document', document); + for (let t = 0; t < 5; ++t) { + for (let bm = 0; bm < TRACK_LENGTH; ++bm) { + let e = (ui.blank_markers[t][bm] = document.createElement('div')); + e.className = 'blank_marker'; + register_action(e, 'blank_marker', bm); + } + } for (let b = 0; b < BONUSES_COUNT; ++b) { let e = (ui.bonuses[b] = document.createElement('div')); e.className = 'bonus'; @@ -191,6 +208,14 @@ function on_update() { ui.cards[cardId].style.left = LAYOUT_CURRENT_EVENTS[i][0] + 'px'; ui.cards[cardId].style.top = LAYOUT_CURRENT_EVENTS[i][1] + 'px'; } + ui.markers.replaceChildren(); + for (let t = 0; t < TRACK_COUNT; ++t) { + for (let bm of view.triggered_track_effects[t]) { + ui.markers.appendChild(ui.blank_markers[t][bm]); + ui.blank_markers[t][bm].style.left = LAYOUT_TRACKS[t][bm][0] + 'px'; + ui.blank_markers[t][bm].style.top = LAYOUT_TRACKS[t][bm][1] + 'px'; + } + } for (let bonus_id of Object.keys(view.bonuses)) { ui.bonuses[bonus_id].setAttribute('data-bonus-on', view.bonuses[bonus_id] + 0); } @@ -202,7 +227,6 @@ function on_update() { ui.cards[c].classList.add('selected'); } } - ; for (let i = 0; i < view.tracks.length; ++i) { ui.standees[i].style.left = LAYOUT_TRACKS[i][view.tracks[i]][0] + 'px'; ui.standees[i].style.top = LAYOUT_TRACKS[i][view.tracks[i]][1] + 'px'; @@ -216,11 +240,19 @@ function on_update() { ui.medaillons[i].style.top = LAYOUT_MEDAILLONS[i][1] + 'px'; } } + for (let faction_id of FACTIONS) { + ui.tableaus[faction_id].replaceChildren(); + for (let c of view.tableaus[faction_id]) { + ui.cards[c].classList.remove('selected'); + ui.tableaus[faction_id].appendChild(ui.cards[c]); + } + } for (let e of action_register) e.classList.toggle('action', is_action(e.my_action, e.my_id)); action_button('Anarchist', 'Anarchist'); action_button('Communist', 'Communist'); action_button('Moderate', 'Moderate'); + action_button('gain_hp', 'Gain Hero Points'); action_button('draw_card', 'Draw card'); action_button('play_for_ap', 'Play card for Action Points'); action_button('play_for_event', 'Play card for Event'); @@ -229,6 +261,7 @@ function on_update() { action_button('up', 'Up'); action_button('down', 'Down'); action_button('next', 'Next'); + action_button('skip', 'Skip'); action_button('done', 'Done'); action_button('undo', 'Undo'); } diff --git a/play.ts b/play.ts index 644ec89..36d64e5 100644 --- a/play.ts +++ b/play.ts @@ -12,15 +12,25 @@ declare const view: View; const BONUSES_COUNT = 2; // const PIECE_COUNT = 32; -const CARD_COUNT = 62; +const CARD_COUNT = 63; const MEDAILLONS_COUNT = 5; const STANDEES_COUNT = 5; +const TRACK_COUNT = 5; +const TRACK_LENGTH = 11; +const FACTIONS = ['a', 'c', 'm']; let ui = { map: document.getElementById('map'), + markers: document.getElementById('markers'), hand: document.getElementById('hand'), current_events: document.getElementById('current_events'), + tableaus: { + a: document.getElementById('tableau_a'), + c: document.getElementById('tableau_c'), + m: document.getElementById('tableau_m'), + }, tracks: document.getElementById('tracks'), + blank_markers: [[], [], [], [], []], bonuses: [], fronts: {}, frontValues: {}, @@ -179,20 +189,14 @@ function on_init() { console.log('document', document); - // create space elements - // for (let s = 0; s < SPACE_COUNT; ++s) { - // let e = ui.spaces[s].document.createElement('div'); - // e.className = 'space'; - // register_action(e, 'space', s); - // ui.map.appendChild(e); - // } - - // // create piece elements - // for (let p = 0; p < PIECE_COUNT; ++p) { - // let e = (ui.pieces[p] = document.createElement('div')); - // e.className = 'piece'; - // register_action(e, 'piece', p); - // } + // Create blank_markers + for (let t = 0; t < 5; ++t) { + for (let bm = 0; bm < TRACK_LENGTH; ++bm) { + let e = (ui.blank_markers[t][bm] = document.createElement('div')); + e.className = 'blank_marker'; + register_action(e, 'blank_marker', bm); + } + } // create bonus markers for (let b = 0; b < BONUSES_COUNT; ++b) { @@ -257,6 +261,16 @@ function on_update() { ui.cards[cardId].style.top = LAYOUT_CURRENT_EVENTS[i][1] + 'px'; } + + ui.markers.replaceChildren(); + for (let t = 0; t < TRACK_COUNT; ++t) { + for (let bm of view.triggered_track_effects[t]) { + ui.markers.appendChild(ui.blank_markers[t][bm]); + ui.blank_markers[t][bm].style.left = LAYOUT_TRACKS[t][bm][0] + 'px'; + ui.blank_markers[t][bm].style.top = LAYOUT_TRACKS[t][bm][1] + 'px'; + } + } + for (let bonus_id of Object.keys(view.bonuses)) { ui.bonuses[bonus_id].setAttribute( 'data-bonus-on', @@ -271,7 +285,7 @@ function on_update() { if (c === view.selected_card) { ui.cards[c].classList.add('selected'); } - }; + } for (let i = 0; i < view.tracks.length; ++i) { // ui.tracks.appendChild(ui.standees[i]); @@ -290,12 +304,21 @@ function on_update() { } } + for (let faction_id of FACTIONS) { + ui.tableaus[faction_id].replaceChildren(); + for (let c of view.tableaus[faction_id]) { + ui.cards[c].classList.remove('selected'); + ui.tableaus[faction_id].appendChild(ui.cards[c]); + } + } + for (let e of action_register) e.classList.toggle('action', is_action(e.my_action, e.my_id)); action_button('Anarchist', 'Anarchist'); action_button('Communist', 'Communist'); action_button('Moderate', 'Moderate'); + action_button('gain_hp', 'Gain Hero Points'); action_button('draw_card', 'Draw card'); action_button('play_for_ap', 'Play card for Action Points'); action_button('play_for_event', 'Play card for Event'); @@ -304,6 +327,7 @@ function on_update() { action_button('up', 'Up'); action_button('down', 'Down'); action_button('next', 'Next'); + action_button('skip', 'Skip'); action_button('done', 'Done'); action_button('undo', 'Undo'); } diff --git a/rules.js b/rules.js index 62592e5..8dc2b2e 100644 --- a/rules.js +++ b/rules.js @@ -26,14 +26,16 @@ const player_faction_map = { [exports.MODERATE]: MODERATES_ID, }; const front_names = { - a: 'the Aragon Front', - m: 'the Madrid Front', - n: 'the Nothern Front', - s: 'the Southern Front', + a: 'Aragon Front', + m: 'Madrid Front', + n: 'Nothern Front', + s: 'Southern Front', d: 'the Front closest to Defeat', v: 'the Front closest to Victory', }; +const bonus_names = ['Morale Bonus', 'Teamwork Bonus']; const { cards, tracks, } = data_1.default; +const bonuses = [data_1.MORALE_BONUS, data_1.TEAMWORK_BONUS]; const faction_cards = { [ANARCHISTS_ID]: make_list(37, 54), [COMMUNISTS_ID]: make_list(19, 36), @@ -56,9 +58,15 @@ function gen_action(action, argument) { view.actions[action].push(argument); } } +function gen_action_bonus(bonus_id) { + gen_action('bonus', bonus_id); +} function gen_action_card(card_id) { gen_action('card', card_id); } +function gen_action_front(front_id) { + gen_action('front', front_id); +} function gen_action_standee(track_id) { gen_action('standee', track_id); } @@ -78,59 +86,53 @@ const leaf_node = 'l'; const seq_node = 's'; const function_node = 'f'; const resolved = 1; +function create_leaf_node(state, faction, args) { + return { + t: leaf_node, + s: state, + p: faction, + a: args, + r: 0, + }; +} +function create_function_node(func_name, args) { + return { + t: function_node, + f: func_name, + a: args, + r: 0, + }; +} +function create_seq_node(children) { + return { + t: seq_node, + c: children, + }; +} function setup_bag_of_glory() { - console.log('setup_bag_of_glory'); game.engine = [ - { - t: leaf_node, - p: game.initiative, - s: 'add_glory', - }, - { - t: function_node, - f: 'end_of_turn', - }, + create_leaf_node('add_glory', game.initiative), + create_function_node('end_of_turn'), ]; next(); } function setup_choose_card() { console.log('setup_choose_card'); const player_order = get_player_order(); - game.engine = player_order.map((faction_id) => ({ - t: leaf_node, - p: faction_id, - s: 'choose_card', - })); - game.engine.push({ - t: function_node, - f: 'setup_player_turn', - }); + game.engine = player_order.map((faction_id) => create_leaf_node('choose_card', faction_id)); + game.engine.push(create_function_node('setup_player_turn')); next(); } function setup_player_turn() { console.log('setup_player_turn'); const player_order = get_player_order(); - game.engine = player_order.map((faction_id) => ({ - t: seq_node, - c: [ - { - t: leaf_node, - s: 'player_turn', - p: faction_id, - }, - ], - })); - game.engine.push({ - t: function_node, - f: 'resolve_fascist_test', - }); - game.engine.push({ - t: function_node, - f: 'setup_bag_of_glory', - }); + game.engine = player_order.map((faction_id) => create_seq_node([create_leaf_node('player_turn', faction_id)])); + game.engine.push(create_function_node('resolve_fascist_test')); + game.engine.push(create_function_node('setup_bag_of_glory')); next(); } const engine_functions = { + check_activate_icon, end_of_turn, end_of_year, setup_bag_of_glory, @@ -224,8 +226,10 @@ function game_view(state, player) { fronts: game.fronts, hand: game.hands[faction_id], medaillons: game.medaillons, - selected_card: game.cards_in_play[faction_id], + selected_card: game.chosen_cards[faction_id], + tableaus: game.tableaus, tracks: game.tracks, + triggered_track_effects: game.triggered_track_effects, }; if (player !== game.active) { let inactive = states[game.state].inactive || game.state; @@ -278,7 +282,7 @@ function setup(seed, _scenario, _options) { [MODERATES_ID]: 0, pool: 14, }, - cards_in_play: { + chosen_cards: { [ANARCHISTS_ID]: null, [COMMUNISTS_ID]: null, [MODERATES_ID]: null, @@ -297,6 +301,11 @@ function setup(seed, _scenario, _options) { [MODERATES_ID]: [], }, tracks: [5, 5, 6, 3, 3], + trash: { + [ANARCHISTS_ID]: [], + [COMMUNISTS_ID]: [], + [MODERATES_ID]: [], + }, triggered_track_effects: [[], [], [], [], []], log: [], undo: [], @@ -335,6 +344,12 @@ function start_turn() { }); next(); } +states.activate_icon = { + inactive: 'activate an icon', + prompt() { + view.prompt = 'Choose an icon to activate'; + } +}; states.add_glory = { inactive: 'add tokens to the Bag of Glory', prompt() { @@ -357,25 +372,43 @@ states.add_glory = { resolve_active_and_proceed(); }, }; +states.add_to_front = { + inactive: 'add strength to a Front', + prompt() { + const args = get_active_node_args(); + const possible_fronts = get_fronts_to_add_to(args.t); + view.prompt = + possible_fronts.length === 1 + ? `Add strength to ${front_names[possible_fronts[0]]}` + : 'Add strength to a Front'; + for (let f of possible_fronts) { + gen_action_front(f); + } + }, + front(f) { + const value = get_active_node_args().v; + update_front(f, value); + resolve_active_and_proceed(); + }, +}; states.attack_front = { inactive: 'attack a Front', prompt() { const node = get_active_node(); - const front = node.a.f; + const front = node.a.t; view.prompt = 'Attack ' + front_names[front]; if (front === 'd' || front === 'v') { const fronts = get_fronts_closest_to(front); fronts.forEach((id) => gen_action('front', id)); } else { - gen_action('front', front); + gen_action_front(front); } }, front(f) { const node = get_active_node(); const value = node.a.v; - game.fronts[f] += value; - log_h3(`${Math.abs(value)} attacks added to ${front_names[f]}`); + update_front(f, value); resolve_active_and_proceed(); }, }; @@ -383,9 +416,18 @@ states.choose_area_ap = { inactive: 'choose area to use Action Points', prompt() { view.prompt = 'Choose area of the board to affect'; - for (let track of tracks) { + for (const track of tracks) { gen_action_standee(track.id); } + const fronts = get_fronts_to_add_to(data_1.ANY); + for (const front of fronts) { + gen_action_front(front); + } + }, + front(f) { + const s = get_active_node_args().strength; + update_front(f, s); + resolve_active_and_proceed(); }, standee(track_id) { console.log('standee', track_id); @@ -401,22 +443,36 @@ states.choose_area_ap = { resolve_active_and_proceed(); }, }; -states.move_track_up_or_down = { - inactive: 'move a track', +states.change_bonus = { + inactive: 'gain select Bonus', prompt() { - const node = get_active_node(); - view.prompt = `Move ${get_track_name(node.a.track_id)} up or down`; - gen_action('up'); - gen_action('down'); + const args = get_active_node_args(); + if ((args.v === data_1.ON && + game.bonuses[data_1.TEAMWORK_BONUS] === data_1.ON && + game.bonuses[data_1.MORALE_BONUS] === data_1.ON) || + (args.v === data_1.OFF && game.bonuses[args.t] === data_1.OFF)) { + gen_action('skip'); + } + if (args.t === data_1.ANY && args.v === data_1.ON) { + view.prompt = 'Turn on a Bonus'; + for (const bonus of bonuses) { + if (game.bonuses[bonus] === data_1.OFF) { + gen_action_bonus(bonus); + } + } + } + else if (args.v === data_1.OFF) { + view.prompt = `Turn off ${bonus_names[args.t]}`; + gen_action_bonus(args.t); + } }, - down() { - const node = get_active_node(); - move_track(node.a.track_id, -1 * node.a.strength); + bonus(b) { + const value = get_active_node_args().v; + game.bonuses[b] = value; + log(`${bonus_names[b]} ${value === data_1.ON ? 'on' : 'off'}`); resolve_active_and_proceed(); }, - up() { - const node = get_active_node(); - move_track(node.a.track_id, node.a.strength); + skip() { resolve_active_and_proceed(); }, }; @@ -429,18 +485,53 @@ states.choose_card = { gen_action_card(c); }, card(c) { - log_h3(`${game.active} chooses a card`); - if (!game.cards_in_play) { - game.cards_in_play = {}; + game.chosen_cards[player_faction_map[game.active]] = c; + resolve_active_and_proceed(); + }, +}; +states.gain_hero_points = { + inactive: 'gain Hero Points', + prompt() { + const value = get_active_node_args().v; + view.prompt = value > 1 ? `Gain ${value} Hero Points` : 'Gain 1 Hero Point'; + gen_action('gain_hp'); + }, + gain_hp() { + const value = get_active_node_args().v; + if (game.hero_points.pool > value) { + game.hero_points.pool -= value; + game.hero_points[get_active_faction()] += value; } - game.cards_in_play[player_faction_map[game.active]] = c; resolve_active_and_proceed(); }, }; states.lose_hero_points = { inactive: 'choose a Player', prompt() { - view.prompt = 'Lose Hero Points'; + const args = get_active_node_args(); + view.prompt = 'Choose player to lose Hero Points'; + if (args.t === data_1.PLAYER_WITH_MOST_HERO_POINTS) { + const factions = get_factions_with_most_hero_poins(); + console.log('faction', factions); + for (let faction_id of factions) { + gen_action(faction_player_map[faction_id]); + } + } + }, + Anarchist() { + const value = get_active_node_args().v; + lose_hero_point(ANARCHISTS_ID, value); + resolve_active_and_proceed(); + }, + Communist() { + const value = get_active_node_args().v; + lose_hero_point(ANARCHISTS_ID, value); + resolve_active_and_proceed(); + }, + Moderate() { + const value = get_active_node_args().v; + lose_hero_point(ANARCHISTS_ID, value); + resolve_active_and_proceed(); }, }; states.move_track = { @@ -459,23 +550,46 @@ states.move_track = { resolve_active_and_proceed(); }, }; +states.move_track_up_or_down = { + inactive: 'move a track', + prompt() { + const node = get_active_node(); + view.prompt = `Move ${get_track_name(node.a.track_id)} up or down`; + gen_action('up'); + gen_action('down'); + }, + down() { + const node = get_active_node(); + move_track(node.a.track_id, -1 * node.a.strength); + resolve_active_and_proceed(); + }, + up() { + const node = get_active_node(); + move_track(node.a.track_id, node.a.strength); + resolve_active_and_proceed(); + }, +}; states.player_turn = { inactive: 'play their turn', prompt() { const faction_id = get_faction_id(game.active); - const hero_points = game.hero_points[faction_id]; - view.prompt = - hero_points === 0 - ? 'Play your card' - : 'Play your card or spend Hero points'; - if (game.cards_in_play[faction_id] !== null) { + const can_spend_hp = game.hero_points[faction_id] > 0; + const can_play_card = game.hands[faction_id].includes(game.chosen_cards[faction_id]); + view.prompt = 'Play a card or spend Hero points'; + if (!(can_play_card || can_spend_hp)) { + view.prompt = 'End turn'; + } + else if (!can_play_card && can_spend_hp) { + view.prompt = 'Spend Hero Points or end turn'; + } + if (can_play_card) { gen_action('play_for_ap'); gen_action('play_for_event'); } else { gen_action('done'); } - if (hero_points > 0) { + if (can_spend_hp) { gen_action('spend_hp'); } }, @@ -484,34 +598,25 @@ states.player_turn = { }, play_for_ap() { const faction_id = get_faction_id(game.active); - const card = game.cards_in_play[faction_id]; + const card = game.chosen_cards[faction_id]; log_h3(`${game.active} plays ${cards[card].title} for the Action Points`); - if (!game.discard) { - game.discard = { - [ANARCHISTS_ID]: [], - [COMMUNISTS_ID]: [], - [MODERATES_ID]: [], - f: [], - }; - } array_remove(game.hands[faction_id], game.hands[faction_id].indexOf(card)); - game.discard[faction_id].push(card); - insert_before_active_node({ - t: leaf_node, - p: faction_id, - s: 'choose_area_ap', - a: { + game.tableaus[faction_id].push(card); + insert_before_active_node(create_seq_node([ + create_leaf_node('choose_area_ap', faction_id, { strength: cards[card].strength, - }, - }); + }), + create_function_node('check_activate_icon'), + ])); next(); }, play_for_event() { const faction_id = get_faction_id(game.active); - const card = game.cards_in_play[faction_id]; + const card = game.chosen_cards[faction_id]; + array_remove(game.hands[faction_id], game.hands[faction_id].indexOf(card)); + game.trash[faction_id].push(card); log_h3(`${game.active} plays ${cards[card].title} for the Event`); - game.cards_in_play[faction_id] = null; - game.tableaus[faction_id].push(card); + insert_after_active_node(create_effects_node(cards[card].effects)); resolve_active_and_proceed(); }, spend_hp() { @@ -522,52 +627,6 @@ states.player_turn = { }); next(); }, - card(c) { - const faction = get_active_faction(); - log_h3(`${game.active} plays ${cards[c].title} to their tableau`); - if (!game.tableaus) { - game.tableaus = { - [ANARCHISTS_ID]: [], - [COMMUNISTS_ID]: [], - [MODERATES_ID]: [], - }; - } - game.cards_in_play[faction] = null; - game.tableaus[faction].push(c); - array_remove(game.hands[faction], game.hands[faction].indexOf(c)); - resolve_active_and_proceed(); - }, -}; -states.resolve_event = { - inactive: 'resolve Fascist Event', - prompt() { - const card = get_current_event(); - const node = get_active_node(game.engine); - const effect = card.effects[node.a]; - view.prompt = get_event_prompt(effect); - if (effect.type === 'track') { - gen_action('standee', effect.target); - } - else if (effect.type === 'hero_points' && - effect.target === data_1.PLAYER_WITH_MOST_HERO_POINTS) { - const factions = get_factions_with_most_hero_poins(); - for (let faction_id of factions) { - gen_action(get_player(faction_id)); - } - } - }, - Anarchist() { - lose_hero_point(ANARCHISTS_ID, 1); - resolve_active_and_proceed(); - }, - Communist() { - lose_hero_point(ANARCHISTS_ID, 1); - resolve_active_and_proceed(); - }, - Moderate() { - lose_hero_point(ANARCHISTS_ID, 1); - resolve_active_and_proceed(); - }, }; states.spend_hero_points = { inactive: 'spend Hero points', @@ -595,6 +654,12 @@ function pop_undo() { game.log = save_log; game.undo = save_undo; } +function check_activate_icon() { + if (game.bonuses[data_1.MORALE_BONUS] === data_1.ON) { + insert_after_active_node(create_leaf_node('activate_icon', get_active_faction_id())); + } + resolve_active_and_proceed(); +} function end_of_turn() { log_h2('End of turn'); if (game.turn === 4) { @@ -611,13 +676,25 @@ function resolve_fascist_test() { log_h2('Fascist test is resolved'); next(); } +function get_fronts_to_add_to(target) { + console.log('get_fronts_to_add_to', target); + if (target === data_1.CLOSEST_TO_DEFEAT || target === data_1.CLOSEST_TO_VICTORY) { + return get_fronts_closest_to(target); + } + else if (target === data_1.ANY) { + return ['a', 'm', 'n', 's']; + } + else { + return [target]; + } +} function move_track(track_id, change) { const current_value = game.tracks[track_id]; let new_value = current_value + change; new_value = Math.max(new_value, track_id === data_1.GOVERNMENT ? 1 : 0); new_value = Math.min(new_value, 10); game.tracks[track_id] = new_value; - log(`${game.active} moves ${get_track_name(track_id)} to ${new_value}`); + log(`${get_track_name(track_id)} to ${new_value}`); const triggered_spaces = change > 0 ? make_list(current_value + 1, new_value).reverse() : make_list(new_value, current_value - 1); @@ -635,42 +712,46 @@ function move_track(track_id, change) { } }); } +function update_front(f, change) { + game.fronts[f] += change; + log(`${front_names[f]}: ${change > 0 ? '+' : ''}${change}`); +} +function create_effects_node(effects) { + const nodes = effects.reduce((accrued, current) => { + const node = resolve_effect(current); + if (node !== null) { + accrued.push(node); + } + return accrued; + }, []); + return { + t: seq_node, + c: nodes, + }; +} +const effect_type_state_map = { + attack: 'attack_front', + bonus: 'change_bonus', + front: 'add_to_front', + track: 'move_track', +}; function resolve_effect(effect, faction = get_active_faction()) { - if (effect.type === 'attack') { - return { - t: leaf_node, - p: faction, - s: 'attack_front', - a: { - f: effect.target, - v: effect.value, - }, - }; - } - if (effect.type === 'track') { - return { - t: leaf_node, - p: faction, - s: 'move_track', - a: { - t: effect.target, - v: effect.value, - }, - }; + const args = { + t: effect.target, + v: effect.value, + }; + let state = effect_type_state_map[effect.type]; + if (state !== undefined) { + return create_leaf_node(state, faction, args); } if (effect.type === 'hero_points' && effect.target === data_1.PLAYER_WITH_MOST_HERO_POINTS) { - return { - t: leaf_node, - p: faction, - s: 'lose_hero_points', - a: { - p: effect.target, - v: effect.value, - }, - }; + state = 'lose_hero_points'; } - return null; + if (effect.type === 'hero_points' && effect.target === data_1.SELF) { + state = 'gain_hero_points'; + } + return state === undefined ? null : create_leaf_node(state, faction, args); } function draw_card(deck) { clear_undo(); @@ -686,33 +767,19 @@ function draw_item(ids) { return r; } function lose_hero_point(faction, value) { - const points_lost = Math.min(game.hero_points[faction], value); + const points_lost = Math.min(game.hero_points[faction], Math.abs(value)); game.hero_points.pool += points_lost; game.hero_points[faction] -= points_lost; + if (points_lost === 0) { + return; + } if (points_lost === 1) { - log(`${get_player(faction)} loses 1 Hero Point`); + log(`${get_player(faction)}: -1 Hero Point`); } else { - log(`${get_player(faction)} loses ${points_lost} Hero Points`); + log(`${get_player(faction)}: -${points_lost} Hero Points`); } } -function get_current_event() { - return cards[game.current_events[game.current_events.length - 1]]; -} -function get_event_prompt(effect) { - let prompt = ''; - switch (effect.type) { - case 'attack': - return 'Attack ' + front_names[effect.target]; - case 'bonus': - break; - case 'hero_points': - return 'Select player with most Hero points'; - case 'track': - return 'Decrease ' + tracks[effect.target].name; - } - return prompt; -} function get_fronts_closest_to(target) { const values = Object.values(game.fronts); const targetValue = target === 'd' ? Math.min(...values) : Math.max(...values); @@ -783,11 +850,18 @@ function get_next_faction(faction_id) { return role_ids[index + 1]; } function get_factions_with_most_hero_poins() { - const most_hero_points = Math.max(...Object.values(game.hero_points)); - const faction_ids = []; - Object.entries(game.hero_points).forEach(([faction, hero_points]) => { - if (hero_points === most_hero_points) { - faction_ids.push(faction); + let most_hero_points = null; + let faction_ids = []; + Object.entries(game.hero_points).forEach(([id, value]) => { + if (id === 'pool') { + return; + } + if (most_hero_points === null || value > most_hero_points) { + most_hero_points = value; + faction_ids = [id]; + } + else if (most_hero_points === value) { + faction_ids.push(id); } }); return faction_ids; diff --git a/rules.ts b/rules.ts index 0b4901f..e5206bd 100644 --- a/rules.ts +++ b/rules.ts @@ -11,6 +11,7 @@ import { LeafNode, Player, PlayerCard, + SeqNode, States, View, } from './types'; @@ -24,13 +25,20 @@ import data, { // MORALE_BONUS, // TEAMWORK_BONUS, // OFF, - ON, LIBERTY, + CLOSEST_TO_DEFEAT, + CLOSEST_TO_VICTORY, COLLECTIVIZATION, GOVERNMENT, SOVIET_SUPPORT, FOREIGN_AID, + ON, PLAYER_WITH_MOST_HERO_POINTS, + SELF, + ANY, + TEAMWORK_BONUS, + MORALE_BONUS, + OFF, // StaticData, // PLAYER_WITH_MOST_HERO_POINTS, } from './data'; @@ -67,20 +75,24 @@ const player_faction_map: Record = { }; const front_names: Record = { - a: 'the Aragon Front', - m: 'the Madrid Front', - n: 'the Nothern Front', - s: 'the Southern Front', + a: 'Aragon Front', + m: 'Madrid Front', + n: 'Nothern Front', + s: 'Southern Front', d: 'the Front closest to Defeat', v: 'the Front closest to Victory', }; +const bonus_names: string[] = ['Morale Bonus', 'Teamwork Bonus']; + const { cards, // fronts, tracks, } = data; +const bonuses = [MORALE_BONUS, TEAMWORK_BONUS]; + const faction_cards = { [ANARCHISTS_ID]: make_list(37, 54) as CardId[], [COMMUNISTS_ID]: make_list(19, 36) as CardId[], @@ -108,10 +120,18 @@ function gen_action(action: string, argument?: number | string) { } } +function gen_action_bonus(bonus_id: number) { + gen_action('bonus', bonus_id); +} + function gen_action_card(card_id: CardId) { gen_action('card', card_id); } +function gen_action_front(front_id: string) { + gen_action('front', front_id); +} + function gen_action_standee(track_id: number) { gen_action('standee', track_id); } @@ -150,18 +170,40 @@ const seq_node = 's'; const function_node = 'f'; const resolved = 1; +function create_leaf_node( + state: string, + faction: FactionId, + args?: any +): LeafNode { + return { + t: leaf_node, + s: state, + p: faction, + a: args, + r: 0, + }; +} + +function create_function_node(func_name: string, args?: any): FunctionNode { + return { + t: function_node, + f: func_name, + a: args, + r: 0, + }; +} + +function create_seq_node(children: EngineNode[]): SeqNode { + return { + t: seq_node, + c: children, + }; +} + function setup_bag_of_glory() { - console.log('setup_bag_of_glory'); game.engine = [ - { - t: leaf_node, - p: game.initiative, - s: 'add_glory', - }, - { - t: function_node, - f: 'end_of_turn', - }, + create_leaf_node('add_glory', game.initiative), + create_function_node('end_of_turn'), ]; next(); } @@ -169,43 +211,26 @@ function setup_bag_of_glory() { function setup_choose_card() { console.log('setup_choose_card'); const player_order = get_player_order(); - game.engine = player_order.map((faction_id) => ({ - t: leaf_node, - p: faction_id, - s: 'choose_card', - })); - game.engine.push({ - t: function_node, - f: 'setup_player_turn', - }); + game.engine = player_order.map((faction_id) => + create_leaf_node('choose_card', faction_id) + ); + game.engine.push(create_function_node('setup_player_turn')); next(); } function setup_player_turn() { console.log('setup_player_turn'); const player_order = get_player_order(); - game.engine = player_order.map((faction_id) => ({ - t: seq_node, - c: [ - { - t: leaf_node, - s: 'player_turn', - p: faction_id, - }, - ], - })); - game.engine.push({ - t: function_node, - f: 'resolve_fascist_test', - }); - game.engine.push({ - t: function_node, - f: 'setup_bag_of_glory', - }); + game.engine = player_order.map((faction_id) => + create_seq_node([create_leaf_node('player_turn', faction_id)]) + ); + game.engine.push(create_function_node('resolve_fascist_test')); + game.engine.push(create_function_node('setup_bag_of_glory')); next(); } const engine_functions: Record = { + check_activate_icon, end_of_turn, end_of_year, setup_bag_of_glory, @@ -360,8 +385,10 @@ function game_view(state: Game, player: Player) { fronts: game.fronts, hand: game.hands[faction_id], medaillons: game.medaillons, - selected_card: game.cards_in_play[faction_id], + selected_card: game.chosen_cards[faction_id], + tableaus: game.tableaus, tracks: game.tracks, + triggered_track_effects: game.triggered_track_effects, }; if (player !== game.active) { @@ -419,7 +446,7 @@ export function setup(seed: number, _scenario: string, _options: unknown) { [MODERATES_ID]: 0, pool: 14, }, - cards_in_play: { + chosen_cards: { [ANARCHISTS_ID]: null, [COMMUNISTS_ID]: null, [MODERATES_ID]: null, @@ -438,6 +465,11 @@ export function setup(seed: number, _scenario: string, _options: unknown) { [MODERATES_ID]: [], }, tracks: [5, 5, 6, 3, 3], + trash: { + [ANARCHISTS_ID]: [], + [COMMUNISTS_ID]: [], + [MODERATES_ID]: [], + }, triggered_track_effects: [[], [], [], [], []], log: [], undo: [], @@ -495,6 +527,13 @@ function start_turn() { // region STATES +states.activate_icon = { + inactive: 'activate an icon', + prompt() { + view.prompt = 'Choose an icon to activate' + } +} + states.add_glory = { inactive: 'add tokens to the Bag of Glory', prompt() { @@ -517,24 +556,43 @@ states.add_glory = { }, }; +states.add_to_front = { + inactive: 'add strength to a Front', + prompt() { + const args = get_active_node_args(); + const possible_fronts = get_fronts_to_add_to(args.t); + view.prompt = + possible_fronts.length === 1 + ? `Add strength to ${front_names[possible_fronts[0]]}` + : 'Add strength to a Front'; + for (let f of possible_fronts) { + gen_action_front(f); + } + }, + front(f: string) { + const value = get_active_node_args().v; + update_front(f, value); + resolve_active_and_proceed(); + }, +}; + states.attack_front = { inactive: 'attack a Front', prompt() { const node = get_active_node(); - const front = node.a.f; + const front = node.a.t; view.prompt = 'Attack ' + front_names[front]; if (front === 'd' || front === 'v') { const fronts = get_fronts_closest_to(front); fronts.forEach((id) => gen_action('front', id)); } else { - gen_action('front', front); + gen_action_front(front); } }, front(f: string) { const node = get_active_node(); const value = node.a.v; - game.fronts[f] += value; - log_h3(`${Math.abs(value)} attacks added to ${front_names[f]}`); + update_front(f, value); resolve_active_and_proceed(); }, }; @@ -543,9 +601,18 @@ states.choose_area_ap = { inactive: 'choose area to use Action Points', prompt() { view.prompt = 'Choose area of the board to affect'; - for (let track of tracks) { + for (const track of tracks) { gen_action_standee(track.id); } + const fronts = get_fronts_to_add_to(ANY); + for (const front of fronts) { + gen_action_front(front); + } + }, + front(f: string) { + const s = get_active_node_args().strength; + update_front(f, s); + resolve_active_and_proceed(); }, standee(track_id: number) { console.log('standee', track_id); @@ -562,22 +629,37 @@ states.choose_area_ap = { }, }; -states.move_track_up_or_down = { - inactive: 'move a track', +states.change_bonus = { + inactive: 'gain select Bonus', prompt() { - const node = get_active_node(); - view.prompt = `Move ${get_track_name(node.a.track_id)} up or down`; - gen_action('up'); - gen_action('down'); + const args = get_active_node_args(); + if ( + (args.v === ON && + game.bonuses[TEAMWORK_BONUS] === ON && + game.bonuses[MORALE_BONUS] === ON) || + (args.v === OFF && game.bonuses[args.t] === OFF) + ) { + gen_action('skip'); + } + if (args.t === ANY && args.v === ON) { + view.prompt = 'Turn on a Bonus'; + for (const bonus of bonuses) { + if (game.bonuses[bonus] === OFF) { + gen_action_bonus(bonus); + } + } + } else if (args.v === OFF) { + view.prompt = `Turn off ${bonus_names[args.t]}`; + gen_action_bonus(args.t); + } }, - down() { - const node = get_active_node(); - move_track(node.a.track_id, -1 * node.a.strength); + bonus(b: number) { + const value = get_active_node_args().v; + game.bonuses[b] = value; + log(`${bonus_names[b]} ${value === ON ? 'on' : 'off'}`); resolve_active_and_proceed(); }, - up() { - const node = get_active_node(); - move_track(node.a.track_id, node.a.strength); + skip() { resolve_active_and_proceed(); }, }; @@ -590,11 +672,24 @@ states.choose_card = { for (let c of hand) gen_action_card(c); }, card(c: CardId) { - log_h3(`${game.active} chooses a card`); - if (!game.cards_in_play) { - game.cards_in_play = {}; + game.chosen_cards[player_faction_map[game.active]] = c; + resolve_active_and_proceed(); + }, +}; + +states.gain_hero_points = { + inactive: 'gain Hero Points', + prompt() { + const value = get_active_node_args().v; + view.prompt = value > 1 ? `Gain ${value} Hero Points` : 'Gain 1 Hero Point'; + gen_action('gain_hp'); + }, + gain_hp() { + const value = get_active_node_args().v; + if (game.hero_points.pool > value) { + game.hero_points.pool -= value; + game.hero_points[get_active_faction()] += value; } - game.cards_in_play[player_faction_map[game.active]] = c; resolve_active_and_proceed(); }, }; @@ -602,7 +697,30 @@ states.choose_card = { states.lose_hero_points = { inactive: 'choose a Player', prompt() { - view.prompt = 'Lose Hero Points'; + const args = get_active_node_args(); + view.prompt = 'Choose player to lose Hero Points'; + if (args.t === PLAYER_WITH_MOST_HERO_POINTS) { + const factions = get_factions_with_most_hero_poins(); + console.log('faction', factions); + for (let faction_id of factions) { + gen_action(faction_player_map[faction_id]); + } + } + }, + Anarchist() { + const value = get_active_node_args().v; + lose_hero_point(ANARCHISTS_ID, value); + resolve_active_and_proceed(); + }, + Communist() { + const value = get_active_node_args().v; + lose_hero_point(ANARCHISTS_ID, value); + resolve_active_and_proceed(); + }, + Moderate() { + const value = get_active_node_args().v; + lose_hero_point(ANARCHISTS_ID, value); + resolve_active_and_proceed(); }, }; @@ -614,12 +732,32 @@ states.move_track = { const value = node.a.v; view.prompt = `Move ${tracks[track].name} ${value > 0 ? 'up' : 'down'}`; // return 'Decrease ' + tracks[effect.target].name; - gen_action_standee(track) + gen_action_standee(track); }, standee(s: number) { const node = get_active_node(); const value = node.a.v; - move_track(s, value) + move_track(s, value); + resolve_active_and_proceed(); + }, +}; + +states.move_track_up_or_down = { + inactive: 'move a track', + prompt() { + const node = get_active_node(); + view.prompt = `Move ${get_track_name(node.a.track_id)} up or down`; + gen_action('up'); + gen_action('down'); + }, + down() { + const node = get_active_node(); + move_track(node.a.track_id, -1 * node.a.strength); + resolve_active_and_proceed(); + }, + up() { + const node = get_active_node(); + move_track(node.a.track_id, node.a.strength); resolve_active_and_proceed(); }, }; @@ -628,21 +766,26 @@ states.player_turn = { inactive: 'play their turn', prompt() { const faction_id = get_faction_id(game.active); - const hero_points = game.hero_points[faction_id]; - view.prompt = - hero_points === 0 - ? 'Play your card' - : 'Play your card or spend Hero points'; + const can_spend_hp = game.hero_points[faction_id] > 0; + const can_play_card = game.hands[faction_id].includes( + game.chosen_cards[faction_id] + ); + view.prompt = 'Play a card or spend Hero points'; + if (!(can_play_card || can_spend_hp)) { + view.prompt = 'End turn'; + } else if (!can_play_card && can_spend_hp) { + view.prompt = 'Spend Hero Points or end turn'; + } // const card = game.cards_in_play[faction_id]; - if (game.cards_in_play[faction_id] !== null) { + if (can_play_card) { // gen_action_card(); gen_action('play_for_ap'); gen_action('play_for_event'); } else { gen_action('done'); } - if (hero_points > 0) { + if (can_spend_hp) { gen_action('spend_hp'); } }, @@ -651,34 +794,29 @@ states.player_turn = { }, play_for_ap() { const faction_id = get_faction_id(game.active); - const card = game.cards_in_play[faction_id]; + const card = game.chosen_cards[faction_id]; log_h3(`${game.active} plays ${cards[card].title} for the Action Points`); - if (!game.discard) { - game.discard = { - [ANARCHISTS_ID]: [], - [COMMUNISTS_ID]: [], - [MODERATES_ID]: [], - f: [], - }; - } array_remove(game.hands[faction_id], game.hands[faction_id].indexOf(card)); - game.discard[faction_id].push(card); - insert_before_active_node({ - t: leaf_node, - p: faction_id, - s: 'choose_area_ap', - a: { - strength: (cards[card] as PlayerCard).strength, - }, - }); + game.tableaus[faction_id].push(card); + insert_before_active_node( + create_seq_node([ + create_leaf_node('choose_area_ap', faction_id, { + strength: (cards[card] as PlayerCard).strength, + }), + create_function_node('check_activate_icon'), + ]) + ); next(); }, play_for_event() { const faction_id = get_faction_id(game.active); - const card = game.cards_in_play[faction_id]; + const card = game.chosen_cards[faction_id]; + array_remove(game.hands[faction_id], game.hands[faction_id].indexOf(card)); + game.trash[faction_id].push(card); log_h3(`${game.active} plays ${cards[card].title} for the Event`); - game.cards_in_play[faction_id] = null; - game.tableaus[faction_id].push(card); + + insert_after_active_node(create_effects_node(cards[card].effects)); + resolve_active_and_proceed(); }, spend_hp() { @@ -690,58 +828,58 @@ states.player_turn = { // insert spend hero points node before current node next(); }, - card(c: CardId) { - const faction = get_active_faction(); - log_h3(`${game.active} plays ${cards[c].title} to their tableau`); - if (!game.tableaus) { - game.tableaus = { - [ANARCHISTS_ID]: [], - [COMMUNISTS_ID]: [], - [MODERATES_ID]: [], - }; - } - game.cards_in_play[faction] = null; - game.tableaus[faction].push(c); - array_remove(game.hands[faction], game.hands[faction].indexOf(c)); - resolve_active_and_proceed(); - }, + // card(c: CardId) { + // const faction = get_active_faction(); + // log_h3(`${game.active} plays ${cards[c].title} to their tableau`); + // if (!game.tableaus) { + // game.tableaus = { + // [ANARCHISTS_ID]: [], + // [COMMUNISTS_ID]: [], + // [MODERATES_ID]: [], + // }; + // } + // game.chosen_cards[faction] = null; + // game.tableaus[faction].push(c); + // array_remove(game.hands[faction], game.hands[faction].indexOf(c)); + // resolve_active_and_proceed(); + // }, }; -states.resolve_event = { - inactive: 'resolve Fascist Event', - prompt() { - const card = get_current_event(); - const node = get_active_node(game.engine) as LeafNode; - const effect: Effect = card.effects[node.a]; - view.prompt = get_event_prompt(effect); - -if (effect.type === 'track') { - gen_action('standee', effect.target); - } else if ( - effect.type === 'hero_points' && - effect.target === PLAYER_WITH_MOST_HERO_POINTS - ) { - const factions = get_factions_with_most_hero_poins(); - for (let faction_id of factions) { - gen_action(get_player(faction_id)); - } - } - // for (let p = 0; p < 5; ++p) gen_action('standee', p); - }, +// states.resolve_event = { +// inactive: 'resolve Fascist Event', +// prompt() { +// const card = get_current_event(); +// const node = get_active_node(game.engine) as LeafNode; +// const effect: Effect = card.effects[node.a]; +// view.prompt = get_event_prompt(effect); + +// if (effect.type === 'track') { +// gen_action('standee', effect.target); +// } else if ( +// effect.type === 'hero_points' && +// effect.target === PLAYER_WITH_MOST_HERO_POINTS +// ) { +// const factions = get_factions_with_most_hero_poins(); +// for (let faction_id of factions) { +// gen_action(get_player(faction_id)); +// } +// } +// // for (let p = 0; p < 5; ++p) gen_action('standee', p); +// }, - Anarchist() { - lose_hero_point(ANARCHISTS_ID, 1); - resolve_active_and_proceed(); - }, - Communist() { - lose_hero_point(ANARCHISTS_ID, 1); - resolve_active_and_proceed(); - }, - Moderate() { - lose_hero_point(ANARCHISTS_ID, 1); - resolve_active_and_proceed(); - }, -}; +// Anarchist() { +// lose_hero_point(ANARCHISTS_ID, 1); +// resolve_active_and_proceed(); +// }, +// Communist() { +// lose_hero_point(ANARCHISTS_ID, 1); +// resolve_active_and_proceed(); +// }, +// Moderate() { +// lose_hero_point(ANARCHISTS_ID, 1); +// resolve_active_and_proceed(); +// }, +// }; states.spend_hero_points = { inactive: 'spend Hero points', @@ -793,6 +931,16 @@ function pop_undo() { // #region GAME FUNCTIONS +// Check if Morale bonus is on so player can activate icon +function check_activate_icon() { + if (game.bonuses[MORALE_BONUS] === ON) { + insert_after_active_node( + create_leaf_node('activate_icon', get_active_faction_id()) + ); + } + resolve_active_and_proceed(); +} + function end_of_turn() { // REMOVE playre tplems from the Fronts; log_h2('End of turn'); @@ -812,6 +960,18 @@ function resolve_fascist_test() { next(); } +// TODO: check for defeated / won fronts +function get_fronts_to_add_to(target: string): string[] { + console.log('get_fronts_to_add_to', target); + if (target === CLOSEST_TO_DEFEAT || target === CLOSEST_TO_VICTORY) { + return get_fronts_closest_to(target); + } else if (target === ANY) { + return ['a', 'm', 'n', 's']; + } else { + return [target]; + } +} + function move_track(track_id: number, change: number) { // Check if track can be moved // Updata values @@ -822,7 +982,7 @@ function move_track(track_id: number, change: number) { new_value = Math.max(new_value, track_id === GOVERNMENT ? 1 : 0); new_value = Math.min(new_value, 10); game.tracks[track_id] = new_value; - log(`${game.active} moves ${get_track_name(track_id)} to ${new_value}`); + log(`${get_track_name(track_id)} to ${new_value}`); const triggered_spaces = change > 0 ? make_list(current_value + 1, new_value).reverse() @@ -845,67 +1005,57 @@ function move_track(track_id: number, change: number) { }); } +// TODO: acccount for victory / defeat of front +function update_front(f: string, change: number) { + game.fronts[f] += change; + log(`${front_names[f]}: ${change > 0 ? '+' : ''}${change}`); +} + +function create_effects_node(effects: Effect[]): EngineNode { + const nodes = effects.reduce((accrued: EngineNode[], current: Effect) => { + const node = resolve_effect(current); + if (node !== null) { + accrued.push(node); + } + return accrued; + }, []); + return { + t: seq_node, + c: nodes, + }; +} + +const effect_type_state_map: Record = { + attack: 'attack_front', + bonus: 'change_bonus', + front: 'add_to_front', + track: 'move_track', +}; + function resolve_effect( effect: Effect, faction: FactionId = get_active_faction() ): EngineNode | null { - if (effect.type === 'attack') { - return { - t: leaf_node, - p: faction, - s: 'attack_front', - a: { - f: effect.target, - v: effect.value, - }, - }; - } - if (effect.type === 'track') { - return { - t: leaf_node, - p: faction, - s: 'move_track', - a: { - t: effect.target, - v: effect.value, - }, - }; + const args = { + t: effect.target, + v: effect.value, + }; + let state = effect_type_state_map[effect.type]; + + if (state !== undefined) { + return create_leaf_node(state, faction, args); } + if ( effect.type === 'hero_points' && effect.target === PLAYER_WITH_MOST_HERO_POINTS ) { - return { - t: leaf_node, - p: faction, - s: 'lose_hero_points', - a: { - p: effect.target, - v: effect.value, - }, - }; + state = 'lose_hero_points'; } - return null; - - // if ( - // effect.type === 'attack' && - // (effect.target === 'd' || effect.target === 'v') - // ) { - // const fronts = get_fronts_closest_to(effect.target); - // fronts.forEach((id) => gen_action('front', id)); - // } else if (effect.type === 'attack') { - // gen_action('front', effect.target); - // } else if (effect.type === 'track') { - // gen_action('standee', effect.target); - // } else if ( - // effect.type === 'hero_points' && - // effect.target === PLAYER_WITH_MOST_HERO_POINTS - // ) { - // const factions = get_factions_with_most_hero_poins(); - // for (let faction_id of factions) { - // gen_action(get_player(faction_id)); - // } - // } + if (effect.type === 'hero_points' && effect.target === SELF) { + state = 'gain_hero_points'; + } + return state === undefined ? null : create_leaf_node(state, faction, args); } // #endregion @@ -936,13 +1086,16 @@ function draw_item(ids: number[]): number { } function lose_hero_point(faction: FactionId, value: number) { - const points_lost = Math.min(game.hero_points[faction], value); + const points_lost = Math.min(game.hero_points[faction], Math.abs(value)); game.hero_points.pool += points_lost; game.hero_points[faction] -= points_lost; + if (points_lost === 0) { + return; + } if (points_lost === 1) { - log(`${get_player(faction)} loses 1 Hero Point`); + log(`${get_player(faction)}: -1 Hero Point`); } else { - log(`${get_player(faction)} loses ${points_lost} Hero Points`); + log(`${get_player(faction)}: -${points_lost} Hero Points`); } } @@ -950,11 +1103,11 @@ function lose_hero_point(faction: FactionId, value: number) { // #region EVENTS -function get_current_event(): EventCard { - return cards[ - game.current_events[game.current_events.length - 1] - ] as EventCard; -} +// function get_current_event(): EventCard { +// return cards[ +// game.current_events[game.current_events.length - 1] +// ] as EventCard; +// } // function get_front_name(frontId: string) { // switch (frontId) { @@ -975,20 +1128,20 @@ function get_current_event(): EventCard { // } // } -function get_event_prompt(effect: Effect) { - let prompt = ''; - switch (effect.type) { - case 'attack': - return 'Attack ' + front_names[effect.target as string]; - case 'bonus': - break; - case 'hero_points': - return 'Select player with most Hero points'; - case 'track': - return 'Decrease ' + tracks[effect.target].name; - } - return prompt; -} +// function get_event_prompt(effect: Effect) { +// let prompt = ''; +// switch (effect.type) { +// case 'attack': +// return 'Attack ' + front_names[effect.target as string]; +// case 'bonus': +// break; +// case 'hero_points': +// return 'Select player with most Hero points'; +// case 'track': +// return 'Decrease ' + tracks[effect.target].name; +// } +// return prompt; +// } // function resolve_event_attack(target: string | number, value: number) { // switch (target) { @@ -1156,12 +1309,18 @@ function get_next_faction(faction_id: FactionId): FactionId { return role_ids[index + 1]; } -function get_factions_with_most_hero_poins() { - const most_hero_points = Math.max(...Object.values(game.hero_points)); - const faction_ids = []; - Object.entries(game.hero_points).forEach(([faction, hero_points]) => { - if (hero_points === most_hero_points) { - faction_ids.push(faction); +function get_factions_with_most_hero_poins(): FactionId[] { + let most_hero_points = null; + let faction_ids = []; + Object.entries(game.hero_points).forEach(([id, value]) => { + if (id === 'pool') { + return; + } + if (most_hero_points === null || value > most_hero_points) { + most_hero_points = value; + faction_ids = [id]; + } else if (most_hero_points === value) { + faction_ids.push(id); } }); return faction_ids; diff --git a/types.d.ts b/types.d.ts index b470acc..318d935 100644 --- a/types.d.ts +++ b/types.d.ts @@ -21,7 +21,7 @@ export interface Game { bag_of_glory: Record; blank_markers: number[][]; bonuses: number[]; - cards_in_play: Record; + chosen_cards: Record; current_events: CardId[]; discard: Record; engine: EngineNode[]; @@ -37,6 +37,7 @@ export interface Game { medaillons: Array; tableaus: Record; tracks: number[]; + trash: Record; triggered_track_effects: number[][]; result?: string; victory?: string; @@ -66,7 +67,9 @@ export interface View { fronts: Game['fronts']; hand: CardId[]; medaillons: Game['medaillons']; + tableaus: Game['tableaus']; tracks: number[]; + triggered_track_effects: Game['triggered_track_effects']; } export type States = { @@ -108,14 +111,42 @@ export interface EventCard extends CardBase { effects: Effect[]; } +export type Icon = + | 'add_to_front' + | 'collectivization' + | 'd_collectivization' + | 'd_foreign_aid' + | 'd_government' + | 'd_liberty' + | 'd_soviet_support' + | 'draw_card' + | 'foreign_aid' + | 'government' + | 'government_to_center' + | 'liberty' + | 'soviet_support' + | 'teamwork_on'; + export interface PlayerCard extends CardBase { type: 'pc'; strength: number; effects: Effect[]; + icons: Icon[]; } export interface Effect { - type: 'attack' | 'track' | 'bonus' | 'hero_points' | 'front' | 'medaillon' | 'draw_card' | 'swap_card_tableau_hand' | 'add_to_tableau' | 'remove_blank_marker' | 'return_card'; + type: + | 'attack' + | 'track' + | 'bonus' + | 'hero_points' + | 'front' + | 'medaillon' + | 'draw_card' + | 'swap_card_tableau_hand' + | 'add_to_tableau' + | 'remove_blank_marker' + | 'return_card'; target: string | number; value: number; } @@ -137,4 +168,4 @@ export interface StaticData { name: string; triggers: Array; }>; -} \ No newline at end of file +} -- cgit v1.2.3