-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
42 changed files
with
2,365 additions
and
1,697 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,257 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.ObjectModel; | ||
using System.Linq; | ||
#if DEBUG | ||
using System.Diagnostics; | ||
#endif | ||
|
||
namespace Kermalis.SudokuSolver; | ||
|
||
#if DEBUG | ||
[DebuggerDisplay("{DebugString()}", Name = "{ToString()}")] | ||
#endif | ||
public sealed class Cell | ||
{ | ||
public const int EMPTY_VALUE = 0; | ||
/// <summary>6 in Column, 6 in Row, 8 in Block</summary> | ||
public const int NUM_VISIBLE_CELLS = 6 + 6 + 8; | ||
|
||
internal readonly Puzzle Puzzle; | ||
public int OriginalValue { get; private set; } | ||
public SPoint Point { get; } | ||
public Region Block { get; private set; } | ||
public Region Column { get; private set; } | ||
public Region Row { get; private set; } | ||
/// <summary>The <see cref="NUM_VISIBLE_CELLS"/> cells this cell is grouped with. Block, Row, Column</summary> | ||
public ReadOnlyCollection<Cell> VisibleCells { get; private set; } | ||
|
||
public int Value { get; private set; } | ||
public HashSet<int> Candidates { get; } | ||
|
||
public List<CellSnapshot> Snapshots { get; } | ||
|
||
internal Cell(Puzzle puzzle, int value, SPoint point) | ||
{ | ||
Puzzle = puzzle; | ||
|
||
OriginalValue = value; | ||
Value = value; | ||
Point = point; | ||
|
||
Candidates = new HashSet<int>(Utils.OneToNine); | ||
Snapshots = []; | ||
|
||
// Will be set in InitRegions | ||
Block = null!; | ||
Column = null!; | ||
Row = null!; | ||
// Will be set in InitVisibleCells | ||
VisibleCells = null!; | ||
} | ||
internal void InitRegions() | ||
{ | ||
Block = Puzzle.Blocks[Point.BlockIndex]; | ||
Column = Puzzle.Columns[Point.Column]; | ||
Row = Puzzle.Rows[Point.Row]; | ||
} | ||
internal void InitVisibleCells() | ||
{ | ||
int counter = 0; | ||
var neighbors = new Cell[NUM_VISIBLE_CELLS]; | ||
for (int i = 0; i < 9; i++) | ||
{ | ||
// Add 8 neighbors from block | ||
Cell other = Block[i]; | ||
if (other != this) | ||
{ | ||
neighbors[counter++] = other; | ||
} | ||
|
||
// Add 6 neighbors from row | ||
other = Row[i]; | ||
if (other.Block != Block) | ||
{ | ||
neighbors[counter++] = other; | ||
} | ||
|
||
// Add 6 neighbors from column | ||
other = Column[i]; | ||
if (other.Block != Block) | ||
{ | ||
neighbors[counter++] = other; | ||
} | ||
} | ||
VisibleCells = new ReadOnlyCollection<Cell>(neighbors); | ||
} | ||
|
||
// TODO: Remove | ||
public bool ChangeCandidates(int candidate, bool remove = true) | ||
{ | ||
return remove ? Candidates.Remove(candidate) : Candidates.Add(candidate); | ||
} | ||
internal bool ChangeCandidates(IEnumerable<int> candidates, bool remove = true) | ||
{ | ||
bool changed = false; | ||
foreach (int candidate in candidates) | ||
{ | ||
if (remove ? Candidates.Remove(candidate) : Candidates.Add(candidate)) | ||
{ | ||
changed = true; | ||
} | ||
} | ||
return changed; | ||
} | ||
internal static bool ChangeCandidates(IEnumerable<Cell> cells, int candidate, bool remove = true) | ||
{ | ||
bool changed = false; | ||
foreach (Cell cell in cells) | ||
{ | ||
if (remove ? cell.Candidates.Remove(candidate) : cell.Candidates.Add(candidate)) | ||
{ | ||
changed = true; | ||
} | ||
} | ||
return changed; | ||
} | ||
internal static bool ChangeCandidates(IEnumerable<Cell> cells, IEnumerable<int> candidates, bool remove = true) | ||
{ | ||
bool changed = false; | ||
foreach (Cell cell in cells) | ||
{ | ||
foreach (int candidate in candidates) | ||
{ | ||
if (remove ? cell.Candidates.Remove(candidate) : cell.Candidates.Add(candidate)) | ||
{ | ||
changed = true; | ||
} | ||
} | ||
} | ||
return changed; | ||
} | ||
|
||
/// <summary>Changes the current value to <paramref name="newValue"/>. <see cref="Candidates"/> is updated. | ||
/// If <paramref name="refreshOtherCellCandidates"/> is true, the entire puzzle's candidates are refreshed.</summary> | ||
internal void Set(int newValue, bool refreshOtherCellCandidates = false) | ||
{ | ||
int oldValue = Value; | ||
Value = newValue; | ||
|
||
if (newValue == EMPTY_VALUE) | ||
{ | ||
for (int i = 1; i <= 9; i++) | ||
{ | ||
Candidates.Add(i); | ||
} | ||
ChangeCandidates(VisibleCells, oldValue, remove: false); | ||
} | ||
else | ||
{ | ||
Candidates.Clear(); | ||
ChangeCandidates(VisibleCells, newValue); | ||
} | ||
|
||
if (refreshOtherCellCandidates) | ||
{ | ||
Puzzle.RefreshCandidates(); | ||
} | ||
} | ||
public void ChangeOriginalValue(int value) | ||
{ | ||
if (value is < EMPTY_VALUE or > 9) | ||
{ | ||
throw new ArgumentOutOfRangeException(nameof(value), value, null); | ||
} | ||
|
||
OriginalValue = value; | ||
Set(value, refreshOtherCellCandidates: true); | ||
} | ||
/*internal void CreateSnapshot(bool isCulprit, bool isSemiCulprit) | ||
{ | ||
Span<int> cache = stackalloc int[9]; | ||
Snapshots.Add(new CellSnapshot(Value, new ReadOnlyCollection<int>(Candidates.AsInt(cache).ToArray()), isCulprit, isSemiCulprit)); | ||
}*/ | ||
internal void CreateSnapshot(bool isCulprit, bool isSemiCulprit) | ||
{ | ||
Snapshots.Add(new CellSnapshot(Value, new ReadOnlyCollection<int>(Candidates.ToArray()), isCulprit, isSemiCulprit)); | ||
} | ||
|
||
public override int GetHashCode() | ||
{ | ||
return Point.GetHashCode(); | ||
} | ||
public override bool Equals(object? obj) | ||
{ | ||
if (obj is Cell other) | ||
{ | ||
return other.Point.Equals(Point); | ||
} | ||
return false; | ||
} | ||
public override string ToString() | ||
{ | ||
return Point.ToString(); | ||
} | ||
#if DEBUG | ||
public string DebugString() | ||
{ | ||
string s = Point.ToString() + " "; | ||
if (Value == EMPTY_VALUE) | ||
{ | ||
s += "has candidates: " + Candidates.Print(); | ||
} | ||
else | ||
{ | ||
s += "- " + Value.ToString(); | ||
} | ||
return s; | ||
} | ||
#endif | ||
|
||
/*/// <summary>Returns the visible cells that this cell and <paramref name="other"/> share. | ||
/// Result length is 2 (1 row + 1 column) or 7 (7 row/column) or 13 (7 block + 6 row/column)</summary> | ||
internal Span<Cell> IntersectVisibleCells(Cell other, Span<Cell> cache) | ||
{ | ||
int counter = 0; | ||
for (int i = 0; i < NUM_VISIBLE_CELLS; i++) | ||
{ | ||
Cell cell = VisibleCells[i]; | ||
if (other.VisibleCells.Contains(cell)) | ||
{ | ||
cache[counter++] = cell; | ||
} | ||
} | ||
return cache.Slice(0, counter); | ||
} | ||
/// <summary>Returns the visible cells that this cell, <paramref name="otherA"/>, and <paramref name="otherB"/> all share. | ||
/// Result length is 0 or 1 (1 row/column) or 6 (6 block/row/column) or 12 (6 block + 6 row/column)</summary> | ||
internal Span<Cell> IntersectVisibleCells(Cell otherA, Cell otherB, Span<Cell> cache) | ||
{ | ||
int counter = 0; | ||
for (int i = 0; i < NUM_VISIBLE_CELLS; i++) | ||
{ | ||
Cell cell = VisibleCells[i]; | ||
if (otherA.VisibleCells.Contains(cell) && otherB.VisibleCells.Contains(cell)) | ||
{ | ||
cache[counter++] = cell; | ||
} | ||
} | ||
return cache.Slice(0, counter); | ||
}*/ | ||
|
||
/*/// <summary>Result length is 12 or 14</summary> | ||
internal Span<Cell> VisibleCellsExceptRegion(Region except, Span<Cell> cache) | ||
{ | ||
int counter = 0; | ||
for (int i = 0; i < 8 + 6 + 6; i++) | ||
{ | ||
Cell cell = VisibleCells[i]; | ||
if (except.IndexOf(cell) == -1) | ||
{ | ||
// Add if "except" region does not contain the visible cell | ||
cache[counter++] = cell; | ||
} | ||
} | ||
return cache.Slice(0, counter); | ||
}*/ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
using System.Collections.ObjectModel; | ||
|
||
namespace Kermalis.SudokuSolver; | ||
|
||
public sealed class CellSnapshot | ||
{ | ||
public int Value { get; } | ||
public ReadOnlyCollection<int> Candidates { get; } | ||
public bool IsCulprit { get; } | ||
public bool IsSemiCulprit { get; } | ||
|
||
internal CellSnapshot(int value, ReadOnlyCollection<int> candidates, bool isCulprit, bool isSemiCulprit) | ||
{ | ||
Value = value; | ||
Candidates = candidates; | ||
IsCulprit = isCulprit; | ||
IsSemiCulprit = isSemiCulprit; | ||
} | ||
} |
Oops, something went wrong.