/* * pop_geom.c — общая геометрия + PRNG оригинала. См. pop_geom.h. */ #include "pop_geom.h" /* data.h: шаг 14 (ширина тайла в координатах персонажа), [5] = col 0. */ const int16_t pop_x_bump[20] = { -12, 2, 16, 30, 44, 58, 72, 86, 100, 114, 128, 142, 156, 170, 184, 198, 212, 226, 240, 254 }; const int16_t pop_y_land[5] = {-8, 55, 118, 181, 244}; int8_t pop_y_to_row(int16_t y) { return (int8_t)((y + 60) / TILE_SIZEY % 4 - 1); } /* Уложить значение в диапазон 0..maxv. Вызовы оригинала — prandom(1), * prandom(255), prandom(0xFF): делитель степень двойки, то есть маска, а не * деление. __moduint на Z80 стоит заметно дороже проверки n & (n-1). */ static uint16_t pop_rnd_fit(uint16_t v, uint16_t maxv) { uint16_t n = (uint16_t)(maxv + 1); if ((uint16_t)(n & (uint16_t)(n - 1)) == 0) return (uint16_t)(v & (uint16_t)(n - 1)); return (uint16_t)(v % n); } #if POP_PRANDOM_EXACT /* s = s * 214013 + 2531011, 16-битными половинами. * * 214013 = 0x0003_43FD. Раскладываем произведение так, чтобы обошлось без * __mullong: старшее слово результата не зависит от старших бит множителя * выше 32-го, поэтому * s*M = SL*ML (полные 32 бита) * + ((SL*MH + SH*ML) mod 2^16) << 16 (только младшие 16 бит) * где ML = 0x43FD, MH = 3. Единственное «широкое» умножение — SL*ML, * 16x16 -> 32; собираем его из четырёх байтовых произведений (каждое * помещается в 16 бит, поэтому это обычный 16-битный __mulint). * * Проверка эквивалентности — на хосте: те же значения, что давал * unsigned long (см. комментарий к pop_rnd_t в pop_geom.h). */ uint16_t pop_prandom(pop_rnd_t *seed, uint16_t maxv) { uint16_t sl = seed->lo, sh = seed->hi; uint8_t al = (uint8_t)sl, ah = (uint8_t)(sl >> 8); uint16_t t0 = (uint16_t)((uint16_t)al * 0xFDu); /* al * ML_lo */ uint16_t t1 = (uint16_t)((uint16_t)al * 0x43u); /* al * ML_hi */ uint16_t t2 = (uint16_t)((uint16_t)ah * 0xFDu); /* ah * ML_lo */ uint16_t t3 = (uint16_t)((uint16_t)ah * 0x43u); /* ah * ML_hi */ uint16_t m = (uint16_t)(t1 + t2); /* средние байты */ uint16_t pl, ph; ph = (uint16_t)(t3 + (m >> 8)); if (m < t1) ph = (uint16_t)(ph + 0x100u); /* перенос из t1+t2 */ pl = (uint16_t)(t0 + (uint16_t)(m << 8)); if (pl < t0) ph++; /* перенос в старшее слово */ /* (SL*MH + SH*ML) << 16 — только младшие 16 бит слагаемого. */ ph = (uint16_t)(ph + (uint16_t)(sl * 3u) + (uint16_t)(sh * 0x43FDu)); /* + 2531011 = 0x0026_9EC3 */ { uint16_t nl = (uint16_t)(pl + 0x9EC3u); ph = (uint16_t)(ph + 0x0026u); if (nl < pl) ph++; seed->lo = nl; seed->hi = ph; } return pop_rnd_fit(ph, maxv); } #else /* !POP_PRANDOM_EXACT — быстрый 16-битный генератор (по умолчанию) */ /* xorshift16 + шаг Вейля. Зачем не LCG оригинала: тот 32-битный, и даже * без __mullong (см. ветку выше) стоит ~7 000 тактов на вызов — при двух * вызовах за кадр это 3 % бюджета на генератор случайных чисел. Здесь * умножений нет вовсе, только сдвиги и xor. * * Почему НЕ 8-битный RND Apple II (RNDseed := 5*RNDseed + 23 mod 256, * Prince-of-Persia-Apple-II): он ещё быстрее, но у LCG по модулю 256 * младшие биты вырождены — бит 0 просто чередуется. Наши вызовы это * видят: раскладка кладки берёт prandom(1) (один бит) и prandom(4), и * вместо шума получилась бы правильная шахматка. У xorshift такой * структуры нет, а стоит он примерно столько же. * * Шаг Вейля (+0x9E37, нечётный) нужен ещё и потому, что у чистого * xorshift ноль — неподвижная точка, а сид кладки (номер комнаты + ряд + * колонка) вполне может оказаться нулём. * * ПОСЛЕДСТВИЕ: последовательность отличается от SDLPoP. Значит другая * (но статистически такая же) раскладка кладки и другие броски в боёвке. * Если понадобится сверять картинку с оригиналом — собрать с * -DPOP_PRANDOM_EXACT=1, там бит-в-бит тот же LCG. */ uint16_t pop_prandom(pop_rnd_t *seed, uint16_t maxv) { uint16_t s = seed->lo; s ^= (uint16_t)(s << 7); s ^= (uint16_t)(s >> 9); s ^= (uint16_t)(s << 8); s = (uint16_t)(s + 0x9E37u); seed->lo = s; seed->hi = 0; /* старшая половина не используется */ /* Отдаём СТАРШИЙ байт: у него нет короткопериодической структуры * младших бит, а вызывающие берут от результата именно младшие. */ return pop_rnd_fit((uint16_t)(s >> 8), maxv); } #endif