summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data.js38
-rw-r--r--data.ts42
-rw-r--r--images/bonus/bonus_0_off.png (renamed from images/morale_bonus_off.png)bin103547 -> 103547 bytes
-rw-r--r--images/bonus/bonus_0_on.png (renamed from images/morale_bonus_on.png)bin83460 -> 83460 bytes
-rw-r--r--images/bonus/bonus_1_off.png (renamed from images/teamwork_bonus_off.png)bin95000 -> 95000 bytes
-rw-r--r--images/bonus/bonus_1_on.png (renamed from images/teamwork_bonus_on.png)bin80885 -> 80885 bytes
-rw-r--r--images/medaillons/medaillon_0.png (renamed from images/medaillons/subterfuge.png)bin83925 -> 83925 bytes
-rw-r--r--images/medaillons/medaillon_1.png (renamed from images/medaillons/valor.png)bin86793 -> 86793 bytes
-rw-r--r--images/medaillons/medaillon_2.png (renamed from images/medaillons/momentum.png)bin82783 -> 82783 bytes
-rw-r--r--images/medaillons/medaillon_3.png (renamed from images/medaillons/strategy.png)bin84146 -> 84146 bytes
-rw-r--r--images/medaillons/medaillon_4.png (renamed from images/medaillons/propaganda.png)bin90155 -> 90155 bytes
-rw-r--r--images/medaillons/medaillon_5.png (renamed from images/medaillons/intelligence.png)bin80227 -> 80227 bytes
-rw-r--r--images/medaillons/medaillon_6.png (renamed from images/medaillons/volunteers.png)bin90532 -> 90532 bytes
-rw-r--r--images/medaillons/medaillon_7.png (renamed from images/medaillons/archives.png)bin89231 -> 89231 bytes
-rw-r--r--images/medaillons/medaillon_8.png (renamed from images/medaillons/organization.png)bin84308 -> 84308 bytes
-rw-r--r--land-and-freedom.css78
-rw-r--r--land-and-freedom.scss61
-rw-r--r--play.js179
-rw-r--r--play.ts255
-rw-r--r--rules.js372
-rw-r--r--rules.ts388
-rw-r--r--tsconfig.json3
-rw-r--r--types.d.ts95
23 files changed, 1085 insertions, 426 deletions
diff --git a/data.js b/data.js
index 0b7d294..a028139 100644
--- a/data.js
+++ b/data.js
@@ -559,5 +559,43 @@ const data = {
top: 426,
},
],
+ medaillons: [
+ {
+ id: 0,
+ name: 'Subterfuge'
+ },
+ {
+ id: 1,
+ name: 'Valor'
+ },
+ {
+ id: 2,
+ name: 'Momentum'
+ },
+ {
+ id: 3,
+ name: 'Strategy'
+ },
+ {
+ id: 4,
+ name: 'Propaganda'
+ },
+ {
+ id: 5,
+ name: 'Intelligence'
+ },
+ {
+ id: 6,
+ name: 'Volunteers'
+ },
+ {
+ id: 7,
+ name: 'Archives'
+ },
+ {
+ id: 8,
+ name: 'Organization'
+ },
+ ]
};
exports.default = data;
diff --git a/data.ts b/data.ts
index 7f6e283..c1d62b3 100644
--- a/data.ts
+++ b/data.ts
@@ -30,6 +30,10 @@ export interface StaticData {
left: number;
top: number;
}>;
+ medaillons: Array<{
+ id: number;
+ name: string;
+ }>
}
const LIBERTY = 0;
@@ -597,5 +601,43 @@ const data: StaticData = {
top: 426,
},
],
+ medaillons: [
+ {
+ id: 0,
+ name: 'Subterfuge'
+ },
+ {
+ id: 1,
+ name: 'Valor'
+ },
+ {
+ id: 2,
+ name: 'Momentum'
+ },
+ {
+ id: 3,
+ name: 'Strategy'
+ },
+ {
+ id: 4,
+ name: 'Propaganda'
+ },
+ {
+ id: 5,
+ name: 'Intelligence'
+ },
+ {
+ id: 6,
+ name: 'Volunteers'
+ },
+ {
+ id: 7,
+ name: 'Archives'
+ },
+ {
+ id: 8,
+ name: 'Organization'
+ },
+ ]
};
export default data;
diff --git a/images/morale_bonus_off.png b/images/bonus/bonus_0_off.png
index f68e463..f68e463 100644
--- a/images/morale_bonus_off.png
+++ b/images/bonus/bonus_0_off.png
Binary files differ
diff --git a/images/morale_bonus_on.png b/images/bonus/bonus_0_on.png
index 9db21c2..9db21c2 100644
--- a/images/morale_bonus_on.png
+++ b/images/bonus/bonus_0_on.png
Binary files differ
diff --git a/images/teamwork_bonus_off.png b/images/bonus/bonus_1_off.png
index 29e95b1..29e95b1 100644
--- a/images/teamwork_bonus_off.png
+++ b/images/bonus/bonus_1_off.png
Binary files differ
diff --git a/images/teamwork_bonus_on.png b/images/bonus/bonus_1_on.png
index 6b71aa9..6b71aa9 100644
--- a/images/teamwork_bonus_on.png
+++ b/images/bonus/bonus_1_on.png
Binary files differ
diff --git a/images/medaillons/subterfuge.png b/images/medaillons/medaillon_0.png
index acec979..acec979 100644
--- a/images/medaillons/subterfuge.png
+++ b/images/medaillons/medaillon_0.png
Binary files differ
diff --git a/images/medaillons/valor.png b/images/medaillons/medaillon_1.png
index 8ad2d04..8ad2d04 100644
--- a/images/medaillons/valor.png
+++ b/images/medaillons/medaillon_1.png
Binary files differ
diff --git a/images/medaillons/momentum.png b/images/medaillons/medaillon_2.png
index 4200d41..4200d41 100644
--- a/images/medaillons/momentum.png
+++ b/images/medaillons/medaillon_2.png
Binary files differ
diff --git a/images/medaillons/strategy.png b/images/medaillons/medaillon_3.png
index 0733f78..0733f78 100644
--- a/images/medaillons/strategy.png
+++ b/images/medaillons/medaillon_3.png
Binary files differ
diff --git a/images/medaillons/propaganda.png b/images/medaillons/medaillon_4.png
index ad18b04..ad18b04 100644
--- a/images/medaillons/propaganda.png
+++ b/images/medaillons/medaillon_4.png
Binary files differ
diff --git a/images/medaillons/intelligence.png b/images/medaillons/medaillon_5.png
index 4c27495..4c27495 100644
--- a/images/medaillons/intelligence.png
+++ b/images/medaillons/medaillon_5.png
Binary files differ
diff --git a/images/medaillons/volunteers.png b/images/medaillons/medaillon_6.png
index ead86f3..ead86f3 100644
--- a/images/medaillons/volunteers.png
+++ b/images/medaillons/medaillon_6.png
Binary files differ
diff --git a/images/medaillons/archives.png b/images/medaillons/medaillon_7.png
index acc4053..acc4053 100644
--- a/images/medaillons/archives.png
+++ b/images/medaillons/medaillon_7.png
Binary files differ
diff --git a/images/medaillons/organization.png b/images/medaillons/medaillon_8.png
index e228bb9..e228bb9 100644
--- a/images/medaillons/organization.png
+++ b/images/medaillons/medaillon_8.png
Binary files differ
diff --git a/land-and-freedom.css b/land-and-freedom.css
index e4c95a9..00fbfd0 100644
--- a/land-and-freedom.css
+++ b/land-and-freedom.css
@@ -44,6 +44,7 @@ main {
flex-direction: column;
align-items: center;
padding: 4px;
+ border-radius: 20px;
}
.front .value {
@@ -55,6 +56,7 @@ main {
.front[data-front-id=m] {
width: 104px;
height: 114px;
+ border-radius: 23px;
}
.card {
@@ -340,6 +342,7 @@ main {
}
.card.action:hover,
+.card.selected,
.front.action:hover,
.standee.action:hover {
box-shadow: 0 0 0 3px yellow;
@@ -365,11 +368,84 @@ main {
background-image: url("images/standees/standee_4.png");
}
+.bonus {
+ box-sizing: border-box;
+ position: absolute;
+ width: 54px;
+ height: 54px;
+ background-size: cover;
+ background-repeat: no-repeat;
+}
+
+.bonus[data-bonus-id="0"][data-bonus-on="0"] {
+ background-image: url("images/bonus/bonus_0_off.png");
+}
+
+.bonus[data-bonus-id="0"][data-bonus-on="1"] {
+ background-image: url("images/bonus/bonus_0_on.png");
+}
+
+.bonus[data-bonus-id="1"][data-bonus-on="0"] {
+ background-image: url("images/bonus/bonus_1_off.png");
+}
+
+.bonus[data-bonus-id="1"][data-bonus-on="1"] {
+ background-image: url("images/bonus/bonus_1_on.png");
+}
+
+.medaillon {
+ box-sizing: border-box;
+ position: absolute;
+ width: 47px;
+ height: 47px;
+ background-size: cover;
+ background-repeat: no-repeat;
+ box-shadow: 0 0 0 1px #333;
+ border-radius: 10px;
+}
+
+.medaillon[data-medaillon-id="0"] {
+ background-image: url("images/medaillons/medaillon_0.png");
+}
+
+.medaillon[data-medaillon-id="1"] {
+ background-image: url("images/medaillons/medaillon_1.png");
+}
+
+.medaillon[data-medaillon-id="2"] {
+ background-image: url("images/medaillons/medaillon_2.png");
+}
+
+.medaillon[data-medaillon-id="3"] {
+ background-image: url("images/medaillons/medaillon_3.png");
+}
+
+.medaillon[data-medaillon-id="4"] {
+ background-image: url("images/medaillons/medaillon_4.png");
+}
+
+.medaillon[data-medaillon-id="5"] {
+ background-image: url("images/medaillons/medaillon_5.png");
+}
+
+.medaillon[data-medaillon-id="6"] {
+ background-image: url("images/medaillons/medaillon_6.png");
+}
+
+.medaillon[data-medaillon-id="7"] {
+ background-image: url("images/medaillons/medaillon_7.png");
+}
+
+.medaillon[data-medaillon-id="8"] {
+ background-image: url("images/medaillons/medaillon_8.png");
+}
+
#log {
background-color: floralwhite;
}
-#log .h1, #log .h2 {
+#log .h1,
+#log .h2 {
font-size: 10px;
padding-top: 2px;
padding-bottom: 2px;
diff --git a/land-and-freedom.scss b/land-and-freedom.scss
index fc8e962..5dd9325 100644
--- a/land-and-freedom.scss
+++ b/land-and-freedom.scss
@@ -61,6 +61,7 @@ main {
flex-direction: column;
align-items: center;
padding: 4px;
+ border-radius: 20px;
// background-color: yellow;
// opacity: 0.5;
}
@@ -74,6 +75,7 @@ main {
.front[data-front-id='m'] {
width: 104px;
height: 114px;
+ border-radius: 23px;
}
.card {
@@ -122,6 +124,7 @@ main {
}
.card.action:hover,
+.card.selected,
.front.action:hover,
.standee.action:hover {
box-shadow: 0 0 0 3px $selected-color;
@@ -137,12 +140,54 @@ main {
}
}
-#log { background-color: floralwhite; }
-#log .h1, #log .h2 {
- font-size: 10px;
- padding-top: 2px;
- padding-bottom: 2px;
- text-align: center;
+.bonus {
+ box-sizing: border-box;
+ position: absolute;
+ width: 54px;
+ height: 54px;
+ background-size: cover;
+ background-repeat: no-repeat;
+}
+
+@for $i from 0 through 1 {
+ .bonus[data-bonus-id='#{$i}'][data-bonus-on='0'] {
+ background-image: url('images/bonus/bonus_#{$i}_off.png');
+ }
+ .bonus[data-bonus-id='#{$i}'][data-bonus-on='1'] {
+ background-image: url('images/bonus/bonus_#{$i}_on.png');
+ }
+}
+
+.medaillon {
+ box-sizing: border-box;
+ position: absolute;
+ width: 47px;
+ height: 47px;
+ background-size: cover;
+ background-repeat: no-repeat;
+ box-shadow: 0 0 0 1px #333;
+ border-radius: 10px;
+}
+
+@for $i from 0 through 8 {
+ .medaillon[data-medaillon-id='#{$i}'] {
+ background-image: url('images/medaillons/medaillon_#{$i}.png');
+ }
+}
+
+#log {
+ background-color: floralwhite;
+}
+#log .h1,
+#log .h2 {
+ font-size: 10px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ text-align: center;
+}
+#log .h1 {
+ background-color: hsl(4, 40%, 73%);
+}
+#log .h2 {
+ background-color: hsl(250, 40%, 83%);
}
-#log .h1 { background-color: hsl(4, 40%, 73%); }
-#log .h2 { background-color: hsl(250, 40%, 83%); } \ No newline at end of file
diff --git a/play.js b/play.js
index 9c1b6f2..c39f702 100644
--- a/play.js
+++ b/play.js
@@ -1,41 +1,109 @@
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
-/* global view, player, send_action, action_button */
-// const SPACE_COUNT = 64;
-// const PIECE_COUNT = 32;
+const BONUSES_COUNT = 2;
const CARD_COUNT = 62;
+const MEDAILLONS_COUNT = 5;
const STANDEES_COUNT = 5;
let ui = {
map: document.getElementById('map'),
hand: document.getElementById('hand'),
current_events: document.getElementById('current_events'),
tracks: document.getElementById('tracks'),
+ bonuses: [],
fronts: {},
frontValues: {},
+ medaillons: [],
spaces: [],
standees: [],
pieces: [],
cards: [],
};
let action_register = [];
+const LAYOUT_BONUSES = [
+ [435, 481],
+ [493, 481],
+];
const LAYOUT_CURRENT_EVENTS = [
[172, 648],
[309, 648],
[445, 648],
[584, 648],
];
+const LAYOUT_MEDAILLONS = [
+ [364, 556],
+ [415, 556],
+ [466, 556],
+ [517, 556],
+ [568, 556],
+];
const LAYOUT_TRACKS = [
- [[581, 46], [618, 46], [655, 46], [691, 46], [728, 46], [765, 46], [801, 46], [838, 46], [874, 46], [911, 46], [948, 46]],
- [[581, 156], [618, 156], [655, 156], [691, 156], [728, 156], [765, 156], [801, 156], [838, 156], [874, 156], [911, 156], [948, 156]],
- [[581, 267], [618, 267], [655, 267], [691, 267], [728, 267], [765, 267], [801, 267], [838, 267], [874, 267], [911, 267], [948, 267]],
- [[581, 378], [618, 378], [655, 378], [691, 378], [728, 378], [765, 378], [801, 378], [838, 378], [874, 378], [911, 378], [948, 378]],
- [[581, 489], [618, 489], [655, 489], [691, 489], [728, 489], [765, 489], [801, 489], [838, 489], [874, 489], [911, 489], [948, 489]],
+ [
+ [581, 46],
+ [618, 46],
+ [655, 46],
+ [691, 46],
+ [728, 46],
+ [765, 46],
+ [801, 46],
+ [838, 46],
+ [874, 46],
+ [911, 46],
+ [948, 46],
+ ],
+ [
+ [581, 156],
+ [618, 156],
+ [655, 156],
+ [691, 156],
+ [728, 156],
+ [765, 156],
+ [801, 156],
+ [838, 156],
+ [874, 156],
+ [911, 156],
+ [948, 156],
+ ],
+ [
+ [581, 267],
+ [618, 267],
+ [655, 267],
+ [691, 267],
+ [728, 267],
+ [765, 267],
+ [801, 267],
+ [838, 267],
+ [874, 267],
+ [911, 267],
+ [948, 267],
+ ],
+ [
+ [581, 378],
+ [618, 378],
+ [655, 378],
+ [691, 378],
+ [728, 378],
+ [765, 378],
+ [801, 378],
+ [838, 378],
+ [874, 378],
+ [911, 378],
+ [948, 378],
+ ],
+ [
+ [581, 489],
+ [618, 489],
+ [655, 489],
+ [691, 489],
+ [728, 489],
+ [765, 489],
+ [801, 489],
+ [838, 489],
+ [874, 489],
+ [911, 489],
+ [948, 489],
+ ],
];
-// @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'));
@@ -43,8 +111,9 @@ const LAYOUT_TRACKS = [
element.style.left = `${left}px`;
element.style.top = `${top}px`;
element.setAttribute('data-front-id', `${id}`);
- spaces.appendChild(element);
- const frontValueElement = (ui.frontValues[front.id] = document.createElement('span'));
+ ui.map.appendChild(element);
+ const frontValueElement = (ui.frontValues[front.id] =
+ document.createElement('span'));
frontValueElement.classList.add('value');
register_action(element, 'front', id);
element.appendChild(frontValueElement);
@@ -78,20 +147,23 @@ function on_init() {
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 track standees
+ for (let b = 0; b < BONUSES_COUNT; ++b) {
+ let e = (ui.bonuses[b] = document.createElement('div'));
+ e.className = 'bonus';
+ e.setAttribute('data-bonus-id', '' + b);
+ e.style.left = LAYOUT_BONUSES[b][0] + 'px';
+ e.style.top = LAYOUT_BONUSES[b][1] + 'px';
+ LAYOUT_BONUSES;
+ register_action(e, 'bonus', b);
+ ui.map.appendChild(ui.bonuses[b]);
+ }
+ for (let m = 0; m < MEDAILLONS_COUNT; ++m) {
+ let e = (ui.medaillons[m] = document.createElement('div'));
+ e.className = 'medaillon';
+ e.setAttribute('data-medaillon-id', '' + m);
+ register_action(e, 'medaillon', m);
+ ui.map.appendChild(ui.medaillons[m]);
+ }
for (let s = 0; s < STANDEES_COUNT; ++s) {
let e = (ui.standees[s] = document.createElement('div'));
e.className = 'standee';
@@ -100,7 +172,6 @@ function on_init() {
ui.tracks.appendChild(ui.standees[s]);
}
console.log('standees', ui.standees);
- // create card elements
for (let c = 1; c < CARD_COUNT; ++c) {
let e = (ui.cards[c] = document.createElement('div'));
e.className = 'card';
@@ -109,15 +180,9 @@ function on_init() {
}
console.log('action_register', action_register[0]);
}
-// @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.current_events.replaceChildren();
for (let i = 0; i < view.current_events.length; i++) {
const cardId = view.current_events[i];
@@ -126,38 +191,54 @@ function on_update() {
ui.cards[cardId].style.left = LAYOUT_CURRENT_EVENTS[i][0] + 'px';
ui.cards[cardId].style.top = LAYOUT_CURRENT_EVENTS[i][1] + 'px';
}
+ for (let bonus_id of Object.keys(view.bonuses)) {
+ ui.bonuses[bonus_id].setAttribute('data-bonus-on', view.bonuses[bonus_id] + 0);
+ }
ui.hand.replaceChildren();
- for (let c of view.hand)
+ for (let c of view.hand) {
+ ui.cards[c].classList.remove('selected');
ui.hand.appendChild(ui.cards[c]);
- for (let i = 0; i < view.tracks.length; i++) {
- // ui.tracks.appendChild(ui.standees[i]);
+ if (c === view.selected_card) {
+ ui.cards[c].classList.add('selected');
+ }
+ }
+ ;
+ for (let i = 0; i < view.tracks.length; ++i) {
ui.standees[i].style.left = LAYOUT_TRACKS[i][view.tracks[i]][0] + 'px';
ui.standees[i].style.top = LAYOUT_TRACKS[i][view.tracks[i]][1] + 'px';
}
for (let frontId of Object.keys(view.fronts)) {
ui.frontValues[frontId].replaceChildren(view.fronts[frontId]);
}
+ for (let i = 0; i < view.medaillons.length; ++i) {
+ if (view.medaillons[i] !== null) {
+ ui.medaillons[i].style.left = LAYOUT_MEDAILLONS[i][0] + 'px';
+ ui.medaillons[i].style.top = LAYOUT_MEDAILLONS[i][1] + 'px';
+ }
+ }
for (let e of action_register)
e.classList.toggle('action', is_action(e.my_action, e.my_id));
+ action_button('Anarchist', 'Anarchist');
+ action_button('Communist', 'Communist');
+ action_button('Moderate', 'Moderate');
+ action_button('draw_card', 'Draw card');
+ action_button('play_for_ap', 'Play card for Action Points');
+ action_button('play_for_event', 'Play card for Event');
+ action_button('spend_hp', 'Spend Hero Points');
+ action_button('add_glory', 'Add Glory');
action_button('next', 'Next');
+ action_button('done', 'Done');
action_button('undo', 'Undo');
- action_button('add_glory', 'Add Glory');
}
-// @ts-ignore
function on_log(text) {
- let p = document.createElement("div");
+ let p = document.createElement('div');
if (text.match(/^>/)) {
text = text.substring(1);
p.className = 'i';
}
- text = text.replace(/&/g, "&amp;");
- text = text.replace(/</g, "&lt;");
- text = text.replace(/>/g, "&gt;");
- // text = text.replace(/C(\d+)/g, sub_card_name)
- // text = text.replace(/S(\d+)/g, sub_space_name)
- // text = text.replace(/U(\d+)/g, sub_unit_name)
- // TODO dice icons
- // text = text.replace(/\bD\d\b/g, sub_icon)
+ text = text.replace(/&/g, '&amp;');
+ text = text.replace(/</g, '&lt;');
+ text = text.replace(/>/g, '&gt;');
if (text.match(/^\.h1/)) {
text = text.substring(4);
p.className = 'h1';
diff --git a/play.ts b/play.ts
index 26e942c..e0cb69a 100644
--- a/play.ts
+++ b/play.ts
@@ -1,20 +1,23 @@
'use strict';
-import { StaticData } from './data';
-import { Player, View } from './rules';
+import {
+
+ StaticData,
+} from './data';
+import { View } from './types';
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 BONUSES_COUNT = 2;
// const PIECE_COUNT = 32;
const CARD_COUNT = 62;
+const MEDAILLONS_COUNT = 5;
const STANDEES_COUNT = 5;
let ui = {
@@ -22,8 +25,10 @@ let ui = {
hand: document.getElementById('hand'),
current_events: document.getElementById('current_events'),
tracks: document.getElementById('tracks'),
+ bonuses: [],
fronts: {},
frontValues: {},
+ medaillons: [],
spaces: [],
standees: [],
pieces: [],
@@ -32,6 +37,11 @@ let ui = {
let action_register = [];
+const LAYOUT_BONUSES = [
+ [435, 481],
+ [493, 481],
+];
+
const LAYOUT_CURRENT_EVENTS = [
[172, 648],
[309, 648],
@@ -39,20 +49,84 @@ const LAYOUT_CURRENT_EVENTS = [
[584, 648],
];
+const LAYOUT_MEDAILLONS = [
+ [364, 556],
+ [415, 556],
+ [466, 556],
+ [517, 556],
+ [568, 556],
+];
+
const LAYOUT_TRACKS = [
- [[581, 46],[618, 46],[655, 46],[691, 46],[728, 46],[765, 46],[801, 46],[838, 46],[874, 46],[911, 46],[948, 46]],
- [[581, 156],[618, 156],[655, 156],[691, 156],[728, 156],[765, 156],[801, 156],[838, 156],[874, 156],[911, 156],[948, 156]],
- [[581, 267],[618, 267],[655, 267],[691, 267],[728, 267],[765, 267],[801, 267],[838, 267],[874, 267],[911, 267],[948, 267]],
- [[581, 378],[618, 378],[655, 378],[691, 378],[728, 378],[765, 378],[801, 378],[838, 378],[874, 378],[911, 378],[948, 378]],
- [[581, 489],[618, 489],[655, 489],[691, 489],[728, 489],[765, 489],[801, 489],[838, 489],[874, 489],[911, 489],[948, 489]],
+ [
+ [581, 46],
+ [618, 46],
+ [655, 46],
+ [691, 46],
+ [728, 46],
+ [765, 46],
+ [801, 46],
+ [838, 46],
+ [874, 46],
+ [911, 46],
+ [948, 46],
+ ],
+ [
+ [581, 156],
+ [618, 156],
+ [655, 156],
+ [691, 156],
+ [728, 156],
+ [765, 156],
+ [801, 156],
+ [838, 156],
+ [874, 156],
+ [911, 156],
+ [948, 156],
+ ],
+ [
+ [581, 267],
+ [618, 267],
+ [655, 267],
+ [691, 267],
+ [728, 267],
+ [765, 267],
+ [801, 267],
+ [838, 267],
+ [874, 267],
+ [911, 267],
+ [948, 267],
+ ],
+ [
+ [581, 378],
+ [618, 378],
+ [655, 378],
+ [691, 378],
+ [728, 378],
+ [765, 378],
+ [801, 378],
+ [838, 378],
+ [874, 378],
+ [911, 378],
+ [948, 378],
+ ],
+ [
+ [581, 489],
+ [618, 489],
+ [655, 489],
+ [691, 489],
+ [728, 489],
+ [765, 489],
+ [801, 489],
+ [838, 489],
+ [874, 489],
+ [911, 489],
+ [948, 489],
+ ],
];
// @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'));
@@ -60,8 +134,9 @@ const LAYOUT_TRACKS = [
element.style.left = `${left}px`;
element.style.top = `${top}px`;
element.setAttribute('data-front-id', `${id}`);
- spaces.appendChild(element);
- const frontValueElement = (ui.frontValues[front.id] = document.createElement('span'));
+ ui.map.appendChild(element);
+ const frontValueElement = (ui.frontValues[front.id] =
+ document.createElement('span'));
frontValueElement.classList.add('value');
register_action(element, 'front', id);
element.appendChild(frontValueElement);
@@ -70,14 +145,13 @@ const LAYOUT_TRACKS = [
console.log('ui', ui);
-
function register_action(
- e: HTMLElement & {my_action?: string; my_id?: string | number},
+ e: HTMLElement & { my_action?: string; my_id?: string | number },
action: string,
id: string | number
) {
- e.my_action = action
- e.my_id = id
+ e.my_action = action;
+ e.my_id = id;
e.onmousedown = on_click_action;
action_register.push(e);
}
@@ -124,11 +198,32 @@ function on_init() {
// register_action(e, 'piece', p);
// }
+ // create bonus markers
+ for (let b = 0; b < BONUSES_COUNT; ++b) {
+ let e = (ui.bonuses[b] = document.createElement('div'));
+ e.className = 'bonus';
+ e.setAttribute('data-bonus-id', '' + b);
+ e.style.left = LAYOUT_BONUSES[b][0] + 'px';
+ e.style.top = LAYOUT_BONUSES[b][1] + 'px';
+ LAYOUT_BONUSES;
+ register_action(e, 'bonus', b);
+ ui.map.appendChild(ui.bonuses[b]);
+ }
+
+ // create track medaillons
+ for (let m = 0; m < MEDAILLONS_COUNT; ++m) {
+ let e = (ui.medaillons[m] = document.createElement('div'));
+ e.className = 'medaillon';
+ e.setAttribute('data-medaillon-id', '' + m);
+ register_action(e, 'medaillon', m);
+ ui.map.appendChild(ui.medaillons[m]);
+ }
+
// create track standees
for (let s = 0; s < STANDEES_COUNT; ++s) {
let e = (ui.standees[s] = document.createElement('div'));
e.className = 'standee';
- e.setAttribute('data-standee-id', '' + s)
+ e.setAttribute('data-standee-id', '' + s);
register_action(e, 'standee', s);
ui.tracks.appendChild(ui.standees[s]);
}
@@ -139,11 +234,11 @@ function on_init() {
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)
+ e.setAttribute('data-card-id', '' + data.cards[c].id);
register_action(e, 'card', c);
}
- console.log('action_register',action_register[0]);
+ console.log('action_register', action_register[0]);
}
// @ts-ignore
@@ -166,67 +261,91 @@ function on_update() {
ui.cards[cardId].style.top = LAYOUT_CURRENT_EVENTS[i][1] + 'px';
}
- ui.hand.replaceChildren();
- for (let c of view.hand) ui.hand.appendChild(ui.cards[c]);
+ for (let bonus_id of Object.keys(view.bonuses)) {
+ ui.bonuses[bonus_id].setAttribute(
+ 'data-bonus-on',
+ view.bonuses[bonus_id] + 0
+ );
+ }
- for (let i = 0; i < view.tracks.length; i++) {
+ ui.hand.replaceChildren();
+ for (let c of view.hand) {
+ ui.cards[c].classList.remove('selected');
+ ui.hand.appendChild(ui.cards[c]);
+ if (c === view.selected_card) {
+ ui.cards[c].classList.add('selected');
+ }
+ };
+
+ for (let i = 0; i < view.tracks.length; ++i) {
// ui.tracks.appendChild(ui.standees[i]);
ui.standees[i].style.left = LAYOUT_TRACKS[i][view.tracks[i]][0] + 'px';
ui.standees[i].style.top = LAYOUT_TRACKS[i][view.tracks[i]][1] + 'px';
}
-
+
for (let frontId of Object.keys(view.fronts)) {
ui.frontValues[frontId].replaceChildren(view.fronts[frontId]);
}
+ for (let i = 0; i < view.medaillons.length; ++i) {
+ if (view.medaillons[i] !== null) {
+ ui.medaillons[i].style.left = LAYOUT_MEDAILLONS[i][0] + 'px';
+ ui.medaillons[i].style.top = LAYOUT_MEDAILLONS[i][1] + 'px';
+ }
+ }
+
for (let e of action_register)
e.classList.toggle('action', is_action(e.my_action, e.my_id));
+ action_button('Anarchist', 'Anarchist');
+ action_button('Communist', 'Communist');
+ action_button('Moderate', 'Moderate');
+ action_button('draw_card', 'Draw card');
+ action_button('play_for_ap', 'Play card for Action Points');
+ action_button('play_for_event', 'Play card for Event');
+ action_button('spend_hp', 'Spend Hero Points');
+ action_button('add_glory', 'Add Glory');
action_button('next', 'Next');
+ action_button('done', 'Done');
action_button('undo', 'Undo');
- action_button('add_glory', 'Add Glory');
}
// @ts-ignore
function on_log(text) {
- let p = document.createElement("div")
-
- if (text.match(/^>/)) {
- text = text.substring(1)
- p.className = 'i'
- }
-
- text = text.replace(/&/g, "&amp;")
- text = text.replace(/</g, "&lt;")
- text = text.replace(/>/g, "&gt;")
- // text = text.replace(/C(\d+)/g, sub_card_name)
- // text = text.replace(/S(\d+)/g, sub_space_name)
- // text = text.replace(/U(\d+)/g, sub_unit_name)
-
- // TODO dice icons
- // text = text.replace(/\bD\d\b/g, sub_icon)
-
- if (text.match(/^\.h1/)) {
- text = text.substring(4)
- p.className = 'h1'
- }
- else if (text.match(/^\.h2/)) {
- text = text.substring(4)
- p.className = 'h2'
- }
- else if (text.match(/^\.h3\.allies/)) {
- text = text.substring(10)
- p.className = 'h3 allies'
- }
- else if (text.match(/^\.h3\.germans/)) {
- text = text.substring(11)
- p.className = 'h3 germans'
- }
- else if (text.match(/^\.h3/)) {
- text = text.substring(4)
- p.className = 'h3'
- }
-
- p.innerHTML = text
- return p
-} \ No newline at end of file
+ let p = document.createElement('div');
+
+ if (text.match(/^>/)) {
+ text = text.substring(1);
+ p.className = 'i';
+ }
+
+ text = text.replace(/&/g, '&amp;');
+ text = text.replace(/</g, '&lt;');
+ text = text.replace(/>/g, '&gt;');
+ // text = text.replace(/C(\d+)/g, sub_card_name)
+ // text = text.replace(/S(\d+)/g, sub_space_name)
+ // text = text.replace(/U(\d+)/g, sub_unit_name)
+
+ // TODO dice icons
+ // text = text.replace(/\bD\d\b/g, sub_icon)
+
+ if (text.match(/^\.h1/)) {
+ text = text.substring(4);
+ p.className = 'h1';
+ } else if (text.match(/^\.h2/)) {
+ text = text.substring(4);
+ p.className = 'h2';
+ } else if (text.match(/^\.h3\.allies/)) {
+ text = text.substring(10);
+ p.className = 'h3 allies';
+ } else if (text.match(/^\.h3\.germans/)) {
+ text = text.substring(11);
+ p.className = 'h3 germans';
+ } else if (text.match(/^\.h3/)) {
+ text = text.substring(4);
+ p.className = 'h3';
+ }
+
+ p.innerHTML = text;
+ return p;
+}
diff --git a/rules.js b/rules.js
index 99af9ab..cf1b88f 100644
--- a/rules.js
+++ b/rules.js
@@ -1,29 +1,29 @@
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
-exports.roles = exports.scenarios = void 0;
+exports.roles = exports.scenarios = exports.MODERATE = exports.COMMUNIST = exports.ANARCHIST = 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';
+let game = {};
+var view = {};
+exports.ANARCHIST = 'Anarchist';
+exports.COMMUNIST = 'Communist';
+exports.MODERATE = 'Moderate';
const ANARCHISTS_ID = 'a';
const COMMUNISTS_ID = 'c';
const MODERATES_ID = 'm';
const role_ids = [ANARCHISTS_ID, COMMUNISTS_ID, MODERATES_ID];
const faction_player_map = {
- [ANARCHISTS_ID]: ANARCHIST,
- [COMMUNISTS_ID]: COMMUNIST,
- [MODERATES_ID]: MODERATE,
+ [ANARCHISTS_ID]: exports.ANARCHIST,
+ [COMMUNISTS_ID]: exports.COMMUNIST,
+ [MODERATES_ID]: exports.MODERATE,
};
const player_faction_map = {
- [ANARCHIST]: ANARCHISTS_ID,
- [COMMUNIST]: COMMUNISTS_ID,
- [MODERATE]: MODERATES_ID,
+ [exports.ANARCHIST]: ANARCHISTS_ID,
+ [exports.COMMUNIST]: COMMUNISTS_ID,
+ [exports.MODERATE]: MODERATES_ID,
};
const front_names = {
a: 'the Aragon Front',
@@ -40,19 +40,19 @@ const track_names = {
[data_1.SOVIET_SUPPORT]: 'Soviet Support',
[data_1.FOREIGN_AID]: 'Foreign Aid',
};
-const { cards,
-// fronts
- } = data_1.default;
+const { cards, } = data_1.default;
const faction_cards = {
[ANARCHISTS_ID]: make_list(37, 54),
[COMMUNISTS_ID]: make_list(19, 36),
[MODERATES_ID]: make_list(1, 18),
};
+const medaillons = make_list(0, 8);
+console.log('medaillons', medaillons);
const fascist_decks = {
1: make_list(55, 62),
};
exports.scenarios = ['Standard'];
-exports.roles = [ANARCHIST, COMMUNIST, MODERATE];
+exports.roles = [exports.ANARCHIST, exports.COMMUNIST, exports.MODERATE];
function gen_action(action, argument) {
if (argument === undefined) {
view.actions[action] = 1;
@@ -66,15 +66,6 @@ function gen_action(action, argument) {
function gen_action_card(card_id) {
gen_action('card', card_id);
}
-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);
-// }
function action(state, player, action, arg) {
console.log('action', state, player, action, arg);
game = state;
@@ -87,7 +78,6 @@ function action(state, player, action, arg) {
throw new Error('Invalid action: ' + action);
return game;
}
-// #region ENGINE
const leaf_node = 'l';
const seq_node = 's';
const function_node = 'f';
@@ -109,9 +99,10 @@ function setup_bag_of_glory() {
}
function setup_choose_card() {
console.log('setup_choose_card');
- game.engine = exports.roles.map((role) => ({
+ const player_order = get_player_order();
+ game.engine = player_order.map((faction_id) => ({
t: leaf_node,
- p: player_faction_map[role],
+ p: faction_id,
s: 'choose_card',
}));
game.engine.push({
@@ -122,11 +113,8 @@ function setup_choose_card() {
}
function setup_player_turn() {
console.log('setup_player_turn');
- // TODO: reverse order in second round
- const first = game.initiative;
- const second = get_next_faction(first);
- const third = get_next_faction(second);
- game.engine = [first, second, third].map((faction_id) => ({
+ const player_order = get_player_order();
+ game.engine = player_order.map((faction_id) => ({
t: seq_node,
c: [
{
@@ -154,16 +142,13 @@ const engine_functions = {
setup_player_turn,
resolve_fascist_test,
};
-function get_active_node(engine) {
+function get_active(engine) {
for (let i of engine) {
- if (i.t === leaf_node && i.r !== resolved) {
- return i;
- }
- if (i.t === function_node && i.r !== resolved) {
- return i;
+ if ((i.t === leaf_node || i.t === function_node) && i.r !== resolved) {
+ return { parent: engine, node: i };
}
if (i.t === seq_node) {
- const next_child = get_active_node(i.c);
+ const next_child = get_active(i.c);
if (next_child !== null) {
return next_child;
}
@@ -171,6 +156,10 @@ function get_active_node(engine) {
}
return null;
}
+function get_active_node(engine) {
+ const a = get_active(engine);
+ return a === null ? null : a.node;
+}
function get_active_node_args() {
const node = get_active_node(game.engine);
if (node.t === leaf_node) {
@@ -178,6 +167,23 @@ function get_active_node_args() {
}
return null;
}
+function insert_before_or_after_active_node(node, position, engine = game.engine) {
+ const a = get_active(engine);
+ if (a === null) {
+ return;
+ }
+ const i = a.parent.indexOf(a.node);
+ console.log('insert_before_active_node', i);
+ if (i >= 0) {
+ array_insert(a.parent, i + (position == 'after' ? 1 : 0), node);
+ }
+}
+function insert_after_active_node(node, engine = game.engine) {
+ insert_before_or_after_active_node(node, 'after', engine);
+}
+function insert_before_active_node(node, engine = game.engine) {
+ insert_before_or_after_active_node(node, 'before', engine);
+}
function next() {
console.log('next');
const node = get_active_node(game.engine);
@@ -217,9 +223,12 @@ function game_view(state, player) {
prompt: null,
location: game.location,
selected: game.selected,
+ bonuses: game.bonuses,
current_events: game.current_events,
fronts: game.fronts,
hand: game.hands[faction_id],
+ medaillons: game.medaillons,
+ selected_card: game.cards_in_play[faction_id],
tracks: game.tracks,
};
if (player !== game.active) {
@@ -236,19 +245,17 @@ function game_view(state, player) {
}
return view;
}
-// #endregion
-// #region SETUP
function setup(seed, _scenario, _options) {
- // game.seed = seed;
game = {
seed: seed,
state: null,
- active: ANARCHIST,
+ active: exports.ANARCHIST,
bag_of_glory: {
[ANARCHISTS_ID]: 1,
[COMMUNISTS_ID]: 1,
[MODERATES_ID]: 1,
},
+ blank_markers: [[], [], [], [], []],
bonuses: [data_1.ON, data_1.ON],
current_events: [],
engine: [],
@@ -267,6 +274,7 @@ function setup(seed, _scenario, _options) {
[ANARCHISTS_ID]: 2,
[COMMUNISTS_ID]: 2,
[MODERATES_ID]: 0,
+ pool: 14,
},
cards_in_play: {
[ANARCHISTS_ID]: null,
@@ -274,6 +282,13 @@ function setup(seed, _scenario, _options) {
[MODERATES_ID]: null,
},
initiative: MODERATES_ID,
+ medaillons: [
+ draw_item(medaillons),
+ draw_item(medaillons),
+ draw_item(medaillons),
+ draw_item(medaillons),
+ draw_item(medaillons),
+ ],
tableaus: {
[ANARCHISTS_ID]: [],
[COMMUNISTS_ID]: [],
@@ -298,7 +313,6 @@ function draw_hand_cards() {
}
});
}
-// #endregion
function start_year() {
log_h1('Year ' + game.year);
draw_hand_cards();
@@ -322,13 +336,7 @@ function start_turn() {
f: 'setup_choose_card',
});
next();
- // game.state = 'resolve_event';
- // game.active = faction_player_map[game.initiative];
- // game.state_data = {
- // current_effect: 0,
- // };
}
-// region STATES
states.add_glory = {
inactive: 'add tokens to the Bag of Glory',
prompt() {
@@ -369,7 +377,13 @@ states.resolve_event = {
else if (effect.type === 'track') {
gen_action('standee', effect.target);
}
- // for (let p = 0; p < 5; ++p) gen_action('standee', p);
+ else if (effect.type === 'hero_points' &&
+ effect.target === data_1.PLAYER_WITH_MOST_HERO_POINTS) {
+ const factions = get_factions_with_most_hero_poins();
+ for (let faction_id of factions) {
+ gen_action(get_player(faction_id));
+ }
+ }
},
front(f) {
const card = get_current_event();
@@ -385,6 +399,18 @@ states.resolve_event = {
log_h3(`${track_names[effect.target]} decreased by ${Math.abs(value)}`);
resolve_active_and_proceed();
},
+ Anarchist() {
+ lose_hero_point(ANARCHISTS_ID, 1);
+ resolve_active_and_proceed();
+ },
+ Communist() {
+ lose_hero_point(ANARCHISTS_ID, 1);
+ resolve_active_and_proceed();
+ },
+ Moderate() {
+ lose_hero_point(ANARCHISTS_ID, 1);
+ resolve_active_and_proceed();
+ },
};
states.choose_card = {
inactive: 'choose a card',
@@ -406,8 +432,41 @@ states.choose_card = {
states.player_turn = {
inactive: 'play their turn',
prompt() {
- view.prompt = 'Play your card or spend Hero points';
- gen_action_card(game.cards_in_play[player_faction_map[game.active]]);
+ const faction_id = get_faction_id(game.active);
+ const hero_points = game.hero_points[faction_id];
+ view.prompt =
+ hero_points === 0
+ ? 'Play your card'
+ : 'Play your card or spend Hero points';
+ if (game.cards_in_play[faction_id] !== null) {
+ gen_action('play_for_ap');
+ gen_action('play_for_event');
+ }
+ if (hero_points > 0) {
+ gen_action('spend_hp');
+ }
+ },
+ play_for_ap() {
+ const faction_id = get_faction_id(game.active);
+ const card = game.cards_in_play[faction_id];
+ log_h3(`${game.active} plays ${cards[card].title} for the Action Points`);
+ resolve_active_and_proceed();
+ },
+ play_for_event() {
+ const faction_id = get_faction_id(game.active);
+ const card = game.cards_in_play[faction_id];
+ log_h3(`${game.active} plays ${cards[card].title} for the Event`);
+ game.cards_in_play[faction_id] = null;
+ game.tableaus[faction_id].push(card);
+ resolve_active_and_proceed();
+ },
+ spend_hp() {
+ insert_before_active_node({
+ t: leaf_node,
+ p: get_faction_id(game.active),
+ s: 'spend_hero_points',
+ });
+ next();
},
card(c) {
const faction = get_active_faction();
@@ -425,22 +484,24 @@ states.player_turn = {
resolve_active_and_proceed();
},
};
-// 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
-// // },
-// };
-// #endrregion
+states.spend_hero_points = {
+ inactive: 'spend Hero points',
+ prompt() {
+ view.prompt = 'Spend your Hero points';
+ gen_action('done');
+ gen_action('draw_card');
+ },
+ done() {
+ resolve_active_and_proceed();
+ },
+ draw_card() {
+ game.hero_points[get_active_faction_id()]--;
+ log(`${game.active} draws a card`);
+ if (game.hero_points[get_active_faction_id()] === 0) {
+ resolve_active_and_proceed();
+ }
+ },
+};
function pop_undo() {
const save_log = game.log;
const save_undo = game.undo;
@@ -449,9 +510,7 @@ function pop_undo() {
game.log = save_log;
game.undo = save_undo;
}
-// #region GAME FUNCTIONS
function end_of_turn() {
- // REMOVE playre tplems from the Fronts;
log_h2('End of turn');
if (game.turn === 4) {
end_of_year();
@@ -467,47 +526,34 @@ function resolve_fascist_test() {
log_h2('Fascist test is resolved');
next();
}
-// #endregion
-// #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 move_track(track, change) { }
function draw_card(deck) {
- console.log('draw_card_deck', deck);
clear_undo();
let i = random(deck.length);
- console.log('random ', i);
let c = deck[i];
- console.log('draw_card_id', c);
set_delete(deck, c);
return c;
}
-// #endregion
-// #region EVENTS
+function draw_item(ids) {
+ let i = random(ids.length);
+ let r = ids[i];
+ set_delete(ids, r);
+ return r;
+}
+function lose_hero_point(faction, value) {
+ const points_lost = Math.min(game.hero_points[faction], value);
+ game.hero_points.pool += points_lost;
+ game.hero_points[faction] -= points_lost;
+ if (points_lost === 1) {
+ log(`${get_player(faction)} loses 1 Hero Point`);
+ }
+ else {
+ log(`${get_player(faction)} loses ${points_lost} Hero Points`);
+ }
+}
function get_current_event() {
return cards[game.current_events[game.current_events.length - 1]];
}
-// function get_front_name(frontId: string) {
-// switch (frontId) {
-// case 'a':
-// return 'the Aragon Front';
-// case 'm':
-// return 'the Madrid Front';
-// case 'n':
-// return 'the Nothern Front';
-// case 's':
-// return 'the Southern Front';
-// case 'd':
-// return 'the Front closest to Defeat';
-// case 'v':
-// return 'the Front closest to Victory';
-// default:
-// return '';
-// }
-// }
function get_event_prompt(effect) {
let prompt = '';
switch (effect.type) {
@@ -516,52 +562,17 @@ function get_event_prompt(effect) {
case 'bonus':
break;
case 'hero_points':
- break;
+ return 'Select player with most Hero points';
case 'track':
return 'Decrease ' + track_names[effect.target];
}
return prompt;
}
-// 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() {
-// // log_h1('Year ' + game.year);
-// 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 FRONTS
function get_fronts_closest_to(target) {
const values = Object.values(game.fronts);
const targetValue = target === 'd' ? Math.min(...values) : Math.max(...values);
return Object.keys(game.fronts).filter((frontId) => game.fronts[frontId] === targetValue);
}
-// #endregion
-// #region LOGGING
function log_br() {
if (game.log.length > 0 && game.log[game.log.length - 1] !== '')
game.log.push('');
@@ -569,18 +580,6 @@ function log_br() {
function log(msg) {
game.log.push(msg);
}
-// function logevent(cap: Card) {
-// game.log.push(`E${cap}.`)
-// }
-// function logcap(cap: Card) {
-// game.log.push(`C${cap}.`)
-// }
-// function logi(msg: string) {
-// game.log.push('>' + msg);
-// }
-// function logii(msg: string) {
-// game.log.push('>>' + msg);
-// }
function log_h1(msg) {
log_br();
log('.h1 ' + msg);
@@ -591,26 +590,10 @@ function log_h2(msg) {
log('.h2 ' + msg);
log_br();
}
-// function log_h2_active(msg: string) {
-// log_br();
-// log('.h2 ' + msg);
-// log_br();
-// }
-// function log_h2_common(msg: string) {
-// log_br();
-// log('.h2 ' + msg);
-// log_br();
-// }
function log_h3(msg) {
log_br();
log('.h3 ' + msg);
}
-// function log_h4(msg: string) {
-// log_br();
-// log('.h4 ' + msg);
-// }
-// #endregion LOGGING
-// #region UTILITY
function clear_undo() {
console.log('game clear undo', game?.undo);
if (game?.undo && game.undo.length > 0)
@@ -619,13 +602,50 @@ function clear_undo() {
function get_active_faction() {
return player_faction_map[game.active];
}
+function get_active_faction_id() {
+ return player_faction_map[game.active];
+}
+function get_faction_id(player) {
+ return player_faction_map[player];
+}
+function get_player(faction_id) {
+ return faction_player_map[faction_id];
+}
+function get_player_order(first_player = game.initiative) {
+ const order = [];
+ let faction = first_player;
+ for (let i = 0; i < 3; ++i) {
+ order.push(faction);
+ faction =
+ game.year === 2
+ ? get_previous_faction(faction)
+ : get_next_faction(faction);
+ }
+ return order;
+}
+function get_previous_faction(faction_id) {
+ const index = role_ids.indexOf(faction_id);
+ if (index === 0) {
+ return role_ids[2];
+ }
+ return role_ids[index - 1];
+}
function get_next_faction(faction_id) {
const index = role_ids.indexOf(faction_id);
- let next_index = index + 1;
- if (next_index === role_ids.length) {
- next_index = 0;
+ if (index === 2) {
+ return role_ids[0];
}
- return role_ids[next_index];
+ return role_ids[index + 1];
+}
+function get_factions_with_most_hero_poins() {
+ const most_hero_points = Math.max(...Object.values(game.hero_points));
+ const faction_ids = [];
+ Object.entries(game.hero_points).forEach(([faction, hero_points]) => {
+ if (hero_points === most_hero_points) {
+ faction_ids.push(faction);
+ }
+ });
+ return faction_ids;
}
function make_list(first, last) {
let list = [];
@@ -634,9 +654,6 @@ function make_list(first, last) {
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) {
@@ -655,11 +672,14 @@ function set_delete(set, item) {
}
}
}
-// #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;
}
+function array_insert(array, index, item) {
+ for (let i = array.length; i > index; --i)
+ array[i] = array[i - 1];
+ array[index] = item;
+}
diff --git a/rules.ts b/rules.ts
index 49f4687..0348814 100644
--- a/rules.ts
+++ b/rules.ts
@@ -1,5 +1,17 @@
'use strict';
+import {
+ CardId,
+ EngineNode,
+ FactionId,
+ FunctionNode,
+ Game,
+ LeafNode,
+ Player,
+ States,
+ View,
+} from './types';
+
import data, {
// LIBERTY,
// COLLECTIVIZATION,
@@ -17,89 +29,23 @@ import data, {
GOVERNMENT,
SOVIET_SUPPORT,
FOREIGN_AID,
+ PLAYER_WITH_MOST_HERO_POINTS,
// 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: string[];
- undo: Game[];
- turn: number;
- year: number;
- active: Player | null;
- state: string | null;
- bag_of_glory: Record<FactionId, number>;
- bonuses: number[];
- cards_in_play: Record<FactionId, CardId>;
- current_events: CardId[];
- engine: EngineNode[];
- fronts: {
- a: number;
- m: number;
- n: number;
- s: number;
- };
- hands: Record<FactionId, CardId[]>;
- hero_points: Record<FactionId, number>;
- initiative: FactionId;
- tableaus: Record<FactionId, CardId[]>;
- tracks: number[];
-
- result?: string;
- victory?: string;
-
- location?: string;
- selected?: string;
-
- state_data: any;
- // played_card: CardId
-
- // turn: Turn
-}
-
-export interface View {
- engine: Game['engine'];
- log: number | string[];
- active?: string | null;
- prompt: string | null;
- actions?: any;
- victory?: string;
- location?: string;
- selected?: string;
- fronts: Game['fronts'];
- current_events: CardId[];
- hand: CardId[];
- tracks: number[];
-}
-
// 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;
+export const ANARCHIST = 'Anarchist' as Player;
+export const COMMUNIST = 'Communist' as Player;
+export const MODERATE = 'Moderate' as Player;
const ANARCHISTS_ID = 'a' as FactionId;
const COMMUNISTS_ID = 'c' as FactionId;
@@ -147,6 +93,10 @@ const faction_cards = {
[MODERATES_ID]: make_list(1, 18) as CardId[],
};
+const medaillons = make_list(0, 8) as number[];
+
+console.log('medaillons', medaillons);
+
const fascist_decks = {
1: make_list(55, 62),
};
@@ -168,9 +118,9 @@ function gen_action_card(card_id: CardId) {
gen_action('card', card_id);
}
-function gen_action_space(space) {
- gen_action('space', space);
-}
+// function gen_action_space(space) {
+// gen_action('space', space);
+// }
// function gen_action_piece(piece) {
// gen_action('piece', piece);
@@ -220,9 +170,10 @@ function setup_bag_of_glory() {
function setup_choose_card() {
console.log('setup_choose_card');
- game.engine = roles.map((role) => ({
+ const player_order = get_player_order();
+ game.engine = player_order.map((faction_id) => ({
t: leaf_node,
- p: player_faction_map[role],
+ p: faction_id,
s: 'choose_card',
}));
game.engine.push({
@@ -234,11 +185,8 @@ function setup_choose_card() {
function setup_player_turn() {
console.log('setup_player_turn');
- // TODO: reverse order in second round
- const first = game.initiative;
- const second = get_next_faction(first);
- const third = get_next_faction(second);
- game.engine = [first, second, third].map((faction_id) => ({
+ const player_order = get_player_order();
+ game.engine = player_order.map((faction_id) => ({
t: seq_node,
c: [
{
@@ -268,38 +216,15 @@ const engine_functions: Record<string, Function> = {
resolve_fascist_test,
};
-type EngineNode = FunctionNode | LeafNode | SeqNode;
-
-interface FunctionNode {
- t: 'f';
- f: string; // function to be triggered
- a?: any; // args
- r?: 0 | 1; // 1 if resolved
-}
-
-interface SeqNode {
- t: 's'; // Type
- c: EngineNode[];
-}
-
-interface LeafNode {
- t: 'l';
- s: string; // State
- p: FactionId; // Player
- a?: any; // args
- r?: 0 | 1; // 1 if resolved
-}
-
-function get_active_node(engine: EngineNode[]): FunctionNode | LeafNode | null {
+function get_active(
+ engine: EngineNode[]
+): { parent: EngineNode[]; node: FunctionNode | LeafNode } | null {
for (let i of engine) {
- if (i.t === leaf_node && i.r !== resolved) {
- return i;
- }
- if (i.t === function_node && i.r !== resolved) {
- return i;
+ if ((i.t === leaf_node || i.t === function_node) && i.r !== resolved) {
+ return { parent: engine, node: i };
}
if (i.t === seq_node) {
- const next_child = get_active_node(i.c);
+ const next_child = get_active(i.c);
if (next_child !== null) {
return next_child;
}
@@ -308,6 +233,41 @@ function get_active_node(engine: EngineNode[]): FunctionNode | LeafNode | null {
return null;
}
+// function get_active_node_parent(engine: EngineNode[]): EngineNode[] | null {
+// for (let i of engine) {
+// if ((i.t === leaf_node || i.t === function_node) && i.r !== resolved) {
+// return engine;
+// }
+// if (i.t === seq_node) {
+// const next_child = get_active_node_parent(i.c);
+// if (next_child !== null) {
+// return next_child;
+// }
+// }
+// }
+// return null;
+// }
+
+function get_active_node(engine: EngineNode[]): FunctionNode | LeafNode | null {
+ const a = get_active(engine);
+ return a === null ? null : a.node;
+ // for (let i of engine) {
+ // if ((i.t === leaf_node || i.t === function_node) && i.r !== resolved) {
+ // return i;
+ // }
+ // // if (i.t === function_node && i.r !== resolved) {
+ // // return i;
+ // // }
+ // if (i.t === seq_node) {
+ // const next_child = get_active_node(i.c);
+ // if (next_child !== null) {
+ // return next_child;
+ // }
+ // }
+ // }
+ // return null;
+}
+
function get_active_node_args(): any {
const node = get_active_node(game.engine);
if (node.t === leaf_node) {
@@ -316,6 +276,36 @@ function get_active_node_args(): any {
return null;
}
+function insert_before_or_after_active_node(
+ node: EngineNode,
+ position: 'before' | 'after',
+ engine: EngineNode[] = game.engine
+) {
+ const a = get_active(engine);
+ if (a === null) {
+ return;
+ }
+ const i = a.parent.indexOf(a.node);
+ console.log('insert_before_active_node', i);
+ if (i >= 0) {
+ array_insert(a.parent, i + (position == 'after' ? 1 : 0), node);
+ }
+}
+
+function insert_after_active_node(
+ node: EngineNode,
+ engine: EngineNode[] = game.engine
+) {
+ insert_before_or_after_active_node(node, 'after', engine);
+}
+
+function insert_before_active_node(
+ node: EngineNode,
+ engine: EngineNode[] = game.engine
+) {
+ insert_before_or_after_active_node(node, 'before', engine);
+}
+
function next() {
console.log('next');
const node = get_active_node(game.engine);
@@ -364,9 +354,13 @@ function game_view(state: Game, player: Player) {
prompt: null,
location: game.location,
selected: game.selected,
+
+ bonuses: game.bonuses,
current_events: game.current_events,
fronts: game.fronts,
hand: game.hands[faction_id],
+ medaillons: game.medaillons,
+ selected_card: game.cards_in_play[faction_id],
tracks: game.tracks,
};
@@ -398,6 +392,7 @@ export function setup(seed: number, _scenario: string, _options: unknown) {
[COMMUNISTS_ID]: 1,
[MODERATES_ID]: 1,
},
+ blank_markers: [[], [], [], [], []],
bonuses: [ON, ON],
current_events: [],
engine: [],
@@ -416,6 +411,7 @@ export function setup(seed: number, _scenario: string, _options: unknown) {
[ANARCHISTS_ID]: 2,
[COMMUNISTS_ID]: 2,
[MODERATES_ID]: 0,
+ pool: 14,
},
cards_in_play: {
[ANARCHISTS_ID]: null,
@@ -423,6 +419,13 @@ export function setup(seed: number, _scenario: string, _options: unknown) {
[MODERATES_ID]: null,
},
initiative: MODERATES_ID,
+ medaillons: [
+ draw_item(medaillons),
+ draw_item(medaillons),
+ draw_item(medaillons),
+ draw_item(medaillons),
+ draw_item(medaillons),
+ ],
tableaus: {
[ANARCHISTS_ID]: [],
[COMMUNISTS_ID]: [],
@@ -528,6 +531,14 @@ states.resolve_event = {
gen_action('front', effect.target);
} else if (effect.type === 'track') {
gen_action('standee', effect.target);
+ } else if (
+ effect.type === 'hero_points' &&
+ effect.target === PLAYER_WITH_MOST_HERO_POINTS
+ ) {
+ const factions = get_factions_with_most_hero_poins();
+ for (let faction_id of factions) {
+ gen_action(get_player(faction_id));
+ }
}
// for (let p = 0; p < 5; ++p) gen_action('standee', p);
},
@@ -545,6 +556,18 @@ states.resolve_event = {
log_h3(`${track_names[effect.target]} decreased by ${Math.abs(value)}`);
resolve_active_and_proceed();
},
+ Anarchist() {
+ lose_hero_point(ANARCHISTS_ID, 1);
+ resolve_active_and_proceed();
+ },
+ Communist() {
+ lose_hero_point(ANARCHISTS_ID, 1);
+ resolve_active_and_proceed();
+ },
+ Moderate() {
+ lose_hero_point(ANARCHISTS_ID, 1);
+ resolve_active_and_proceed();
+ },
};
states.choose_card = {
@@ -567,8 +590,45 @@ states.choose_card = {
states.player_turn = {
inactive: 'play their turn',
prompt() {
- view.prompt = 'Play your card or spend Hero points';
- gen_action_card(game.cards_in_play[player_faction_map[game.active]]);
+ const faction_id = get_faction_id(game.active);
+ const hero_points = game.hero_points[faction_id];
+ view.prompt =
+ hero_points === 0
+ ? 'Play your card'
+ : 'Play your card or spend Hero points';
+
+ // const card = game.cards_in_play[faction_id];
+ if (game.cards_in_play[faction_id] !== null) {
+ // gen_action_card();
+ gen_action('play_for_ap');
+ gen_action('play_for_event');
+ }
+ if (hero_points > 0) {
+ gen_action('spend_hp');
+ }
+ },
+ play_for_ap() {
+ const faction_id = get_faction_id(game.active);
+ const card = game.cards_in_play[faction_id];
+ log_h3(`${game.active} plays ${cards[card].title} for the Action Points`);
+ resolve_active_and_proceed();
+ },
+ play_for_event() {
+ const faction_id = get_faction_id(game.active);
+ const card = game.cards_in_play[faction_id];
+ log_h3(`${game.active} plays ${cards[card].title} for the Event`);
+ game.cards_in_play[faction_id] = null;
+ game.tableaus[faction_id].push(card);
+ resolve_active_and_proceed();
+ },
+ spend_hp() {
+ insert_before_active_node({
+ t: leaf_node,
+ p: get_faction_id(game.active),
+ s: 'spend_hero_points',
+ });
+ // insert spend hero points node before current node
+ next();
},
card(c: CardId) {
const faction = get_active_faction();
@@ -587,6 +647,27 @@ states.player_turn = {
},
};
+states.spend_hero_points = {
+ inactive: 'spend Hero points',
+ prompt() {
+ view.prompt = 'Spend your Hero points';
+ gen_action('done');
+
+ gen_action('draw_card');
+ },
+ done() {
+ resolve_active_and_proceed();
+ },
+ draw_card() {
+ game.hero_points[get_active_faction_id()]--;
+ log(`${game.active} draws a card`);
+ // TODO: Draw card
+ if (game.hero_points[get_active_faction_id()] === 0) {
+ resolve_active_and_proceed();
+ }
+ },
+};
+
// states.move_to = {
// inactive: 'move',
// prompt() {
@@ -635,6 +716,8 @@ function resolve_fascist_test() {
next();
}
+function move_track(track: number, change: number) {}
+
// #endregion
// #region CARDS
@@ -648,16 +731,31 @@ function resolve_fascist_test() {
// }
function draw_card(deck: CardId[]): CardId {
- console.log('draw_card_deck', deck);
clear_undo();
let i = random(deck.length);
- console.log('random ', i);
let c = deck[i] as CardId;
- console.log('draw_card_id', c);
set_delete(deck, c);
return c;
}
+function draw_item(ids: number[]): number {
+ let i = random(ids.length);
+ let r = ids[i] as CardId;
+ set_delete(ids, r);
+ return r;
+}
+
+function lose_hero_point(faction: FactionId, value: number) {
+ const points_lost = Math.min(game.hero_points[faction], value);
+ game.hero_points.pool += points_lost;
+ game.hero_points[faction] -= points_lost;
+ if (points_lost === 1) {
+ log(`${get_player(faction)} loses 1 Hero Point`);
+ } else {
+ log(`${get_player(faction)} loses ${points_lost} Hero Points`);
+ }
+}
+
// #endregion
// #region EVENTS
@@ -695,7 +793,7 @@ function get_event_prompt(effect: CardEffect) {
case 'bonus':
break;
case 'hero_points':
- break;
+ return 'Select player with most Hero points';
case 'track':
return 'Decrease ' + track_names[effect.target];
}
@@ -826,13 +924,57 @@ function get_active_faction(): FactionId {
return player_faction_map[game.active];
}
+function get_active_faction_id(): FactionId {
+ return player_faction_map[game.active];
+}
+
+function get_faction_id(player: Player): FactionId {
+ return player_faction_map[player];
+}
+
+function get_player(faction_id: FactionId) {
+ return faction_player_map[faction_id];
+}
+
+function get_player_order(first_player = game.initiative): FactionId[] {
+ const order = [];
+ let faction = first_player;
+ for (let i = 0; i < 3; ++i) {
+ order.push(faction);
+ faction =
+ game.year === 2
+ ? get_previous_faction(faction)
+ : get_next_faction(faction);
+ }
+
+ return order;
+}
+
+function get_previous_faction(faction_id: FactionId): FactionId {
+ const index = role_ids.indexOf(faction_id);
+ if (index === 0) {
+ return role_ids[2];
+ }
+ return role_ids[index - 1];
+}
+
function get_next_faction(faction_id: FactionId): FactionId {
const index = role_ids.indexOf(faction_id);
- let next_index = index + 1;
- if (next_index === role_ids.length) {
- next_index = 0;
+ if (index === 2) {
+ return role_ids[0];
}
- return role_ids[next_index];
+ return role_ids[index + 1];
+}
+
+function get_factions_with_most_hero_poins() {
+ const most_hero_points = Math.max(...Object.values(game.hero_points));
+ const faction_ids = [];
+ Object.entries(game.hero_points).forEach(([faction, hero_points]) => {
+ if (hero_points === most_hero_points) {
+ faction_ids.push(faction);
+ }
+ });
+ return faction_ids;
}
function make_list(first: number, last: number) {
@@ -873,10 +1015,10 @@ function array_remove<T>(array: T[], index: number) {
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_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;
diff --git a/tsconfig.json b/tsconfig.json
index 6517e5c..fb00fed 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -21,6 +21,7 @@
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
- "noPropertyAccessFromIndexSignature": false
+ "noPropertyAccessFromIndexSignature": false,
+ "removeComments": true
}
}
diff --git a/types.d.ts b/types.d.ts
new file mode 100644
index 0000000..4ba17f7
--- /dev/null
+++ b/types.d.ts
@@ -0,0 +1,95 @@
+declare const brand: unique symbol;
+
+// branded typing
+export type Brand<T, TBrand extends string> = T & {
+ [brand]: TBrand;
+};
+
+export type Player = Brand<string, 'Player'>;
+export type CardId = Brand<number, 'CardId'>;
+export type FactionId = Brand<string, 'FactionId'>;
+
+export interface Game {
+ [index: number]: any;
+ seed: number;
+ log: string[];
+ undo: Game[];
+ turn: number;
+ year: number;
+ active: Player | null;
+ state: string | null;
+ bag_of_glory: Record<FactionId, number>;
+ blank_markers: number[][];
+ bonuses: number[];
+ cards_in_play: Record<FactionId, CardId>;
+ current_events: CardId[];
+ engine: EngineNode[];
+ fronts: {
+ a: number;
+ m: number;
+ n: number;
+ s: number;
+ };
+ hands: Record<FactionId, CardId[]>;
+ hero_points: Record<FactionId | 'pool', number>;
+ initiative: FactionId;
+ medaillons: Array<number | null>;
+ tableaus: Record<FactionId, CardId[]>;
+ tracks: number[];
+
+ result?: string;
+ victory?: string;
+
+ location?: string;
+ selected?: string;
+
+ state_data: any;
+ // played_card: CardId
+
+ // turn: Turn
+}
+
+export interface View {
+ engine: Game['engine'];
+ log: number | string[];
+ active?: string | null;
+ prompt: string | null;
+ actions?: any;
+ victory?: string;
+ location?: string;
+ selected?: string;
+
+ selected_card: CardId | null;
+ bonuses: Game['bonuses'];
+ current_events: CardId[];
+ fronts: Game['fronts'];
+ hand: CardId[];
+ medaillons: Game['medaillons'];
+ tracks: number[];
+}
+
+export type States = {
+ [key: string]: any;
+};
+
+export type EngineNode = FunctionNode | LeafNode | SeqNode;
+
+export interface FunctionNode {
+ t: 'f';
+ f: string; // function to be triggered
+ a?: any; // args
+ r?: 0 | 1; // 1 if resolved
+}
+
+export interface SeqNode {
+ t: 's'; // Type
+ c: EngineNode[];
+}
+
+export interface LeafNode {
+ t: 'l';
+ s: string; // State
+ p: FactionId; // Player
+ a?: any; // args
+ r?: 0 | 1; // 1 if resolved
+} \ No newline at end of file