From f05a9531508822f5dfb51105334444b53fe11b3e Mon Sep 17 00:00:00 2001 From: Frans Bongers Date: Sat, 30 Nov 2024 22:18:21 +0100 Subject: ui setup bonuses and medaillons --- data.js | 38 ++++ data.ts | 42 ++++ images/bonus/bonus_0_off.png | Bin 0 -> 103547 bytes images/bonus/bonus_0_on.png | Bin 0 -> 83460 bytes images/bonus/bonus_1_off.png | Bin 0 -> 95000 bytes images/bonus/bonus_1_on.png | Bin 0 -> 80885 bytes images/medaillons/archives.png | Bin 89231 -> 0 bytes images/medaillons/intelligence.png | Bin 80227 -> 0 bytes images/medaillons/medaillon_0.png | Bin 0 -> 83925 bytes images/medaillons/medaillon_1.png | Bin 0 -> 86793 bytes images/medaillons/medaillon_2.png | Bin 0 -> 82783 bytes images/medaillons/medaillon_3.png | Bin 0 -> 84146 bytes images/medaillons/medaillon_4.png | Bin 0 -> 90155 bytes images/medaillons/medaillon_5.png | Bin 0 -> 80227 bytes images/medaillons/medaillon_6.png | Bin 0 -> 90532 bytes images/medaillons/medaillon_7.png | Bin 0 -> 89231 bytes images/medaillons/medaillon_8.png | Bin 0 -> 84308 bytes images/medaillons/momentum.png | Bin 82783 -> 0 bytes images/medaillons/organization.png | Bin 84308 -> 0 bytes images/medaillons/propaganda.png | Bin 90155 -> 0 bytes images/medaillons/strategy.png | Bin 84146 -> 0 bytes images/medaillons/subterfuge.png | Bin 83925 -> 0 bytes images/medaillons/valor.png | Bin 86793 -> 0 bytes images/medaillons/volunteers.png | Bin 90532 -> 0 bytes images/morale_bonus_off.png | Bin 103547 -> 0 bytes images/morale_bonus_on.png | Bin 83460 -> 0 bytes images/teamwork_bonus_off.png | Bin 95000 -> 0 bytes images/teamwork_bonus_on.png | Bin 80885 -> 0 bytes land-and-freedom.css | 78 +++++++- land-and-freedom.scss | 61 +++++- play.js | 179 ++++++++++++----- play.ts | 255 +++++++++++++++++------- rules.js | 372 ++++++++++++++++++----------------- rules.ts | 388 +++++++++++++++++++++++++------------ tsconfig.json | 3 +- types.d.ts | 95 +++++++++ 36 files changed, 1085 insertions(+), 426 deletions(-) create mode 100644 images/bonus/bonus_0_off.png create mode 100644 images/bonus/bonus_0_on.png create mode 100644 images/bonus/bonus_1_off.png create mode 100644 images/bonus/bonus_1_on.png delete mode 100644 images/medaillons/archives.png delete mode 100644 images/medaillons/intelligence.png create mode 100644 images/medaillons/medaillon_0.png create mode 100644 images/medaillons/medaillon_1.png create mode 100644 images/medaillons/medaillon_2.png create mode 100644 images/medaillons/medaillon_3.png create mode 100644 images/medaillons/medaillon_4.png create mode 100644 images/medaillons/medaillon_5.png create mode 100644 images/medaillons/medaillon_6.png create mode 100644 images/medaillons/medaillon_7.png create mode 100644 images/medaillons/medaillon_8.png delete mode 100644 images/medaillons/momentum.png delete mode 100644 images/medaillons/organization.png delete mode 100644 images/medaillons/propaganda.png delete mode 100644 images/medaillons/strategy.png delete mode 100644 images/medaillons/subterfuge.png delete mode 100644 images/medaillons/valor.png delete mode 100644 images/medaillons/volunteers.png delete mode 100644 images/morale_bonus_off.png delete mode 100644 images/morale_bonus_on.png delete mode 100644 images/teamwork_bonus_off.png delete mode 100644 images/teamwork_bonus_on.png create mode 100644 types.d.ts 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/bonus/bonus_0_off.png b/images/bonus/bonus_0_off.png new file mode 100644 index 0000000..f68e463 Binary files /dev/null and b/images/bonus/bonus_0_off.png differ diff --git a/images/bonus/bonus_0_on.png b/images/bonus/bonus_0_on.png new file mode 100644 index 0000000..9db21c2 Binary files /dev/null and b/images/bonus/bonus_0_on.png differ diff --git a/images/bonus/bonus_1_off.png b/images/bonus/bonus_1_off.png new file mode 100644 index 0000000..29e95b1 Binary files /dev/null and b/images/bonus/bonus_1_off.png differ diff --git a/images/bonus/bonus_1_on.png b/images/bonus/bonus_1_on.png new file mode 100644 index 0000000..6b71aa9 Binary files /dev/null and b/images/bonus/bonus_1_on.png differ diff --git a/images/medaillons/archives.png b/images/medaillons/archives.png deleted file mode 100644 index acc4053..0000000 Binary files a/images/medaillons/archives.png and /dev/null differ diff --git a/images/medaillons/intelligence.png b/images/medaillons/intelligence.png deleted file mode 100644 index 4c27495..0000000 Binary files a/images/medaillons/intelligence.png and /dev/null differ diff --git a/images/medaillons/medaillon_0.png b/images/medaillons/medaillon_0.png new file mode 100644 index 0000000..acec979 Binary files /dev/null and b/images/medaillons/medaillon_0.png differ diff --git a/images/medaillons/medaillon_1.png b/images/medaillons/medaillon_1.png new file mode 100644 index 0000000..8ad2d04 Binary files /dev/null and b/images/medaillons/medaillon_1.png differ diff --git a/images/medaillons/medaillon_2.png b/images/medaillons/medaillon_2.png new file mode 100644 index 0000000..4200d41 Binary files /dev/null and b/images/medaillons/medaillon_2.png differ diff --git a/images/medaillons/medaillon_3.png b/images/medaillons/medaillon_3.png new file mode 100644 index 0000000..0733f78 Binary files /dev/null and b/images/medaillons/medaillon_3.png differ diff --git a/images/medaillons/medaillon_4.png b/images/medaillons/medaillon_4.png new file mode 100644 index 0000000..ad18b04 Binary files /dev/null and b/images/medaillons/medaillon_4.png differ diff --git a/images/medaillons/medaillon_5.png b/images/medaillons/medaillon_5.png new file mode 100644 index 0000000..4c27495 Binary files /dev/null and b/images/medaillons/medaillon_5.png differ diff --git a/images/medaillons/medaillon_6.png b/images/medaillons/medaillon_6.png new file mode 100644 index 0000000..ead86f3 Binary files /dev/null and b/images/medaillons/medaillon_6.png differ diff --git a/images/medaillons/medaillon_7.png b/images/medaillons/medaillon_7.png new file mode 100644 index 0000000..acc4053 Binary files /dev/null and b/images/medaillons/medaillon_7.png differ diff --git a/images/medaillons/medaillon_8.png b/images/medaillons/medaillon_8.png new file mode 100644 index 0000000..e228bb9 Binary files /dev/null and b/images/medaillons/medaillon_8.png differ diff --git a/images/medaillons/momentum.png b/images/medaillons/momentum.png deleted file mode 100644 index 4200d41..0000000 Binary files a/images/medaillons/momentum.png and /dev/null differ diff --git a/images/medaillons/organization.png b/images/medaillons/organization.png deleted file mode 100644 index e228bb9..0000000 Binary files a/images/medaillons/organization.png and /dev/null differ diff --git a/images/medaillons/propaganda.png b/images/medaillons/propaganda.png deleted file mode 100644 index ad18b04..0000000 Binary files a/images/medaillons/propaganda.png and /dev/null differ diff --git a/images/medaillons/strategy.png b/images/medaillons/strategy.png deleted file mode 100644 index 0733f78..0000000 Binary files a/images/medaillons/strategy.png and /dev/null differ diff --git a/images/medaillons/subterfuge.png b/images/medaillons/subterfuge.png deleted file mode 100644 index acec979..0000000 Binary files a/images/medaillons/subterfuge.png and /dev/null differ diff --git a/images/medaillons/valor.png b/images/medaillons/valor.png deleted file mode 100644 index 8ad2d04..0000000 Binary files a/images/medaillons/valor.png and /dev/null differ diff --git a/images/medaillons/volunteers.png b/images/medaillons/volunteers.png deleted file mode 100644 index ead86f3..0000000 Binary files a/images/medaillons/volunteers.png and /dev/null differ diff --git a/images/morale_bonus_off.png b/images/morale_bonus_off.png deleted file mode 100644 index f68e463..0000000 Binary files a/images/morale_bonus_off.png and /dev/null differ diff --git a/images/morale_bonus_on.png b/images/morale_bonus_on.png deleted file mode 100644 index 9db21c2..0000000 Binary files a/images/morale_bonus_on.png and /dev/null differ diff --git a/images/teamwork_bonus_off.png b/images/teamwork_bonus_off.png deleted file mode 100644 index 29e95b1..0000000 Binary files a/images/teamwork_bonus_off.png and /dev/null differ diff --git a/images/teamwork_bonus_on.png b/images/teamwork_bonus_on.png deleted file mode 100644 index 6b71aa9..0000000 Binary files a/images/teamwork_bonus_on.png and /dev/null 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, "&"); - text = text.replace(//g, ">"); - // 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, '&'); + text = text.replace(//g, '>'); 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, "&") - text = text.replace(//g, ">") - // 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, '&'); + text = text.replace(//g, '>'); + // 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 & { - [brand]: TBrand; -}; -export type CardId = Brand; -export type Player = Brand; -export type FactionId = Brand; - -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; - bonuses: number[]; - cards_in_play: Record; - current_events: CardId[]; - engine: EngineNode[]; - fronts: { - a: number; - m: number; - n: number; - s: number; - }; - hands: Record; - hero_points: Record; - initiative: FactionId; - tableaus: Record; - 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 = { 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(array: T[], index: number) { array.length = n - 1; } -// function array_insert(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(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(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 & { + [brand]: TBrand; +}; + +export type Player = Brand; +export type CardId = Brand; +export type FactionId = Brand; + +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; + blank_markers: number[][]; + bonuses: number[]; + cards_in_play: Record; + current_events: CardId[]; + engine: EngineNode[]; + fronts: { + a: number; + m: number; + n: number; + s: number; + }; + hands: Record; + hero_points: Record; + initiative: FactionId; + medaillons: Array; + tableaus: Record; + 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 -- cgit v1.2.3