"use strict";

// TODO: check all card data

// TODO: log helpers - piece name (army/road/tribe color/coalition)
// TODO: log helpers - card name (number, name, and court or market location)

let cards = require("./cards.js");

const Afghan = 'Afghan';
const British = 'British';
const Russian = 'Russian';

const Political = 'Political';
const Intelligence = 'Intelligence';
const Economic = 'Economic';
const Military = 'Military';

const Persia = 201;
const Transcaspia = 202;
const Herat = 203;
const Kabul = 204;
const Kandahar = 205;
const Punjab = 206;

const Persia_Transcaspia = 301;
const Persia_Herat = 302;
const Transcaspia_Herat = 303;
const Transcaspia_Kabul = 304;
const Herat_Kabul = 305;
const Herat_Kandahar = 306;
const Kabul_Kandahar = 307;
const Kabul_Punjab = 308;
const Kandahar_Punjab = 309;

const Gift = 400;
const Safe_House = 500;

const first_region = 201;
const last_region = 206;

const PUBLIC_WITHDRAWAL = 111;
const SAFE_HOUSE_1 = 41;
const SAFE_HOUSE_2 = 72;

const player_names = [
	"Gray",
	"Blue",
	"Tan",
	"Red",
	"Black",
];

const player_index = Object.fromEntries(Object.entries(player_names).map(([k,v])=>[v,k|0]));

const region_names = {
	[Persia]: "Persia",
	[Transcaspia]: "Transcaspia",
	[Herat]: "Herat",
	[Kabul]: "Kabul",
	[Kandahar]: "Kandahar",
	[Punjab]: "Punjab",
};

const region_index = {
	"Persia": Persia,
	"Transcaspia": Transcaspia,
	"Herat": Herat,
	"Kabul": Kabul,
	"Kandahar": Kandahar,
	"Punjab": Punjab,
};

cards.forEach(card => {
	if (card) {
		card.region = region_index[card.region];
		if (card.name === 'EVENT')
			card.name = card.if_discarded + " / " + card.if_purchased;
	}
});

const border_names = {
	[Persia_Transcaspia]: "Persia/Transcaspia",
	[Persia_Herat]: "Persia/Herat",
	[Transcaspia_Herat]: "Transcaspia/Herat",
	[Transcaspia_Kabul]: "Transcaspia/Kabul",
	[Herat_Kabul]: "Herat/Kabul",
	[Herat_Kandahar]: "Herat/Kandahar",
	[Kabul_Kandahar]: "Kabul/Kandahar",
	[Kabul_Punjab]: "Kabul/Punjab",
	[Kandahar_Punjab]: "Kandahar/Punjab",
}

const borders = {
	[Transcaspia]: [ Persia_Transcaspia, Transcaspia_Herat, Transcaspia_Kabul ],
	[Kabul]: [ Transcaspia_Kabul, Herat_Kabul, Kabul_Kandahar, Kabul_Punjab ],
	[Punjab]: [ Kabul_Punjab, Kandahar_Punjab ],
	[Persia]: [ Persia_Transcaspia, Persia_Herat ],
	[Herat]: [ Transcaspia_Herat, Persia_Herat, Herat_Kabul, Herat_Kandahar ],
	[Kandahar]: [ Kabul_Kandahar, Herat_Kandahar, Kandahar_Punjab ],
}

const roads = {
	[Transcaspia]: [ Persia, Herat, Kabul ],
	[Kabul]: [ Transcaspia, Herat, Kandahar, Punjab ],
	[Punjab]: [ Kabul, Kandahar ],
	[Persia]: [ Transcaspia, Herat ],
	[Herat]: [ Transcaspia, Persia, Kabul, Kandahar ],
	[Kandahar]: [ Kabul, Herat, Punjab ],
}

function is_dominance_check(c) {
	return c >= 101 && c <= 104;
}

function is_event_card(c) {
	return c > 100;
}

let game = null;
let player = null;
let view = null;

let states = {};

const scenario_player_count = { "2P": 2, "3P": 3, "4P": 4, "5P": 5 }

exports.scenarios = [ "3P", "4P", "5P", "2P" ];

exports.roles = function (scenario) {
	switch (scenario) {
	case "2P": return player_names.slice(0, 2);
	case "3P": return player_names.slice(0, 3);
	case "4P": return player_names.slice(0, 4);
	case "5P": return player_names.slice(0, 5);
	}
}

exports.ready = function (scenario, options, players) {
	switch (scenario) {
	case "2P": return players.length === 2;
	case "3P": return players.length === 3;
	case "4P": return players.length === 4;
	case "5P": return players.length === 5;
	}
}

function random(n) {
	return ((game.seed = game.seed * 69621 % 0x7fffffff) / 0x7fffffff) * n | 0;
}

function shuffle(deck) {
	for (let i = deck.length - 1; i > 0; --i) {
		let j = random(i + 1);
		let tmp = deck[j];
		deck[j] = deck[i];
		deck[i] = tmp;
	}
}

function remove_from_array(array, item) {
	let i = array.indexOf(item);
	if (i >= 0)
		array.splice(i, 1);
}

function set_active(new_active) {
	game.active = new_active;
	update_aliases();
}

function update_aliases() {
	player = game.players[game.active];
}

function find_card_in_market(c) {
	for (let row = 0; row < 2; ++row)
		for (let col = 0; col < 6; ++col)
			if (c === game.market_cards[row][col])
				return [row, col];
	return null;
}

function find_card_in_court(c) {
	for (let p = 0; p < game.players.length; ++p) {
		let court = game.players[p].court;
		for (let i = 0; i < court.length; ++i)
			if (court[i] === c)
				return p;
	}
	return -1;
}

function next_player(current) {
	return (current + 1) % game.players.length;
}

function a_or_an(s) {
	let x = s[0];
	switch (s[0]) {
	case 'A': case 'a':
	case 'O': case 'o':
	case 'E': case 'e':
	case 'U': case 'u':
	case 'I': case 'i':
		return "an " + s;
	}
	return "a " + s;
}

function logbr() {
	if (game.log.length > 0 && game.log[game.log.length-1] !== "")
		game.log.push("");
}

function log(msg) {
	game.log.push(msg);
}

function clear_undo() {
	game.undo = [];
}

function push_undo() {
	game.undo.push(JSON.stringify(game, (k,v) => {
		if (k === 'undo') return 0;
		if (k === 'log') return v.length;
		return v;
	}));
}

function pop_undo() {
	let save_undo = game.undo;
	let save_log = game.log;
	game = JSON.parse(save_undo.pop());
	game.undo = save_undo;
	save_log.length = game.log;
	game.log = save_log;
}

function gen_action(action, argument=undefined) {
	if (argument !== undefined) {
		if (!(action in view.actions)) {
			view.actions[action] = [ argument ];
		} else {
			if (!view.actions[action].includes(argument))
				view.actions[action].push(argument);
		}
	} else {
		view.actions[action] = 1;
	}
}

// STATE QUERIES

function active_has_court_card(c) {
	let court = player.court;
	for (let i = 0; i < court.length; ++i)
		if (court[i] === c)
			return true;
	return false;
}

function player_has_court_card(p, c) {
	let court = game.players[p].court;
	for (let i = 0; i < court.length; ++i)
		if (court[i] === c)
			return true;
	return false;
}

function any_player_has_court_card(p, c) {
	for (let p = 0; p < game.players.length; ++p) {
		let court = game.players[p].court;
		for (let i = 0; i < court.length; ++i)
			if (court[i] === c)
				return true;
	}
	return false;
}

function active_has_russian_influence() { return active_has_court_card(70); }
function active_has_persian_influence() { return active_has_court_card(68); }
function active_has_herat_influence() { return active_has_court_card(66); }
function active_has_claim_of_ancient_lineage() { return active_has_court_card(5); }
function active_has_indian_supplies() { return active_has_court_card(51); }
function active_has_well_connected() { return active_has_court_card(56); }
function active_has_strange_bedfellows() { return active_has_court_card(21); }
function active_has_infrastructure() { return active_has_court_card(78); }
function active_has_civil_service_reforms() { return active_has_court_card(24); }
function active_has_charismatic_courtiers() { return active_has_court_card(42); }
function active_has_blackmail_kandahar() { return active_has_court_card(43); }
function active_has_blackmail_herat() { return active_has_court_card(54); }

function player_has_bodyguards(p) { return player_has_court_card(p, 15) || player_has_court_card(p, 83); }
function player_has_indispensable_advisors(p) { return player_has_court_card(p, 1); }
function player_has_citadel_in_kabul(p) { return player_has_court_card(p, 17); }
function player_has_citadel_in_transcaspia(p) { return player_has_court_card(p, 97); }
function player_has_safe_house(p) { return player_has_court_card(p, 41) || player_has_court_card(p, 72); }

function any_player_has_insurrection(p) { return any_player_has_court_card(p, 3); }

function player_has_citadel(p, r) {
	if (r === Kabul) return player_has_citadel_in_kabul(p);
	if (r === Transcaspia) return player_has_citadel_in_transcaspia(p);
	return false;
}

function player_coalition_blocks(p) {
	switch (game.players[p].loyalty) {
	case Afghan: return 0;
	case British: return 12;
	case Russian: return 24;
	}
}

function active_coalition_blocks() {
	switch (player.loyalty) {
	case Afghan: return 0;
	case British: return 12;
	case Russian: return 24;
	}
}

function player_cylinders(p) {
	return 36 + p * 10;
}

function active_cylinders() {
	return 36 + game.active * 10;
}

