summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--play.css23
-rw-r--r--play.html5
-rw-r--r--play.js27
-rw-r--r--rules.js289
-rw-r--r--tools/gencards.js4
5 files changed, 291 insertions, 57 deletions
diff --git a/play.css b/play.css
index d338b14..45e1c09 100644
--- a/play.css
+++ b/play.css
@@ -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)}
diff --git a/play.html b/play.html
index 7a1005a..5038688 100644
--- a/play.html
+++ b/play.html
@@ -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>
diff --git a/play.js b/play.js
index ef65b2f..8a5cf42 100644
--- a/play.js
+++ b/play.js
@@ -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) {
diff --git a/rules.js b/rules.js
index 9a3d6c8..9b51295 100644
--- a/rules.js
+++ b/rules.js
@@ -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)