summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data.js563
-rw-r--r--data.ts601
-rw-r--r--land-and-freedom.css287
-rw-r--r--land-and-freedom.scss85
-rw-r--r--package.json14
-rw-r--r--play.html9
-rw-r--r--play.js170
-rw-r--r--play.ts131
-rw-r--r--rules.js369
-rw-r--r--rules.ts423
-rw-r--r--tsconfig.json26
11 files changed, 2482 insertions, 196 deletions
diff --git a/data.js b/data.js
new file mode 100644
index 0000000..bc8f837
--- /dev/null
+++ b/data.js
@@ -0,0 +1,563 @@
+"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;
+const LIBERTY = 0;
+exports.LIBERTY = LIBERTY;
+const COLLECTIVIZATION = 1;
+exports.COLLECTIVIZATION = COLLECTIVIZATION;
+const GOVERNMENT = 2;
+exports.GOVERNMENT = GOVERNMENT;
+const SOVIET_SUPPORT = 3;
+exports.SOVIET_SUPPORT = SOVIET_SUPPORT;
+const FOREIGN_AID = 4;
+exports.FOREIGN_AID = FOREIGN_AID;
+const MORALE_BONUS = 0;
+exports.MORALE_BONUS = MORALE_BONUS;
+const TEAMWORK_BONUS = 1;
+exports.TEAMWORK_BONUS = TEAMWORK_BONUS;
+const OFF = 0;
+exports.OFF = OFF;
+const ON = 1;
+exports.ON = ON;
+const PLAYER_WITH_MOST_HERO_POINTS = 0;
+exports.PLAYER_WITH_MOST_HERO_POINTS = PLAYER_WITH_MOST_HERO_POINTS;
+const data = {
+ cards: [
+ {},
+ {
+ id: 1,
+ strength: 1,
+ title: 'CLANDESTINE FRENCH ARMS',
+ type: 'pc',
+ },
+ {
+ id: 2,
+ strength: 2,
+ title: 'POPULAR ARMY OF THE REPUBLIC',
+ type: 'pc',
+ },
+ {
+ id: 3,
+ strength: 2,
+ title: 'MEXICAN GUNS',
+ type: 'pc',
+ },
+ {
+ id: 4,
+ strength: 1,
+ title: 'BATTLE OF GUADALAJARA',
+ type: 'pc',
+ },
+ {
+ id: 5,
+ strength: 1,
+ title: '"SI ME OUIERES ESCRIBIR"',
+ type: 'pc',
+ },
+ {
+ id: 6,
+ strength: 2,
+ title: 'XYZ LINE',
+ type: 'pc',
+ },
+ {
+ id: 7,
+ strength: 3,
+ title: 'INDALECIO PRIETO',
+ type: 'pc',
+ },
+ {
+ id: 8,
+ strength: 1,
+ title: "PEOPLE'S OLYMPIAD",
+ type: 'pc',
+ },
+ {
+ id: 9,
+ strength: 1,
+ title: 'FOUR ANARCHIST MINISTERS',
+ type: 'pc',
+ },
+ {
+ id: 10,
+ strength: 1,
+ title: 'GUERNICA',
+ type: 'pc',
+ },
+ {
+ id: 11,
+ strength: 1,
+ title: 'ERNEST HEMINGWAY',
+ type: 'pc',
+ },
+ {
+ id: 12,
+ strength: 1,
+ title: 'HUESCA OFFENSIVE',
+ type: 'pc',
+ },
+ {
+ id: 13,
+ strength: 1,
+ title: 'PABLO NERUDA',
+ type: 'pc',
+ },
+ {
+ id: 14,
+ strength: 1,
+ title: 'EUSKO GUDAROSTEA',
+ type: 'pc',
+ },
+ {
+ id: 15,
+ strength: 2,
+ title: 'JUAN NEGRÍN',
+ type: 'pc',
+ },
+ {
+ id: 16,
+ strength: 1,
+ title: 'PUBLICIZE FASCIST WAR CRIMES',
+ type: 'pc',
+ },
+ {
+ id: 17,
+ strength: 1,
+ title: 'AGRARIAN REFORM',
+ type: 'pc',
+ },
+ {
+ id: 18,
+ strength: 1,
+ title: 'IMPOSE FACTORY MANAGERS',
+ type: 'pc',
+ },
+ {
+ id: 19,
+ strength: 2,
+ title: '¡NO PASARÁN!',
+ type: 'pc',
+ },
+ {
+ id: 20,
+ strength: 2,
+ title: 'RUSSIAN FIGHTERS',
+ type: 'pc',
+ },
+ {
+ id: 21,
+ strength: 1,
+ title: 'ENRIQUE LÍSTER',
+ type: 'pc',
+ },
+ {
+ id: 22,
+ strength: 2,
+ title: 'LARGO CABALLERO',
+ type: 'pc',
+ },
+ {
+ id: 23,
+ strength: 2,
+ title: 'SOVIET TANKS',
+ type: 'pc',
+ },
+ {
+ id: 24,
+ strength: 3,
+ title: 'DOLORES IBÁRRURI',
+ type: 'pc',
+ },
+ {
+ id: 25,
+ strength: 1,
+ title: 'PAUL ROBESON',
+ type: 'pc',
+ },
+ {
+ id: 26,
+ strength: 1,
+ title: 'MADRID DEFENSE COUNCIL',
+ type: 'pc',
+ },
+ {
+ id: 27,
+ strength: 1,
+ title: "STALIN GETS THE REPUBLIC'S GOLD",
+ type: 'pc',
+ },
+ {
+ id: 28,
+ strength: 1,
+ title: 'INTERNATIONAL BRIGADES',
+ type: 'pc',
+ },
+ {
+ id: 29,
+ strength: 1,
+ title: 'BAN WOMEN FROM THE FRONT',
+ type: 'pc',
+ },
+ {
+ id: 30,
+ strength: 1,
+ title: 'ABRAHAM LINCOLN BRIGADE',
+ type: 'pc',
+ },
+ {
+ id: 31,
+ strength: 2,
+ title: 'OUTLAW THE POUM',
+ type: 'pc',
+ },
+ {
+ id: 32,
+ strength: 1,
+ title: 'DISBAND THE CONTROL PATROLS',
+ type: 'pc',
+ },
+ {
+ id: 33,
+ strength: 1,
+ title: 'MAY DAYS',
+ type: 'pc',
+ },
+ {
+ id: 34,
+ strength: 1,
+ title: 'FIFTH REGIMENT',
+ type: 'pc',
+ },
+ {
+ id: 35,
+ strength: 1,
+ title: 'THÄLMANN BATTALION',
+ type: 'pc',
+ },
+ {
+ id: 36,
+ strength: 1,
+ title: 'DE-COLLECTIVIZE AGRICULTURE',
+ type: 'pc',
+ },
+ {
+ id: 37,
+ strength: 3,
+ title: 'BUENAVENTURA DURRUTI',
+ type: 'pc',
+ },
+ {
+ id: 38,
+ strength: 2,
+ title: 'MUJERES LIBRES',
+ type: 'pc',
+ },
+ {
+ id: 39,
+ strength: 1,
+ title: 'IRON COLUMN',
+ type: 'pc',
+ },
+ {
+ id: 40,
+ strength: 2,
+ title: 'ASTURIAN MINERS',
+ type: 'pc',
+ },
+ {
+ id: 41,
+ strength: 2,
+ title: 'CNT-FAI',
+ type: 'pc',
+ },
+ {
+ id: 42,
+ strength: 2,
+ title: 'DURRUTI COLUMN',
+ type: 'pc',
+ },
+ {
+ id: 43,
+ strength: 1,
+ title: 'GEORGE ORWELL',
+ type: 'pc',
+ },
+ {
+ id: 44,
+ strength: 1,
+ title: 'F.I.J.L.',
+ type: 'pc',
+ },
+ {
+ id: 45,
+ strength: 1,
+ title: 'ARM THE UNIONS',
+ type: 'pc',
+ },
+ {
+ id: 46,
+ strength: 1,
+ title: 'GUERRILLAS',
+ type: 'pc',
+ },
+ {
+ id: 47,
+ strength: 1,
+ title: 'RADICAL EDUCATION',
+ type: 'pc',
+ },
+ {
+ id: 48,
+ strength: 1,
+ title: 'MATTEOTTI BATTALION',
+ type: 'pc',
+ },
+ {
+ id: 49,
+ strength: 1,
+ title: 'COLLECTIVIZE AGRICULTURE',
+ type: 'pc',
+ },
+ {
+ id: 50,
+ strength: 1,
+ title: 'ARMORED VEHICLES',
+ type: 'pc',
+ },
+ {
+ id: 51,
+ strength: 1,
+ title: 'INDUSTRIAL DEMOCRACY',
+ type: 'pc',
+ },
+ {
+ id: 52,
+ strength: 2,
+ title: 'AFFINITY GROUPS',
+ type: 'pc',
+ },
+ {
+ id: 53,
+ strength: 1,
+ title: 'GENDER-INCLUSIVE MILITIA',
+ type: 'pc',
+ },
+ {
+ id: 54,
+ strength: 1,
+ title: 'FEDERICA MONTSENY',
+ type: 'pc',
+ },
+ {
+ id: 55,
+ effects: [
+ {
+ target: 's',
+ type: 'attack',
+ value: 4,
+ },
+ {
+ target: 'a',
+ type: 'attack',
+ value: 1,
+ },
+ {
+ target: SOVIET_SUPPORT,
+ type: 'track',
+ value: -2,
+ },
+ ],
+ title: 'SPANISH LEGION',
+ type: 'ec',
+ year: 1,
+ },
+ {
+ id: 56,
+ effects: [
+ {
+ target: 's',
+ type: 'attack',
+ value: 3,
+ },
+ {
+ target: 'v',
+ type: 'attack',
+ value: 2,
+ },
+ {
+ target: MORALE_BONUS,
+ type: 'bonus',
+ value: OFF,
+ },
+ ],
+ title: 'BRITISH TREACHERY AT GIBRALTAR',
+ type: 'ec',
+ year: 1,
+ },
+ {
+ id: 57,
+ effects: [
+ {
+ target: 'm',
+ type: 'attack',
+ value: 5,
+ },
+ {
+ target: FOREIGN_AID,
+ type: 'track',
+ value: -2,
+ },
+ {
+ target: PLAYER_WITH_MOST_HERO_POINTS,
+ type: 'hero_points',
+ value: -1,
+ },
+ ],
+ title: 'PARACUELLOS MASSACRES',
+ type: 'ec',
+ year: 1,
+ },
+ {
+ id: 58,
+ effects: [
+ {
+ target: 'n',
+ type: 'attack',
+ value: 5,
+ },
+ {
+ target: 'v',
+ type: 'attack',
+ value: 1,
+ },
+ {
+ target: COLLECTIVIZATION,
+ type: 'track',
+ value: -1,
+ },
+ ],
+ title: 'CARLISTS',
+ type: 'ec',
+ year: 1,
+ },
+ {
+ id: 59,
+ effects: [
+ {
+ target: 'm',
+ type: 'attack',
+ value: 4,
+ },
+ {
+ target: 'v',
+ type: 'attack',
+ value: 2,
+ },
+ {
+ target: LIBERTY,
+ type: 'track',
+ value: -1,
+ },
+ ],
+ title: 'ASSASSINATION OF GARCIA LORCA',
+ type: 'ec',
+ year: 1,
+ },
+ {
+ id: 60,
+ effects: [
+ {
+ target: 'n',
+ type: 'attack',
+ value: 3,
+ },
+ {
+ target: 'v',
+ type: 'attack',
+ value: 3,
+ },
+ {
+ target: LIBERTY,
+ type: 'track',
+ value: -1,
+ },
+ ],
+ title: 'GENERAL SANJURIO',
+ type: 'ec',
+ year: 1,
+ },
+ {
+ id: 61,
+ effects: [
+ {
+ target: 'a',
+ type: 'attack',
+ value: 4,
+ },
+ {
+ target: 'v',
+ type: 'attack',
+ value: 2,
+ },
+ {
+ target: PLAYER_WITH_MOST_HERO_POINTS,
+ type: 'hero_points',
+ value: -1,
+ },
+ ],
+ title: 'FAILED INVASION OF MALLORCA',
+ type: 'ec',
+ year: 1,
+ },
+ {
+ id: 62,
+ effects: [
+ {
+ target: 's',
+ type: 'attack',
+ value: 5,
+ },
+ {
+ target: MORALE_BONUS,
+ type: 'bonus',
+ value: OFF,
+ },
+ {
+ target: PLAYER_WITH_MOST_HERO_POINTS,
+ type: 'hero_points',
+ value: -1,
+ },
+ ],
+ title: 'AIRLIFT OF THE ARMY OF AFRICA',
+ type: 'ec',
+ year: 1,
+ },
+ ],
+ fronts: [
+ {
+ id: 1,
+ name: 'Northern',
+ left: 89,
+ top: 96,
+ },
+ {
+ id: 2,
+ name: 'Aragon',
+ left: 340,
+ top: 182,
+ },
+ {
+ id: 3,
+ name: 'Madrid',
+ left: 115,
+ top: 262,
+ },
+ {
+ id: 4,
+ name: 'Southern',
+ left: 205,
+ top: 426,
+ },
+ ],
+};
+exports.default = data;
diff --git a/data.ts b/data.ts
new file mode 100644
index 0000000..a01495d
--- /dev/null
+++ b/data.ts
@@ -0,0 +1,601 @@
+export interface CardBase {
+ id: number;
+ title: string;
+}
+
+export interface CardEffect {
+ type: 'attack' | 'track' | 'bonus' | 'hero_points';
+ target: string | number;
+ value: number;
+}
+
+export interface EventCard extends CardBase {
+ type: 'ec';
+ year: number;
+ effects: CardEffect[];
+}
+
+export interface PlayerCard extends CardBase {
+ type: 'pc';
+ strength: number;
+}
+
+export type Card = EventCard | PlayerCard;
+
+export interface StaticData {
+ cards: Card[];
+ fronts: Array<{
+ id: number;
+ name: string;
+ left: number;
+ top: number;
+ }>;
+}
+
+const LIBERTY = 0;
+const COLLECTIVIZATION = 1;
+const GOVERNMENT = 2;
+const SOVIET_SUPPORT = 3;
+const FOREIGN_AID = 4;
+
+const MORALE_BONUS = 0;
+const TEAMWORK_BONUS = 1;
+
+const OFF = 0;
+const ON = 1;
+
+const PLAYER_WITH_MOST_HERO_POINTS = 0;
+
+export {
+ LIBERTY,
+ COLLECTIVIZATION,
+ GOVERNMENT,
+ SOVIET_SUPPORT,
+ FOREIGN_AID,
+ MORALE_BONUS,
+ TEAMWORK_BONUS,
+ OFF,
+ ON,
+ PLAYER_WITH_MOST_HERO_POINTS,
+};
+
+const data: StaticData = {
+ cards: [
+ {} as Card,
+ {
+ id: 1,
+ strength: 1,
+ title: 'CLANDESTINE FRENCH ARMS',
+ type: 'pc',
+ },
+ {
+ id: 2,
+ strength: 2,
+ title: 'POPULAR ARMY OF THE REPUBLIC',
+ type: 'pc',
+ },
+ {
+ id: 3,
+ strength: 2,
+ title: 'MEXICAN GUNS',
+ type: 'pc',
+ },
+ {
+ id: 4,
+ strength: 1,
+ title: 'BATTLE OF GUADALAJARA',
+ type: 'pc',
+ },
+ {
+ id: 5,
+ strength: 1,
+ title: '"SI ME OUIERES ESCRIBIR"',
+ type: 'pc',
+ },
+ {
+ id: 6,
+ strength: 2,
+ title: 'XYZ LINE',
+ type: 'pc',
+ },
+ {
+ id: 7,
+ strength: 3,
+ title: 'INDALECIO PRIETO',
+ type: 'pc',
+ },
+ {
+ id: 8,
+ strength: 1,
+ title: "PEOPLE'S OLYMPIAD",
+ type: 'pc',
+ },
+ {
+ id: 9,
+ strength: 1,
+ title: 'FOUR ANARCHIST MINISTERS',
+ type: 'pc',
+ },
+ {
+ id: 10,
+ strength: 1,
+ title: 'GUERNICA',
+ type: 'pc',
+ },
+ {
+ id: 11,
+ strength: 1,
+ title: 'ERNEST HEMINGWAY',
+ type: 'pc',
+ },
+ {
+ id: 12,
+ strength: 1,
+ title: 'HUESCA OFFENSIVE',
+ type: 'pc',
+ },
+ {
+ id: 13,
+ strength: 1,
+ title: 'PABLO NERUDA',
+ type: 'pc',
+ },
+ {
+ id: 14,
+ strength: 1,
+ title: 'EUSKO GUDAROSTEA',
+ type: 'pc',
+ },
+ {
+ id: 15,
+ strength: 2,
+ title: 'JUAN NEGRÍN',
+ type: 'pc',
+ },
+ {
+ id: 16,
+ strength: 1,
+ title: 'PUBLICIZE FASCIST WAR CRIMES',
+ type: 'pc',
+ },
+ {
+ id: 17,
+ strength: 1,
+ title: 'AGRARIAN REFORM',
+ type: 'pc',
+ },
+ {
+ id: 18,
+ strength: 1,
+ title: 'IMPOSE FACTORY MANAGERS',
+ type: 'pc',
+ },
+ {
+ id: 19,
+ strength: 2,
+ title: '¡NO PASARÁN!',
+ type: 'pc',
+ },
+ {
+ id: 20,
+ strength: 2,
+ title: 'RUSSIAN FIGHTERS',
+ type: 'pc',
+ },
+ {
+ id: 21,
+ strength: 1,
+ title: 'ENRIQUE LÍSTER',
+ type: 'pc',
+ },
+ {
+ id: 22,
+ strength: 2,
+ title: 'LARGO CABALLERO',
+ type: 'pc',
+ },
+ {
+ id: 23,
+ strength: 2,
+ title: 'SOVIET TANKS',
+ type: 'pc',
+ },
+ {
+ id: 24,
+ strength: 3,
+ title: 'DOLORES IBÁRRURI',
+ type: 'pc',
+ },
+ {
+ id: 25,
+ strength: 1,
+ title: 'PAUL ROBESON',
+ type: 'pc',
+ },
+ {
+ id: 26,
+ strength: 1,
+ title: 'MADRID DEFENSE COUNCIL',
+ type: 'pc',
+ },
+ {
+ id: 27,
+ strength: 1,
+ title: "STALIN GETS THE REPUBLIC'S GOLD",
+ type: 'pc',
+ },
+ {
+ id: 28,
+ strength: 1,
+ title: 'INTERNATIONAL BRIGADES',
+ type: 'pc',
+ },
+ {
+ id: 29,
+ strength: 1,
+ title: 'BAN WOMEN FROM THE FRONT',
+ type: 'pc',
+ },
+ {
+ id: 30,
+ strength: 1,
+ title: 'ABRAHAM LINCOLN BRIGADE',
+ type: 'pc',
+ },
+ {
+ id: 31,
+ strength: 2,
+ title: 'OUTLAW THE POUM',
+ type: 'pc',
+ },
+ {
+ id: 32,
+ strength: 1,
+ title: 'DISBAND THE CONTROL PATROLS',
+ type: 'pc',
+ },
+ {
+ id: 33,
+ strength: 1,
+ title: 'MAY DAYS',
+ type: 'pc',
+ },
+ {
+ id: 34,
+ strength: 1,
+ title: 'FIFTH REGIMENT',
+ type: 'pc',
+ },
+ {
+ id: 35,
+ strength: 1,
+ title: 'THÄLMANN BATTALION',
+ type: 'pc',
+ },
+ {
+ id: 36,
+ strength: 1,
+ title: 'DE-COLLECTIVIZE AGRICULTURE',
+ type: 'pc',
+ },
+ {
+ id: 37,
+ strength: 3,
+ title: 'BUENAVENTURA DURRUTI',
+ type: 'pc',
+ },
+ {
+ id: 38,
+ strength: 2,
+ title: 'MUJERES LIBRES',
+ type: 'pc',
+ },
+ {
+ id: 39,
+ strength: 1,
+ title: 'IRON COLUMN',
+ type: 'pc',
+ },
+ {
+ id: 40,
+ strength: 2,
+ title: 'ASTURIAN MINERS',
+ type: 'pc',
+ },
+ {
+ id: 41,
+ strength: 2,
+ title: 'CNT-FAI',
+ type: 'pc',
+ },
+ {
+ id: 42,
+ strength: 2,
+ title: 'DURRUTI COLUMN',
+ type: 'pc',
+ },
+ {
+ id: 43,
+ strength: 1,
+ title: 'GEORGE ORWELL',
+ type: 'pc',
+ },
+ {
+ id: 44,
+ strength: 1,
+ title: 'F.I.J.L.',
+ type: 'pc',
+ },
+ {
+ id: 45,
+ strength: 1,
+ title: 'ARM THE UNIONS',
+ type: 'pc',
+ },
+ {
+ id: 46,
+ strength: 1,
+ title: 'GUERRILLAS',
+ type: 'pc',
+ },
+ {
+ id: 47,
+ strength: 1,
+ title: 'RADICAL EDUCATION',
+ type: 'pc',
+ },
+ {
+ id: 48,
+ strength: 1,
+ title: 'MATTEOTTI BATTALION',
+ type: 'pc',
+ },
+ {
+ id: 49,
+ strength: 1,
+ title: 'COLLECTIVIZE AGRICULTURE',
+ type: 'pc',
+ },
+ {
+ id: 50,
+ strength: 1,
+ title: 'ARMORED VEHICLES',
+ type: 'pc',
+ },
+ {
+ id: 51,
+ strength: 1,
+ title: 'INDUSTRIAL DEMOCRACY',
+ type: 'pc',
+ },
+ {
+ id: 52,
+ strength: 2,
+ title: 'AFFINITY GROUPS',
+ type: 'pc',
+ },
+ {
+ id: 53,
+ strength: 1,
+ title: 'GENDER-INCLUSIVE MILITIA',
+ type: 'pc',
+ },
+ {
+ id: 54,
+ strength: 1,
+ title: 'FEDERICA MONTSENY',
+ type: 'pc',
+ },
+ {
+ id: 55,
+ effects: [
+ {
+ target: 's',
+ type: 'attack',
+ value: 4,
+ },
+ {
+ target: 'a',
+ type: 'attack',
+ value: 1,
+ },
+ {
+ target: SOVIET_SUPPORT,
+ type: 'track',
+ value: -2,
+ },
+ ],
+ title: 'SPANISH LEGION',
+ type: 'ec',
+ year: 1,
+ },
+ {
+ id: 56,
+ effects: [
+ {
+ target: 's',
+ type: 'attack',
+ value: 3,
+ },
+ {
+ target: 'v',
+ type: 'attack',
+ value: 2,
+ },
+ {
+ target: MORALE_BONUS,
+ type: 'bonus',
+ value: OFF,
+ },
+ ],
+ title: 'BRITISH TREACHERY AT GIBRALTAR',
+ type: 'ec',
+ year: 1,
+ },
+ {
+ id: 57,
+ effects: [
+ {
+ target: 'm',
+ type: 'attack',
+ value: 5,
+ },
+ {
+ target: FOREIGN_AID,
+ type: 'track',
+ value: -2,
+ },
+ {
+ target: PLAYER_WITH_MOST_HERO_POINTS,
+ type: 'hero_points',
+ value: -1,
+ },
+ ],
+ title: 'PARACUELLOS MASSACRES',
+ type: 'ec',
+ year: 1,
+ },
+ {
+ id: 58,
+ effects: [
+ {
+ target: 'n',
+ type: 'attack',
+ value: 5,
+ },
+ {
+ target: 'v',
+ type: 'attack',
+ value: 1,
+ },
+ {
+ target: COLLECTIVIZATION,
+ type: 'track',
+ value: -1,
+ },
+ ],
+ title: 'CARLISTS',
+ type: 'ec',
+ year: 1,
+ },
+ {
+ id: 59,
+ effects: [
+ {
+ target: 'm',
+ type: 'attack',
+ value: 4,
+ },
+ {
+ target: 'v',
+ type: 'attack',
+ value: 2,
+ },
+ {
+ target: LIBERTY,
+ type: 'track',
+ value: -1,
+ },
+ ],
+ title: 'ASSASSINATION OF GARCIA LORCA',
+ type: 'ec',
+ year: 1,
+ },
+ {
+ id: 60,
+ effects: [
+ {
+ target: 'n',
+ type: 'attack',
+ value: 3,
+ },
+ {
+ target: 'v',
+ type: 'attack',
+ value: 3,
+ },
+ {
+ target: LIBERTY,
+ type: 'track',
+ value: -1,
+ },
+ ],
+ title: 'GENERAL SANJURIO',
+ type: 'ec',
+ year: 1,
+ },
+ {
+ id: 61,
+ effects: [
+ {
+ target: 'a',
+ type: 'attack',
+ value: 4,
+ },
+ {
+ target: 'v',
+ type: 'attack',
+ value: 2,
+ },
+ {
+ target: PLAYER_WITH_MOST_HERO_POINTS,
+ type: 'hero_points',
+ value: -1,
+ },
+ ],
+ title: 'FAILED INVASION OF MALLORCA',
+ type: 'ec',
+ year: 1,
+ },
+ {
+ id: 62,
+ effects: [
+ {
+ target: 's',
+ type: 'attack',
+ value: 5,
+ },
+ {
+ target: MORALE_BONUS,
+ type: 'bonus',
+ value: OFF,
+ },
+ {
+ target: PLAYER_WITH_MOST_HERO_POINTS,
+ type: 'hero_points',
+ value: -1,
+ },
+ ],
+ title: 'AIRLIFT OF THE ARMY OF AFRICA',
+ type: 'ec',
+ year: 1,
+ },
+ ],
+ fronts: [
+ {
+ id: 1,
+ name: 'Northern',
+ left: 89,
+ top: 96,
+ },
+ {
+ id: 2,
+ name: 'Aragon',
+ left: 340,
+ top: 182,
+ },
+ {
+ id: 3,
+ name: 'Madrid',
+ left: 115,
+ top: 262,
+ },
+ {
+ id: 4,
+ name: 'Southern',
+ left: 205,
+ top: 426,
+ },
+ ],
+};
+export default data;
diff --git a/land-and-freedom.css b/land-and-freedom.css
index 0d6b494..5928f57 100644
--- a/land-and-freedom.css
+++ b/land-and-freedom.css
@@ -1,3 +1,14 @@
+main {
+ background-color: darkolivegreen;
+}
+
+/* MAP */
+#mapwrap {
+ width: 1000px;
+ height: 850px;
+ box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.2666666667);
+}
+
#map {
background-repeat: no-repeat;
background-size: 100% 100%;
@@ -15,3 +26,279 @@
background-image: url(images/map100.png);
}
}
+#hand {
+ display: flex;
+ flex-direction: row;
+ gap: 8px;
+ margin-top: 16px;
+ padding: 16px;
+ justify-content: center;
+}
+
+.front {
+ position: absolute;
+ box-sizing: border-box;
+ width: 93px;
+ height: 102px;
+}
+
+.front[data-front-id="3"] {
+ width: 104px;
+ height: 114px;
+}
+
+.card {
+ background-size: 100% 100%;
+ width: 198px;
+ height: 270px;
+ border-radius: 5px;
+ box-shadow: 0 0 0 1px #333;
+}
+
+.card[data-card-id="1"] {
+ background-image: url("images/cards100/card_1.png");
+}
+
+.card[data-card-id="2"] {
+ background-image: url("images/cards100/card_2.png");
+}
+
+.card[data-card-id="3"] {
+ background-image: url("images/cards100/card_3.png");
+}
+
+.card[data-card-id="4"] {
+ background-image: url("images/cards100/card_4.png");
+}
+
+.card[data-card-id="5"] {
+ background-image: url("images/cards100/card_5.png");
+}
+
+.card[data-card-id="6"] {
+ background-image: url("images/cards100/card_6.png");
+}
+
+.card[data-card-id="7"] {
+ background-image: url("images/cards100/card_7.png");
+}
+
+.card[data-card-id="8"] {
+ background-image: url("images/cards100/card_8.png");
+}
+
+.card[data-card-id="9"] {
+ background-image: url("images/cards100/card_9.png");
+}
+
+.card[data-card-id="10"] {
+ background-image: url("images/cards100/card_10.png");
+}
+
+.card[data-card-id="11"] {
+ background-image: url("images/cards100/card_11.png");
+}
+
+.card[data-card-id="12"] {
+ background-image: url("images/cards100/card_12.png");
+}
+
+.card[data-card-id="13"] {
+ background-image: url("images/cards100/card_13.png");
+}
+
+.card[data-card-id="14"] {
+ background-image: url("images/cards100/card_14.png");
+}
+
+.card[data-card-id="15"] {
+ background-image: url("images/cards100/card_15.png");
+}
+
+.card[data-card-id="16"] {
+ background-image: url("images/cards100/card_16.png");
+}
+
+.card[data-card-id="17"] {
+ background-image: url("images/cards100/card_17.png");
+}
+
+.card[data-card-id="18"] {
+ background-image: url("images/cards100/card_18.png");
+}
+
+.card[data-card-id="19"] {
+ background-image: url("images/cards100/card_19.png");
+}
+
+.card[data-card-id="20"] {
+ background-image: url("images/cards100/card_20.png");
+}
+
+.card[data-card-id="21"] {
+ background-image: url("images/cards100/card_21.png");
+}
+
+.card[data-card-id="22"] {
+ background-image: url("images/cards100/card_22.png");
+}
+
+.card[data-card-id="23"] {
+ background-image: url("images/cards100/card_23.png");
+}
+
+.card[data-card-id="24"] {
+ background-image: url("images/cards100/card_24.png");
+}
+
+.card[data-card-id="25"] {
+ background-image: url("images/cards100/card_25.png");
+}
+
+.card[data-card-id="26"] {
+ background-image: url("images/cards100/card_26.png");
+}
+
+.card[data-card-id="27"] {
+ background-image: url("images/cards100/card_27.png");
+}
+
+.card[data-card-id="28"] {
+ background-image: url("images/cards100/card_28.png");
+}
+
+.card[data-card-id="29"] {
+ background-image: url("images/cards100/card_29.png");
+}
+
+.card[data-card-id="30"] {
+ background-image: url("images/cards100/card_30.png");
+}
+
+.card[data-card-id="31"] {
+ background-image: url("images/cards100/card_31.png");
+}
+
+.card[data-card-id="32"] {
+ background-image: url("images/cards100/card_32.png");
+}
+
+.card[data-card-id="33"] {
+ background-image: url("images/cards100/card_33.png");
+}
+
+.card[data-card-id="34"] {
+ background-image: url("images/cards100/card_34.png");
+}
+
+.card[data-card-id="35"] {
+ background-image: url("images/cards100/card_35.png");
+}
+
+.card[data-card-id="36"] {
+ background-image: url("images/cards100/card_36.png");
+}
+
+.card[data-card-id="37"] {
+ background-image: url("images/cards100/card_37.png");
+}
+
+.card[data-card-id="38"] {
+ background-image: url("images/cards100/card_38.png");
+}
+
+.card[data-card-id="39"] {
+ background-image: url("images/cards100/card_39.png");
+}
+
+.card[data-card-id="40"] {
+ background-image: url("images/cards100/card_40.png");
+}
+
+.card[data-card-id="41"] {
+ background-image: url("images/cards100/card_41.png");
+}
+
+.card[data-card-id="42"] {
+ background-image: url("images/cards100/card_42.png");
+}
+
+.card[data-card-id="43"] {
+ background-image: url("images/cards100/card_43.png");
+}
+
+.card[data-card-id="44"] {
+ background-image: url("images/cards100/card_44.png");
+}
+
+.card[data-card-id="45"] {
+ background-image: url("images/cards100/card_45.png");
+}
+
+.card[data-card-id="46"] {
+ background-image: url("images/cards100/card_46.png");
+}
+
+.card[data-card-id="47"] {
+ background-image: url("images/cards100/card_47.png");
+}
+
+.card[data-card-id="48"] {
+ background-image: url("images/cards100/card_48.png");
+}
+
+.card[data-card-id="49"] {
+ background-image: url("images/cards100/card_49.png");
+}
+
+.card[data-card-id="50"] {
+ background-image: url("images/cards100/card_50.png");
+}
+
+.card[data-card-id="51"] {
+ background-image: url("images/cards100/card_51.png");
+}
+
+.card[data-card-id="52"] {
+ background-image: url("images/cards100/card_52.png");
+}
+
+.card[data-card-id="53"] {
+ background-image: url("images/cards100/card_53.png");
+}
+
+.card[data-card-id="54"] {
+ background-image: url("images/cards100/card_54.png");
+}
+
+.card[data-card-id="55"] {
+ background-image: url("images/cards100/card_55.png");
+}
+
+.card[data-card-id="56"] {
+ background-image: url("images/cards100/card_56.png");
+}
+
+.card[data-card-id="57"] {
+ background-image: url("images/cards100/card_57.png");
+}
+
+.card[data-card-id="58"] {
+ background-image: url("images/cards100/card_58.png");
+}
+
+.card[data-card-id="59"] {
+ background-image: url("images/cards100/card_59.png");
+}
+
+.card[data-card-id="60"] {
+ background-image: url("images/cards100/card_60.png");
+}
+
+.card[data-card-id="61"] {
+ background-image: url("images/cards100/card_61.png");
+}
+
+.card[data-card-id="62"] {
+ background-image: url("images/cards100/card_62.png");
+}
diff --git a/land-and-freedom.scss b/land-and-freedom.scss
index 65694fc..9c68e03 100644
--- a/land-and-freedom.scss
+++ b/land-and-freedom.scss
@@ -1,12 +1,81 @@
+// @use "sass:math";
+@use 'sass:map';
+
+main {
+ // background-color: rgb(213, 196, 131);
+ background-color: darkolivegreen;
+}
+
+/* MAP */
+
+#mapwrap {
+ width: 1000px;
+ height: 850px;
+ // background-color: #999f85;
+ box-shadow: 1px 2px 4px #0004;
+}
+
+// #map {
+// background-repeat: no-repeat;
+// background-size: cover;
+// width: 1275px;
+// height: 1650px;
+// overflow: clip;
+// }
+
#map {
- background-repeat: no-repeat;
- background-size: 100% 100%;
- width: 1000px;
- height: 850px;
- overflow: clip;
+ background-repeat: no-repeat;
+ background-size: 100% 100%;
+ width: 1000px;
+ height: 850px;
+ overflow: clip;
}
-#map { background-image: url(images/map75.png) }
+#map {
+ background-image: url(images/map75.png);
+}
@media (min-resolution: 97dpi) {
- #map { background-image: url(images/map100.png) }
-} \ No newline at end of file
+ #map {
+ background-image: url(images/map100.png);
+ }
+}
+
+#hand {
+ display: flex;
+ flex-direction: row;
+ gap: 8px;
+ margin-top: 16px;
+ padding: 16px;
+ justify-content: center;
+}
+
+.front {
+ position: absolute;
+ box-sizing: border-box;
+ width: 93px;
+ height: 102px;
+ // background-color: yellow;
+ // opacity: 0.5;
+}
+
+.front[data-front-id='3'] {
+ width: 104px;
+ height: 114px;
+}
+
+.card {
+ background-size: 100% 100%;
+ // width: 275px;
+ // height: 375px;
+ width: 198px;
+ height: 270px;
+ // border: 1px solid black;
+ border-radius: 5px;
+ box-shadow: 0 0 0 1px #333;
+}
+
+@for $i from 1 through 62 {
+ .card[data-card-id='#{$i}'] {
+ background-image: url('images/cards100/card_#{$i}.png');
+ }
+}
diff --git a/package.json b/package.json
index 613747e..b63e15d 100644
--- a/package.json
+++ b/package.json
@@ -1,15 +1,19 @@
{
"name": "land-and-freedom",
"version": "1.0.0",
- "description": "",
+ "description": "Land and Freedom for RTT",
"main": "rules.js",
"scripts": {
"build:scss": "sass --no-source-map land-and-freedom.scss land-and-freedom.css",
- "watch:scss": "sass --no-source-map --watch land-and-freedom.scss land-and-freedom.css"
+ "build:ts": "tsc",
+ "watch:scss": "sass --no-source-map --watch land-and-freedom.scss land-and-freedom.css",
+ "watch:ts": "tsc --watch",
+ "build": "npm run build:ts && npm run build:scss"
},
- "author": "",
+ "author": "Frans Bongers",
"license": "ISC",
"devDependencies": {
- "sass": "^1.81.0"
+ "sass": "^1.81.0",
+ "typescript": "^5.7.2"
}
-} \ No newline at end of file
+}
diff --git a/play.html b/play.html
index 8fac6ec..08fbd0e 100644
--- a/play.html
+++ b/play.html
@@ -11,6 +11,8 @@
<link rel="stylesheet" href="/common/client.css">
<link rel="stylesheet" href="land-and-freedom.css">
<script defer src="/common/client.js"></script>
+<script>var exports = {};</script>
+<script defer src="data.js"></script>
<script defer src="play.js"></script>
</head>
<style>
@@ -38,8 +40,11 @@
<main>
<div id="mapwrap">
<div id="map">
- <div id="spaces"></div>
- <div id="pieces"></div>
+ <div id="spaces">
+ </div>
+ <div id="pieces">
+
+ </div>
</div>
</div>
<div id="hand"></div>
diff --git a/play.js b/play.js
index 6a782fb..1c40758 100644
--- a/play.js
+++ b/play.js
@@ -1,90 +1,98 @@
-"use strict"
-
+'use strict';
+Object.defineProperty(exports, "__esModule", { value: true });
/* global view, player, send_action, action_button */
-
-const SPACE_COUNT = 64
-const PIECE_COUNT = 32
-const CARD_COUNT = 52
-
+// const SPACE_COUNT = 64;
+// const PIECE_COUNT = 32;
+const CARD_COUNT = 62;
let ui = {
- map: document.getElementById("map"),
- hand: document.getElementById("hand"),
- spaces: [],
- pieces: [],
- cards: [],
+ map: document.getElementById('map'),
+ hand: document.getElementById('hand'),
+ fronts: [],
+ spaces: [],
+ pieces: [],
+ cards: [],
+};
+let action_register = [];
+// @ts-ignore
+(function build_map() {
+ // @ts-ignore
+ const spaces = document.getElementById('spaces');
+ console.log('build_map', data);
+ data.fronts.forEach((front, index) => {
+ const { id, top, left } = front;
+ const element = (ui.fronts[index] = document.createElement('div'));
+ element.classList.add('front');
+ element.style.left = `${left}px`;
+ element.style.top = `${top}px`;
+ // element.style.height = `${height}px`;
+ // element.style.width = `${width}px`;
+ element.setAttribute('data-front-id', `${id}`);
+ spaces.appendChild(element);
+ });
+})();
+// @ts-ignore
+function register_action(e, _action, _id) {
+ // e.my_action = action
+ // e.my_id = id
+ e.onmousedown = on_click_action;
+ action_register.push(e);
}
-
-let action_register = []
-
-function register_action(e, action, id) {
- e.my_action = action
- e.my_id = id
- e.onmousedown = on_click_action
- action_register.push(e)
-}
-
function on_click_action(evt) {
- if (evt.button === 0)
- if (send_action(evt.target.my_action, evt.target.my_id))
- evt.stopPropagation()
-}
-
-function is_action(action, arg) {
- if (arg === undefined)
- return !!(view.actions && view.actions[action] === 1)
- return !!(view.actions && view.actions[action] && view.actions[action].includes(arg))
+ if (evt.button === 0)
+ if (send_action(evt.target.my_action, evt.target.my_id))
+ evt.stopPropagation();
}
-
-let on_init_once = false
-
+// function is_action(action, arg) {
+// if (arg === undefined) return !!(view.actions && view.actions[action] === 1);
+// return !!(
+// view.actions &&
+// view.actions[action] &&
+// view.actions[action].includes(arg)
+// );
+// }
+let on_init_once = false;
function on_init() {
- console.log('on_init')
- if (on_init_once)
- return
- on_init_once = true
-
- // 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", s)
- }
-
- // create card elements
- for (let c = 0; c < CARD_COUNT; ++c) {
- let e = ui.cards[c] = document.createElement("div")
- e.className = "card"
- register_action(e, "card", s)
- }
+ console.log('on_init');
+ if (on_init_once)
+ return;
+ on_init_once = true;
+ console.log('ui', ui);
+ 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 card elements
+ for (let c = 1; c < CARD_COUNT; ++c) {
+ let e = (ui.cards[c] = document.createElement('div'));
+ e.className = 'card';
+ e.setAttribute('data-card-id', '' + data.cards[c].id);
+ register_action(e, 'card', c);
+ }
}
-
+// @ts-ignore
function on_update() {
- console.log('on_update', view)
- on_init()
-
- for (let s = 0; s < SPACE_COUNT; ++s)
- ui.spaces[s].replaceChildren()
-
- for (let p = 0; p < PIECE_COUNT; ++p) {
- let s = view.location[p]
- ui.spaces[s].appendChild(ui.pieces[p])
- }
-
- ui.hand.replaceChildren()
- for (let c of view.hand)
- ui.hand.appendChild(ui.cards[c])
-
- for (let e of action_register)
- e.classList.toggle("action", is_action(e.my_action, e.my_id))
-
- action_button("next", "Next")
- action_button("undo", "Undo")
+ console.log('on_update', view);
+ on_init();
+ // for (let s = 0; s < SPACE_COUNT; ++s) ui.spaces[s].replaceChildren();
+ // for (let p = 0; p < PIECE_COUNT; ++p) {
+ // let s = view.location[p];
+ // ui.spaces[s].appendChild(ui.pieces[p]);
+ // }
+ ui.hand.replaceChildren();
+ for (let c of view.hand)
+ ui.hand.appendChild(ui.cards[c]);
+ // for (let e of action_register)
+ // e.classList.toggle('action', is_action(e.my_action, e.my_id));
+ action_button('next', 'Next');
+ action_button('undo', 'Undo');
}
diff --git a/play.ts b/play.ts
new file mode 100644
index 0000000..2c3d2a2
--- /dev/null
+++ b/play.ts
@@ -0,0 +1,131 @@
+'use strict';
+
+import { StaticData } from './data';
+import { Player, View } from './rules';
+
+declare function action_button(action: string, text: string): void;
+// declare function register_action(element: HTMLElement, type: string, s: number): void;
+declare function send_action(action: string, text: string): boolean;
+declare const data: StaticData;
+declare const view: View;
+declare const player: Player;
+
+/* global view, player, send_action, action_button */
+
+// const SPACE_COUNT = 64;
+// const PIECE_COUNT = 32;
+const CARD_COUNT = 62;
+
+let ui = {
+ map: document.getElementById('map'),
+ hand: document.getElementById('hand'),
+ fronts: [],
+ spaces: [],
+ pieces: [],
+ cards: [],
+};
+
+let action_register = [];
+
+// @ts-ignore
+(function build_map() {
+ // @ts-ignore
+
+ const spaces = document.getElementById('spaces');
+ console.log('build_map', data);
+ data.fronts.forEach((front, index) => {
+ const { id, top, left } = front;
+ const element = (ui.fronts[index] = document.createElement('div'));
+ element.classList.add('front');
+ element.style.left = `${left}px`;
+ element.style.top = `${top}px`;
+ // element.style.height = `${height}px`;
+ // element.style.width = `${width}px`;
+ element.setAttribute('data-front-id', `${id}`);
+ spaces.appendChild(element);
+ });
+})();
+
+// @ts-ignore
+function register_action(
+ e: HTMLElement,
+ _action: string,
+ _id: string | number
+) {
+ // e.my_action = action
+ // e.my_id = id
+ e.onmousedown = on_click_action;
+ action_register.push(e);
+}
+
+function on_click_action(evt) {
+ if (evt.button === 0)
+ if (send_action(evt.target.my_action, evt.target.my_id))
+ evt.stopPropagation();
+}
+
+// function is_action(action, arg) {
+// if (arg === undefined) return !!(view.actions && view.actions[action] === 1);
+// return !!(
+// view.actions &&
+// view.actions[action] &&
+// view.actions[action].includes(arg)
+// );
+// }
+
+let on_init_once = false;
+
+function on_init() {
+ console.log('on_init');
+ if (on_init_once) return;
+ on_init_once = true;
+
+ console.log('ui', ui);
+
+ 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 card elements
+ for (let c = 1; c < CARD_COUNT; ++c) {
+ let e = (ui.cards[c] = document.createElement('div'));
+ e.className = 'card';
+ e.setAttribute('data-card-id', '' + data.cards[c].id)
+ register_action(e, 'card', c);
+ }
+}
+
+// @ts-ignore
+function on_update() {
+ console.log('on_update', view);
+ on_init();
+
+ // for (let s = 0; s < SPACE_COUNT; ++s) ui.spaces[s].replaceChildren();
+
+ // for (let p = 0; p < PIECE_COUNT; ++p) {
+ // let s = view.location[p];
+ // ui.spaces[s].appendChild(ui.pieces[p]);
+ // }
+
+ ui.hand.replaceChildren();
+ for (let c of view.hand) ui.hand.appendChild(ui.cards[c]);
+
+ // for (let e of action_register)
+ // e.classList.toggle('action', is_action(e.my_action, e.my_id));
+
+ action_button('next', 'Next');
+ action_button('undo', 'Undo');
+}
diff --git a/rules.js b/rules.js
index 08edea8..2952679 100644
--- a/rules.js
+++ b/rules.js
@@ -1,108 +1,277 @@
-"use strict"
-
-var states = {}
-var game = null
-var view = null
-
-exports.scenarios = [ "Standard" ]
-
-exports.roles = [ "Anarchist", "Moderate", "Fascist" ]
-
+'use strict';
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.roles = exports.scenarios = void 0;
+exports.action = action;
+exports.view = game_view;
+exports.setup = setup;
+const data_1 = require("./data");
+const states = {};
+let game = {}; // = null
+var view = {}; // = null
+const ANARCHIST = 'Anarchist';
+const COMMUNIST = 'Communist';
+const MODERATE = 'Moderate';
+const ANARCHISTS_ID = 'a';
+const COMMUNISTS_ID = 'c';
+const MODERATES_ID = 'm';
+const { cards,
+// fronts
+ } = data_1.default;
+const faction_cards = {
+ [ANARCHIST]: make_list(37, 54),
+ [COMMUNIST]: make_list(19, 36),
+ [MODERATE]: make_list(1, 18),
+};
+const fascist_decks = {
+ 1: make_list(55, 62),
+};
+exports.scenarios = ['Standard'];
+exports.roles = [ANARCHIST, COMMUNIST, MODERATE];
function gen_action(action, argument) {
- if (!(action in view.actions))
- view.actions[action] = []
- view.actions[action].push(argument)
+ if (!(action in view.actions))
+ view.actions[action] = [];
+ view.actions[action].push(argument);
}
-
function gen_action_space(space) {
- gen_action("space", space)
+ gen_action('space', space);
}
-
function gen_action_piece(piece) {
- gen_action("piece", piece)
-}
-
-function gen_action_card(card) {
- gen_action("card", card)
-}
-
-exports.action = function (state, player, action, arg) {
- game = state
- let S = states[game.state]
- if (action in S)
- S[action](arg, player)
- else if (action === "undo" && game.undo && game.undo.length > 0)
- pop_undo()
- else
- throw new Error("Invalid action: " + action)
- return game
-}
-
-exports.view = function (state, player) {
- game = state
-
- view = {
- log: game.log,
- prompt: null,
- location: game.location,
- selected: game.selected,
- }
-
- if (player !== game.active) {
- let inactive = states[game.state].inactive || game.state
- view.prompt = `Waiting for ${game.active} to ${inactive}.`
- } else {
- view.actions = {}
- states[game.state].prompt()
- if (game.undo && game.undo.length > 0)
- view.actions.undo = 1
- else
- view.actions.undo = 0
- }
-
- return view
-}
-
-exports.setup = function (seed, scenario, options) {
- game = {
- seed: seed,
- state: null,
- active: "Anarchist",
- log: [],
- undo: [],
- }
-
- game.state = "move"
-
- return game
-}
-
+ gen_action('piece', piece);
+}
+// function gen_action_card(card) {
+// gen_action('card', card);
+// }
+function action(state, player, action, arg) {
+ game = state;
+ let S = states[game.state];
+ if (action in S)
+ S[action](arg, player);
+ else if (action === 'undo' && game.undo && game.undo.length > 0)
+ pop_undo();
+ else
+ throw new Error('Invalid action: ' + action);
+ return game;
+}
+function game_view(state, player) {
+ console.log('game_view', state);
+ console.log('player', player);
+ game = state;
+ const faction_id = get_faction_id(player);
+ view = {
+ log: game.log,
+ prompt: null,
+ location: game.location,
+ selected: game.selected,
+ current_events: game.current_events,
+ hand: game.hands[faction_id],
+ };
+ if (player !== game.active) {
+ let inactive = states[game.state].inactive || game.state;
+ view.prompt = `Waiting for ${game.active} to ${inactive}.`;
+ }
+ else {
+ view.actions = {};
+ states[game.state].prompt();
+ if (game.undo && game.undo.length > 0)
+ view.actions.undo = 1;
+ else
+ view.actions.undo = 0;
+ }
+ return view;
+}
+// #endregion
+// #region setup
+function setup(seed, _scenario, _options) {
+ game = {
+ seed: seed,
+ state: null,
+ active: ANARCHIST,
+ bag_of_glory: {
+ [ANARCHISTS_ID]: 1,
+ [COMMUNISTS_ID]: 1,
+ [MODERATES_ID]: 1,
+ },
+ bonuses: [data_1.ON, data_1.ON],
+ current_events: [],
+ fronts: {
+ a: 2,
+ m: 2,
+ n: 2,
+ s: 2,
+ },
+ hands: {
+ [ANARCHISTS_ID]: [
+ draw_card(faction_cards[ANARCHIST]),
+ draw_card(faction_cards[ANARCHIST]),
+ draw_card(faction_cards[ANARCHIST]),
+ draw_card(faction_cards[ANARCHIST]),
+ draw_card(faction_cards[ANARCHIST]),
+ ],
+ [COMMUNISTS_ID]: [
+ draw_card(faction_cards[COMMUNIST]),
+ draw_card(faction_cards[COMMUNIST]),
+ draw_card(faction_cards[COMMUNIST]),
+ draw_card(faction_cards[COMMUNIST]),
+ draw_card(faction_cards[COMMUNIST]),
+ ],
+ [MODERATES_ID]: [
+ draw_card(faction_cards[MODERATE]),
+ draw_card(faction_cards[MODERATE]),
+ draw_card(faction_cards[MODERATE]),
+ draw_card(faction_cards[MODERATE]),
+ draw_card(faction_cards[MODERATE]),
+ ],
+ },
+ hero_points: {
+ [ANARCHISTS_ID]: 2,
+ [COMMUNISTS_ID]: 2,
+ [MODERATES_ID]: 0,
+ },
+ initiative: MODERATES_ID,
+ tracks: [5, 5, 6, 3, 3],
+ log: [],
+ undo: [],
+ turn: 1,
+ year: 1,
+ };
+ fascist_event();
+ game.state = 'move';
+ return game;
+}
+// #endregion
states.move = {
- inactive: "move",
- prompt() {
- view.prompt = "Move a piece."
- for (let p = 0; p < 32; ++p)
- gen_action_piece(p)
- },
- piece(p) {
- game.selected = p
- game.state = "move_to"
- },
-}
-
+ inactive: 'move',
+ prompt() {
+ view.prompt = 'Move a piece.';
+ for (let p = 0; p < 32; ++p)
+ gen_action_piece(p);
+ },
+ // piece(p) {
+ // game.selected = p
+ // game.state = "move_to"
+ // },
+};
states.move_to = {
- inactive: "move",
- prompt() {
- view.prompt = "Move the piece to a space."
- for (let s = 0; s < 64; ++s)
- gen_action_space(s)
- },
- space(to) {
- game.location[game.selected] = to
- game.state = "move"
- if (game.active === PLAYER1)
- game.active = PLAYER2
- else
- game.active = PLAYER1
- },
+ inactive: 'move',
+ prompt() {
+ view.prompt = 'Move the piece to a space.';
+ for (let s = 0; s < 64; ++s)
+ gen_action_space(s);
+ },
+ // space(to) {
+ // game.location[game.selected] = to
+ // game.state = "move"
+ // if (game.active === PLAYER1)
+ // game.active = PLAYER2
+ // else
+ // game.active = PLAYER1
+ // },
+};
+function pop_undo() {
+ const save_log = game.log;
+ const save_undo = game.undo;
+ game = save_undo.pop();
+ save_log.length = game.log;
+ game.log = save_log;
+ game.undo = save_undo;
+}
+// #region CARDS
+// function draw_faction_card(faction: Player): CardId {
+// return draw_faction_cards(faction, 1)[0];
+// }
+// function draw_faction_cards(faction: Player, count: number = 1): CardId[] {
+// const drawnCards = [];
+// }
+function draw_card(deck) {
+ clear_undo();
+ let i = random(deck.length);
+ let c = deck[i];
+ set_delete(deck, c);
+ return c;
+}
+// #endregion
+// #region EVENTS
+function resolve_event_attack(target, value) {
+ switch (target) {
+ case 'v':
+ break;
+ case 'd':
+ break;
+ default:
+ game.fronts[target] += value;
+ }
+}
+function fascist_event() {
+ const deck = fascist_decks[game.year];
+ const cardId = draw_card(deck);
+ game.current_events.push(cardId);
+ const card = cards[cardId];
+ card.effects.forEach((effect) => {
+ switch (effect.type) {
+ case 'attack':
+ resolve_event_attack(effect.target, effect.value);
+ break;
+ case 'bonus':
+ break;
+ case 'hero_points':
+ break;
+ case 'track':
+ game.tracks[effect.target] += effect.value;
+ break;
+ }
+ });
+}
+// #endregion
+// #region UTILITY
+function clear_undo() {
+ if (game.undo.length > 0)
+ game.undo = [];
+}
+function get_faction_id(player) {
+ switch (player) {
+ case ANARCHIST:
+ return ANARCHISTS_ID;
+ case COMMUNIST:
+ return COMMUNISTS_ID;
+ case MODERATE:
+ return MODERATES_ID;
+ default:
+ throw new Error('Unknown player');
+ }
+}
+function make_list(first, last) {
+ let list = [];
+ for (let i = first; i <= last; i++)
+ list.push(i);
+ return list;
+}
+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 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;
+ }
+ }
+}
+// #endregion
+// #region ARRAY
+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;
}
diff --git a/rules.ts b/rules.ts
new file mode 100644
index 0000000..a2a71ca
--- /dev/null
+++ b/rules.ts
@@ -0,0 +1,423 @@
+'use strict';
+
+import data, {
+ // LIBERTY,
+ // COLLECTIVIZATION,
+ // GOVERNMENT,
+ // SOVIET_SUPPORT,
+ // FOREIGN_AID,
+ // MORALE_BONUS,
+ // TEAMWORK_BONUS,
+ // OFF,
+ ON,
+ EventCard,
+ // StaticData,
+ // PLAYER_WITH_MOST_HERO_POINTS,
+} from './data';
+
+declare const brand: unique symbol;
+
+// branded typing
+type Brand<T, TBrand extends string> = T & {
+ [brand]: TBrand;
+};
+export type CardId = Brand<number, 'CardId'>;
+export type Player = Brand<string, 'Player'>;
+export type FactionId = Brand<string, 'FactionId'>;
+
+interface Game {
+ [index: number]: any;
+ seed: number;
+ log: number | string[];
+ undo: Game[];
+ turn: number;
+ year: number;
+ active: Player | null;
+ state: string | null;
+ bag_of_glory: Record<FactionId, number>;
+ bonuses: number[];
+ current_events: CardId[];
+ fronts: {
+ a: number;
+ m: number;
+ n: number;
+ s: number;
+ };
+ hands: Record<FactionId, CardId[]>;
+ hero_points: Record<FactionId, number>;
+ initiative: FactionId;
+ tracks: number[];
+
+ result?: string;
+ victory?: string;
+
+ location?: string;
+ selected?: string;
+
+ // played_card: CardId
+
+ // turn: Turn
+}
+
+export interface View {
+ log: number | string[];
+ active?: string | null;
+ prompt: string | null;
+ actions?: any;
+ victory?: string;
+ location?: string;
+ selected?: string;
+
+ current_events: CardId[];
+ hand: CardId[];
+}
+
+// interface State {
+// inactive: string;
+// prompt: () => void;
+// }
+
+type States = {
+ [key: string]: any;
+};
+
+const states = {} as States;
+let game = {} as Game; // = null
+var view = {} as View; // = null
+
+const ANARCHIST = 'Anarchist' as Player;
+const COMMUNIST = 'Communist' as Player;
+const MODERATE = 'Moderate' as Player;
+
+const ANARCHISTS_ID = 'a' as FactionId;
+const COMMUNISTS_ID = 'c' as FactionId;
+const MODERATES_ID = 'm' as FactionId;
+
+const {
+ cards,
+ // fronts
+} = data;
+
+const faction_cards = {
+ [ANARCHIST]: make_list(37, 54) as CardId[],
+ [COMMUNIST]: make_list(19, 36) as CardId[],
+ [MODERATE]: make_list(1, 18) as CardId[],
+};
+
+const fascist_decks = {
+ 1: make_list(55, 62),
+};
+
+export const scenarios = ['Standard'];
+
+export const roles: Player[] = [ANARCHIST, COMMUNIST, MODERATE];
+
+function gen_action(action, argument) {
+ if (!(action in view.actions)) view.actions[action] = [];
+ view.actions[action].push(argument);
+}
+
+function gen_action_space(space) {
+ gen_action('space', space);
+}
+
+function gen_action_piece(piece) {
+ gen_action('piece', piece);
+}
+
+// function gen_action_card(card) {
+// gen_action('card', card);
+// }
+
+export function action(
+ state: any,
+ player: Player,
+ action: string,
+ arg: unknown
+) {
+ game = state;
+ let S = states[game.state];
+ if (action in S) S[action](arg, player);
+ else if (action === 'undo' && game.undo && game.undo.length > 0) pop_undo();
+ else throw new Error('Invalid action: ' + action);
+ return game;
+}
+
+// #region VIEW
+
+export { game_view as view };
+
+function game_view(state: Game, player: Player) {
+ console.log('game_view', state);
+ console.log('player', player);
+ game = state;
+
+ const faction_id = get_faction_id(player);
+
+ view = {
+ log: game.log,
+ prompt: null,
+ location: game.location,
+ selected: game.selected,
+ current_events: game.current_events,
+ hand: game.hands[faction_id],
+ };
+
+ if (player !== game.active) {
+ let inactive = states[game.state].inactive || game.state;
+ view.prompt = `Waiting for ${game.active} to ${inactive}.`;
+ } else {
+ view.actions = {};
+ states[game.state].prompt();
+ if (game.undo && game.undo.length > 0) view.actions.undo = 1;
+ else view.actions.undo = 0;
+ }
+
+ return view;
+}
+
+// #endregion
+
+// #region setup
+
+export function setup(seed: number, _scenario: string, _options: unknown) {
+ game = {
+ seed: seed,
+ state: null,
+ active: ANARCHIST,
+ bag_of_glory: {
+ [ANARCHISTS_ID]: 1,
+ [COMMUNISTS_ID]: 1,
+ [MODERATES_ID]: 1,
+ },
+ bonuses: [ON, ON],
+ current_events: [],
+ fronts: {
+ a: 2,
+ m: 2,
+ n: 2,
+ s: 2,
+ },
+ hands: {
+ [ANARCHISTS_ID]: [
+ draw_card(faction_cards[ANARCHIST]),
+ draw_card(faction_cards[ANARCHIST]),
+ draw_card(faction_cards[ANARCHIST]),
+ draw_card(faction_cards[ANARCHIST]),
+ draw_card(faction_cards[ANARCHIST]),
+ ],
+ [COMMUNISTS_ID]: [
+ draw_card(faction_cards[COMMUNIST]),
+ draw_card(faction_cards[COMMUNIST]),
+ draw_card(faction_cards[COMMUNIST]),
+ draw_card(faction_cards[COMMUNIST]),
+ draw_card(faction_cards[COMMUNIST]),
+ ],
+ [MODERATES_ID]: [
+ draw_card(faction_cards[MODERATE]),
+ draw_card(faction_cards[MODERATE]),
+ draw_card(faction_cards[MODERATE]),
+ draw_card(faction_cards[MODERATE]),
+ draw_card(faction_cards[MODERATE]),
+ ],
+ },
+ hero_points: {
+ [ANARCHISTS_ID]: 2,
+ [COMMUNISTS_ID]: 2,
+ [MODERATES_ID]: 0,
+ },
+ initiative: MODERATES_ID,
+ tracks: [5, 5, 6, 3, 3],
+ log: [],
+ undo: [],
+ turn: 1,
+ year: 1,
+ };
+
+ fascist_event();
+
+ game.state = 'move';
+
+ return game;
+}
+
+// #endregion
+
+states.move = {
+ inactive: 'move',
+ prompt() {
+ view.prompt = 'Move a piece.';
+ for (let p = 0; p < 32; ++p) gen_action_piece(p);
+ },
+ // piece(p) {
+ // game.selected = p
+ // game.state = "move_to"
+ // },
+};
+
+states.move_to = {
+ inactive: 'move',
+ prompt() {
+ view.prompt = 'Move the piece to a space.';
+ for (let s = 0; s < 64; ++s) gen_action_space(s);
+ },
+ // space(to) {
+ // game.location[game.selected] = to
+ // game.state = "move"
+ // if (game.active === PLAYER1)
+ // game.active = PLAYER2
+ // else
+ // game.active = PLAYER1
+ // },
+};
+
+function pop_undo() {
+ const save_log = game.log;
+ const save_undo = game.undo;
+ game = save_undo.pop()!;
+ (save_log as string[]).length = game.log as number;
+ game.log = save_log;
+ game.undo = save_undo;
+}
+
+// #region CARDS
+// function draw_faction_card(faction: Player): CardId {
+// return draw_faction_cards(faction, 1)[0];
+// }
+
+// function draw_faction_cards(faction: Player, count: number = 1): CardId[] {
+// const drawnCards = [];
+
+// }
+
+function draw_card(deck: CardId[]) {
+ clear_undo();
+ let i = random(deck.length);
+ let c = deck[i];
+ set_delete(deck, c);
+ return c;
+}
+
+// #endregion
+
+// #region EVENTS
+
+function resolve_event_attack(target: string | number, value: number) {
+ switch (target) {
+ case 'v':
+ break;
+ case 'd':
+ break;
+ default:
+ game.fronts[target] += value;
+ }
+}
+
+function fascist_event() {
+ const deck = fascist_decks[game.year];
+ const cardId = draw_card(deck);
+ game.current_events.push(cardId);
+
+ const card = cards[cardId] as EventCard;
+
+ card.effects.forEach((effect) => {
+ switch (effect.type) {
+ case 'attack':
+ resolve_event_attack(effect.target, effect.value);
+ break;
+ case 'bonus':
+ break;
+ case 'hero_points':
+ break;
+ case 'track':
+ game.tracks[effect.target] += effect.value;
+ break;
+ }
+ });
+}
+
+// #endregion
+
+// #region UTILITY
+
+function clear_undo() {
+ if (game.undo.length > 0) game.undo = [];
+}
+
+function get_faction_id(player: Player) {
+ switch (player) {
+ case ANARCHIST:
+ return ANARCHISTS_ID;
+ case COMMUNIST:
+ return COMMUNISTS_ID;
+ case MODERATE:
+ return MODERATES_ID;
+ default:
+ throw new Error('Unknown player');
+ }
+}
+
+function make_list(first: number, last: number) {
+ let list = [];
+ for (let i = first; i <= last; i++) list.push(i);
+ return list;
+}
+
+function random(range: number): number {
+ // 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 set_delete<T>(set: T[], item: T) {
+ 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;
+ }
+ }
+}
+
+// #endregion
+
+// #region ARRAY
+
+function array_remove<T>(array: T[], index: number) {
+ let n = array.length;
+ for (let i = index + 1; i < n; ++i) array[i - 1] = array[i];
+ array.length = n - 1;
+}
+
+// function array_insert<T>(array: T[], index: number, item: T) {
+// for (let i = array.length; i > index; --i) array[i] = array[i - 1];
+// array[index] = item;
+// }
+
+// function array_remove_pair<T>(array: T[], index: number) {
+// 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<K, V>(
+// array: (K | V)[],
+// index: number,
+// key: K,
+// value: V
+// ) {
+// 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;
+// }
+
+// #endregion
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..825d119
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "target": "es2016",
+ "declaration": false,
+ "sourceMap": false,
+ "module": "commonjs",
+ "moduleResolution": "node",
+
+ "strict": false,
+ //"strictNullChecks": true,
+ "strictFunctionTypes": true,
+ "strictBindCallApply": true,
+ //"strictPropertyInitialization": true,
+ "noImplicitThis": true,
+ "useUnknownInCatchVariables": true,
+ "alwaysStrict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ //"exactOptionalPropertyTypes": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedIndexedAccess": true,
+ "noImplicitOverride": true,
+ "noPropertyAccessFromIndexSignature": false
+ }
+}