diff options
Diffstat (limited to 'rules.js')
-rw-r--r-- | rules.js | 373 |
1 files changed, 104 insertions, 269 deletions
@@ -544,7 +544,7 @@ states.place_starting_infl = { add_infl(space, 'available_ops') }, done() { - do_log_summary() + summary_flush() game.temp ++ game.available_ops = game.starting_infl[game.temp] change_player() @@ -552,7 +552,7 @@ states.place_starting_infl = { valid_spaces_setup() }, start() { - do_log_summary() + summary_flush() delete game.starting_infl new_turn() clear_undo() @@ -890,11 +890,11 @@ states.add_influence = { }, end_round() { push_undo() - do_log_summary() + summary_flush() end_round() }, done() { - do_log_summary() + summary_flush() reset_austria_hungary_border_reopened() game.state = 'resolve_opponent_event' }, @@ -1283,7 +1283,6 @@ states.the_crowd_turns_against_ceausescu_infl = { add_infl(space, 'vm_available_ops') }, done() { - do_log_summary() if (game.return !== game.active) { change_player() } @@ -1623,10 +1622,8 @@ states.support_loss = { remove_infl(space, 'available_ops') }, done() { - if (game.summary.length > 0) - do_log_summary() - else - logi('None') + // TODO: log "None" here? + summary_flush() change_player() log_h5('Victory Point') game.phase = 0 @@ -2252,7 +2249,6 @@ states.stasi_play_ceh = { function add_infl(space, ops) { push_undo() - let starting_control = check_control(space) // If AHBR - check AHBR conditions if (game.persistent_events.includes(C_AUSTRIA_HUNGARY_BORDER_REOPENED)) { @@ -2261,6 +2257,7 @@ function add_infl(space, ops) { } } + // TODO: move these checks to start of influence ops // Check Genscher if ( game.persistent_events.includes(C_GENSCHER) && @@ -2293,8 +2290,7 @@ function add_infl(space, ops) { game.demInfl[space]++ } - let end_control = check_control(space) - log_summary('£ in %' + space) /* + get_icons(starting_control,end_control))*/ + summary_influence(space) check_tyrant() // Check Austria Hungary Border Reopened is true and condition has been met @@ -2337,7 +2333,6 @@ function add_infl(space, ops) { function remove_infl(space, ops) { push_undo() - let starting_control = check_control(space) if (game.remove_opponent_infl === true) { if (game.active === COM) { @@ -2365,8 +2360,7 @@ function remove_infl(space, ops) { } } } - let end_control = check_control(space) - log_summary('£ from %' + space) /* + get_icons(starting_control,end_control))*/ + summary_influence(space) check_tyrant() game[ops]-- if (game.vm_influence_added && game.vm_influence_added[space] >= 0) { @@ -2381,12 +2375,8 @@ function do_sc(space) { clear_undo() let tear_gas_start = game.persistent_events.includes(C_TEAR_GAS) let the_wall_start = game.persistent_events.includes(C_THE_WALL) - //logi(`%${space}:`) - let starting_control = check_control(space) let roll = roll_d6() - - // Continue with Support Check Logic log(`%${space}: D${roll}`) @@ -2527,9 +2517,7 @@ function do_sc(space) { game.valid_spaces = game.valid_spaces.filter(id => id !== space) } } - let end_control = check_control(space) - //logi('%' + space + ': ' +change_infl + ' SP' + get_icons(starting_control, end_control)) - log(change_infl + ' SP for ' + game.active + '.') /* + get_icons(starting_control, end_control))*/ + log(change_infl + ' SP for ' + game.active + '.') check_tyrant_sc() } else { @@ -2797,61 +2785,6 @@ function check_com_control(space_id) { } } -function check_control(space) { - if (check_dem_control(space)) - return DEM - else if (check_com_control(space)) - return COM - else if (game.demInfl[space] > game.comInfl[space]) - return 'd_ahead' - else if (game.comInfl[space] > game.demInfl[space]) - return 'c_ahead' - else - return "none" -} -/* -function get_icons(starting_control, end_control) { - if (game.state === 'place_starting_infl') - return '' - else { - if (starting_control !== end_control) { - if (starting_control === DEM && end_control === COM) - return ' (£DC .to £CC)' - else if (starting_control === COM && end_control === DEM) - return ' (£CC .to £DC)' - else if (starting_control === 'd_ahead' && end_control === DEM) - return ' (£DU .to £DC)' - else if (starting_control === 'd_ahead' && end_control === COM) - return ' (£DU .to £CC)' - else if (starting_control === 'c_ahead' && end_control === DEM) - return ' (£CU .to £DC)' - else if (starting_control === 'c_ahead' && end_control === COM) - return ' (£CU .to £CC)' - else if (starting_control === DEM && end_control === 'd_ahead') - return ' (£DC .to £DU)' - else if (starting_control === DEM && end_control === 'none') - return ' (£DC .to £UU)' - else if (starting_control === DEM && end_control === 'c_ahead') - return ' (£DC .to £CU)' - else if (starting_control === COM && end_control === 'c_ahead') - return ' (£CC .to £CU)' - else if (starting_control === COM && end_control === 'none') - return ' (£CC .to £UU)' - else if (starting_control === COM && end_control === 'd_ahead') - return ' (£CC .to £DU)' - else if (starting_control === 'none' && end_control === DEM) - return ' (£UU .to £DC)' - else if (starting_control === 'none' && end_control === COM) - return ' (£UU .to £CC)' - else - return '' - } - else - return '' - } -} - */ - function do_tst_attempt() { let roll = roll_d6() logi(`D${roll}`) @@ -4005,19 +3938,25 @@ function new_turn() { // Remove events that only last one turn + let no_longer_in_effect = [] for (let e of one_turn_events) { if (game.persistent_events.includes(e)) { end_one_turn_event(e) + no_longer_in_effect.push(e) } } + if (game.prudence) { delete game.prudence - log_summary("C" + C_PRUDENCE) + no_longer_in_effect.push(C_PRUDENCE) } - if (game.summary.length > 0) { + + if (no_longer_in_effect.length > 0) { log('No longer in effect:') - pop_summary_i() + for (let c of no_longer_in_effect) + logi("C" + c) } + delete game.stasi_card delete game.stand_fast @@ -4084,7 +4023,6 @@ function new_turn() { function end_one_turn_event(event) { game.persistent_events = game.persistent_events.filter(n => n !== event) game.strategy_removed.push(event) - log_summary(`C${event}`) } function change_player() { @@ -4276,7 +4214,6 @@ function check_tyrant() { log('+2 VP C' + C_THE_TYRANT_IS_GONE + '.') game.vp += 2 if (check_vp()) { - do_log_summary() return } delete game.the_tyrant_is_gone @@ -4346,6 +4283,8 @@ function country_name(country) { // ======== LOG FUNCTIONS ============= function log(msg) { + if (game.summary.length > 0) + throw new Error("UNFLUSHED SUMMARY") game.log.push(msg) } @@ -4390,25 +4329,27 @@ function log_tst_8_banner() { function log_event(n) { log_br() if (cards[n].side === "C") - game.log.push(".E:C" + n + ".C") + log(".E:C" + n + ".C") else if (cards[n].side === "D") - game.log.push(".E:C" + n + ".D") + log(".E:C" + n + ".D") else - game.log.push(".E:C" + n + ".N") + log(".E:C" + n + ".N") } function log_br() { + if (game.summary.length > 0) + throw new Error("UNFLUSHED SUMMARY") if (game.log.length > 0 && game.log[game.log.length - 1] !== "") { game.log.push("") } } function logi(msg) { - game.log.push(">" + msg) + log(">" + msg) } function logii(msg) { - game.log.push(">>" + msg) + log(">>" + msg) } function log_h1(msg) { @@ -4441,16 +4382,11 @@ function log_h5(msg) { function log_gap(msg) { log_br() - game.log.push(msg) + log(msg) } function log_msg_gap(msg) { - game.log.push(msg) - log_br() -} - -function logi_msg_gap(msg) { - game.log.push(">" + msg) + log(msg) log_br() } @@ -4458,133 +4394,79 @@ function log_round() { log_h2(`Action Round ${game.round}`) } -function log_side() { - if (game.active === DEM) - log(".d") - else - log(".c") -} - -// ============= SUMMARY FUNCTIONS ============= +// ============= INFLUENCE SUMMARY ============= -function push_summary() { - if (game.summary) - throw "TOO MANY SUMMARIES" - game.summary = [] +function summary_influence(space) { + map_set(game.summary, space, map_get(game.summary, space, 0) + 1) } -function log_summary(msg) { - if (msg.startsWith('Added') || msg.startsWith('£')) { - for (let item of game.summary) { - if (item[1] === msg) { - item[0]++ - return - } - } - } - game.summary.push([1, msg]) -} - -function pop_summary() { +function summary_flush() { if (game.summary.length > 0) { - for (let [n, msg] of game.summary) { - if (n > 1) { - log(msg.replace("£ SP", `${n}`)) - } else { - log(msg.replace("£ SP", `${n}`)) - } - } + map_for_each(game.summary, (space, n) => { + game.log.push(">" + n + " %" + space) + }) + map_clear(game.summary) + } else { + logi("None") } - game.summary = [] } -function pop_summary_i() { - if (game.summary.length > 0) { - for (let [n, msg] of game.summary) { - logi(msg.replace("£", `${n}`)) - } - } - game.summary = [] -} -/* -function pop_summary_i() { - console.log('game.summary', game.summary) - if (game.summary.length > 0) { - // Create a map to group by space and track totals and details - let grouped_summary = new Map() - let c63_logged = false - let deferred_logs = [] - - for (let [ n, msg ] of game.summary) { - // Special case: Log C63 only once - if (msg === '(-1 Op due to C63)') { - if (!c63_logged) { - logi(msg) - c63_logged = true - } - continue - } - - // Direct log for messages like C50, C123 - if (!/^£/.test(msg)) { - if (msg.includes('VP')) { - deferred_logs.push(msg) - } else { - logi(msg) - } - } - - // Extract the space identifier (e.g., %33) and type (e.g., "in" or "from") - let space_match = msg.match(/(in|from) (%\d+)/) - if (!space_match) - continue // Skip if no valid match - - let type = space_match[1] - let space = space_match[2] +// ============ MAP AND SET FUNCTIONS =========== - // Extract additional details (e.g., £DC .to £UU) - let details_match = msg.match(/\((.*?)\)$/) - let details = details_match ? details_match[1] : '' +// Array remove and insert (faster than splice) - // Split the details into "start" and "end" parts - let [ start, end ] = details.split(' .to ') +function array_insert_pair(array, index, key, value) { + for (let i = array.length; i > index; i -= 2) { + array[i] = array[i-2] + array[i+1] = array[i-1] + } + array[index] = key + array[index+1] = value +} - // Group by type and space - let key = `${type} ${space}` - if (!grouped_summary.has(key)) { - grouped_summary.set(key, { total: 0, start: '', end: '' }) - } - let entry = grouped_summary.get(key) - entry.total += n +// Map as plain sorted array of key/value pairs - // Update the start only if it's not already set - if (start && !entry.start) { - entry.start = start - } +function map_clear(map) { + map.length = 0 +} - // Always update the end with the most recent value - if (end) { - entry.end = end - } - } +function map_get(map, key, missing) { + let a = 0 + let b = (map.length >> 1) - 1 + while (a <= b) { + let m = (a + b) >> 1 + let x = map[m<<1] + if (key < x) + b = m - 1 + else if (key > x) + a = m + 1 + else + return map[(m<<1)+1] + } + return missing +} - // Log the grouped results - for (let [ key, { total, start, end } ] of grouped_summary) { - let details = start && end ? ` (${start} .to ${end})` : '' - logi(`${total} ${key}${details}`) - } - for (let msg of deferred_logs) { - logi(msg) +function map_set(map, key, value) { + let a = 0 + let b = (map.length >> 1) - 1 + while (a <= b) { + let m = (a + b) >> 1 + let x = map[m<<1] + if (key < x) + b = m - 1 + else if (key > x) + a = m + 1 + else { + map[(m<<1)+1] = value + return } } - - game.summary = [] + array_insert_pair(map, a<<1, key, value) } -*/ -function do_log_summary() { - if (game.summary.length > 0) { - pop_summary_i() - } + +function map_for_each(map, f) { + for (let i = 0; i < map.length; i += 2) + f(map[i], map[i+1]) } // ============ UNDO FUNCTIONS ================== @@ -4672,6 +4554,9 @@ function vm_inst(a) { } function vm_next() { + if (game.summary.length > 0) + summary_flush() + //throw new Error("UNFLUSHED SUMMARY IN EVENT " + game.vm.fp) game.vm.ip++ vm_exec() } @@ -4681,16 +4566,6 @@ function vm_log() { vm_next() } -function vm_logi() { - logi(vm_operand(1)) - vm_next() -} - -function vm_logi_msg_gap() { - logi_msg_gap(vm_operand(1)) - vm_next() -} - function vm_operand(a) { let x = CODE[game.vm.fp][game.vm.ip][a] if (a > 0 && typeof x === "function") @@ -5008,7 +4883,6 @@ function vm_take_control_prep() { } function vm_take_control(space) { - let starting_control = check_control(space) if (game.active === DEM) { let current_infl = game.demInfl[space] let opponent_infl = game.comInfl[space] @@ -5027,23 +4901,20 @@ function vm_take_control(space) { } } game.valid_spaces = game.valid_spaces.filter(id => id !== space) - let end_control = check_control(space) if (game.state === 'vm_kremlin_coup_take_control') - logi('Took control of %' + space + '.') /*+ get_icons(starting_control, end_control)) */ + logi('Took control of %' + space + '.') else - log('Took control of %' + space +'.') /*+ get_icons(starting_control, end_control))*/ + log('Took control of %' + space + '.') } function vm_do_add_infl_free(space) { push_undo() - let starting_control = check_control(space) if (game.active === COM) { game.comInfl[space]++ } else { game.demInfl[space]++ } - let end_control = check_control(space) - log_summary('£ in %' + space) /*+ get_icons(starting_control,end_control))*/ + summary_influence(space) game.vm_available_ops-- check_tyrant() } @@ -5073,15 +4944,12 @@ function vm_add_x_infl() { function vm_do_add_x_infl(space) { push_undo() - let starting_control = check_control(space) - if (game.active === COM) { game.comInfl[space] += game.vm_available_ops } else { game.demInfl[space] += game.vm_available_ops } - let end_control = check_control(space) - logi(`${game.vm_available_ops} in %${space}`) /*${get_icons(starting_control, end_control)}`)*/ + logi(`${game.vm_available_ops} %${space}`) check_tyrant() game.vm_available_ops = 0 game.valid_spaces = [] @@ -5096,7 +4964,6 @@ function vm_add_limited_infl() { function vm_do_add_limited_infl(space, max_infl) { push_undo() - let starting_control = check_control(space) game.vm_available_ops -- if (!game.vm_influence_added) { @@ -5113,8 +4980,7 @@ function vm_do_add_limited_infl(space, max_infl) { game.demInfl[space] ++ } - let end_control = check_control(space) - log_summary('£ in %' + space) /*+ get_icons(starting_control,end_control))*/ + summary_influence(space) game.vm_influence_added[space] ++ if (game.vm_influence_added[space] === max_infl) { @@ -5151,7 +5017,6 @@ function vm_remove_x_opp_infl() { function vm_do_remove_x_infl(space) { push_undo() - let starting_control = check_control(space) if (game.remove_opponent_infl) { if (game.active === COM) { if (game.demInfl[space] >= game.vm_available_ops) { @@ -5185,8 +5050,7 @@ function vm_do_remove_x_infl(space) { } } } - let end_control = check_control(space) - logi(`${game.vm_available_ops} from %${space}`) /*${get_icons(starting_control, end_control)}`)*/ + logi(`${game.vm_available_ops} from %${space}`) check_tyrant() game.vm_available_ops = 0 game.valid_spaces = [] @@ -5202,7 +5066,6 @@ function vm_remove_limited_opp_infl() { function vm_do_remove_limited_infl(space, max_infl) { push_undo() - let starting_control = check_control(space) game.vm_available_ops -- if (!game.vm_influence_added) { @@ -5231,8 +5094,7 @@ function vm_do_remove_limited_infl(space, max_infl) { game.valid_spaces = game.valid_spaces.filter(id => id !== space) } - let end_control = check_control(space) - log_summary('£ from %' + space) /*+ get_icons(starting_control,end_control))*/ + summary_influence(space) check_tyrant() if (game.vm_available_ops === 0) { game.valid_spaces = [] @@ -5246,28 +5108,23 @@ function vm_remove_all_infl() { function vm_do_remove_all_infl(space) { push_undo() - let starting_control = check_control(space) if (game.remove_opponent_infl === true) { if (game.active === COM) { game.demInfl[space] = 0 - let end_control = check_control(space) - log(`Removed all Democratic SP from %${space}`) /*${get_icons(starting_control, end_control)}`)*/ + log(`Removed all Democratic SP from %${space}.`) } else { game.comInfl[space] = 0 - let end_control = check_control(space) - log(`Removed all Communist SP from %${space}`) /*${get_icons(starting_control, end_control)}`)*/ + log(`Removed all Communist SP from %${space}.`) } check_tyrant() } else { if (game.active === COM) { game.comInfl[space] = 0 - let end_control = check_control(space) - log(`Removed all Communist SP from %${space}`) /*${get_icons(starting_control, end_control)}`)*/ + log(`Removed all Communist SP from %${space}.`) } else { game.demInfl[space] = 0 - let end_control = check_control(space) - log(`Removed all Democratic SP from %${space}`) /*${get_icons(starting_control, end_control)}`)*/ + log(`Removed all Democratic SP from %${space}.`) } check_tyrant() } @@ -5317,11 +5174,9 @@ function vm_support_check_modified() { function vm_switch_infl(space) { push_undo() - let starting_control = check_control(space) game.demInfl[space] -= game.vm_available_ops game.comInfl[space] += game.vm_available_ops - let end_control = check_control(space) - log(`Replaced ${pluralize(game.vm_available_ops,'SP')} in %${space}.`) /* ${get_icons(starting_control,end_control)}`)*/ + log(`Replaced ${pluralize(game.vm_available_ops,'SP')} in %${space}.`) game.vm_available_ops = 0 check_tyrant() } @@ -6384,20 +6239,17 @@ states.vm_add_infl = { add_infl(space, 'vm_available_ops') if (game.vm_available_ops === 0) { game.valid_spaces = [] - do_log_summary() game.vm_event_done = true vm_next() } }, done() { push_undo() - do_log_summary() game.vm_event_done = true vm_next() }, end_round() { push_undo() - do_log_summary() game.vm_event_done = true vm_next() }, @@ -6422,7 +6274,6 @@ states.vm_add_infl_free = { vm_do_add_infl_free(space) if (game.vm_available_ops === 0) { game.valid_spaces = [] - do_log_summary() game.vm_event_done = true vm_next() } @@ -6431,14 +6282,12 @@ states.vm_add_infl_free = { push_undo() game.valid_spaces = [] game.vm_event_done = true - do_log_summary() vm_next() }, end_round() { push_undo() game.valid_spaces = [] game.vm_event_done = true - do_log_summary() vm_next() }, } @@ -6489,7 +6338,6 @@ states.vm_add_limited_infl = { vm_do_add_limited_infl(space, game.vm_max_infl) if (game.vm_available_ops === 0 || game.valid_spaces.length === 0) { game.valid_spaces = [] - do_log_summary() game.vm_event_done = true vm_next() } @@ -6528,17 +6376,12 @@ states.vm_remove_infl = { let require_done = [C_INFLATIONARY_CURRENCY, C_THE_WALL_MUST_GO] if (!require_done.includes(game.vm_event)) { if (game.vm_available_ops === 0) { - do_log_summary() vm_next() } } }, done() { - if (game.summary.length > 0) { - pop_summary_i() - } else { - log('No influence to remove') - } + summary_flush() vm_next() }, } @@ -6586,13 +6429,11 @@ states.vm_remove_limited_infl = { vm_do_remove_limited_infl(space, game.vm_max_infl) if (game.vm_available_ops === 0) { game.vm_event_done = true - do_log_summary() vm_next() } }, done() { game.vm_event_done = true - do_log_summary() vm_next() }, } @@ -7852,7 +7693,6 @@ states.vm_nomenklatura_add = { vm_do_add_infl_free(space) if (game.vm_available_ops === 0) { game.valid_spaces = [] - do_log_summary() vm_next() } }, @@ -8272,13 +8112,11 @@ states.vm_we_are_the_people_remove = { if (game.vm_influence_added[S_LUTHERAN_CHURCH] === 4 || game.valid_spaces.length === 0 ) { - do_log_summary() finish_we_are_the_people() } }, done() { push_undo() - do_log_summary() if (!game.vm_influence_added[S_LUTHERAN_CHURCH]) { logi('None') vm_next() @@ -8298,7 +8136,6 @@ states.vm_we_are_the_people_add = { vm_do_add_limited_infl(space, game.vm_max_infl) if (game.vm_available_ops === 0) { game.valid_spaces = [] - do_log_summary() vm_next() } }, @@ -8437,12 +8274,10 @@ states.vm_tst_4 = { space(space) { remove_infl(space, 'vm_available_ops') if (game.vm_available_ops === 0) { - do_log_summary() vm_next() } }, done() { - do_log_summary() vm_next() }, } @@ -8597,7 +8432,7 @@ states.vm_scare_tactics = { remove_infl(space, 'vm_available_ops') }, done() { - do_log_summary() + summary_flush() log('Loses initiative.') log_gap(`Round ${game.ps_round}:`) vm_next() |