summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--play.js4
-rw-r--r--rules.js373
2 files changed, 104 insertions, 273 deletions
diff --git a/play.js b/play.js
index 0d9acc8..29d10e9 100644
--- a/play.js
+++ b/play.js
@@ -998,10 +998,6 @@ function sub_icon(match) {
return ICONS[match] || match
}
-function wrap_icons(match) {
- return `<span class="marginalia">${match.replace("(", "").replace(")", "")}</span>`
-}
-
const ICONS = {
'->': '\u2192',
'.dT5': '<span class="number d_tst">5</span>',
diff --git a/rules.js b/rules.js
index de921f4..ea20498 100644
--- a/rules.js
+++ b/rules.js
@@ -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()