-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathForwardChecking.java
More file actions
164 lines (137 loc) · 6.23 KB
/
ForwardChecking.java
File metadata and controls
164 lines (137 loc) · 6.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import java.util.List;
/**
* Implements forward checking logic for a Pips-based constraint satisfaction game.
* This class validates if a given game state is still solvable based on remaining
* resources and regional constraints.
*/
public final class ForwardChecking {
/**
* Determines if the current game state is feasible.
* * @param game The current state of the Pips game.
* @param solutionDepth The current depth in the search tree (used to throttle expensive checks).
* @return true if the state is potentially solvable; false if a dead-end is detected.
*/
public static boolean feasible(PipsGame game, int solutionDepth) {
final int totalSlots = game.remainingSumSlots;
final int totalTarget = game.remainingSumTarget;
// 1. Basic Boundary Checks
if (totalSlots == 0) return totalTarget == 0;
if (totalSlots < 0 || totalTarget < 0) return false;
final int[] pipSupply = game.pipSupply;
final int minP = PipsConstants.MIN_PIP;
final int maxP = PipsConstants.MAX_PIP;
long totalSupply = 0;
int maxSingleSupply = 0;
int activeKinds = 0;
int firstP = -1;
int lastP = -1;
// 2. Aggregate Pip Statistics
boolean allEven = true;
boolean allOdd = true;
for (int p = minP; p <= maxP; p++) {
int count = pipSupply[p];
if (count > 0) {
totalSupply += count;
if (count > maxSingleSupply) maxSingleSupply = count;
if (firstP == -1) firstP = p;
lastP = p;
activeKinds++;
// --- NEW PARITY LOGIC ---
if ((p & 1) == 0) allOdd = false; // Using bitwise & 1 is a tiny bit faster than % 2
else allEven = false;
}
}
// Fail if we don't have enough physical pips
if (totalSupply < totalSlots) return false;
// If all pips are even, target must be even.
if (allEven && (totalTarget & 1) != 0) return false;
// If all pips are odd, target parity must match slot count parity.
// (Odd + Odd = Even, Odd + Odd + Odd = Odd)
if (allOdd && (totalTarget & 1) != (totalSlots & 1)) return false;
// 3. Global Constraint Validation
if (activeKinds == 2) {
// Check if the target sum falls within the theoretical range of the two available pips
long minPossible = (long) firstP * totalSlots;
long maxPossible = (long) lastP * totalSlots;
if (totalTarget < minPossible || totalTarget > maxPossible) {
return false;
}
} else if (activeKinds == 1) {
// Only one pip type remains; target must be exactly (pip value * slots)
if ((long) firstP * totalSlots != totalTarget) {
return false;
}
}
// Only perform the expensive sum check at deeper levels of the search tree
if (solutionDepth >= 2) {
if (!isSumPossible(pipSupply, totalSlots, totalTarget, minP, maxP)) {
return false;
}
}
// 4. Regional Constraint Validation
final List<Region> regions = game.regions;
final int regionCount = regions.size();
for (int i = 0; i < regionCount; i++) {
Region g = regions.get(i);
int remaining = g.remainingSlots;
if (remaining <= 0) continue; // Skip if region is already full
int constraintType = g.constraintTypeId;
// Handle "All Equal" constraints: All pips in this region must be the same value
if (constraintType == PipsConstants.TYPE_ALL_EQUAL) {
if (g.filledCount > 0) {
// If some slots are filled, remaining pips must match the requiredValue
if (pipSupply[g.requiredValue] < remaining) {
return false;
}
} else {
// If empty, at least one pip type must have enough supply to fill the region
if (maxSingleSupply < remaining) {
return false;
}
}
}
// Handle "Sum" constraints: Region must sum to a specific value
else if (constraintType == PipsConstants.TYPE_SUM) {
// --- NEW CHEAP BOUNDARY CHECKS ---
// Fast failure: Check if the max/min possible values can even reach the target
if ((long) lastP * remaining < g.remainingTarget) return false;
if ((long) firstP * remaining > g.remainingTarget) return false;
// Only perform the expensive greedy search if the simple math passes
if (!isSumPossible(pipSupply, remaining, g.remainingTarget, minP, maxP)) {
return false;
}
}
}
return true;
}
/**
* Greedy check to see if a target sum is achievable given current supplies.
* Calculates the absolute minimum and maximum possible sums using available pips.
*/
private static boolean isSumPossible(int[] supply, int slots, int target, int minP, int maxP) {
// Calculate Minimum Possible Sum (Greedy: use smallest pips first)
int minSum = 0;
int leftMin = slots;
for (int p = minP; p <= maxP && leftMin > 0; p++) {
int available = supply[p];
if (available > 0) {
int take = Math.min(available, leftMin);
minSum += take * p;
leftMin -= take;
}
}
if (minSum > target) return false;
// Calculate Maximum Possible Sum (Greedy: use largest pips first)
int maxSum = 0;
int leftMax = slots;
for (int p = maxP; p >= minP && leftMax > 0; p--) {
int available = supply[p];
if (available > 0) {
int take = Math.min(available, leftMax);
maxSum += take * p;
leftMax -= take;
}
}
return maxSum >= target;
}
}