diff options
author | Frans Bongers <fransbongers@franss-mbp.home> | 2024-11-24 22:36:37 +0100 |
---|---|---|
committer | Frans Bongers <fransbongers@franss-mbp.home> | 2024-11-24 22:36:37 +0100 |
commit | 902a07087c2808c77943b9617a2ab063b1c260e9 (patch) | |
tree | 0d11c373a0da7ab35d23df0d881f8eeeb1d14322 | |
parent | 1b0d1a568e4224f6f3ff927e06997b9152e86ddf (diff) | |
download | land-and-freedom-902a07087c2808c77943b9617a2ab063b1c260e9.tar.gz |
initial setup
-rw-r--r-- | data.js | 563 | ||||
-rw-r--r-- | data.ts | 601 | ||||
-rw-r--r-- | land-and-freedom.css | 287 | ||||
-rw-r--r-- | land-and-freedom.scss | 85 | ||||
-rw-r--r-- | package.json | 14 | ||||
-rw-r--r-- | play.html | 9 | ||||
-rw-r--r-- | play.js | 170 | ||||
-rw-r--r-- | play.ts | 131 | ||||
-rw-r--r-- | rules.js | 369 | ||||
-rw-r--r-- | rules.ts | 423 | ||||
-rw-r--r-- | tsconfig.json | 26 |
11 files changed, 2482 insertions, 196 deletions
@@ -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; @@ -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 +} @@ -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> @@ -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'); } @@ -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'); +} @@ -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 + } +} |