CONTRACTS: Display contract answers on completely failed contracts (#2440)

This commit is contained in:
Adam Weeden
2026-01-08 16:21:52 -05:00
committed by GitHub
parent edf3d11b72
commit 7af9dca6bc
23 changed files with 221 additions and 48 deletions

View File

@@ -82,6 +82,10 @@ export class CodingContract {
this.reward = reward;
}
getAnswer() {
return CodingContractTypes[this.type].getAnswer(this.state);
}
getData(): unknown {
const func = CodingContractTypes[this.type].getData;
return func ? func(this.state) : this.state;

View File

@@ -33,11 +33,13 @@ interface CodingContractType<Data, Answer, State = Data> {
difficulty: number;
/** Function that generates a valid 'state' for a contract type */
generate: () => State;
/** Function that returns an answer, if possible, for a given contract */
getAnswer: (data: Data) => Answer | null;
/**
* Transforms the 'state' for a contract into its 'data'. The state is
* stored persistently as JSON, so it must be serializable. The data is what
* is given to the user and shown in the description. If this function is
* ommitted, it will be the identity function (i.e. State == Data).
* omitted, it will be the identity function (i.e. State == Data).
* You can use this to make problems where the "solver" is not a function
* that can be copy-pasted to user code to solve the problem.
*/

View File

@@ -33,15 +33,17 @@ export const algorithmicStockTrader: Pick<
return arr;
},
numTries: 5,
solver: (data, answer) => {
getAnswer: (data) => {
let maxCur = 0;
let maxSoFar = 0;
for (let i = 1; i < data.length; ++i) {
maxCur = Math.max(0, (maxCur += data[i] - data[i - 1]));
maxSoFar = Math.max(maxCur, maxSoFar);
}
return maxSoFar === answer;
return maxSoFar;
},
solver: (data, answer) => {
return algorithmicStockTrader[CodingContractName.AlgorithmicStockTraderI].getAnswer(data) === answer;
},
convertAnswer: (ans) => parseInt(ans, 10),
validateAnswer: (ans): ans is number => typeof ans === "number",
@@ -71,13 +73,16 @@ export const algorithmicStockTrader: Pick<
return arr;
},
solver: (data, answer) => {
getAnswer: (data) => {
let profit = 0;
for (let p = 1; p < data.length; ++p) {
profit += Math.max(data[p] - data[p - 1], 0);
}
return profit === answer;
return profit;
},
solver: (data, answer) => {
return algorithmicStockTrader[CodingContractName.AlgorithmicStockTraderII].getAnswer(data) === answer;
},
convertAnswer: (ans) => parseInt(ans, 10),
validateAnswer: (ans): ans is number => typeof ans === "number",
@@ -107,7 +112,7 @@ export const algorithmicStockTrader: Pick<
return arr;
},
solver: (data, answer) => {
getAnswer: (data) => {
let hold1 = Number.MIN_SAFE_INTEGER;
let hold2 = Number.MIN_SAFE_INTEGER;
let release1 = 0;
@@ -119,7 +124,10 @@ export const algorithmicStockTrader: Pick<
hold1 = Math.max(hold1, price * -1);
}
return release2 === answer;
return release2;
},
solver: (data, answer) => {
return algorithmicStockTrader[CodingContractName.AlgorithmicStockTraderIII].getAnswer(data) === answer;
},
convertAnswer: (ans) => parseInt(ans, 10),
validateAnswer: (ans): ans is number => typeof ans === "number",
@@ -154,13 +162,13 @@ export const algorithmicStockTrader: Pick<
return [k, prices];
},
solver: (data, answer) => {
getAnswer: (data) => {
const k: number = data[0];
const prices: number[] = data[1];
const len = prices.length;
if (len < 2) {
return answer === 0;
return 0;
}
if (k > len / 2) {
let res = 0;
@@ -168,7 +176,7 @@ export const algorithmicStockTrader: Pick<
res += Math.max(prices[i] - prices[i - 1], 0);
}
return res === answer;
return res;
}
const hold: number[] = [];
@@ -189,7 +197,10 @@ export const algorithmicStockTrader: Pick<
}
}
return rele[k] === answer;
return rele[k];
},
solver: (data, answer) => {
return algorithmicStockTrader[CodingContractName.AlgorithmicStockTraderIV].getAnswer(data) === answer;
},
convertAnswer: (ans) => parseInt(ans, 10),
validateAnswer: (ans): ans is number => typeof ans === "number",

