<commit_before>import {
	User,
	Room,
} from '@karuta/core';

import {
	Command as cmd,
	Player,
	PlayerPhase as Phase,
	Card,
	CardArea,
	CardAreaType,
	CardOptionStruct,
	General,
	Skill,
	SkillArea,
	SkillAreaType,
	SkillOwner,
} from '@karuta/sanguosha-core';

import CardOption from './CardOption';

interface ChooseGeneralOptions {
	timeout: number;
	num: number;
	sameKingdom: boolean;
	forced: boolean;
}

const CHOOSE_GENERAL_DEFAULT_OPTIONS = {
	timeout: 40,
	num: 1,
	sameKingdom: false,
	forced: true,
};

type PropertyValue = string | number | boolean | object | null;

interface PlayAction {
	card?: Card;
}

class ServerPlayer extends Player implements SkillOwner {
	protected user: User;

	protected headSkillArea: SkillArea;

	protected headAcquiredSkillArea: SkillArea;

	protected deputySkillArea: SkillArea;

	protected deputyAcquiredSkillArea: SkillArea;

	protected handArea: CardArea;

	protected equipArea: CardArea;

	protected judgeArea: CardArea;

	protected processArea: CardArea;

	protected useCount: Map<string, number>;

	protected useLimit: Map<string, number>;

	protected phases: Phase[];

	protected requestTimeout: number;

	constructor(user: User) {
		super();
		this.user = user;

		this.headSkillArea = new SkillArea(SkillAreaType.Head);
		this.headAcquiredSkillArea = new SkillArea(SkillAreaType.HeadAcquired);
		this.deputySkillArea = new SkillArea(SkillAreaType.Deputy);
		this.deputyAcquiredSkillArea = new SkillArea(SkillAreaType.DeputyAcquired);

		this.handArea = new CardArea(CardAreaType.Hand, this);
		this.handArea.setOpen(false);
		this.equipArea = new CardArea(CardAreaType.Equip, this);
		this.judgeArea = new CardArea(CardAreaType.Judge, this);
		this.processArea = new CardArea(CardAreaType.Process, this);

		this.useCount = new Map();
		this.useLimit = new Map();
		this.phases = [];
		this.requestTimeout = 15000;
	}

	getUser(): User {
		return this.user;
	}

	getId(): number {
		return this.user ? this.user.getId() : 0;
	}

	getName(): string | undefined {
		return this.user ? this.user.getName() : undefined;
	}

	getRoom(): Room | null {
		return this.user ? this.user.getRoom() : null;
	}

	getSkillArea(): SkillArea {
		return this.headSkillArea;
	}

	getAcquiredSkillArea(): SkillArea {
		return this.headAcquiredSkillArea;
	}

	getHeadSkillArea(): SkillArea {
		return this.headSkillArea;
	}

	getHeadAcquiredSkillArea(): SkillArea {
		return this.headAcquiredSkillArea;
	}

	getDeputySkillArea(): SkillArea {
		return this.deputySkillArea;
	}

	getDeputyAcquiredSkillArea(): SkillArea {
		return this.deputyAcquiredSkillArea;
	}

	findSkillArea(type: SkillAreaType): SkillArea {
		switch (type) {
		case SkillAreaType.Head:
			return this.getHeadSkillArea();
		case SkillAreaType.Deputy:
			return this.getDeputySkillArea();
		case SkillAreaType.HeadAcquired:
			return this.getHeadAcquiredSkillArea();
		case SkillAreaType.DeputyAcquired:
			return this.getDeputyAcquiredSkillArea();
		default:
			return this.getHeadSkillArea();
		}
	}

	getSkillAreas(): SkillArea[] {
		return [
			this.headSkillArea,
			this.deputySkillArea,
			this.headAcquiredSkillArea,
			this.deputyAcquiredSkillArea,
		];
	}

	hasSkill(skill: Skill): boolean {
		return this.getSkillAreas().some((area) => area.has(skill));
	}

	getHandArea(): CardArea {
		return this.handArea;
	}

	getEquipArea(): CardArea {
		return this.equipArea;
	}

