- tests/test.js: 148 -> 206 проверок (разделы 23 wrap/torus, 24 зоны, 25 бюджет, 26 размеры, 27 сейв v2/миграция, 28 рендер/resize 300x300, 29 HUD 381-400px); resetGlobals сбрасывает tool/reason/winner; sleep заменён на waitCond/settleViewport. - tools/old-ref/: независимый torus-эталон (nextStepCleanWrap, CLI --torus, gen-torus.js, reference-torus.json); diff-our.js сверяет stepN при setWrap(true). - index.html: медиазапрос HUD 380->400px (убрана микро-обрезка 381-388px); инлайн-комментарий про согласованность useWrap/setWrap. - AGENTS.md: полный список контракта дополнен; useWrap-заметка уточнена; покрытие приведено в соответствие. README: скорость — константа в коде, не UI. - .gitignore: tools/old-ref/.tmp-*.txt. - Прогоны: check: OK; суита 206/206 (3 повтора); паритет clean 273/273 + torus 273/273. - Ревью-гейт R2-3 (Oracle): PASS; minor/nit закрыты.
496 lines
22 KiB
C++
496 lines
22 KiB
C++
// Автономный harness: ТОЧНЫЙ порт Life::nextStep() из old/life/life.cpp
|
||
// (Qt-контейнеры заменены на std::vector<unsigned char> с той же семантикой).
|
||
//
|
||
// Оси: field[i][j], i in [0,wid) = X (столбец), j in [0,hei) = Y (строка).
|
||
// Подтверждено painter.cpp: drawRect(i*sclx, j*scly) и mouseEvent: field[x*wid][y*hei].
|
||
// ASCII: строк текста = hei (Y), символов в строке = wid (X); ascii[j][i] = field[i][j].
|
||
//
|
||
// Сборка: g++ -std=c++17 -O2 -o old_ref old_ref.cpp
|
||
#include <bits/stdc++.h>
|
||
using namespace std;
|
||
|
||
struct Life {
|
||
int wid = 0, hei = 0;
|
||
vector<vector<unsigned char>> field, tmp;
|
||
|
||
Life() {}
|
||
Life(int w, int h) { setField(w, h); }
|
||
|
||
void setField(int w, int h) {
|
||
wid = w; hei = h;
|
||
field.assign(wid, vector<unsigned char>(hei, 0));
|
||
tmp = field;
|
||
}
|
||
|
||
// ---- ТОЧНЫЙ порт Life::nextStep() (баг parent[] сохранён как есть) ----
|
||
void nextStep() {
|
||
int num;
|
||
int parent[8];
|
||
for (int k = 0; k < 8; k++) parent[k] = 0;
|
||
for (int i = 0; i < wid; i++) {
|
||
for (int j = 0; j < hei; j++) {
|
||
num = 0;
|
||
if (i > 0) if (field[i-1][j] > 0) num++, parent[num-1] = field[i-1][j];
|
||
if (j > 0) if (field[i][j-1] > 0) num++, parent[num-1] = field[i][j-1];
|
||
if (i < wid-1) if (field[i+1][j] > 0) num++, parent[num-1] = field[i+1][j];
|
||
if (j < hei-1) if (field[i][j+1] > 0) num++, parent[num-1] = field[i][j+1];
|
||
if (i > 0 && j > 0) if (field[i-1][j-1] > 0) num++, parent[num-1] = field[i-1][j-1];
|
||
if (i > 0 && j < hei-1) if (field[i-1][j+1] > 0) num++, parent[num-1] = field[i-1][j+1];
|
||
if (i < wid-1 && j > 0) if (field[i+1][j-1] > 0) num++, parent[num-1] = field[i+1][j-1];
|
||
if (i < wid-1 && j < hei-1) if (field[i+1][j+1] > 0) num++, parent[num-1] = field[i+1][j+1];
|
||
if (field[i][j] == 0) {
|
||
if (num == 3) {
|
||
int firstp = 0, secondp = 0, p = 0;
|
||
for (int n = 0; n < 8; n++) {
|
||
if (parent[n] > 0) {
|
||
if (firstp == 0) firstp = parent[n];
|
||
else {
|
||
if (secondp == 0) secondp = parent[n];
|
||
else {
|
||
if (parent[n] == secondp) p = secondp;
|
||
else p = firstp;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
tmp[i][j] = p;
|
||
}
|
||
} else {
|
||
if (num < 2 || num > 3) tmp[i][j] = 0;
|
||
else tmp[i][j] = field[i][j];
|
||
}
|
||
}
|
||
}
|
||
field = tmp;
|
||
}
|
||
|
||
// ---- ЭТАЛОН "чистое большинство" НА ТОРЕ (wrap) ----
|
||
// Соседи за краем берутся с противоположной стороны. Это НОВАЯ опция новой игры
|
||
// (в прототипе old заворотов не было), но правило рождения — то же чистое
|
||
// «большинство из 3 + tie-break наименьший id». Нужен для независимой проверки wrap.
|
||
void nextStepCleanWrap() {
|
||
vector<vector<unsigned char>> out = field;
|
||
for (int i = 0; i < wid; i++) {
|
||
for (int j = 0; j < hei; j++) {
|
||
int cnt[4] = {0,0,0,0};
|
||
int num = 0;
|
||
for (int di = -1; di <= 1; di++) for (int dj = -1; dj <= 1; dj++) {
|
||
if (!di && !dj) continue;
|
||
int x = ((i + di) % wid + wid) % wid;
|
||
int y = ((j + dj) % hei + hei) % hei;
|
||
if (field[x][y] > 0) { num++; if (field[x][y] < 4) cnt[field[x][y]]++; }
|
||
}
|
||
if (field[i][j] == 0) {
|
||
if (num == 3) {
|
||
int best = -1;
|
||
for (int c = 1; c <= 3; c++) if (cnt[c] >= 2) { best = c; break; }
|
||
if (best < 0) for (int c = 1; c <= 3; c++) if (cnt[c] == 1) { best = c; break; }
|
||
out[i][j] = (unsigned char)(best < 0 ? 0 : best);
|
||
} else out[i][j] = 0;
|
||
} else {
|
||
if (num < 2 || num > 3) out[i][j] = 0;
|
||
else out[i][j] = field[i][j];
|
||
}
|
||
}
|
||
}
|
||
field = out;
|
||
}
|
||
|
||
// Вариант ровно того же кода, но обход клеток j внешний, i внутренний.
|
||
// Нужен только для проверки зависимости от ПОРЯДКА ОБХОДА.
|
||
void nextStepOrderSwap() {
|
||
int num;
|
||
int parent[8];
|
||
for (int k = 0; k < 8; k++) parent[k] = 0;
|
||
for (int j = 0; j < hei; j++) {
|
||
for (int i = 0; i < wid; i++) {
|
||
num = 0;
|
||
if (i > 0) if (field[i-1][j] > 0) num++, parent[num-1] = field[i-1][j];
|
||
if (j > 0) if (field[i][j-1] > 0) num++, parent[num-1] = field[i][j-1];
|
||
if (i < wid-1) if (field[i+1][j] > 0) num++, parent[num-1] = field[i+1][j];
|
||
if (j < hei-1) if (field[i][j+1] > 0) num++, parent[num-1] = field[i][j+1];
|
||
if (i > 0 && j > 0) if (field[i-1][j-1] > 0) num++, parent[num-1] = field[i-1][j-1];
|
||
if (i > 0 && j < hei-1) if (field[i-1][j+1] > 0) num++, parent[num-1] = field[i-1][j+1];
|
||
if (i < wid-1 && j > 0) if (field[i+1][j-1] > 0) num++, parent[num-1] = field[i+1][j-1];
|
||
if (i < wid-1 && j < hei-1) if (field[i+1][j+1] > 0) num++, parent[num-1] = field[i+1][j+1];
|
||
if (field[i][j] == 0) {
|
||
if (num == 3) {
|
||
int firstp = 0, secondp = 0, p = 0;
|
||
for (int n = 0; n < 8; n++) {
|
||
if (parent[n] > 0) {
|
||
if (firstp == 0) firstp = parent[n];
|
||
else {
|
||
if (secondp == 0) secondp = parent[n];
|
||
else {
|
||
if (parent[n] == secondp) p = secondp;
|
||
else p = firstp;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
tmp[i][j] = p;
|
||
}
|
||
} else {
|
||
if (num < 2 || num > 3) tmp[i][j] = 0;
|
||
else tmp[i][j] = field[i][j];
|
||
}
|
||
}
|
||
}
|
||
field = tmp;
|
||
}
|
||
|
||
// ---- ЭТАЛОН "чистое большинство из 3 живых соседей" ----
|
||
// Правило: клетка рождается ровно при 3 живых соседях, цвет = цвет, встретившийся >=2 раз;
|
||
// если все три разные (1,2,3) -> наименьший id (по AGENTS.md). Выживание/смерть как в Conway.
|
||
void nextStepClean() {
|
||
vector<vector<unsigned char>> out = field;
|
||
for (int i = 0; i < wid; i++) {
|
||
for (int j = 0; j < hei; j++) {
|
||
int cnt[4] = {0,0,0,0};
|
||
int num = 0;
|
||
for (int di = -1; di <= 1; di++) for (int dj = -1; dj <= 1; dj++) {
|
||
if (!di && !dj) continue;
|
||
int x = i + di, y = j + dj;
|
||
if (x < 0 || y < 0 || x >= wid || y >= hei) continue;
|
||
if (field[x][y] > 0) { num++; if (field[x][y] < 4) cnt[field[x][y]]++; }
|
||
}
|
||
if (field[i][j] == 0) {
|
||
if (num == 3) {
|
||
int best = -1;
|
||
for (int c = 1; c <= 3; c++) if (cnt[c] >= 2) { best = c; break; }
|
||
if (best < 0) for (int c = 1; c <= 3; c++) if (cnt[c] == 1) { best = c; break; } // tie-break: наименьший id
|
||
out[i][j] = (unsigned char)(best < 0 ? 0 : best);
|
||
} else out[i][j] = 0;
|
||
} else {
|
||
if (num < 2 || num > 3) out[i][j] = 0;
|
||
else out[i][j] = field[i][j];
|
||
}
|
||
}
|
||
}
|
||
field = out;
|
||
}
|
||
};
|
||
|
||
// ---------- утилиты ASCII ----------
|
||
static string fieldToAscii(const Life& L) {
|
||
string s;
|
||
for (int j = 0; j < L.hei; j++) {
|
||
for (int i = 0; i < L.wid; i++) {
|
||
unsigned char v = L.field[i][j];
|
||
s += (v == 0 ? '.' : (v == 1 ? '1' : (v == 2 ? '2' : (char)('0' + v))));
|
||
}
|
||
s += '\n';
|
||
}
|
||
return s;
|
||
}
|
||
|
||
static void loadAscii(Life& L, const vector<string>& lines) {
|
||
int h = (int)lines.size();
|
||
int w = 0;
|
||
for (auto& l : lines) w = max(w, (int)l.size());
|
||
L.setField(w, h);
|
||
for (int j = 0; j < h; j++) {
|
||
for (int i = 0; i < (int)lines[j].size(); i++) {
|
||
char c = lines[j][i];
|
||
if (c == '.' || c == ' ') L.field[i][j] = 0;
|
||
else L.field[i][j] = (unsigned char)(c - '0');
|
||
}
|
||
}
|
||
L.tmp = L.field;
|
||
}
|
||
|
||
static void trimNL(string& s) { while (!s.empty() && (s.back() == '\n')) s.pop_back(); }
|
||
|
||
// ---------- генератор LCG (детерминированный, без rand()) ----------
|
||
struct LCG {
|
||
uint64_t s;
|
||
LCG(uint64_t seed) : s(seed ? seed : 0x9E3779B97F4A7C15ULL) {}
|
||
uint32_t next() { s = s * 6364136223846793005ULL + 1442695040888963407ULL; return (uint32_t)(s >> 33); }
|
||
};
|
||
|
||
static vector<string> genRandom(int w, int h, uint64_t seed, int colors) {
|
||
LCG r(seed);
|
||
vector<string> lines(h, string(w, '.'));
|
||
for (int j = 0; j < h; j++)
|
||
for (int i = 0; i < w; i++) {
|
||
uint32_t t = r.next() % 100;
|
||
if (t < 50) lines[j][i] = '.';
|
||
else lines[j][i] = (char)('1' + (r.next() % colors));
|
||
}
|
||
return lines;
|
||
}
|
||
|
||
static vector<string> splitLines(const string& s) {
|
||
vector<string> out; string cur;
|
||
for (char c : s) { if (c == '\n') { out.push_back(cur); cur.clear(); } else cur += c; }
|
||
if (!cur.empty()) out.push_back(cur);
|
||
return out;
|
||
}
|
||
|
||
struct Case {
|
||
string name;
|
||
vector<string> lines;
|
||
int steps;
|
||
};
|
||
|
||
static string jsonEscape(const string& in) {
|
||
string o;
|
||
for (char c : in) {
|
||
if (c == '\n') o += "\\n";
|
||
else if (c == '\\') o += "\\\\";
|
||
else if (c == '"') o += "\\\"";
|
||
else o += c;
|
||
}
|
||
return o;
|
||
}
|
||
|
||
int main(int argc, char** argv) {
|
||
// CLI: old_ref <file.txt> <steps> [--proto|--clean|--swap|--torus]
|
||
if (argc >= 3 && argv[1][0] != '-') {
|
||
string mode = (argc >= 4) ? argv[3] : "--proto";
|
||
ifstream f(argv[1]);
|
||
string line; vector<string> lines;
|
||
while (getline(f, line)) { if (!line.empty()) lines.push_back(line); }
|
||
int steps = atoi(argv[2]);
|
||
Life L; loadAscii(L, lines);
|
||
cout << fieldToAscii(L);
|
||
for (int s = 0; s < steps; s++) {
|
||
if (mode == "--clean") L.nextStepClean();
|
||
else if (mode == "--torus") L.nextStepCleanWrap();
|
||
else if (mode == "--swap") L.nextStepOrderSwap();
|
||
else L.nextStep();
|
||
cout << "--- step " << (s+1) << " ---\n" << fieldToAscii(L);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
// ---------- Батарея тестов ----------
|
||
vector<Case> cases;
|
||
auto add = [&](string name, vector<string> lines, int steps) { cases.push_back({name, lines, steps}); };
|
||
|
||
add("empty5x5", splitLines(".....\n.....\n.....\n.....\n....."), 10);
|
||
add("block2x2_5x5", splitLines(".....\n.11..\n.11..\n.....\n....."), 10);
|
||
add("blinker_h_5x5", splitLines(".....\n.....\n.111.\n.....\n....."), 10);
|
||
add("blinker_v_5x5", splitLines(".....\n..1..\n..1..\n..1..\n....."), 10);
|
||
add("glider_8x8", splitLines("........\n..1.....\n...1....\n.111....\n........\n........\n........\n........"), 10);
|
||
add("single_cell_5x5", splitLines(".....\n.....\n..1..\n.....\n....."), 10);
|
||
add("single_cell_6x6", splitLines("......\n......\n...1..\n......\n......\n......"), 10);
|
||
|
||
// Смешанные цвета: ключевые случаи наследования
|
||
// P1 (цвет 1) в форме блока вокруг клетки рождения, один сосед цвета 2.
|
||
add("mix_2_1_5x5", splitLines(".....\n.111.\n.121.\n.111.\n....."), 10);
|
||
add("mix_row_121_5x5", splitLines(".....\n.....\n121..\n.....\n....."), 10);
|
||
// Наследование при рождении: изолированные 3 соседа вокруг пустой клетки (2,2)
|
||
// 112 -> 1; 122 -> 2; 121 -> 1; 221 -> 2
|
||
add("birth_112_5x5", splitLines(".....\n.11..\n.2...\n.....\n....."), 10);
|
||
add("birth_122_5x5", splitLines(".....\n.12..\n.2...\n.....\n....."), 10);
|
||
add("birth_121_5x5", splitLines(".....\n.12..\n.1...\n.....\n....."), 10);
|
||
add("birth_221_5x5", splitLines(".....\n.22..\n.1...\n.....\n....."), 10);
|
||
// Минимальный контрпример бага parent[] (3x3, расходится с чистым большинством на шаге 1)
|
||
add("cc_3x3_stale", splitLines("..2\n2.1\n1.."), 3);
|
||
|
||
// Несколько рождений за один шаг в разных местах (проверка порядка обхода)
|
||
add("multi_births_6x6", splitLines("......\n.1.1..\n..1...\n...1.1\n....1.\n......"), 10);
|
||
|
||
// Спец-случай: сначала клетка с 4+ соседями (заполнит parent[]), затем рождение
|
||
add("stale_split_6x6", splitLines("......\n.1111.\n.1.1..\n...1..\n......\n......"), 10);
|
||
|
||
// Детерминированные "плотные" случайные поля разных размеров/соотношений
|
||
for (int sz : {5, 6, 8, 10, 12}) {
|
||
add("rnd_" + to_string(sz) + "x" + to_string(sz) + "_s1", genRandom(sz, sz, 0x1234u + sz, 2), 10);
|
||
add("rnd_" + to_string(sz) + "x" + to_string(sz) + "_s2", genRandom(sz, sz, 0xABCDu + sz, 2), 10);
|
||
}
|
||
// Только цвет 1 (проверка чистого Conway без цвета)
|
||
add("rnd_8x8_onecolor", genRandom(8, 8, 0x55u, 1), 10);
|
||
// Только цвет 2
|
||
{
|
||
auto g = genRandom(8, 8, 0x77u, 1);
|
||
for (auto& l : g) for (auto& c : l) if (c == '1') c = '2';
|
||
add("rnd_8x8_all2", g, 10);
|
||
}
|
||
|
||
// ---------- Прогон + запись эталона ----------
|
||
// Файлы пишутся в текущий каталог (запуск, как правило, из tools/old-ref).
|
||
string json = "[\n";
|
||
bool firstCase = true;
|
||
|
||
// для анализа
|
||
struct Div { string name; int step; string got, want; };
|
||
vector<Div> divsClean, divsSwap;
|
||
|
||
for (auto& c : cases) {
|
||
Life P; loadAscii(P, c.lines);
|
||
Life Cl; loadAscii(Cl, c.lines);
|
||
Life Sw; loadAscii(Sw, c.lines);
|
||
|
||
vector<string> protoSeq, cleanSeq, swapSeq;
|
||
protoSeq.push_back(fieldToAscii(P));
|
||
cleanSeq.push_back(fieldToAscii(Cl));
|
||
swapSeq.push_back(fieldToAscii(Sw));
|
||
for (int s = 0; s < c.steps; s++) {
|
||
P.nextStep();
|
||
Cl.nextStepClean();
|
||
Sw.nextStepOrderSwap();
|
||
protoSeq.push_back(fieldToAscii(P));
|
||
cleanSeq.push_back(fieldToAscii(Cl));
|
||
swapSeq.push_back(fieldToAscii(Sw));
|
||
}
|
||
|
||
// ref-файл (все шаги, включая шаг 0)
|
||
{
|
||
ofstream rf("ref-" + c.name + ".txt");
|
||
for (size_t s = 0; s < protoSeq.size(); s++) {
|
||
rf << "=== step " << s << " ===\n" << protoSeq[s];
|
||
}
|
||
}
|
||
|
||
// сравнение prototype vs clean / swap
|
||
{
|
||
Life PC; loadAscii(PC, c.lines);
|
||
Life PS; loadAscii(PS, c.lines);
|
||
for (size_t s = 1; s < protoSeq.size(); s++) {
|
||
if (divsClean.empty() || true) {
|
||
// отмечаем первое расхождение на каждом шаге отдельно
|
||
}
|
||
if (cleanSeq[s] != protoSeq[s]) {
|
||
divsClean.push_back({c.name, (int)s, protoSeq[s], cleanSeq[s]});
|
||
}
|
||
if (swapSeq[s] != protoSeq[s]) {
|
||
divsSwap.push_back({c.name, (int)s, protoSeq[s], swapSeq[s]});
|
||
}
|
||
}
|
||
}
|
||
|
||
// JSON
|
||
json += string(firstCase ? "" : ",\n") + " {\"name\":\"" + c.name + "\",\"w\":" + to_string(P.wid) +
|
||
",\"h\":" + to_string(P.hei) + ",\"steps\":[";
|
||
for (size_t s = 0; s < protoSeq.size(); s++) {
|
||
json += (s ? "," : "") + string("\"") + jsonEscape(protoSeq[s]) + "\"";
|
||
}
|
||
json += "]}";
|
||
firstCase = false;
|
||
}
|
||
json += "\n]\n";
|
||
{
|
||
ofstream jf("reference.json");
|
||
jf << json;
|
||
}
|
||
|
||
// ---------- Поиск минимального контрпримера (prototype != clean) ----------
|
||
// Перебор всех полей 3x3 с цветами {0,1,2}: 3^9 = 19683.
|
||
string bestCounter;
|
||
int bestLive = 99;
|
||
for (int code = 0; code < 19683; code++) {
|
||
int t = code;
|
||
vector<string> lines(3, string(3, '.'));
|
||
for (int k = 0; k < 9; k++) {
|
||
int v = t % 3; t /= 3;
|
||
lines[k / 3][k % 3] = (v == 0 ? '.' : (char)('0' + v));
|
||
}
|
||
Life A; loadAscii(A, lines);
|
||
Life B; loadAscii(B, lines);
|
||
A.nextStep(); B.nextStepClean();
|
||
if (fieldToAscii(A) != fieldToAscii(B)) {
|
||
int live = 0;
|
||
for (auto& l : lines) for (char c : l) if (c != '.') live++;
|
||
if (live < bestLive) {
|
||
bestLive = live;
|
||
bestCounter = lines[0] + "\n" + lines[1] + "\n" + lines[2] + "\n";
|
||
}
|
||
}
|
||
}
|
||
|
||
// Также перебор 4x4? 3^16 = 43M — дорого, пропускаем; случайный поиск 4x4/5x5.
|
||
string rndCounter;
|
||
{
|
||
LCG r(0xC0FFEEu);
|
||
for (int iter = 0; iter < 300000; iter++) {
|
||
int w = 4 + (r.next() % 3), h = 4 + (r.next() % 3);
|
||
vector<string> lines(h, string(w, '.'));
|
||
for (int j = 0; j < h; j++) for (int i = 0; i < w; i++) {
|
||
uint32_t t = r.next() % 100;
|
||
lines[j][i] = (t < 55 ? '.' : (char)('1' + r.next() % 2));
|
||
}
|
||
Life A; loadAscii(A, lines);
|
||
Life B; loadAscii(B, lines);
|
||
A.nextStep(); B.nextStepClean();
|
||
if (fieldToAscii(A) != fieldToAscii(B)) {
|
||
int live = 0; string s;
|
||
for (auto& l : lines) { s += l + "\n"; for (char c : l) if (c != '.') live++; }
|
||
if (rndCounter.empty()) rndCounter = s + "// live=" + to_string(live) + "\n";
|
||
}
|
||
}
|
||
}
|
||
|
||
// ---------- Проверка "протечки цвета": рождённый цвет всегда среди соседей? ----------
|
||
long long leakCount = 0, birthCount = 0;
|
||
{
|
||
LCG lk(0xBEEF1234u);
|
||
for (int iter = 0; iter < 300000; iter++) {
|
||
int w = 4 + (lk.next() % 9), h = 4 + (lk.next() % 9);
|
||
vector<string> lines(h, string(w, '.'));
|
||
for (int j = 0; j < h; j++) for (int i = 0; i < w; i++) {
|
||
uint32_t t = lk.next() % 100;
|
||
lines[j][i] = (t < 50 ? '.' : (char)('1' + lk.next() % 2));
|
||
}
|
||
Life A; loadAscii(A, lines);
|
||
auto before = A.field;
|
||
A.nextStep();
|
||
for (int i = 0; i < w; i++) for (int j = 0; j < h; j++) {
|
||
if (before[i][j] == 0 && A.field[i][j] != 0) {
|
||
birthCount++;
|
||
bool among = false;
|
||
for (int di = -1; di <= 1; di++) for (int dj = -1; dj <= 1; dj++) {
|
||
if (!di && !dj) continue;
|
||
int x = i + di, y = j + dj;
|
||
if (x < 0 || y < 0 || x >= w || y >= h) continue;
|
||
if (before[x][y] == A.field[i][j]) among = true;
|
||
}
|
||
if (!among) leakCount++;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// ---------- Отчёт в stdout ----------
|
||
cout << "===== HARNESS: эталон записан =====\n";
|
||
cout << "Cases: " << cases.size() << "\n";
|
||
cout << "reference.json: ./reference.json\n";
|
||
cout << "ref-*.txt: " << cases.size() << " файлов\n\n";
|
||
|
||
cout << "===== prototype vs CLEAN majority (расхождения) =====\n";
|
||
if (divsClean.empty()) cout << "нет расхождений ни на одном шаге\n";
|
||
else {
|
||
cout << "всего расхождений (шаг/кейс): " << divsClean.size() << "\n";
|
||
for (size_t i = 0; i < divsClean.size() && i < 40; i++) {
|
||
cout << "[" << i << "] case=" << divsClean[i].name << " step=" << divsClean[i].step << "\n";
|
||
cout << " proto:\n" << divsClean[i].got;
|
||
cout << " clean:\n" << divsClean[i].want;
|
||
}
|
||
}
|
||
|
||
cout << "\n===== prototype vs ORDER-SWAPPED (расхождения) =====\n";
|
||
if (divsSwap.empty()) cout << "нет расхождений — результат не зависит от порядка обхода на этой батарее\n";
|
||
else {
|
||
cout << "всего расхождений: " << divsSwap.size() << "\n";
|
||
for (size_t i = 0; i < divsSwap.size() && i < 20; i++) {
|
||
cout << "[" << i << "] case=" << divsSwap[i].name << " step=" << divsSwap[i].step << "\n";
|
||
cout << " x-major(proto):\n" << divsSwap[i].got;
|
||
cout << " y-major(swap):\n" << divsSwap[i].want;
|
||
}
|
||
}
|
||
|
||
cout << "\n===== Минимальный контрпример (proto != clean) в 3x3 =====\n";
|
||
if (bestCounter.empty()) cout << "в 3x3 не найдено\n";
|
||
else cout << "live=" << bestLive << "\n" << bestCounter;
|
||
|
||
cout << "\n===== Проверка протечки цвета (300000 случайных полей) =====\n";
|
||
cout << "рождений всего: " << birthCount << ", цвет НЕ среди соседей: " << leakCount << "\n";
|
||
|
||
cout << "\n===== Первый случайный контрпример 4..6 =====\n";
|
||
if (rndCounter.empty()) cout << "не найдено\n";
|
||
else cout << rndCounter;
|
||
|
||
return 0;
|
||
}
|