function active_gifts() {
	let x = active_cylinders();
	let n = 0;
	for (let i = x; i < x + 10; ++i)
		if (game.pieces[i] === Gift)
			++n;
	return n;
}

function gift_cost() {
	return 2 * active_gifts() + 2;
}

function ruler_of_region(r) {
	let ruler = -1;

	let n_afghan = 0;
	let n_british = 0;
	let n_russian = 0;
	for (let i = 0; i < 12; ++i) {
		if (game.pieces[i] === r)
			n_afghan ++;
		if (game.pieces[i+12] === r)
			n_british ++;
		if (game.pieces[i+24] === r)
			n_russian ++;
	}

	let max_ruling = Math.max(n_afghan, n_british, n_russian);

	for (let p = 0; p < game.players.length; ++p) {
		let n_tribes = 0;
		let x = player_cylinders(p);
		for (let i = x; i < x + 10; ++i)
			if (game.pieces[i] === r)
				n_tribes++;

		let n_ruling = n_tribes;
		if (game.players[p].loyalty === Afghan)
			n_ruling += n_afghan;
		if (game.players[p].loyalty === British)
			n_ruling += n_british;
		if (game.players[p].loyalty === Russian)
			n_ruling += n_russian;

		if (n_ruling === max_ruling) {
			ruler = -1;
		} else if (n_ruling > max_ruling) {
			max_ruling = n_ruling;
			if (n_tribes > 0)
				ruler = p;
			else
				ruler = -1;
		}
	}

	return ruler;
}

function player_rules_region(p, r) {
	return ruler_of_region(r) === p;
}

function active_rules_region(r) {
	return player_rules_region(game.active, r);
}

function player_rules_any_region(p) {
	for (let r = first_region; r <= last_region; ++r)
		if (player_rules_region(p, r))
			return true;
	return false;
}

function active_rules_any_region() {
	return player_rules_any_region(game.active);
}

function active_can_betray() {
	let x = active_cylinders();
	for (let i = x; i < x + 10; ++i) {
		if (game.pieces[i] >= 1 && game.pieces[i] <= 100) {
			let c = game.pieces[i];
			let p = find_card_in_court(c);
			if (!player_has_bodyguards(p))
				return true;
		}
	}
	return false;
}

function card_has_no_spies(c) {
	for (let p = 0; p < game.players.length; ++p) {
		let x = player_cylinders(p);
		for (let i = x; i < x + 10; ++i)
			if (game.pieces[i] === c)
				return false;
	}
	return true;
}

function market_cost(col, c) {
	if (col === 0)
		return 0;
	if (cards[c].patriot === Russian && active_has_russian_influence())
		return 0;
	if (cards[c].region === Persia && active_has_persian_influence())
		return 0;
	if (cards[c].region === Herat && active_has_herat_influence())
		return 0;
	if (game.favored === Military)
		return 2 * col;
	return col;
}

function is_favored_suit(c) {
	if (cards[c].suit === game.favored)
		return true;
	if (player.events.new_tactics && cards[c].suit === Military)
		return true;
	if (c === 91) return true; // Savvy Operator
	if (c === 99) return true; // Irregulars
}

function rightmost_card(row, i) {
	while (i >= 0 && game.market_cards[row][i] === 0)
		--i;
	return i;
}

function pay_action_cost(count) {
	// log(`Paid ${count} rupees.`);
	player.coins -= count;
	let ra = rightmost_card(0, 5);
	let rb = rightmost_card(1, 5);
	for (let i = 0; i < count; i += 2) {
		if (ra >= 0) game.market_coins[0][ra] ++;
		if (rb >= 0) game.market_coins[1][rb] ++;
		ra = rightmost_card(0, ra-1);
		rb = rightmost_card(1, rb-1);
	}
	check_public_withdrawal();
}

function remove_all_tribes_and_armies(where) {
	for (let i = 0; i < game.pieces.length; ++i)
		if (game.pieces[i] === where)
			game.pieces[i] = 0;
}

function player_with_most_spies(c) {
	let who = -1;
	let max_spies = 0;
	for (let p = 0; p < game.players.length; ++p) {
		let n_spies = count_player_cylinders(p, c);
		if (n_spies === max_spies) {
			who = -1;
		} else if (n_spies > max_spies) {
			max_spies = n_spies;
			who = p;
		}
	}
	return who;
}

function select_available_cylinder() {
	let x = active_cylinders();
	for (let i = x; i < x + 10; ++i)
		if (game.pieces[i] === 0)
			return i;
	return -1;
}

function gen_select_cylinder() {
	let x = active_cylinders();
	for (let i = x; i < x + 10; ++i)
		if (!game.used_pieces.includes(i))
			gen_action('piece', i);
}

function gen_select_spy_to_move() {
	let x = active_cylinders();
	for (let i = x; i < x + 10; ++i)
		if (game.pieces[i] > 0 && game.pieces[i] <= 100)
			gen_action('piece', i);
}

function gen_select_tribe_to_move() {
	let x = active_cylinders();
	for (let i = x; i < x + 10; ++i)
		if (game.pieces[i] >= first_region && game.pieces[i] <= last_region)
			gen_action('piece', i);
}

function select_available_block() {
	let b = active_coalition_blocks();
	for (let i = b; i < b + 12; ++i)
		if (game.pieces[i] === 0)
			return i;
	return -1;
}

function gen_select_block() {
	let b = active_coalition_blocks();
	for (let i = b; i < b + 12; ++i)
		if (!game.used_pieces.includes(i))
			gen_action('piece', i);
}

function gen_select_army_to_move() {
	// TODO: only select armies in regions that can move
	let b = active_coalition_blocks();
	for (let i = b; i < b + 12; ++i)
		if (game.pieces[i] >= first_region && game.pieces[i] <= last_region)
			gen_action('piece', i);
}

// DISCARD COURT CARD

function discard_court_card(c) {
	let pidx = -1;

	// Remove card from court
	for (let p = 0; p < game.players.length; ++p) {
		let i = game.players[p].court.indexOf(c);
		if (i >= 0) {
			game.players[p].court.splice(i, 1);
			pidx = p;
			break;
		}
	}

	log(`${player_names[pidx]} discarded ${cards[c].name}.`);

	// Return all spies on card
	for (let p = 0; p < game.players.length; ++p) {
		let x = player_cylinders(p);
		for (let i = x; i < x + 10; ++i)
			if (game.pieces[i] === c)
				game.pieces[i] = 0;
	}

	// Return rupees for leverage
	if (cards[c].leveraged) {
		log(`${player_names[pidx]} returned leverage.`);
		game.players[pidx].coins -= 2;
	}

	check_court_overthrow(pidx, cards[c].region);
}

// CHANGE LOYALTY

function change_loyalty(new_loyalty) {
	player.loyalty = new_loyalty;

	log(`${player_names[game.active]} loyalty to ${player.loyalty}.`);

	for (let i = 0; i < player.court.length;) {
		let c = player.court[i];
		let card = cards[c];
		if (card.patriot && card.patriot !== new_loyalty) {
			discard_court_card(c);
		} else {
			++i;
		}
	}

	let x = active_cylinders();
	for (let i = x; i < x + 10; ++i)
		if (game.pieces[i] === Gift)
			game.pieces[i] = 0;

	player.prizes = 0;
}

// RETURN LEVERAGE

function check_leverage() {
	let first = game.phasing;
	for (let p = first; p < game.players.length; ++p)
		if (check_player_leverage(p))
			return;
	for (let p = 0; p < first; ++p)
		if (check_player_leverage(p))
			return;
	check_safe_house();
}

function check_player_leverage(p) {
	if (game.players[p].coins < 0) {
		if (game.players[p].hand.length + game.players[p].court.length === 0) {
			game.players[p].coins = 0;
		} else {
			if (game.active !== p)
				clear_undo();
			set_active(p);
			game.state = 'leverage';
			return true;
		}
	}
	return false;
}

states.leverage = {
	prompt() {
		if (player.coins < 0) {
			view.prompt = `Discard cards from your hand or court to pay for leverage.`;
			for (let i = 0; i < player.hand.length; ++i)
				gen_action('card', player.hand[i]);
			for (let i = 0; i < player.court.length; ++i)
				gen_action('card', player.court[i]);
		} else {
			view.prompt = `Discard cards from your hand or court to pay for leverage \u2014 done.`;
			gen_action('next');
		}
	},
	card(c) {
		push_undo();
		player.coins ++;
		if (player.hand.includes(c))
			remove_from_array(player.hand, c);
		else
			discard_court_card(c);
		if (player.hand.length + player.court.length === 0)
			player.coins = 0;
	},
	next() {
		check_leverage();
	}
}

// OVERTHROW

function check_court_overthrow(p, r) {
	let court = game.players[p].court;
	let x = player_cylinders(p);

	let nc = 0;
	for (let i = 0; i < court.length; ++i)
		if (cards[court[i]].region === r)
			++nc;

	let nt = 0;
	for (let i = x; i < x + 10; ++i)
		if (game.pieces[i] === r)
			++nt;

	if (nc === 0 && nt > 0) {
		log(`${player_names[p]} is overthrown in ${region_names[r]}.`);
		for (let i = x; i < x + 10; ++i)
			if (game.pieces[i] === r)
				game.pieces[i] = 0;
	}
}