View File

@@ -37,14 +37,17 @@ export const arrayJumpingGame: Pick<
return arr;
},
numTries: 1,
solver: (data, answer) => {
getAnswer: (data) => {
const n: number = data.length;
let i = 0;
for (let reach = 0; i < n && i <= reach; ++i) {
reach = Math.max(i + data[i], reach);
}
const solution: boolean = i === n;
return (solution ? 1 : 0) === answer;
return solution ? 1 : 0;
},
solver: (data, answer) => {
return arrayJumpingGame[CodingContractName.ArrayJumpingGame].getAnswer(data) === answer;
},
convertAnswer: (ans) => {
const num = parseInt(ans);
@@ -85,7 +88,7 @@ export const arrayJumpingGame: Pick<
return arr;
},
numTries: 3,
solver: (data, answer) => {
getAnswer: (data) => {
const n: number = data.length;
let reach = 0;
let jumps = 0;
@@ -105,7 +108,10 @@ export const arrayJumpingGame: Pick<
lastJump = jumpedFrom;
jumps++;
}
return jumps === answer;
return jumps;
},
solver: (data, answer) => {
return arrayJumpingGame[CodingContractName.ArrayJumpingGameII].getAnswer(data) === answer;
},
convertAnswer: (ans) => parseInt(ans, 10),
validateAnswer: (ans): ans is number => typeof ans === "number",

View File

@@ -1,4 +1,5 @@
import { CodingContractTypes } from "../ContractTypes";
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
import { CodingContractName } from "@enums";
export const compression: Pick<
@@ -49,8 +50,8 @@ export const compression: Pick<
return plain.substring(0, length);
},
solver: (plain, answer) => {
if (plain.length === 0) return answer === "";
getAnswer: (plain) => {
if (plain.length === 0) return "";
let out = "";
let count = 1;
@@ -63,7 +64,10 @@ export const compression: Pick<
count = 1;
}
out += count + plain[plain.length - 1];
return out === answer;
return out;
},
solver: (plain, answer) => {
return compression[CodingContractName.CompressionIRLECompression].getAnswer(plain) === answer;
},
convertAnswer: (ans) => ans.replace(/\s/g, ""),
validateAnswer: (ans): ans is string => typeof ans === "string",
@@ -97,8 +101,11 @@ export const compression: Pick<
generate: (): string => {
return comprLZEncode(comprLZGenerate());
},
getAnswer: (compr) => {
return comprLZDecode(compr) ?? "";
},
solver: (compr, answer) => {
return (comprLZDecode(compr) ?? "") === answer;
return compression[CodingContractName.CompressionIILZDecompression].getAnswer(compr) === answer;
},
convertAnswer: (ans) => ans.replace(/\s/g, ""),
validateAnswer: (ans): ans is string => typeof ans === "string",
@@ -135,8 +142,20 @@ export const compression: Pick<
generate: (): string => {
return comprLZGenerate();
},
getAnswer: (plain) => {
return comprLZEncode(plain);
},
solver: (plain, answer) => {
return answer.length <= comprLZEncode(plain).length && comprLZDecode(answer) === plain;
const encoded = compression[CodingContractName.CompressionIIILZCompression].getAnswer(plain);
if (encoded === null) {
exceptionAlert(
new Error(
`Unexpected null when calculating the answer for ${CodingContractName.CompressionIIILZCompression} contract. Data: ${plain}`,
),
);
return false;
}
return answer.length <= encoded.length && comprLZDecode(answer) === plain;
},
convertAnswer: (ans) => ans.replace(/\s/g, ""),
validateAnswer: (ans): ans is string => typeof ans === "string",

View File