	getJudgeArea(): CardArea {
		return this.judgeArea;
	}

	getProcessArea(): CardArea {
		return this.processArea;
	}

	getCardAreas(): CardArea[] {
		return [
			this.handArea,
			this.equipArea,
			this.judgeArea,
		];
	}

	isEmpty(): boolean {
		for (const area of this.getCardAreas()) {
			if (area.size > 0) {
				return false;
			}
		}
		return true;
	}

	setRequestTimeout(msecs: number): void {
		this.requestTimeout = msecs;
	}

	getRequestTimeout(): number {
		return this.requestTimeout;
	}

	async askForGeneral(generals: General[], options: Partial<ChooseGeneralOptions> = {}): Promise<General[]> {
		const opt: ChooseGeneralOptions = {
			...CHOOSE_GENERAL_DEFAULT_OPTIONS,
			...options,
		};

		let reply = [];
		try {
			reply = await this.user.request(cmd.ChooseGeneral, {
				sameKingdom: !!opt.sameKingdom,
				num: opt.num || 1,
				generals: generals.map((general, i) => ({
					id: i,
					kingdom: general.getKingdom(),
					name: general.getName(),
				})),
			}, opt.timeout ? opt.timeout * 1000 : this.requestTimeout * 2);
		} catch (error) {
			console.error(error);
		}

		const chosenGenerals = [];
		if (reply && reply instanceof Array) {
			for (const id of reply) {
				if (id >= 0 && id < generals.length) {
					chosenGenerals.push(generals[id]);
				}
			}
		}

		if (opt.forced && chosenGenerals.length < opt.num && generals.length >= opt.num) {
			let availableGenerals = generals;
			if (opt.sameKingdom && opt.num > 1) {
				const kingdoms = new Map();
				for (const general of generals) {
					const alliances = kingdoms.get(general.getKingdom());
					if (!alliances) {
						kingdoms.set(general.getKingdom(), [general]);<commit_msg>Avoid "undefined" in error log<commit_after>import {
	User,
	Room,
} from '@karuta/core';

import {
	Command as cmd,
	Player,
	PlayerPhase as Phase,
	Card,
	CardArea,
	CardAreaType,
	CardOptionStruct,
	General,
	Skill,
	SkillArea,
	SkillAreaType,
	SkillOwner,
} from '@karuta/sanguosha-core';

import CardOption from './CardOption';

interface ChooseGeneralOptions {
	timeout: number;
	num: number;
	sameKingdom: boolean;
	forced: boolean;
}

const CHOOSE_GENERAL_DEFAULT_OPTIONS = {
	timeout: 40,
	num: 1,
	sameKingdom: false,
	forced: true,
};

type PropertyValue = string | number | boolean | object | null;

interface PlayAction {
	card?: Card;
}

class ServerPlayer extends Player implements SkillOwner {
	protected user: User;

	protected headSkillArea: SkillArea;

	protected headAcquiredSkillArea: SkillArea;

	protected deputySkillArea: SkillArea;

	protected deputyAcquiredSkillArea: SkillArea;

	protected handArea: CardArea;

	protected equipArea: CardArea;

	protected judgeArea: CardArea;

	protected processArea: CardArea;

	protected useCount: Map<string, number>;

	protected useLimit: Map<string, number>;

	protected phases: Phase[];

	protected requestTimeout: number;

	constructor(user: User) {
		super();
		this.user = user;

		this.headSkillArea = new SkillArea(SkillAreaType.Head);
		this.headAcquiredSkillArea = new SkillArea(SkillAreaType.HeadAcquired);
		this.deputySkillArea = new SkillArea(SkillAreaType.Deputy);
		this.deputyAcquiredSkillArea = new SkillArea(SkillAreaType.DeputyAcquired);

		this.handArea = new CardArea(CardAreaType.Hand, this);
		this.handArea.setOpen(false);
		this.equipArea = new CardArea(CardAreaType.Equip, this);
		this.judgeArea = new CardArea(CardAreaType.Judge, this);
		this.processArea = new CardArea(CardAreaType.Process, this);

		this.useCount = new Map();
		this.useLimit = new Map();
		this.phases = [];
		this.requestTimeout = 15000;
	}

	getUser(): User {
		return this.user;
	}

	getId(): number {
		return this.user ? this.user.getId() : 0;
	}

	getName(): string | undefined {
		return this.user ? this.user.getName() : undefined;
	}

	getRoom(): Room | null {
		return this.user ? this.user.getRoom() : null;
	}

	getSkillArea(): SkillArea {
		return this.headSkillArea;
	}

	getAcquiredSkillArea(): SkillArea {
		return this.headAcquiredSkillArea;
	}

	getHeadSkillArea(): SkillArea {
		return this.headSkillArea;
	}

	getHeadAcquiredSkillArea(): SkillArea {
		return this.headAcquiredSkillArea;
	}

	getDeputySkillArea(): SkillArea {
		return this.deputySkillArea;
	}

	getDeputyAcquiredSkillArea(): SkillArea {
		return this.deputyAcquiredSkillArea;
	}

	findSkillArea(type: SkillAreaType): SkillArea {
		switch (type) {
		case SkillAreaType.Head:
			return this.getHeadSkillArea();
		case SkillAreaType.Deputy:
			return this.getDeputySkillArea();
		case SkillAreaType.HeadAcquired:
			return this.getHeadAcquiredSkillArea();
		case SkillAreaType.DeputyAcquired:
			return this.getDeputyAcquiredSkillArea();
		default:
			return this.getHeadSkillArea();
		}
	}

	getSkillAreas(): SkillArea[] {
		return [
			this.headSkillArea,
			this.deputySkillArea,
			this.headAcquiredSkillArea,
			this.deputyAcquiredSkillArea,
		];
	}

	hasSkill(skill: Skill): boolean {
		return this.getSkillAreas().some((area) => area.has(skill));
	}

	getHandArea(): CardArea {
		return this.handArea;
	}

	getEquipArea(): CardArea {
		return this.equipArea;
	}

	getJudgeArea(): CardArea {
		return this.judgeArea;
	}

	getProcessArea(): CardArea {
		return this.processArea;
	}

	getCardAreas(): CardArea[] {
		return [
			this.handArea,
			this.equipArea,
			this.judgeArea,
		];
	}

	isEmpty(): boolean {
		for (const area of this.getCardAreas()) {
			if (area.size > 0) {
				return false;
			}
		}
		return true;
	}

	setRequestTimeout(msecs: number): void {
		this.requestTimeout = msecs;
	}

	getRequestTimeout(): number {
		return this.requestTimeout;
	}

	async askForGeneral(generals: General[], options: Partial<ChooseGeneralOptions> = {}): Promise<General[]> {
		const opt: ChooseGeneralOptions = {
			...CHOOSE_GENERAL_DEFAULT_OPTIONS,
			...options,
		};

		let reply = [];
		try {
			reply = await this.user.request(cmd.ChooseGeneral, {
				sameKingdom: !!opt.sameKingdom,
				num: opt.num || 1,
				generals: generals.map((general, i) => ({
					id: i,
					kingdom: general.getKingdom(),
					name: general.getName(),
				})),
			}, opt.timeout ? opt.timeout * 1000 : this.requestTimeout * 2);
		} catch (error) {
			// Timeout
		}

		const chosenGenerals = [];
		if (reply && reply instanceof Array) {
			for (const id of reply) {
				if (id >= 0 && id < generals.length) {
					chosenGenerals.push(generals[id]);
				}
			}
		}

		if (opt.forced && chosenGenerals.length < opt.num && generals.length >= opt.num) {
			let availableGenerals = generals;
			if (opt.sameKingdom && opt.num > 1) {
				const kingdoms = new Map();
				for (const general of generals) {
					const alliances = kingdoms.get(general.getKingdom());
					if (!alliances) {
						kingdoms.set(general.getKingdom(), [general]);
{'fraction_non_alphanumeric': 0.12255772646536411, 'fraction_numerical': 0.003552397868561279, 'mean_word_length': 4.090406830738322, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2614499', 'n_tokens_mistral': 3971, 'n_tokens_neox': 3474, 'n_words': 733}