function check_region_overthrow(p, r) {
	let court = game.players[p].court;
	let x = player_cylinders(p);

	let nc = 0;
	for (let i = 0; i < court.length; ++i)
		if (cards[court[i]].region === r)
			++nc;

	let nt = 0;
	for (let i = x; i < x + 10; ++i)
		if (game.pieces[i] === r)
			++nt;

	if (nt === 0 && nc > 0) {
		log(`${player_names[p]} is overthrown in ${region_names[r]}.`);
		for (let i = 0; i < court.length;) {
			if (cards[court[i]].region === r)
				discard_court_card(court[i]);
			else
				++i;
		}
	}
}

// BRIBES

states.bribe = {
	prompt() {
		let p = ruler_of_region(cards[game.card].region);
		view.prompt = `Must pay ${game.count} rupee bribe to ${player_names[p]}.`;
		if (player.coins - game.reserve >= game.count)
			gen_action('pay');
		gen_action('beg');
		if (player.events.courtly_manners)
			gen_action('courtly_manners');
		if (game.undo.length === 0)
			gen_action('refuse');
	},
	courtly_manners() {
		log(`${player_names[p]} chose not to pay bribe.`);
		end_bribe();
	},
	pay() {
		let p = ruler_of_region(cards[game.card].region);
		game.players[p].coins += game.count;
		game.players[game.active].coins -= game.count;
		end_bribe();
	},
	beg() {
		clear_undo();
		let p = ruler_of_region(cards[game.card].region);
		game.state = 'waive';
		set_active(p);
	},
	refuse() {
		game.card = 0;
		game.where = 0;
		resume_actions();
	},
}

states.waive = {
	prompt() {
		if (typeof game.where === 'string')
			view.prompt = `${player_names[game.phasing]} asks you to waive the bribe to use ${cards[game.card].name} to ${game.where}.`;
		else
			view.prompt = `${player_names[game.phasing]} asks you to waive the bribe to play ${cards[game.card].name}.`;
		let max_cost = Math.min(game.players[game.phasing].coins - game.reserve, game.count);
		gen_action('waive');
		for (let i = 1; i < max_cost; ++i)
			gen_action('offer_' + i);
		gen_action('refuse');
	},
	waive() {
		log(`${player_names[game.active]} waived the bribe.`);
		set_active(game.phasing);
		end_bribe();
	},
	offer_1() { do_offer(1); },
	offer_2() { do_offer(2); },
	offer_3() { do_offer(3); },
	offer_4() { do_offer(4); },
	offer_5() { do_offer(5); },
	offer_6() { do_offer(6); },
	offer_7() { do_offer(7); },
	offer_8() { do_offer(8); },
	offer_9() { do_offer(9); },
	refuse() {
		log(`${player_names[game.active]} refused to waive the bribe.`);
		game.state = 'bribe'
		set_active(game.phasing);
	},
}

function do_offer(n) {
	log(`${player_names[game.active]} reduced the bribe to ${n}.`);
	game.count = 1;
	game.state = 'bribe';
	set_active(game.phasing);
}

function end_bribe() {
	if (typeof game.where === 'string')
		do_card_action_2();
	else
		do_play_2();
}

// STARTING LOYALTY

states.loyalty = {
	prompt() {
		view.prompt = `Choose your loyalty \u2014 Afghan, British, or Russian.`;
		gen_action('loyalty_afghan');
		gen_action('loyalty_british');
		gen_action('loyalty_russian');
	},
	loyalty_afghan() {
		set_starting_loyalty(Afghan);
	},
	loyalty_british() {
		set_starting_loyalty(British);
	},
	loyalty_russian() {
		set_starting_loyalty(Russian);
	},
}

function set_starting_loyalty(loyalty) {
	log(`${player_names[game.active]} loyalty to ${loyalty}.`);
	player.loyalty = loyalty;
	let next = next_player(game.active);
	if (game.players[next].loyalty)
		goto_actions();
	else
		set_active(next);
}

// ACTION PHASE

function goto_actions() {
	game.phasing = game.active;
	game.actions = 2;
	game.used_cards = []; // track cards that have been used
	game.used_pieces = [];
	game.selected = -1;
	game.where = 0;
	logbr();
	log(`.turn ${player_names[game.phasing]}`);
	logbr();

	let bmh = active_can_blackmail(Herat);
	let bmk = active_can_blackmail(Kandahar);
	if (bmh || bmk) {
		game.state = 'blackmail';
		game.where = (bmh && bmk) ? -1 : (bmh ? Herat : Kandahar);
		game.selected = select_available_cylinder();
	} else {
		resume_actions();
	}
}

function end_action() {
	check_leverage();
}

function resume_actions() {
	set_active(game.phasing);
	game.selected = -1;
	game.where = 0;
	game.state = 'actions';
}

function goto_next_player() {
	clear_undo();
	game.phasing = next_player(game.phasing);
	set_active(game.phasing);
	goto_actions();
}

states.actions = {
	prompt() {
		// Pass / End turn
		if (game.actions > 0) {
			gen_action('pass');
		} else {
			gen_action('next');
		}

		// Purchase
		if (game.actions > 0) {
			for (let row = 0; row < 2; ++row) {
				for (let col = 0; col < 6; ++col) {
					let c = game.market_cards[row][col];
					if (c && market_cost(col, c) <= player.coins && !game.used_cards.includes(c) && c !== PUBLIC_WITHDRAWAL)
						gen_action('purchase', c);
				}
			}
		}

		// Play
		if (game.actions > 0) {
			for (let i = 0; i < player.hand.length; ++i) {
				let c = player.hand[i];
				gen_action('play_left', c);
				gen_action('play_right', c);
			}
		}

		// Card-based actions
		let bonus = false;
		for (let i = 0; i < player.court.length; ++i) {
			let c = player.court[i];
			let card = cards[c];

			if (game.used_cards.includes(c))
				continue;

			let favored = is_favored_suit(c);
			if (favored || game.actions > 0) {
				let usable = false;

				if (card.tax) {
					gen_action('tax', c);
					usable = true;
				}
				if (card.gift && active_gifts() < 3 && player.coins >= gift_cost()) {
					gen_action('gift', c);
					usable = true;
				}
				if (card.build && player.coins >= 2 && active_rules_any_region()) {
					gen_action('build', c);
					usable = true;
				}
				// TODO: check if any moves are possible
				if (card.move) {
					gen_action('move', c);
					usable = true;
				}

				if (card.betray && player.coins >= 2 && active_can_betray()) {
					gen_action('betray', c);
					usable = true;
				}

				// TODO: check if any battles are possible
				if (card.battle) {
					gen_action('battle', c);
					usable = true;
				}

				if (usable && favored)
					bonus = true;
			}
		}

		if (game.actions === 2)
			view.prompt = `You may take two actions.`;
		else if (game.actions === 1)
			view.prompt = `You may take one more action.`;
		else
			view.prompt = `You have no more actions.`;
		if (bonus)
			view.prompt += ` You may take bonus actions.`;

	},

	purchase(c) {
		push_undo();
		logbr();

		let [row, col] = find_card_in_market(c);
		game.actions --;

		let cost = market_cost(col, c);
		let cost_per_card = cost / col;
		for (let i = 0; i < col; ++i) {
			if (game.market_cards[row][i] > 0) {
				game.market_coins[row][i] += cost_per_card;
				game.used_cards.push(game.market_cards[row][i]);
			} else {
				game.market_coins[1-row][i] += cost_per_card;
				game.used_cards.push(game.market_cards[1-row][i]);
			}
		}
		check_public_withdrawal();

		logbr();

		if (cost > 0) {
			/*
			if (cost > 1)
				log(`Paid ${cost} rupees.`);
			else
				log(`Paid ${cost} rupee.`);
			*/
			player.coins -= cost;
		}

		if (game.market_coins[row][col] > 0) {
			/*
			if (game.market_coins[row][col] > 1)
				log(`Took ${game.market_coins[row][col]} rupees.`);
			else
				log(`Took ${game.market_coins[row][col]} rupee.`);
			*/
			player.coins += game.market_coins[row][col];
		}

		game.market_coins[row][col] = 0;
		game.market_cards[row][col] = 0;

		if (is_dominance_check(c)) {
			log(`Purchased Dominance Check.`);
			do_dominance_check();
			if (game.state === 'game_over')
				return;
			resume_actions();
		} else if (is_event_card(c)) {
			log(`Purchased ${cards[c].if_purchased}.`);
			events_if_purchased[cards[c].if_purchased]();
		} else {
			log(`Purchased ${cards[c].name}.`);
			player.hand.push(c);
			resume_actions();
		}
	},

	tax(c) { do_card_action_1(c, "Tax", 0); },
	gift(c) { do_card_action_1(c, "Gift", gift_cost()); },
	build(c) { do_card_action_1(c, "Build", 2); },
	move(c) { do_card_action_1(c, "Move", 0); },
	betray(c) { do_card_action_1(c, "Betray", 2); },
	battle(c) { do_card_action_1(c, "Battle", 0); },
	play_left(c) { do_play_1(c, 0); },
	play_right(c) { do_play_1(c, 1); },

	pass() {
		logbr();
		log(`Passed.`);
		goto_cleanup_court();
	},
	next() {
		goto_cleanup_court();
	},
}

// PLAY CARD

function do_play_1(c, side) {
	push_undo();
	game.card = c;
	game.where = side;
	if (!active_has_charismatic_courtiers() && !game.events.disregard_for_customs) {
		let ruler = ruler_of_region(cards[c].region);
		if (ruler >= 0 && ruler !== game.active) {
			game.state = 'bribe';
			game.count = count_player_cylinders(ruler, cards[c].region);
			game.reserve = 0;
			return;
		}
	}
	do_play_2();
}

