Skip to content

Commit

Permalink
Add a ValueIndexing constraint.
Browse files Browse the repository at this point in the history
This can be used to implement the Slingshot constraint.
  • Loading branch information
sigh committed Nov 3, 2024
1 parent a36caf8 commit 3cd0f27
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 0 deletions.
6 changes: 6 additions & 0 deletions data/example_puzzles.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,12 @@ const DISPLAYED_EXAMPLES = [
input: '.AntiKnight.Diagonal~1.Diagonal~-1.Cage~15~R4C6~R5C6~R6C6.Cage~15~R4C4~R5C4~R6C4.Cage~15~R4C4~R4C5~R4C6.Cage~15~R6C4~R6C5~R6C6.Cage~15~R5C4~R5C5~R5C6.Cage~15~R4C5~R5C5~R6C5.Cage~15~R6C4~R5C5~R4C6.Cage~15~R4C4~R5C5~R6C6.~R4C1_3~R4C2_8~R4C3_4~R9C9_2',
solution: '843567219275913846619428375384672951726159483951834627537286194462791538198345762',
},
{
name: 'Slingshot sudoku',
src: 'https://www.reddit.com/r/sudoku/comments/ueeocq/logic_wiz_slingshot_sudoku_rules_and_links_in/',
input: '.ValueIndexing~R1C4~R2C4~R2C3~R2C2~R2C1.ValueIndexing~R1C6~R2C6~R2C7~R2C8~R2C9.ValueIndexing~R6C9~R7C9~R7C8~R7C7~R7C6~R7C5~R7C4~R7C3~R7C2~R7C1.ValueIndexing~R5C7~R5C8~R6C8~R7C8~R8C8~R9C8.ValueIndexing~R4C7~R4C6~R5C6~R6C6~R7C6~R8C6~R9C6.ValueIndexing~R4C3~R4C4~R5C4~R6C4~R7C4~R8C4~R9C4.ValueIndexing~R5C3~R5C2~R6C2~R7C2~R8C2~R9C2.ValueIndexing~R6C1~R7C1~R7C2~R7C3~R7C4~R7C5~R7C6~R7C7~R7C8~R7C9.~R2C4_3~R2C2_5~R3C3_6~R4C3_9~R6C3_1~R8C2_8~R9C1_2~R9C9_8~R8C8_1~R6C7_3~R4C7_2~R3C7_8~R2C8_2~R2C6_1.V~R7C8~R7C9.V~R7C1~R7C2.V~R4C5~R5C5.X~R5C5~R6C5.X~R4C6~R5C6.X~R4C4~R5C4.X~R2C5~R3C5.X~R1C6~R1C7.X~R1C9~R1C8.X~R1C4~R1C3.X~R1C2~R1C1',
solution: '372859164854361927916247853769134285538926741421785396147598632683472519295613478',
},
{
name: '16x16',
src: 'http://forum.enjoysudoku.com/symmertic-16x16-grid-t38266.html#p295157',
Expand Down
1 change: 1 addition & 0 deletions js/debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ const runSolveTests = async (onFailure) => {
'Event horizon', // Duplicate cell in sum, BinaryPairwise optimization.
'Copycat, easy', // Same value - 2 sets, repeated values
'Clone sudoku', // Same value - single cell sets
'Slingshot sudoku', // ValueIndexing
'Numbered Rooms vs X-Sums', // Or constraint
'Or with Givens', // Or constraint (update watched cells)
'And with AllDifferent', // And constraint (with cellExclusions)
Expand Down
57 changes: 57 additions & 0 deletions js/solver/handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2093,6 +2093,63 @@ SudokuConstraintHandler.SumLine = class SumLine extends SudokuConstraintHandler
}
}

SudokuConstraintHandler.ValueIndexing = class ValueIndexing extends SudokuConstraintHandler {
constructor(valueCell, controlCell, ...indexedCells) {
super([valueCell, controlCell, ...indexedCells]);
this._controlCell = controlCell;
this._valueCell = valueCell;
this._indexedCells = indexedCells;
}

initialize(initialGridCells, cellExclusions, shape, stateAllocator) {
const numCells = this._indexedCells.length;
const mask = (1 << numCells) - 1;
initialGridCells[this._controlCell] &= mask;

return !!initialGridCells[this._valueCell];
}

enforceConsistency(grid, handlerAccumulator) {
const cells = this._indexedCells;
const controlCell = this._controlCell
const numCells = cells.length;
const valueCell = this._valueCell;

const originalControl = grid[controlCell];
const originalValues = grid[valueCell];

let possibleValues = 0;
let possibleControl = 0;
for (let i = 0, v = 1; i < numCells; i++, v <<= 1) {
if ((originalControl & v) && (grid[cells[i]] & originalValues)) {
possibleValues |= grid[cells[i]] & originalValues;
possibleControl |= v;
}
}

// If there is a single control value then we can constrain the indexed
// cell.
if (!(possibleControl & (possibleControl - 1))) {
const index = LookupTables.toIndex(possibleControl);
const cell = cells[index];
grid[cell] = (possibleValues &= grid[cell]);
if (grid[cell] === 0) return false;
}

if (originalValues !== possibleValues) {
if (!(grid[valueCell] = possibleValues)) return false;
handlerAccumulator.addForCell(valueCell);
}

if (possibleControl !== originalControl) {
if (!(grid[controlCell] = possibleControl)) return false;
handlerAccumulator.addForCell(controlCell);
}

return true;
}
}

SudokuConstraintHandler.Indexing = class Indexing extends SudokuConstraintHandler {
constructor(controlCell, indexedCells, indexedValue) {
super([controlCell]);
Expand Down
28 changes: 28 additions & 0 deletions js/sudoku_builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -1642,6 +1642,27 @@ class SudokuConstraint {
}
}

static ValueIndexing = class ValueIndexing extends SudokuConstraintBase {
static DESCRIPTION = (`
Arrows point from a cell with digit X towards the same digit X,
where the digit in the second cell on the line indicates how many cells
away the digit X is on the line.`);
static CATEGORY = 'LinesAndSets';
static DISPLAY_CONFIG = {
displayClass: 'GenericLine',
startMarker: LineOptions.SMALL_FULL_CIRCLE_MARKER,
arrow: true,
dashed: true,
};
static VALIDATE_CELLS_FN = (cells, shape) => cells.length > 2;

constructor(...cells) {
super(arguments);
this.cells = cells;
}
}


static V = class V extends SudokuConstraintBase {
static DESCRIPTION = (`
Values must add to 5. Adjacent cells only.`);
Expand Down Expand Up @@ -2831,6 +2852,13 @@ class SudokuBuilder {
yield new SudokuConstraintHandler.Sum(cells, 5);
break;

case 'ValueIndexing':
{
const cells = constraint.cells.map(
c => shape.parseCellId(c).cell);
yield new SudokuConstraintHandler.ValueIndexing(...cells);
}
break;
case 'Windoku':
for (const cells of SudokuConstraint.Windoku.regions(shape)) {
yield new SudokuConstraintHandler.AllDifferent(cells);
Expand Down

0 comments on commit 3cd0f27

Please sign in to comment.