106 lines
4.0 KiB
TypeScript
106 lines
4.0 KiB
TypeScript
import { LiteHashDRBG, RandUtil } from '@sammo-ts/common';
|
|
|
|
import { simpleSerialize } from '../war/utils.js';
|
|
|
|
export type NeutralResourceAuctionType = 'BUY_RICE' | 'SELL_RICE';
|
|
|
|
export interface NeutralAuctionPlanInput {
|
|
hiddenSeed: string | number;
|
|
seedYear: number;
|
|
seedMonth: number;
|
|
nationCount: number;
|
|
consumeTournamentRoll: boolean;
|
|
averageGold: number;
|
|
averageRice: number;
|
|
buyRiceAuctionCount: number;
|
|
sellRiceAuctionCount: number;
|
|
}
|
|
|
|
export interface NeutralResourceAuctionPlan {
|
|
auctionType: NeutralResourceAuctionType;
|
|
amount: number;
|
|
startBidAmount: number;
|
|
finishBidAmount: number;
|
|
closeTurnCnt: number;
|
|
}
|
|
|
|
const clamp = (value: number, min: number, max: number): number => {
|
|
if (max < min) {
|
|
return min;
|
|
}
|
|
return Math.min(max, Math.max(min, value));
|
|
};
|
|
|
|
const roundToTens = (value: number): number => Math.round(value / 10) * 10;
|
|
|
|
const normalizeCount = (value: number): number => (Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0);
|
|
|
|
const normalizeAverage = (value: number): number => clamp(Number.isFinite(value) ? value : 0, 1_000, 20_000);
|
|
|
|
const canOpenResourceAuction = (plan: NeutralResourceAuctionPlan): boolean =>
|
|
plan.closeTurnCnt >= 1 &&
|
|
plan.closeTurnCnt <= 24 &&
|
|
plan.amount >= 100 &&
|
|
plan.amount <= 10_000 &&
|
|
plan.startBidAmount >= plan.amount * 0.5 &&
|
|
plan.startBidAmount <= plan.amount * 2 &&
|
|
plan.finishBidAmount >= plan.amount * 1.1 &&
|
|
plan.finishBidAmount <= plan.amount * 2 &&
|
|
plan.finishBidAmount >= plan.startBidAmount * 1.1;
|
|
|
|
export const buildNeutralResourceAuctionPlan = (input: NeutralAuctionPlanInput): NeutralResourceAuctionPlan[] => {
|
|
// ref TurnExecutionHelper는 날짜를 넘기기 전에 이전 연월로 monthly RNG를 만든다.
|
|
const rng = new RandUtil(
|
|
new LiteHashDRBG(simpleSerialize(input.hiddenSeed, 'monthly', input.seedYear, input.seedMonth))
|
|
);
|
|
|
|
// ref postUpdateMonthly()의 국가 국력 보정이 registerAuction()보다 먼저 RNG를 소비한다.
|
|
for (let nationIdx = 0; nationIdx < normalizeCount(input.nationCount); nationIdx += 1) {
|
|
rng.nextRange(0.95, 1.05);
|
|
}
|
|
// 토너먼트가 없고 자동 개시가 켜진 경우 성공 여부와 무관하게 한 번 소비한다.
|
|
if (input.consumeTournamentRoll) {
|
|
rng.nextBool(0.4);
|
|
}
|
|
|
|
const averageGold = normalizeAverage(input.averageGold);
|
|
const averageRice = normalizeAverage(input.averageRice);
|
|
const result: NeutralResourceAuctionPlan[] = [];
|
|
|
|
const buyRiceAuctionCount = normalizeCount(input.buyRiceAuctionCount);
|
|
if (rng.nextBool(1 / (buyRiceAuctionCount + 5))) {
|
|
const multiplier = rng.nextRangeInt(1, 5);
|
|
const rawAmount = (averageRice / 20) * multiplier;
|
|
const rawStartBid = clamp((averageGold / 20) * 0.9 * multiplier, rawAmount * 0.8, rawAmount * 1.2);
|
|
const plan: NeutralResourceAuctionPlan = {
|
|
auctionType: 'BUY_RICE',
|
|
amount: roundToTens(rawAmount),
|
|
startBidAmount: roundToTens(rawStartBid),
|
|
finishBidAmount: roundToTens(rawAmount * 2),
|
|
closeTurnCnt: rng.nextRangeInt(3, 12),
|
|
};
|
|
if (canOpenResourceAuction(plan)) {
|
|
result.push(plan);
|
|
}
|
|
}
|
|
|
|
const sellRiceAuctionCount = normalizeCount(input.sellRiceAuctionCount);
|
|
if (rng.nextBool(1 / (sellRiceAuctionCount + 5))) {
|
|
const multiplier = rng.nextRangeInt(1, 5);
|
|
const rawAmount = (averageGold / 20) * multiplier;
|
|
const rawStartBid = clamp((averageRice / 20) * 1.1 * multiplier, rawAmount * 0.8, rawAmount * 1.2);
|
|
const plan: NeutralResourceAuctionPlan = {
|
|
auctionType: 'SELL_RICE',
|
|
amount: roundToTens(rawAmount),
|
|
startBidAmount: roundToTens(rawStartBid),
|
|
finishBidAmount: roundToTens(rawAmount * 2),
|
|
closeTurnCnt: rng.nextRangeInt(3, 12),
|
|
};
|
|
if (canOpenResourceAuction(plan)) {
|
|
result.push(plan);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|