function do_play_2() {
	let c = game.card;
	let side = game.where;
	game.actions --;
	logbr();
	log(`Played ${cards[c].name} (${region_names[cards[c].region]}).`);
	let idx = player.hand.indexOf(c);
	player.hand.splice(idx, 1);
	if (side)
		player.court.push(c);
	else
		player.court.unshift(c);
	goto_play_patriot();
}

function goto_play_patriot() {
	let card = cards[game.card];
	if (card.patriot && card.patriot !== player.loyalty)
		change_loyalty(card.patriot);
	goto_play_tribes();
}

function goto_play_tribes() {
	let card = cards[game.card];
	if (card.tribes) {
		game.count = card.tribes;
		game.state = 'place_tribe';
		game.where = card.region;
		game.selected = select_available_cylinder();
	} else {
		goto_play_roads();
	}
}

states.place_tribe = {
	inactive: "place tribe",
	prompt() {
		if (game.selected < 0) {
			view.prompt = `Place tribe in ${region_names[game.where]} \u2014 select a cylinder.`;
			gen_select_cylinder();
		} else {
			view.prompt = `Place tribe in ${region_names[game.where]}.`;
			gen_action('space', game.where);
		}
	},
	piece(x) {
		push_undo();
		game.selected = x;
	},
	space(s) {
		push_undo();
		log(`${player_names[game.active]} tribe to ${region_names[s]}.`);
		game.pieces[game.selected] = s;
		game.used_pieces.push(game.selected);
		game.selected = -1;
		if (--game.count === 0)
			goto_play_roads();
		else
			game.selected = select_available_cylinder();
	},
}

function goto_play_roads() {
	let card = cards[game.card];
	if (card.roads) {
		game.count = card.roads;
		game.state = 'place_road';
		game.where = card.region;
		game.selected = select_available_block();
	} else {
		goto_play_armies();
	}
}

states.place_road = {
	inactive: "place road",
	prompt() {
		if (game.selected < 0) {
			view.prompt = `Place ${player.loyalty} road in ${region_names[game.where]} \u2014 select a block to move.`;
			gen_select_block();
		} else {
			view.prompt = `Place ${player.loyalty} road in ${region_names[game.where]}.`;
			for (let s of borders[game.where])
				gen_action('space', s);
		}
	},
	piece(x) {
		push_undo();
		game.selected = x;
	},
	space(s) {
		push_undo();
		log(`${player.loyalty} road to ${border_names[s]}.`);
		game.pieces[game.selected] = s;
		game.used_pieces.push(game.selected);
		game.selected = -1;
		if (--game.count === 0)
			goto_play_armies();
		else
			game.selected = select_available_block();
	},
}

function goto_play_armies() {
	let card = cards[game.card];
	if (card.armies) {
		game.count = card.armies;
		game.state = 'place_army';
		game.where = card.region;
		game.selected = select_available_block();
	} else {
		goto_play_spies();
	}
}

states.place_army = {
	inactive: "place army",
	prompt() {
		if (game.selected < 0) {
			view.prompt = `Place ${player.loyalty} army in ${region_names[game.where]} \u2014 select a block to move.`;
			gen_select_block();
		} else {
			view.prompt = `Place ${player.loyalty} army in ${region_names[game.where]}.`;
			gen_action('space', game.where);
		}
	},
	piece(x) {
		push_undo();
		game.selected = x;
	},
	space(s) {
		push_undo();
		log(`${player.loyalty} army to ${region_names[s]}.`);
		game.pieces[game.selected] = s;
		game.used_pieces.push(game.selected);
		game.selected = -1;
		if (--game.count === 0)
			goto_play_spies();
		else
			game.selected = select_available_block();
	},
}

function goto_play_spies() {
	let card = cards[game.card];
	if (card.spies) {
		game.count = card.spies;
		game.state = 'place_spy';
		game.where = card.region;
		game.selected = select_available_cylinder();
	} else {
		goto_play_leveraged();
	}
}

states.place_spy = {
	inactive: "place spy",
	prompt() {
		if (game.selected < 0) {
			view.prompt = `Place spy on a court card in ${region_names[game.where]} \u2014 select a cylinder.`;
			gen_select_cylinder();
		} else {
			view.prompt = `Place spy on a court card in ${region_names[game.where]}.`;
			for (let p = 0; p < game.players.length; ++p) {
				let court = game.players[p].court;
				for (let i = 0; i < court.length; ++i) {
					let card = cards[court[i]];
					if (card.region === game.where) {
						gen_action('card', court[i]);
					}
				}
			}
		}
	},
	piece(x) {
		push_undo();
		game.selected = x;
	},
	card(c) {
		push_undo();
		log(`${player_names[game.active]} spy to ${cards[c].name}.`);
		game.pieces[game.selected] = c;
		game.used_pieces.push(game.selected);
		game.selected = -1;
		if (--game.count === 0)
			goto_play_leveraged();
		else
			game.selected = select_available_cylinder();
	},
}

function goto_play_leveraged() {
	let card = cards[game.card];
	if (card.leveraged) {
		log(`Leveraged.`);
		player.coins += 2;
	}
	goto_play_climate();
}

function goto_play_climate() {
	// TODO: manual click?
	let card = cards[game.card];
	if (card.climate && !game.events.pashtunwali_values) {
		log(`Favored suit to ${card.climate}.`);
		game.favored = card.climate;
	}
	end_action();
}

// CARD-BASED ACTION (COMMON)

const card_action_table = {
	Tax() {
		game.state = 'tax';
	},

	Gift() {
		pay_action_cost(gift_cost());
		game.selected = select_available_cylinder();
		if (game.selected < 0)
			game.state = 'gift';
		else
			do_gift();
	},

	Build() {
		game.count = Math.min(3, Math.floor(player.coins / 2));
		if (player.events.nation_building)
			game.count *= 2;
		game.selected = select_available_block();
		game.state = 'build';
	},

	Move() {
		game.selected = -1;
		game.state = 'move';
	},

	Betray() {
		pay_action_cost(2);
		game.state = 'betray';
	},

	Battle() {
		game.state = 'battle';
		game.where = 0;
	},
}

function do_card_action_1(c, what, reserve) {
	push_undo();
	game.card = c;
	game.where = what;
	if (!active_has_civil_service_reforms() && !game.events.disregard_for_customs) {
		let who = player_with_most_spies(c);
		if (who >= 0 && who !== game.active) {
			game.state = 'bribe';
			game.count = count_player_cylinders(who, c);
			game.reserve = reserve;
			return;
		}
	}
	do_card_action_2();
}

function do_card_action_2() {
	let c = game.card;
	let what = game.where;
	game.used_cards.push(c);
	if (!is_favored_suit(c))
		game.actions --;
	game.count = cards[c].rank;
	logbr();
	log(`Used ${cards[c].name} to ${what}.`);
	card_action_table[what]();
}

// CARD-BASED ACTION: TAX

function can_tax_player(active, p, claim) {
	let okay = claim;
	let shelter = 0;
	let court = game.players[p].court;
	for (let i = 0; i < court.length; ++i) {
		let c = court[i];
		if (!okay && player_rules_region(active, cards[c].region))
			okay = true;
		if (cards[c].suit === Economic)
			shelter += cards[c].rank;
	}
	return okay && game.players[p].coins > shelter;
}

function do_tax_player(p) {
	push_undo();
	log(`Taxed ${player_names[p]} player.`);
	game.players[p].coins --;
	player.coins ++;
	if (--game.count === 0)
		end_action();
}

states.tax = {
	prompt() {
		if (game.count === 1)
			view.prompt = `Tax \u2014 take up to ${game.count} rupee from market cards or players.`;
		else
			view.prompt = `Tax \u2014 take up to ${game.count} rupees from market cards or players.`;

		for (let row = 0; row < 2; ++row) {
			for (let col = 0; col < 6; ++col) {
				if (game.market_coins[row][col] > 0)
					gen_action('card', game.market_cards[row][col]);
			}
		}

		let claim = active_has_claim_of_ancient_lineage();
		for (let p = 0; p < game.players.length; ++p) {
			if (p !== game.active && can_tax_player(game.active, p, claim)) {
				gen_action('player_' + p);
				break;
			}
		}

		gen_action('pass');
	},
	pass() {
		push_undo();
		end_action();
	},
	card(c) {
		push_undo();
		let [row, col] = find_card_in_market(c);
		log(`Taxed market.`);
		game.market_coins[row][col] --;
		player.coins ++;
		if (--game.count === 0)
			end_action();
	},
	player_0() { do_tax_player(0); },
	player_1() { do_tax_player(1); },
	player_2() { do_tax_player(2); },
	player_3() { do_tax_player(3); },
	player_4() { do_tax_player(4); },
}

// CARD-BASED ACTION: GIFT

states.gift = {
	prompt() {
		view.prompt = `Select cylinder to use as Gift.`;
		gen_select_cylinder();
	},
	piece(x) {
		push_undo();
		game.selected = x;
		do_gift();
	},
}

function do_gift() {
	game.pieces[game.selected] = Gift;
	game.used_pieces.push(game.selected);
	end_action();
}

// CARD-BASED ACTION: BUILD

