#include #include #include #include #include #include #include #include #include #include #include #include #if defined(_MSC_VER) #include #endif namespace { constexpr uint64_t kJavaMultiplier = 0x5DEECE66DULL; constexpr uint64_t kJavaAddend = 0xBULL; constexpr uint64_t kJavaMask = (1ULL << 48) - 1; constexpr uint64_t kSlimeXor = 987234911ULL; struct Args { int64_t startSeed = 0; int64_t endSeed = 1'000'000; int32_t n = 3; int32_t required = -1; int32_t radius = 100; int32_t threads = std::max(1u, std::thread::hardware_concurrency()); int64_t seedBatch = 256; int32_t maxResults = 32; bool allLocations = false; }; struct Hit { int64_t seed; int32_t x; int32_t z; int32_t count; }; [[noreturn]] void usage(int exitCode = 1) { std::cerr << "Usage: slime_cpu [args...]\n" << " -s First world seed, inclusive (default: 0)\n" << " -e Last world seed, exclusive (default: 1000000)\n" << " -n N for the NxN window (default: 3)\n" << " -d Required slime chunks in the window (default: N*N)\n" << " -r Search start positions from -r..r on both axes (default: 100)\n" << " -p CPU worker threads (default: hardware concurrency)\n" << " --batch Seeds fetched per worker batch (default: 256)\n" << " --max-results Print at most n hits, while still counting all hits (default: 32)\n" << " --all-locations Count every matching location, not just the first per seed\n" << " --help Show this help\n"; std::exit(exitCode); } const char* requireValue(int argc, char** argv, int& i) { if (i + 1 >= argc) { usage(); } return argv[++i]; } Args parseArgs(int argc, char** argv) { Args args; for (int i = 1; i < argc; ++i) { const std::string arg = argv[i]; if (arg == "--help") { usage(0); } else if (arg == "-s") { args.startSeed = std::stoll(requireValue(argc, argv, i)); } else if (arg == "-e") { args.endSeed = std::stoll(requireValue(argc, argv, i)); } else if (arg == "-n") { args.n = std::stoi(requireValue(argc, argv, i)); } else if (arg == "-d") { args.required = std::stoi(requireValue(argc, argv, i)); } else if (arg == "-r") { args.radius = std::stoi(requireValue(argc, argv, i)); } else if (arg == "-p") { args.threads = std::stoi(requireValue(argc, argv, i)); } else if (arg == "--batch") { args.seedBatch = std::stoll(requireValue(argc, argv, i)); } else if (arg == "--max-results") { args.maxResults = std::stoi(requireValue(argc, argv, i)); } else if (arg == "--all-locations") { args.allLocations = true; } else { usage(); } } const int64_t cells = int64_t(args.n) * args.n; if (args.required < 0) { args.required = static_cast(cells); } if (args.startSeed >= args.endSeed || args.n <= 0 || args.radius < 0 || args.threads <= 0 || args.seedBatch <= 0 || args.maxResults < 0 || cells > INT32_MAX || args.required <= 0 || args.required > cells) { usage(); } return args; } uint64_t javaIntAsLongBits(uint32_t value) { return (value & 0x80000000u) ? (0xFFFFFFFF00000000ULL | value) : uint64_t(value); } uint64_t coordTerm(int32_t chunkX, int32_t chunkZ) { const uint32_t x = static_cast(chunkX); const uint32_t z = static_cast(chunkZ); uint64_t term = 0; term += javaIntAsLongBits(x * x * 4987142u); term += javaIntAsLongBits(x * 5947611u); term += javaIntAsLongBits(z * z) * 4392871ULL; term += javaIntAsLongBits(z * 389711u); return term; } bool isSlimeFromTerm(uint64_t worldSeedBits, uint64_t term) { uint64_t randomSeed = ((worldSeedBits + term) ^ kSlimeXor ^ kJavaMultiplier) & kJavaMask; for (;;) { randomSeed = (randomSeed * kJavaMultiplier + kJavaAddend) & kJavaMask; const uint32_t bits = static_cast(randomSeed >> 17); const uint32_t value = bits % 10u; if (bits - value + 9u <= 0x7FFFFFFFu) { return value == 0; } } } int trailingZeroes(uint64_t value) { #if defined(_MSC_VER) unsigned long index = 0; _BitScanForward64(&index, value); return static_cast(index); #else return __builtin_ctzll(value); #endif } uint64_t lowBitsMask(int32_t bits) { const int32_t rem = bits & 63; return rem == 0 ? ~0ULL : ((1ULL << rem) - 1ULL); } struct SearchSpace { int32_t n; int32_t radius; int32_t window; int32_t chunkWidth; int32_t chunkRows; int32_t chunkWords; int32_t windowWords; uint64_t lastWindowMask; std::vector terms; SearchSpace(int32_t radiusIn, int32_t nIn) : n(nIn), radius(radiusIn), window(radiusIn * 2 + 1), chunkWidth(window + nIn - 1), chunkRows(window + nIn - 1), chunkWords((chunkWidth + 63) / 64), windowWords((window + 63) / 64), lastWindowMask(lowBitsMask(window)), terms(size_t(chunkWidth) * chunkRows) { for (int32_t z = 0; z < chunkRows; ++z) { for (int32_t x = 0; x < chunkWidth; ++x) { terms[size_t(z) * chunkWidth + x] = coordTerm(x - radius, z - radius); } } } }; struct Workspace { std::vector rows; std::vector horizontal; std::vector rowValues; std::vector rowRing; std::vector vertical; explicit Workspace(const SearchSpace& space) : rows(size_t(space.chunkRows) * space.chunkWords), horizontal(size_t(space.chunkRows) * space.chunkWords), rowValues(space.chunkWidth), rowRing(size_t(space.n) * space.window), vertical(space.window) {} }; struct ResultSink { std::mutex outputMutex; std::atomic hits{0}; int32_t maxResults; explicit ResultSink(int32_t maxResultsIn) : maxResults(maxResultsIn) {} void record(const Hit& hit) { const uint64_t index = hits.fetch_add(1, std::memory_order_relaxed); if (index >= static_cast(maxResults)) { return; } std::lock_guard lock(outputMutex); std::cout << "hit seed=" << hit.seed << " x=" << hit.x << " z=" << hit.z << " count=" << hit.count << '\n'; } }; uint64_t shiftedWord(const uint64_t* row, int32_t words, int32_t word, int32_t shift) { const int32_t whole = shift >> 6; const int32_t bits = shift & 63; const int32_t src = word + whole; const uint64_t lo = src < words ? row[src] : 0ULL; if (bits == 0) { return lo; } const uint64_t hi = src + 1 < words ? row[src + 1] : 0ULL; return (lo >> bits) | (hi << (64 - bits)); } uint64_t findFullSquare( int64_t seed, const SearchSpace& space, Workspace& workspace, ResultSink& sink, bool allLocations ) { const uint64_t seedBits = static_cast(seed); std::fill(workspace.rows.begin(), workspace.rows.end(), 0ULL); for (int32_t z = 0; z < space.chunkRows; ++z) { uint64_t* row = workspace.rows.data() + size_t(z) * space.chunkWords; const uint64_t* termRow = space.terms.data() + size_t(z) * space.chunkWidth; for (int32_t x = 0; x < space.chunkWidth; ++x) { if (isSlimeFromTerm(seedBits, termRow[x])) { row[x >> 6] |= 1ULL << (x & 63); } } } for (int32_t z = 0; z < space.chunkRows; ++z) { const uint64_t* row = workspace.rows.data() + size_t(z) * space.chunkWords; uint64_t* out = workspace.horizontal.data() + size_t(z) * space.chunkWords; for (int32_t word = 0; word < space.chunkWords; ++word) { uint64_t mask = row[word]; for (int32_t shift = 1; shift < space.n; ++shift) { mask &= shiftedWord(row, space.chunkWords, word, shift); } out[word] = mask; } } uint64_t found = 0; for (int32_t z = 0; z < space.window; ++z) { for (int32_t word = 0; word < space.windowWords; ++word) { uint64_t mask = ~0ULL; for (int32_t dz = 0; dz < space.n; ++dz) { mask &= workspace.horizontal[(size_t(z + dz) * space.chunkWords) + word]; } if (word == space.windowWords - 1) { mask &= space.lastWindowMask; } while (mask != 0) { const int32_t bit = trailingZeroes(mask); const int32_t x = word * 64 + bit; sink.record({seed, x - space.radius, z - space.radius, space.n * space.n}); ++found; if (!allLocations) { return found; } mask &= mask - 1; } } } return found; } uint64_t findAtLeastCount( int64_t seed, int32_t required, const SearchSpace& space, Workspace& workspace, ResultSink& sink, bool allLocations ) { const uint64_t seedBits = static_cast(seed); std::fill(workspace.vertical.begin(), workspace.vertical.end(), 0); uint64_t found = 0; for (int32_t z = 0; z < space.chunkRows; ++z) { const uint64_t* termRow = space.terms.data() + size_t(z) * space.chunkWidth; for (int32_t x = 0; x < space.chunkWidth; ++x) { workspace.rowValues[x] = isSlimeFromTerm(seedBits, termRow[x]) ? 1 : 0; } int32_t* ringRow = workspace.rowRing.data() + size_t(z % space.n) * space.window; if (z >= space.n) { for (int32_t x = 0; x < space.window; ++x) { workspace.vertical[x] -= ringRow[x]; } } int32_t rowSum = 0; for (int32_t x = 0; x < space.n; ++x) { rowSum += workspace.rowValues[x]; } for (int32_t x = 0; x < space.window; ++x) { if (x != 0) { rowSum += workspace.rowValues[x + space.n - 1]; rowSum -= workspace.rowValues[x - 1]; } ringRow[x] = rowSum; workspace.vertical[x] += rowSum; } if (z + 1 < space.n) { continue; } const int32_t startZ = z - space.n + 1; for (int32_t x = 0; x < space.window; ++x) { const int32_t count = workspace.vertical[x]; if (count >= required) { sink.record({seed, x - space.radius, startZ - space.radius, count}); ++found; if (!allLocations) { return found; } } } } return found; } void worker( const Args& args, const SearchSpace& space, std::atomic& nextSeed, ResultSink& sink ) { Workspace workspace(space); for (;;) { const int64_t begin = nextSeed.fetch_add(args.seedBatch, std::memory_order_relaxed); if (begin >= args.endSeed) { return; } const int64_t end = std::min(args.endSeed, begin + args.seedBatch); for (int64_t seed = begin; seed < end; ++seed) { if (args.required == args.n * args.n) { findFullSquare(seed, space, workspace, sink, args.allLocations); } else { findAtLeastCount(seed, args.required, space, workspace, sink, args.allLocations); } } } } } // namespace int main(int argc, char** argv) { try { const Args args = parseArgs(argc, argv); const SearchSpace space(args.radius, args.n); ResultSink sink(args.maxResults); std::atomic nextSeed(args.startSeed); std::cout << "CPU search: seeds [" << args.startSeed << ", " << args.endSeed << "), N=" << args.n << ", required=" << args.required << ", radius=" << args.radius << ", threads=" << args.threads << (args.allLocations ? ", all locations\n" : ", first location per seed\n"); const auto started = std::chrono::steady_clock::now(); std::vector workers; workers.reserve(args.threads); for (int32_t i = 0; i < args.threads; ++i) { workers.emplace_back(worker, std::cref(args), std::cref(space), std::ref(nextSeed), std::ref(sink)); } for (std::thread& thread : workers) { thread.join(); } const auto finished = std::chrono::steady_clock::now(); const std::chrono::duration elapsed = finished - started; std::cout << "checked " << (args.endSeed - args.startSeed) << " seeds, hits=" << sink.hits.load(std::memory_order_relaxed) << ", elapsed=" << elapsed.count() << "s\n"; return 0; } catch (const std::exception& ex) { std::cerr << "error: " << ex.what() << '\n'; return 1; } }