From cd5a083ea9ade6a7f6ef7e16e336fff9f30343aa Mon Sep 17 00:00:00 2001
From: Tor Andersson <tor@ccxvii.net>
Date: Mon, 27 May 2024 00:28:00 +0200
Subject: chevert unstack. stay in attack position. no move search.

---
 play.css  |  20 ++--
 play.html |   1 -
 play.js   | 216 ++++++----------------------------------
 rules.js  | 335 ++++++--------------------------------------------------------
 4 files changed, 69 insertions(+), 503 deletions(-)

diff --git a/play.css b/play.css
index b69659f..658b1e1 100644
--- a/play.css
+++ b/play.css
@@ -205,6 +205,15 @@ TWOD.piece.cylinder {
 	border: 1px solid black;
 }
 
+#combat {
+	position: absolute;
+	width: 52px;
+	height: 52px;
+	border: 4px solid #c00;
+	background-color: #c008;
+	border-radius: 50%;
+}
+
 .panel {
 	background-color: #444;
 	width: clamp(824px, calc(100% - 30px), 1636px);
@@ -279,17 +288,6 @@ TWOD.piece.cylinder {
 .space.objective.action.imperial { background-color: #fbe30080; }
 .space.objective.action.france { background-color: #ed1c2380; }
 
-@media (pointer: fine) {
-
-.move line { stroke: white; }
-.battle line { stroke: #c00; }
-.retreat line { stroke: #800; opacity: 33%; }
-line { stroke-linecap: round; }
-line.road { stroke-width: 6px; }
-line.major_road { stroke-width: 8px; }
-
-}
-
 .marker.conquest.austria { background-image: url(images/conquest_austria.2x.png) }
 .marker.conquest.france { background-image: url(images/conquest_france.2x.png) }
 .marker.conquest.imperial { background-image: url(images/conquest_imperial.2x.png) }
diff --git a/play.html b/play.html
index 3dc4d9a..f384dac 100644
--- a/play.html
+++ b/play.html
@@ -38,7 +38,6 @@
 
 <div id="mapwrap">
 	<div id="map">
-		<svg id="roads" class="move" viewBox="0 0 2485 1654"></svg>
 		<div id="spaces"></div>
 		<div id="markers"></div>
 		<div id="pieces"></div>
diff --git a/play.js b/play.js
index cc3eb25..5803c11 100644
--- a/play.js
+++ b/play.js
@@ -1,14 +1,14 @@
 "use strict"
 
 // vim: set nowrap:
+/* globals data, view, action_button, action_button_with_argument, confirm_action_button, send_action
+*/
 
 // TODO: sort selected generals above deselected generals when detaching?
 // TODO: show battle icon overlay (instead of roads)
 // TODO: remove roads and path highlighting code
 // TODO: tooltips
 
-const svgNS = "http://www.w3.org/2000/svg"
-
 function toggle_pieces() {
 	document.getElementById("pieces").classList.toggle("hide")
 }
@@ -47,7 +47,6 @@ set_add_all(all_objectives, data.type.objective_russia)
 
 const objective1 = [ [], [], [], [], [], [], [] ]
 const objective2 = [ [], [], [], [], [], [], [] ]
-const protect = [ [], [], [], [], [], [], [] ]
 
 for (let s of data.type.objective_prussia) set_add(objective1[P_PRUSSIA], s)
 for (let s of data.type.objective_russia) set_add(objective1[P_RUSSIA], s)
@@ -63,7 +62,6 @@ const power_class = [ "prussia", "hanover", "russia", "sweden", "austria", "impe
 const power_name = [ "Prussia", "Hanover", "Russia", "Sweden", "Austria", "Imperial Army", "France" ]
 
 const GENERAL_POWER = [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 3, 4, 4, 4, 4, 4, 5, 6, 6, 6 ]
-const TRAIN_POWER = [ 0, 0, 1, 2, 2, 3, 4, 4, 5, 6, 6 ]
 
 const all_powers = [ 0, 1, 2, 3, 4, 5, 6 ]
 
@@ -90,6 +88,10 @@ const all_power_trains = [
 const RESERVE = 4
 let suit_class = [ "S", "C", "H", "D", "R" ]
 
+function to_deck(c) {
+	return c >> 7
+}
+
 function to_suit(c) {
 	return (c >> 4) & 7
 }
@@ -304,12 +306,9 @@ function register_action(target, action, id) {
 }
 
 function on_click_action(evt, target) {
-	if (evt.button === 0) {
-		if (send_action(target.my_action, target.my_id)) {
-			hide_move_path()
+	if (evt.button === 0)
+		if (send_action(target.my_action, target.my_id))
 			evt.stopPropagation()
-		}
-	}
 }
 
 function process_actions() {
@@ -323,33 +322,6 @@ function is_action(action, arg) {
 	return !!(view.actions && view.actions[action] && set_has(view.actions[action], arg))
 }
 
-function make_road(c1, c2, type) {
-	let e = document.createElementNS(svgNS, "line")
-	e.setAttribute("class", type)
-	let x1 = data.cities.x[c1]
-	let y1 = data.cities.y[c1]
-	let x2 = data.cities.x[c2]
-	let y2 = data.cities.y[c2]
-
-	let v = Math.hypot(x2 - x1, y2 - y1)
-	let dx = (x2 - x1) / v
-	let dy = (y2 - y1) / v
-	let r = 18
-	x1 += r * dx
-	y1 += r * dy
-	x2 -= r * dx
-	y2 -= r * dy
-
-	e.setAttribute("x1", x1)
-	e.setAttribute("y1", y1)
-	e.setAttribute("x2", x2)
-	e.setAttribute("y2", y2)
-	e.setAttribute("visibility", "hidden")
-	ui.roads[c1][c2] = e
-	ui.roads[c2][c1] = e
-	ui.roads_element.appendChild(e)
-}
-
 function create_piece(action, id, style) {
 	let e = document.createElement("div")
 	e.className = style
@@ -365,10 +337,6 @@ function create_marker(style) {
 	return e
 }
 
-function make_tc_id(n, suit, value) {
-	return (n << 7) | (suit << 4) | value
-}
-
 function make_tc_deck(n) {
 	for (let suit = 0; suit <= 3; ++suit) {
 		for (let value = 2; value <= 13; ++value) {
@@ -493,6 +461,9 @@ function on_init() {
 		make_tc_deck_back("deck_5"),
 	]
 
+	ui.combat = document.createElement("div")
+	ui.combat.id = "combat"
+
 	ui.tcbreak = document.createElement("div")
 	ui.tcbreak.className = "draw-break"
 
@@ -500,23 +471,6 @@ function on_init() {
 	for (let fc = 0; fc <= 18; ++fc)
 		ui.fate[fc] = make_fate_card(fc)
 
-	if (1) {
-		for (let a = 0; a <= last_city; ++a)
-			ui.roads[a] = []
-		for (let a = 0; a <= last_city; ++a) {
-			for (let b of cities.major_roads[a]) {
-				if (a < b) {
-					make_road(a, b, "major_road")
-				}
-			}
-			for (let b of cities.roads[a]) {
-				if (a < b) {
-					make_road(a, b, "road")
-				}
-			}
-		}
-	}
-
 	for (let a = 0; a <= last_city; ++a) {
 		let e = ui.cities[a] = document.createElement("div")
 		let x = cities.x[a]
@@ -569,117 +523,18 @@ function on_init() {
 
 on_init()
 
-/* SHOW PATH FOR MOVES */
+/* TOOLTIPS */
 
 function on_focus_city(evt) {
-	document.getElementById("status").textContent = evt.target.my_name
-	if (view) {
-		if (view.move_minor)
-			show_move_path(evt.target.my_id)
-		if (view.retreat)
-			show_retreat_path(evt.target.my_id)
-	}
 }
 
 function on_blur_city() {
-	document.getElementById("status").textContent = ""
-	hide_move_path()
 }
 
 function on_focus_piece(evt) {
-	document.getElementById("status").textContent = evt.target.my_name
-	if (view && view.move_minor)
-		show_move_path(view.pos[evt.target.my_id])
 }
 
 function on_blur_piece() {
-	document.getElementById("status").textContent = ""
-	hide_move_path()
-}
-
-var _move_path = []
-var _battle_road = null
-
-function hide_move_path() {
-	if (_move_path) {
-		for (let i = 1; i < _move_path.length; ++i) {
-			let x = _move_path[i-1]
-			let y = _move_path[i]
-			ui.roads[x][y].setAttribute("visibility", "hidden")
-		}
-		_move_path = null
-	}
-}
-
-function show_move_path(x) {
-	hide_move_path()
-
-	if (map_get(view.move_major, x, -1) >= 0) {
-		_move_path = []
-		while (x >= 0) {
-			_move_path.push(x)
-			x = map_get(view.move_major, x, -1)
-		}
-	}
-
-	else
-
-	if (map_get(view.move_minor, x, -1) >= 0) {
-		_move_path = []
-		while (x >= 0) {
-			_move_path.push(x)
-			x = map_get(view.move_minor, x, -1)
-		}
-	}
-
-	if (_move_path) {
-		for (let i = 1; i < _move_path.length; ++i) {
-			let x = _move_path[i-1]
-			let y = _move_path[i]
-			ui.roads[x][y].setAttribute("visibility", "visible")
-		}
-	}
-}
-
-function show_retreat_path(x) {
-	hide_move_path()
-	_move_path = map_get(view.retreat, x, null)
-	if (_move_path) {
-		_move_path = _move_path.slice()
-		_move_path.push(x)
-		for (let i = 1; i < _move_path.length; ++i) {
-			let x = _move_path[i-1]
-			let y = _move_path[i]
-			ui.roads[x][y].setAttribute("visibility", "visible")
-		}
-	}
-}
-
-function show_battle_path() {
-	_battle_road = ui.roads[view.attacker][view.defender]
-	_battle_road.setAttribute("visibility", "visible")
-}
-
-function hide_battle_path() {
-	if (_battle_road) {
-		_battle_road.setAttribute("visibility", "hidden")
-		_battle_road = null
-	}
-}
-
-function update_path() {
-	hide_move_path()
-	hide_battle_path()
-	if (view.move_major) {
-		ui.roads_element.setAttribute("class", "move")
-	} else if (view.retreat) {
-		ui.roads_element.setAttribute("class", "retreat")
-	} else if (view.attacker !== undefined) {
-		ui.roads_element.setAttribute("class", "battle")
-		show_battle_path()
-	} else {
-		ui.roads_element.setAttribute("class", null)
-	}
 }
 
 /* UPDATE UI */
@@ -796,6 +651,14 @@ function layout_train(id, s) {
 	e.classList.toggle("selected", set_has(view.selected, id))
 }
 
+function layout_combat_marker() {
+	let x = (data.cities.x[view.attacker] + data.cities.x[view.defender]) >> 1
+	let y = (data.cities.y[view.attacker] + data.cities.y[view.defender]) >> 1
+	ui.combat.style.left = x - 30 + "px"
+	ui.combat.style.top = y - 30 + "px"
+	ui.combat.style.zIndex = y
+}
+
 function create_conquest(style, s) {
 	let x = data.cities.x[s]
 	let y = data.cities.y[s]
@@ -807,18 +670,6 @@ function create_conquest(style, s) {
 	return e
 }
 
-function to_deck(c) {
-	return c >> 7
-}
-
-function to_suit(c) {
-	return (c >> 4) & 7
-}
-
-function to_value(c) {
-	return c & 15
-}
-
 function update_favicon() {
 	let favicon = document.querySelector('link[rel="icon"]')
 	switch (params.role) {
@@ -859,7 +710,9 @@ function colorize(text) {
 }
 
 function on_update() {
-	ui.prompt.innerHTML = colorize(view.prompt)
+	let text = colorize(view.prompt)
+	if (text !== view.prompt)
+		ui.prompt.innerHTML = text
 
 	ui.header.classList.toggle("prussia", view.power === P_PRUSSIA)
 	ui.header.classList.toggle("hanover", view.power === P_HANOVER)
@@ -876,8 +729,6 @@ function on_update() {
 	for (let t = 24; t <= 34; ++t)
 		layout_train(t, view.pos[t])
 
-	update_path()
-
 	let back = [ 0, 0, 0, 0, 0 ]
 
 	for (let i = 0; i < 5; ++i)
@@ -916,6 +767,11 @@ function on_update() {
 	for (let s of view.retro)
 		ui.markers_element.appendChild(ui.retro[s])
 
+	if (view.attacker !== undefined && view.defender !== undefined) {
+		ui.markers_element.appendChild(ui.combat)
+		layout_combat_marker()
+	}
+
 	/* troops 1-8, reserve 1-10 with modifiers +1 and +5 */
 	for (let v = 16; v >= 0; --v)
 		action_button_with_argument("value", v, v)
@@ -1066,19 +922,3 @@ function set_add_all(set, other) {
 	for (let item of other)
 		set_add(set, item)
 }
-
-function map_get(map, key, missing) {
-	let a = 0
-	let b = (map.length >> 1) - 1
-	while (a <= b) {
-		let m = (a + b) >> 1
-		let x = map[m<<1]
-		if (key < x)
-			b = m - 1
-		else if (key > x)
-			a = m + 1
-		else
-			return map[(m<<1)+1]
-	}
-	return missing
-}
diff --git a/rules.js b/rules.js
index 37520d4..713e234 100644
--- a/rules.js
+++ b/rules.js
@@ -1,5 +1,8 @@
 "use strict"
 
+// TODO: prussian offensive (no capture when not offensive)
+// TODO: final score summary at game end (FWC rules)
+
 const R_FREDERICK = "Frederick"
 const R_ELISABETH = "Elisabeth"
 const R_MARIA_THERESA = "Maria Theresa"
@@ -364,13 +367,6 @@ function clear_fate_effect() {
 	game.fx = 0
 }
 
-function may_unstack() {
-	// TODO: 3-piece stack?
-	if (game.fx === NEXT_TURN_CHEVERT_MAY_NOT_UNSTACK)
-		return !set_has(game.selected, GEN_CHEVERT)
-	return true
-}
-
 function forbid_play_value_10_or_more() {
 	if (game.fx === NEXT_TURN_IF_PRUSSIA_AND_FRANCE_FIGHT_EACH_OTHER_THEY_MAY_NOT_USE_TCS_WITH_VALUES_OF_10_OR_MORE) {
 		let a = get_stack_power(game.attacker)
@@ -1463,9 +1459,9 @@ states.movement = {
 			game.major = 0
 
 		if (is_supply_train(p))
-			resume_move_supply_train()
+			game.state = "move_supply_train"
 		else
-			resume_move_general()
+			game.state = "move_general"
 	},
 	confirm_end_movement() {
 		this.end_movement()
@@ -1483,7 +1479,6 @@ function format_move(max) {
 	return ` up to ${n} cities.`
 }
 
-// TODO: also force moving if in such a position and can move away
 function forbid_stopping_at(from) {
 	switch (game.fx) {
 		case NEXT_TURN_SOUBISE_AND_HILDBURGHAUSEN_MAY_NOT_ATTACK_WITH_THE_SAME_TC_SYMBOL:
@@ -1500,22 +1495,6 @@ function forbid_stopping_at(from) {
 	return false
 }
 
-function forbid_attack(from, to) {
-	switch (game.fx) {
-		case NEXT_TURN_SOUBISE_AND_HILDBURGHAUSEN_MAY_NOT_ATTACK_WITH_THE_SAME_TC_SYMBOL:
-			return game.pos[GEN_SOUBISE] === from && game.ia_attack === get_space_suit(from)
-		case NEXT_TURN_NO_GENERAL_MAY_BE_ATTACKED_IN_THE_CITY_OF_HALLE:
-			return to === HALLE
-		case NEXT_TURN_CUMBERLAND_MAY_NOT_MOVE_INTO_ATTACK_POSITION:
-			return game.pos[GEN_CUMBERLAND] === from
-		case NEXT_TURN_SOUBISE_MAY_NOT_MOVE_INTO_ATTACK_POSITION:
-			return game.pos[GEN_SOUBISE] === from
-		case NEXT_TURN_FRIEDRICH_MAY_NOT_MOVE_INTO_ATTACK_POSITION:
-			return game.pos[GEN_FRIEDRICH] === from
-	}
-	return false
-}
-
 function forbid_capture(s) {
 	switch (game.fx) {
 		case NEXT_TURN_CUMBERLAND_MAY_NOT_MOVE_INTO_ATTACK_POSITION:
@@ -1548,10 +1527,6 @@ function can_move_train_to(to) {
 	return !has_any_piece(to)
 }
 
-function can_continue_train_from(_) {
-	return true
-}
-
 function can_move_general_in_theory(p, to) {
 	if (has_friendly_supply_train(to))
 		return false
@@ -1595,65 +1570,6 @@ function can_continue_general_from(from) {
 	return true
 }
 
-function search_move(from, range, road_type, can_move_to, can_continue_from) {
-	let seen = [ from, -1 ]
-	let queue = [ from << 4 ]
-	while (queue.length > 0) {
-		let item = queue.shift()
-		let here = item >> 4
-		let dist = (item & 15) + 1
-		for (let next of data.cities[road_type][here]) {
-			if (map_has(seen, next))
-				continue
-			if (!can_move_to(next))
-				continue
-			if (dist <= range) {
-				map_set(seen, next, here)
-				if (can_continue_from(next))
-					queue.push((next << 4) | dist)
-			}
-		}
-	}
-	return seen
-}
-
-function resume_move_supply_train() {
-	if (game.count === 2 + game.major) {
-		end_move_piece()
-	} else {
-		let here = game.pos[game.selected[0]]
-		game.state = "move_supply_train"
-		if (game.major && game.count < 3)
-			game.move_major = search_move(here, 3 - game.count, "major_roads", can_move_train_to, can_continue_train_from)
-		else
-			game.move_major = []
-		if (game.count < 2)
-			game.move_minor = search_move(here, 2 - game.count, "adjacent", can_move_train_to, can_continue_train_from)
-		else
-			game.move_minor = []
-	}
-}
-
-function resume_move_general() {
-	let range = movement_range()
-	if (game.count === range + game.major) {
-		end_move_piece()
-	} else {
-		game.state = "move_general"
-		/* NEW
-		let here = game.pos[game.selected[0]]
-		if (game.major && game.count < range+1)
-			game.move_major = search_move(here, range+1 - game.count, "major_roads", can_move_general_to, can_continue_general_from)
-		else
-			game.move_major = []
-		if (game.count < range)
-			game.move_minor = search_move(here, range - game.count, "adjacent", can_move_general_to, can_continue_general_from)
-		else
-			game.move_minor = []
-		*/
-	}
-}
-
 function move_general_to(to) {
 	let pow = game.power
 	let who = game.selected[0]
@@ -1738,77 +1654,7 @@ function move_general_immediately(to) {
 	}
 }
 
-states.move_supply_train_NEW = {
-	inactive: "move",
-	prompt() {
-		prompt("Move supply train" + format_move(2))
-		view.selected = game.selected
-
-		let who = game.selected[0]
-		let here = game.pos[who]
-
-		if (game.move_major)
-			map_for_each_key(game.move_major, s => { if (s !== here) gen_action_space(s) })
-		if (game.move_minor)
-			map_for_each_key(game.move_minor, s => { if (s !== here) gen_action_space(s) })
-
-		view.move_major = game.move_major
-		view.move_minor = game.move_minor
-
-		/*
-		if (game.count < 2 + game.major)
-			for (let next of data.cities.major_roads[here])
-				if (!has_any_piece(next))
-					gen_action_space(next)
-		if (game.count < 2)
-			for (let next of data.cities.roads[here])
-				if (!has_any_piece(next))
-					gen_action_space(next)
-		*/
-
-		if (game.count > 0) {
-			gen_action_piece(who)
-			view.actions.stop = 1
-		}
-	},
-	piece(_) {
-		this.stop()
-	},
-	stop() {
-		end_move_piece()
-	},
-	space(to) {
-		let who = game.selected[0]
-
-		set_add(game.moved, who)
-		game.pos[who] = to
-
-		log("P" + who + " to S" + to)
-
-		let m = map_get(game.move_major, to, 0)
-		if (m > 0) {
-			while (m > 0) {
-				// TODO: reverse
-				log(">S" + m)
-				m = map_get(game.move_major, m)
-				++ game.count
-			}
-		} else {
-			m = map_get(game.move_minor, to, 0)
-			while (m > 0) {
-				// TODO: reverse
-				log(">S" + m)
-				m = map_get(game.move_minor, m)
-				++ game.count
-			}
-			game.major = 0
-		}
-
-		resume_move_supply_train()
-	},
-}
-
-states.move_supply_train_OLD = {
+states.move_supply_train = {
 	inactive: "move",
 	prompt() {
 		prompt("Move supply train" + format_move(2))
@@ -1854,7 +1700,7 @@ states.move_supply_train_OLD = {
 	},
 }
 
-states.move_general_NEW = {
+states.move_general = {
 	inactive: "move",
 	prompt() {
 		prompt("Move " + format_selected() + format_move(movement_range()))
@@ -1864,110 +1710,20 @@ states.move_general_NEW = {
 		let here = game.pos[who]
 
 		if (game.count === 0) {
-			if (may_unstack()) {
-				if (game.selected.length > 1)
-					view.actions.detach = 1
-				else
-					view.actions.detach = 0
-			}
-
-			let s_take = count_stacked_take()
-			let s_give = count_stacked_give()
-			let u_take = count_unstacked_take()
-			let u_give = count_unstacked_give()
-			if (s_take > 0 && u_give > 0)
-				view.actions.take = 1
-			if (s_give > 0 && u_take > 0)
-				view.actions.give = 1
-		} else {
-			if (forbid_stopping_at(here)) {
-				view.actions.stop = 0
+			if (game.fx === NEXT_TURN_CHEVERT_MAY_NOT_UNSTACK && game.pos[GEN_CHEVERT] === here) {
+				view.prompt += " Chevert may not unstack."
+				if (count_generals(here) === 3) {
+					// two options: leave alone, or leave with chevert
+					// to leave with chevert, detach non-chevert
+					// to leave alone, detach non-chevert, then detach chevert
+					if (game.selected.length === 3)
+						for (let p of game.selected)
+							if (p !== GEN_CHEVERT)
+								gen_action_piece(p)
+					if (game.selected.length === 2)
+						gen_action_piece(GEN_CHEVERT)
+				}
 			} else {
-				gen_action_piece(who)
-				view.actions.stop = 1
-			}
-		}
-
-		view.move_major = game.move_major
-		view.move_minor = game.move_minor
-
-		if (game.move_major)
-			map_for_each_key(game.move_major, s => { if (s !== here) gen_action_space_or_piece(s) })
-		if (game.move_minor)
-			map_for_each_key(game.move_minor, s => { if (s !== here) gen_action_space_or_piece(s) })
-	},
-	take() {
-		game.state = "move_take"
-	},
-	give() {
-		game.state = "move_give"
-	},
-	detach() {
-		game.state = "move_detach"
-	},
-	piece(p) {
-		if (p === game.selected[0])
-			this.stop()
-		else
-			this.space(game.pos[p])
-	},
-	stop() {
-		for (let p of game.selected)
-			set_add(game.moved, p)
-		end_move_piece()
-	},
-	space(to) {
-		let who = game.selected[0]
-		let path = [ to ]
-
-		let m = map_get(game.move_major, to, -1)
-		if (m >= 0) {
-			while (m >= 0) {
-				path.unshift(m)
-				m = map_get(game.move_major, m, -1)
-				++game.count
-			}
-		} else {
-			m = map_get(game.move_minor, to, -1)
-			while (m >= 0) {
-				path.unshift(m)
-				m = map_get(game.move_minor, m, -1)
-				++game.count
-			}
-			game.major = 0
-		}
-
-		log("P" + who + " " + path.map(s => "S" + s).join(" > "))
-
-		let stop = false
-		path.shift() // skip start space
-		for (let s of path)
-			stop ||= move_general_to(s)
-
-		if (stop)
-			this.stop()
-		else
-			resume_move_general()
-	},
-}
-
-states.move_general_OLD = {
-	inactive: "move",
-	prompt() {
-		prompt("Move " + format_selected() + format_move(movement_range()))
-		view.selected = game.selected
-
-		let who = game.selected[0]
-		let here = game.pos[who]
-
-		if (game.count === 0) {
-			if (may_unstack()) {
-				/*
-				if (game.selected.length > 1)
-					view.actions.detach = 1
-				else
-					view.actions.detach = 0
-				*/
 				if (game.selected.length > 1)
 					for (let p of game.selected)
 						gen_action_piece(p)
@@ -2007,9 +1763,6 @@ states.move_general_OLD = {
 	give() {
 		game.state = "move_give"
 	},
-	detach() {
-		game.state = "move_detach"
-	},
 	piece(p) {
 		if (game.count === 0) {
 			if (set_has(game.selected, p))
@@ -2042,24 +1795,6 @@ states.move_general_OLD = {
 	},
 }
 
-states.move_general = states.move_general_OLD
-states.move_supply_train = states.move_supply_train_OLD
-//states.move_general = states.move_general_NEW
-//states.move_supply_train = states.move_supply_train_NEW
-
-states.move_detach = {
-	inactive: "move",
-	prompt() {
-		prompt("Move " + format_selected() + ". Detach general from stack.")
-		for (let p of game.selected)
-			gen_action_piece(p)
-	},
-	piece(p) {
-		set_delete(game.selected, p)
-		game.state = "move_general"
-	},
-}
-
 states.move_take = {
 	inactive: "move",
 	prompt() {
@@ -2332,11 +2067,11 @@ function prompt_combat(value, extra = null) {
 }
 
 function inactive_attack() {
-	return "attack " + format_combat(game.count)
+	return "combat " + format_combat(game.count)
 }
 
 function inactive_defend() {
-	return "defend " + format_combat(-game.count)
+	return "combat " + format_combat(-game.count)
 }
 
 function goto_combat() {
@@ -2357,10 +2092,8 @@ function goto_combat() {
 	for (let a of from) {
 		for (let b of to) {
 			if (set_has(data.cities.adjacent[a], b)) {
-				if (!forbid_attack(a, b)) {
-					game.combat.push(a)
-					game.combat.push(b)
-				}
+				game.combat.push(a)
+				game.combat.push(b)
 			}
 		}
 	}
@@ -2897,7 +2630,7 @@ function search_retreat_possible_dfs(result, seen, here, range) {
 		if (has_any_piece(next))
 			continue
 		if (range === 1) {
-			map_set(result, next, seen.slice())
+			set_add(result, next)
 		} else {
 			seen.push(next)
 			search_retreat_possible_dfs(result, seen, next, range - 1)
@@ -2924,12 +2657,9 @@ function search_retreat(loser, winner, range) {
 	}
 
 	let result = []
-	map_for_each(possible, (s, path) => {
-		if (map_get(distance, s, -1) === max) {
+	for (let s of possible)
+		if (map_get(distance, s, -1) === max)
 			result.push(s)
-			result.push(path)
-		}
-	})
 	return result
 }
 
@@ -2938,15 +2668,14 @@ states.retreat = {
 	prompt() {
 		prompt("Retreat " + format_selected() + " " + Math.abs(game.count) + " cities.")
 		view.selected = game.selected
-		view.retreat = game.retreat
-		map_for_each_key(game.retreat, gen_action_space)
+		for (let s of game.retreat)
+			gen_action_space(s)
 	},
 	space(to) {
 		push_undo()
 		log("Retreated to S" + to + ".")
-		for (let p of game.selected) {
+		for (let p of game.selected)
 			game.pos[p] = to
-		}
 		delete game.retreat
 		game.state = "retreat_done"
 	},
@@ -3578,7 +3307,7 @@ function goto_flip_5_or_6_from_nearest_train(power, list) {
 }
 
 states.flip_5_or_6_from_nearest_train = {
-	inactive: "flip general out of supply",
+	inactive: "flip generals out of supply",
 	prompt() {
 		prompt("Flip " + format_selected() + " out of supply.")
 		for (let p of game.selected)
@@ -3592,7 +3321,7 @@ states.flip_5_or_6_from_nearest_train = {
 }
 
 states.flip_any_one_prussian_general_or_stack_in_austria_or_saxony = {
-	inactive: "flip general out of supply",
+	inactive: "flip any one Prussian general/stack in Austria or Saxony out of supply",
 	prompt() {
 		prompt("Flip any one Prussian general/stack in Austria or Saxony out of supply.")
 		for (let p of game.selected)
-- 
cgit v1.2.3