diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..09331e1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,41 @@ +name: CI + +on: + pull_request: + branches: + - main + push: + branches: + - main + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run lint + run: npm run lint + + - name: Run unit tests + run: npm run test:run + + - name: Build + run: npm run build diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 1d6f546..fb3132a 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -24,7 +24,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 20 + node-version-file: .nvmrc cache: npm - name: Install dependencies diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/src/domain/rules/slither/rules.test.ts b/src/domain/rules/slither/rules.test.ts index c4a3bc6..6f3160e 100644 --- a/src/domain/rules/slither/rules.test.ts +++ b/src/domain/rules/slither/rules.test.ts @@ -1899,6 +1899,10 @@ describe('slither strong inference rule', () => { if (!colorAssumptionRule) { throw new Error('Expected color-assumption-inference rule') } + const unboundedColorAssumptionRule = createColorAssumptionInferenceRule( + () => slitherRules.filter((rule) => rule.id !== 'color-assumption-inference' && rule.id !== 'strong-inference'), + { maxMs: Number.POSITIVE_INFINITY }, + ) const strongRule = slitherRules.find((rule) => rule.id === 'strong-inference') if (!strongRule) { throw new Error('Expected strong-inference rule') @@ -2112,7 +2116,7 @@ describe('slither strong inference rule', () => { current = nextPuzzle } - const result = colorAssumptionRule.apply(current) + const result = unboundedColorAssumptionRule.apply(current) expect(result).not.toBeNull() expect(result?.diffs).toEqual([{ kind: 'cell', cellKey: cellKey(2, 12), fromFill: null, toFill: 'green' }]) @@ -2121,7 +2125,12 @@ describe('slither strong inference rule', () => { ...(targetBranch.cells[cellKey(7, 0)] ?? {}), fill: 'yellow', } - const targetResult = runTrialUntilFixpoint(targetBranch, rulesBeforeColorAssumption, 120, Date.now() + 2000) + const targetResult = runTrialUntilFixpoint( + targetBranch, + rulesBeforeColorAssumption, + 120, + Number.POSITIVE_INFINITY, + ) expect(targetResult.contradiction).toBe(true) }) diff --git a/src/domain/rules/slither/rules/colorAssumptionInference.ts b/src/domain/rules/slither/rules/colorAssumptionInference.ts index 41071c3..21effcf 100644 --- a/src/domain/rules/slither/rules/colorAssumptionInference.ts +++ b/src/domain/rules/slither/rules/colorAssumptionInference.ts @@ -15,6 +15,12 @@ const COLOR_ASSUMPTION_MAX_CANDIDATES = 120 const COLOR_ASSUMPTION_MAX_TRIAL_STEPS = 120 const COLOR_ASSUMPTION_MAX_MS = 2000 +type ColorAssumptionInferenceOptions = { + maxCandidates?: number + maxTrialSteps?: number + maxMs?: number +} + type ColorAssumptionCandidate = { cellKey: string row: number @@ -99,17 +105,23 @@ const getCellAssumptionDiff = ( }, ] -export const createColorAssumptionInferenceRule = (getDeterministicRules: () => Rule[]): Rule => ({ +export const createColorAssumptionInferenceRule = ( + getDeterministicRules: () => Rule[], + options: ColorAssumptionInferenceOptions = {}, +): Rule => ({ id: 'color-assumption-inference', name: 'Color Assumption Inference', apply: (puzzle: PuzzleIR): RuleApplication | null => { const deterministicRules = getDeterministicRules() - const candidates = collectColorAssumptionCandidates(puzzle, COLOR_ASSUMPTION_MAX_CANDIDATES) + const candidates = collectColorAssumptionCandidates( + puzzle, + options.maxCandidates ?? COLOR_ASSUMPTION_MAX_CANDIDATES, + ) if (candidates.length === 0) { return null } - const deadlineMs = Date.now() + COLOR_ASSUMPTION_MAX_MS + const deadlineMs = Date.now() + (options.maxMs ?? COLOR_ASSUMPTION_MAX_MS) for (const candidate of candidates) { if (Date.now() > deadlineMs) { break @@ -121,10 +133,20 @@ export const createColorAssumptionInferenceRule = (getDeterministicRules: () => const yellowSetupOk = applyCellAssumption(yellowBranch, candidate.cellKey, 'yellow') const greenResult = greenSetupOk - ? runTrialUntilFixpoint(greenBranch, deterministicRules, COLOR_ASSUMPTION_MAX_TRIAL_STEPS, deadlineMs) + ? runTrialUntilFixpoint( + greenBranch, + deterministicRules, + options.maxTrialSteps ?? COLOR_ASSUMPTION_MAX_TRIAL_STEPS, + deadlineMs, + ) : { contradiction: true, timedOut: false, exhausted: false, puzzle: greenBranch } const yellowResult = yellowSetupOk - ? runTrialUntilFixpoint(yellowBranch, deterministicRules, COLOR_ASSUMPTION_MAX_TRIAL_STEPS, deadlineMs) + ? runTrialUntilFixpoint( + yellowBranch, + deterministicRules, + options.maxTrialSteps ?? COLOR_ASSUMPTION_MAX_TRIAL_STEPS, + deadlineMs, + ) : { contradiction: true, timedOut: false, exhausted: false, puzzle: yellowBranch } if (greenResult.timedOut || yellowResult.timedOut) {