diff options
author | Tor Andersson <tor@ccxvii.net> | 2023-06-23 12:51:59 +0200 |
---|---|---|
committer | Tor Andersson <tor@ccxvii.net> | 2023-07-07 18:39:37 +0200 |
commit | 480df7113c50d45f748b4af8ce59172d58568376 (patch) | |
tree | 02ae88f35942a42cb76e504d629862b5a46f0065 | |
parent | ae71215fc3ae9c7f838a6cd76f82ad64a3728acd (diff) | |
download | time-of-crisis-480df7113c50d45f748b4af8ce59172d58568376.tar.gz |
Events.
-rw-r--r-- | play.css | 23 | ||||
-rw-r--r-- | play.html | 5 | ||||
-rw-r--r-- | play.js | 27 | ||||
-rw-r--r-- | rules.js | 289 | ||||
-rw-r--r-- | tools/gencards.js | 4 |
5 files changed, 291 insertions, 57 deletions
@@ -53,6 +53,15 @@ body.p3 #crisis_table { background-image: url(overlay_3p_75.jpg); } +#active_event { + position: absolute; + display: block; + width: 250px; + height: 350px; + bottom: 50px; + left: 50px; +} + body.Solo #.role { display: none } svg { @@ -66,7 +75,7 @@ svg .region, svg .sea { } svg .region.action, svg .sea.action { - fill: white; + fill: black; fill-opacity: 0.3; } @@ -435,6 +444,14 @@ body.p2 #Galatia_NPG { display: block } box-shadow: 0 0 0 1px #444, 0 0 4px #000; } +.card.event { + background-color: #f1f2f4; +} + +.card.influence { + background-color: #ece8dc; +} + .card.action { transition: transform 100ms ease; box-shadow: 0 0 0px 3px #fff; @@ -458,7 +475,7 @@ body.p2 #Galatia_NPG { display: block } min-height: 350px; } -.card.event_back{background-image:url(cards.1x/event_back.jpg)} +.card.event_0{background-image:url(cards.1x/event_back.jpg)} .card.event_1{background-image:url(cards.1x/event_01.jpg)} .card.event_2{background-image:url(cards.1x/event_02.jpg)} .card.event_3{background-image:url(cards.1x/event_03.jpg)} @@ -499,7 +516,7 @@ body.p2 #Galatia_NPG { display: block } .card.influence_s4x{background-image:url(cards.1x/influence_s4x.jpg)} @media (min-resolution:97dpi) { -.card.event_back{background-image:url(cards.2x/event_back.jpg)} +.card.event_0{background-image:url(cards.2x/event_back.jpg)} .card.event_1{background-image:url(cards.2x/event_01.jpg)} .card.event_2{background-image:url(cards.2x/event_02.jpg)} .card.event_3{background-image:url(cards.2x/event_03.jpg)} @@ -51,6 +51,7 @@ <div id="mapwrap"> <div id="map"> <div id="crisis_table"></div> +<div id="active_event"></div> <svg id="mapsvg" width="2550" height="1650" viewBox="0 0 2550 1650"> <g class="region" id="region_aegyptus"><path d="M1499 1309c-1 0-3 2-6 4-4 3-12 6-18 6-4 0-21 10-29 17-7 7-15 17-17 24-4 11-2 25 7 39 3 6 4 14 2 21-3 9-14 26-19 30-8 6-16 12-19 13-5 1-6 2-3 18 6 33 6 45 3 64-3 15-6 55-4 60l1 2h662v-4c-1-3-2-5-4-7-2-3-8-11-11-19-1-3-2-6-2-11v-7l-6-5c-4-3-10-10-13-15s-8-11-9-12c-2-1-4-4-4-7-1-2-4-6-5-9-6-7-7-10-6-15 1-6-2-13-9-20-5-6-5-6-5-11 0-3 1-7 2-9 0-1 2-6 3-11 0-5 2-10 3-12l15-15c8-6 14-12 16-15 5-8 8-19 5-19-1 0-3 0-5 1s-3 1-5 0-4-1-10 1c-9 3-15 2-20-2-3-2-5-3-7 0-1 1-4 2-6 3-4 1-5 1-7 0-1-1-2-2-2-3s-1-2-3-3c-6-2-10-7-10-10s0-3-5-3c-3 0-7-1-11-3-3-1-7-2-7-2-1 0-3 2-6 3-5 5-17 8-23 6-2-1-4-1-5 0-1 0-2 2-4 5-2 6-5 7-8 6-5-2-6-1-14 7-20 19-33 23-46 15-8-5-16-8-22-8-6 1-19-1-22-3-1-2-3-2-6-2-6 1-14-2-18-5-3-3-5-4-14-6-7-1-16-3-22-3-6-1-13-3-16-3-9-3-15-2-26 1-5 2-10 4-12 4-5 0-10-4-12-8 0-2-3-7-5-11-4-8-7-9-19-7-4 1-11 1-15 1-7-1-9-1-14-5-6-3-7-3-17-4-12-1-16-2-21-7-4-4-4-4-4-9 1-7 0-9-8-12-3-1-8-2-10-3-3 0-6-1-8-2-3-1-7-3-9-3-5-1-24-2-26-1z"/></g> @@ -120,7 +121,7 @@ </div> <div id="played_panel" class="panel"> -<div id="played_header" class="panel_header">Played / Events</div> +<div id="played_header" class="panel_header">Played</div> <div id="played" class="panel_body"> </div> </div> @@ -132,7 +133,7 @@ </div> <div id="draw_panel" class="panel"> -<div id="draw_header" class="panel_header">Draw</div> +<div id="draw_header" class="panel_header">Available</div> <div id="draw" class="panel_body"> </div> </div> @@ -132,6 +132,9 @@ function is_no_place_governor(province) { function get_support(province) { return view.support[province] } +function get_rival_emperor_location(id) { + return view.rival_emperors[id] +} function get_barbarian_location(id) { return view.barbarians[id] & 63 } @@ -562,11 +565,13 @@ const LAYOUT_PATTERN = [ let ui = { cards: [], + event_cards: [], militia: [], body: document.querySelector("body"), header: document.querySelector("header"), hand: document.getElementById("hand"), draw: document.getElementById("draw"), + active_event: document.getElementById("active_event"), discard: document.getElementById("discard"), played: document.getElementById("played"), market: document.getElementById("market"), @@ -728,7 +733,11 @@ function is_action(action, arg) { function on_init() { for (let c = 0; c < CARD_INDEX.length; ++c) { let name = CARD_INFO[CARD_INDEX[c]].name - ui.cards[c] = create("div", { className: "card influence_" + name.toLowerCase(), my_action: "card", my_id: c }) + ui.cards[c] = create("div", { className: "card influence influence_" + name.toLowerCase(), my_action: "card", my_id: c }) + } + + for (let e = 0; e <= 15; ++e) { + ui.event_cards[e] = create("div", { className: "card event event_" + e }) } for (let i = 0; i < 33; ++i) @@ -1056,6 +1065,16 @@ function on_update() { } } + for (let re = 0; re < 3; ++re) { + let loc = get_rival_emperor_location(re) + if (loc === UNAVAILABLE) + hide(ui.rival_emperors[re]) + else { + show(ui.rival_emperors[re]) + layout_stack(-1, [ ui.rival_emperors[re] ], loc, false, 8, 8) + } + } + for (let region = 0; region < 21; ++region) { ui.regions[region].classList.toggle("selected", view.selected_region === region) } @@ -1196,10 +1215,8 @@ function on_update() { ui.dice[3].className = "dice white d" + view.dice[3] layout_barbarian_dice(ui.dice[2], ui.dice[3], view.crisis) - if (view.events) { - for (let c of view.events) - ui.played.appendChild(ui.cards[c]) - } + ui.active_event.replaceChildren() + ui.active_event.appendChild(ui.event_cards[view.event]) ui.played.replaceChildren() if (view.played) { @@ -202,6 +202,14 @@ const BARBARIAN_HOMELAND = [ SASSANIDS_HOMELAND, ] +const CNIVA = 0 +const ARDASHIR = 1 +const SHAPUR = 2 + +const POSTUMUS = 0 +const PRIEST_KING = 1 +const ZENOBIA = 2 + const BARBARIAN_INVASION = [ // Alamanni [ @@ -292,6 +300,25 @@ const EVENT_INFLATION = 13 const EVENT_GOOD_AUGURIES = 14 const EVENT_DIOCLETIAN = 15 +const EVENT_NAME = [ + "None", + "Plague of Cyprian", + "Ardashir", + "Priest King", + "Palmyra Allies", + "Shapur I", + "Postumus", + "Ludi Saeculares", + "Cniva", + "Zenobia", + "Bad Auguries", + "Raiding Parties", + "Preparing for War", + "Inflation", + "Good_auguries", + "Diocletian", +] + const CARD_M1 = [ 0, 11 ] const CARD_S1 = [ 12, 23 ] const CARD_P1 = [ 24, 35 ] @@ -325,36 +352,36 @@ const CARD_INDEX = [ ] const CARD_INFO = [ - { name: "M1", type: 0, cost: 1, event: "None" }, - { name: "S1", type: 1, cost: 1, event: "None" }, - { name: "P1", type: 2, cost: 1, event: "None" }, - { name: "M2", type: 0, cost: 2, event: "Castra" }, - { name: "S2", type: 1, cost: 2, event: "Tribute" }, - { name: "P2", type: 2, cost: 2, event: "Quaestor" }, - { name: "M2X", type: 0, cost: 2, event: "Cavalry" }, - { name: "S2X", type: 1, cost: 2, event: "Princeps Senatus" }, - { name: "P2X", type: 2, cost: 2, event: "Ambitus" }, - { name: "M3", type: 0, cost: 3, event: "Flanking Maneuver" }, - { name: "S3", type: 1, cost: 3, event: "Foederati" }, - { name: "P3", type: 2, cost: 3, event: "Mob" }, - { name: "M3X", type: 0, cost: 3, event: "Force March" }, - { name: "S3X", type: 1, cost: 3, event: "Frumentarii" }, - { name: "P3X", type: 2, cost: 3, event: "Mobile Vulgus" }, - { name: "M4", type: 0, cost: 4, event: "Praetorian Guard" }, - { name: "S4", type: 1, cost: 4, event: "Damnatio Memoriae" }, - { name: "S4B", type: 1, cost: 4, event: "Damnatio Memoriae (exp)" }, - { name: "P4", type: 2, cost: 4, event: "Pretender" }, - { name: "M4X", type: 0, cost: 4, event: "Spiculum" }, - { name: "S4X", type: 1, cost: 4, event: "Triumph" }, - { name: "P4X", type: 2, cost: 4, event: "Demagogue" }, + { name: "M1", type: 0, value: 1, event: "None" }, + { name: "S1", type: 1, value: 1, event: "None" }, + { name: "P1", type: 2, value: 1, event: "None" }, + { name: "M2", type: 0, value: 2, event: "Castra" }, + { name: "S2", type: 1, value: 2, event: "Tribute" }, + { name: "P2", type: 2, value: 2, event: "Quaestor" }, + { name: "M2X", type: 0, value: 2, event: "Cavalry" }, + { name: "S2X", type: 1, value: 2, event: "Princeps Senatus" }, + { name: "P2X", type: 2, value: 2, event: "Ambitus" }, + { name: "M3", type: 0, value: 3, event: "Flanking Maneuver" }, + { name: "S3", type: 1, value: 3, event: "Foederati" }, + { name: "P3", type: 2, value: 3, event: "Mob" }, + { name: "M3X", type: 0, value: 3, event: "Force March" }, + { name: "S3X", type: 1, value: 3, event: "Frumentarii" }, + { name: "P3X", type: 2, value: 3, event: "Mobile Vulgus" }, + { name: "M4", type: 0, value: 4, event: "Praetorian Guard" }, + { name: "S4", type: 1, value: 4, event: "Damnatio Memoriae" }, + { name: "S4B", type: 1, value: 4, event: "Damnatio Memoriae (exp)" }, + { name: "P4", type: 2, value: 4, event: "Pretender" }, + { name: "M4X", type: 0, value: 4, event: "Spiculum" }, + { name: "S4X", type: 1, value: 4, event: "Triumph" }, + { name: "P4X", type: 2, value: 4, event: "Demagogue" }, ] function card_name(c) { return CARD_INFO[CARD_INDEX[c]].name } -function card_cost(c) { - return CARD_INFO[CARD_INDEX[c]].cost +function card_value(c) { + return CARD_INFO[CARD_INDEX[c]].value } function card_influence(c) { @@ -401,9 +428,9 @@ function play_card_event(c) { } function add_card_ip(c) { - let cost = CARD_INFO[CARD_INDEX[c]].cost + let value = CARD_INFO[CARD_INDEX[c]].value let type = CARD_INFO[CARD_INDEX[c]].type - game.ip[type] += cost + game.ip[type] += value } function is_region(where) { @@ -445,6 +472,9 @@ function is_barbarian_active(id) { return !is_barbarian_inactive(id) } function set_barbarian_inactive(id) { game.barbarians[id] |= 64 } function set_barbarian_active(id) { game.barbarians[id] &= 63 } +function get_rival_emperor_location(id) { return game.rival_emperors[id] } +function set_rival_emperor_location(id, loc) { game.rival_emperors[id] = loc } + function get_legion_location(ix) { return game.legions[ix] & 63 } function set_legion_location(ix, loc) { game.legions[ix] = loc } function is_legion_reduced(ix) { return game.legions[ix] & 64 } @@ -734,7 +764,7 @@ function has_barbarian_leader(where) { function has_rival_emperor(where) { for (let i = 0; i < 3; ++i) - if (game.rival_emperors[i] === where) + if (get_rival_emperor_location(i) === where) return true return false } @@ -834,6 +864,13 @@ function count_legions_in_army(army_id) { return n } +function count_own_legions() { + let n = 0 + for (let i = 0; i < 6; ++i) + n += count_legions_in_army(game.current * 6 + i) + return n +} + function count_barbarians_in_army(army_id) { let n = 0 for (let id = 0; id < game.barbarians.length; ++id) @@ -960,6 +997,22 @@ function roll_dice(count, target) { return hits } +function roll_dice_no_reroll(count, target) { + let hits = 0 + let summary = [] + for (let i = 0; i < count; ++i) { + let die = roll_die() + if (die >= target) { + summary.push("B" + die) + hits += 1 + } else { + summary.push("W" + die) + } + } + log("Rolled " + summary.join(" ")) + return hits +} + function eliminate_barbarian(id) { let tribe = get_barbarian_tribe(id) set_barbarian_location(id, BARBARIAN_HOMELAND[tribe]) @@ -1134,7 +1187,7 @@ function goto_crisis() { if (sum === 12) return goto_pax_deorum() if (sum === 7) - return goto_event() + return goto_crisis_event() if (game.legacy.length === 2) return goto_barbarian_crisis(CRISIS_TABLE_2P[sum - 2]) @@ -1143,6 +1196,8 @@ function goto_crisis() { return goto_barbarian_crisis(CRISIS_TABLE_4P[sum - 2]) } +// CRISIS: IRA DEORUM + function goto_ira_deorum() { logi("Ira Deorum") @@ -1177,17 +1232,15 @@ states.ira_deorum = { }, } +// CRISIS: PAX DEORUM + function goto_pax_deorum() { logi("Pax Deorum") logi("TODO") goto_take_actions() } -function goto_event() { - logi("Event") - logi("TODO") - goto_take_actions() -} +// CRISIS: BARBARIAN INVASION function goto_barbarian_crisis(tribe) { logi(BARBARIAN_NAME[tribe]) @@ -1276,6 +1329,154 @@ function invade_with_barbarian_counter(id, path, where) { } } +// CRISIS: EVENT + +function goto_crisis_event() { + game.active_event = game.events.pop() + + log_h3(EVENT_NAME[game.active_event]) + + switch (game.active_event) { + case EVENT_ARDASHIR: return goto_crisis_barbarian_leader(ARDASHIR, SASSANIDS_HOMELAND) + case EVENT_SHAPUR_I: return goto_crisis_barbarian_leader(SHAPUR, SASSANIDS_HOMELAND) + case EVENT_CNIVA: return goto_crisis_barbarian_leader(CNIVA, GOTHS_HOMELAND) + case EVENT_PRIEST_KING: return goto_crisis_rival_emperor(PRIEST_KING, SYRIA) + case EVENT_POSTUMUS: return goto_crisis_rival_emperor(POSTUMUS, GALLIA) + case EVENT_ZENOBIA: return goto_crisis_rival_emperor(ZENOBIA, AEGYPTUS) + case EVENT_PALMYRA_ALLIES: return goto_palmyra_allies() + case EVENT_LUDI_SAECULARES: return goto_ludi_saeculares() + case EVENT_DIOCLETIAN: return goto_game_end() + } + + goto_take_actions() +} + +// CRISIS: BARBARIAN LEADER + +function goto_crisis_barbarian_leader(id, where) { + game.count = id + game.where = where + game.state = "crisis_barbarian_leader" +} + +states.crisis_barbarian_leader = { + prompt() { + prompt("Crisis: " + EVENT_NAME[game.active_event] + ".") + gen_action_region(game.where) + }, + region(where) { + set_barbarian_leader_location(game.count, game.where) + goto_take_actions() + }, +} + +function set_barbarian_leader_location(id, where) { +} + +// CRISIS: RIVAL EMPEROR + +function goto_crisis_rival_emperor(id, where) { + game.count = id + game.where = where + game.state = "crisis_rival_emperor" +} + +states.crisis_rival_emperor = { + prompt() { + prompt("Crisis: " + EVENT_NAME[game.active_event] + ".") + gen_action_region(game.where) + }, + region(where) { + console.log("RE", game.count, game.where) + set_rival_emperor_location(game.count, game.where) + goto_take_actions() + }, +} + +// CRISIS: PALMYRA ALLIES + +function goto_palmyra_allies() { + game.count = roll_dice_no_reroll(4, 4) + resume_palmyra_allies() +} + +function resume_palmyra_allies() { + // TODO: barbarian leaders + if ( + (find_active_barbarian_of_tribe(SASSANIDS, GALATIA) >= 0) || + (find_active_barbarian_of_tribe(SASSANIDS, SYRIA) >= 0) || + (find_active_barbarian_of_tribe(SASSANIDS, SASSANIDS_HOMELAND) >= 0) + ) + game.state = "palmyra_allies" + else + goto_take_actions() +} + +states.palmyra_allies = { + prompt() { + prompt("Palmyra Allies: Remove " + game.count + " active Sassanids.") + let id + + // TODO: barbarian leaders + id = find_active_barbarian_of_tribe(SASSANIDS, GALATIA) + if (id >= 0) + gen_action_barbarian(id) + id = find_active_barbarian_of_tribe(SASSANIDS, SYRIA) + if (id >= 0) + gen_action_barbarian(id) + id = find_active_barbarian_of_tribe(SASSANIDS, SASSANIDS_HOMELAND) + if (id >= 0) + gen_action_barbarian(id) + }, + barbarian(id) { + push_undo() + eliminate_barbarian(id) + resume_palmyra_allies() + }, +} + +// CRISIS: LUDI SAECULARES + +function goto_ludi_saeculares() { + game.count = game.current // remember current player + let emperor = get_province_player(ITALIA) + if (emperor >= 0) { + game.current = emperor + game.state = "ludi_saeculares" + } else { + log("There is no Emperor.") + goto_take_actions() + } +} + +states.ludi_saeculares = { + prompt() { + prompt("Ludi Saeculares: Discard one card from your hand.") + for (let c of current_hand()) + gen_action_card(c) + }, + card(c) { + push_undo() + set_remove(current_hand(), c) + set_add(current_discard(), c) + award_legacy(game.current, "Ludi Saeculares", 2 * card_value(c)) + game.state = "ludi_saeculares_done" + }, +} + +states.ludi_saeculares_done = { + prompt() { + prompt("Ludi Saeculares: Discard one card from your hand.") + view.actions.done = 1 + }, + done() { + if (game.current !== game.count) + clear_undo() + game.current = game.count + goto_take_actions() + }, +} + // === TAKE ACTIONS === function goto_take_actions() { @@ -2213,13 +2414,13 @@ function can_foederati_from_region(where) { return false } -function gen_foederati(where, recruit) { +function gen_foederati(where) { let tribe_count = get_tribe_count() for (let tribe = 0; tribe < tribe_count; ++tribe) { let id = find_active_barbarian_of_tribe(where, tribe) if (id >= 0) gen_action_barbarian(id) - if (recruit || is_province(where)) { + if (is_province(where)) { id = find_inactive_barbarian_of_tribe(where, tribe) if (id >= 0) gen_action_barbarian(id) @@ -2258,10 +2459,9 @@ states.foederati = { prompt("Foederati: Remove or recruit a Barbarian.") view.selected_general = game.selected_general let from = get_general_location(game.selected_general) - let recruit = (game.selected_general >= 0 && count_legions_in_army(game.selected_general) > count_barbarians_in_army(game.selected_general)) - gen_foederati(from, recruit) + gen_foederati(from) for (let to of ADJACENT[from]) - gen_foederati(to, recruit) + gen_foederati(to) }, barbarian(id) { let tribe = get_barbarian_tribe(id) @@ -2709,7 +2909,6 @@ function has_hits_on_defender() { } function goto_assign_hits() { - // TODO: flanking maneuver goto_assign_hits_on_attacker() } @@ -2898,7 +3097,7 @@ function goto_support_check() { function is_any_rival_emperor_or_pretender() { for (let i = 0; i < 3; ++i) - if (game.rival_emperors[i] !== UNAVAILABLE) + if (get_rival_emperor_location(i) === UNAVAILABLE) return true for (let where = 0; where < 12; ++where) if (is_seat_of_power(where) && is_enemy_province(where)) @@ -3141,7 +3340,7 @@ states.buy_trash = { for (let m of game.market) { if (m.length > 0) { let c = m[0] - let cost = card_cost(c) + let cost = card_value(c) if (cost > nprov) cost *= 2 cost += game.count @@ -3161,7 +3360,7 @@ states.buy_trash = { log("Bought " + card_name(c)) set_add(current_discard(), c) set_delete(find_market_with_card(c), c) - let cost = card_cost(c) + let cost = card_value(c) if (cost > count_own_provinces()) cost *= 2 cost += game.count @@ -3376,7 +3575,7 @@ function setup_events() { let deck = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 ] shuffle(deck) // Shuffle Diocletian with last 3 cards - array_insert(deck, 11 + random(4), 15) + array_insert(deck, random(4), 15) return deck } @@ -3409,7 +3608,7 @@ exports.setup = function (seed, scenario, options) { state: "setup_province", first: 0, events: null, - active_events: [], + active_event: 0, ip: [], pp: 0, @@ -3572,7 +3771,7 @@ exports.view = function (state, player_name) { barbarian_leaders: game.barbarian_leaders, dice: game.dice, - events: game.active_events, + event: game.active_event, played: game.played, used: game.used, market: game.market.map(m => m[0] | 0), diff --git a/tools/gencards.js b/tools/gencards.js index ee88c70..1ca352d 100644 --- a/tools/gencards.js +++ b/tools/gencards.js @@ -8,8 +8,8 @@ const P = 2 const CARD_INDEX = [] const CARD_INFO = [] -function mk(n, type, cost, name, event) { - CARD_INFO[xx] = { name, type, cost, event } +function mk(n, type, value, name, event) { + CARD_INFO[xx] = { name, type, value, event } var a = ix var b = ix + n - 1 for (let i = 0; i < n; ++i) |