summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--play.html4
-rw-r--r--play.js39
-rw-r--r--rules.js263
3 files changed, 246 insertions, 60 deletions
diff --git a/play.html b/play.html
index ba769b4..4fae6ed 100644
--- a/play.html
+++ b/play.html
@@ -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); }
diff --git a/play.js b/play.js
index eb03ad7..7e5a4b5 100644
--- a/play.js
+++ b/play.js
@@ -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])
diff --git a/rules.js b/rules.js
index c31415a..4baaa3c 100644
--- a/rules.js
+++ b/rules.js
@@ -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)