@@ -57,13 +57,16 @@ export const encryption: Pick<
Math.floor(Math.random() * 25 + 1),
];
},
solver: (data, answer) => {
getAnswer: (data) => {
// data = [plaintext, shift value]
// build char array, shifting via map and join to final results
const cipher = [...data[0]]
.map((a) => (a === " " ? a : String.fromCharCode(((a.charCodeAt(0) - 65 - data[1] + 26) % 26) + 65)))
.join("");
return cipher === answer;
return cipher;
},
solver: (data, answer) => {
return encryption[CodingContractName.EncryptionICaesarCipher].getAnswer(data) === answer;
},
convertAnswer: (ans) => ans,
validateAnswer: (ans): ans is string => typeof ans === "string",
@@ -222,7 +225,7 @@ export const encryption: Pick<
keys.sort(() => Math.random() - 0.5)[0],
];
},
solver: (data, answer) => {
getAnswer: (data) => {
// data = [plaintext, keyword]
// build char array, shifting via map and corresponding keyword letter and join to final results
const cipher = [...data[0]]
@@ -232,7 +235,10 @@ export const encryption: Pick<
: String.fromCharCode(((a.charCodeAt(0) - 2 * 65 + data[1].charCodeAt(i % data[1].length)) % 26) + 65);
})
.join("");
return cipher === answer;
return cipher;
},
solver: (data, answer) => {
return encryption[CodingContractName.EncryptionIIVigenereCipher].getAnswer(data) === answer;
},
convertAnswer: (ans) => ans,
validateAnswer: (ans): ans is string => typeof ans === "string",

View File