states.build = {
	prompt() {
		view.prompt = `Build up to ${game.count} armies and/or roads.`;
		gen_action('next');
		let must_pay = !player.events.nation_building || (game.count & 1) === 0;
		if (!must_pay || player.coins >= 2) {
			if (game.selected < 0) {
				gen_select_block();
			} else {
				for (let r = first_region; r <= last_region; ++r) {
					if (active_rules_region(r)) {
						gen_action('space', r);
						for (let s of borders[r])
							gen_action('space', s);
					}
				}
			}
		}
	},
	piece(x) {
		push_undo();
		game.selected = x;
	},
	space(s) {
		push_undo();

		let must_pay = !player.events.nation_building || (game.count & 1) === 0;
		if (must_pay)
			pay_action_cost(2);

		if (s <= last_region)
			log(`${player.loyalty} army to ${region_names[s]}.`);
		else
			log(`${player.loyalty} road to ${border_names[s]}.`);
		game.pieces[game.selected] = s;
		game.used_pieces.push(game.selected);
		game.selected = -1;
		--game.count;

		must_pay = !player.events.nation_building || (game.count & 1) === 0;
		if (game.count === 0 || (must_pay && player.coins < 2))
			end_build_action();
		else
			game.selected = select_available_block();
	},
	next() {
		push_undo();
		end_build_action();
	},
}

states.infrastructure = {
	prompt() {
		view.prompt = `Place an additional block.`;
		if (game.selected < 0) {
			gen_select_block();
		} else {
			for (let r = first_region; r <= last_region; ++r) {
				if (active_rules_region(r)) {
					gen_action('space', r);
					for (let s of borders[r])
						gen_action('space', s);
				}
			}
		}
	},
	piece(x) {
		push_undo();
		game.selected = x;
	},
	space(s) {
		push_undo();
		if (s <= last_region)
			log(`${player.loyalty} army to ${region_names[s]}.`);
		else
			log(`${player.loyalty} road to ${border_names[s]}.`);
		game.pieces[game.selected] = s;
		game.used_pieces.push(game.selected);
		game.selected = -1;
		end_action();
	},
}

function end_build_action() {
	if (active_has_infrastructure())
		game.state = 'infrastructure';
	else
		end_action();
}

// CARD-BASED ACTION: MOVE

function last_court_position() {
	let n = 0;
	for (let p = 0; p < game.players.length; ++p)
		n += game.players[p].court.length;
	return n - 1;
}

function court_card_from_position(y) {
	let x = 0;
	for (let p = 0; p < game.players.length; ++p) {
		let court = game.players[p].court;
		if (y < x + court.length)
			return court[y - x];
		x += court.length;
	}
	return 0;
}

function court_position_from_card(c) {
	let x = 0;
	for (let p = 0; p < game.players.length; ++p) {
		let court = game.players[p].court;
		for (let i = 0; i < court.length; ++i) {
			if (c === court[i])
				return x + i;
		}
		x += court.length;
	}
	return -1;
}

function find_border(a, b) {
	if (a > b) {
		let c = a; a = b; b = c;
	}
	if (a === Persia && b === Transcaspia) return Persia_Transcaspia;
	if (a === Persia && b === Herat) return Persia_Herat;
	if (a === Transcaspia && b === Herat) return Transcaspia_Herat;
	if (a === Transcaspia && b === Kabul) return Transcaspia_Kabul;
	if (a === Herat && b === Kabul) return Herat_Kabul;
	if (a === Herat && b === Kandahar) return Herat_Kandahar;
	if (a === Kabul && b === Kandahar) return Kabul_Kandahar;
	if (a === Kabul && b === Punjab) return Kabul_Punjab;
	if (a === Kandahar && b === Punjab) return Kandahar_Punjab;
	throw new Error(`bad border ${a} ${b}`);
}

function can_army_move_across_border(here, next) {
	let border = find_border(here, next);
	let b = active_coalition_blocks();
	for (let i = b; i < b + 12; ++i)
		if (game.pieces[i] === border)
			return true;
	return false;
}

states.move = {
	prompt() {
		if (game.selected >= 0) {
			let here = game.pieces[game.selected];

			// Spy on a court card
			if (here <= 100) {
				let c = here;
				here = court_position_from_card(here);
				view.prompt = `Move spy from ${cards[c].name}.`;

				let last = last_court_position();
				let prev = here > 0 ? here - 1 : last;
				let next = here < last ? here + 1 : 0;
				gen_action('card', court_card_from_position(prev));
				gen_action('card', court_card_from_position(next));

				if (active_has_well_connected()) {
					let pprev = prev > 0 ? prev - 1 : last;
					let nnext = next < last ? next + 1 : 0;
					gen_action('card', court_card_from_position(pprev));
					gen_action('card', court_card_from_position(nnext));
				}

				if (active_has_strange_bedfellows()) {
					let r = cards[c].region;
					for (let p = 0; p < game.players.length; ++p) {
						let court = game.players[p].court;
						for (let i = 0; i < court.length; ++i)
							if (cards[court[i]].region === r)
								gen_action('card', court[i]);
					}
				}
			}

			// Army or tribe in a region
			else {
				if (game.selected < 36)
					view.prompt = `Move ${player.loyalty} army from ${region_names[here]}.`;
				else
					view.prompt = `Move ${player_names[game.active]} tribe from ${region_names[here]}.`;
				let supplies = active_has_indian_supplies();
				for (let next of roads[here])
					if (supplies || can_army_move_across_border(here, next))
						gen_action('space', next);
			}
		} else {
			if (player.events.nationalism) {
				if (game.count === 1)
					view.prompt = `Move up to ${game.count} spy, army, or tribe \u2014 select a spy, army, or tribe to move.`;
				else
					view.prompt = `Move up to ${game.count} spies, armies, and/or tribes \u2014 select a spy, army, or tribe to move.`;
			} else {
				if (game.count === 1)
					view.prompt = `Move up to ${game.count} spy or army \u2014 select a spy or army to move.`;
				else
					view.prompt = `Move up to ${game.count} spies and/or armies \u2014 select a spy or army to move.`;
			}
			gen_action('next');
			gen_select_army_to_move();
			gen_select_spy_to_move();
			if (player.events.nationalism)
				gen_select_tribe_to_move();
		}
	},
	piece(x) {
		push_undo();
		game.selected = x;
	},
	card(c) {
		push_undo();
		let old = game.pieces[game.selected];
		log(`${player_names[game.active]} spy from ${cards[old].name} to ${cards[c].name}.`);
		game.pieces[game.selected] = c;
		game.selected = -1;
		if (--game.count === 0)
			end_action();
	},
	space(s) {
		push_undo();
		let old = game.pieces[game.selected];
		if (game.selected < 36)
			log(`${player.loyalty} army from ${region_names[old]} to ${region_names[s]}.`);
		else
			log(`${player_names[game.active]} tribe from ${region_names[old]} to ${region_names[s]}.`);
		game.pieces[game.selected] = s;
		game.selected = -1;
		if (--game.count === 0)
			end_action();
	},
	next() {
		push_undo();
		end_action();
	},
}

// CARD-BASED ACTION: BETRAY

states.betray = {
	prompt() {
		view.prompt = `Discard one court card where you have a spy.`;
		let x = active_cylinders();
		for (let i = x; i < x + 10; ++i) {
			if (game.pieces[i] > 0 && game.pieces[i] <= 100) {
				let c = game.pieces[i];
				let p = find_card_in_court(c);
				if (!player_has_bodyguards(p))
					gen_action('card', c);
			}
		}
	},
	card(c) {
		push_undo();
		discard_court_card(c);
		if (cards[c].prize) {
			game.card = c;
			game.state = 'accept_prize';
		} else {
			end_action();
		}
	},
}

states.accept_prize = {
	prompt() {
		view.prompt = `You may accept ${cards[game.card].name} as ${a_or_an(cards[game.card].prize)} prize.`;
		gen_action('accept');
		gen_action('refuse');
	},
	accept() {
		log(`${player_names[game.active]} took ${cards[game.card].name} as ${a_or_an(cards[game.card].prize)} prize.`);
		if (cards[game.card].prize !== player.loyalty)
			change_loyalty(cards[game.card].prize);
		player.prizes ++;
		end_action();
	},
	refuse() {
		end_action();
	},
}

// CARD-BASED ACTION: BATTLE

function gen_battle_blocks(where) {
	if (player.loyalty !== Afghan)
		for (let i = 0; i < 12; ++i)
			if (game.pieces[i] === where)
				gen_action('piece', i);
	if (player.loyalty !== British)
		for (let i = 12; i < 24; ++i)
			if (game.pieces[i] === where)
				gen_action('piece', i);
	if (player.loyalty !== Russian)
		for (let i = 24; i < 36; ++i)
			if (game.pieces[i] === where)
				gen_action('piece', i);
}

function count_active_spies_on_card(where) {
	let n = 0;
	let x = active_cylinders();
	for (let i = x; i < x + 10; ++i)
		if (game.pieces[i] === where)
			++n;
	return n;
}

function count_enemy_spies_on_card(where) {
	let n = 0;
	for (let p = 0; p < game.players.length; ++p) {
		if (p !== game.active) {
			let x = player_cylinders(p);
			for (let i = x; i < x + 10; ++i) {
				if (game.pieces[i] === where)
					++n;
			}
		}
	}
	return n;
}

function count_player_armies_in_region(p, where) {
	let n = 0;
	let b = player_coalition_blocks(p);
	for (let i = b; i < b + 12; ++i)
		if (game.pieces[i] === where)
			++n;
	return n;
}

function count_player_cylinders(p, r) {
	let x = player_cylinders(p);
	let n = 0;
	for (let i = x; i < x + 10; ++i)
		if (game.pieces[i] === r)
			++n;
	return n;
}

function count_active_armies_in_region(where) {
	return count_player_armies_in_region(game.active, where);
}

