diff --git a/.gitignore b/.gitignore index 41331fa..5cad55b 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,3 @@ results npm-debug.log node_modules/ -/.vscode/* diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4815fbc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib", + "editor.formatOnSave": false, + "editor.codeActionsOnSave": [] +} diff --git a/src/challenges/c2-sort-the-numbers/index.ts b/src/challenges/c2-sort-the-numbers/index.ts index 8a86989..e42fa2b 100644 --- a/src/challenges/c2-sort-the-numbers/index.ts +++ b/src/challenges/c2-sort-the-numbers/index.ts @@ -1,11 +1,5 @@ import type { Challenge } from '../types.js'; -class ArrayNoSort extends Array { - public override sort() { - return ['allowed', 'no', 'sort'] as unknown as this; - } -} - const challenge: Challenge<[arr: readonly number[]], readonly number[]> = { title: 'Sort The Numbers', description: @@ -20,9 +14,6 @@ const challenge: Challenge<[arr: readonly number[]], readonly number[]> = { output: [1, 1, 2, 10, 12, 16, 22, 23, 33, 34, 65, 150, 250, 300], }, ], - context: { - Array: ArrayNoSort, - }, }; export default challenge; diff --git a/src/challenges/c5-add/index.ts b/src/challenges/c5-add/index.ts index 59614e6..2840581 100644 --- a/src/challenges/c5-add/index.ts +++ b/src/challenges/c5-add/index.ts @@ -23,7 +23,7 @@ const challenge: Challenge<[input: [number, number]], number> = { }, ], context: { - eval: () => 42, + eval: () => "eval is not the answer you're looking for", }, assertRules: (playString) => { if (playString.includes('+')) { diff --git a/src/challenges/types.ts b/src/challenges/types.ts index 64f0a5d..abdfd42 100644 --- a/src/challenges/types.ts +++ b/src/challenges/types.ts @@ -31,4 +31,5 @@ export interface Challenge< assertRules?: (playString: string) => void; context?: Record; timeLimitMinutes?: number; + runBefore?: () => void; } diff --git a/src/game/game-verifier.ts b/src/game/game-verifier.ts index 605b805..579bdeb 100644 --- a/src/game/game-verifier.ts +++ b/src/game/game-verifier.ts @@ -4,8 +4,6 @@ import * as url from 'url'; import type { VerifyJob } from './verifier.js'; import * as game from './game.js'; -import { challenges } from '../challenges/index.js'; -import { Primitive } from '../challenges/types.js'; const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); @@ -17,15 +15,10 @@ export const verify = ( ) => { const currentGame = game.getOrError(); const verifier = child_process.fork(path.resolve(__dirname, 'verifier.ts')); - const challenge = challenges.find((ch) => ch.key === currentGame.key); - if (!challenge) { - throw new Error('Challenge not found'); - } - - const job: VerifyJob = { + const job: VerifyJob = { file, - challenge, + key: currentGame.key, }; verifier.send(job); diff --git a/src/game/utils.ts b/src/game/utils.ts index 0b252f4..0a59134 100644 --- a/src/game/utils.ts +++ b/src/game/utils.ts @@ -34,33 +34,21 @@ export const formatValue = (value: unknown): string => { return `Unknown value: ${value}`; }; -export const formatTypeAndValue = (value: unknown, actual: Primitive) => { +export const formatTypeAndValue = ( + value: Primitive | readonly unknown[] | Record, + isResult: Primitive +) => { if (value === null) { return 'null'; } - if (typeof value === 'undefined') { - return 'undefined'; - } - if (typeof value === 'function') { - return 'function'; + return `${isResult ? 'different ' : ''}function`; } if (Array.isArray(value)) { - return (actual ? 'different ' : '') + 'array of ' + value.length + ' items'; - } - - if (typeof value === 'string') { - return ( - (actual ? 'different ' : '') + 'string of ' + value.length + ' chars' - ); - } - - if (typeof value === 'object') { - return (actual ? 'different ' : '') + 'object'; + return `array ${formatValue(value)}`; } - const digits = value.toString().replace(/[^0-9]/g, '').length; - return (actual ? 'different ' : '') + `${digits} digit number`; + return `${typeof value} ${formatValue(value)}`; }; diff --git a/src/game/verifier.ts b/src/game/verifier.ts index ebdb371..e11cd1b 100644 --- a/src/game/verifier.ts +++ b/src/game/verifier.ts @@ -2,19 +2,13 @@ import lodash from 'lodash'; import { readFileSync } from 'fs'; import { runInNewContext } from 'vm'; -import type { Challenge, Primitive } from '../challenges/types.js'; -import { formatTypeAndValue } from './utils.js'; +import { formatTypeAndValue, formatValue } from './utils.js'; +import { challenges } from '../challenges/index.js'; +import { Primitive } from '../challenges/types.js'; -export interface VerifyJob< - A extends readonly Primitive[], - R extends Primitive, -> { +export interface VerifyJob { file: string; - challenge: Omit, 'example'> & { - key: string; - solution: string; - example: string; - }; + key: string; } const hasKey = ( @@ -22,12 +16,21 @@ const hasKey = ( key: K ): obj is { [P in K]: unknown } => key in obj; -process.on('message', (entry: VerifyJob) => { +process.on('message', (entry: VerifyJob) => { try { + const challenge = challenges.find((ch) => ch.key === entry.key); + + if (!challenge) { + throw new Error('Challenge not found'); + } + const script = readFileSync(entry.file, 'utf8'); const context: { play?: unknown; module: { exports: unknown } } = { + ...challenge.context, module: { - exports: {}, + exports: { + runBefore: challenge.runBefore, + }, }, }; @@ -38,6 +41,15 @@ process.on('message', (entry: VerifyJob) => { runInNewContext(toRun, context); + if ( + typeof context.module.exports === 'object' && + !!context.module.exports && + hasKey(context.module.exports, 'runBefore') && + typeof context.module.exports.runBefore === 'function' + ) { + context.module.exports.runBefore(); + } + const play = typeof context.module.exports === 'function' ? context.module.exports @@ -56,7 +68,9 @@ process.on('message', (entry: VerifyJob) => { } try { - entry.challenge.assertions.forEach((assertion) => { + challenge.assertRules?.(script); + + challenge.assertions.forEach((assertion) => { const result = play(...assertion.input); const expected = @@ -75,12 +89,10 @@ process.on('message', (entry: VerifyJob) => { )} but received ${formatTypeAndValue( result, true - )} when supplied with ${formatTypeAndValue(assertion.input, false)}` + )} when supplied with arguments: ${assertion.input.map((input) => formatValue(input)).join(', ')}` ); } }); - - entry.challenge.assertRules?.(entry.challenge.solution); } catch (err) { return process.send?.({ err: @@ -88,7 +100,7 @@ process.on('message', (entry: VerifyJob) => { ? err : err instanceof Error ? err.message - : 'Unknown error', + : `Unknown error ${err}`, valid: false, }); } @@ -99,7 +111,7 @@ process.on('message', (entry: VerifyJob) => { console.error('Verifier failed with error:', err); process.send?.({ - err: 'Your script contains an error', + err: `Your script contains an error: ${err}`, valid: false, }); }