Skip to content

Commit 2e101a8

Browse files
committed
Added a solver method for handling the last unsolved clue in a line.
1 parent 10b1c3b commit 2e101a8

File tree

5 files changed

+113
-2
lines changed

5 files changed

+113
-2
lines changed

FinModelUtility/Fin/Fin.Picross/src/PicrossSolver.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ public IReadOnlyList<IReadOnlySet<IPicrossMove>> Solve(
1616
var columnClueStates = ToClueStates_(clues.Columns);
1717
var columnLineStates = columnClueStates
1818
.Select((clues, x) => new PicrossLineState {
19+
IsColumn = true,
1920
ClueStates = clues,
2021
CellStates = boardState.GetColumn(x).ToArray(),
2122
})
2223
.ToArray();
2324
var rowClueStates = ToClueStates_(clues.Rows);
2425
var rowLineStates = rowClueStates
2526
.Select((clues, y) => new PicrossLineState {
27+
IsColumn = false,
2628
ClueStates = clues,
2729
CellStates = boardState.GetRow(y).ToArray(),
2830
})
@@ -122,13 +124,15 @@ private static readonly IReadOnlyList<IPicrossSolverMethod> SOLVER_METHODS_
122124
= [
123125
new AlreadySolvedPicrossSolverMethod(),
124126
new ExtendFirstClueSolverMethod(),
127+
// TODO: Get rid of this
125128
new ExtendLastClueSolverMethod(),
126129
new FillSmallestUnknownsBetweenEmptiesSolverMethod(),
127130
new GapsAroundFirstClueSolverMethod(),
128131
new GapsAroundKnownCluesSolverMethod(),
129132
new GapsBetweenKnownCluesSolverMethod(),
130133
new GapsBetweenNeighboringCluesSolverMethod(),
131134
new GapsBetweenNeighboringShortCluesSolverMethod(),
135+
new LastClueSolverMethod(),
132136
new MatchingBiggestOrUniqueLengthSolverMethod(),
133137
];
134138

@@ -385,7 +389,7 @@ var targetI
385389
if (isFirstClue && clueUnknownCount == 0 && !clueState.Solved) {
386390
var clueStartI = forward ? i : i + increment * (clueLength - 1);
387391
yield return new PicrossClueMove(
388-
PicrossClueMoveSource.FIRST_CLUE,
392+
PicrossClueMoveSource.FIRST_CLUE_IN_LINE,
389393
clueState.Clue,
390394
clueStartI);
391395
}

FinModelUtility/Fin/Fin.Picross/src/moves/CellMoves.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public enum PicrossCellMoveSource {
1919
EMPTY_BETWEEN_CLUES,
2020
EMPTY_AROUND_KNOWN_CLUE,
2121
EMPTY_UP_TO_FIRST_CLUE,
22+
WITHIN_LAST_CLUE,
2223
}
2324

2425
[Equatable]

FinModelUtility/Fin/Fin.Picross/src/moves/ClueMoves.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ public enum PicrossClueMoveSource {
66
FREEBIE_EMPTY,
77
FREEBIE_FULL_LENGTH,
88
FREEBIE_PERFECT_FIT,
9-
FIRST_CLUE,
9+
FIRST_CLUE_IN_LINE,
1010
ALL_CLUES_SOLVED,
1111
ONLY_MATCHING_CLUE,
12+
LAST_UNSOLVED_CLUE,
1213
}
1314

1415
[Equatable]
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
using fin.picross.moves;
2+
using fin.util.enumerables;
3+
4+
namespace fin.picross.solver;
5+
6+
public class LastClueSolverMethod : IPicrossSolverMethod {
7+
public IEnumerable<IPicrossMove1d>
8+
TryToFindMoves(IPicrossLineState lineState) {
9+
// Make sure we only run this solver if there's a single unsolved clue
10+
var clueStates = lineState.ClueStates;
11+
var lastUnsolvedClue
12+
= clueStates.FirstOrDefaultAndCount(c => !c.Solved,
13+
out var unsolvedClueCount);
14+
if (lastUnsolvedClue == null || unsolvedClueCount > 1) {
15+
yield break;
16+
}
17+
18+
var cellStates = lineState.CellStates;
19+
var length = cellStates.Count;
20+
21+
int? firstUnclaimedFilledCellIndex = null;
22+
int? lastUnclaimedFilledCellIndex = null;
23+
24+
// Find first and last unclaimed filled cells in row
25+
for (var i = 0; i < length; ++i) {
26+
var cellState = cellStates[i];
27+
var isUnclaimed = cellState.Status == PicrossCellStatus.KNOWN_FILLED &&
28+
(lineState.IsColumn
29+
? cellState.ColumnClue == null
30+
: cellState.RowClue == null);
31+
32+
if (!isUnclaimed) {
33+
continue;
34+
}
35+
36+
firstUnclaimedFilledCellIndex ??= i;
37+
lastUnclaimedFilledCellIndex = i;
38+
}
39+
40+
// If there are no unclaimed cells, there's nothing we can do
41+
if (firstUnclaimedFilledCellIndex == null ||
42+
lastUnclaimedFilledCellIndex == null) {
43+
yield break;
44+
}
45+
46+
// Fill in any cells between the first and last filled unclaimed cell
47+
for (var i = firstUnclaimedFilledCellIndex.Value + 1;
48+
i < lastUnclaimedFilledCellIndex.Value;
49+
++i) {
50+
if (cellStates[i].Status == PicrossCellStatus.UNKNOWN) {
51+
yield return new PicrossCellMove1d(
52+
PicrossCellMoveType.MARK_FILLED,
53+
PicrossCellMoveSource.WITHIN_LAST_CLUE,
54+
i);
55+
}
56+
}
57+
58+
// Get the total accounted for length of the clue
59+
var clueLength = lastUnsolvedClue.Length;
60+
var filledLength = lastUnclaimedFilledCellIndex.Value -
61+
firstUnclaimedFilledCellIndex.Value +
62+
1;
63+
64+
// If it's already the full length, we're done!
65+
if (clueLength == filledLength) {
66+
yield break;
67+
}
68+
69+
// Otherwise, we need to fill in cells that are too far away
70+
var remainingClueLength = clueLength - filledLength;
71+
72+
var remainingClueLengthBefore = remainingClueLength;
73+
for (var i = firstUnclaimedFilledCellIndex.Value - 1; i >= 0; --i) {
74+
var cellStatus = cellStates[i].Status;
75+
if (cellStatus == PicrossCellStatus.UNKNOWN) {
76+
if (remainingClueLengthBefore-- <= 0) {
77+
yield return new PicrossCellMove1d(
78+
PicrossCellMoveType.MARK_EMPTY,
79+
PicrossCellMoveSource.TOO_FAR_FROM_KNOWN,
80+
i);
81+
}
82+
} else {
83+
remainingClueLengthBefore = 0;
84+
}
85+
}
86+
87+
var remainingClueLengthAfter = remainingClueLength;
88+
for (var i = lastUnclaimedFilledCellIndex.Value + 1; i < length; ++i) {
89+
var cellStatus = cellStates[i].Status;
90+
if (cellStatus == PicrossCellStatus.UNKNOWN) {
91+
if (remainingClueLengthAfter-- <= 0) {
92+
yield return new PicrossCellMove1d(
93+
PicrossCellMoveType.MARK_EMPTY,
94+
PicrossCellMoveSource.TOO_FAR_FROM_KNOWN,
95+
i);
96+
}
97+
} else {
98+
remainingClueLengthAfter = 0;
99+
}
100+
}
101+
}
102+
}

FinModelUtility/Fin/Fin.Picross/src/solver/PicrossLineState.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
namespace fin.picross.solver;
22

33
public interface IPicrossLineState {
4+
bool IsColumn { get; }
45
IReadOnlyList<IReadOnlyPicrossCellState> CellStates { get; }
56
IReadOnlyList<IReadOnlyPicrossClueState> ClueStates { get; }
67
}
78

89
public class PicrossLineState : IPicrossLineState {
10+
public required bool IsColumn { get; init; }
11+
912
public required IReadOnlyList<IReadOnlyPicrossCellState> CellStates {
1013
get;
1114
init;

0 commit comments

Comments
 (0)