From a6f5d220f496829911ac3011cdbbcc045f057b63 Mon Sep 17 00:00:00 2001 From: Tor Andersson Date: Thu, 24 Jun 2021 14:01:24 +0200 Subject: crusader: Many fixes. --- badges/Cross_of_the_Knights_Templar.svg | 1 + badges/Star_and_Crescent.svg | 1 + badges/camping-tent.svg | 1 + badges/stone-tower.svg | 1 + badges/trebuchet.svg | 1 + besieged.svg | 1 - besieging.svg | 1 - jihad-christian.svg | 1 - jihad-muslim.svg | 1 - play.html | 36 +++++-- rules.js | 180 ++++++++++++++++++-------------- ui.js | 18 +++- 12 files changed, 149 insertions(+), 94 deletions(-) create mode 100644 badges/Cross_of_the_Knights_Templar.svg create mode 100644 badges/Star_and_Crescent.svg create mode 100644 badges/camping-tent.svg create mode 100644 badges/stone-tower.svg create mode 100644 badges/trebuchet.svg delete mode 100644 besieged.svg delete mode 100644 besieging.svg delete mode 100644 jihad-christian.svg delete mode 100644 jihad-muslim.svg diff --git a/badges/Cross_of_the_Knights_Templar.svg b/badges/Cross_of_the_Knights_Templar.svg new file mode 100644 index 0000000..d96e204 --- /dev/null +++ b/badges/Cross_of_the_Knights_Templar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/badges/Star_and_Crescent.svg b/badges/Star_and_Crescent.svg new file mode 100644 index 0000000..e2d7cb9 --- /dev/null +++ b/badges/Star_and_Crescent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/badges/camping-tent.svg b/badges/camping-tent.svg new file mode 100644 index 0000000..832d8b8 --- /dev/null +++ b/badges/camping-tent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/badges/stone-tower.svg b/badges/stone-tower.svg new file mode 100644 index 0000000..7885d8d --- /dev/null +++ b/badges/stone-tower.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/badges/trebuchet.svg b/badges/trebuchet.svg new file mode 100644 index 0000000..aff8da2 --- /dev/null +++ b/badges/trebuchet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/besieged.svg b/besieged.svg deleted file mode 100644 index 9328f94..0000000 --- a/besieged.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/besieging.svg b/besieging.svg deleted file mode 100644 index 57bb874..0000000 --- a/besieging.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/jihad-christian.svg b/jihad-christian.svg deleted file mode 100644 index 196101e..0000000 --- a/jihad-christian.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/jihad-muslim.svg b/jihad-muslim.svg deleted file mode 100644 index a1538be..0000000 --- a/jihad-muslim.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/play.html b/play.html index b374274..3fd6f1d 100644 --- a/play.html +++ b/play.html @@ -188,35 +188,52 @@ body.shift .block.known:hover { .block.Franks.selected { border-color: gold; } .block.Saracens.selected { border-color: gold; } .block.Assassins.selected { border-color: hotpink; } +.block.Franks.highlight:not(.selected) { box-shadow: 0 0 3px white; } .block.highlight { cursor: pointer; } .block.moved { filter: brightness(80%) grayscale(40%); } .block.highlight.moved { filter: brightness(95%) grayscale(40%); } + .map .block.castle.known { filter: grayscale(50%); } .map.stack_layout .block.castle { filter: grayscale(90%); } -.map .block.castle:not(.known) { - background-image: url("/crusader-rex/besieged.svg"); +.block.castle:not(.known) { + background-image: url("badges/stone-tower.svg"); background-size: 60%; background-position: center; } -.map .block.besieging:not(.known) { - background-image: url("/crusader-rex/besieging.svg"); +.block.besieging:not(.known) { + background-image: url("badges/trebuchet.svg"); + background-size: 60%; + background-position: center; +} +.block.winter_campaign:not(.known) { + background-image: url("badges/camping-tent.svg"); background-size: 60%; background-position: center; } - .block.Franks.jihad { - background-image: url("/crusader-rex/jihad-christian.svg"); + background-image: url("badges/Cross_of_the_Knights_Templar.svg"); background-size: 60%; background-position: center; } .block.Saracen.jihad { - background-image: url("/crusader-rex/jihad-muslim.svg"); + background-image: url("badges/Star_and_Crescent.svg"); background-size: 60%; background-position: center; } +.block.besieging.Franks.jihad:not(.known) { + background-image: url("badges/trebuchet.svg"), url("badges/Cross_of_the_Knights_Templar.svg"); + background-size: 60%, 40%; + background-position: 30% 80%, 85% 15%; +} +.block.besieging.Saracens.jihad:not(.known) { + background-image: url("badges/trebuchet.svg"), url("badges/Star_and_Crescent.svg"); + background-size: 60%, 40%; + background-position: 30% 80%, 85% 15%; +} + .block.r0 { transform: rotate(0deg); } .block.r1 { transform: rotate(-90deg); } .block.r2 { transform: rotate(-180deg); } @@ -398,10 +415,11 @@ body.shift .block.known:hover {
$PROMPT
- - + + + diff --git a/rules.js b/rules.js index 9659602..19d0a42 100644 --- a/rules.js +++ b/rules.js @@ -3,9 +3,10 @@ // TODO: optional rule - iron bridge // TODO: optional rule - force marches -// TODO: can sea move into fortified port that is under attack but not yet besieged? -// TODO: pause after battle ends to show final result/action +// TODO: can sea move into fortified port that is under attack but not yet besieged? no... +// TODO: pause after battle ends to show final result/action? // TODO: optional retreat after combat round 3 if storming +// TODO: new combat deployment in round 2/3 if defenders are wiped out? maybe... exports.scenarios = [ "Third Crusade" @@ -256,9 +257,14 @@ function list_seats(who) { if (is_saladin_family(who)) who = SALADIN; switch (block_type(who)) { - case 'nomads': return [ block_home(who) ]; - case 'turcopoles': who = "Turcopoles"; break; - case 'military_orders': who = BLOCKS[who].name; break; + case 'nomads': + return [ block_home(who) ]; + case 'turcopoles': + who = "Turcopoles"; + break; + case 'military_orders': + who = BLOCKS[who].name; + break; } let list = []; for (let town in SHIELDS) @@ -271,9 +277,14 @@ function is_home_seat(where, who) { if (is_saladin_family(who)) who = SALADIN; switch (block_type(who)) { - case 'nomads': return [ block_home(who) ]; - case 'turcopoles': who = "Turcopoles"; break; - case 'military_orders': who = BLOCKS[who].name; break; + case 'nomads': + return where == block_home(who); + case 'turcopoles': + who = "Turcopoles"; + break; + case 'military_orders': + who = BLOCKS[who].name; + break; } for (let town in SHIELDS) if (SHIELDS[town].includes(who)) @@ -482,6 +493,9 @@ function is_friendly_or_vacant_town(where) { function is_contested_or_enemy_town(where) { return is_contested_town(where) || is_enemy_town(where); } +function is_enemy_occupied_town(where) { + return count_enemy(where) > 0; +} /* Field queries exclude castles. */ function is_friendly_field(where) { @@ -675,8 +689,8 @@ function can_block_land_move(who) { function can_use_richards_sea_legs(who, to) { // English Crusaders may attack by sea. // If combined with another attack, the English must be the Main Attacker. - if (is_contested_or_enemy_town(to)) { - if (is_english_crusader(who)) { + if (is_english_crusader(who)) { + if (is_enemy_field(to)) { if (!game.attacker[to]) return true; if (game.attacker[to] == FRANKS) @@ -787,7 +801,7 @@ function can_block_retreat(who) { function can_block_regroup_to(who, to) { // regroup during winter campaign - if (is_winter() && is_contested_or_enemy_town(to)) + if (is_winter() && is_enemy_occupied_town(to)) return false; if (is_friendly_field(to) || is_vacant_town(to)) { let from = game.location[who]; @@ -1028,7 +1042,7 @@ states.saracen_deployment = { start_year(); }, pass: function () { - game.who = SALADIN; + game.who = null; start_year(); } } @@ -1477,6 +1491,11 @@ function end_move_phase() { game.who = null; game.where = null; game.moves = 0; + + // declined to use winter campaign + if (game.winter_campaign == game.active) + delete game.winter_campaign; + if (game.active == game.jihad) goto_select_jihad(); else @@ -1484,18 +1503,18 @@ function end_move_phase() { } function format_moves(phase, prompt) { - if (game.moves == 0) - return phase + "No moves left."; - if (game.moves == 1) - return phase + prompt + " \u2014 1 move left."; - return phase + prompt + " \u2014 " + game.moves + " moves left."; } states.move_phase = { prompt: function (view, current) { if (is_inactive_player(current)) return view.prompt = "Move Phase: Waiting for " + game.active + "."; - view.prompt = format_moves("Move Phase: ", "Group Move, Sea Move, or Muster"); + if (game.moves == 0) + view.prompt = "Move Phase: No moves left."; + else if (game.moves == 1) + view.prompt = "Move Phase: 1 move left."; + else + view.prompt = "Move Phase: " + game.moves + " moves left."; gen_action_undo(view); gen_action(view, 'end_move_phase'); if (game.moves > 0) { @@ -1504,8 +1523,15 @@ states.move_phase = { gen_action(view, 'sea_move'); if (can_muster_anywhere()) gen_action(view, 'muster'); + if (game.winter_campaign == game.active) + gen_action(view, 'winter_campaign'); } }, + winter_campaign: function () { + push_undo(); + --game.moves; + game.state = 'winter_campaign'; + }, group_move: function () { push_undo(); --game.moves; @@ -1604,13 +1630,13 @@ states.group_move_to = { let from = game.location[game.who]; if (game.distance > 0) { // cannot start or reinforce battles in winter - if (!(is_winter() && is_contested_or_enemy_town(from))) + if (!(is_winter() && is_enemy_occupied_town(from))) gen_action(view, 'town', from); } for (let to of TOWNS[from].exits) { if (to != game.last_from && can_block_land_move_to(game.who, from, to)) { // cannot start or reinforce battles in winter - if (is_winter() && is_contested_or_enemy_town(to)) { + if (is_winter() && is_enemy_occupied_town(to)) { // but can move through friendly sieges if (!is_friendly_field(to)) continue; @@ -1666,7 +1692,7 @@ states.sea_move = { prompt: function (view, current) { if (is_inactive_player(current)) return view.prompt = "Move Phase: Waiting for " + game.active + "."; - view.prompt = format_moves("Sea Move: ", "Choose a block to sea move"); + view.prompt = "Sea Move: Choose a block to move."; gen_action_undo(view); for (let b in BLOCKS) if (can_block_sea_move(b)) @@ -1756,7 +1782,7 @@ states.muster = { prompt: function (view, current) { if (is_inactive_player(current)) return view.prompt = "Move Phase: Waiting for " + game.active + "."; - view.prompt = "Muster: Choose one friendly or vacant muster town."; + view.prompt = "Muster: Choose a friendly muster town."; gen_action_undo(view); for (let where in TOWNS) { // cannot start or reinforce battles in winter @@ -1918,6 +1944,26 @@ function end_muster_move() { game.state = 'muster_who'; } +// WINTER CAMPAIGN + +states.winter_campaign = { + prompt: function (view, current) { + if (is_inactive_player(current)) + return view.prompt = "Move Phase: Waiting for " + game.active + "."; + view.prompt = "Winter Campaign: Select a siege to maintain over the winter."; + gen_action_undo(view); + for (let town in TOWNS) + if (is_friendly_field(town) && is_under_siege(town)) + gen_action(view, 'town', town); + }, + town: function (where) { + log(game.active + " winter campaign in " + where + "."); + game.winter_campaign = where; + game.state = 'move_phase'; + }, + undo: pop_undo +} + // COMBAT PHASE function goto_combat_phase() { @@ -2094,7 +2140,7 @@ states.regroup = { clear_undo(); print_summary(game.active + " regroup:"); if (is_winter()) - end_winter_campaign(); + goto_winter_2(); else if (is_contested_town(game.where)) next_combat_round(); else @@ -3008,10 +3054,10 @@ states.draw_phase = { town: function (where) { let type = block_type(game.who); - log(game.active + " arrive in " + where + "."); + log(game.active + " draw to " + where + "."); game.location[game.who] = where; - if ((type == 'outremers' || type == 'emirs' || type == 'nomads') && is_home_seat(where, game.who)); + if ((type == 'outremers' || type == 'emirs' || type == 'nomads') && is_home_seat(where, game.who)) game.steps[game.who] = 1; else game.steps[game.who] = block_max_steps(game.who); @@ -3035,7 +3081,7 @@ function end_draw_phase() { function end_game_turn() { if (is_winter()) { - goto_winter_campaign(); + goto_winter_1(); } else { if (check_sudden_death()) return; @@ -3044,64 +3090,45 @@ function end_game_turn() { } } -// WINTER CAMPAIGN +// WINTER SUPPLY -function goto_winter_campaign() { +function goto_winter_1() { log(""); - if (game.winter_campaign) { - log("Start Winter Campaign."); - game.active = game.winter_campaign; - game.state = 'winter_campaign'; - } else { - log("Start Winter."); - end_winter_campaign(); - } + log("Start Winter of " + game.year + "."); + if (game.winter_campaign) + winter_siege_attrition(); + else + goto_winter_2(); } -states.winter_campaign = { - prompt: function (view, current) { - if (is_inactive_player(current)) - return view.prompt = "Winter Campaign: Waiting for " + game.active + "."; - view.prompt = "Winter Campaign: Select a siege to maintain over the winter."; - gen_action(view, 'pass'); - for (let town in TOWNS) - if (is_friendly_field(town) && is_under_siege(town)) - gen_action(view, 'town', town); - }, - town: function (where) { - log(game.active + " maintain siege of " + where + "."); - game.winter_campaign = where; - game.where = where; +function winter_siege_attrition() { + log(game.active + " winter campaign in " + game.winter_campaign + "."); + game.where = game.winter_campaign; - let target = (game.where == TYRE || game.where == TRIPOLI) ? 2 : 4; - for (let b in BLOCKS) { - if (is_block_in_castle_in(b, game.where)) { - let die = roll_d6(); - if (die <= target) { - log("Attrition roll " + DIE_HIT[die] + "."); - reduce_block(b); - } else { - log("Attrition roll " + DIE_MISS[die] + "."); - } + let target = (game.where == TYRE || game.where == TRIPOLI) ? 2 : 4; + for (let b in BLOCKS) { + if (is_block_in_castle_in(b, game.where)) { + let die = roll_d6(); + if (die <= target) { + log("Attrition roll " + DIE_HIT[die] + "."); + reduce_block(b); + } else { + log("Attrition roll " + DIE_MISS[die] + "."); } } + } - if (!is_under_siege(game.where)) { - log(game.where + " falls to siege attrition."); - goto_regroup(); - } else { - log("Siege continues."); - end_winter_campaign(); - } - }, - pass: function () { - log(game.active + " decline to winter campaign."); - game.winter_campaign = null; - end_winter_campaign(); - }, + if (!is_under_siege(game.where)) { + log(game.where + " falls to siege attrition."); + goto_regroup(); + } else { + log("Siege continues."); + game.where = null; + goto_winter_2(); + } } -function end_winter_campaign() { +function goto_winter_2() { eliminate_besieging_blocks(FRANKS); eliminate_besieging_blocks(SARACENS); lift_all_sieges(); @@ -3128,8 +3155,6 @@ function eliminate_besieging_blocks(owner) { game.summary = null; } -// WINTER SUPPLY - function need_winter_supply_check() { for (let town in TOWNS) { if (town == game.winter_campaign) @@ -3468,6 +3493,7 @@ exports.view = function(state, current) { year: game.year, turn: game.turn, active: game.active, + p1: game.p1, f_vp: game.f_vp, s_vp: game.s_vp, f_card: (game.show_cards || current == FRANKS) ? game.f_card : 0, @@ -3487,6 +3513,8 @@ exports.view = function(state, current) { if (game.jihad && game.jihad != game.p1) view.jihad = game.jihad; + if (game.winter_campaign && game.winter_campaign != game.p1 && game.winter_campaign != game.p2) + view.winter_campaign = game.winter_campaign; states[game.state].prompt(view, current); diff --git a/ui.js b/ui.js index 6c99613..4930ce7 100644 --- a/ui.js +++ b/ui.js @@ -226,6 +226,7 @@ function on_click_card(evt) { function on_button_next(evt) { send_action('next'); } function on_button_pass(evt) { send_action('pass'); } function on_button_undo(evt) { send_action('undo'); } +function on_button_winter_campaign(evt) { send_action('winter_campaign'); } function on_button_group_move(evt) { send_action('group_move'); } function on_button_end_group_move(evt) { send_action('end_group_move'); } function on_button_sea_move(evt) { send_action('sea_move'); } @@ -590,8 +591,16 @@ function update_map() { known = ""; element.classList = info.owner + known + " block" + image + steps + moved; } else { - let besieging = (game.sieges[town] == info.owner) ? " besieging" : ""; - let jihad = (game.jihad == town) ? " jihad" : ""; + let besieging = ""; + if (game.sieges[town] == info.owner) { + if (game.winter_campaign == town) + besieging = " winter_campaign"; + else + besieging = " besieging"; + } + let jihad = ""; + if (game.jihad == town && info.owner == game.p1) + jihad = " jihad"; element.classList = info.owner + " block" + moved + besieging + jihad; } if (info.owner == FRANKS) @@ -626,10 +635,8 @@ function update_map() { if (game.who) ui.blocks[game.who].classList.add('selected'); } - for (let b of game.castle) { + for (let b of game.castle) ui.blocks[b].classList.add('castle'); - ui.battle_block[b].classList.add('castle'); - } } function update_card_display(element, card, prior_card) { @@ -790,6 +797,7 @@ function on_update() { show_action_button("#next_button", "next"); show_action_button("#pass_button", "pass"); show_action_button("#undo_button", "undo"); + show_action_button("#winter_campaign_button", "winter_campaign"); show_action_button("#group_move_button", "group_move"); show_action_button("#end_group_move_button", "end_group_move"); show_action_button("#sea_move_button", "sea_move"); -- cgit v1.2.3