CODINGCONTRACT: Add support for other answer formats (#1892)

This commit is contained in:
G4mingJon4s
2025-01-26 18:35:04 +01:00
committed by GitHub
parent b161142796
commit ffae0045a4
11 changed files with 544 additions and 401 deletions
+63 -32
View File
@@ -1,10 +1,12 @@
import { Player } from "@player";
import { CodingContract } from "../CodingContracts";
import { CodingContract as ICodingContract } from "@nsdefs";
import { CodingContractObject, CodingContract as ICodingContract } from "@nsdefs";
import { InternalAPI, NetscriptContext } from "../Netscript/APIWrapper";
import { helpers } from "../Netscript/NetscriptHelpers";
import { codingContractTypesMetadata } from "../data/codingcontracttypes";
import { CodingContractName } from "@enums";
import { generateDummyContract } from "../CodingContractGenerator";
import { isCodingContractName } from "../data/codingcontracttypes";
import { type BaseServer } from "../Server/BaseServer";
export function NetscriptCodingContract(): InternalAPI<ICodingContract> {
const getCodingContract = function (ctx: NetscriptContext, hostname: string, filename: string): CodingContract {
@@ -17,43 +19,49 @@ export function NetscriptCodingContract(): InternalAPI<ICodingContract> {
return contract;
};
function attemptContract(
ctx: NetscriptContext,
server: BaseServer,
contract: CodingContract,
answer: unknown,
): string {
if (contract.isSolution(answer)) {
const reward = Player.gainCodingContractReward(contract.reward, contract.getDifficulty());
helpers.log(ctx, () => `Successfully completed Coding Contract '${contract.fn}'. Reward: ${reward}`);
server.removeContract(contract.fn);
return reward;
}
if (++contract.tries >= contract.getMaxNumTries()) {
helpers.log(ctx, () => `Coding Contract attempt '${contract.fn}' failed. Contract is now self-destructing`);
server.removeContract(contract.fn);
} else {
helpers.log(
ctx,
() =>
`Coding Contract attempt '${contract.fn}' failed. ${
contract.getMaxNumTries() - contract.tries
} attempt(s) remaining.`,
);
}
return "";
}
return {
attempt: (ctx) => (answer, _filename, _hostname?) => {
const filename = helpers.string(ctx, "filename", _filename);
const hostname = _hostname ? helpers.string(ctx, "hostname", _hostname) : ctx.workerScript.hostname;
const contract = getCodingContract(ctx, hostname, filename);
if (typeof answer !== "number" && typeof answer !== "string" && !Array.isArray(answer))
throw new Error("The answer provided was not a number, string, or array");
// Convert answer to string.
// Todo: better typing for contracts, don't do this weird string conversion of the player answer
const answerStr = typeof answer === "string" ? answer : JSON.stringify(answer);
const creward = contract.reward;
if (!contract.isValid(answer))
throw helpers.errorMessage(
ctx,
`Answer is not in the right format for contract '${contract.type}'. Got: ${answer}`,
);
const serv = helpers.getServer(ctx, hostname);
if (contract.isSolution(answerStr)) {
const reward = Player.gainCodingContractReward(creward, contract.getDifficulty());
helpers.log(ctx, () => `Successfully completed Coding Contract '${filename}'. Reward: ${reward}`);
serv.removeContract(filename);
return reward;
} else {
++contract.tries;
if (contract.tries >= contract.getMaxNumTries()) {
helpers.log(ctx, () => `Coding Contract attempt '${filename}' failed. Contract is now self-destructing`);
serv.removeContract(filename);
} else {
helpers.log(
ctx,
() =>
`Coding Contract attempt '${filename}' failed. ${
contract.getMaxNumTries() - contract.tries
} attempts remaining.`,
);
}
return "";
}
return attemptContract(ctx, serv, contract, answer);
},
getContractType: (ctx) => (_filename, _hostname?) => {
const filename = helpers.string(ctx, "filename", _filename);
@@ -65,8 +73,29 @@ export function NetscriptCodingContract(): InternalAPI<ICodingContract> {
const filename = helpers.string(ctx, "filename", _filename);
const hostname = _hostname ? helpers.string(ctx, "hostname", _hostname) : ctx.workerScript.hostname;
const contract = getCodingContract(ctx, hostname, filename);
return structuredClone(contract.getData());
},
getContract: (ctx) => (_filename, _hostname?) => {
const filename = helpers.string(ctx, "filename", _filename);
const hostname = _hostname ? helpers.string(ctx, "hostname", _hostname) : ctx.workerScript.hostname;
const server = helpers.getServer(ctx, hostname);
const contract = getCodingContract(ctx, hostname, filename);
// asserting type here is required, since it is not feasible to properly type getData
return {
type: contract.type,
data: contract.getData(),
submit: (answer: unknown) => {
helpers.checkEnvFlags(ctx);
return attemptContract(ctx, server, contract, answer);
},
description: contract.getDescription(),
numTriesRemaining: () => {
helpers.checkEnvFlags(ctx);
return contract.getMaxNumTries() - contract.tries;
},
} as CodingContractObject;
},
getDescription: (ctx) => (_filename, _hostname?) => {
const filename = helpers.string(ctx, "filename", _filename);
const hostname = _hostname ? helpers.string(ctx, "hostname", _hostname) : ctx.workerScript.hostname;
@@ -81,8 +110,10 @@ export function NetscriptCodingContract(): InternalAPI<ICodingContract> {
},
createDummyContract: (ctx) => (_type) => {
const type = helpers.string(ctx, "type", _type);
if (!isCodingContractName(type))
return helpers.errorMessage(ctx, `The given type is not a valid contract type. Got '${type}'`);
return generateDummyContract(type);
},
getContractTypes: () => () => codingContractTypesMetadata.map((c) => c.name),
getContractTypes: () => () => Object.values(CodingContractName),
};
}