/* * spranim — тест авто-анимаций спрайтового движка (§9г): * sprite_anim (LOOP/PINGPONG/ONCE) + sprite_moveto + статусы + стопы. * * Логика проверяется ПРОГРАММНО по полям sprite_t: спрайты невидимы, * тикер работает независимо от видимости, блитов нет — sprite_update * гоняется в текстовом режиме (скобка W3 безвредна). Ожидания: * * T1 LOOP 0..3 speed=2: смена раз в 2 тика: после 8 тиков frame * снова 0 (0→1→2→3→0), src вернулся на кадр 0. * T2 PINGPONG 0..3 speed=1: 1,2,3,2,1,0,1 — на тике 7 frame==1, * разворот на границе без удвоения краёв. * T3 ONCE 0..2 speed=1: после 2 тиков frame==2 и ANIM_ON ещё стоит, * после 3-го — ON снят, DONE взведён, frame==2 (замер на last). * T4 MOVETO (0,0)→(100,50) step=5 interval=4 (пример из §9г): * тик 4: (5,3); тик 8: (10,5); тик 79: НЕ DONE; тик 80: (100,50), * MOVE_ON снят, MOVE_DONE взведён. Параллельно у спрайта идёт * LOOP-анимация (одновременность) — frame успел смениться. * T5 sprite_anim_stop(1): ON снят, frame==1, ось = 16. * sprite_move_stop(at_target=1) на полпути: прыжок в цель + DONE. * T7 Y-СОРТИРОВКА: gfx_sprite_ysort(1) + прямой вызов сортировщика: * порядок по {layer, y}; персистентный resort после смены y; * слой поверх y (layer=1 в хвосте при минимальном y). * * Кадры: RAM-лента 16×16×4 (getimage), кадр k залит k — содержимое * не важно (блитов нет), важен размер h=16 → an_step. */ #include #include #include #include "../../libbgi/_bgi.h" /* T7: прямой доступ к сорт-таблице */ static uint8_t strip[4 + 16 * 16 * 4]; static sprite_t sp[4]; /* Адрес пикселей кадра k вертикальной ленты (stride 16, кадр 16 строк): * тикер двигает КЭШ src (sy не трогает — см. sprite.h), поэтому * положение кадра проверяем по нему. */ #define FRAME_SRC(k) ((const uint8_t *)strip + 4 + (uint16_t)(k) * 256) static void build_strip(void) { memset(strip, 0, sizeof strip); strip[0] = 16; strip[2] = 64; /* w=16, h=64 (4 кадра) */ } static void tick(sprite_t *arr, uint8_t n, uint16_t times) { while (times--) sprite_update(arr, n); } int main(void) { uint8_t ok; puts("SPRANIM: frame/tween auto-animation"); build_strip(); /* ---- T1: LOOP ---------------------------------------------------- */ sprite_init(&sp[0], strip); sp[0].w = 16; sp[0].h = 16; sprite_anim(&sp[0], 0, 3, 2, ANIM_LOOP); tick(sp, 1, 2); ok = (sprite_anim_frame(&sp[0]) == 1 && sp[0].src == FRAME_SRC(1)); tick(sp, 1, 4); /* всего 6: 0→1→2→3 */ ok = ok && (sprite_anim_frame(&sp[0]) == 3 && sp[0].src == FRAME_SRC(3)); tick(sp, 1, 2); /* всего 8: wrap → 0 */ ok = ok && (sprite_anim_frame(&sp[0]) == 0 && sp[0].src == FRAME_SRC(0)); printf("T1 LOOP: %s\n", ok ? "PASS" : "FAIL"); /* ---- T2: PINGPONG ------------------------------------------------ */ sprite_init(&sp[0], strip); sp[0].w = 16; sp[0].h = 16; sprite_anim(&sp[0], 0, 3, 1, ANIM_PINGPONG); tick(sp, 1, 3); /* 1,2,3 */ ok = (sprite_anim_frame(&sp[0]) == 3); tick(sp, 1, 1); /* разворот: 2 */ ok = ok && (sprite_anim_frame(&sp[0]) == 2 && sp[0].src == FRAME_SRC(2)); tick(sp, 1, 2); /* 1,0 */ ok = ok && (sprite_anim_frame(&sp[0]) == 0); tick(sp, 1, 1); /* разворот: 1 */ ok = ok && (sprite_anim_frame(&sp[0]) == 1 && sp[0].src == FRAME_SRC(1)); printf("T2 PINGPONG: %s\n", ok ? "PASS" : "FAIL"); /* ---- T3: ONCE ---------------------------------------------------- */ sprite_init(&sp[0], strip); sp[0].w = 16; sp[0].h = 16; sprite_anim(&sp[0], 0, 2, 1, ANIM_ONCE); tick(sp, 1, 2); /* 1,2 — дошли до last */ ok = (sprite_anim_frame(&sp[0]) == 2 && (sprite_anim_status(&sp[0]) & SPR_ANIM_ON) && !(sprite_anim_status(&sp[0]) & SPR_ANIM_DONE)); tick(sp, 1, 1); /* фиксация: ON→off,DONE */ ok = ok && (sprite_anim_frame(&sp[0]) == 2 && sp[0].src == FRAME_SRC(2) && !(sprite_anim_status(&sp[0]) & SPR_ANIM_ON) && (sprite_anim_status(&sp[0]) & SPR_ANIM_DONE)); tick(sp, 1, 5); /* стоит на месте */ ok = ok && (sprite_anim_frame(&sp[0]) == 2); printf("T3 ONCE: %s\n", ok ? "PASS" : "FAIL"); /* ---- T4: MOVETO + одновременная кадровая ------------------------- */ sprite_init(&sp[0], strip); sp[0].w = 16; sp[0].h = 16; /* x=y=0 после init */ sprite_anim(&sp[0], 0, 3, 3, ANIM_LOOP); sprite_moveto(&sp[0], 100, 50, 5, 4); ok = (sprite_moving(&sp[0]) != 0); tick(sp, 1, 4); ok = ok && (sp[0].x == 5 && sp[0].y == 3); /* Y: 3/2/3/2 */ tick(sp, 1, 4); ok = ok && (sp[0].x == 10 && sp[0].y == 5); tick(sp, 1, 71); /* всего 79 — ещё едет */ ok = ok && sprite_moving(&sp[0]) && !(sprite_anim_status(&sp[0]) & SPR_MOVE_DONE); tick(sp, 1, 1); /* 80: приехали */ ok = ok && (sp[0].x == 100 && sp[0].y == 50 && !sprite_moving(&sp[0]) && (sprite_anim_status(&sp[0]) & SPR_MOVE_DONE)); ok = ok && (sprite_anim_frame(&sp[0]) != 0 || sp[0].an_timer != 3); /* кадровая тоже тикала */ printf("T4 MOVETO: %s (x=%d y=%d)\n", ok ? "PASS" : "FAIL", sp[0].x, sp[0].y); /* ---- T5: стопы ---------------------------------------------------- */ sprite_init(&sp[0], strip); sp[0].w = 16; sp[0].h = 16; sprite_anim(&sp[0], 0, 3, 1, ANIM_LOOP); tick(sp, 1, 2); /* frame 2 */ sprite_anim_stop(&sp[0], 1); /* остановить на кадре 1 */ ok = (!(sprite_anim_status(&sp[0]) & SPR_ANIM_ON) && sprite_anim_frame(&sp[0]) == 1 && sp[0].sy == 16 && sp[0].src == FRAME_SRC(1)); tick(sp, 1, 4); /* не двигается */ ok = ok && (sprite_anim_frame(&sp[0]) == 1); sprite_moveto(&sp[0], 60, 60, 3, 1); tick(sp, 1, 5); /* на полпути */ sprite_move_stop(&sp[0], 1); /* добить в цель */ ok = ok && (sp[0].x == 60 && sp[0].y == 60 && !sprite_moving(&sp[0]) && (sprite_anim_status(&sp[0]) & SPR_MOVE_DONE)); sprite_moveto(&sp[0], 0, 0, 3, 1); tick(sp, 1, 5); sprite_move_stop(&sp[0], 0); /* замереть где есть */ ok = ok && (sp[0].x != 0 && !sprite_moving(&sp[0]) && !(sprite_anim_status(&sp[0]) & SPR_MOVE_DONE)); printf("T5 STOPS: %s\n", ok ? "PASS" : "FAIL"); /* ---- T6: sprite_frame ПОСЛЕ тикера ------------------------------- * * Тикер двигает src, оставляя sx/sy как есть — sprite_frame обязан * сверять КАДР по src, а не по (sx,sy): просьба вернуть кадр (0,0) * при формально не изменившихся sx/sy должна вернуть src. */ sprite_init(&sp[0], strip); sp[0].w = 16; sp[0].h = 16; sprite_anim(&sp[0], 0, 3, 1, ANIM_LOOP); tick(sp, 1, 2); /* src уехал на кадр 2 */ sprite_anim_stop(&sp[0], -1); /* замереть на текущем */ ok = (sp[0].src == FRAME_SRC(2) && sp[0].sy == 0); sprite_frame(&sp[0], 0, 0); /* sx/sy те же, src ушёл */ ok = ok && (sp[0].src == FRAME_SRC(0)); printf("T6 REFRAME: %s\n", ok ? "PASS" : "FAIL"); /* ---- T7: Y-сортировка (слои + персистентность) -------------------- * * Проверяем через возвращаемую таблицу (записи {key16, ptr16} по 4 * байта, ptr по +2): порядок указателей = порядок отрисовки. */ { uint8_t *tab; uint8_t i; sprite_t *o0, *o1, *o2, *o3; #define YS_PTR(t, i) (*(sprite_t **)((t) + (i) * 4 + 2)) for (i = 0; i < 4; i++) { sprite_init(&sp[i], strip); sp[i].w = 16; sp[i].h = 16; } sp[0].y = 50; sp[1].y = 10; sp[2].y = 30; sp[3].y = 0; sp[3].layer = 1; /* слой поверх всех */ gfx_sprite_ysort(YSORT_Y | YSORT_LAYER); /* полный порядок */ tab = (*_spr_ysort_fn)(sp, 4); o0 = YS_PTR(tab, 0); o1 = YS_PTR(tab, 1); o2 = YS_PTR(tab, 2); o3 = YS_PTR(tab, 3); /* ожидание: y10, y30, y50, затем layer1 (несмотря на y=0) */ ok = (o0 == &sp[1] && o1 == &sp[2] && o2 == &sp[0] && o3 == &sp[3]); sp[1].y = 60; /* обгон: y10 → y60 */ tab = (*_spr_ysort_fn)(sp, 4); /* персистентный resort */ o0 = YS_PTR(tab, 0); o1 = YS_PTR(tab, 1); o2 = YS_PTR(tab, 2); o3 = YS_PTR(tab, 3); ok = ok && (o0 == &sp[2] && o1 == &sp[0] && o2 == &sp[1] && o3 == &sp[3]); /* LAYER-only: y замаскирован — слой 0 в порядке МАССИВА * (сеттер форсит rebuild), sp[3] (layer 1) в хвосте. */ gfx_sprite_ysort(YSORT_LAYER); tab = (*_spr_ysort_fn)(sp, 4); o0 = YS_PTR(tab, 0); o1 = YS_PTR(tab, 1); o2 = YS_PTR(tab, 2); o3 = YS_PTR(tab, 3); ok = ok && (o0 == &sp[0] && o1 == &sp[1] && o2 == &sp[2] && o3 == &sp[3]); gfx_sprite_ysort(0); printf("T7 YSORT: %s\n", ok ? "PASS" : "FAIL"); } puts("done"); return 0; }