BUGFIX: Coding contracts may have duplicate names (#2399)

This commit is contained in:
imcute_aaaa
2025-11-27 16:59:11 +09:00
committed by GitHub
parent 329fdc50fb
commit 77fe36db89
5 changed files with 125 additions and 20 deletions
+34 -17
View File
@@ -86,6 +86,9 @@ export function generateRandomContract(): void {
const problemType = getRandomProblemType(maxDif);
const contractFn = getRandomFilename(randServer, reward);
if (contractFn == null) {
return;
}
const contract = new CodingContract(contractFn, problemType, reward);
randServer.addContract(contract);
@@ -102,16 +105,22 @@ export function generateRandomContractOnHome(): void {
const serv = Player.getHomeComputer();
const contractFn = getRandomFilename(serv, reward);
if (contractFn == null) {
return;
}
const contract = new CodingContract(contractFn, problemType, reward);
serv.addContract(contract);
}
export const generateDummyContract = (problemType: CodingContractName): string => {
export const generateDummyContract = (problemType: CodingContractName): string | null => {
if (!CodingContractTypes[problemType]) throw new Error(`Invalid problem type: '${problemType}'`);
const serv = Player.getHomeComputer();
const contractFn = getRandomFilename(serv);
if (contractFn == null) {
return null;
}
const contract = new CodingContract(contractFn, problemType, null);
serv.addContract(contract);
@@ -152,7 +161,9 @@ export function generateContract(params: IGenerateContractParams): void {
}
const filename = params.fn ? params.fn : getRandomFilename(server, reward);
if (filename == null) {
return;
}
const contract = new CodingContract(filename, problemType, reward);
server.addContract(contract);
}
@@ -252,28 +263,34 @@ function getRandomServer(): BaseServer | null {
return randServer;
}
function getRandomFilename(
function getRandomAlphanumericString(length: number) {
const alphanumericChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
for (let i = 0; i < length; ++i) {
result += alphanumericChars.charAt(Math.random() * alphanumericChars.length);
}
return result;
}
/**
* This function will return null if the randomized name collides with another contract's name on the specified server.
* Callers of this function must return early and not generate a contract when it happens. It likely happens when there
* are ~240k contracts on the specified server.
*/
export function getRandomFilename(
server: BaseServer,
reward: ICodingContractReward = { type: CodingContractRewardType.Money },
): ContractFilePath {
let contractFn = `contract-${getRandomIntInclusive(0, 1e6)}`;
for (let i = 0; i < 1000; ++i) {
if (
server.contracts.filter((c: CodingContract) => {
return c.fn === contractFn;
}).length <= 0
) {
break;
}
contractFn = `contract-${getRandomIntInclusive(0, 1e6)}`;
}
): ContractFilePath | null {
let contractFn = `contract-${getRandomAlphanumericString(6)}`;
if ("name" in reward) {
// Only alphanumeric characters in the reward name.
contractFn += `-${reward.name.replace(/[^a-zA-Z0-9]/g, "")}`;
}
contractFn += ".cct";
// Return null if there is a contract with the same name.
if (server.contracts.filter((c: CodingContract) => c.fn === contractFn).length) {
return null;
}
const validatedPath = resolveContractFilePath(contractFn);
if (!validatedPath) throw new Error(`Generated contract path could not be validated: ${contractFn}`);
return validatedPath;