/* * 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 /* ---- LCG оригинала: s = s*214013 + 2531011 (seg009:321) ------------ * * * ЕДИНСТВЕННОЕ место в порте на ассемблере, и вот почему. На Z80 нет * умножения, а SDCC для 32-битного `s * 214013` зовёт __mullong — 8 430 * тактов на вызов (замер в MAME). Написанное на C через 16-битные * половины это ~3 500. Здесь ~1 000, при полностью той же * последовательности, что в SDLPoP. Альтернатива «взять генератор * попроще» (8-битный RND Apple II, xorshift) экономит меньше и ломает * совместимость с эталоном, по которому сверяем картинку. * * Приём: 214013 = ((((1<<1)+1)<<2 + 1)<<4 + 1)<<10 - 3 — схема Горнера по * РАЗРЕЖЕННОЙ записи константы. Вместо 12 сложений (по числу единиц в * 0x343FD) — 17 удвоений, ТРИ сложения и одно вычитание. Величина 3*s, * нужная в конце, попадается по дороге на втором шаге — её и сохраняем. * * ABI __sdcccall(1): arg1 (указатель на сид) -> HL, возврат -> DE. IX не * используется, сохранять нечего. Клоббер AF/BC/DE/HL. Аккумулятор — * DE:HL (DE старшее слово); s.lo живёт в BC, s.hi и 3*s — в статиках * (регистров на всё не хватает). */ uint16_t pop_pr_ptr; /* адрес сида */ uint16_t pop_pr_sh; /* старшее слово исходного сида */ uint16_t pop_pr_3s_lo, pop_pr_3s_hi; /* 3*s, нужное в конце */ static uint16_t pop_lcg_step(pop_rnd_t *seed) __naked { (void)seed; __asm ld (_pop_pr_ptr), hl ld c, (hl) inc hl ld b, (hl) ; BC = s.lo inc hl ld a, (hl) ld (_pop_pr_sh), a inc hl ld a, (hl) ld (_pop_pr_sh + 1), a ; (pop_pr_sh) = s.hi ld h, b ld l, c ld de, (_pop_pr_sh) ; акк = s ;; акк = акк*2 + s => 3s add hl, hl rl e rl d add hl, bc ex de, hl ld a, (_pop_pr_sh) ; LD A,(nn) флаги НЕ трогает adc a, l ld l, a ld a, (_pop_pr_sh + 1) adc a, h ld h, a ex de, hl ld (_pop_pr_3s_lo), hl ld (_pop_pr_3s_hi), de ;; акк = акк*4 + s => 13s add hl, hl rl e rl d add hl, hl rl e rl d add hl, bc ex de, hl ld a, (_pop_pr_sh) adc a, l ld l, a ld a, (_pop_pr_sh + 1) adc a, h ld h, a ex de, hl ;; акк = акк*16 + s => 209s add hl, hl rl e rl d add hl, hl rl e rl d add hl, hl rl e rl d add hl, hl rl e rl d add hl, bc ex de, hl ld a, (_pop_pr_sh) adc a, l ld l, a ld a, (_pop_pr_sh + 1) adc a, h ld h, a ex de, hl ;; акк *= 1024 => 214016s (10 удвоений) add hl, hl rl e rl d add hl, hl rl e rl d add hl, hl rl e rl d add hl, hl rl e rl d add hl, hl rl e rl d add hl, hl rl e rl d add hl, hl rl e rl d add hl, hl rl e rl d add hl, hl rl e rl d add hl, hl rl e rl d ;; акк -= 3s => 214013s ld bc, (_pop_pr_3s_lo) or a ; CY = 0 sbc hl, bc ld bc, (_pop_pr_3s_hi) ; LD BC,(nn) флаги НЕ трогает ex de, hl sbc hl, bc ex de, hl ;; акк += 2531011 = 0x0026_9EC3 ld bc, #0x9EC3 add hl, bc ld bc, #0x0026 ex de, hl adc hl, bc ex de, hl ;; сохранить сид, вернуть СТАРШЕЕ слово в DE push de ex de, hl ; DE = младшее слово ld hl, (_pop_pr_ptr) ld (hl), e inc hl ld (hl), d inc hl pop de ; DE = старшее слово ld (hl), e inc hl ld (hl), d ret __endasm; } uint16_t pop_prandom(pop_rnd_t *seed, uint16_t maxv) { return pop_rnd_fit(pop_lcg_step(seed), maxv); } #else /* !POP_PRANDOM_EXACT — генератор попроще, НЕ совместимый с эталоном */ /* 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 — другая (но * статистически такая же) раскладка кладки и другие броски в боёвке. * Поэтому по умолчанию собирается НЕ эта ветка: после переписывания 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