MISC: Refactor coding contracts for type safety (#1653)

* MISC: Refactor coding contracts for type safety

This refactor does three things without any behavior changes:
* Adds type safety by introducing generic type parameters to the coding
  contract definitions, so they can use concrete types instead of
  "unknown". This also eliminates a bunch of boilerplate casts.
* Removes the unneeded CodingContractType class. We can use the metadata
  type directly.
* Introduces a new hidden state to coding contracts. Instead of
  generating and storing the data (which is what is shown to the user as
  the problem's input), the state is stored instead. This allows
  problems to (for instance) generate the answer up-front, and check the
  solution directly against the answer, instead of needing to embed a
  solver in the problem (which can then easily be ripped from the source
  code).

For compatibility, state == data by default. I plan to make use of this
feature in a followup, but it is unused currently.
This commit is contained in:
David Walker 2024-09-15 23:53:10 -07:00 committed by GitHub
parent cde5e3f6ae
commit 21ffa5322f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 158 additions and 256 deletions

@ -1,59 +1,19 @@
import type { FactionName } from "@enums"; import type { FactionName } from "@enums";
import { codingContractTypesMetadata, DescriptionFunc, GeneratorFunc, SolverFunc } from "./data/codingcontracttypes"; import { codingContractTypesMetadata, type CodingContractType } from "./data/codingcontracttypes";
import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "./utils/JSONReviver"; import { Generic_fromJSON, Generic_toJSON, IReviverValue, constructorsForReviver } from "./utils/JSONReviver";
import { CodingContractEvent } from "./ui/React/CodingContractModal"; import { CodingContractEvent } from "./ui/React/CodingContractModal";
import { ContractFilePath, resolveContractFilePath } from "./Paths/ContractFilePath"; import { ContractFilePath, resolveContractFilePath } from "./Paths/ContractFilePath";
/* Represents different types of problems that a Coding Contract can have */
class CodingContractType {
/** Function that generates a description of the problem */
desc: DescriptionFunc;
/** Number that generally represents the problem's difficulty. Bigger numbers = harder */
difficulty: number;
/** A function that randomly generates a valid 'data' for the problem */
generate: GeneratorFunc;
/** Name of the type of problem */
name: string;
/** The maximum number of tries the player gets on this kind of problem before it self-destructs */
numTries: number;
/** Stores a function that checks if the provided answer is correct */
solver: SolverFunc;
constructor(
name: string,
desc: DescriptionFunc,
gen: GeneratorFunc,
solver: SolverFunc,
diff: number,
numTries: number,
) {
this.name = name;
this.desc = desc;
this.generate = gen;
this.solver = solver;
this.difficulty = diff;
this.numTries = numTries;
}
}
/* Contract Types */ /* Contract Types */
export const CodingContractTypes: Record<string, CodingContractType> = {}; export const CodingContractTypes: Record<string, CodingContractType<unknown>> = {};
for (const md of codingContractTypesMetadata) { for (const md of codingContractTypesMetadata) {
CodingContractTypes[md.name] = new CodingContractType( // Because functions are contravariant with their parameters, we can't
md.name, // consider arbitrary CodingContractTypes as CodingContractType<unknown>
md.desc, // directly. However, we do want that as the final type, to enforce that the
md.gen, // state and data are unknown. So we cast through CodingContractType<any>.
md.solver, CodingContractTypes[md.name] = md as CodingContractType<any>;
md.difficulty,
md.numTries,
);
} }
// Numeric enum // Numeric enum
@ -95,8 +55,8 @@ export type ICodingContractReward =
* The player receives a reward if the problem is solved correctly * The player receives a reward if the problem is solved correctly
*/ */
export class CodingContract { export class CodingContract {
/* Relevant data for the contract's problem */ /* Relevant state for the contract's problem */
data: unknown; state: unknown;
/* Contract's filename */ /* Contract's filename */
fn: ContractFilePath; fn: ContractFilePath;
@ -120,16 +80,17 @@ export class CodingContract {
this.fn = path; this.fn = path;
this.type = type; this.type = type;
this.data = CodingContractTypes[type].generate(); this.state = CodingContractTypes[type].generate();
this.reward = reward; this.reward = reward;
} }
getData(): unknown { getData(): unknown {
return this.data; const func = CodingContractTypes[this.type].getData;
return func ? func(this.state) : this.state;
} }
getDescription(): string { getDescription(): string {
return CodingContractTypes[this.type].desc(this.data).replaceAll("&nbsp;", " "); return CodingContractTypes[this.type].desc(this.getData()).replaceAll("&nbsp;", " ");
} }
getDifficulty(): number { getDifficulty(): number {
@ -137,15 +98,15 @@ export class CodingContract {
} }
getMaxNumTries(): number { getMaxNumTries(): number {
return CodingContractTypes[this.type].numTries; return CodingContractTypes[this.type].numTries ?? 10;
} }
getType(): string { getType(): string {
return CodingContractTypes[this.type].name; return this.type;
} }
isSolution(solution: string): boolean { isSolution(solution: string): boolean {
return CodingContractTypes[this.type].solver(this.data, solution); return CodingContractTypes[this.type].solver(this.state, solution);
} }
/** Creates a popup to prompt the player to solve the problem */ /** Creates a popup to prompt the player to solve the problem */
@ -174,6 +135,11 @@ export class CodingContract {
/** Initializes a CodingContract from a JSON save state. */ /** Initializes a CodingContract from a JSON save state. */
static fromJSON(value: IReviverValue): CodingContract { static fromJSON(value: IReviverValue): CodingContract {
// In previous versions, there was a data field instead of a state field.
if ("data" in value.data) {
value.data.state = value.data.data;
delete value.data.data;
}
return Generic_fromJSON(CodingContract, value.data); return Generic_fromJSON(CodingContract, value.data);
} }
} }

@ -4,23 +4,31 @@ import { comprGenChar, comprLZGenerate, comprLZEncode, comprLZDecode } from "../
import { HammingEncode, HammingDecode, HammingEncodeProperly } from "../utils/HammingCodeTools"; import { HammingEncode, HammingDecode, HammingEncodeProperly } from "../utils/HammingCodeTools";
import { filterTruthy } from "../utils/helpers/ArrayHelpers"; import { filterTruthy } from "../utils/helpers/ArrayHelpers";
/* Function that generates a valid 'data' for a contract type */ export interface CodingContractType<Data, State = Data> {
export type GeneratorFunc = () => unknown; /**
* Function that returns a string with the problem's description.
/* Function that checks if the provided solution is the correct one */ * Requires the 'data' of a Contract as input
export type SolverFunc = (data: unknown, answer: string) => boolean; */
desc: (data: Data) => string;
/* Function that returns a string with the problem's description. /** Difficulty of the contract. Higher is harder. */
Requires the 'data' of a Contract as input */
export type DescriptionFunc = (data: unknown) => string;
interface ICodingContractTypeMetadata {
desc: DescriptionFunc;
difficulty: number; difficulty: number;
gen: GeneratorFunc; /** Function that generates a valid 'state' for a contract type */
generate: () => State;
/**
* 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).
* 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.
*/
getData?: (state: State) => Data;
/** Name of the problem. Used to request contracts of this type. */
name: string; name: string;
numTries: number; /** How many tries you get. Defaults to 10. */
solver: SolverFunc; numTries?: number;
/** Function that checks if the provided solution is correct. */
solver: (state: State, answer: string) => boolean;
} }
/* Helper functions for Coding Contract implementations */ /* Helper functions for Coding Contract implementations */
@ -48,32 +56,30 @@ function removeQuotesFromString(str: string): string {
return strCpy; return strCpy;
} }
function convert2DArrayToString(arr: unknown[][]): string { function convert2DArrayToString(arr: number[][]): string {
const components: string[] = []; const components: string[] = [];
arr.forEach((e: unknown) => { for (const e of arr) {
let s = String(e); let s = String(e);
s = ["[", s, "]"].join(""); s = ["[", s, "]"].join("");
components.push(s); components.push(s);
}); }
return components.join(",").replace(/\s/g, ""); return components.join(",").replace(/\s/g, "");
} }
export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [ export const codingContractTypesMetadata = [
{ {
desc: (n: unknown): string => { desc: (n: number): string => {
return ["A prime factor is a factor that is a prime number.", `What is the largest prime factor of ${n}?`].join( return ["A prime factor is a factor that is a prime number.", `What is the largest prime factor of ${n}?`].join(
" ", " ",
); );
}, },
difficulty: 1, difficulty: 1,
gen: (): number => { generate: (): number => {
return getRandomIntInclusive(500, 1e9); return getRandomIntInclusive(500, 1e9);
}, },
name: "Find Largest Prime Factor", name: "Find Largest Prime Factor",
numTries: 10, solver: (data: number, ans: string): boolean => {
solver: (data: unknown, ans: string): boolean => {
if (typeof data !== "number") throw new Error("solver expected number");
let fac = 2; let fac = 2;
let n: number = data; let n: number = data;
while (n > (fac - 1) * (fac - 1)) { while (n > (fac - 1) * (fac - 1)) {
@ -85,10 +91,9 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return (n === 1 ? fac - 1 : n) === parseInt(ans, 10); return (n === 1 ? fac - 1 : n) === parseInt(ans, 10);
}, },
}, } satisfies CodingContractType<number>,
{ {
desc: (_n: unknown): string => { desc: (n: number[]): string => {
const n = _n as number[];
return [ return [
"Given the following integer array, find the contiguous subarray", "Given the following integer array, find the contiguous subarray",
"(containing at least one number) which has the largest sum and return that sum.", "(containing at least one number) which has the largest sum and return that sum.",
@ -97,7 +102,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
].join(" "); ].join(" ");
}, },
difficulty: 1, difficulty: 1,
gen: (): number[] => { generate: (): number[] => {
const len: number = getRandomIntInclusive(5, 40); const len: number = getRandomIntInclusive(5, 40);
const arr: number[] = []; const arr: number[] = [];
arr.length = len; arr.length = len;
@ -108,9 +113,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return arr; return arr;
}, },
name: "Subarray with Maximum Sum", name: "Subarray with Maximum Sum",
numTries: 10, solver: (data: number[], ans: string): boolean => {
solver: (_data: unknown, ans: string): boolean => {
const data = _data as number[];
const nums: number[] = data.slice(); const nums: number[] = data.slice();
for (let i = 1; i < nums.length; i++) { for (let i = 1; i < nums.length; i++) {
nums[i] = Math.max(nums[i], nums[i] + nums[i - 1]); nums[i] = Math.max(nums[i], nums[i] + nums[i - 1]);
@ -118,10 +121,9 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return parseInt(ans, 10) === Math.max(...nums); return parseInt(ans, 10) === Math.max(...nums);
}, },
}, } satisfies CodingContractType<number[]>,
{ {
desc: (n: unknown): string => { desc: (n: number): string => {
if (typeof n !== "number") throw new Error("solver expected number");
return [ return [
"It is possible write four as a sum in exactly four different ways:\n\n", "It is possible write four as a sum in exactly four different ways:\n\n",
"&nbsp;&nbsp;&nbsp;&nbsp;3 + 1\n", "&nbsp;&nbsp;&nbsp;&nbsp;3 + 1\n",
@ -133,12 +135,11 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
].join(" "); ].join(" ");
}, },
difficulty: 1.5, difficulty: 1.5,
gen: (): number => { generate: (): number => {
return getRandomIntInclusive(8, 100); return getRandomIntInclusive(8, 100);
}, },
name: "Total Ways to Sum", name: "Total Ways to Sum",
numTries: 10, solver: (data: number, ans: string): boolean => {
solver: (data: unknown, ans: string): boolean => {
if (typeof data !== "number") throw new Error("solver expected number"); if (typeof data !== "number") throw new Error("solver expected number");
const ways: number[] = [1]; const ways: number[] = [1];
ways.length = data + 1; ways.length = data + 1;
@ -151,10 +152,9 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return ways[data] === parseInt(ans, 10); return ways[data] === parseInt(ans, 10);
}, },
}, } satisfies CodingContractType<number>,
{ {
desc: (_data: unknown): string => { desc: (data: [number, number[]]): string => {
const data = _data as [number, number[]];
const n: number = data[0]; const n: number = data[0];
const s: number[] = data[1]; const s: number[] = data[1];
return [ return [
@ -165,7 +165,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
].join(" "); ].join(" ");
}, },
difficulty: 2, difficulty: 2,
gen: (): [number, number[]] => { generate: (): [number, number[]] => {
const n: number = getRandomIntInclusive(12, 200); const n: number = getRandomIntInclusive(12, 200);
const maxLen: number = getRandomIntInclusive(8, 12); const maxLen: number = getRandomIntInclusive(8, 12);
const s: number[] = []; const s: number[] = [];
@ -182,9 +182,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return [n, s]; return [n, s];
}, },
name: "Total Ways to Sum II", name: "Total Ways to Sum II",
numTries: 10, solver: (data: [number, number[]], ans: string): boolean => {
solver: (_data: unknown, ans: string): boolean => {
const data = _data as [number, number[]];
// https://www.geeksforgeeks.org/coin-change-dp-7/?ref=lbp // https://www.geeksforgeeks.org/coin-change-dp-7/?ref=lbp
const n = data[0]; const n = data[0];
const s = data[1]; const s = data[1];
@ -198,10 +196,9 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
} }
return ways[n] === parseInt(ans, 10); return ways[n] === parseInt(ans, 10);
}, },
}, } satisfies CodingContractType<[number, number[]]>,
{ {
desc: (_n: unknown): string => { desc: (n: number[][]): string => {
const n = _n as number[][];
let d: string = [ let d: string = [
"Given the following array of arrays of numbers representing a 2D matrix,", "Given the following array of arrays of numbers representing a 2D matrix,",
"return the elements of the matrix as an array in spiral order:\n\n", "return the elements of the matrix as an array in spiral order:\n\n",
@ -239,7 +236,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return d; return d;
}, },
difficulty: 2, difficulty: 2,
gen: (): number[][] => { generate: (): number[][] => {
const m: number = getRandomIntInclusive(1, 15); const m: number = getRandomIntInclusive(1, 15);
const n: number = getRandomIntInclusive(1, 15); const n: number = getRandomIntInclusive(1, 15);
const matrix: number[][] = []; const matrix: number[][] = [];
@ -258,9 +255,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return matrix; return matrix;
}, },
name: "Spiralize Matrix", name: "Spiralize Matrix",
numTries: 10, solver: (data: number[][], ans: string): boolean => {
solver: (_data: unknown, ans: string): boolean => {
const data = _data as number[][];
const spiral: number[] = []; const spiral: number[] = [];
const m: number = data.length; const m: number = data.length;
const n: number = data[0].length; const n: number = data[0].length;
@ -325,10 +320,9 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return true; return true;
}, },
}, } satisfies CodingContractType<number[][]>,
{ {
desc: (_arr: unknown): string => { desc: (arr: number[]): string => {
const arr = _arr as number[];
return [ return [
"You are given the following array of integers:\n\n", "You are given the following array of integers:\n\n",
`${arr}\n\n`, `${arr}\n\n`,
@ -343,7 +337,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
].join(" "); ].join(" ");
}, },
difficulty: 2.5, difficulty: 2.5,
gen: (): number[] => { generate: (): number[] => {
const len: number = getRandomIntInclusive(3, 25); const len: number = getRandomIntInclusive(3, 25);
const arr: number[] = []; const arr: number[] = [];
arr.length = len; arr.length = len;
@ -359,8 +353,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
}, },
name: "Array Jumping Game", name: "Array Jumping Game",
numTries: 1, numTries: 1,
solver: (_data: unknown, ans: string): boolean => { solver: (data: number[], ans: string): boolean => {
const data = _data as number[];
const n: number = data.length; const n: number = data.length;
let i = 0; let i = 0;
for (let reach = 0; i < n && i <= reach; ++i) { for (let reach = 0; i < n && i <= reach; ++i) {
@ -369,10 +362,9 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
const solution: boolean = i === n; const solution: boolean = i === n;
return (ans === "1" && solution) || (ans === "0" && !solution); return (ans === "1" && solution) || (ans === "0" && !solution);
}, },
}, } satisfies CodingContractType<number[]>,
{ {
desc: (_arr: unknown): string => { desc: (arr: number[]): string => {
const arr = _arr as number[];
return [ return [
"You are given the following array of integers:\n\n", "You are given the following array of integers:\n\n",
`${arr}\n\n`, `${arr}\n\n`,
@ -387,7 +379,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
].join(" "); ].join(" ");
}, },
difficulty: 3, difficulty: 3,
gen: (): number[] => { generate: (): number[] => {
const len: number = getRandomIntInclusive(3, 25); const len: number = getRandomIntInclusive(3, 25);
const arr: number[] = []; const arr: number[] = [];
arr.length = len; arr.length = len;
@ -404,8 +396,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
}, },
name: "Array Jumping Game II", name: "Array Jumping Game II",
numTries: 3, numTries: 3,
solver: (_data: unknown, ans: string): boolean => { solver: (data: number[], ans: string): boolean => {
const data = _data as number[];
const n: number = data.length; const n: number = data.length;
let reach = 0; let reach = 0;
let jumps = 0; let jumps = 0;
@ -427,10 +418,9 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
} }
return jumps === parseInt(ans, 10); return jumps === parseInt(ans, 10);
}, },
}, } satisfies CodingContractType<number[]>,
{ {
desc: (_arr: unknown): string => { desc: (arr: number[][]): string => {
const arr = _arr as number[][];
return [ return [
"Given the following array of arrays of numbers representing a list of", "Given the following array of arrays of numbers representing a list of",
"intervals, merge all overlapping intervals.\n\n", "intervals, merge all overlapping intervals.\n\n",
@ -444,7 +434,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
].join(" "); ].join(" ");
}, },
difficulty: 3, difficulty: 3,
gen: (): number[][] => { generate: (): number[][] => {
const intervals: number[][] = []; const intervals: number[][] = [];
const numIntervals: number = getRandomIntInclusive(3, 20); const numIntervals: number = getRandomIntInclusive(3, 20);
for (let i = 0; i < numIntervals; ++i) { for (let i = 0; i < numIntervals; ++i) {
@ -457,8 +447,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
}, },
name: "Merge Overlapping Intervals", name: "Merge Overlapping Intervals",
numTries: 15, numTries: 15,
solver: (_data: unknown, ans: string): boolean => { solver: (data: number[][], ans: string): boolean => {
const data = _data as number[][];
const intervals: number[][] = data.slice(); const intervals: number[][] = data.slice();
intervals.sort((a: number[], b: number[]) => { intervals.sort((a: number[], b: number[]) => {
return a[0] - b[0]; return a[0] - b[0];
@ -483,9 +472,9 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return sanitizedResult === sanitizedAns || sanitizedResult === removeBracketsFromArrayString(sanitizedAns); return sanitizedResult === sanitizedAns || sanitizedResult === removeBracketsFromArrayString(sanitizedAns);
}, },
}, } satisfies CodingContractType<number[][]>,
{ {
desc: (data: unknown): string => { desc: (data: string): string => {
return [ return [
"Given the following string containing only digits, return", "Given the following string containing only digits, return",
"an array with all possible valid IP address combinations", "an array with all possible valid IP address combinations",
@ -499,7 +488,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
].join(" "); ].join(" ");
}, },
difficulty: 3, difficulty: 3,
gen: (): string => { generate: (): string => {
let str = ""; let str = "";
for (let i = 0; i < 4; ++i) { for (let i = 0; i < 4; ++i) {
const num: number = getRandomIntInclusive(0, 255); const num: number = getRandomIntInclusive(0, 255);
@ -510,9 +499,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return str; return str;
}, },
name: "Generate IP Addresses", name: "Generate IP Addresses",
numTries: 10, solver: (data: string, ans: string): boolean => {
solver: (data: unknown, ans: string): boolean => {
if (typeof data !== "string") throw new Error("solver expected string");
const ret: string[] = []; const ret: string[] = [];
for (let a = 1; a <= 3; ++a) { for (let a = 1; a <= 3; ++a) {
for (let b = 1; b <= 3; ++b) { for (let b = 1; b <= 3; ++b) {
@ -550,10 +537,9 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return true; return true;
}, },
}, } satisfies CodingContractType<string>,
{ {
desc: (_data: unknown): string => { desc: (data: number[]): string => {
const data = _data as number[];
return [ return [
"You are given the following array of stock prices (which are numbers)", "You are given the following array of stock prices (which are numbers)",
"where the i-th element represents the stock price on day i:\n\n", "where the i-th element represents the stock price on day i:\n\n",
@ -565,7 +551,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
].join(" "); ].join(" ");
}, },
difficulty: 1, difficulty: 1,
gen: (): number[] => { generate: (): number[] => {
const len: number = getRandomIntInclusive(3, 50); const len: number = getRandomIntInclusive(3, 50);
const arr: number[] = []; const arr: number[] = [];
arr.length = len; arr.length = len;
@ -577,8 +563,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
}, },
name: "Algorithmic Stock Trader I", name: "Algorithmic Stock Trader I",
numTries: 5, numTries: 5,
solver: (_data: unknown, ans: string): boolean => { solver: (data: number[], ans: string): boolean => {
const data = _data as number[];
let maxCur = 0; let maxCur = 0;
let maxSoFar = 0; let maxSoFar = 0;
for (let i = 1; i < data.length; ++i) { for (let i = 1; i < data.length; ++i) {
@ -588,10 +573,9 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return maxSoFar.toString() === ans; return maxSoFar.toString() === ans;
}, },
}, } satisfies CodingContractType<number[]>,
{ {
desc: (_data: unknown): string => { desc: (data: number[]): string => {
const data = _data as number[];
return [ return [
"You are given the following array of stock prices (which are numbers)", "You are given the following array of stock prices (which are numbers)",
"where the i-th element represents the stock price on day i:\n\n", "where the i-th element represents the stock price on day i:\n\n",
@ -605,7 +589,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
].join(" "); ].join(" ");
}, },
difficulty: 2, difficulty: 2,
gen: (): number[] => { generate: (): number[] => {
const len: number = getRandomIntInclusive(3, 50); const len: number = getRandomIntInclusive(3, 50);
const arr: number[] = []; const arr: number[] = [];
arr.length = len; arr.length = len;
@ -616,9 +600,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return arr; return arr;
}, },
name: "Algorithmic Stock Trader II", name: "Algorithmic Stock Trader II",
numTries: 10, solver: (data: number[], ans: string): boolean => {
solver: (_data: unknown, ans: string): boolean => {
const data = _data as number[];
let profit = 0; let profit = 0;
for (let p = 1; p < data.length; ++p) { for (let p = 1; p < data.length; ++p) {
profit += Math.max(data[p] - data[p - 1], 0); profit += Math.max(data[p] - data[p - 1], 0);
@ -626,10 +608,9 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return profit.toString() === ans; return profit.toString() === ans;
}, },
}, } satisfies CodingContractType<number[]>,
{ {
desc: (_data: unknown): string => { desc: (data: number[]): string => {
const data = _data as number[];
return [ return [
"You are given the following array of stock prices (which are numbers)", "You are given the following array of stock prices (which are numbers)",
"where the i-th element represents the stock price on day i:\n\n", "where the i-th element represents the stock price on day i:\n\n",
@ -643,7 +624,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
].join(" "); ].join(" ");
}, },
difficulty: 5, difficulty: 5,
gen: (): number[] => { generate: (): number[] => {
const len: number = getRandomIntInclusive(3, 50); const len: number = getRandomIntInclusive(3, 50);
const arr: number[] = []; const arr: number[] = [];
arr.length = len; arr.length = len;
@ -654,9 +635,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return arr; return arr;
}, },
name: "Algorithmic Stock Trader III", name: "Algorithmic Stock Trader III",
numTries: 10, solver: (data: number[], ans: string): boolean => {
solver: (_data: unknown, ans: string): boolean => {
const data = _data as number[];
let hold1 = Number.MIN_SAFE_INTEGER; let hold1 = Number.MIN_SAFE_INTEGER;
let hold2 = Number.MIN_SAFE_INTEGER; let hold2 = Number.MIN_SAFE_INTEGER;
let release1 = 0; let release1 = 0;
@ -670,10 +649,9 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return release2.toString() === ans; return release2.toString() === ans;
}, },
}, } satisfies CodingContractType<number[]>,
{ {
desc: (_data: unknown): string => { desc: (data: [number, number[]]): string => {
const data = _data as [number, number[]];
const k = data[0]; const k = data[0];
const prices = data[1]; const prices = data[1];
return [ return [
@ -691,7 +669,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
].join(" "); ].join(" ");
}, },
difficulty: 8, difficulty: 8,
gen: (): [number, number[]] => { generate: (): [number, number[]] => {
const k = getRandomIntInclusive(2, 10); const k = getRandomIntInclusive(2, 10);
const len = getRandomIntInclusive(3, 50); const len = getRandomIntInclusive(3, 50);
const prices: number[] = []; const prices: number[] = [];
@ -703,9 +681,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return [k, prices]; return [k, prices];
}, },
name: "Algorithmic Stock Trader IV", name: "Algorithmic Stock Trader IV",
numTries: 10, solver: (data: [number, number[]], ans: string): boolean => {
solver: (_data: unknown, ans: string): boolean => {
const data = _data as [number, number[]];
const k: number = data[0]; const k: number = data[0];
const prices: number[] = data[1]; const prices: number[] = data[1];
@ -742,10 +718,9 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return parseInt(ans) === rele[k]; return parseInt(ans) === rele[k];
}, },
}, } satisfies CodingContractType<[number, number[]]>,
{ {
desc: (_data: unknown): string => { desc: (data: number[][]): string => {
const data = _data as number[][];
function createTriangleRecurse(data: number[][], level = 0): string { function createTriangleRecurse(data: number[][], level = 0): string {
const numLevels: number = data.length; const numLevels: number = data.length;
if (level >= numLevels) { if (level >= numLevels) {
@ -782,7 +757,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
].join(" "); ].join(" ");
}, },
difficulty: 5, difficulty: 5,
gen: (): number[][] => { generate: (): number[][] => {
const triangle: number[][] = []; const triangle: number[][] = [];
const levels: number = getRandomIntInclusive(3, 12); const levels: number = getRandomIntInclusive(3, 12);
triangle.length = levels; triangle.length = levels;
@ -798,9 +773,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return triangle; return triangle;
}, },
name: "Minimum Path Sum in a Triangle", name: "Minimum Path Sum in a Triangle",
numTries: 10, solver: (data: number[][], ans: string): boolean => {
solver: (_data: unknown, ans: string): boolean => {
const data = _data as number[][];
const n: number = data.length; const n: number = data.length;
const dp: number[] = data[n - 1].slice(); const dp: number[] = data[n - 1].slice();
for (let i = n - 2; i > -1; --i) { for (let i = n - 2; i > -1; --i) {
@ -811,10 +784,9 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return dp[0] === parseInt(ans); return dp[0] === parseInt(ans);
}, },
}, } satisfies CodingContractType<number[][]>,
{ {
desc: (_data: unknown): string => { desc: (data: number[]): string => {
const data = _data as number[];
const numRows = data[0]; const numRows = data[0];
const numColumns = data[1]; const numColumns = data[1];
return [ return [
@ -830,16 +802,14 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
].join(" "); ].join(" ");
}, },
difficulty: 3, difficulty: 3,
gen: (): number[] => { generate: (): number[] => {
const numRows: number = getRandomIntInclusive(2, 14); const numRows: number = getRandomIntInclusive(2, 14);
const numColumns: number = getRandomIntInclusive(2, 14); const numColumns: number = getRandomIntInclusive(2, 14);
return [numRows, numColumns]; return [numRows, numColumns];
}, },
name: "Unique Paths in a Grid I", name: "Unique Paths in a Grid I",
numTries: 10, solver: (data: number[], ans: string): boolean => {
solver: (_data: unknown, ans: string): boolean => {
const data = _data as number[];
const n: number = data[0]; // Number of rows const n: number = data[0]; // Number of rows
const m: number = data[1]; // Number of columns const m: number = data[1]; // Number of columns
const currentRow: number[] = []; const currentRow: number[] = [];
@ -856,10 +826,9 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return parseInt(ans) === currentRow[n - 1]; return parseInt(ans) === currentRow[n - 1];
}, },
}, } satisfies CodingContractType<number[]>,
{ {
desc: (_data: unknown): string => { desc: (data: number[][]): string => {
const data = _data as number[][];
let gridString = ""; let gridString = "";
for (const line of data) { for (const line of data) {
gridString += `${line.toString()},\n`; gridString += `${line.toString()},\n`;
@ -876,7 +845,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
].join(" "); ].join(" ");
}, },
difficulty: 5, difficulty: 5,
gen: (): number[][] => { generate: (): number[][] => {
const numRows: number = getRandomIntInclusive(2, 12); const numRows: number = getRandomIntInclusive(2, 12);
const numColumns: number = getRandomIntInclusive(2, 12); const numColumns: number = getRandomIntInclusive(2, 12);
@ -907,9 +876,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return grid; return grid;
}, },
name: "Unique Paths in a Grid II", name: "Unique Paths in a Grid II",
numTries: 10, solver: (data: number[][], ans: string): boolean => {
solver: (_data: unknown, ans: string): boolean => {
const data = _data as number[][];
const obstacleGrid: number[][] = []; const obstacleGrid: number[][] = [];
obstacleGrid.length = data.length; obstacleGrid.length = data.length;
for (let i = 0; i < obstacleGrid.length; ++i) { for (let i = 0; i < obstacleGrid.length; ++i) {
@ -930,11 +897,10 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return obstacleGrid[obstacleGrid.length - 1][obstacleGrid[0].length - 1] === parseInt(ans); return obstacleGrid[obstacleGrid.length - 1][obstacleGrid[0].length - 1] === parseInt(ans);
}, },
}, } satisfies CodingContractType<number[][]>,
{ {
name: "Shortest Path in a Grid", name: "Shortest Path in a Grid",
desc: (_data: unknown): string => { desc: (data: number[][]): string => {
const data = _data as number[][];
return [ return [
"You are located in the top-left corner of the following grid:\n\n", "You are located in the top-left corner of the following grid:\n\n",
`&nbsp;&nbsp;[${data.map((line) => "[" + line + "]").join(",\n&nbsp;&nbsp;&nbsp;")}]\n\n`, `&nbsp;&nbsp;[${data.map((line) => "[" + line + "]").join(",\n&nbsp;&nbsp;&nbsp;")}]\n\n`,
@ -958,8 +924,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
].join(" "); ].join(" ");
}, },
difficulty: 7, difficulty: 7,
numTries: 10, generate: (): number[][] => {
gen: (): number[][] => {
const height = getRandomIntInclusive(6, 12); const height = getRandomIntInclusive(6, 12);
const width = getRandomIntInclusive(6, 12); const width = getRandomIntInclusive(6, 12);
const dstY = height - 1; const dstY = height - 1;
@ -984,8 +949,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return grid; return grid;
}, },
solver: (_data: unknown, ans: string): boolean => { solver: (data: number[][], ans: string): boolean => {
const data = _data as number[][];
const width = data[0].length; const width = data[0].length;
const height = data.length; const height = data.length;
const dstY = height - 1; const dstY = height - 1;
@ -1060,9 +1024,9 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
// Path was valid, finally verify that the answer path brought us to the end coordinates // Path was valid, finally verify that the answer path brought us to the end coordinates
return ansY == dstY && ansX == dstX; return ansY == dstY && ansX == dstX;
}, },
}, } satisfies CodingContractType<number[][]>,
{ {
desc: (data: unknown): string => { desc: (data: string): string => {
return [ return [
"Given the following string:\n\n", "Given the following string:\n\n",
`${data}\n\n`, `${data}\n\n`,
@ -1079,7 +1043,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
].join(" "); ].join(" ");
}, },
difficulty: 10, difficulty: 10,
gen: (): string => { generate: (): string => {
const len: number = getRandomIntInclusive(6, 20); const len: number = getRandomIntInclusive(6, 20);
const chars: string[] = []; const chars: string[] = [];
chars.length = len; chars.length = len;
@ -1101,9 +1065,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return chars.join(""); return chars.join("");
}, },
name: "Sanitize Parentheses in Expression", name: "Sanitize Parentheses in Expression",
numTries: 10, solver: (data: string, ans: string): boolean => {
solver: (data: unknown, ans: string): boolean => {
if (typeof data !== "string") throw new Error("solver expected string");
let left = 0; let left = 0;
let right = 0; let right = 0;
const res: string[] = []; const res: string[] = [];
@ -1169,10 +1131,9 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return true; return true;
}, },
}, } satisfies CodingContractType<string>,
{ {
desc: (_data: unknown): string => { desc: (data: [string, number]): string => {
const data = _data as [string, number];
const digits: string = data[0]; const digits: string = data[0];
const target: number = data[1]; const target: number = data[1];
@ -1197,7 +1158,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
].join(" "); ].join(" ");
}, },
difficulty: 10, difficulty: 10,
gen: (): [string, number] => { generate: (): [string, number] => {
const numDigits = getRandomIntInclusive(4, 12); const numDigits = getRandomIntInclusive(4, 12);
const digitsArray: string[] = []; const digitsArray: string[] = [];
digitsArray.length = numDigits; digitsArray.length = numDigits;
@ -1215,9 +1176,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return [digits, target]; return [digits, target];
}, },
name: "Find All Valid Math Expressions", name: "Find All Valid Math Expressions",
numTries: 10, solver: (data: [string, number], ans: string): boolean => {
solver: (_data: unknown, ans: string): boolean => {
const data = _data as [string, number];
const num = data[0]; const num = data[0];
const target = data[1]; const target = data[1];
@ -1284,12 +1243,11 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return true; return true;
}, },
}, } satisfies CodingContractType<[string, number]>,
{ {
name: "HammingCodes: Integer to Encoded Binary", name: "HammingCodes: Integer to Encoded Binary",
numTries: 10,
difficulty: 5, difficulty: 5,
desc: (n: unknown): string => { desc: (n: number): string => {
return [ return [
"You are given the following decimal value: \n", "You are given the following decimal value: \n",
`${n} \n\n`, `${n} \n\n`,
@ -1309,21 +1267,19 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
"or the 3Blue1Brown videos on Hamming Codes. (https://youtube.com/watch?v=X8jsijhllIA)", "or the 3Blue1Brown videos on Hamming Codes. (https://youtube.com/watch?v=X8jsijhllIA)",
].join(" "); ].join(" ");
}, },
gen: (): number => { generate: (): number => {
const x = Math.pow(2, 4); const x = Math.pow(2, 4);
const y = Math.pow(2, getRandomIntInclusive(1, 57)); const y = Math.pow(2, getRandomIntInclusive(1, 57));
return getRandomIntInclusive(Math.min(x, y), Math.max(x, y)); return getRandomIntInclusive(Math.min(x, y), Math.max(x, y));
}, },
solver: (data: unknown, ans: string): boolean => { solver: (data: number, ans: string): boolean => {
if (typeof data !== "number") throw new Error("solver expected number");
return ans === HammingEncode(data); return ans === HammingEncode(data);
}, },
}, } satisfies CodingContractType<number>,
{ {
name: "HammingCodes: Encoded Binary to Integer", name: "HammingCodes: Encoded Binary to Integer",
difficulty: 8, difficulty: 8,
numTries: 10, desc: (n: string): string => {
desc: (n: unknown): string => {
return [ return [
"You are given the following encoded binary string: \n", "You are given the following encoded binary string: \n",
`'${n}' \n\n`, `'${n}' \n\n`,
@ -1346,7 +1302,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
"or the 3Blue1Brown videos on Hamming Codes. (https://youtube.com/watch?v=X8jsijhllIA)", "or the 3Blue1Brown videos on Hamming Codes. (https://youtube.com/watch?v=X8jsijhllIA)",
].join(" "); ].join(" ");
}, },
gen: (): string => { generate: (): string => {
const _alteredBit = Math.round(Math.random()); const _alteredBit = Math.round(Math.random());
const x = Math.pow(2, 4); const x = Math.pow(2, 4);
const y = Math.pow(2, getRandomIntInclusive(1, 57)); const y = Math.pow(2, getRandomIntInclusive(1, 57));
@ -1359,17 +1315,15 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
} }
return _buildArray.join(""); return _buildArray.join("");
}, },
solver: (data: unknown, ans: string): boolean => { solver: (data: string, ans: string): boolean => {
if (typeof data !== "string") throw new Error("solver expected string");
return parseInt(ans, 10) === HammingDecode(data); return parseInt(ans, 10) === HammingDecode(data);
}, },
}, } satisfies CodingContractType<string>,
{ {
name: "Proper 2-Coloring of a Graph", name: "Proper 2-Coloring of a Graph",
difficulty: 7, difficulty: 7,
numTries: 5, numTries: 5,
desc: (_data: unknown): string => { desc: (data: [number, [number, number][]]): string => {
const data = _data as [number, [number, number][]];
return [ return [
`You are given the following data, representing a graph:\n`, `You are given the following data, representing a graph:\n`,
`${JSON.stringify(data)}\n`, `${JSON.stringify(data)}\n`,
@ -1392,7 +1346,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
`Output: []`, `Output: []`,
].join(" "); ].join(" ");
}, },
gen: (): [number, [number, number][]] => { generate: (): [number, [number, number][]] => {
//Generate two partite sets //Generate two partite sets
const n = Math.floor(Math.random() * 5) + 3; const n = Math.floor(Math.random() * 5) + 3;
const m = Math.floor(Math.random() * 5) + 3; const m = Math.floor(Math.random() * 5) + 3;
@ -1439,7 +1393,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return [n + m, edges]; return [n + m, edges];
}, },
solver: (_data: unknown, ans: string): boolean => { solver: (data: [number, [number, number][]], ans: string): boolean => {
//Helper function to get neighbourhood of a vertex //Helper function to get neighbourhood of a vertex
function neighbourhood(vertex: number): number[] { function neighbourhood(vertex: number): number[] {
const adjLeft = data[1].filter(([a]) => a == vertex).map(([, b]) => b); const adjLeft = data[1].filter(([a]) => a == vertex).map(([, b]) => b);
@ -1447,8 +1401,6 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return adjLeft.concat(adjRight); return adjLeft.concat(adjRight);
} }
const data = _data as [number, [number, number][]];
//Sanitize player input //Sanitize player input
const sanitizedPlayerAns = removeBracketsFromArrayString(ans); const sanitizedPlayerAns = removeBracketsFromArrayString(ans);
@ -1517,12 +1469,11 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
//Return false if the coloring is the wrong size //Return false if the coloring is the wrong size
else return false; else return false;
}, },
}, } satisfies CodingContractType<[number, [number, number][]]>,
{ {
name: "Compression I: RLE Compression", name: "Compression I: RLE Compression",
difficulty: 2, difficulty: 2,
numTries: 10, desc: (plaintext: string): string => {
desc: (plaintext: unknown): string => {
return [ return [
"Run-length encoding (RLE) is a data compression technique which encodes data as a series of runs of", "Run-length encoding (RLE) is a data compression technique which encodes data as a series of runs of",
"a repeated single character. Runs are encoded as a length, followed by the character itself. Lengths", "a repeated single character. Runs are encoded as a length, followed by the character itself. Lengths",
@ -1538,7 +1489,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
"&nbsp; &nbsp; zzzzzzzzzzzzzzzzzzz &nbsp;-> &nbsp;9z9z1z &nbsp;(or 9z8z2z, etc.)", "&nbsp; &nbsp; zzzzzzzzzzzzzzzzzzz &nbsp;-> &nbsp;9z9z1z &nbsp;(or 9z8z2z, etc.)",
].join(" "); ].join(" ");
}, },
gen: (): string => { generate: (): string => {
const length = 50 + Math.floor(25 * (Math.random() + Math.random())); const length = 50 + Math.floor(25 * (Math.random() + Math.random()));
let plain = ""; let plain = "";
@ -1562,8 +1513,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return plain.substring(0, length); return plain.substring(0, length);
}, },
solver: (plain: unknown, ans: string): boolean => { solver: (plain: string, ans: string): boolean => {
if (typeof plain !== "string") throw new Error("solver expected string");
if (ans.length % 2 !== 0) { if (ans.length % 2 !== 0) {
return false; return false;
} }
@ -1597,12 +1547,11 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
return ans.length <= length; return ans.length <= length;
}, },
}, } satisfies CodingContractType<string>,
{ {
name: "Compression II: LZ Decompression", name: "Compression II: LZ Decompression",
difficulty: 4, difficulty: 4,
numTries: 10, desc: (compressed: string): string => {
desc: (compressed: unknown): string => {
return [ return [
"Lempel-Ziv (LZ) compression is a data compression technique which encodes data using references to", "Lempel-Ziv (LZ) compression is a data compression technique which encodes data using references to",
"earlier parts of the data. In this variant of LZ, data is encoded in two types of chunk. Each chunk", "earlier parts of the data. In this variant of LZ, data is encoded in two types of chunk. Each chunk",
@ -1626,19 +1575,17 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
"&nbsp; &nbsp; 5aaabb450723abb &nbsp;-> &nbsp;aaabbaaababababaabb", "&nbsp; &nbsp; 5aaabb450723abb &nbsp;-> &nbsp;aaabbaaababababaabb",
].join(" "); ].join(" ");
}, },
gen: (): string => { generate: (): string => {
return comprLZEncode(comprLZGenerate()); return comprLZEncode(comprLZGenerate());
}, },
solver: (compr: unknown, ans: string): boolean => { solver: (compr: string, ans: string): boolean => {
if (typeof compr !== "string") throw new Error("solver expected string");
return ans === comprLZDecode(compr); return ans === comprLZDecode(compr);
}, },
}, } satisfies CodingContractType<string>,
{ {
name: "Compression III: LZ Compression", name: "Compression III: LZ Compression",
difficulty: 10, difficulty: 10,
numTries: 10, desc: (plaintext: string): string => {
desc: (plaintext: unknown): string => {
return [ return [
"Lempel-Ziv (LZ) compression is a data compression technique which encodes data using references to", "Lempel-Ziv (LZ) compression is a data compression technique which encodes data using references to",
"earlier parts of the data. In this variant of LZ, data is encoded in two types of chunk. Each chunk", "earlier parts of the data. In this variant of LZ, data is encoded in two types of chunk. Each chunk",
@ -1665,18 +1612,15 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
"&nbsp; &nbsp; aaaaaaaaaaaaaa &nbsp;-> &nbsp;1a91041", "&nbsp; &nbsp; aaaaaaaaaaaaaa &nbsp;-> &nbsp;1a91041",
].join(" "); ].join(" ");
}, },
gen: (): string => { generate: (): string => {
return comprLZGenerate(); return comprLZGenerate();
}, },
solver: (plain: unknown, ans: string): boolean => { solver: (plain: string, ans: string): boolean => {
if (typeof plain !== "string") throw new Error("solver expected string");
return comprLZDecode(ans) === plain && ans.length <= comprLZEncode(plain).length; return comprLZDecode(ans) === plain && ans.length <= comprLZEncode(plain).length;
}, },
}, } satisfies CodingContractType<string>,
{ {
desc: (_data: unknown): string => { desc: (data: [string, number]): string => {
if (!Array.isArray(_data)) throw new Error("data should be array of string");
const data = _data as [string, number];
return [ return [
"Caesar cipher is one of the simplest encryption technique.", "Caesar cipher is one of the simplest encryption technique.",
"It is a type of substitution cipher in which each letter in the plaintext ", "It is a type of substitution cipher in which each letter in the plaintext ",
@ -1690,7 +1634,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
].join(" "); ].join(" ");
}, },
difficulty: 1, difficulty: 1,
gen: (): [string, number] => { generate: (): [string, number] => {
// return [plaintext, shift value] // return [plaintext, shift value]
const words = [ const words = [
"ARRAY", "ARRAY",
@ -1728,10 +1672,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
]; ];
}, },
name: "Encryption I: Caesar Cipher", name: "Encryption I: Caesar Cipher",
numTries: 10, solver: (data: [string, number], ans: string): boolean => {
solver: (_data: unknown, ans: string): boolean => {
if (!Array.isArray(_data)) throw new Error("data should be array of string");
const data = _data as [string, number];
// data = [plaintext, shift value] // data = [plaintext, shift value]
// build char array, shifting via map and join to final results // build char array, shifting via map and join to final results
const cipher = [...data[0]] const cipher = [...data[0]]
@ -1739,11 +1680,9 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
.join(""); .join("");
return cipher === ans; return cipher === ans;
}, },
}, } satisfies CodingContractType<[string, number]>,
{ {
desc: (_data: unknown): string => { desc: (data: [string, string]): string => {
if (!Array.isArray(_data)) throw new Error("data should be array of string");
const data = _data as [string, string];
return [ return [
"Vigenère cipher is a type of polyalphabetic substitution. It uses ", "Vigenère cipher is a type of polyalphabetic substitution. It uses ",
"the Vigenère square to encrypt and decrypt plaintext with a keyword.\n\n", "the Vigenère square to encrypt and decrypt plaintext with a keyword.\n\n",
@ -1771,7 +1710,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
].join(" "); ].join(" ");
}, },
difficulty: 2, difficulty: 2,
gen: (): [string, string] => { generate: (): [string, string] => {
// return [plaintext, keyword] // return [plaintext, keyword]
const words = [ const words = [
"ARRAY", "ARRAY",
@ -1897,10 +1836,7 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
]; ];
}, },
name: "Encryption II: Vigenère Cipher", name: "Encryption II: Vigenère Cipher",
numTries: 10, solver: (data: [string, string], ans: string): boolean => {
solver: (_data: unknown, ans: string): boolean => {
if (!Array.isArray(_data)) throw new Error("data should be array of string");
const data = _data as [string, string];
// data = [plaintext, keyword] // data = [plaintext, keyword]
// build char array, shifting via map and corresponding keyword letter and join to final results // build char array, shifting via map and corresponding keyword letter and join to final results
const cipher = [...data[0]] const cipher = [...data[0]]
@ -1912,5 +1848,5 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [
.join(""); .join("");
return cipher === ans; return cipher === ans;
}, },
}, } satisfies CodingContractType<[string, string]>,
]; ] as const;

@ -56,7 +56,7 @@ export function CodingContractModal(): React.ReactElement {
const contractType = CodingContractTypes[contract.c.type]; const contractType = CodingContractTypes[contract.c.type];
const description = []; const description = [];
for (const [i, value] of contractType.desc(contract.c.data).split("\n").entries()) for (const [i, value] of contractType.desc(contract.c.getData()).split("\n").entries())
description.push(<span key={i} dangerouslySetInnerHTML={{ __html: value + "<br />" }}></span>); description.push(<span key={i} dangerouslySetInnerHTML={{ __html: value + "<br />" }}></span>);
return ( return (
<Modal open={contract !== null} onClose={close}> <Modal open={contract !== null} onClose={close}>