introduce bitloops
This commit is contained in:
@@ -16,9 +16,11 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.BitSet;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
import static java.nio.charset.StandardCharsets.*;
|
import static java.nio.charset.StandardCharsets.*;
|
||||||
|
|
||||||
@@ -37,7 +39,7 @@ public record SwedishGenerator(Rng rng) {
|
|||||||
public CandidateInfo(int n) { this(null, n); }
|
public CandidateInfo(int n) { this(null, n); }
|
||||||
}
|
}
|
||||||
|
|
||||||
static final CandidateInfo[] CANDIDATES = IntStream.range(0, 10192 << 2).mapToObj(CandidateInfo::new).toArray(CandidateInfo[]::new);
|
// static final CandidateInfo[] CANDIDATES = IntStream.range(0, 10192 << 2).mapToObj(CandidateInfo::new).toArray(CandidateInfo[]::new);
|
||||||
|
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
@FunctionalInterface interface SlotVisitor { void visit(int key, long packedPos, int len); }
|
@FunctionalInterface interface SlotVisitor { void visit(int key, long packedPos, int len); }
|
||||||
@@ -108,7 +110,7 @@ public record SwedishGenerator(Rng rng) {
|
|||||||
|
|
||||||
public static record FillResult(boolean ok,
|
public static record FillResult(boolean ok,
|
||||||
Gridded grid,
|
Gridded grid,
|
||||||
HashMap<Integer, Lemma> clueMap,
|
Map<Integer, Lemma> clueMap,
|
||||||
FillStats stats) {
|
FillStats stats) {
|
||||||
|
|
||||||
public void calcSimpel() {
|
public void calcSimpel() {
|
||||||
@@ -191,7 +193,7 @@ public record SwedishGenerator(Rng rng) {
|
|||||||
if (idx < 64) lo |= (1L << idx);
|
if (idx < 64) lo |= (1L << idx);
|
||||||
else hi |= (1L << (idx & 63));
|
else hi |= (1L << (idx & 63));
|
||||||
}
|
}
|
||||||
void clear(int idx) { g[idx] = DASH; }
|
void clearletter(int idx) { g[idx] = DASH; }
|
||||||
void clearClue(int idx) {
|
void clearClue(int idx) {
|
||||||
g[idx] = DASH;
|
g[idx] = DASH;
|
||||||
if (idx < 64) lo &= ~(1L << idx);
|
if (idx < 64) lo &= ~(1L << idx);
|
||||||
@@ -246,7 +248,7 @@ public record SwedishGenerator(Rng rng) {
|
|||||||
boolean hasRoomForClue(long packed) { return (packed & GT_1_OFFSET_53_BIT) != X && notClue(packed & 0x7FL) && notClue((packed >>> 7) & 0x7FL); }
|
boolean hasRoomForClue(long packed) { return (packed & GT_1_OFFSET_53_BIT) != X && notClue(packed & 0x7FL) && notClue((packed >>> 7) & 0x7FL); }
|
||||||
void forEachSlot(SlotVisitor visitor) {
|
void forEachSlot(SlotVisitor visitor) {
|
||||||
for (var l = lo; l != X; l &= l - 1) processSlot(this, visitor, Long.numberOfTrailingZeros(l));
|
for (var l = lo; l != X; l &= l - 1) processSlot(this, visitor, Long.numberOfTrailingZeros(l));
|
||||||
for (var h = hi; h != X; h &= h - 1) processSlot(this, visitor, 64 | Long.numberOfTrailingZeros(h));
|
for (var h = hi; h != X; h &= h - 1) processSlot(this, visitor, 64 + Long.numberOfTrailingZeros(h));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,17 +354,19 @@ public record SwedishGenerator(Rng rng) {
|
|||||||
|
|
||||||
static record Slot(int key, long packedPos) {
|
static record Slot(int key, long packedPos) {
|
||||||
|
|
||||||
|
static final int BIT_FOR_DIR = 3;
|
||||||
static Slot from(int key, long packedPos, int len) { return new Slot(key, packedPos | ((long) len << 56)); }
|
static Slot from(int key, long packedPos, int len) { return new Slot(key, packedPos | ((long) len << 56)); }
|
||||||
void undoPlace(Grid grid, int mask) { for (int i = 0, len = len(); i < len; i++) if ((mask & (1L << i)) != 0) grid.clear(pos(i)); }
|
void undoPlace(Grid grid, int mask) { for (int i = 0, len = len(); i < len; i++) if ((mask & (1L << i)) != 0) grid.clearletter(pos(i)); }
|
||||||
public int len() { return (int) (packedPos >>> 56); }
|
public int len() { return (int) (packedPos >>> 56); }
|
||||||
public int clueR() { return Grid.r((key >>> 4)); }
|
public int clueR() { return Grid.r((key >>> BIT_FOR_DIR)); }
|
||||||
public int clueIndex() { return key >>> 4; }
|
public int clueIndex() { return key >>> BIT_FOR_DIR; }
|
||||||
public int clueC() { return Grid.c((key >>> 4)); }
|
public int clueC() { return Grid.c((key >>> BIT_FOR_DIR)); }
|
||||||
public int dir() { return key & 15; }
|
public int dir() { return key & 7; }
|
||||||
public boolean horiz() { return horiz(key); }
|
public boolean horiz() { return horiz(key); }
|
||||||
public int pos(int i) { return offset(packedPos, i); }
|
public int pos(int i) { return offset(packedPos, i); }
|
||||||
public static boolean horiz(int key) { return (key & 1) == 0/*((key & 15) & 1) == 0*/; }
|
public static boolean horiz(int key) { return (key & 1) == 0/*((key & 15) & 1) == 0*/; }
|
||||||
public static int offset(long packedPos, int i) { return (int) ((packedPos >> (i * 7)) & 127); }
|
public static int offset(long packedPos, int i) { return (int) ((packedPos >> (i * 7)) & 127); }
|
||||||
|
public static int packSlotDir(int idx, int d) { return (idx << BIT_FOR_DIR) | d; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void processSlot(Grid grid, SlotVisitor visitor, int idx) {
|
private static void processSlot(Grid grid, SlotVisitor visitor, int idx) {
|
||||||
@@ -376,7 +380,7 @@ public record SwedishGenerator(Rng rng) {
|
|||||||
packedPos |= iidx << offset;
|
packedPos |= iidx << offset;
|
||||||
}
|
}
|
||||||
if (k > 0) {
|
if (k > 0) {
|
||||||
visitor.visit((idx << 4) | d, packedPos, k);
|
visitor.visit(Slot.packSlotDir(idx, d), packedPos, k);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,38 +414,39 @@ public record SwedishGenerator(Rng rng) {
|
|||||||
var covV = ctx.covV;
|
var covV = ctx.covV;
|
||||||
Arrays.fill(covH, 0, SIZE, 0);
|
Arrays.fill(covH, 0, SIZE, 0);
|
||||||
Arrays.fill(covV, 0, SIZE, 0);
|
Arrays.fill(covV, 0, SIZE, 0);
|
||||||
int clueIdx;
|
long lo_cl = grid.lo, hi_cl = grid.hi;
|
||||||
long lo_cl = grid.lo, hi_cl = grid.hi;
|
long penalty = (((long) Math.abs(grid.clueCount() - TARGET_CLUES)) << 3);
|
||||||
long penalty = 0L;
|
boolean hasSlots = false;
|
||||||
|
|
||||||
for (int i = 0; i < 65; i += 64) {
|
for (int i = 0; i < 65; i += 64) {
|
||||||
for (long bits = (i == 0 ? lo_cl : hi_cl); bits != X; bits &= bits - 1) {
|
for (long bits = (i == 0 ? lo_cl : hi_cl); bits != X; bits &= bits - 1) {
|
||||||
clueIdx = i + Long.numberOfTrailingZeros(bits);
|
int clueIdx = i + Long.numberOfTrailingZeros(bits);
|
||||||
var d = grid.digitAt(clueIdx);
|
var d = grid.digitAt(clueIdx);
|
||||||
var nbrs16 = OFFSETS[d];
|
var nbrs16 = OFFSETS[d];
|
||||||
long packed = nbrs16.path()[clueIdx];
|
long packed = nbrs16.path()[clueIdx];
|
||||||
int n = (int) (packed >>> 56) * 7, k, idx;
|
int n = (int) (packed >>> 56) * 7, k, idx;
|
||||||
var horiz = Slot.horiz(d) ? covH : covV;
|
var horiz = Slot.horiz(d) ? covH : covV;
|
||||||
for (k = 0; k < n && k < MAX_WORD_LENGTH7; k += 7) {
|
for (k = 0; k < n && k < MAX_WORD_LENGTH7; k += 7) {
|
||||||
idx = (int) ((packed >>> (k)) & 0x7F);
|
idx = (int) ((packed >>> (k)) & 0x7F);
|
||||||
if (grid.isClue(idx)) break;
|
if (grid.isClue(idx)) break;
|
||||||
horiz[idx] += 1;
|
horiz[idx] += 1;
|
||||||
}
|
}
|
||||||
if (k > 0) {
|
if (k > 0) {
|
||||||
|
hasSlots = true;
|
||||||
if (k < MIN_LEN7) penalty += 8000;
|
if (k < MIN_LEN7) penalty += 8000;
|
||||||
penalty |= 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((penalty & 1) == 0) return 1_000_000_000L;
|
if (!hasSlots) return 1_000_000_000L;
|
||||||
penalty = (penalty & ~3L) + (((long) Math.abs(grid.clueCount() - TARGET_CLUES)) << 3);
|
|
||||||
var seen = ctx.seen;
|
var seen = ctx.seen;
|
||||||
var stack = ctx.stack;
|
var stack = ctx.stack;
|
||||||
seen.clear();
|
seen.clear();
|
||||||
|
|
||||||
for (int i = 0; i < 65; i += 64) {
|
for (int i = 0; i < 65; i += 64) {
|
||||||
for (long bits = (i == 0 ? lo_cl : hi_cl); bits != X; bits &= bits - 1) {
|
for (long bits = (i == 0 ? lo_cl : hi_cl); bits != X; bits &= bits - 1) {
|
||||||
clueIdx = i + Long.numberOfTrailingZeros(bits);
|
int clueIdx = i + Long.numberOfTrailingZeros(bits);
|
||||||
if (seen.get(clueIdx)) continue;
|
if (seen.get(clueIdx)) continue;
|
||||||
int size = 0;
|
int size = 0;
|
||||||
stack[0] = clueIdx;
|
stack[0] = clueIdx;
|
||||||
@@ -462,8 +467,8 @@ public record SwedishGenerator(Rng rng) {
|
|||||||
for (int i = 0; i < 65; i += 64) {
|
for (int i = 0; i < 65; i += 64) {
|
||||||
long bits = (i == 0 ? ~lo_cl : (~hi_cl & 0xFFL));
|
long bits = (i == 0 ? ~lo_cl : (~hi_cl & 0xFFL));
|
||||||
for (; bits != X; bits &= bits - 1) {
|
for (; bits != X; bits &= bits - 1) {
|
||||||
clueIdx = i + Long.numberOfTrailingZeros(bits);
|
int clueIdx = i + Long.numberOfTrailingZeros(bits);
|
||||||
var rci = IT[clueIdx];
|
var rci = IT[clueIdx];
|
||||||
if ((4 - rci.nbrCount()) + Long.bitCount(rci.n1() & lo_cl) + Long.bitCount(rci.n2() & hi_cl) >= 3) penalty += 400;
|
if ((4 - rci.nbrCount()) + Long.bitCount(rci.n1() & lo_cl) + Long.bitCount(rci.n2() & hi_cl) >= 3) penalty += 400;
|
||||||
var h = covH[clueIdx];
|
var h = covH[clueIdx];
|
||||||
var v = covV[clueIdx];
|
var v = covV[clueIdx];
|
||||||
@@ -601,9 +606,14 @@ public record SwedishGenerator(Rng rng) {
|
|||||||
|
|
||||||
if (Main.VERBOSE && (gen & 15) == 15) System.out.println(" gen " + gen + "/" + gens + " bestFitness=" + pop.get(0).fit());
|
if (Main.VERBOSE && (gen & 15) == 15) System.out.println(" gen " + gen + "/" + gens + " bestFitness=" + pop.get(0).fit());
|
||||||
}
|
}
|
||||||
|
GridAndFit best = pop.get(0);
|
||||||
pop.sort(Comparator.comparingLong(GridAndFit::fit));
|
for (int i = 1; i < pop.size(); i++) {
|
||||||
return pop.get(0).grid;
|
var x = pop.get(i);
|
||||||
|
if (x.fit() < best.fit()) best = x;
|
||||||
|
}
|
||||||
|
//pop.sort(Comparator.comparingLong(GridAndFit::fit));
|
||||||
|
//return pop.get(0).grid;
|
||||||
|
return best.grid;
|
||||||
}
|
}
|
||||||
static void patternForSlot(Grid grid, Slot s, byte[] pat) {
|
static void patternForSlot(Grid grid, Slot s, byte[] pat) {
|
||||||
byte ch;
|
byte ch;
|
||||||
@@ -631,7 +641,7 @@ public record SwedishGenerator(Rng rng) {
|
|||||||
} else if (cur != ch) {
|
} else if (cur != ch) {
|
||||||
for (var j = 0; j < i; j++) {
|
for (var j = 0; j < i; j++) {
|
||||||
if ((mask & (1 << j)) != 0) {
|
if ((mask & (1 << j)) != 0) {
|
||||||
grid.clear(s.pos(j));
|
grid.clearletter(s.pos(j));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -655,7 +665,8 @@ public record SwedishGenerator(Rng rng) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (listCount == 0) {
|
if (listCount == 0) {
|
||||||
return CANDIDATES[entry.words.length];
|
//return CANDIDATES[entry.words.length];
|
||||||
|
return new CandidateInfo(null, entry.words.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort constraints by size to optimize intersection
|
// Sort constraints by size to optimize intersection
|
||||||
@@ -693,9 +704,10 @@ public record SwedishGenerator(Rng rng) {
|
|||||||
val multiThreaded = Thread.currentThread().getName().contains("pool");
|
val multiThreaded = Thread.currentThread().getName().contains("pool");
|
||||||
val grid = mask.deepCopyGrid();
|
val grid = mask.deepCopyGrid();
|
||||||
val used = new Bit1029();
|
val used = new Bit1029();
|
||||||
val assigned = new HashMap<Integer, Lemma>();
|
// val assigned = new HashMap<Integer, Lemma>();
|
||||||
val ctx = CTX.get();
|
Lemma[] assigned = new Lemma[1024];
|
||||||
val count = ctx.cellCount;
|
val ctx = CTX.get();
|
||||||
|
val count = ctx.cellCount;
|
||||||
Arrays.fill(count, 0, SIZE, 0);
|
Arrays.fill(count, 0, SIZE, 0);
|
||||||
|
|
||||||
val slots = extractSlots(grid);
|
val slots = extractSlots(grid);
|
||||||
@@ -716,8 +728,10 @@ public record SwedishGenerator(Rng rng) {
|
|||||||
var now = System.currentTimeMillis();
|
var now = System.currentTimeMillis();
|
||||||
if ((now - lastLog) < LOG_EVERY_MS) return;
|
if ((now - lastLog) < LOG_EVERY_MS) return;
|
||||||
lastLog = (now);
|
lastLog = (now);
|
||||||
|
var done = 0;
|
||||||
var done = assigned.size();
|
for (var lemma : assigned) {
|
||||||
|
if (lemma != null) done++;
|
||||||
|
}
|
||||||
var pct = (TOTAL == 0) ? 100 : (int) Math.floor((done / (double) TOTAL) * 100);
|
var pct = (TOTAL == 0) ? 100 : (int) Math.floor((done / (double) TOTAL) * 100);
|
||||||
var filled = Math.min(BAR_LEN, (int) Math.floor((pct / 100.0) * BAR_LEN));
|
var filled = Math.min(BAR_LEN, (int) Math.floor((pct / 100.0) * BAR_LEN));
|
||||||
var bar = "[" + "#".repeat(filled) + "-".repeat(BAR_LEN - filled) + "]";
|
var bar = "[" + "#".repeat(filled) + "-".repeat(BAR_LEN - filled) + "]";
|
||||||
@@ -737,7 +751,7 @@ public record SwedishGenerator(Rng rng) {
|
|||||||
int bestScore = -1;
|
int bestScore = -1;
|
||||||
for (int i = 0, n = slots.size(); i < n; i++) {
|
for (int i = 0, n = slots.size(); i < n; i++) {
|
||||||
var s = slots.get(i);
|
var s = slots.get(i);
|
||||||
if (assigned.containsKey(s.key())) continue;
|
if (assigned[s.key()] != null) continue;
|
||||||
var entry = dictIndex[s.len()];
|
var entry = dictIndex[s.len()];
|
||||||
if (entry == null) return PICK_NOT_DONE;
|
if (entry == null) return PICK_NOT_DONE;
|
||||||
patternForSlot(grid, s, ctx.pattern);
|
patternForSlot(grid, s, ctx.pattern);
|
||||||
@@ -798,11 +812,11 @@ public record SwedishGenerator(Rng rng) {
|
|||||||
if (!placeWord(grid, s, w, ctx.undo, depth)) continue;
|
if (!placeWord(grid, s, w, ctx.undo, depth)) continue;
|
||||||
|
|
||||||
used.set(w.index);
|
used.set(w.index);
|
||||||
assigned.put(k, w);
|
assigned[k] = w;
|
||||||
|
|
||||||
if (backtrack(depth + 1)) return true;
|
if (backtrack(depth + 1)) return true;
|
||||||
|
|
||||||
assigned.remove(k);
|
assigned[k] = null;
|
||||||
used.clear(w.index);
|
used.clear(w.index);
|
||||||
s.undoPlace(grid, ctx.undo[depth]);
|
s.undoPlace(grid, ctx.undo[depth]);
|
||||||
}
|
}
|
||||||
@@ -827,11 +841,11 @@ public record SwedishGenerator(Rng rng) {
|
|||||||
if (!placeWord(grid, s, w, ctx.undo, depth)) continue;
|
if (!placeWord(grid, s, w, ctx.undo, depth)) continue;
|
||||||
|
|
||||||
used.set(w.index);
|
used.set(w.index);
|
||||||
assigned.put(k, w);
|
assigned[k] = w;
|
||||||
|
|
||||||
if (backtrack(depth + 1)) return true;
|
if (backtrack(depth + 1)) return true;
|
||||||
|
|
||||||
assigned.remove(k);
|
assigned[k] = null;
|
||||||
used.clear(w.index);
|
used.clear(w.index);
|
||||||
s.undoPlace(grid, ctx.undo[depth]);
|
s.undoPlace(grid, ctx.undo[depth]);
|
||||||
}
|
}
|
||||||
@@ -852,14 +866,20 @@ public record SwedishGenerator(Rng rng) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stats.seconds = (System.currentTimeMillis() - t0) / 1000.0;
|
stats.seconds = (System.currentTimeMillis() - t0) / 1000.0;
|
||||||
var res = new FillResult(ok, new Gridded(grid), assigned, stats);
|
Map<Integer, Lemma> lemmaMap = new HashMap<>();
|
||||||
|
for (var i = 0; i < assigned.length; i++) {
|
||||||
|
if (assigned[i] != null) {
|
||||||
|
lemmaMap.put(i, assigned[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var res = new FillResult(ok, new Gridded(grid), lemmaMap, stats);
|
||||||
|
|
||||||
// print a final progress line
|
// print a final progress line
|
||||||
if (Main.VERBOSE && !multiThreaded) {
|
if (Main.VERBOSE && !multiThreaded) {
|
||||||
System.out.println(
|
System.out.println(
|
||||||
String.format(Locale.ROOT,
|
String.format(Locale.ROOT,
|
||||||
"[######################] %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %.1fs",
|
"[######################] %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %.1fs",
|
||||||
assigned.size(), TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, stats.seconds
|
lemmaMap.size(), TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, stats.seconds
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ public class MainTest {
|
|||||||
// Regression baseline for seed search starting at 12347, pop 4, gens 20
|
// Regression baseline for seed search starting at 12347, pop 4, gens 20
|
||||||
Assertions.assertEquals(12348, foundSeed, "Found seed changed");
|
Assertions.assertEquals(12348, foundSeed, "Found seed changed");
|
||||||
Assertions.assertEquals(22, res.filled().clueMap().size(), "Number of assigned words changed");
|
Assertions.assertEquals(22, res.filled().clueMap().size(), "Number of assigned words changed");
|
||||||
Assertions.assertEquals(Lemma.pack(new byte[]{ 'I', 'E', 'M', 'A', 'N', 'D', 'S' }), res.filled().clueMap().get(515).word());
|
Assertions.assertEquals(Lemma.pack(new byte[]{ 'I', 'E', 'M', 'A', 'N', 'D', 'S' }), res.filled().clueMap().get(259).word());
|
||||||
Assertions.assertEquals(648985643903632391L, res.filled().grid().grid().lo);
|
Assertions.assertEquals(648985643903632391L, res.filled().grid().grid().lo);
|
||||||
Assertions.assertEquals(140L, res.filled().grid().grid().hi);
|
Assertions.assertEquals(140L, res.filled().grid().grid().hi);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,8 +135,12 @@ public class SwedishGeneratorTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSlot() {
|
void testSlot() {
|
||||||
|
System.out.println("[DEBUG_LOG] Slot.BIT_FOR_DIR = " + Slot.BIT_FOR_DIR);
|
||||||
// key = (r << 8) | (c << 4) | d
|
// key = (r << 8) | (c << 4) | d
|
||||||
var key = (Grid.offset(2, 3) << 4) | 5;
|
var offset = Grid.offset(2, 3);
|
||||||
|
System.out.println("[DEBUG_LOG] Grid.offset(2, 3) = " + offset);
|
||||||
|
var key = (offset << Slot.BIT_FOR_DIR) | 5;
|
||||||
|
System.out.println("[DEBUG_LOG] key = " + key);
|
||||||
long packedPos = 0;
|
long packedPos = 0;
|
||||||
// pos 0: (2, 5)
|
// pos 0: (2, 5)
|
||||||
packedPos |= Grid.offset(2, 5);
|
packedPos |= Grid.offset(2, 5);
|
||||||
@@ -146,6 +150,7 @@ public class SwedishGeneratorTest {
|
|||||||
packedPos |= (long) Grid.offset(4, 5) << 14;
|
packedPos |= (long) Grid.offset(4, 5) << 14;
|
||||||
|
|
||||||
var s = Slot.from(key, packedPos, 3);
|
var s = Slot.from(key, packedPos, 3);
|
||||||
|
System.out.println("[DEBUG_LOG] s.dir() = " + s.dir());
|
||||||
assertEquals(2, s.clueR());
|
assertEquals(2, s.clueR());
|
||||||
assertEquals(3, s.clueC());
|
assertEquals(3, s.clueC());
|
||||||
assertEquals(5, s.dir());
|
assertEquals(5, s.dir());
|
||||||
|
|||||||
Reference in New Issue
Block a user