function count_enemy_blocks_on_border(where) {
	let n = 0;
	if (player.loyalty !== Afghan)
		for (let i = 0; i < 12; ++i)
			if (game.pieces[i] === where)
				++n;
	if (player.loyalty !== British)
		for (let i = 12; i < 24; ++i)
			if (game.pieces[i] === where)
				++n;
	if (player.loyalty !== Russian)
		for (let i = 24; i < 36; ++i)
			if (game.pieces[i] === where)
				++n;
	return n;
}

function count_enemy_tribes_and_blocks_in_region(where) {
	let n = 0;
	for (let p = 0; p < game.players.length; ++p) {
		if (game.players[p].loyalty !== player.loyalty) {
			let x = player_cylinders(p);
			for (let i = x; i < x + 10; ++i)
				if (game.pieces[i] === where)
					++n;
		}
	}
	if (player.loyalty !== Afghan)
		for (let i = 0; i < 12; ++i)
			if (game.pieces[i] === where)
				++n;
	if (player.loyalty !== British)
		for (let i = 12; i < 24; ++i)
			if (game.pieces[i] === where)
				++n;
	if (player.loyalty !== Russian)
		for (let i = 24; i < 36; ++i)
			if (game.pieces[i] === where)
				++n;
	for (let b of borders[where])
		n += count_enemy_blocks_on_border(b);
	return n;
}

function is_battle_card(where) {
	return count_active_spies_on_card(where) > 0 && count_enemy_spies_on_card(where) > 0;
}

function is_battle_region(where) {
	return count_active_armies_in_region(where) > 0 && count_enemy_tribes_and_blocks_in_region(where) > 0;
}

function piece_owner(x) {
	if (x < 12) return "Afghan";
	if (x < 24) return "British";
	if (x < 36) return "Russian";
	if (x < 36+10) return player_names[0];
	if (x < 36+20) return player_names[1];
	if (x < 36+30) return player_names[2];
	if (x < 36+40) return player_names[3];
	if (x < 36+50) return player_names[4];
	return "undefined";
}

states.battle = {
	prompt() {
		if (game.where <= 0) {
			view.prompt = `Start a battle in a single region or on a court card.`
			for (let p = 0; p < game.players.length; ++p) {
				let court = game.players[p].court;
				for (let i = 0; i < court.length; ++i) {
					if (is_battle_card(court[i]))
						gen_action('card', court[i]);
				}
			}
			for (let r = first_region; r <= last_region; ++r) {
				if (is_battle_region(r))
					gen_action('space', r);
			}
		} else {
			let where = game.where;
			if (where >= first_region && where <= last_region) {
				view.prompt = `Remove up to ${game.count} tribes, roads, or armies from ${region_names[where]}.`;
				gen_battle_blocks(where);
				for (let border of borders[where])
					gen_battle_blocks(border);
				for (let p = 0; p < game.players.length; ++p) {
					if (p !== game.active && game.players[p].loyalty !== player.loyalty && !player_has_citadel(p, where)) {
						let x = player_cylinders(p);
						for (let i = x; i < x + 10; ++i)
							if (game.pieces[i] === where)
								gen_action('piece', i);
					}
				}
			} else {
				view.prompt = `Remove up to ${game.count} spies on ${cards[where].name}.`;
				for (let p = 0; p < game.players.length; ++p) {
					if (p !== game.active && !player_has_indispensable_advisors(p)) {
						let x = player_cylinders(p);
						for (let i = x; i < x + 10; ++i)
							if (game.pieces[i] === where)
								gen_action('piece', i);
					}
				}
			}
			gen_action('next');
		}
	},
	card(where) {
		push_undo();
		log(`${player_names[game.active]} starts battle on ${cards[where].name}.`);
		game.where = where;
		game.count = Math.min(game.count, count_active_spies_on_card(where));
	},
	space(s) {
		push_undo();
		log(`${player_names[game.active]} starts battle in ${region_names[s]}.`);
		game.where = s;
		game.count = Math.min(game.count, count_active_armies_in_region(s));
	},
	piece(x) {
		push_undo();
		let where = game.pieces[x];
		game.pieces[x] = 0;
		if (x < 36) {
			if (game.where >= first_region && game.where <= last_region)
				log(`Removed ${piece_owner(x)} army from ${region_names[where]}.`);
			else
				log(`Removed ${piece_owner(x)} road from ${border_names[where]}.`);
		} else {
			let p = Math.floor((x - 36) / 10);
			if (where <= 100) {
				log(`Removed ${piece_owner(x)} spy from ${cards[where].name}.`);
				if (player_has_safe_house(p))
					game.pieces[x] = Safe_House;
			} else {
				log(`Removed ${piece_owner(x)} tribe from ${region_names[where]}.`);
				check_region_overthrow(p, where);
			}
		}
		if (--game.count === 0)
			end_action();
	},
	next() {
		push_undo();
		end_action();
	}
}

// PASSIVE: BLACKMAIL

function active_can_blackmail(r) {
	if ((r === Herat && active_has_blackmail_herat()) || (r === Kandahar && active_has_blackmail_kandahar())) {
		for (let p = 0; p < game.players.length; ++p) {
			let court = game.players[p].court;
			for (let i = 0; i < court.length; ++i) {
				let c = court[i];
				if (cards[c].region === r && card_has_no_spies(c))
					return true;
			}
		}
	}
	return false;
}

states.blackmail = {
	prompt() {
		let bmh = game.where !== Kandahar && active_can_blackmail(Herat);
		let bmk = game.where !== Herat && active_can_blackmail(Kandahar);
		let msg = "";
		if (bmh && bmk)
			msg = "any Herat and/or Kandahar court card without a spy";
		else if (bmh)
			msg = "any Herat court card without a spy";
		else
			msg = "any Kandahar court card without a spy";
		if (game.selected < 0) {
			view.prompt = `Blackmail \u2014 select a spy to place on ${msg}.`;
			gen_select_cylinder();
		} else {
			let bmh = game.where !== Kandahar && active_can_blackmail(Herat);
			let bmk = game.where !== Herat && active_can_blackmail(Kandahar);
			view.prompt = `Blackmail \u2014 place a spy on ${msg}.`;
			for (let p = 0; p < game.players.length; ++p) {
				let court = game.players[p].court;
				for (let i = 0; i < court.length; ++i) {
					let c = court[i];
					if (bmh && cards[c].region === Herat && card_has_no_spies(c))
						gen_action('card', c);
					if (bmk && cards[c].region === Kandahar && card_has_no_spies(c))
						gen_action('card', c);
				}
			}
		}
		gen_action('pass');
	},
	piece(x) {
		push_undo();
		game.selected = x;
	},
	card(c) {
		push_undo();
		log(`${player_names[game.active]} blackmail spy to ${cards[c].name}.`);
		game.pieces[game.selected] = c;
		game.used_pieces.push(game.selected);
		if (game.where < 0) {
			game.where = (cards[c].region === Herat) ? Kandahar : Herat;
			game.selected = select_available_cylinder();
		} else {
			resume_actions();
		}
	},
	pass() {
		push_undo();
		resume_actions();
	},
}

// PASSIVE: SAFEHOUSE

function check_safe_house() {
	let first = game.phasing;
	for (let p = first; p < game.players.length; ++p)
		if (check_player_safe_house(p))
			return;
	for (let p = 0; p < first; ++p)
		if (check_player_safe_house(p))
			return;
	resume_actions();
}

function check_player_safe_house(p) {
	let x = player_cylinders(p);
	for (let i = x; i < x + 10; ++i) {
		if (game.pieces[x] === Safe_House) {
			if (player_has_safe_house(p)) {
				if (game.active !== p)
					clear_undo();
				set_active(p);
				game.state = 'safe_house';
				game.selected = x;
				return true;
			} else {
				game.pieces[x] = 0;
			}
		}
	}
	return false;
}

states.safe_house = {
	prompt() {
		view.prompt = `Safe House \u2014 you may place your killed spy on a Safe House.`;
		if (player.court.indexOf(SAFE_HOUSE_1) >= 0)
			gen_action('card', SAFE_HOUSE_1);
		if (player.court.indexOf(SAFE_HOUSE_2) >= 0)
			gen_action('card', SAFE_HOUSE_2);
		gen_action('pass');
	},
	card(c) {
		push_undo();
		game.pieces[game.selected] = c;
		game.selected = -1;
		check_safe_house();
	},
	pass() {
		push_undo();
		game.pieces[game.selected] = 0;
		game.selected = -1;
		check_safe_house();
	},
}

// CLEANUP

function player_court_size() {
	let stars = 3;
	for (let i = 0; i < player.court.length; ++i) {
		let c = player.court[i];
		if (cards[c].suit === Political)
			stars += cards[c].rank;
	}
	return stars;
}

function player_hand_size() {
	let stars = 2;
	for (let i = 0; i < player.court.length; ++i) {
		let c = player.court[i];
		if (cards[c].suit === Intelligence)
			stars += cards[c].rank;
	}
	return stars;
}

function goto_cleanup_court() {
	if (player.court.length > player_court_size()) {
		game.state = 'cleanup_court';
	} else {
		goto_cleanup_hand();
	}
}

states.cleanup_court = {
	inactive: "cleanup court",
	prompt() {
		let size = player_court_size();
		if (player.court.length <= size) {
			view.prompt = `Discard cards in your court until you are within your limit \u2014 done.`;
			gen_action('next');
		} else {
			view.prompt = `Discard cards in your court until you are within your limit (${size}).`;
			for (let i = 0; i < player.court.length; ++i)
				gen_action('card', player.court[i]);
		}
	},
	card(c) {
		push_undo();
		log(`${player_names[game.active]} discarded ${cards[c].name} from their court.`);
		discard_court_card(c);
	},
	next() {
		push_undo();
		goto_cleanup_hand();
	}
}

