/*
 * 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;
}
