diff options
-rw-r--r-- | play.html | 4 | ||||
-rw-r--r-- | play.js | 39 | ||||
-rw-r--r-- | rules.js | 263 |
3 files changed, 246 insertions, 60 deletions
@@ -347,11 +347,11 @@ body.shift .mustered_vassals { } .asset.action { - box-shadow: 0 0 0 1px #444, 0 0 0 3px white; + box-shadow: 0 0 0 1px #444, 0 0 0 3px white !important; } .asset.selected { - box-shadow: 0 0 0 1px #444, 0 0 0 3px yellow; + box-shadow: 0 0 0 1px #444, 0 0 0 3px yellow !important; } .asset.boat.x1 { background-image: url(images/asset_boat_x1.png); } @@ -1,5 +1,9 @@ "use strict" +// TODO: tooltip on cylinders +// fealty rating and starting assets + forces on calendar +// current assets and forces on map + // TODO: held events, this_turn events const MAP_DPI = 75 @@ -31,6 +35,16 @@ const SLED = 4 const BOAT = 5 const SHIP = 6 +const on_click_asset = [ + (evt) => evt.button === 0 && send_action('prov', evt.target.my_id), + (evt) => evt.button === 0 && send_action('coin', evt.target.my_id), + (evt) => evt.button === 0 && send_action('loot', evt.target.my_id), + (evt) => evt.button === 0 && send_action('cart', evt.target.my_id), + (evt) => evt.button === 0 && send_action('sled', evt.target.my_id), + (evt) => evt.button === 0 && send_action('boat', evt.target.my_id), + (evt) => evt.button === 0 && send_action('ship', evt.target.my_id), +] + const SUMMER = 0 const EARLY_WINTER = 1 const LATE_WINTER = 2 @@ -117,6 +131,10 @@ function is_lord_action(lord) { return !!(view.actions && view.actions.lord && set_has(view.actions.lord, lord)) } +function is_asset_action(lord, action) { + return !!(view.actions && view.actions[action] && set_has(view.actions[action], lord)) +} + function is_plan_action(lord) { return !!(view.actions && view.actions.plan && set_has(view.actions.plan, lord)) } @@ -424,7 +442,7 @@ function on_focus_cylinder(evt) { function on_click_lord_service_marker(evt) { if (evt.button === 0) { let id = evt.target.my_id - send_action('lord_service', id) + send_action('service', id) } } @@ -670,9 +688,12 @@ function add_force(parent, type) { build_div(parent, "unit " + force_type_name[type], type) } -function add_asset(parent, type, n) { +function add_asset(parent, type, n, lord) { // TODO: reuse pool of elements? - build_div(parent, "asset " + asset_type_name[type] + " x"+n, type) + if (is_asset_action(lord, asset_type_name[type])) + build_div(parent, "action asset " + asset_type_name[type] + " x"+n, lord, on_click_asset[type]) + else + build_div(parent, "asset " + asset_type_name[type] + " x"+n, lord, on_click_asset[type]) } function add_veche_vp(parent) { @@ -690,26 +711,26 @@ function update_forces(parent, forces) { } } -function update_assets(parent, assets) { +function update_assets(id, parent, assets) { parent.replaceChildren() for (let i = 0; i < asset_type_count; ++i) { let n = pack4_get(assets, i) while (n >= 4) { - add_asset(parent, i, 4) + add_asset(parent, i, 4, id) n -= 4 } if (asset_type_x3[i]) { while (n >= 3) { - add_asset(parent, i, 3) + add_asset(parent, i, 3, id) n -= 3 } } while (n >= 2) { - add_asset(parent, i, 2) + add_asset(parent, i, 2, id) n -= 2 } while (n >= 1) { - add_asset(parent, i, 1) + add_asset(parent, i, 1, id) n -= 1 } } @@ -730,7 +751,7 @@ function update_vassals(ready_parent, mustered_parent, lord_ix) { } function update_lord_mat(ix) { - update_assets(ui.assets[ix], view.lords.assets[ix]) + update_assets(ix, ui.assets[ix], view.lords.assets[ix]) update_vassals(ui.ready_vassals[ix], ui.mustered_vassals[ix], ix) update_forces(ui.forces[ix], view.lords.forces[ix]) update_forces(ui.routed[ix], view.lords.routed_forces[ix]) @@ -10,7 +10,7 @@ const P1 = TEUTONS const P2 = RUSSIANS // NOTE: With Hidden Mats option, the player order of feed/pay may matter. -const FEED_PAY_DISBAND = false // feed, pay, disband in one go +const FEED_PAY_DISBAND = true // feed, pay, disband in one go let game = null let view = null @@ -306,10 +306,6 @@ function get_lord_routed_forces(lord, n) { return pack4_get(game.lords.routed_forces[lord], n) } -function get_lord_moved(lord) { - return pack1_get(game.lords.moved, lord) -} - function set_lord_locale(lord, locale) { game.lords.locale[lord] = locale } @@ -317,8 +313,8 @@ function set_lord_locale(lord, locale) { function set_lord_service(lord, service) { if (service < 0) service = 0 - if (service > 16) - service = 16 + if (service > 17) + service = 17 game.lords.service[lord] = service } @@ -362,10 +358,6 @@ function add_lord_routed_forces(lord, n, x) { set_lord_routed_forces(lord, n, get_lord_routed_forces(lord, n) + x) } -function set_lord_moved(lord, x) { - game.lords.moved = pack1_set(game.lords.moved, lord, x) -} - function get_lord_vassal_count(lord) { return data.lords[lord].vassals.length } @@ -380,6 +372,33 @@ function set_lord_vassal_service(lord, n, x) { game.vassals[v] = x } +function clear_lords_moved() { + game.lords.moved = 0 +} + +function get_lord_moved(lord) { + return pack2_get(game.lords.moved, lord) +} + +function set_lord_moved(lord, x) { + game.lords.moved = pack2_set(game.lords.moved, lord, x) +} + +function set_lord_unfed(lord, n) { + // reuse "moved" flag for hunger + set_lord_moved(lord, n) +} + +function is_lord_unfed(lord) { + // reuse "moved" flag for hunger + return get_lord_moved(lord) +} + +function feed_lord(lord) { + // reuse "moved" flag for hunger + set_lord_moved(lord, get_lord_moved(lord) - 1) +} + // === GAME STATE HELPERS === function roll_die() { @@ -732,9 +751,9 @@ exports.setup = function (seed, scenario, options) { forces: Array(lord_count).fill(0), routed_forces: Array(lord_count).fill(0), cards: Array(lord_count << 1).fill(NOTHING), - lieutenants: [], - besieged: 0, moved: 0, + besieged: 0, + lieutenants: [], }, vassals: Array(vassal_count).fill(0), legate: NOWHERE, @@ -1023,7 +1042,7 @@ states.setup_lords = { } function end_setup_lords() { - game.lords.moved = 0 + clear_lords_moved() set_active_enemy() if (game.active === P1) { log_h1("Levy " + current_turn_name()) @@ -1198,7 +1217,7 @@ function goto_levy_muster() { } function end_levy_muster() { - game.lords.moved = 0 + clear_lords_moved() set_active_enemy() if (game.active === P2) goto_levy_muster() @@ -1712,6 +1731,8 @@ function goto_actions() { function end_actions() { set_active(P1) + game.command = NOBODY + game.who = NOBODY goto_feed() } @@ -1752,6 +1773,7 @@ states.actions = { }, forage: do_action_forage, ravage: do_action_ravage, + march: do_action_march, pass() { clear_undo() end_actions() @@ -1765,7 +1787,13 @@ states.actions = { // === ACTION: MARCH === function can_action_march() { - return false + return true +} + +function do_action_march() { + push_undo() + set_lord_moved(game.who, 1) + ++game.count } // === ACTION: SIEGE === @@ -1892,74 +1920,189 @@ function can_action_sail() { // === CAMPAIGN: FEED === -function has_friendly_lord_who_moved_or_fought() { +function can_feed_own(lord) { + return get_lord_assets(lord, PROV) > 0 || get_lord_assets(lord, LOOT) > 0 +} + +function can_feed_from_shared(lord) { + let loc = get_lord_locale(lord) + return get_shared_assets(loc, PROV) > 0 || get_shared_assets(loc, LOOT) > 0 +} + +function has_friendly_lord_who_must_feed_own() { for (let lord = first_friendly_lord; lord <= last_friendly_lord; ++lord) - if (get_lord_moved(lord)) + if (is_lord_unfed(lord) && can_feed_own(lord)) + return true + return false +} + +function has_friendly_lord_who_must_feed_shared() { + for (let lord = first_friendly_lord; lord <= last_friendly_lord; ++lord) + if (is_lord_unfed(lord) && can_feed_from_shared(lord)) return true return false } function goto_feed() { - game.state = "feed" - if (!has_friendly_lord_who_moved_or_fought()) - end_feed() + // Count how much food each lord needs + for (let lord = 0; lord <= last_lord; ++lord) + if (get_lord_moved(lord)) + set_lord_unfed(lord, count_lord_forces(lord) / 6 + 1 | 0) + goto_feed_own() +} + +function goto_feed_own() { + game.state = "feed_own" + if (!has_friendly_lord_who_must_feed_own()) + end_feed_own() +} + +function end_feed_own() { + goto_feed_shared() +} + +function goto_feed_shared() { + game.state = "feed_shared" + if (!has_friendly_lord_who_must_feed_shared()) + end_feed_shared() } -// TODO: feed_self -// TODO: feed_other +function end_feed_shared() { + goto_unfed() +} + +function resume_feed_lord_own() { + if (!is_lord_unfed(game.who) || !can_feed_own(game.who)) + goto_feed_own() +} -states.feed = { +function resume_feed_lord_shared() { + if (!is_lord_unfed(game.who) || !can_feed_from_shared(game.who)) + goto_feed_shared() +} + +states.feed_own = { prompt() { view.prompt = "You must Feed lords who Moved or Fought." - for (let lord = first_friendly_lord; lord <= last_friendly_lord; ++lord) - if (get_lord_moved(lord)) + let done = true + for (let lord = first_friendly_lord; lord <= last_friendly_lord; ++lord) { + if (is_lord_unfed(lord) && can_feed_own(lord)) { gen_action_lord(lord) - view.actions.end_feed = 1 + done = false + } + } }, lord(lord) { push_undo() game.who = lord - game.count = ((count_lord_forces(lord) / 6) | 0) + 1 - game.state = "feed_lord" + game.state = "feed_lord_own" }, - end_feed() { - clear_undo() - end_feed() +} + +states.feed_shared = { + prompt() { + view.prompt = "You must Feed lords who Moved or Fought (shared)." + let done = true + for (let lord = first_friendly_lord; lord <= last_friendly_lord; ++lord) { + if (get_lord_moved(lord) > 0 && can_feed_from_shared(lord)) { + gen_action_lord(lord) + done = false + } + } + }, + lord(lord) { + push_undo() + game.who = lord + game.state = "feed_lord_shared" }, } -states.feed_lord = { +states.feed_lord_own = { prompt() { - view.prompt = "You must Feed ${lord_name[game.who]} ${game.count}x Loot or Provender." - // TODO: find loot or prov! - view.actions.unfed = 1 + view.prompt = `You must Feed ${lord_name[game.who]} ${game.count}x own Loot or Provender.` + if (get_lord_assets(game.who, PROV) > 0) + gen_action_prov(game.who) + if (get_lord_assets(game.who, LOOT) > 0) + gen_action_loot(game.who) + }, + prov(lord) { + logi(`Fed L${game.who}.`) + add_lord_assets(lord, PROV, -1) + feed_lord(game.who) + resume_feed_lord_own() }, loot(lord) { - logi(`Fed L${game.who} with Loot from L${lord}.`) + logi(`Fed L${game.who} with Loot.`) add_lord_assets(lord, LOOT, -1) - if (--game.count === 0) - game.state = "feed" + feed_lord(game.who) + resume_feed_lord_own() + }, +} + +states.feed_lord_shared = { + prompt() { + view.prompt = `You must Feed ${lord_name[game.who]} ${game.count}x shared Loot or Provender.` + let loc = get_lord_locale(game.who) + for (let lord = first_friendly_lord; lord <= last_friendly_lord; ++lord) { + if (get_lord_locale(lord) === loc) { + if (get_lord_assets(lord, PROV) > 0) + gen_action_prov(lord) + if (get_lord_assets(lord, LOOT) > 0) + gen_action_loot(lord) + } + } }, prov(lord) { - logi(`Fed L${game.who} with Provender from L${lord}.`) + logi(`Fed L${game.who} from L${lord}.`) add_lord_assets(lord, PROV, -1) - if (--game.count === 0) - game.state = "feed" + feed_lord(game.who) + resume_feed_lord_shared() }, - unfed() { - logi(`Did not feed L${game.who}.`) + loot(lord) { + logi(`Fed L${game.who} with Loot from L${lord}.`) + add_lord_assets(lord, LOOT, -1) + feed_lord(game.who) + resume_feed_lord_shared() + }, +} + +// === LEVY & CAMPAIGN: FEED (UNFED) === + +function has_friendly_lord_who_is_unfed() { + for (let lord = first_friendly_lord; lord <= last_friendly_lord; ++lord) + if (is_lord_unfed(lord)) + return true + return false +} + +function goto_unfed() { + game.state = "unfed" + if (!has_friendly_lord_who_is_unfed()) + end_unfed() +} + +states.unfed = { + prompt() { + view.prompt = "Shift the Service marker for any unfed lords." + for (let lord = first_friendly_lord; lord <= last_friendly_lord; ++lord) + if (is_lord_unfed(lord)) + gen_action_service(lord) + }, + service(lord) { + logi(`Unfed L${lord}.`) add_lord_service(game.who, -1) - game.state = "feed" + set_lord_unfed(lord, 0) + goto_unfed() }, } -function end_feed() { +function end_unfed() { if (FEED_PAY_DISBAND) { goto_pay() } else { set_active_enemy() if (game.active === P2) - goto_feed() + goto_feed_own() else goto_pay() } @@ -2093,7 +2236,7 @@ function end_disband() { set_active_enemy() if (game.active === P2) { if (FEED_PAY_DISBAND) - goto_feed() + goto_feed_own() else goto_pay() } else { @@ -2111,7 +2254,7 @@ function goto_remove_markers() { if (game.events.length > 0) game.events = game.events.filter((c) => data.cards[c].when !== "this_campaign") - game.lords.moved = 0 + clear_lords_moved() goto_command_activation() } @@ -2220,6 +2363,18 @@ function gen_action_plan(lord) { gen_action("plan", lord) } +function gen_action_prov(lord) { + gen_action("prov", lord) +} + +function gen_action_coin(lord) { + gen_action("coin", lord) +} + +function gen_action_loot(lord) { + gen_action("loot", lord) +} + exports.view = function (state, current) { load_state(state) @@ -2305,6 +2460,11 @@ function pack1_get(word, n) { return (word >>> n) & 1 } +function pack2_get(word, n) { + n = n << 1 + return (word >>> n) & 3 +} + function pack4_get(word, n) { n = n << 2 return (word >>> n) & 15 @@ -2314,6 +2474,11 @@ function pack1_set(word, n, x) { return (word & ~(1 << n)) | (x << n) } +function pack2_set(word, n, x) { + n = n << 1 + return (word & ~(3 << n)) | (x << n) +} + function pack4_set(word, n, x) { n = n << 2 return (word & ~(15 << n)) | (x << n) |