function goto_cleanup_hand() {
	if (player.hand.length > player_hand_size()) {
		game.state = 'cleanup_hand';
	} else {
		goto_discard_events();
	}
}

states.cleanup_hand = {
	inactive: "cleanup hand",
	prompt() {
		let size = player_hand_size();
		if (player.hand.length <= size) {
			view.prompt = `Discard cards in your hand until you are within your limit \u2014 done.`;
			gen_action('next');
		} else {
			view.prompt = `Discard cards in your hand until you are within your limit (${size}).`;
			for (let i = 0; i < player.hand.length; ++i)
				gen_action('card', player.hand[i]);
		}
	},
	card(c) {
		push_undo();
		log(`${player_names[game.active]} discarded ${cards[c].name} from their hand.`);
		remove_from_array(player.hand, c);
	},
	next() {
		goto_discard_events();
	}
}

function do_discard_event(row, c) {
	game.market_cards[row][0] = 0;
	logbr();
	log(`Discarded ${cards[c].if_discarded}.`);
	logbr();
	if (is_dominance_check(c)) {
		do_dominance_check();
		if (game.state === 'game_over')
			return;
		goto_discard_events();
	} else {
		events_if_discarded[cards[c].if_discarded](row);
	}
}

function goto_discard_events() {
	clear_undo();
	if (is_event_card(game.market_cards[0][0])) {
		do_discard_event(0, game.market_cards[0][0]);
	} else if (is_event_card(game.market_cards[1][0])) {
		do_discard_event(1, game.market_cards[1][0]);
	} else {
		goto_refill_market();
	}
}

function discard_instability_cards() {
	for (let row = 0; row < 2; ++row) {
		for (let col = 0; col < 6; ++col) {
			let c = game.market_cards[row][col];
			if (is_dominance_check(c))
				game.market_cards[row][col] = 0;
		}
	}
}

function goto_refill_market() {
	// Move all cards (and their rupees) to the left.
	for (let row = 0; row < 2; ++row) {
		let row_cards = game.market_cards[row];
		let row_coins = game.market_coins[row];
		for (let to = 0; to < 6; ++to) {
			if (row_cards[to] === 0) {
				for (let from = to + 1; from < 6; ++from) {
					if (row_cards[from] > 0) {
						row_cards[to] = row_cards[from];
						row_cards[from] = 0;
						row_coins[to] += row_coins[from];
						row_coins[from] = 0;
						break;
					}
				}
			}
		}
	}

	// Instability ...
	let instability = 0;
	for (let row = 0; row < 2; ++row) {
		for (let col = 0; col < 6; ++col) {
			let c = game.market_cards[row][col];
			if (is_dominance_check(c))
				++instability;
		}
	}

	// Fill with new cards from left (top row in each column first)
	for (let col = 0; col < 6; ++col) {
		for (let row = 0; row < 2; ++row) {
			if (game.deck.length > 0) {
				if (game.market_cards[row][col] === 0) {
					let c = game.deck.pop();
					game.market_cards[row][col] = c;
					if (instability > 0 && is_dominance_check(c)) {
						log(`Instability!`);
						discard_instability_cards();
						do_dominance_check();
						if (game.state === 'game_over')
							return;
						goto_refill_market();
					}
				}
			}
		}
	}

	goto_next_player();
}

// EVENTS: IF DISCARDED

const events_if_discarded = {

	"Military" () {
		game.favor = Military;
		goto_discard_events();
	},

	"Embarrassment of Riches" () {
		game.events.embarrassment_of_riches = 1;
		goto_discard_events();
	},

	"Disregard for Customs" () {
		game.events.disregard_for_customs = 1;
		goto_discard_events();
	},

	"Failure to Impress" () {
		for (let p = 0; p < game.players.length; ++p)
			game.players[p].prizes = 0;
		goto_discard_events();
	},

	"Riots in Punjab" () {
		remove_all_tribes_and_armies(Punjab);
		goto_discard_events();
	},

	"Riots in Herat" () {
		remove_all_tribes_and_armies(Herat);
		goto_discard_events();
	},

	"No effect" () {
		goto_discard_events();
	},

	"Riots in Kabul" () {
		remove_all_tribes_and_armies(Kabul);
		goto_discard_events();
	},

	"Riots in Persia" () {
		remove_all_tribes_and_armies(Persia);
		goto_discard_events();
	},

	"Confidence Failure" () {
		game.state = 'confidence_failure';
		if (player.hand.length === 0)
			next_confidence_failure();
	},

	"Intelligence" () {
		game.favored = Intelligence;
		goto_discard_events();
	},

	"Political" () {
		game.favored = Political;
		goto_discard_events();
	},

}

states.confidence_failure = {
	prompt() {
		view.prompt = "Confidence Failure \u2014 discard a card from your hand.";
		for (let i = 0; i < player.hand.length; ++i)
			gen_action('card', player.hand[i]);
	},
	card(c) {
		log(`${player_names[game.active]} discarded ${cards[c].name} from their hand.`);
		remove_from_array(player.hand, c);
		next_confidence_failure();
	},
}

function next_confidence_failure() {
	for (;;) {
		let next = next_player(game.active);
		if (next === game.phasing)
			return goto_discard_events();
		if (game.players[next].hand.length > 0)
			return set_active(next);
	}
}

// EVENTS: IF PURCHASED

const events_if_purchased = {

	"New Tactics" () {
		player.events.new_tactics = 1;
		end_action();
	},

	"Koh-i-noor Recovered" () {
		player.events.koh_i_noor = 1;
		end_action();
	},

	"Courtly Manners" () {
		player.events.courtly_manners = 1;
		end_action();
	},

	"Rumor" () {
		game.state = 'rumor';
	},

	"Conflict Fatigue" () {
		game.events.conflict_fatigue = 1;
		end_action();
	},

	"Nationalism" () {
		player.events.nationalism = 1;
		end_action();
	},

	"Public Withdrawal" () {
		throw new Error("cannot purchase");
	},

	"Nation Building" () {
		player.events.nation_building = 1;
		end_action();
	},

	"Backing of Persian Aristocracy" () {
		player.coins += 3;
		end_action();
	},

	"Other Persuasive Methods" () {
		game.state = 'other_persuasive_methods';
	},

	"Pashtunwali Values" () {
		game.state = 'pashtunwali_values';
	},

	"Rebuke" () {
		game.state = 'rebuke';
	},

}

// TODO: other_persuasive_methods
// TODO: pashtunwali_values
// TODO: rebuke

function check_public_withdrawal() {
	// Remove any money placed on card "Public Withdrawal" from the game.
	for (let row = 0; row < 2; ++row)
		for (let col = 0; col < 6; ++col)
			if (game.market_cards[row][col] === PUBLIC_WITHDRAWAL)
				game.market_coins[row][col] = 0;
}

states.rumor = {
	prompt() {
		view.prompt = `Rumor \u2014 choose a player.`;
		for (let p = 0; p < game.players.length; ++p)
			if (p !== game.active)
				gen_action('player_' + p);
	},
	player_0() { do_rumor(0); },
	player_1() { do_rumor(1); },
	player_2() { do_rumor(2); },
	player_3() { do_rumor(3); },
	player_4() { do_rumor(4); },
}

function do_rumor(p) {
	push_undo();
	log(`${player_names[game.active]} chose ${player_names[p]} for Rumor.`);
	game.players[p].events.rumor = 1;
	end_action();
}

states.other_persuasive_methods = {
	prompt() {
		view.prompt = `Other Persuasive Methods \u2014 exchange your hand with another player.`;
		for (let p = 0; p < game.players.length; ++p)
			if (p !== game.active)
				gen_action('player_' + p);
	},
	player_0() { do_other_persuasive_methods(0); },
	player_1() { do_other_persuasive_methods(1); },
	player_2() { do_other_persuasive_methods(2); },
	player_3() { do_other_persuasive_methods(3); },
	player_4() { do_other_persuasive_methods(4); },
}

function do_other_persuasive_methods(p) {
	// TODO: clear_undo instead?
	push_undo();
	log(`${player_names[game.active]} exchanged hands with ${player_names[p]}.`);
	let swap = game.players[game.active].hand;
	game.players[game.active].hand = game.players[p].hand;
	game.players[p].hand = swap;
	end_action();
}

states.pashtunwali_values = {
	prompt() {
		view.prompt = `Pashtunwali Values \u2014 choose a suit to favor.`;
		gen_action('suit_political');
		gen_action('suit_intelligence');
		gen_action('suit_economic');
		gen_action('suit_military');
	},
	suit_political() { do_pashtunwali_values(Political); },
	suit_intelligence() { do_pashtunwali_values(Intelligence); },
	suit_economic() { do_pashtunwali_values(Economic); },
	suit_military() { do_pashtunwali_values(Military); },
}

function do_pashtunwali_values(suit) {
	log(`Favored suit to ${suit}.`);
	game.favored = suit;
	game.events.pashtunwali_values = 1;
	end_action();
}

states.rebuke = {
	prompt() {
		view.prompt = `Rebuke \u2014 remove all tribes and armies in a single region.`;
		for (let s = first_region; s <= last_region; ++s)
			// TODO: can pick empty region?
			gen_action('space', s);
	},
	space(s) {
		push_undo();
		log(`Removed all tribes and armies in ${region_names[s]}.`);
		remove_all_tribes_and_armies(s);
		end_action();
	},
}

