/* * t_geom.c — тесты pop_geom.c (геометрия комнаты + PRNG оригинала). * * Модуль выбран первым, потому что не тянет за собой ничего: ни графики, * ни данных уровня, ни клавиатуры — только свой заголовок. * * Главный тест здесь — geom_lcg_matches_reference. В pop_geom.c LCG * оригинала (s = s*214013 + 2531011) написан на ассемблере по схеме * Горнера ради обхода __mullong, и заявка «бит-в-бит как в SDLPoP» до сих * пор держалась только на комментарии. Тест сверяет рукописный asm с * наивной 32-битной формулой на каждом шаге — и по возвращаемому значению, * и по обеим половинам сида. */ #include "tcheck.h" #include "pop_geom.h" /* ---- геометрия ------------------------------------------------------ */ TC_TEST(geom_xbump_layout) { /* Шаг колонок равномерный и равен TILE_SIZEX — на этом стоит вся * арифметика колонок (x_bump[col + FIRST_ONSCREEN_COLUMN]). */ uint8_t i; for (i = 1; i < 20; i++) TC_EQ(pop_x_bump[i] - pop_x_bump[i - 1], TILE_SIZEX); /* Центр тайла колонки 0 — та самая величина, которую кладут в Char.x * do_startpos и pos_guards (seg003). */ TC_EQ(pop_x_bump[0 + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX, 72); TC_EQ(pop_x_bump[9 + FIRST_ONSCREEN_COLUMN] + TILE_SIZEX, 198); } TC_TEST(geom_yland_rows) { /* y_land[row + 1]; [0] — ряд «над комнатой». */ TC_EQ(pop_y_land[0], -8); TC_EQ(pop_y_land[1], 55); TC_EQ(pop_y_land[2], 118); TC_EQ(pop_y_land[3], 181); TC_EQ(pop_y_land[4], 244); /* Шаг между рядами — TILE_SIZEY. */ TC_EQ(pop_y_land[2] - pop_y_land[1], TILE_SIZEY); TC_EQ(pop_y_land[3] - pop_y_land[2], TILE_SIZEY); } TC_TEST(geom_y_to_row) { /* Пол каждого ряда должен давать номер этого ряда. */ TC_EQ(pop_y_to_row(pop_y_land[1]), 0); TC_EQ(pop_y_to_row(pop_y_land[2]), 1); TC_EQ(pop_y_to_row(pop_y_land[3]), 2); /* Ряд над комнатой. */ TC_EQ(pop_y_to_row(pop_y_land[0]), -1); /* Оборот mod 4: ряд «под комнатой» сворачивается обратно в -1 — * ровно на это опирается спавн падающего куска у нижней кромки. */ TC_EQ(pop_y_to_row(pop_y_land[4]), -1); } /* ---- PRNG: сверка asm-LCG с эталонной формулой ---------------------- */ #if POP_PRANDOM_EXACT #define LCG_ITERS 128 TC_TEST(geom_lcg_matches_reference) { pop_rnd_t s; unsigned long ref = 12345UL; /* произвольный ненулевой старт */ uint16_t i, got, want; pop_prandom_set(s, 12345u); for (i = 0; i < LCG_ITERS; i++) { got = pop_prandom(&s, 255u); ref = ref * 214013UL + 2531011UL; want = (uint16_t)((uint16_t)(ref >> 16) % 256u); /* Обрываемся на первом расхождении: иначе одна ошибка в умножении * забила бы весь буфер отчёта однотипными строками. */ if (got != want || s.lo != (uint16_t)ref || s.hi != (uint16_t)(ref >> 16)) { TC_EQ(got, want); TC_EQ(s.lo, (uint16_t)ref); TC_EQ(s.hi, (uint16_t)(ref >> 16)); break; } } TC_EQ(i, LCG_ITERS); /* прошли всю дистанцию без расхождений */ } TC_TEST(geom_lcg_seed_zero) { /* Ноль — законный сид (кладку сеют номером комнаты + ряд + колонка, * что вполне даёт 0). У LCG ноль не является неподвижной точкой — * убеждаемся, что asm это воспроизводит. */ pop_rnd_t s; unsigned long ref = 0UL; pop_prandom_set(s, 0u); pop_prandom(&s, 255u); ref = ref * 214013UL + 2531011UL; TC_EQ(s.lo, (uint16_t)ref); TC_EQ(s.hi, (uint16_t)(ref >> 16)); } #endif /* POP_PRANDOM_EXACT */ TC_TEST(geom_prandom_range) { /* Оба генератора обязаны укладываться в 0..maxv, в том числе когда * maxv+1 НЕ степень двойки (там другая ветка pop_rnd_fit). */ pop_rnd_t s; uint16_t i, v, seen_hi = 0; uint16_t out_of_range = 0; pop_prandom_set(s, 1u); for (i = 0; i < 200; i++) { v = pop_prandom(&s, 4u); /* n = 5, не степень двойки */ if (v > 4) out_of_range++; if (v == 4) seen_hi = 1; } TC_EQ(out_of_range, 0); TC_TRUE(seen_hi); /* верхняя граница достижима */ pop_prandom_set(s, 7u); out_of_range = 0; for (i = 0; i < 64; i++) { v = pop_prandom(&s, 1u); /* n = 2, маска */ if (v > 1) out_of_range++; } TC_EQ(out_of_range, 0); } int main(void) { TC_RUN(geom_xbump_layout); TC_RUN(geom_yland_rows); TC_RUN(geom_y_to_row); #if POP_PRANDOM_EXACT TC_RUN(geom_lcg_matches_reference); TC_RUN(geom_lcg_seed_zero); #endif TC_RUN(geom_prandom_range); return 0; }