diff options
author | Joël Simoneau <simoneaujoel@gmail.com> | 2024-12-23 11:42:40 -0500 |
---|---|---|
committer | Joël Simoneau <simoneaujoel@gmail.com> | 2024-12-23 11:42:40 -0500 |
commit | 74ecddba5a3c053b1dd57f0f4e330dca1dec0406 (patch) | |
tree | be408030d3d9cd43a1a29ba30ea114321443794e | |
parent | 2716d69eea22eb82bdf79cc0eb14c08b920c72c7 (diff) | |
download | vijayanagara-74ecddba5a3c053b1dd57f0f4e330dca1dec0406.tar.gz |
wip attack
-rw-r--r-- | images/die_gold_pips.svg | 37 | ||||
-rw-r--r-- | play.css | 73 | ||||
-rw-r--r-- | play.html | 13 | ||||
-rw-r--r-- | play.js | 35 | ||||
-rw-r--r-- | rules.js | 273 |
5 files changed, 416 insertions, 15 deletions
diff --git a/images/die_gold_pips.svg b/images/die_gold_pips.svg new file mode 100644 index 0000000..2ccc0bf --- /dev/null +++ b/images/die_gold_pips.svg @@ -0,0 +1,37 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="336" height="56"> +<g fill="#e7c70d" fill-opacity="0.8" stroke="black" stroke-width="1"> +<g transform="translate(0 0)"> +<circle r="6" cx="28" cy="28"/> +</g> +<g transform="translate(56 0)"> +<circle r="6" cx="12" cy="12"/> +<circle r="6" cx="44" cy="44"/> +</g> +<g transform="translate(112 0)"> +<circle r="6" cx="12" cy="44"/> +<circle r="6" cx="44" cy="12"/> +<circle r="6" cx="28" cy="28"/> +</g> +<g transform="translate(168 0)"> +<circle r="6" cx="12" cy="12"/> +<circle r="6" cx="12" cy="44"/> +<circle r="6" cx="44" cy="12"/> +<circle r="6" cx="44" cy="44"/> +</g> +<g transform="translate(224 0)"> +<circle r="6" cx="12" cy="12"/> +<circle r="6" cx="12" cy="44"/> +<circle r="6" cx="44" cy="12"/> +<circle r="6" cx="44" cy="44"/> +<circle r="6" cx="28" cy="28"/> +</g> +<g transform="translate(280 0)"> +<circle r="6" cx="12" cy="12"/> +<circle r="6" cx="12" cy="28"/> +<circle r="6" cx="12" cy="44"/> +<circle r="6" cx="44" cy="12"/> +<circle r="6" cx="44" cy="28"/> +<circle r="6" cx="44" cy="44"/> +</g> +</g> +</svg> @@ -62,6 +62,66 @@ main { background-color: #777; } } } +/* COMBAT TABLE */ + +.player_pool { + width: 500px; + background-color: #0003; + padding: 8px; + border-radius: 24px; + margin: 24px auto; +} + +.player_name { + color: antiquewhite; + text-shadow: 0 0 3px goldenrod; + font-size: 20px; + text-align: center; + font-style: italic; + font-family: "Source Serif"; + padding: 4px; +} + +.table_pool { + display: flex; + justify-content: center; + height: 36px; + gap: 12px; + align-items: center; + margin: 8px; +} + +.die { + width: 37px; + height: 37px; + border-width: 3px; + border-style: solid; + background-size: 600% 100%; + background-repeat: no-repeat; + background-image: url(images/die_gold_pips.svg); + box-shadow: 0 0 0 1px #333, 1px 2px 3px 1px #0004; +} + +.d0 { display: none; } +.d1 { background-position: 0% 0; } +.d2 { background-position: 20% 0; } +.d3 { background-position: 40% 0; } +.d4 { background-position: 60% 0; } +.d5 { background-position: 80% 0; } +.d6 { background-position: 100% 0; } + +.attacker .die { + background-color: #9e302f; + border-color: #9c3b3a #701d1b #701d1b #9c3b3a; +} + +.defender .die { + background-color: #c09e87; + border-color: #d6af95 #a18069 #a18069 #d6af95; +} + +.die.action { box-shadow: 0 0 0 1px #333, 0 0 0px 3px white; } + /* LOG */ #log .h1 { background-color: silver; font-weight: bold; padding-top:4px; padding-bottom:4px; margin: 8px 0; text-align: center; } #log .h1 { background-image: linear-gradient(60deg, gray, turquoise, gold); text-shadow: 0 0 4px white; } @@ -74,6 +134,19 @@ main { background-color: #777; } #log .h2.mi { background-color: #ebc9be } #log { font-variant-numeric: tabular-nums; } +#log .italic { font-style: italic; } +#log div { padding-left: 20px; text-indent: -12px; } +#log div.indent { padding-left: 32px; text-indent: -12px; } + +#log .adice { + font-size: 14px; + color: #9e302f; +} + +#log .ddice { + font-size: 14px; + color: #000000; +} /* MAP */ @@ -57,6 +57,19 @@ <div id="table"> +<div id="combat_table"> + + <div id="attacker" class="player_pool"> + <div id="name_attack" class="player_name">Attacker</div> + <div id="pool_a" class="table_pool attacker"></div> + </div> + <div id="defender" class="player_pool defender"> + <div id="pool_d" class="table_pool"></div> + <div id="name_defender" class="player_name">Defender</div> + </div> + +</div> + <div id="mapwrap"> <div id="map"> <svg id="svgmap" width="1275" height="1650" viewBox="0 0 1275 1650"> @@ -465,6 +465,11 @@ let ui = { document.getElementById("bk_cylinder"), document.getElementById("ve_cylinder"), ], + pool: [ + document.getElementById("pool_a"), + document.getElementById("pool_d"), + ], + dice: [] } function create(t, p, ...c) { @@ -603,6 +608,15 @@ function init_ui() { create_piece_list(MI, TROOPS, "piece mongol cube", 2, 0) + // dice + for (let d = 0; d < 6; ++d) { + let e = null + let pool = (d < 4) ? "pool_a" : "pool_d" + ui.dice[d] = e = create("div", { className: "die d0" }) + register_action(e, "die", d) + document.getElementById(pool).append(e) + } + } /* UPDATE */ @@ -907,7 +921,7 @@ function on_update() { let discs = [ ] for (let s = 0; s < data.spaces.length; ++s) { let xy - + if (s <= last_province) { items.length = discs.length = 0 filter_piece_list(discs, s, DS, DISC) @@ -1021,6 +1035,14 @@ function on_update() { for (let i = 0; i < ui.pieces.length; ++i) ui.pieces[i].classList.toggle("selected", set_has(view.who, i)) + // Update dice + for (let i = 0; i < 6; ++i) { + let v = view.dice[i] + if (v >= 0) + ui.dice[i].className = "die d" + v + ui.dice[i].classList.toggle("action", is_action("die", i)) + } + // Influence layout_influence() @@ -1041,12 +1063,14 @@ function on_update() { action_button("end_march", "End March") // Command buttons + action_button("attack", "Attack") action_button("migrate", "Migrate") action_button("end_migrate", "End Migrate") action_button("rally", "Rally") action_button("end_rally", "End Rally") action_button("rebel", "Rebel") action_button("end_rebel", "End Rebel") + action_button("roll", "Roll") // Decree buttons action_button("build", "Build") @@ -1072,6 +1096,7 @@ function on_update() { action_button("end_gifts", "Refuse Compromising Gifts") // Other buttons + action_button("roll", "Roll") action_button("end_cavalry", "End Cavalry Selection") action_button("next", "Next") @@ -1404,6 +1429,14 @@ function on_log(text) { text = text.substring(3) p.className = "indent italic" } + else if (text.match(/^\.ad/)) { + text = text.substring(4) + p.className += " adice" + } + else if (text.match(/^\.dd/)) { + text = text.substring(4) + p.className += " ddice" + } text = text.replace(/C(\d+)/g, sub_card) text = text.replace(/S(\d+)/g, sub_space) @@ -49,6 +49,10 @@ const last_province = S_TAMILAKAM const faction_name = [ "Delhi Sultanate", "Bahmani Kingdom", "Vijayanagara Empire", "Mongol Invaders" ] const faction_acronyms = [ "ds", "bk", "ve", "mi" ] +const AD = [ "0", '\u2776', '\u2777', '\u2778', '\u2779', '\u277A', '\u277B' ] +const DD = [ "0", '\u2460', '\u2461', '\u2462', '\u2463', '\u2464', '\u2465' ] + + exports.scenarios = [ "Standard", "Solo" ] exports.roles = function (scenario, _options) { @@ -107,6 +111,7 @@ exports.view = function (state, role) { rebel: game.rebel, order: game.order, who: {}, + dice: game.dice, } if (game.result) { @@ -215,6 +220,7 @@ exports.setup = function (seed, scenario, _options) { inf_shift: null, decree: null, vm: null, + dice: [0, 0, 0, 0, 0, 0] } if (scenario === "Solo") @@ -465,6 +471,7 @@ function goto_rebel() { function goto_tax() { init_decree("Tax") + game.decree.count = 1 game.state = "tax" } @@ -536,6 +543,7 @@ states.main_phase = { view.actions.end_of_turn = 1 } }, + attack: goto_attack, build: goto_build, campaign: goto_campaign, collect: goto_collect, @@ -761,7 +769,7 @@ states.strategic_assistance = { states.tax = { prompt() { - if (game.decree.n === 1) { + if (game.decree.count === 1) { view.prompt = `Tax: Collect ${tax_count()} from Controlled Prosperity and Temples.` gen_action_resources(VE) } else { @@ -774,7 +782,7 @@ states.tax = { let t = tax_count() add_resources(game.current, t) logi_resources(VE, t) - game.decree.n = 0 + game.decree.count = 0 }, end_tax: end_decree } @@ -880,11 +888,202 @@ function end_decree() { /* SHARED COMMANDS */ -function can_attack() {} +function can_attack() { + for (let s = first_space; s <= last_space; ++s) + if (can_attack_in_space(s)) + return true + return false +} -function can_attack_in_space(s) {} +function can_attack_in_space(s) { + if (!has_valid_attackers(s, game.current)) + return false -function goto_attack() {} + if (game.current === DS) { + if ( + can_attack_rebel_in_space(s, BK) || + can_attack_rebel_in_space(s, VE) || + has_piece(s, MI, TROOPS) + ) + return true + } else if (game.current === BK) { + if (count_faction_pieces(s, DS) > 0 || count_faction_pieces(s, VE) > 0) + return true + } else if (game.current === VE) { + if (count_faction_pieces(s, DS) > 0 || count_faction_pieces(s, BK) > 0) + return true + } + return false +} + +function can_attack_rebel_in_space(s, faction) { + if ( + count_pieces(s, faction, DISC) === 1 && + count_faction_pieces(s, faction) === 1 + ) + return true + + if (has_rebel(s, faction)) + return true + + return false +} + +function goto_attack() { + init_command("Attack") + game.cmd.attacker = game.current + game.state = "attack" +} + +function goto_attack_select() { + game.cmd.target = [0, 0, 0, 0] + if (game.current === DS) { + game.cmd.target[BK] += can_attack_rebel_in_space(game.cmd.where, BK) + game.cmd.target[VE] += can_attack_rebel_in_space(game.cmd.where, VE) + game.cmd.target[MI] += has_piece(game.cmd.where, MI, TROOPS) + } else if (game.current === BK) { + game.cmd.target[DS] += count_faction_pieces(game.cmd.where, DS) > 0 + game.cmd.target[VE] += count_faction_pieces(game.cmd.where, VE) > 0 + } else if (game.current === VE) { + game.cmd.target[DS] += count_faction_pieces(game.cmd.where, DS) > 0 + game.cmd.target[BK] += count_faction_pieces(game.cmd.where, BK) > 0 + } + + let n = game.cmd.target.reduce((a, c) => a + c, 0); + if (n === 1) { + game.cmd.target = game.cmd.target.indexOf(1) + game.state = "attack_space" + } else { + game.state = "attack_select" + } +} + +function roll_attack(faction_d) { + for (let d = 0; d < 6; ++d) + game.dice[d] = random(6) + 1 + + if (faction_d === BK && has_piece(game.cmd.where, BK, DISC)) + game.dice[3] = 0 +} + +function goto_attack_cavalry() { + game.cmd.clog = false + game.state = "attack_cavalry" +} + +function attack_use_cavalry(d) { + use_cavalry(game.current) + + let is_attacker = (game.current === game.cmd.attacker) + let is_a_die = (d < 4) + + if (is_a_die && is_attacker) { + charge_die(d) + } else if (!is_a_die && is_attacker) { + screen_die(d) + } else if (is_a_die && !is_attacker) { + screen_die(d) + } else if (!is_a_die && !is_attacker) { + charge_die[d] + } +} + +function charge_die(d) { + if (d < 4) { + logi(`.ad ${AD[game.dice[d]]} \u27f6 ${AD[game.dice[d]-1]} charged`) + } else { + logi(`.dd ${DD[game.dice[d]]} \u27f6 ${DD[game.dice[d]-1]} charged`) + } + game.dice[d] -= 1 +} + +function screen_die(d) { + if (d < 4) { + logi(`.ad ${AD[game.dice[d]]} screened`) + } else { + logi(`.dd ${DD[game.dice[d]]} screened`) + } + game.dice[d] = 0 +} + +states.attack = { + prompt() { + if (game.current === DS) + view.prompt = "Attack: Select Spaces with Rebels or Mongol Invaders." + else + view.prompt = "Attack: Select Provinces with enemy pieces." + + if (can_select_cmd_space(1) && can_attack()) { + for (let s = first_space; s <= last_space; ++s) { + if (!is_selected_cmd_space(s) && can_attack_in_space(s)) + gen_action_space(s) + } + } + }, + space(s) { + game.cmd.where = s + log_space(game.cmd.where, "Attack") + goto_attack_select() + } +} + +states.attack_select = { + prompt() { + view.prompt = "Attack: Choose a Faction to attack" + } +} + +states.attack_space = { + prompt() { + view.prompt = "Attack: " + + view.actions.roll = 1 + }, + roll() { + roll_attack() + log(">.ad " + game.dice.slice(0,4).map(d => AD[d]).join(" ")) + log(">.dd " + game.dice.slice(4).map(d => DD[d]).join(" ")) + log_br() + + goto_attack_cavalry() + } +} + +states.attack_cavalry = { + prompt() { + view.prompt = "Attack: Use cavalry to..." + + // game.cmd.attacker + // game.cmd.target + // game.current + // + + // STEP ? + // step 1 - attacker cavalry + // step 2 - defender calvary + + let curr_die = [0, 1, 2, 3, 4, 5] + // TODO: restrict range for MI player + + if (has_cavalry(game.current)) + for (let d of curr_die) + if (game.dice[d] > 1) + gen_action_die(d) + + view.actions.next = 1 + }, + die(d) { + if (!game.cmd.cavalry) { + log(`${faction_name[game.current]} is using Cavalry.`) + game.cmd.cavalry = true + } + + attack_use_cavalry(d) + }, + next() { + + } +} /* DELHI SULTANATE COMMANDS */ @@ -1105,6 +1304,7 @@ function goto_migrate_space() { } states.migrate = { + // TODO: Check Rebel status after migration prompt() { view.prompt = "Migrate: Select a Province as the Migrate destination." @@ -1305,13 +1505,13 @@ function collect_count() { function goto_collect() { init_decree("Collect") - game.decree.n = 1 + game.decree.count = 1 game.state = "collect" } states.collect = { prompt() { - if (game.decree.n > 0) { + if (game.decree.count > 0) { view.prompt = `Collect Tribute: Collect ${collect_count()} from half the Tributaries prosperity` gen_action_resources(DS) } else { @@ -1323,7 +1523,7 @@ states.collect = { let c = collect_count() add_resources(DS, c) logi_resources(DS, c) - game.decree.n = 0 + game.decree.count = 0 goto_cavalry(2, "collect") }, end_collect: end_decree, @@ -1444,7 +1644,7 @@ function can_build_in_space(s) { function goto_build() { init_decree("Build") - game.decree.n = 1 + game.decree.count = 1 game.state = "build" } @@ -1455,7 +1655,7 @@ states.build = { else if (game.current === VE) view.prompt = "Build: Select a Province with a Raja." - if (game.decree.n === 1) { + if (game.decree.count === 1) { for (let s = first_space; s <= last_space; ++s) { if (can_build_in_space(s)) gen_action_space(s) @@ -1473,7 +1673,7 @@ states.build = { place_piece(p, s) log_space(s, "Build") pop_summary() - game.decree.n = 0 + game.decree.count = 0 }, end_build: end_decree, } @@ -1658,12 +1858,13 @@ function trade_count() { function goto_trade() { init_decree("Trade") + game.decree.count = 1 game.state = "trade" } states.trade = { prompt() { - if (game.decree.n === 1) { + if (game.decree.count === 1) { view.prompt = `Trade: Collect ${trade_count()} from Provinces with your presence.` gen_action_resources(BK) } else { @@ -1675,7 +1876,7 @@ states.trade = { let t = trade_count() add_resources(game.current, t) logi_resources(BK, t) - game.decree.n = 0 + game.decree.count = 0 goto_cavalry(trade_cavalry_count(), "trade") }, end_trade: end_decree, @@ -1760,6 +1961,16 @@ function remove_tributary(s) { update_control() } +function has_rebel(s, faction) { + let p0 = first_piece[faction][ELITE] + let p1 = last_piece[faction][ELITE] + for (let p = p0; p <= p1; ++p) + if (piece_space(p) === s) + if (is_rebel(p)) + return true + return false +} + function is_rebel(p) { let faction = piece_faction(p) let piece_index = p - first_piece[faction][ELITE] @@ -1890,6 +2101,20 @@ function has_unmoved_piece(s, faction) { return unmoved } +function has_valid_attackers(s, faction) { + let valid_attacker = false + for_each_movable(faction, p => { + if (piece_space(p) === s) + valid_attacker = true + if (piece_space(p) === s || (has_piece(piece_space(p), faction, DISC) && SPACES[s].adjacent.includes(piece_space(p)))) + if (!game.cmd.pieces || game.cmd.pieces.length === 0) + valid_attacker = true + else if (!set_has(game.cmd.pieces, p)) + valid_attacker = true + }) + return valid_attacker +} + function gen_place_piece(faction, type) { for_each_piece(faction, type, p => { if (piece_space(p) === AVAILABLE) { @@ -2004,6 +2229,22 @@ function trade_cavalry_count() { return 3; } +function find_cavalry(faction) { + for (let c = 0; c <= LAST_CAVALRY; ++c) + if (game.cavalry[c] === faction) + return c + return -1 +} + +function has_cavalry(faction) { + return find_cavalry(faction) != -1 +} + +function use_cavalry(faction) { + let c = find_cavalry(faction) + game.cavalry[c] = AVAILABLE +} + /* INFLUENCE */ function add_influence(faction) { @@ -2127,6 +2368,10 @@ function gen_action(action, argument) { set_add(view.actions[action], argument) } +function gen_action_die(d) { + gen_action("die", d) +} + function gen_action_influence(faction) { gen_action("inf", faction) } @@ -2614,7 +2859,7 @@ function for_each_movable(faction, f) { if (faction === BK) for_each_piece(BK, ELITE, f) else if (faction === VE) - for_each_piece(BK, ELITE, f) + for_each_piece(VE, ELITE, f) else if (faction === DS) { for_each_piece(DS, TROOPS, f) for_each_piece(DS, ELITE, f) |