summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--play.html10
-rw-r--r--rules.js292
-rw-r--r--ui.js14
3 files changed, 244 insertions, 72 deletions
diff --git a/play.html b/play.html
index dfc3c5a..80b931b 100644
--- a/play.html
+++ b/play.html
@@ -112,6 +112,14 @@
opacity: 0.8;
z-index: 9;
}
+.town.where {
+ opacity: 0.8;
+ border-color: blue;
+ z-index: 9;
+}
+.town.where.highlight {
+ box-shadow: 0 0 2px 4px white;
+}
/* BLOCKS */
@@ -319,9 +327,9 @@ body.shift .block.known:hover {
<button id="eliminate_button" class="hide" onclick="on_button_eliminate()">Eliminate</button>
<button id="sea_move_button" class="hide" onclick="on_button_sea_move()">Sea Move</button>
- <button id="group_move_button" class="hide" onclick="on_button_group_move()">Group Move</button>
<button id="muster_button" class="hide" onclick="on_button_muster()">Muster</button>
<button id="end_muster_button" class="hide" onclick="on_button_end_muster()">End muster</button>
+ <button id="end_sea_move_button" class="hide" onclick="on_button_end_sea_move()">End sea move</button>
<button id="end_retreat_button" class="hide" onclick="on_button_end_retreat()">End retreat</button>
<button id="end_regroup_button" class="hide" onclick="on_button_end_regroup()">End regroup</button>
<button id="end_move_phase_button" class="hide" onclick="on_button_end_move_phase()">End move phase</button>
diff --git a/rules.js b/rules.js
index 3682c88..5b2d4d1 100644
--- a/rules.js
+++ b/rules.js
@@ -4,7 +4,7 @@
// TODO: saladin seat adjustment at setup
exports.scenarios = [
- "Rules 2.0",
+ "Third Crusade"
];
const { CARDS, BLOCKS, TOWNS, PORTS, ROADS } = require('./data');
@@ -74,7 +74,7 @@ function print_turn_log_no_count(text) {
function print_turn_log(text) {
function print_move(last) {
- return "\n" + n + " " + last.join(" \u2192 ");
+ return "\n" + n + " - " + last.join(" \u2192 ");
}
game.turn_log.sort();
let last = game.turn_log[0];
@@ -325,7 +325,7 @@ function is_pinned(who, from) {
return false;
}
-function can_block_use_road(who, from, to) {
+function can_block_use_road(from, to) {
switch (road_type(from, to)) {
case 'major': return road_limit(from, to) < 4;
case 'minor': return road_limit(from, to) < 2;
@@ -334,7 +334,7 @@ function can_block_use_road(who, from, to) {
}
function can_block_land_move_to(who, from, to) {
- if (can_block_use_road(who, from, to)) {
+ if (can_block_use_road(from, to)) {
if (count_pinning(from) > 0)
if (road_was_last_used_by_enemy(from, to))
return false;
@@ -395,7 +395,7 @@ function can_block_continue(who, from, to) {
function can_block_retreat_to(who, to) {
if (is_friendly_town(to) || is_vacant_town(to)) {
let from = game.location[who];
- if (can_block_use_road(who, from, to)) {
+ if (can_block_use_road(from, to)) {
if (road_was_last_used_by_enemy(from, to))
return false;
return true;
@@ -407,7 +407,7 @@ function can_block_retreat_to(who, to) {
function can_block_regroup_to(who, to) {
if (is_friendly_town(to) || is_vacant_town(to)) {
let from = game.location[who];
- if (can_block_use_road(who, from, to))
+ if (can_block_use_road(from, to))
return true;
}
return false;
@@ -423,16 +423,57 @@ function can_block_regroup(who) {
return false;
}
-function can_block_muster_via(who, from, next, muster) {
- if (can_block_land_move_to(who, from, next) && is_friendly_or_vacant_town(next)) {
- if (next == muster)
- return true;
- if (road_type(from, next) != 'minor') {
- if (TOWNS[next].exits.includes(muster))
- if (can_block_land_move_to(who, next, muster))
- return true;
+function can_block_use_road_to_muster(from, to) {
+ return can_block_use_road(from, to) && is_friendly_or_vacant_town(to);
+}
+
+function can_block_muster_with_3_moves(n0, muster) {
+ for (let n1 of TOWNS[n0].exits) {
+ if (can_block_use_road_to_muster(n0, n1)) {
+ if (n1 == muster)
+ return true;
+ for (let n2 of TOWNS[n1].exits) {
+ if (n2 == n0) continue; // don't backtrack!
+ if (can_block_use_road_to_muster(n1, n2)) {
+ if (n2 == muster)
+ return true;
+ for (let n3 of TOWNS[n2].exits) {
+ if (can_block_use_road_to_muster(n2, n3)) {
+ if (n3 == muster)
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+function can_block_muster_with_2_moves(n0, muster) {
+ for (let n1 of TOWNS[n0].exits) {
+ if (can_block_use_road_to_muster(n0, n1)) {
+ if (n1 == muster)
+ return true;
+ for (let n2 of TOWNS[n1].exits) {
+ if (can_block_use_road_to_muster(n1, n2)) {
+ if (n2 == muster)
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+function can_block_muster_with_1_move(n0, muster) {
+ for (let n1 of TOWNS[n0].exits) {
+ if (can_block_use_road_to_muster(n0, n1)) {
+ if (n1 == muster)
+ return true;
}
}
+ return false;
}
function can_block_muster(who, muster) {
@@ -442,9 +483,10 @@ function can_block_muster(who, muster) {
if (can_activate(who)) {
if (is_pinned(who, from))
return false;
- for (let next of TOWNS[from].exits)
- if (can_block_muster_via(who, from, next, muster))
- return true;
+ if (block_move(who) == 3)
+ return can_block_muster_with_3_moves(from, muster);
+ else
+ return can_block_muster_with_2_moves(from, muster);
}
return false;
}
@@ -661,6 +703,7 @@ function start_player_turn() {
function end_player_turn() {
game.moves = 0;
game.activated = null;
+ game.mustered = null;
game.main_road = null;
if (game.active == game.p2) {
@@ -719,6 +762,7 @@ function goto_move_phase(moves) {
game.state = 'group_move';
game.moves = moves;
game.activated = [];
+ game.mustered = [];
game.main_road = {};
game.turn_log = [];
}
@@ -737,9 +781,11 @@ states.group_move = {
view.prompt = "Group Move: Choose a block to group move. " + game.moves + "AP left.";
gen_action_undo(view);
gen_action(view, 'end_move_phase');
- if (game.moves > 0) {
- gen_action(view, 'sea_move');
+ if (can_muster_anywhere())
gen_action(view, 'muster');
+ if (can_sea_move_anywhere())
+ gen_action(view, 'sea_move');
+ if (game.moves > 0) {
for (let b in BLOCKS)
if (can_block_land_move(b))
gen_action(view, 'block', b);
@@ -760,9 +806,11 @@ states.group_move = {
game.state = 'group_move_to';
},
sea_move: function () {
+ push_undo();
game.state = 'sea_move';
},
muster: function () {
+ push_undo();
game.state = 'muster';
},
end_move_phase: end_move_phase,
@@ -809,36 +857,32 @@ states.group_move_to = {
undo: pop_undo
}
+function can_sea_move_anywhere() {
+ if (game.moves > 0) {
+ for (let b in BLOCKS)
+ if (can_block_sea_move(b))
+ return true;
+ }
+ return false;
+}
+
states.sea_move = {
prompt: function (view, current) {
if (is_inactive_player(current))
return view.prompt = "Move Phase: Waiting for " + game.active + ".";
view.prompt = "Sea Move: Choose a block to sea move. " + game.moves + "AP left.";
gen_action_undo(view);
- gen_action(view, 'end_move_phase');
- gen_action(view, 'group_move');
if (game.moves > 0) {
- gen_action(view, 'muster');
- for (let b in BLOCKS) {
- let from = game.location[b];
- if (can_block_sea_move(b)) {
+ for (let b in BLOCKS)
+ if (can_block_sea_move(b))
gen_action(view, 'block', b);
- }
- }
}
},
- group_move: function () {
- game.state = 'group_move';
- },
- muster: function () {
- game.state = 'muster';
- },
block: function (who) {
push_undo();
game.who = who;
game.state = 'sea_move_to';
},
- end_move_phase: end_move_phase,
undo: pop_undo
}
@@ -871,39 +915,51 @@ states.sea_move_to = {
log_move_continue(to);
}
- game.state = 'sea_move';
+ game.state = 'group_move';
game.who = null;
},
block: pop_undo,
undo: pop_undo
}
+function can_muster_anywhere() {
+ if (game.moves > 0)
+ return true;
+ for (let where of game.mustered) {
+ if (is_friendly_town(where))
+ if (can_muster_to(where))
+ return true;
+ }
+ return false;
+}
+
states.muster = {
prompt: function (view, current) {
if (is_inactive_player(current))
return view.prompt = "Waiting for " + game.active + " to move.";
- view.prompt = "Muster: Choose one friendly or vacant muster area.";
+ view.prompt = "Muster: Choose one friendly or vacant muster town. " + game.moves + "AP left.";
gen_action_undo(view);
- gen_action(view, 'group_move');
- gen_action(view, 'sea_move');
- gen_action_undo(view);
- gen_action(view, 'end_action_phase');
- for (let where in TOWNS) {
- if (is_friendly_or_vacant_town(where))
- if (can_muster_to(where))
- gen_action(view, 'town', where);
+ gen_action(view, 'end_muster');
+ if (game.moves > 0) {
+ for (let where in TOWNS) {
+ if (is_friendly_town(where))
+ if (can_muster_to(where))
+ gen_action(view, 'town', where);
+ }
+ } else {
+ for (let where of game.mustered) {
+ if (is_friendly_town(where))
+ if (can_muster_to(where))
+ gen_action(view, 'town', where);
+ }
}
},
- area: function (where) {
+ town: function (where) {
push_undo();
game.where = where;
game.state = 'muster_who';
},
- end_action_phase: function () {
- clear_undo();
- print_turn_log(game.active + " musters:");
- end_player_turn();
- },
+ end_muster: pop_undo,
undo: pop_undo,
}
@@ -911,9 +967,9 @@ states.muster_who = {
prompt: function (view, current) {
if (is_inactive_player(current))
return view.prompt = "Waiting for " + game.active + " to move.";
- view.prompt = "Muster: Move blocks to the designated muster area.";
+ view.prompt = "Muster: Move blocks to " + game.where + ".";
gen_action_undo(view);
- gen_action(view, 'end_action_phase');
+ gen_action(view, 'end_muster');
for (let b in BLOCKS)
if (can_block_muster(b, game.where))
gen_action(view, 'block', b);
@@ -923,11 +979,123 @@ states.muster_who = {
game.who = who;
game.state = 'muster_move_1';
},
- end_action_phase: function () {
+ end_muster: function () {
game.where = null;
- clear_undo();
- print_turn_log(game.active + " musters:");
- end_player_turn();
+ game.state = 'group_move';
+ },
+ undo: pop_undo,
+}
+
+function end_muster_move() {
+ let muster = game.where;
+ log_move_end();
+ game.moved[game.who] = true;
+ game.who = null;
+ game.state = 'muster_who';
+ if (!game.mustered.includes(muster)) {
+ logp("musters to " + muster + ".");
+ game.mustered.push(muster);
+ --game.moves;
+ }
+}
+
+states.muster_move_1 = {
+ prompt: function (view, current) {
+ if (is_inactive_player(current))
+ return view.prompt = "Waiting for " + game.active + " to move.";
+ view.prompt = "Muster: Move " + block_name(game.who) + " to " + game.where + ".";
+ gen_action_undo(view);
+ gen_action(view, 'block', game.who);
+ let from = game.location[game.who];
+ let muster = game.where;
+ if (block_move(game.who) == 3) {
+ for (let to of TOWNS[from].exits) {
+ if (can_block_use_road_to_muster(from, to)) {
+ if (to == muster || can_block_muster_with_2_moves(to, muster))
+ gen_action(view, 'town', to);
+ }
+ }
+ } else {
+ for (let to of TOWNS[from].exits) {
+ if (can_block_use_road_to_muster(from, to)) {
+ if (to == muster || can_block_muster_with_1_move(to, muster))
+ gen_action(view, 'town', to);
+ }
+ }
+ }
+ },
+ town: function (to) {
+ let from = game.location[game.who];
+ log_move_start(from);
+ log_move_continue(to);
+ move_block(game.who, from, to);
+ if (to == game.where) {
+ end_muster_move();
+ } else {
+ game.state = 'muster_move_2';
+ }
+ },
+ block: pop_undo,
+ undo: pop_undo,
+}
+
+states.muster_move_2 = {
+ prompt: function (view, current) {
+ if (is_inactive_player(current))
+ return view.prompt = "Waiting for " + game.active + " to move.";
+ view.prompt = "Muster: Move " + block_name(game.who) + " to " + game.where + ".";
+ gen_action_undo(view);
+ let from = game.location[game.who];
+ let muster = game.where;
+ if (block_move(game.who) == 3) {
+ for (let to of TOWNS[from].exits) {
+ if (can_block_use_road_to_muster(from, to)) {
+ if (to == muster || can_block_muster_with_1_move(to, muster))
+ gen_action(view, 'town', to);
+ }
+ }
+ } else {
+ for (let to of TOWNS[from].exits) {
+ if (can_block_use_road_to_muster(from, to)) {
+ if (to == muster)
+ gen_action(view, 'town', to);
+ }
+ }
+ }
+ },
+ town: function (to) {
+ let from = game.location[game.who];
+ log_move_continue(to);
+ move_block(game.who, from, to);
+ if (to == game.where) {
+ end_muster_move();
+ } else {
+ game.state = 'muster_move_3';
+ }
+ },
+ undo: pop_undo,
+}
+
+states.muster_move_3 = {
+ prompt: function (view, current) {
+ if (is_inactive_player(current))
+ return view.prompt = "Waiting for " + game.active + " to move.";
+ view.prompt = "Muster: Move " + block_name(game.who) + " to " + game.where + ".";
+ gen_action_undo(view);
+ let from = game.location[game.who];
+ let muster = game.where;
+ for (let to of TOWNS[from].exits) {
+ if (can_block_use_road_to_muster(from, to)) {
+ if (to == muster)
+ gen_action(view, 'town', to);
+ }
+ }
+ },
+ town: function (to) {
+ let from = game.location[game.who];
+ log_move_continue(to);
+ move_block(game.who, from, to);
+ end_muster_move();
},
undo: pop_undo,
}
@@ -1254,20 +1422,20 @@ states.retreat_to = {
prompt: function (view, current) {
if (is_inactive_player(current))
return view.prompt = "Waiting for " + game.active + " to retreat.";
- view.prompt = "Retreat: Move the army to a friendly or neutral area.";
+ view.prompt = "Retreat: Move the army to a friendly or neutral town.";
gen_action_undo(view);
gen_action(view, 'block', game.who);
let can_retreat = false;
- for (let to of AREAS[game.where].exits) {
+ for (let to of TOWNS[game.where].exits) {
if (can_block_retreat_to(game.who, to)) {
- gen_action(view, 'area', to);
+ gen_action(view, 'town', to);
can_retreat = true;
}
}
if (!can_retreat)
gen_action(view, 'eliminate');
},
- area: function (to) {
+ town: function (to) {
let from = game.where;
game.turn_log.push([from, to]);
move_block(game.who, game.where, to);
@@ -1289,7 +1457,7 @@ states.retreat_in_battle = {
return view.prompt = "Waiting for " + game.active + " to retreat.";
gen_action(view, 'undo');
gen_action(view, 'block', game.who);
- view.prompt = "Retreat: Move the army to a friendly or vacant area.";
+ view.prompt = "Retreat: Move the army to a friendly or vacant town.";
for (let to of TOWNS[game.where].exits)
if (can_block_retreat_to(game.who, to))
gen_action(view, 'town', to);
@@ -1352,7 +1520,7 @@ states.regroup_to = {
prompt: function (view, current) {
if (is_inactive_player(current))
return view.prompt = "Waiting for " + game.active + " to regroup.";
- view.prompt = "Regroup: Move the army to a friendly or vacant area.";
+ view.prompt = "Regroup: Move the army to a friendly or vacant town.";
gen_action_undo(view);
gen_action(view, 'block', game.who);
for (let to of TOWNS[game.where].exits)
diff --git a/ui.js b/ui.js
index c0a164d..aa48ca1 100644
--- a/ui.js
+++ b/ui.js
@@ -32,8 +32,6 @@ function wide_map() {
zoom_map();
}
-let game = null;
-
let ui = {
cards: {},
towns: {},
@@ -179,9 +177,9 @@ function on_click_card(evt) {
function on_button_undo(evt) { send_action('undo'); }
function on_button_pass(evt) { send_action('pass'); }
function on_button_sea_move(evt) { send_action('sea_move'); }
-function on_button_group_move(evt) { send_action('group_move'); }
function on_button_muster(evt) { send_action('muster'); }
function on_button_end_muster(evt) { send_action('end_muster'); }
+function on_button_end_sea_move(evt) { send_action('end_sea_move'); }
function on_button_end_move_phase(evt) { send_action('end_move_phase'); }
function on_button_end_regroup(evt) { send_action('end_regroup'); }
function on_button_end_retreat(evt) { send_action('end_retreat'); }
@@ -656,7 +654,7 @@ function update_battle() {
}
}
- if (ui.player == FRANK) {
+ if (player == FRANK) {
fill_cell("FR", game.battle.FR, true);
fill_cell("FA", game.battle.FA, false);
fill_cell("FB", game.battle.FB, false);
@@ -677,15 +675,13 @@ function update_battle() {
}
}
-function on_update(state, player) {
- game = state;
-
+function on_update() {
show_action_button("#pass_button", "pass");
show_action_button("#undo_button", "undo");
- show_action_button("#group_move_button", "group_move");
show_action_button("#sea_move_button", "sea_move");
show_action_button("#muster_button", "muster");
show_action_button("#end_muster_button", "end_muster");
+ show_action_button("#end_sea_move_button", "end_sea_move");
show_action_button("#end_move_phase_button", "end_move_phase");
show_action_button("#end_regroup_button", "end_regroup");
show_action_button("#end_retreat_button", "end_retreat");
@@ -701,7 +697,7 @@ function on_update(state, player) {
document.querySelector(".battle_header").textContent = game.battle.title;
document.querySelector(".battle_message").textContent = game.battle.flash;
document.querySelector(".battle").classList.add("show");
- update_battle(player);
+ update_battle();
} else {
document.querySelector(".battle").classList.remove("show");
}