// DOMINANCE CHECK

function count_cylinders_in_play(p) {
	let n = 0;
	let x = player_cylinders(p);
	for (let i = x; i < x + 10; ++i)
		if (game.pieces[i] > 0)
			++n;
	return n;
}

function count_influence_points(p) {
	let n = 1 + game.players[p].prizes;
	let x = player_cylinders(p);

	if (!game.events.embarrassment_of_riches) {
		let gv = game.players[p].events.koh_i_noor ? 2 : 1;
		for (let i = x; i < x + 10; ++i)
			if (game.pieces[i] === Gift)
				n += gv;
	}

	if (!game.players[p].events.rumor) {
		let court = game.players[p].court;
		for (let i = 0; i < court.length; ++i)
			if (cards[court[i]].patriot)
				++n;
	}

	return n;
}

function assign_vp(points, score, sorted) {
	const PLACE = [ "1st", "2nd", "3rd" ];
	let place = 0;
	sorted.sort((a,b) => b-a);
	while (points.length > 0 && sorted.length > 0) {
		let n = 0;
		for (let p = 0; p < game.players.length; ++p)
			if (score[p] === sorted[0])
				++n;
		let v = 0;
		for (let i = 0; i < n; ++i)
			v += points[i] | 0;
		v = Math.floor(v / n);
		let msg = `${PLACE[place]} place:`;
		for (let p = 0; p < game.players.length; ++p) {
			if (score[p] === sorted[0]) {
				msg += "\n" + player_names[p] + " scored " + v + " vp.";
				game.players[p].vp += v;
			}
		}
		log(msg);
		points = points.slice(n);
		sorted = sorted.slice(n);
		place += n;
	}
}

function is_final_dominance_check() {
	for (let row = 0; row < 2; ++row)
		for (let col = 0; col < 6; ++col)
			if (is_dominance_check(game.market_cards[row][col]))
				return false;
	for (let i = 0; i < game.deck.length; ++i)
		if (is_dominance_check(game.deck[i]))
			return false;
	return true;
}

function do_dominance_check() {
	let n_afghan = 0;
	let n_british = 0;
	let n_russian = 0;
	let success = null;

	for (let i = 0; i < 12; ++i) {
		if (game.pieces[i] > 0)
			n_afghan ++;
		if (game.pieces[i+12] > 0)
			n_british ++;
		if (game.pieces[i+24] > 0)
			n_russian ++;
	}

	let limit = game.events.conflict_fatigue ? 2 : 4;
	if (n_afghan >= n_british+limit && n_afghan >= n_russian+limit)
		success = Afghan;
	else if (n_british >= n_afghan+limit && n_british >= n_russian+limit)
		success = British;
	else if (n_russian >= n_british+limit && n_russian >= n_afghan+limit)
		success = Russian;

	let final = is_final_dominance_check();

	let score = new Array(game.players.length).fill(0);
	if (success) {
		logbr();
		log(`.dc.${success} Dominant ${success} Coalition`);
		logbr();
		if (final)
			log(`Final Dominance Check.`);
		for (let p = 0; p < game.players.length; ++p) {
			if (game.players[p].loyalty === success) {
				score[p] = count_influence_points(p);
				log(`${player_names[p]} had ${score[p]} influence.`);
			}
		}
		if (final)
			assign_vp([10, 6, 2], score, score.filter(x=>x>0));
		else
			assign_vp([5, 3, 1], score, score.filter(x=>x>0));
	} else {
		logbr();
		log(`.dc.unsuccessful Unsuccessful Check`);
		logbr();
		if (final)
			log(`Final Dominance Check.`);
		for (let p = 0; p < game.players.length; ++p) {
			score[p] = count_cylinders_in_play(p);
			log(`${player_names[p]} had ${score[p]} cylinders.`);
		}
		if (final)
			assign_vp([6, 2], score, score.slice());
		else
			assign_vp([3, 1], score, score.slice());
	}

	// Clear the board.
	for (let i = 0; i < 36; ++i)
		game.pieces[i] = 0;

	// Prince Akbar Khan
	if (any_player_has_insurrection()) {
		log(`Insurrection placed two Afghan armies in Kabul.`);
		game.pieces[0] = Kabul;
		game.pieces[1] = Kabul;
	}

	// Check instant victory
	let vps = game.players.map((pp,i) => pp.vp).sort((a,b)=>b-a);
	if (vps[0] >= vps[1] + 4)
		goto_game_over();

	if (final)
		goto_game_over();

	game.events = {};
	for (let p = 0; p < game.players.length; ++p)
		game.players[p].events = {};
}

function vp_tie(pp) {
	let court = pp.court;
	let stars = 0;
	for (let i = 0; i < court.length; ++i) {
		let c = court[i];
		if (cards[c].suit === Military)
			stars += cards[c].rank;
	}
	return pp.vp * 10000 + stars * 100 + pp.coins;
}

function goto_game_over() {
	let vps = game.players.map((pp,i) => [vp_tie(pp),i]).sort((a,b)=>b[0]-a[0]);
	let result = [];
	for (let i = 0; i < vps.length; ++i)
		if (vps[i][0] === vps[0][0])
			result.push(player_names[vps[i][1]])
	game.result = result.join(", ");
	game.victory = result.join(" and ") + " won!";
	logbr();
	log(game.victory);
	game.state = 'game_over';
}

// SETUP

function prepare_deck() {
	let court_cards = [];
	for (let i = 1; i <= 100; ++i)
		court_cards.push(i);

	let event_cards = [];
	for (let i = 105; i <= 116; ++i)
		event_cards.push(i);

	let piles = [ [], [], [], [], [], [] ];

	shuffle(court_cards);

	for (let i = 0; i < 6; ++i)
		for (let k = 0; k < 5 + game.players.length; ++k)
			piles[i].push(court_cards.pop());

	// Leftmost pile is 5, rightmost pile is 0
	piles[3].push(101);
	piles[2].push(102);
	piles[1].push(103);
	piles[0].push(104);

	shuffle(event_cards);

	piles[4].push(event_cards.pop());
	piles[4].push(event_cards.pop());
	piles[3].push(event_cards.pop());
	piles[2].push(event_cards.pop());
	piles[1].push(event_cards.pop());
	piles[0].push(event_cards.pop());

	for (let i = 0; i < 6; ++i)
		shuffle(piles[i]);

	game.deck = piles.flat();
}

exports.setup = function (seed, scenario, options) {
	let player_count = scenario_player_count[scenario];

	game = {
		seed: seed,

		active: 0,
		state: "none",
		used_cards: [],
		used_pieces: [],
		count: 0,
		reserve: 0,
		selected: -1,
		region: 0,
		card: 0,
		where: 0,

		phasing: null,
		actions: 0,
		deck: [],
		favored: Political,
		events: {},
		pieces: new Array(36 + player_count * 10).fill(0),
		market_cards: [
			[0,0,0,0,0,0],
			[0,0,0,0,0,0],
		],
		market_coins: [
			[0,0,0,0,0,0],
			[0,0,0,0,0,0],
		],
		players: [],

		log: [],
		undo: [],
	};

	for (let i = 0; i < player_count; ++i) {
		game.players[i] = {
			vp: 0,
			loyalty: null,
			prizes: 0,
			coins: 4,
			hand: [],
			court: [],
			events: {},
		}
	}

	prepare_deck();

	for (let row = 0; row < 2; ++row)
		for (let col = 0; col < 6; ++col)
			game.market_cards[row][col] = game.deck.pop();

	// Starting loyalty, starting with a random player.
	game.state = 'loyalty';
	game.active = random(player_count);

	return save_game();
}

function load_game(state) {
	game = state;
	game.active = player_index[game.active];
	update_aliases();
}

function save_game() {
	game.active = player_names[game.active];
	return game;
}

exports.action = function (state, current, action, arg) {
	load_game(state);
	let S = states[game.state];
	if (action in S) {
		S[action](arg, current);
	} else {
		if (action === 'undo' && game.undo && game.undo.length > 0)
			pop_undo();
		else
			throw new Error("Invalid action: " + action);
	}
	return save_game();
}

exports.resign = function (state, current) {
	load_game(state);
	goto_game_over();
	return save_game();
}

exports.view = function(state, current) {
	current = player_index[current];
	load_game(state);

	view = {
		log: game.log,
		active: player_names[game.active],
		prompt: null,
		favored: game.favored,
		events: game.events,
		pieces: game.pieces,
		market_cards: game.market_cards,
		market_coins: game.market_coins,
		players: game.players,
		selected: game.selected,
		ruler: [
			ruler_of_region(Persia),
			ruler_of_region(Transcaspia),
			ruler_of_region(Herat),
			ruler_of_region(Kabul),
			ruler_of_region(Kandahar),
			ruler_of_region(Punjab)
		],
	};

	if (game.state === 'game_over') {
		view.prompt = game.victory;
	} else if (current === 'Observer' || game.active !== current) {
		let inactive = states[game.state].inactive || game.state;
		view.prompt = `Waiting for ${player_names[game.active]} \u2014 ${inactive}...`;
	} else {
		view.actions = {}
		states[game.state].prompt();
		view.prompt = player_names[game.active] + ": " + view.prompt;
		if (game.undo && game.undo.length > 0)
			view.actions.undo = 1;
		else
			view.actions.undo = 0;
	}

	save_game();
	return view;
}