diff options
-rw-r--r-- | play.html | 67 | ||||
-rw-r--r-- | play.js | 155 | ||||
-rw-r--r-- | rules.js | 567 |
3 files changed, 460 insertions, 329 deletions
@@ -40,19 +40,25 @@ body.Versailles header.your_turn { background-color: skyblue; } /* PANELS */ .panel_grid { - display: grid; - grid-template-columns: min-content 1fr; - max-width: 1500px; - margin: 18px auto; - gap: 18px; + display: flex; + flex-wrap: wrap; + margin: 20px auto; + justify-content: center; + gap: 20px; } -#hand_panel { - grid-column: 1 / 3; +#hand, #final, #discard, #objective, #set_aside { + min-height: 350px; } -#objective { - min-width: 250px; +#hand { min-width: 1060px } +#final { min-width: 250px } +#objective { min-width: 520px } +#set_aside { min-width: 790px } +#discard { min-width: 250px } + +#hand, #set_aside { + flex-grow: 1 } .panel { @@ -72,12 +78,11 @@ body.Versailles header.your_turn { background-color: skyblue; } display: flex; justify-content: start; flex-wrap: wrap; - padding: 18px; - gap: 18px; - min-height: 350px; + padding: 20px; + gap: 20px; } -body.Observer #final_panel { display: none } +body.Observer #set_aside_panel { display: none } /* CARDS */ @@ -91,19 +96,6 @@ body.Observer #final_panel { display: none } box-shadow: 1px 1px 5px rgba(0,0,0,0.5); } -.card_info { - padding: 12px 0; - border-bottom: 1px solid black; - background-color: gray; -} - -.card_info .card { - margin: 0 auto; - width: 125px; - height: 175px; - border-radius: 8px; -} - #tooltip { position: fixed; z-index: 100; @@ -143,6 +135,7 @@ body.Observer #final_panel { display: none } } .piece { + pointer-events: none; position: absolute; background-size: cover; background-repeat: no-repeat; @@ -153,6 +146,7 @@ body.Observer #final_panel { display: none } } .piece.action { + pointer-events: auto; filter: drop-shadow(0 -2px 0 white) drop-shadow(0 2px 0 white) @@ -164,6 +158,10 @@ body.Observer #final_panel { display: none } box-shadow: 0 0 0 3px white; } +.card.selected { + box-shadow: 0 0 0 3px yellow; +} + /* original size (72dpi?) */ .piece.cube { width: 28px; height: 30px; } .piece.disc { width: 40px; height: 32px; } @@ -404,7 +402,6 @@ body.Observer #final_panel { display: none } <div class="role_user">-</div> </div> </div> - <div class="card_info"><div id="discarded_card" class="card card_strategy_back"></div></div> </div> <div id="log"></div> </aside> @@ -431,14 +428,24 @@ body.Observer #final_panel { display: none } <div id="hand" class="panel_body"></div> </div> +<div id="final_panel" class="panel"> +<div id="final_header" class="panel_header">Final</div> +<div id="final" class="panel_body"></div> +</div> + +<div id="discard_panel" class="panel"> +<div id="discard_header" class="panel_header">Discard</div> +<div id="discard" class="panel_body"></div> +</div> + <div id="objective_panel" class="panel"> <div id="objective_header" class="panel_header">Objective</div> <div id="objective" class="panel_body"></div> </div> -<div id="final_panel" class="panel"> -<div id="final_header" class="panel_header">Set-aside cards</div> -<div id="final" class="panel_body"></div> +<div id="set_aside_panel" class="panel"> +<div id="set_aside_header" class="panel_header">Set-aside cards</div> +<div id="set_aside" class="panel_body"></div> </div> </div> @@ -157,6 +157,12 @@ const boxes = { "Prussian Collaboration 3": [306,330,157,39], } +function is_action(action) { + if (view.actions && view.actions[action]) + return true + return false +} + function is_card_action(action, card) { if (view.actions && view.actions[action] && view.actions[action].includes(card)) return true @@ -183,6 +189,27 @@ function on_focus_space(evt) { document.getElementById("status").textContent = evt.target.my_name } +function on_click_red_momentum(evt) { + if (evt.button === 0) { + if (send_action('red_momentum')) + evt.stopPropagation() + } +} + +function on_click_blue_momentum(evt) { + if (evt.button === 0) { + if (send_action('blue_momentum')) + evt.stopPropagation() + } +} + +function on_click_card(evt) { + if (evt.button === 0) { + if (send_action('card', evt.target.my_card)) + evt.stopPropagation() + } +} + function on_click_space(evt) { if (evt.button === 0) { if (send_action('space', evt.target.my_space)) @@ -207,6 +234,9 @@ function on_click_disc(evt) { function build_user_interface() { let elt + document.getElementById("red_momentum").addEventListener("mousedown", on_click_red_momentum) + document.getElementById("blue_momentum").addEventListener("mousedown", on_click_blue_momentum) + for (let c = 1; c <= 41 + 12; ++c) { elt = ui.cards[c] = document.createElement("div") elt.className = `card card_${c}` @@ -336,11 +366,6 @@ function on_log(text) { } function on_update() { - if (view.discard) - document.getElementById("discarded_card").className = `card card_${view.discard}` - else - document.getElementById("discarded_card").className = `card card_strategy_back` - if (view.initiative === "Commune") document.getElementById("commune_info").textContent = "\u2756" else @@ -351,22 +376,35 @@ function on_update() { document.getElementById("versailles_info").textContent = "" ui.round_marker.className = `piece pawn round${view.round}` - ui.red_momentum.className = `piece cylinder red m${view.red_momentum}` - ui.blue_momentum.className = `piece cylinder blue m${view.blue_momentum}` + if (is_action("red_momentum")) + ui.red_momentum.className = `piece cylinder red m${view.red_momentum} action` + else + ui.red_momentum.className = `piece cylinder red m${view.red_momentum}` + if (is_action("blue_momentum")) + ui.blue_momentum.className = `piece cylinder blue m${view.blue_momentum} action` + else + ui.blue_momentum.className = `piece cylinder blue m${view.blue_momentum}` ui.military_vp.className = `piece cylinder purple vp${5+view.military_vp}` ui.political_vp.className = `piece cylinder orange vp${5+view.political_vp}` document.getElementById("hand").replaceChildren() document.getElementById("final").replaceChildren() + document.getElementById("discard").replaceChildren() + document.getElementById("set_aside").replaceChildren() document.getElementById("objective").replaceChildren() + if (view.final) + document.getElementById("final").appendChild(ui.cards[view.final]) + if (view.discard) + document.getElementById("discard").appendChild(ui.cards[view.discard]) if (view.hand) for (let c of view.hand) document.getElementById("hand").appendChild(ui.cards[c]) - if (view.final) - for (let c of view.final) - document.getElementById("final").appendChild(ui.cards[c]) + if (view.set_aside) + for (let c of view.set_aside) + document.getElementById("set_aside").appendChild(ui.cards[c]) if (view.objective) - document.getElementById("objective").appendChild(ui.cards[view.objective]) + for (let c of view.objective) + document.getElementById("objective").appendChild(ui.cards[c]) for (let i = 0; i < space_names.length; ++i) layout[i] = [] @@ -391,6 +429,7 @@ function on_update() { for (let i = 1; i < ui.cards.length; ++i) { ui.cards[i].classList.toggle("action", is_card_action('card', i)) + ui.cards[i].classList.toggle("selected", i === view.selected_card) } action_button("commune", "Commune"); @@ -399,95 +438,23 @@ function on_update() { action_button("spend", "Spend"); action_button("draw", "Draw"); + action_button("momentum", "Momentum") + action_button("event", "Event") + action_button("political", "Political") + action_button("military", "Military") + + action_button("ops", "Operations") + action_button("place", "Place") + action_button("remove", "Remove") + action_button("replace", "Replace") + action_button("end_remove", "End Remove"); action_button("end_turn", "End Turn"); + action_button("skip", "Skip"); action_button("done", "Done"); action_button("undo", "Undo"); } -/* CARD ACTION MENU */ - -let current_popup_card = 0 - -function show_popup_menu(evt, list) { - document.querySelectorAll("#popup div").forEach(e => e.classList.remove('enabled')) - for (let item of list) { - let e = document.getElementById("menu_" + item) - e.classList.add('enabled') - } - let popup = document.getElementById("popup") - popup.style.display = 'block' - popup.style.left = (evt.clientX-50) + "px" - popup.style.top = (evt.clientY-12) + "px" - ui.cards[current_popup_card].classList.add("selected") -} - -function hide_popup_menu() { - let popup = document.getElementById("popup") - popup.style.display = 'none' - if (current_popup_card) { - ui.cards[current_popup_card].classList.remove("selected") - current_popup_card = 0 - } -} - -function on_card_event() { - if (send_action('card_event', current_popup_card)) - hide_popup_menu() -} - -function on_card_ops() { - if (send_action('card_ops', current_popup_card)) - hide_popup_menu() -} - -function on_card_ops_political() { - if (send_action('card_ops_political', current_popup_card)) - hide_popup_menu() -} - -function on_card_ops_military() { - if (send_action('card_ops_military', current_popup_card)) - hide_popup_menu() -} - -function on_card_use_discarded() { - if (send_action('card_use_discarded', current_popup_card)) - hide_popup_menu() -} - -function on_card_advance_momentum() { - if (send_action('card_advance_momentum', current_popup_card)) - hide_popup_menu() -} - -function on_click_card(evt) { - if (evt.button === 0) { - if (view.actions) { - let card = evt.target.my_card - if (is_card_action('card', card)) { - send_action('card', card) - } else { - let menu = [] - if (is_card_action('card_event', card)) - menu.push('card_event') - if (is_card_action('card_ops_political', card)) - menu.push('card_ops_political') - if (is_card_action('card_ops_military', card)) - menu.push('card_ops_military') - if (is_card_action('card_use_discarded', card)) - menu.push('card_use_discarded') - if (is_card_action('card_advance_momentum', card)) - menu.push('card_advance_momentum') - if (menu.length > 0) { - current_popup_card = card - show_popup_menu(evt, menu) - } - } - } - } -} - build_user_interface() scroll_with_middle_mouse("main") @@ -1,5 +1,7 @@ "use strict" +// TODO: no card menu + const COMMUNE = "Commune"; const VERSAILLES = "Versailles"; @@ -134,118 +136,118 @@ const BLUE_CRISIS_TRACK = [25, 26, 27, 28] const BLUE_BONUS_CUBES = [29, 30, 31] const PRUSSIAN_COLLABORATION = [32, 33, 34] -const S_NATIONAL_ASSEMBLY = 0 -const S_ROYALISTS = 1 -const S_REPUBLICANS = 2 +const NATIONAL_ASSEMBLY = 0 +const ROYALISTS = 1 +const REPUBLICANS = 2 -const S_PRESS = 3 -const S_CATHOLIC_CHURCH = 4 -const S_SOCIAL_MOVEMENTS = 5 +const PRESS = 3 +const CATHOLIC_CHURCH = 4 +const SOCIAL_MOVEMENTS = 5 -const S_MONT_VALERIEN = 6 -const S_FORT_D_ISSY = 7 -const S_CHATEAU_DE_VINCENNES = 8 +const MONT_VALERIEN = 6 +const FORT_D_ISSY = 7 +const CHATEAU_DE_VINCENNES = 8 -const S_BUTTE_MONTMARTRE = 9 -const S_BUTTE_AUX_CAILLES = 10 -const S_PERE_LACHAISE = 11 +const BUTTE_MONTMARTRE = 9 +const BUTTE_AUX_CAILLES = 10 +const PERE_LACHAISE = 11 -const S_PRUSSIAN_OCCUPIED_TERRITORY = 12 -const S_VERSAILLES_HQ = 13 +const PRUSSIAN_OCCUPIED_TERRITORY = 12 +const VERSAILLES_HQ = 13 -const first_space = S_NATIONAL_ASSEMBLY -const last_space = S_PERE_LACHAISE +const first_space = NATIONAL_ASSEMBLY +const last_space = PERE_LACHAISE const space_count = last_space + 1 const POLITICAL = [ - S_NATIONAL_ASSEMBLY, - S_ROYALISTS, - S_REPUBLICANS, - S_PRESS, - S_CATHOLIC_CHURCH, - S_SOCIAL_MOVEMENTS, + NATIONAL_ASSEMBLY, + ROYALISTS, + REPUBLICANS, + PRESS, + CATHOLIC_CHURCH, + SOCIAL_MOVEMENTS, ] const MILITARY = [ - S_MONT_VALERIEN, - S_FORT_D_ISSY, - S_CHATEAU_DE_VINCENNES, - S_BUTTE_MONTMARTRE, - S_BUTTE_AUX_CAILLES, - S_PERE_LACHAISE, + MONT_VALERIEN, + FORT_D_ISSY, + CHATEAU_DE_VINCENNES, + BUTTE_MONTMARTRE, + BUTTE_AUX_CAILLES, + PERE_LACHAISE, ] function is_political_space(s) { return ( - s === S_NATIONAL_ASSEMBLY || - s === S_ROYALISTS || - s === S_REPUBLICANS || - s === S_PRESS || - s === S_CATHOLIC_CHURCH || - s === S_SOCIAL_MOVEMENTS + s === NATIONAL_ASSEMBLY || + s === ROYALISTS || + s === REPUBLICANS || + s === PRESS || + s === CATHOLIC_CHURCH || + s === SOCIAL_MOVEMENTS ) } function is_military_space(s) { return ( - s === S_MONT_VALERIEN || - s === S_FORT_D_ISSY || - s === S_CHATEAU_DE_VINCENNES || - s === S_BUTTE_MONTMARTRE || - s === S_BUTTE_AUX_CAILLES || - s === S_PERE_LACHAISE + s === MONT_VALERIEN || + s === FORT_D_ISSY || + s === CHATEAU_DE_VINCENNES || + s === BUTTE_MONTMARTRE || + s === BUTTE_AUX_CAILLES || + s === PERE_LACHAISE ) } -const DIM_INSTITUTIONAL = [ S_NATIONAL_ASSEMBLY, S_ROYALISTS, S_REPUBLICANS ] -const DIM_PUBLIC_OPINION = [ S_PRESS, S_CATHOLIC_CHURCH, S_SOCIAL_MOVEMENTS ] +const INSTITUTIONAL = [ NATIONAL_ASSEMBLY, ROYALISTS, REPUBLICANS ] +const PUBLIC_OPINION = [ PRESS, CATHOLIC_CHURCH, SOCIAL_MOVEMENTS ] -const DIM_FORTS = [ S_MONT_VALERIEN, S_FORT_D_ISSY, S_CHATEAU_DE_VINCENNES ] -const DIM_PARIS = [ S_BUTTE_MONTMARTRE, S_BUTTE_AUX_CAILLES, S_PERE_LACHAISE ] +const FORTS = [ MONT_VALERIEN, FORT_D_ISSY, CHATEAU_DE_VINCENNES ] +const PARIS = [ BUTTE_MONTMARTRE, BUTTE_AUX_CAILLES, PERE_LACHAISE ] const ADJACENT_TO = [ - [ S_ROYALISTS, S_REPUBLICANS ], - [ S_PRESS, S_CATHOLIC_CHURCH ], - [ S_PRESS, S_SOCIAL_MOVEMENTS ], - [ S_ROYALISTS, S_REPUBLICANS, S_CATHOLIC_CHURCH, S_SOCIAL_MOVEMENTS ], - [ S_ROYALISTS, S_PRESS ], - [ S_REPUBLICANS, S_PRESS ], - [ S_BUTTE_MONTMARTRE, S_VERSAILLES_HQ ], - [ S_CHATEAU_DE_VINCENNES, S_BUTTE_AUX_CAILLES, S_VERSAILLES_HQ ], - [ S_FORT_D_ISSY, S_PERE_LACHAISE, S_PRUSSIAN_OCCUPIED_TERRITORY ], - [ S_MONT_VALERIEN, S_BUTTE_AUX_CAILLES, S_PERE_LACHAISE ], - [ S_FORT_D_ISSY, S_BUTTE_MONTMARTRE, S_PERE_LACHAISE ], - [ S_CHATEAU_DE_VINCENNES, S_BUTTE_MONTMARTRE, S_BUTTE_AUX_CAILLES, S_PRUSSIAN_OCCUPIED_TERRITORY ], - [ S_CHATEAU_DE_VINCENNES, S_PERE_LACHAISE ], - [ S_MONT_VALERIEN, S_FORT_D_ISSY ], + [ ROYALISTS, REPUBLICANS ], + [ PRESS, CATHOLIC_CHURCH ], + [ PRESS, SOCIAL_MOVEMENTS ], + [ ROYALISTS, REPUBLICANS, CATHOLIC_CHURCH, SOCIAL_MOVEMENTS ], + [ ROYALISTS, PRESS ], + [ REPUBLICANS, PRESS ], + [ BUTTE_MONTMARTRE, VERSAILLES_HQ ], + [ CHATEAU_DE_VINCENNES, BUTTE_AUX_CAILLES, VERSAILLES_HQ ], + [ FORT_D_ISSY, PERE_LACHAISE, PRUSSIAN_OCCUPIED_TERRITORY ], + [ MONT_VALERIEN, BUTTE_AUX_CAILLES, PERE_LACHAISE ], + [ FORT_D_ISSY, BUTTE_MONTMARTRE, PERE_LACHAISE ], + [ CHATEAU_DE_VINCENNES, BUTTE_MONTMARTRE, BUTTE_AUX_CAILLES, PRUSSIAN_OCCUPIED_TERRITORY ], + [ CHATEAU_DE_VINCENNES, PERE_LACHAISE ], + [ MONT_VALERIEN, FORT_D_ISSY ], ] const ADJACENT_FROM = [ [ ], - [ S_NATIONAL_ASSEMBLY, S_PRESS, S_CATHOLIC_CHURCH ], - [ S_NATIONAL_ASSEMBLY, S_PRESS, S_SOCIAL_MOVEMENTS ], - [ S_ROYALISTS, S_REPUBLICANS, S_CATHOLIC_CHURCH, S_SOCIAL_MOVEMENTS ], - [ S_ROYALISTS, S_PRESS ], - [ S_REPUBLICANS, S_PRESS ], - [ S_BUTTE_MONTMARTRE, S_VERSAILLES_HQ ], - [ S_CHATEAU_DE_VINCENNES, S_BUTTE_AUX_CAILLES, S_VERSAILLES_HQ ], - [ S_FORT_D_ISSY, S_PERE_LACHAISE, S_PRUSSIAN_OCCUPIED_TERRITORY ], - [ S_MONT_VALERIEN, S_BUTTE_AUX_CAILLES, S_PERE_LACHAISE ], - [ S_FORT_D_ISSY, S_BUTTE_MONTMARTRE, S_PERE_LACHAISE ], - [ S_CHATEAU_DE_VINCENNES, S_BUTTE_MONTMARTRE, S_BUTTE_AUX_CAILLES, S_PRUSSIAN_OCCUPIED_TERRITORY ], - [ S_CHATEAU_DE_VINCENNES, S_PERE_LACHAISE ], - [ S_MONT_VALERIEN, S_FORT_D_ISSY ], + [ NATIONAL_ASSEMBLY, PRESS, CATHOLIC_CHURCH ], + [ NATIONAL_ASSEMBLY, PRESS, SOCIAL_MOVEMENTS ], + [ ROYALISTS, REPUBLICANS, CATHOLIC_CHURCH, SOCIAL_MOVEMENTS ], + [ ROYALISTS, PRESS ], + [ REPUBLICANS, PRESS ], + [ BUTTE_MONTMARTRE, VERSAILLES_HQ ], + [ CHATEAU_DE_VINCENNES, BUTTE_AUX_CAILLES, VERSAILLES_HQ ], + [ FORT_D_ISSY, PERE_LACHAISE, PRUSSIAN_OCCUPIED_TERRITORY ], + [ MONT_VALERIEN, BUTTE_AUX_CAILLES, PERE_LACHAISE ], + [ FORT_D_ISSY, BUTTE_MONTMARTRE, PERE_LACHAISE ], + [ CHATEAU_DE_VINCENNES, BUTTE_MONTMARTRE, BUTTE_AUX_CAILLES, PRUSSIAN_OCCUPIED_TERRITORY ], + [ CHATEAU_DE_VINCENNES, PERE_LACHAISE ], + [ MONT_VALERIEN, FORT_D_ISSY ], ] // === GAME STATE === function discard_card(c) { - array_remove_item(player_hand(game.active), c) + array_remove_item(player_hand(), c) game.discard = c } function recycle_card(c) { - array_remove_item(player_hand(game.active), c) + array_remove_item(player_hand(), c) game.strategy_deck.unshift(c) } @@ -253,10 +255,6 @@ function is_objective_card(c) { return c >= 42 && c <= 53 } -function is_strategy_card(c) { - return !is_objective_card(c) && c !== 17 && c !== 34 -} - function is_commune_card(c) { return c >= 18 && c <= 34 } @@ -275,12 +273,18 @@ function enemy_player() { return COMMUNE } -function player_hand(current) { - if (current === COMMUNE) +function player_hand() { + if (game.active === COMMUNE) return game.red_hand return game.blue_hand } +function player_set_aside(current) { + if (game.active === COMMUNE) + return game.red_set_aside + return game.blue_set_aside +} + function is_space(s) { return s >= first_space && s <= last_space } @@ -332,13 +336,13 @@ function update_presence_and_control() { game.control = 0 // Permanent Presence - game.presence |= (1 << (S_PERE_LACHAISE)) - game.presence |= (1 << (S_SOCIAL_MOVEMENTS)) - game.presence |= (1 << (S_ROYALISTS + space_count)) + game.presence |= 1 << PERE_LACHAISE + game.presence |= 1 << SOCIAL_MOVEMENTS + game.presence |= 1 << (ROYALISTS + space_count) for (let s = first_space; s <= last_space; ++s) { - let c_bit = (1 << (s)) - let v_bit = (1 << (s + space_count)) + let c_bit = 1 << s + let v_bit = 1 << (s + space_count) if (c_count[s] > 0) game.presence |= c_bit if (v_count[s] > 0) @@ -348,47 +352,46 @@ function update_presence_and_control() { if (v_count[s] > c_count[s]) game.control |= v_bit } - } -function is_present(side, s) { - if (side === COMMUNE) +function is_present(s) { + if (game.active === COMMUNE) return game.presence & (1 << (s)) return game.presence & (1 << (s + space_count)) } function is_commune_control(s) { - if (s === S_VERSAILLES_HQ) + if (s === VERSAILLES_HQ) return false - if (s === S_PRUSSIAN_OCCUPIED_TERRITORY) + if (s === PRUSSIAN_OCCUPIED_TERRITORY) return false return game.control & (1 << (s)) } function is_versailles_control(s) { - if (s === S_VERSAILLES_HQ) + if (s === VERSAILLES_HQ) return true - if (s === S_PRUSSIAN_OCCUPIED_TERRITORY) + if (s === PRUSSIAN_OCCUPIED_TERRITORY) return game.blue_momentum === 3 return game.control & (1 << (s + space_count)) } -function is_control(side, s) { - if (side === COMMUNE) +function is_control(s) { + if (game.active === COMMUNE) return is_commune_control(s) return is_versailles_control(s) } -function is_adjacent_to_control(side, here) { +function is_adjacent_to_control(here) { for (let s of ADJACENT_TO[here]) - if (is_control(side, s)) + if (is_control(s)) return true return false } -function is_control_dimension(side, dim) { +function is_control_dimension(dim) { for (let s of dim) - if (!is_control(side, s)) + if (!is_control(s)) return false return true } @@ -433,6 +436,12 @@ function count_versailles_pieces(s) { return count_versailles_cubes(s) + count_versailles_discs(s) } +function count_friendly_cubes(s) { + if (game.active === COMMUNE) + return count_commune_cubes(s) + return count_versailles_cubes(s) +} + function count_enemy_pieces(s) { if (game.active === COMMUNE) return count_versailles_pieces(s) @@ -517,7 +526,14 @@ function is_disc(p) { function remove_piece(p) { if (is_commune_cube(p)) { - game.pieces[p] = RED_CUBE_POOL[0] // TODO... + if (game.red_momentum >= 1 && count_commune_cubes(RED_CUBE_POOL[0]) < 2) + game.pieces[p] = RED_CUBE_POOL[0] + else if (game.red_momentum >= 2 && count_commune_cubes(RED_CUBE_POOL[1]) < 1) + game.pieces[p] = RED_CUBE_POOL[1] + else if (game.red_momentum >= 3 && count_commune_cubes(RED_CUBE_POOL[2]) < 1) + game.pieces[p] = RED_CUBE_POOL[2] + else + game.pieces[p] = -1 } else if (is_versailles_cube(p)) { game.pieces[p] = BLUE_CUBE_POOL } else { @@ -525,6 +541,10 @@ function remove_piece(p) { } } +function remove_piece_from_play(p) { + game.pieces[p] = -1 +} + function place_piece(p, s) { game.pieces[p] = s } @@ -552,6 +572,7 @@ function find_available_cube() { return p } } + return -1 } function for_each_enemy_cube(s, f) { @@ -619,24 +640,24 @@ function versailles_political_vp() { states.choose_objective_card = { inactive: "choose an objective card", prompt(current) { - view.prompt = "Choose an Objective card." - for (let c of player_hand(current)) - if (is_objective_card(c)) + view.prompt = "Choose an Objective card to keep." + if (current === COMMUNE) + for (let c of game.red_objective) + gen_action_card(c) + else + for (let c of game.blue_objective) gen_action_card(c) }, card(c, current) { - if (current === COMMUNE) { - game.red_objective = c - game.red_hand = game.red_hand.filter(c => !is_objective_card(c)) - } else { - game.blue_objective = c - game.blue_hand = game.blue_hand.filter(c => !is_objective_card(c)) - } - if (game.red_objective > 0 && game.blue_objective > 0) + if (current === COMMUNE) + game.red_objective = [ c ] + else + game.blue_objective = [ c ] + if (game.red_objective.length === 1 && game.blue_objective.length === 1) end_choose_objective_card() - else if (game.red_objective > 0) + else if (game.red_objective.length === 1) game.active = VERSAILLES - else if (game.blue_objective > 0) + else if (game.blue_objective.length === 1) game.active = COMMUNE else game.active = "Both" @@ -660,9 +681,9 @@ function goto_initiative_phase() { } states.initiative_phase = { - inactive: "decide player order", + inactive: "decide initiative", prompt() { - view.prompt = "Decide player order." + view.prompt = "Decide who will have the initiative." view.actions.commune = 1 view.actions.versailles = 1 }, @@ -696,9 +717,8 @@ states.censorship_phase = { inactive: "censorship phase", prompt() { view.prompt = "Discard a card from your hand." - for (let c of player_hand(game.active)) - if (is_strategy_card(c)) - gen_action("card", c) + for (let c of player_hand()) + gen_action("card", c) }, card(c) { log(`Discarded #${c}.`) @@ -719,83 +739,195 @@ function goto_strategy_phase() { } function resume_strategy_phase() { - game.active = enemy_player() - goto_strategy_phase() + if (game.red_hand.length === 1 && game.blue_hand.length === 1) { + goto_set_aside_cards() + } else { + game.active = enemy_player() + goto_strategy_phase() + } +} + +function has_final_crisis_card() { + if (game.active === COMMUNE) + return game.red_final + return game.blue_final } states.strategy_phase = { inactive: "play a card", prompt() { view.prompt = "Play a card." - let hand = player_hand(game.active) - let n_strategy = 0 - for (let c of hand) - if (is_strategy_card(c)) - n_strategy += 1 - for (let c of hand) { - if (is_strategy_card(c)) { - if (can_play_event(c)) - gen_action("card_event", c) - gen_action("card_ops_political", c) - gen_action("card_ops_military", c) - if (game.discard > 0 && can_play_event(game.discard)) - gen_action("card_use_discarded", c) - if (can_advance_momentum()) - gen_action("card_advance_momentum", c) - } - if (c === 17 || c === 34) { - if (n_strategy > 0) { - gen_action("card_ops_political", c) - gen_action("card_ops_military", c) - } - } - } + for (let c of player_hand()) + gen_action_card(c) }, - card_event(c) { + card(c) { push_undo() - log(`Played #${c} for event.`) - discard_card(c) - goto_play_event() + log(`Played #${c}.`) + game.what = c + game.state = "play_card" }, - card_ops_political(c) { - push_undo() - if (c === 17 || c === 34) - return goto_final_crisis_discard(c, POLITICAL) - log(`Played #${c} for ${card_ops[c]} Political ops.`) - discard_card(c) - goto_operations(card_ops[c], POLITICAL) +} + +states.play_card = { + prompt() { + let c = game.what + + view.selected_card = game.what + + view.actions.political = 1 + view.actions.military = 1 + + if (can_play_event(c)) + view.actions.event = 1 + else + view.actions.event = 0 + + if (can_advance_momentum()) { + view.actions.momentum = 1 + if (game.active === COMMUNE) + view.actions.red_momentum = 1 + else + view.actions.blue_momentum = 1 + } + + if (game.discard > 0 && can_play_event(game.discard)) + gen_action_card(game.discard) + + let final = has_final_crisis_card() + if (final > 0) + gen_action_card(final) }, - card_ops_military(c) { - push_undo() - if (c === 17 || c === 34) - return goto_final_crisis_discard(c, MILITARY) - log(`Played #${c} for ${card_ops[c]} Military ops.`) - discard_card(c) - goto_operations(card_ops[c], MILITARY) + event() { + log("Event.") + discard_card(game.what) + goto_play_event(game.what) }, - card_advance_momentum(c) { - push_undo() - log(`Played #${c} to advance momentum.`) + political() { + log("Ops.") + discard_card(game.what) + goto_operations(card_ops[game.what], POLITICAL) + }, + military() { + log("Ops.") + discard_card(game.what) + goto_operations(card_ops[game.what], MILITARY) + }, + momentum() { + log(`Momentum.`) if (game.scenario === "Censorship") - recycle_card(c) + recycle_card(game.what) else - discard_card(c) + discard_card(game.what) if (game.active === COMMUNE) - game.red_momentum += 1 + advance_revolutionary_momentum(1) else - game.blue_momentum += 1 - // TODO: momentum trigger - resume_strategy_phase() + advance_prussian_collaboration(1) }, - card_use_discarded(c) { - push_undo() - log(`Discarded #${c} to play #${game.discard}.`) - let old_c = game.discard - discard_card(c) - goto_play_event(old_c) + red_momentum() { + this.momentum() + }, + blue_momentum() { + this.momentum() + }, + card(c) { + log(`Discarded for #${c}.`) + if (c === game.discard) { + discard_card(game.what) + game.what = c + goto_play_event(c) + } else { + discard_card(game.what) + game.what = c + game.state = "play_final" + } + }, +} + +states.play_final = { + prompt() { + view.prompt = card_names[game.what] + ": Use up to 4 Operations Points." + view.selected_card = game.what + view.actions.political = 1 + view.actions.military = 1 + }, + political() { + discard_final() + goto_operations(4, POLITICAL) + }, + military() { + discard_final() + goto_operations(4, MILITARY) + }, +} + +function discard_final() { + if (game.active === COMMUNE) + game.red_final = 0 + else + game.blue_final = 0 +} + +// === PLAYER MOMENTUM TRACKS === + +function advance_revolutionary_momentum(x) { + game.red_momentum += x + for (let i = game.red_momentum; i < 3; ++i) + for_each_commune_cube(RED_CUBE_POOL[i], p => game.pieces[p] = -1) + game.active = VERSAILLES + if (x > 0 && game.red_momentum >= 2) + game.state = "revolutionary_momentum_trigger" + else + end_momentum_trigger() +} + +function advance_prussian_collaboration(x) { + game.blue_momentum += x + for (let i = 0; i < game.blue_momentum; ++i) + for_each_versailles_cube(PRUSSIAN_COLLABORATION[i], p => game.pieces[p] = BLUE_CUBE_POOL) + game.active = COMMUNE + if (x > 0 && game.blue_momentum >= 2) + game.state = "prussian_collaboration_trigger" + else + end_momentum_trigger() +} + +states.revolutionary_momentum_trigger = { + prompt() { + view.prompt = "Revolutionary Momentum: Place a cube in an Institutional space." + for (let s of INSTITUTIONAL) + gen_action_space(s) + view.actions.skip = 1 + }, + space(s) { + place_piece(find_available_cube(), s) + end_momentum_trigger() + }, + skip() { + end_momentum_trigger() + }, +} + +states.prussian_collaboration_trigger = { + prompt() { + view.prompt = "Prussian Collaboration: Place a cube in a Public Opinion space." + for (let s of PUBLIC_OPINION) + gen_action_space(s) + view.actions.skip = 1 + }, + space(s) { + place_piece(find_available_cube(), s) + end_momentum_trigger() + }, + skip() { + end_momentum_trigger() }, } +function end_momentum_trigger() { + game.active = enemy_player() + resume_strategy_phase() +} + // === OPERATIONS === function goto_operations(count, spaces) { @@ -805,25 +937,24 @@ function goto_operations(count, spaces) { } function goto_final_crisis_discard(c, spaces) { + log("Played #" + c + ".") game.state = "discard_final_crisis" game.count = 4 game.spaces = spaces - array_remove_item(player_hand(game.active), c) } states.discard_final_crisis = { inactive: "discard a card to play final crisis", prompt() { view.prompt = "Discard a card to play Final Crisis card for ops." - let hand = player_hand(game.active) + let hand = player_hand() for (let c of hand) - if (is_strategy_card(c)) - gen_action("card", c) + gen_action("card", c) }, card(c) { push_undo() log(`Discarded #${c} to play Final Crisis card for ops.`) - array_remove_item(player_hand(game.active), c) + array_remove_item(player_hand(), c) game.discard = c goto_operations_remove() }, @@ -847,7 +978,7 @@ function can_operations_remove() { } function can_operations_remove_space(s) { - if (is_present(game.active, s) || is_adjacent_to_control(game.active, s)) { + if (is_present(s) || is_adjacent_to_control(s)) { let c = has_enemy_cube(s) let d = has_enemy_disc(s) if (c || d) { @@ -864,11 +995,11 @@ function can_operations_remove_space(s) { function military_strength(s) { let str = 0 for (let next of ADJACENT_FROM[s]) - if (is_control(game.active, next)) + if (is_control(next)) str += 1 - if (is_present(game.active, s)) + if (is_present(s)) str += 1 - if (is_control(game.active, s)) + if (is_control(s)) str += 1 return str } @@ -989,7 +1120,9 @@ function can_operations_place() { } function can_operations_place_space(s) { - if (is_present(game.active, s) || is_adjacent_to_control(game.active, s)) { + if (is_present(s) || is_adjacent_to_control(s)) { + if (count_friendly_cubes(s) >= 4) + return false let d = has_enemy_disc(s) if (!d || game.count >= 2) return true @@ -999,7 +1132,7 @@ function can_operations_place_space(s) { states.operations_place = { prompt() { - view.prompt = "Operations: Place cubes into Political spaces." + view.prompt = "Operations: Place cubes." for (let s of game.spaces) if (can_operations_place_space(s)) gen_action_space(s) @@ -1045,6 +1178,25 @@ function end_operations() { resume_strategy_phase() } +// === SET ASIDE CARDS === + +function goto_set_aside_cards() { + for (let c of game.red_hand) + game.red_set_aside.push(c) + game.red_hand = [] + + for (let c of game.blue_hand) + game.blue_set_aside.push(c) + game.blue_hand = [] + + goto_pivotal_space_bonus_actions() +} + +function goto_pivotal_space_bonus_actions() { + game.state = "pivotal_space_bonus_actions" + game.active = game.initiative +} + // === EVENTS === function goto_play_event(c) { @@ -1077,13 +1229,15 @@ exports.setup = function (seed, scenario, options) { objective_deck: [], discard: 0, - red_hand: [ 34 ], - red_final: [], - red_objective: 0, + red_final: 34, + red_hand: [], + red_set_aside: [], + red_objective: [], - blue_hand: [ 17 ], - blue_final: [], - blue_objective: 0, + blue_final: 17, + blue_hand: [], + blue_set_aside: [], + blue_objective: [], presence: 0, control: 0, @@ -1105,9 +1259,9 @@ exports.setup = function (seed, scenario, options) { RED_BONUS_CUBES[1], RED_BONUS_CUBES[2], RED_BONUS_CUBES[2], - S_PRESS, - S_SOCIAL_MOVEMENTS, - S_PERE_LACHAISE, + PRESS, + SOCIAL_MOVEMENTS, + PERE_LACHAISE, // Versailles cubes BLUE_CRISIS_TRACK[0], BLUE_CRISIS_TRACK[1], @@ -1119,14 +1273,14 @@ exports.setup = function (seed, scenario, options) { BLUE_BONUS_CUBES[1], BLUE_BONUS_CUBES[2], BLUE_BONUS_CUBES[2], - BLUE_CUBE_POOL, - BLUE_CUBE_POOL, - BLUE_CUBE_POOL, - BLUE_CUBE_POOL, - BLUE_CUBE_POOL, - BLUE_CUBE_POOL, - S_ROYALISTS, - S_PRESS, + PRUSSIAN_COLLABORATION[0], + PRUSSIAN_COLLABORATION[1], + PRUSSIAN_COLLABORATION[1], + PRUSSIAN_COLLABORATION[2], + PRUSSIAN_COLLABORATION[2], + PRUSSIAN_COLLABORATION[2], + ROYALISTS, + PRESS, // Commune discs -1, -1, // Versailles discs @@ -1155,14 +1309,14 @@ exports.setup = function (seed, scenario, options) { if (game.scenario === "Censorship") n = 5 - for (let i = 0; i < 5; ++i) { + for (let i = 0; i < n; ++i) { game.red_hand.push(game.strategy_deck.pop()) game.blue_hand.push(game.strategy_deck.pop()) } for (let i = 0; i < 2; ++i) { - game.red_hand.push(game.objective_deck.pop()) - game.blue_hand.push(game.objective_deck.pop()) + game.red_objective.push(game.objective_deck.pop()) + game.blue_objective.push(game.objective_deck.pop()) } return game @@ -1197,17 +1351,20 @@ exports.view = function(state, player) { discard: game.discard, hand: 0, final: 0, + set_aside: 0, objective: 0 } if (player === COMMUNE) { view.hand = game.red_hand view.final = game.red_final + view.set_aside = game.red_set_aside view.objective = game.red_objective } if (player === VERSAILLES) { view.hand = game.blue_hand view.final = game.blue_final + view.set_aside = game.blue_set_aside view.objective = game.blue_objective } |