From c03eaad9f1480345905e25639062e205065230e3 Mon Sep 17 00:00:00 2001
From: Tor Andersson <tor@ccxvii.net>
Date: Sun, 24 Sep 2023 23:06:08 +0200
Subject: Forbidden hexes.

Disallow placing detachments, blown units, withdrawal, movement,
retreats, and pursuits into forbidden (next to enemy entrance) hexes.
---
 data.js  | 15 +++++++--------
 rules.js | 49 ++++++++++++++++++++++++++++++-------------------
 2 files changed, 37 insertions(+), 27 deletions(-)

diff --git a/data.js b/data.js
index 8047d7b..4f7b94a 100644
--- a/data.js
+++ b/data.js
@@ -22,13 +22,12 @@ data.map = {
 	towns: [1015,1018,1021,1024,1026,1100,1117,1118,1129,1201,1204,1209,1211,1215,1217,1221,1239,1340,1401,1407,1423,1433,1516,1526,1528,1534,1601,1603,1605,1623,1631,1716,1728,1737,1800,1810,1821,1825,1830,1903,1911,1915,1916,1919,1922,1928,1932,2001,2027,2035,2119,2122,2123,2219,2222,2223,2230,2308,2315,2317,2324,2327,2333,2337,2404,2500,2521,2529,2537,2604,2609,2618,2623,2715,2721,2723,2725,2730,2733,2736,2739,2827,2829,2840,2911,2936,3002,3013,3018,3020,3031,3125,3129,3135,3138,3204,3206,3226,3231,3233,3234,3240,3313,3327,3328,3402,3408,3417,3418,3438,3441,3512,3514,3523,3528,3614,3616,3617,3631,3636,3705,3708,3715,3718,3719,3723,3803,3828,3832,3915,3919,3925,3933,4006,4038],
 	streams: [1021,1024,1120,1124,1224,1300,1314,1324,1401,1415,1501,1502,1514,1600,1601,1603,1604,1704,1837,1937,2038,2138,2407,2507,2524,2540,2604,2608,2609,2620,2621,2624,2625,2637,2641,2704,2708,2718,2719,2721,2724,2725,2737,2740,2741,2805,2808,2817,2820,2821,2825,2838,2840,2905,2906,2907,2915,2916,2917,2920,2921,2925,2938,2940,3006,3017,3018,3019,3020,3021,3022,3025,3039,3040,3041,3106,3117,3118,3121,3122,3125,3141,3205,3207,3219,3220,3221,3222,3223,3225,3226,3305,3306,3320,3323,3324,3325,3406,3423,3502,3503,3506,3517,3518,3520,3521,3523,3534,3535,3536,3604,3605,3606,3607,3619,3622,3623,3624,3635,3637,3703,3704,3705,3706,3707,3708,3709,3710,3711,3712,3713,3714,3719,3723,3735,3736,3739,3802,3803,3806,3813,3820,3824,3825,3836,3837,3840,3906,3920,3925,3937,3938,3939,4007,4021,4026,4027,4038,4039],
 	brussels_couillet_road: [1018,1117,1217,1218,1317,1417,1516,1617,1716,1817,1917,2018,2117,2218,2317,2418,2517,2618,2717,2818,2917,3018,3116,3117,3216,3316,3416,3515,3616,3616,3715,3815,3915,4015],
-	forbidden: {
-		1015: [ 1014, 1015, 1016, 1114, 1115 ],
-		1018: [ 1017, 1018, 1019, 1117 ],
-		1020: [ 1019, 1020, 1021, 1120 ],
-		3000: [ 2900, 3000, 3001, 3100 ],
-		4015: [ 4014, 4015, 4016, 3914, 3915 ],
-	},
+	forbidden: [
+		// Forbidden to French: 3000, 4015
+		[ 2900, 3000, 3001, 3100, 3914, 3915, 4014, 4015, 4016 ],
+		// Forbidden to Coalition: 1015, 1017, 1018, 1020
+		[ 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1114, 1115, 1116, 1117, 1120 ],
+	],
 	names: {
 		1015: "Mortigny",
 		1018: "Couillet",
@@ -103,7 +102,7 @@ data.map = {
 		2609: "Aroue",
 		2618: "Quatre-Bras",
 		2623: "Tilly",
-		2712: "Villers la Ville",
+		2721: "Villers la Ville",
 		2715: "Hautain le Val",
 		2723: "Melloreux",
 		2725: "Centinnes",
diff --git a/rules.js b/rules.js
index 83d71c1..c5c802f 100644
--- a/rules.js
+++ b/rules.js
@@ -5,11 +5,12 @@
 // TODO: inactive prompts
 // TODO: prompts - Done when no more to do
 
-// TODO: forbidden - enemy or enemy zoc on entry or adjacent hex special case retreat/recall
-
 // TODO: pause after last battle before next turn (do not auto-pass move and attack?)
-// TODO: confirm attack step?
-// TODO: roll attack step?
+
+// TODO: disable pass with moves returning if have units off map that can enter
+
+// TODO: confirm attack step!
+// TODO: roll attack step!
 
 const P1 = "French"
 const P2 = "Coalition"
@@ -26,6 +27,9 @@ const data = require("./data")
 
 const last_hex = 1000 + (data.map.rows - 1) * 100 + (data.map.cols - 1)
 
+const p1_forbidden = data.map.forbidden[0]
+const p2_forbidden = data.map.forbidden[1]
+
 var move_seen = new Array(last_hex - 999).fill(0)
 var move_cost = new Array(last_hex - 999).fill(0)
 var move_flip = new Array(last_hex - 999).fill(0)
@@ -287,6 +291,12 @@ function piece_is_in_zoc_of_hex(p, x) {
 	return false
 }
 
+function is_forbidden_hex(x) {
+	if (game.active === P1)
+		return set_has(p1_forbidden, x)
+	return set_has(p2_forbidden, x)
+}
+
 function is_river(a, b) {
 	return set_has(data_rivers, a * 10000 + b)
 }
@@ -603,9 +613,9 @@ function can_return_blown_unit(p) {
 	for (let hq of friendly_hqs()) {
 		if (pieces_are_associated(p, hq)) {
 			for_each_adjacent(piece_hex(hq), x => {
-				// TODO: forbidden
 				if (is_empty_hex(x) && !is_enemy_zoc_or_zoi(x))
-					result = true
+					if (!is_forbidden_hex(x))
+						result = true
 			})
 		}
 	}
@@ -696,9 +706,9 @@ states.return_blown_where = {
 		for (let hq of friendly_hqs()) {
 			if (pieces_are_associated(game.who, hq)) {
 				for_each_adjacent(piece_hex(hq), x => {
-					// TODO: forbidden
 					if (is_empty_hex(x) && !is_enemy_zoc_or_zoi(x))
-						gen_action_hex(x)
+						if (!is_forbidden_hex(x))
+							gen_action_hex(x)
 				})
 			}
 		}
@@ -709,6 +719,7 @@ states.return_blown_where = {
 	},
 	hex(x) {
 		log("P" + game.who + "\nto " + x)
+		// TODO: forbidden (retreat then resume return_blown_who)
 		set_piece_hex(game.who, x)
 		game.who = -1
 		game.state = "return_blown_who"
@@ -777,12 +788,12 @@ function end_detachment_placement_step() {
 
 function can_place_detachment_at(x) {
 	// NOTE: must have run search_detachment before calling!
-	// TODO: forbidden
 	return (
 		move_seen[x-1000] &&
 		!is_friendly_zoc_or_zoi(x) &&
 		!hex_has_any_piece(x, friendly_detachments()) &&
-		!hex_has_any_piece(x, enemy_detachments())
+		!hex_has_any_piece(x, enemy_detachments()) &&
+		!is_forbidden_hex(x)
 	)
 }
 
@@ -798,7 +809,6 @@ function can_place_detachment_anywhere(p, hq) {
 function can_place_detachment(p, hq) {
 	// NOTE: must have run search_detachment before calling!
 	let x = piece_hex(p)
-	// TODO: forbidden
 	if (x === AVAILABLE_P1 || x === AVAILABLE_P2) {
 		if (pieces_are_associated(p, hq)) {
 			if (p === GRAND_BATTERY || p === OLD_GUARD) {
@@ -1095,7 +1105,6 @@ states.withdrawal_to = {
 		next_withdrawal()
 	},
 	hex(x) {
-		// TODO: forbidden (withdraw again)
 		let from = piece_hex(game.who)
 		log("P" + game.who + "\tfrom " + from + "\nto " + x)
 		set_piece_hex(game.who, x)
@@ -1295,8 +1304,7 @@ states.movement_to = {
 		for (let row = 0; row < data.map.rows; ++row) {
 			for (let col = 0; col < data.map.cols; ++col) {
 				let x = 1000 + row * 100 + col
-				// TODO: forbidden?
-				if (x !== here && move_seen[x-1000]) {
+				if (x !== here && move_seen[x-1000] && !is_forbidden_hex(x)) {
 					if (move_flip[x-1000])
 						gen_action_stop_hex(x)
 					else
@@ -1708,7 +1716,8 @@ function search_retreat(result, here, from_list, n) {
 		if (n > 1)
 			search_retreat(result, next, from_list, n - 1)
 		else
-			set_add(result, next)
+			if (!is_forbidden_hex(next))
+				set_add(result, next)
 	})
 }
 
@@ -2215,10 +2224,12 @@ function goto_pursuit() {
 	update_zoc()
 
 	if (!hex_has_any_piece(game.attack, enemy_units()) && piece_is_not_in_enemy_zoc(game.who)) {
-		set_piece_hex(game.who, game.attack)
-		// TODO: forbidden (retreat then next_attack)
-		log("P" + game.who + " pursued.")
-		recall_grand_battery_alone()
+		if (!is_forbidden_hex(game.attack)) {
+			// TODO: forbidden (retreat then next_attack)
+			set_piece_hex(game.who, game.attack)
+			log("P" + game.who + " pursued.")
+			recall_grand_battery_alone()
+		}
 	}
 
 	next_attack()
-- 
cgit v1.2.3