@@ -1,4 +1,5 @@
import { filterTruthy } from "../../utils/helpers/ArrayHelpers";
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
import { CodingContractTypes, removeBracketsFromArrayString, removeQuotesFromString } from "../ContractTypes";
import { CodingContractName } from "@enums";
@@ -47,7 +48,7 @@ export const findAllValidMathExpressions: Pick<CodingContractTypes, CodingContra
return [digits, target];
},
solver: (data, answer) => {
getAnswer: (data) => {
const num = data[0];
const target = data[1];
@@ -86,6 +87,20 @@ export const findAllValidMathExpressions: Pick<CodingContractTypes, CodingContra
const result: string[] = [];
helper(result, "", num, target, 0, 0, 0);
return result;
},
solver: (data, answer) => {
const result = findAllValidMathExpressions[CodingContractName.FindAllValidMathExpressions].getAnswer(data);
if (result === null) {
exceptionAlert(
new Error(
`Unexpected null when calculating the answer for ${CodingContractName.FindAllValidMathExpressions} contract. Data: ${data}`,
),
);
return false;
}
if (result.length !== answer.length) return false;
const solutions = new Set(answer);

View File

@@ -13,7 +13,7 @@ export const findLargestPrimeFactor: Pick<CodingContractTypes, CodingContractNam
generate: (): number => {
return getRandomIntInclusive(500, 1e9);
},
solver: (data, answer) => {
getAnswer: (data) => {
let fac = 2;
let n: number = data;
while (n > (fac - 1) * (fac - 1)) {
@@ -23,7 +23,10 @@ export const findLargestPrimeFactor: Pick<CodingContractTypes, CodingContractNam
++fac;
}
return (n === 1 ? fac - 1 : n) === answer;
return n === 1 ? fac - 1 : n;
},
solver: (data, answer) => {
return findLargestPrimeFactor[CodingContractName.FindLargestPrimeFactor].getAnswer(data) === answer;
},
convertAnswer: (ans) => parseInt(ans, 10),
validateAnswer: (ans): ans is number => typeof ans === "number",

View File

@@ -1,5 +1,6 @@
import { CodingContractName } from "@enums";
import { CodingContractTypes, removeBracketsFromArrayString } from "../ContractTypes";
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
export const generateIPAddresses: Pick<CodingContractTypes, CodingContractName.GenerateIPAddresses> = {
@@ -28,7 +29,7 @@ export const generateIPAddresses: Pick<CodingContractTypes, CodingContractName.G
return str;
},
solver: (data, answer) => {
getAnswer: (data) => {
const ret: string[] = [];
for (let a = 1; a <= 3; ++a) {
for (let b = 1; b <= 3; ++b) {
@@ -51,6 +52,18 @@ export const generateIPAddresses: Pick<CodingContractTypes, CodingContractName.G
}
}
return ret;
},
solver: (data, answer) => {
const ret = generateIPAddresses[CodingContractName.GenerateIPAddresses].getAnswer(data);
if (ret === null) {
exceptionAlert(
new Error(
`Unexpected null when calculating the answer for ${CodingContractName.GenerateIPAddresses} contract. Data: ${data}`,
),
);
return false;
}
return ret.length === answer.length && ret.every((ip) => answer.includes(ip));
},
convertAnswer: (ans) => {

View File

@@ -36,8 +36,11 @@ export const hammingCode: Pick<
const y = Math.pow(2, getRandomIntInclusive(1, 57));
return getRandomIntInclusive(Math.min(x, y), Math.max(x, y));
},
getAnswer: (data) => {
return HammingEncode(data);
},
solver: (data, answer) => {
return HammingEncode(data) === answer;
return hammingCode[CodingContractName.HammingCodesIntegerToEncodedBinary].getAnswer(data) === answer;
},
convertAnswer: (ans) => ans,
validateAnswer: (ans): ans is string => typeof ans === "string",
@@ -83,8 +86,11 @@ export const hammingCode: Pick<
}
return _buildArray.join("");
},
getAnswer: (data) => {
return HammingDecode(data);
},
solver: (data, answer) => {
return HammingDecode(data) === answer;
return hammingCode[CodingContractName.HammingCodesEncodedBinaryToInteger].getAnswer(data) === answer;
},
convertAnswer: (ans) => parseInt(ans, 10),
validateAnswer: (ans): ans is number => typeof ans === "number",

View File

@@ -1,3 +1,4 @@
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
import { CodingContractTypes, convert2DArrayToString, removeBracketsFromArrayString } from "../ContractTypes";
import { CodingContractName } from "@enums";
@@ -30,7 +31,7 @@ export const mergeOverlappingIntervals: Pick<CodingContractTypes, CodingContract
return intervals;
},
numTries: 15,
solver: (data, answer) => {
getAnswer: (data) => {
const intervals: [number, number][] = data.slice();
intervals.sort((a: [number, number], b: [number, number]) => {
return a[0] - b[0];
@@ -50,6 +51,22 @@ export const mergeOverlappingIntervals: Pick<CodingContractTypes, CodingContract
}
result.push([start, end]);
return result;
},
solver: (data, answer) => {
const result = mergeOverlappingIntervals[CodingContractName.MergeOverlappingIntervals].getAnswer(data);
if (result === null) {
exceptionAlert(
new Error(
`Unexpected null when calculating the answer for ${
CodingContractName.MergeOverlappingIntervals
} contract. Data: ${JSON.stringify(data)}`,
),
);
return false;
}
return result.length === answer.length && result.every((a, i) => a[0] === answer[i][0] && a[1] === answer[i][1]);
},
convertAnswer: (ans) => {

View File

@@ -56,7 +56,7 @@ export const minimumPathSumInATriangle: Pick<CodingContractTypes, CodingContract
return triangle;
},
solver: (data, answer) => {
getAnswer: (data) => {
const n: number = data.length;
const dp: number[] = data[n - 1].slice();
for (let i = n - 2; i > -1; --i) {
@@ -65,7 +65,10 @@ export const minimumPathSumInATriangle: Pick<CodingContractTypes, CodingContract
}
}
return dp[0] === answer;
return dp[0];
},
solver: (data, answer) => {
return minimumPathSumInATriangle[CodingContractName.MinimumPathSumInATriangle].getAnswer(data) === answer;
},
convertAnswer: (ans) => parseInt(ans, 10),
validateAnswer: (ans): ans is number => typeof ans === "number",

View File

@@ -75,6 +75,9 @@ export const proper2ColoringOfAGraph: Pick<CodingContractTypes, CodingContractNa
return [n + m, edges];
},
getAnswer: () => {
return null;
},
solver: (data, answer) => {
//Helper function to get neighbourhood of a vertex
function neighbourhood(vertex: number): number[] {

View File

@@ -1,3 +1,4 @@
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
import { CodingContractTypes, removeBracketsFromArrayString, removeQuotesFromString } from "../ContractTypes";
import { CodingContractName } from "@enums";
@@ -45,7 +46,7 @@ export const sanitizeParenthesesInExpression: Pick<
return chars.join("");
},
solver: (data, answer) => {
getAnswer: (data) => {
let left = 0;
let right = 0;
const res: string[] = [];
@@ -94,6 +95,20 @@ export const sanitizeParenthesesInExpression: Pick<
dfs(0, 0, left, right, data, "", res);
return res;
},
solver: (data, answer) => {
const res = sanitizeParenthesesInExpression[CodingContractName.SanitizeParenthesesInExpression].getAnswer(data);
if (res === null) {
exceptionAlert(
new Error(
`Unexpected null when calculating the answer for ${CodingContractName.SanitizeParenthesesInExpression} contract. Data: ${data}`,
),
);
return false;
}
if (res.length !== answer.length) return false;
return res.every((sol) => answer.includes(sol));
},

View File

@@ -53,6 +53,9 @@ export const shortestPathInAGrid: Pick<CodingContractTypes, CodingContractName.S
return grid;
},
getAnswer: () => {
return null;
},
solver: (data, answer) => {
const width = data[0].length;
const height = data.length;

View File

@@ -1,5 +1,6 @@
import { CodingContractName } from "@enums";
import { removeBracketsFromArrayString, type CodingContractTypes } from "../ContractTypes";
import { exceptionAlert } from "../../utils/helpers/exceptionAlert";
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
export const spiralizeMatrix: Pick<CodingContractTypes, CodingContractName.SpiralizeMatrix> = {
@@ -55,7 +56,7 @@ export const spiralizeMatrix: Pick<CodingContractTypes, CodingContractName.Spira
return matrix;
},
solver: (data, answer) => {
getAnswer: (data) => {
const spiral: number[] = [];
const m: number = data.length;
const n: number = data[0].length;
@@ -107,6 +108,22 @@ export const spiralizeMatrix: Pick<CodingContractTypes, CodingContractName.Spira
}
}
return spiral;
},
solver: (data, answer) => {
const spiral = spiralizeMatrix[CodingContractName.SpiralizeMatrix].getAnswer(data);
if (spiral === null) {
exceptionAlert(
new Error(
`Unexpected null when calculating the answer for ${
CodingContractName.SpiralizeMatrix
} contract. Data: ${JSON.stringify(data)}`,
),
);
return false;
}
return spiral.length === answer.length && spiral.every((n, i) => n === answer[i]);
},
convertAnswer: (ans) => {

View File

@@ -34,6 +34,9 @@ ${data}`;
const ans = BigInt(state[0]);
return ans * ans + BigInt(state[1]);
},
getAnswer: () => {
return null;
},
solver: (state, answer) => {
return state[0] === answer.toString();
},

View File

@@ -23,13 +23,16 @@ export const subarrayWithMaximumSum: Pick<CodingContractTypes, CodingContractNam
return arr;
},
solver: (data, answer) => {
getAnswer: (data) => {
const nums: number[] = data.slice();
for (let i = 1; i < nums.length; i++) {
nums[i] = Math.max(nums[i], nums[i] + nums[i - 1]);
}
return Math.max(...nums) === answer;
return Math.max(...nums);
},
solver: (data, answer) => {
return subarrayWithMaximumSum[CodingContractName.SubarrayWithMaximumSum].getAnswer(data) === answer;
},
convertAnswer: (ans) => parseInt(ans, 10),
validateAnswer: (ans): ans is number => typeof ans === "number",

View File

@@ -21,7 +21,7 @@ export const totalPrimesInRange: Pick<CodingContractTypes, CodingContractName.To
const high = low + getRandomIntInclusive(1e5, 1e6);
return [low, high];
},
solver: (data, answer) => {
getAnswer: (data) => {
/** Simple implementation of Sieve of Eratosthenes
* https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes*/
function simpleSieve(max: number): number[] {
@@ -76,7 +76,11 @@ export const totalPrimesInRange: Pick<CodingContractTypes, CodingContractName.To
//We trust the player generated the appropriate list of primes (or is more accurate at guessing primes than Gauss was at this range) and as such they deserve the reward.
const primes = primeSieve(data[0], data[1]);
return answer === primes;
return primes;
},
solver: (data, answer) => {
return totalPrimesInRange[CodingContractName.TotalPrimesInRange].getAnswer(data) === answer;
},
convertAnswer: (ans) => parseInt(ans, 10),
validateAnswer: (ans): ans is number => typeof ans === "number",

View File

@@ -22,7 +22,7 @@ export const totalWaysToSum: Pick<
generate: (): number => {
return getRandomIntInclusive(8, 100);
},
solver: (data, answer) => {
getAnswer: (data) => {
if (typeof data !== "number") throw new Error("solver expected number");
const ways: number[] = [1];
ways.length = data + 1;
@@ -33,7 +33,10 @@ export const totalWaysToSum: Pick<
}
}
return ways[data] === answer;
return ways[data];
},
solver: (data, answer) => {
return totalWaysToSum[CodingContractName.TotalWaysToSum].getAnswer(data) === answer;
},
convertAnswer: (ans) => parseInt(ans, 10),
validateAnswer: (ans): ans is number => typeof ans === "number",
@@ -66,7 +69,7 @@ export const totalWaysToSum: Pick<
}
return [n, s];
},
solver: (data, answer) => {
getAnswer: (data) => {
// https://www.geeksforgeeks.org/coin-change-dp-7/?ref=lbp
const n = data[0];
const s = data[1];
@@ -78,7 +81,10 @@ export const totalWaysToSum: Pick<
ways[j] += ways[j - s[i]];
}
}
return ways[n] === answer;
return ways[n];
},
solver: (data, answer) => {
return totalWaysToSum[CodingContractName.TotalWaysToSumII].getAnswer(data) === answer;
},
convertAnswer: (ans) => parseInt(ans, 10),
validateAnswer: (ans): ans is number => typeof ans === "number",

View File

@@ -29,7 +29,7 @@ export const uniquePathsInAGrid: Pick<
return [numRows, numColumns];
},
solver: (data, answer) => {
getAnswer: (data) => {
const n: number = data[0]; // Number of rows
const m: number = data[1]; // Number of columns
const currentRow: number[] = [];
@@ -44,7 +44,10 @@ export const uniquePathsInAGrid: Pick<
}
}
return currentRow[n - 1] === answer;
return currentRow[n - 1];
},
solver: (data, answer) => {
return uniquePathsInAGrid[CodingContractName.UniquePathsInAGridI].getAnswer(data) === answer;
},
convertAnswer: (ans) => parseInt(ans, 10),
validateAnswer: (ans): ans is number => typeof ans === "number",
@@ -97,7 +100,7 @@ export const uniquePathsInAGrid: Pick<
return grid;
},
solver: (data, answer) => {
getAnswer: (data) => {
const obstacleGrid: number[][] = [];
obstacleGrid.length = data.length;
for (let i = 0; i < obstacleGrid.length; ++i) {
@@ -116,7 +119,10 @@ export const uniquePathsInAGrid: Pick<
}
}
return obstacleGrid[obstacleGrid.length - 1][obstacleGrid[0].length - 1] === answer;
return obstacleGrid[obstacleGrid.length - 1][obstacleGrid[0].length - 1];
},
solver: (data, answer) => {
return uniquePathsInAGrid[CodingContractName.UniquePathsInAGridII].getAnswer(data) === answer;
},
convertAnswer: (ans) => parseInt(ans, 10),
validateAnswer: (ans): ans is number => typeof ans === "number",

View File

@@ -55,6 +55,10 @@ export function NetscriptCodingContract(): InternalAPI<ICodingContract> {
case CodingContractResult.Failure: {
if (++contract.tries >= contract.getMaxNumTries()) {
helpers.log(ctx, () => `Coding Contract attempt '${contract.fn}' failed. Contract is now self-destructing`);
const solution = contract.getAnswer();
if (solution !== null) {
helpers.log(ctx, () => `Coding Contract solution was: ${solution}`);
}
server.removeContract(contract.fn);
} else {
helpers.log(

View File

@@ -550,6 +550,10 @@ export class Terminal {
++contract.tries;
if (contract.tries >= contract.getMaxNumTries()) {
this.error("Contract FAILED - Contract is now self-destructing");
const solution = contract.getAnswer();
if (solution !== null) {
this.error(`Coding Contract solution was: ${solution}`);
}
server.removeContract(contract);
} else {
this.error(`Contract FAILED - ${contract.getMaxNumTries() - contract.tries} tries remaining`);