From 5bdc9fa4c356281ede53523e5393084f150647e5 Mon Sep 17 00:00:00 2001 From: icecream17 Date: Sat, 8 Jan 2022 21:45:07 -0600 Subject: [PATCH] 2 string kite --- CHANGELOG.md | 4 ++ Notes.md | 11 +++++ package.json | 2 +- src/Api/Spaces/PureSudoku.test.ts | 2 +- src/Api/Spaces/PureSudoku.ts | 2 +- src/Api/Strategies/Strategies.test.tsx | 18 ++++++- src/Api/Strategies/Strategies.ts | 2 + src/Api/Strategies/hiddenSingles.ts | 11 +---- src/Api/Strategies/twoStringKite.ts | 68 ++++++++++++++++++++++++++ src/Api/Strategies/xyChain.ts | 4 +- src/Api/Strategies/xyLoop.ts | 17 ++----- src/Api/Utils.dependent.ts | 21 +++++++- src/Elems/AsideElems/StrategyList.tsx | 7 ++- src/Elems/MainElems/Cell.tsx | 15 ++++-- src/Elems/Version.tsx | 2 +- 15 files changed, 150 insertions(+), 36 deletions(-) create mode 100644 src/Api/Strategies/twoStringKite.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 856d095a..efeb86b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Note: Many earlier versions are not specified, that's too much work. When a `@types` dependency updates, they almost always don't affect anything. +## v0.28.0 + +- (use) Add 2-string kite + ## v0.27.2 - (deps) **I will only note major dependency updates** - not including devDependencies. diff --git a/Notes.md b/Notes.md index 0d7e483b..a4a2d141 100644 --- a/Notes.md +++ b/Notes.md @@ -699,3 +699,14 @@ TIL You can use `this` in static class methods. () Only declare an inherited property if you're overriding it. () + +## abbreviations + +```rust +col = column +min = minimum +str = string +args = arguments +cand = candidate +char = character +``` diff --git a/package.json b/package.json index 04eec427..93da2fda 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "solver", - "version": "0.27.2", + "version": "0.28.0", "private": true, "homepage": "https://icecream17.github.io/solver", "dependencies": { diff --git a/src/Api/Spaces/PureSudoku.test.ts b/src/Api/Spaces/PureSudoku.test.ts index 83c5d04d..f9fc2a8d 100644 --- a/src/Api/Spaces/PureSudoku.test.ts +++ b/src/Api/Spaces/PureSudoku.test.ts @@ -68,7 +68,7 @@ test('getBox', () => { test('getBoxGroup', () => { const testSudoku = new PureSudoku() const cellData = testSudoku.data.map((row, indexOfRow) => - row.map((cell, indexInRow) => ({ + row.map((_cell, indexInRow) => ({ position: id(indexOfRow as IndexToNine, indexInRow as IndexToNine) })) ) diff --git a/src/Api/Spaces/PureSudoku.ts b/src/Api/Spaces/PureSudoku.ts index f474a7fa..299fe51c 100644 --- a/src/Api/Spaces/PureSudoku.ts +++ b/src/Api/Spaces/PureSudoku.ts @@ -278,7 +278,7 @@ export default class PureSudoku { * Removes a candidate at a cell * * @example - * (new PureSudoku()).toggle(7).at(3, 5) + * (new PureSudoku()).remove(7).at(3, 5) */ remove(candidate: SudokuDigits) { // Using an arrow function here to use `this` diff --git a/src/Api/Strategies/Strategies.test.tsx b/src/Api/Strategies/Strategies.test.tsx index 18e4f35f..3c2fb535 100644 --- a/src/Api/Strategies/Strategies.test.tsx +++ b/src/Api/Strategies/Strategies.test.tsx @@ -738,7 +738,7 @@ describe('strategies', () => { expect(testSudoku.data[4][8]).toStrictEqual([7, 9]) }) - test('1', () => { + test('2', () => { const testSudoku = new PureSudoku() testSudoku.set(2, 2).to(6, 8) testSudoku.set(2, 3).to(3, 8, 9) @@ -746,4 +746,20 @@ describe('strategies', () => { expect(xyzWing(testSudoku).success).toBe(false) }) }) + + describe('2 string kite', () => { + test('1', () => { + const testSudoku = new PureSudoku() + testSudoku.import(`000000700000006009000050000000400000003000000020000000100000000000000089000006089100000000000000080000406009000000700000006009000050000000400009003000000020000000000400009003000000020000000100000000000006009000000080000000700000050000000406009020400009020400009100000000003000000000000080000000709000006000000400709000050000000000080000000700000006009000050000000400000000006009003000000020000000100000000000050000000406009003000000020000000100000000000006709000000080000400709000400009023000000100000000000000080000000009020000700000400000000050000000006000003000700000006000000050000000400009000000080020000700003000000020400009100000000000400709023400009020400009000000700000006000000050000100000000020400009000400089003400089`) + expect(xyzWing(testSudoku).success).toBe(true) + expect(testSudoku.data[7][8]).toStrictEqual([7, 9]) + }) + + test('2', () => { + const testSudoku = new PureSudoku() + testSudoku.import(`123456780123456780123456789123456780123456780123456780123456789123456780123456780123456780123456780123456780123456789123456789123456789123456789123456789123456789123456789123456780123456780123456789123456789123456789123456789123456789123456789123456780123456789123456789123456789123456789123456789123456789123456789123456789123456780123456789123456789123456789123456789123456789123456789123456789123456789123456780123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456780123456789123456789123456789123456789123456789123456789123456789123456789123456780123456789123456789123456789123456789123456789123456789123456789123456789`) + expect(xyzWing(testSudoku).success).toBe(true) + expect(testSudoku.data[7][8]).toStrictEqual([7, 9]) + }) + }) }) diff --git a/src/Api/Strategies/Strategies.ts b/src/Api/Strategies/Strategies.ts index 704c5199..2fd641f4 100644 --- a/src/Api/Strategies/Strategies.ts +++ b/src/Api/Strategies/Strategies.ts @@ -9,6 +9,7 @@ import pairsTriplesAndQuads from "./pairsTriplesAndQuads"; import skyscraper from "./skyscraper"; import swordfish from "./swordfish"; import twoMinusOneLines from "./twoMinusOneLines"; +import twoStringKite from "./twoStringKite"; import updateCandidates from "./updateCandidates"; import xWing from "./xWing"; import xyChain from "./xyChain"; @@ -28,6 +29,7 @@ const STRATEGIES = [ swordfish, jellyfish, skyscraper, + twoStringKite, yWing, twoMinusOneLines, xyzWing, diff --git a/src/Api/Strategies/hiddenSingles.ts b/src/Api/Strategies/hiddenSingles.ts index ee8fabb0..5a5d855d 100644 --- a/src/Api/Strategies/hiddenSingles.ts +++ b/src/Api/Strategies/hiddenSingles.ts @@ -1,14 +1,7 @@ import { ALL_CANDIDATES, IndexToNine, INDICES_TO_NINE, SudokuDigits } from "../../Types" import PureSudoku from "../Spaces/PureSudoku" -import Sudoku from "../Spaces/Sudoku" import { boxAt } from "../Utils" - -function colorCandidate(sudoku: PureSudoku, row: IndexToNine, column: IndexToNine, candidate: SudokuDigits, color = 'blue') { - if (sudoku instanceof Sudoku) { - const element = sudoku.cells[row][column] - element?.highlight([candidate], color) - } -} +import { colorCandidateF } from "../Utils.dependent" /** * The state for a candidate in a group @@ -90,7 +83,7 @@ export default function hiddenSingles(sudoku: PureSudoku) { if (cell !== false && cell !== undefined) { successcount++ sudoku.set(cell.row, cell.column).to(candidate) - colorCandidate(sudoku, cell.row, cell.column, candidate, 'solved') + colorCandidateF(sudoku, cell.row, cell.column, candidate, 'solved') } } } diff --git a/src/Api/Strategies/twoStringKite.ts b/src/Api/Strategies/twoStringKite.ts new file mode 100644 index 00000000..317c5a08 --- /dev/null +++ b/src/Api/Strategies/twoStringKite.ts @@ -0,0 +1,68 @@ +import { ALL_CANDIDATES, SudokuDigits } from "../../Types"; +import PureSudoku from "../Spaces/PureSudoku"; +import { CellID } from "../Utils"; +import { colorCandidateF } from "../Utils.dependent"; + +type CandidateInfo = ReturnType[SudokuDigits] + +/** + * Checks if two cells create a two string kite + * Maybe these checkers could be symbolized as matchers + */ +function check(cell1: CellID, cell2: CellID, candidate: SudokuDigits, candLocations: CandidateInfo, sudoku: PureSudoku) { + /* eslint-disable sonarjs/no-collapsible-if -- It's clearer */ + if (cell1.row === cell2.row || cell1.column === cell2.column) { + return 0 + } + + const sameRowAsCell1 = candLocations.rows[cell1.row] + const sameColAsCell2 = candLocations.columns[cell2.column] + if (sameRowAsCell1.size === 2 && sameColAsCell2.size === 2) { + // Looks big nesting but not really + for (const cell1B of sameRowAsCell1) { + if (cell1B !== cell1) { + for (const cell2B of sameColAsCell2) { + if (cell2B !== cell2) { + // All this does is get 1b and 2b + // 1 1b + // 2 + // 2b + if (sudoku.data[cell2B.row][cell1B.column].includes(candidate)) { + colorCandidateF(sudoku, cell1.row, cell1.column, candidate) + colorCandidateF(sudoku, cell2.row, cell2.column, candidate, 'green') + colorCandidateF(sudoku, cell1B.row, cell1B.column, candidate) + colorCandidateF(sudoku, cell2B.row, cell2B.column, candidate, 'green') + sudoku.remove(candidate).at(cell2B.row, cell1B.column) + return 1 + } + } + } + } + } + } + + return 0 +} + +export default function twoStringKite(sudoku: PureSudoku) { + const candidateLocations = sudoku.getCandidateLocations() + + for (const candidate of ALL_CANDIDATES) { + for (const box of candidateLocations[candidate].boxes) { + if (box.size === 2) { + const [cell1, cell2] = box + const successcount = + check(cell1, cell2, candidate, candidateLocations[candidate], sudoku) + + check(cell2, cell1, candidate, candidateLocations[candidate], sudoku) + if (successcount) { + return { + success: true, + successcount + } as const + } + } + } + } + + return { success: false } as const +} diff --git a/src/Api/Strategies/xyChain.ts b/src/Api/Strategies/xyChain.ts index 6385d2fd..934f9057 100644 --- a/src/Api/Strategies/xyChain.ts +++ b/src/Api/Strategies/xyChain.ts @@ -1,8 +1,8 @@ import { SudokuDigits } from "../../Types"; import PureSudoku from "../Spaces/PureSudoku"; import { affects, assertGet, CandidateID, CellID, id, sharedInSets } from "../Utils"; -import { getCellsWithNCandidates } from "../Utils.dependent"; -import { highlightCell, colorCandidate, cellIsValidLoop } from "./xyLoop"; +import { colorCandidate, getCellsWithNCandidates } from "../Utils.dependent"; +import { highlightCell, cellIsValidLoop } from "./xyLoop"; // Very similar to seenByColor in xyLoop function seenByEnd (sudoku: PureSudoku, { row, column, digit }: CandidateID) { diff --git a/src/Api/Strategies/xyLoop.ts b/src/Api/Strategies/xyLoop.ts index 48f63244..6e8aa8dd 100644 --- a/src/Api/Strategies/xyLoop.ts +++ b/src/Api/Strategies/xyLoop.ts @@ -2,7 +2,7 @@ import { SudokuDigits } from "../../Types"; import PureSudoku from "../Spaces/PureSudoku"; import Sudoku from "../Spaces/Sudoku"; import { affects, assertGet, CandidateID, CellID, id, removeFromArray, sharedInSets } from "../Utils"; -import { getCellsWithNCandidates } from "../Utils.dependent"; +import { colorCandidate, getCellsWithNCandidates } from "../Utils.dependent"; /** * next = has @@ -14,23 +14,12 @@ export function cellIsValidLoop (sudoku: PureSudoku, sees: CellID, has: SudokuDi return cell.includes(has) && !loop.includes(sees) } -/** - * Same as {@link colorGroup}, but this time with a specific candidate - */ -export function colorCandidate (sudoku: PureSudoku, {row, column, digit}: CandidateID, color = 'blue') { - if (sudoku instanceof Sudoku) { - const element = sudoku.cells[row][column] - element?.highlight([digit], color) - } -} - /** * Colors a group of cells', see {@link Cell#highlight} */ -export function highlightCell (sudoku: PureSudoku, cell: CellID, color = 'blue') { +export function highlightCell (sudoku: PureSudoku, {row, column}: CellID, color = 'blue') { if (sudoku instanceof Sudoku) { - const element = sudoku.cells[cell.row][cell.column] - element?.addClass(color) + sudoku.cells[row][column]?.addClass(color) } } diff --git a/src/Api/Utils.dependent.ts b/src/Api/Utils.dependent.ts index c3005ac2..3aa436ec 100644 --- a/src/Api/Utils.dependent.ts +++ b/src/Api/Utils.dependent.ts @@ -4,10 +4,10 @@ * Might want to run `madge --circular --extensions ts ./src` after importing from here */ -import { SudokuDigits, INDICES_TO_NINE } from "../Types"; +import { SudokuDigits, INDICES_TO_NINE, IndexToNine } from "../Types"; import PureSudoku from "./Spaces/PureSudoku"; import Sudoku from "./Spaces/Sudoku"; -import { CellID, id } from "./Utils"; +import { CandidateID, CellID, id } from "./Utils"; /** * Colors a group of cells' candidates, see {@link Cell#highlight} @@ -26,6 +26,23 @@ export function colorGroup (sudoku: PureSudoku, group: Iterable, candida } } +/** + * Same as {@link colorGroup}, but this time with a specific candidate + */ +export function colorCandidateF (sudoku: PureSudoku, row: IndexToNine, column: IndexToNine, digit: SudokuDigits, color = 'blue') { + if (sudoku instanceof Sudoku) { + sudoku.cells[row][column]?.highlight([digit], color) + } +} + +/** + * Same as {@link colorGroup}, but this time with a specific candidate + */ +export function colorCandidate (sudoku: PureSudoku, { row, column, digit }: CandidateID, color = 'blue') { + colorCandidateF(sudoku, row, column, digit, color) +} + + export function getCellsWithNCandidates (sudoku: PureSudoku, N: number) { const cellsWithNCandidates = [] as CellID[] for (const row of INDICES_TO_NINE) { diff --git a/src/Elems/AsideElems/StrategyList.tsx b/src/Elems/AsideElems/StrategyList.tsx index 1902cabc..358506ae 100644 --- a/src/Elems/AsideElems/StrategyList.tsx +++ b/src/Elems/AsideElems/StrategyList.tsx @@ -76,7 +76,12 @@ export default class StrategyList extends React.Component { /> + & string[]) + highlighted: boolean } | { explaining: false previousCandidates: null classes: null candidateClasses: null + highlighted: false } type CellState = Readonly<( @@ -115,7 +117,12 @@ export default class Cell extends React.Component { * * Unchanged: numCandidates===9 */ - pretend: false + pretend: false, + + /** + * Whether a candidate is being highlighted + */ + highlighted: false, } this.whenFocus = this.whenFocus.bind(this) @@ -218,7 +225,8 @@ export default class Cell extends React.Component { } return { - candidateClasses: newCandidateClasses + candidateClasses: newCandidateClasses, + highlighted: true, } }) } @@ -250,6 +258,7 @@ export default class Cell extends React.Component { previousCandidates: null, classes: null, candidateClasses: null, + highlighted: false, }, callback) } @@ -274,7 +283,7 @@ export default class Cell extends React.Component { ) - } else if (this.state.active || this.state.pretend || (this.numCandidates > 1 && this.numCandidates < 9)) { + } else if (this.state.active || this.state.highlighted || (this.numCandidates > 1 && this.numCandidates < 9)) { // Also show candidates when editing a cell // Also show candidates as fallback when numCandidates is in [2, 8] content = ( diff --git a/src/Elems/Version.tsx b/src/Elems/Version.tsx index b2b977c9..eaf689fd 100644 --- a/src/Elems/Version.tsx +++ b/src/Elems/Version.tsx @@ -11,7 +11,7 @@ import StaticComponent from './StaticComponent'; * */ function Version() { - return v0.27.2 + return v0.28.0 } export default StaticComponent(Version)