You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
95 lines
3.2 KiB
95 lines
3.2 KiB
import { RankDataManager } from '../../../manager/RankDataManager';
|
|
import { aiLog } from './AiLog';
|
|
|
|
/** 铲子策略类型(Rank.aiusechanzi 三档) */
|
|
export enum AiShovelPolicyType {
|
|
/** 类型 1:随机挖 */
|
|
Type1 = 1,
|
|
/** 类型 2:扩已开岛 / 贴邻 */
|
|
Type2 = 2,
|
|
/** 类型 3:贴路 */
|
|
Type3 = 3,
|
|
}
|
|
|
|
/** 普通字策略类型(Rank.aiusetext 两档) */
|
|
export enum AiTextPolicyType {
|
|
/** 类型 1:等级优先 */
|
|
Type1 = 1,
|
|
/** 类型 2:单字冲级 */
|
|
Type2 = 2,
|
|
}
|
|
|
|
/** 开战时按对手段位权重抽样,整局固定 */
|
|
export interface AiBattlePersonality {
|
|
/** 本局铲子策略类型 */
|
|
readonly shovelPolicy: AiShovelPolicyType;
|
|
/** 本局普通字策略类型 */
|
|
readonly textPolicy: AiTextPolicyType;
|
|
}
|
|
|
|
/** 从 Rank.aiusechanzi / aiusetext 加权抽样整局策略 */
|
|
export class AiBattlePersonalityFactory {
|
|
/**
|
|
* 按对手段位配表抽样 personality。
|
|
* @param opponentRankId 对手段位 id
|
|
* @param seed 随机种子
|
|
*/
|
|
roll(opponentRankId: number, seed: number): AiBattlePersonality | null {
|
|
const rank = RankDataManager.getRankConfig(opponentRankId);
|
|
if (!rank) {
|
|
console.warn('[AiBattlePersonalityFactory] Rank missing,使用默认策略', opponentRankId);
|
|
return {
|
|
shovelPolicy: AiShovelPolicyType.Type1,
|
|
textPolicy: AiTextPolicyType.Type1,
|
|
};
|
|
}
|
|
const shovel = this._pickType(rank.aiusechanzi, 3, seed, 1);
|
|
const text = this._pickType(rank.aiusetext, 2, seed ^ 0x85ebca6b, 1);
|
|
if (shovel == null || text == null) {
|
|
console.warn('[AiBattlePersonalityFactory] 权重无效,使用默认策略', opponentRankId);
|
|
return {
|
|
shovelPolicy: AiShovelPolicyType.Type1,
|
|
textPolicy: AiTextPolicyType.Type1,
|
|
};
|
|
}
|
|
return {
|
|
shovelPolicy: shovel as AiShovelPolicyType,
|
|
textPolicy: text as AiTextPolicyType,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 加权抽类型(返回 1-based 类型号)。
|
|
* @param weights 权重数组
|
|
* @param expectLen 期望长度
|
|
* @param seed 种子
|
|
* @param typeBase 起始类型号
|
|
*/
|
|
private _pickType(weights: number[] | undefined, expectLen: number, seed: number, typeBase: number): number | null {
|
|
if (!weights || weights.length !== expectLen) {
|
|
aiLog('[AiBattlePersonalityFactory] weight length invalid', expectLen);
|
|
return null;
|
|
}
|
|
const w: number[] = [];
|
|
let total = 0;
|
|
for (let i = 0; i < expectLen; i++) {
|
|
const v = Math.max(0, Math.floor(Number(weights[i]) || 0));
|
|
w.push(v);
|
|
total += v;
|
|
}
|
|
if (total <= 0) {
|
|
aiLog('[AiBattlePersonalityFactory] weight total <= 0');
|
|
return null;
|
|
}
|
|
let x = (Math.floor(Number(seed) || 0) >>> 0) || 1;
|
|
x ^= x << 13;
|
|
x ^= x >>> 17;
|
|
x ^= x << 5;
|
|
let roll = ((x >>> 0) / 4294967296) * total;
|
|
for (let i = 0; i < expectLen; i++) {
|
|
roll -= w[i];
|
|
if (roll < 0) return typeBase + i;
|
|
}
|
|
return typeBase + expectLen - 1;
|
|
}
|
|
}
|
|
|