Files
Sprinter-SDCC/examples/balls/balls.c
T
snark13 72ce66275e libbgi: спрайтовый движок v2 + accel-блит/heal leaf'ы + noclip-путь
Спрайтовая графика поверх accel block-copy (docs/sprite-api-design.md):

- Ядро блиттинга: leaf'ы _bgi_blit_rows_raw (dst фикс, только src-страйд) /
  _bgi_copy_rows_raw (getimage) / _bgi_heal_rows_raw (src==dst). DI один на
  спрайт (санкция: малый спрайт под одним DI аудио не рвёт); src[0]-фикс
  снят (точная MAME подавляет CPU-байт триггера записи — на железе
  перепроверить; для heal был избыточен и снят безусловно).
- Общие bracket-free ядра _gfx_blit_full/_gfx_heal_full (полная ширина:
  клип по экрану + split >256 для putimage) + лин _gfx_blit_sprite/
  _gfx_heal_sprite (кадр ≤64, без split, 8-бит w/h) + noclip-варианты
  (клип-кода нет → полный codegen-win).  Имя *_full (не *_clip) — «clip»
  двусмысленно (sprite-ядра тоже клипуют; различитель — ширина/split).
- Движок retained-модели <sprite.h>: sprite_init/update/flip + inline
  move/frame/show/hide/touch; drawn[2] per-page внутри структуры; кадр —
  двухпроходно heal ВСЕ -> блит ВСЕ под одной W3-скобкой/банком на проход.
- Флаг gfx_sprite_clip(on/off): приложение, само следящее за границами,
  отключает клип (~+19% на анимации; диспетч пока через if — funcptr далее).
- putsprite/movesprite/gfx_blit/putimage(COPY)/getimage переведены на ядро.

Тесты: examples/balls (движок, дабл-буфер, boundary-тест клипа),
tests/sprites (RAM PASS, клип 4 края, атлас), tests/blitw (trig-leak),
tests/spriteclip (hardware-probe: железо НЕ режет за краем -> клип нужен),
tests/blitperf, tests/gfxbanks. size-baseline обновлён (53 программы).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 21:51:52 +03:00

186 lines
7.2 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* balls — демо спрайтового движка v2 (<sprite.h>): N шаров 16×16 летают
* над чёрно-белой шахматкой, отражаясь от краёв. Esc — выход.
*
* РАНЬШЕ (до движка) приложение вело drawn[2][N] координаты вручную и
* само соблюдало дисциплину «heal ВСЕ → блит ВСЕ» на скрытой странице.
* Теперь это работа движка: sprite_move меняет только состояние,
* sprite_flip(sp, N) за кадр делает draw-page=скрытая → heal все старые →
* блит все видимые (одна W3-скобка + один банк на проход) → vsync → флип.
* Учёт drawn[2] спрятан в sprite_t. Профиль/дизайн:
* docs/sprite-api-design.md §9.1.
*
* BOUNDARY-ТЕСТ клипа: диапазон движения выходит ЗА края экрана на
* половину спрайта — шары штатно наполовину уходят за все 4 края,
* включая ОТРИЦАТЕЛЬНЫЕ x/y слева/сверху. Клип _gfx_blit_clip (общий
* для putsprite и движка) обязан авто-обрезать: правый/нижний — урезать
* w/h, левый/верхний — сдвинуть начало источника (sx/sy) и урезать.
*
* Двойная буферизация: рисуем на скрытой странице, флип по vsync (иначе
* heal+блит на видимой ловятся лучом — мерцание). Палитру страницы 1
* зеркалим из 0 (initgraph грузит EGA только в палитру 0).
*
* Скорость привязана к КАДРАМ (50 Гц): один проход цикла = один шаг.
*/
#include <graphics.h>
#include <gfx.h>
#include <sprite.h>
#include <conio.h>
#include <time.h>
#define FPS_METER 1 /* 1 = счётчик кадров/с слева-сверху (диагностика) */
#define NBALLS 16 /* количество шаров */
#define BW 16 /* сторона спрайта */
#define CELL 16 /* клетка шахматки */
/* Диапазон отражения — на пол-спрайта ЗА каждым краем (boundary-тест):
* x ∈ [-8 .. 312], y ∈ [-8 .. 248]. */
#define MINY (0) /* -8: половина за левым/верхним краем */
#define MINX (0) /* -8: половина за левым/верхним краем */
#define MAXX (320 - BW) /* 312: половина за правым краем */
#define MAXY (256 - BW) /* 248: половина за нижним краем */
/* Цвета шаров (не совпадают с фоном BLACK/WHITE) */
static const uint8_t ball_colors[] = {
YELLOW, LIGHTRED, LIGHTGREEN, LIGHTCYAN,
LIGHTMAGENTA, LIGHTBLUE, RED, GREEN
};
#define NCOLORS ((uint8_t)sizeof(ball_colors))
static sprite_t sp[NBALLS]; /* спрайты — движок ведёт drawn[2] */
static int8_t vx[NBALLS], vy[NBALLS]; /* направление ±1 (физика — наша) */
static uint8_t spr[NCOLORS][4 + BW * BW]; /* по спрайту на цвет */
static uint8_t egapal[16 * 4];
static void build_sprites(void)
{
uint8_t c;
int x, y;
for (c = 0; c < NCOLORS; c++) {
uint8_t *p = spr[c];
*p++ = BW; *p++ = 0; *p++ = BW; *p++ = 0;
for (y = 0; y < BW; y++)
for (x = 0; x < BW; x++) {
int dx = 2 * x - (BW - 1), dy = 2 * y - (BW - 1); /* ×2: центр между пикселями */
*p++ = (dx * dx + dy * dy <= 15 * 15)
? ball_colors[c] : GFX_TRANSPARENT;
}
}
}
static void draw_board(void)
{
int cx, cy;
for (cy = 0; cy < 256; cy += CELL)
for (cx = 0; cx < 320; cx += CELL) {
setfillstyle(SOLID_FILL,
(((cx ^ cy) >> 4) & 1) ? WHITE : BLACK);
bar(cx, cy, cx + CELL - 1, cy + CELL - 1);
}
}
static void init_balls(void)
{
uint8_t i;
for (i = 0; i < NBALLS; i++) {
sprite_init(&sp[i], spr[i % NCOLORS]);
sp[i].x = (20 + i * 37) % (MAXX - 1) + 1;
sp[i].y = (20 + i * 53) % (MAXY - 1) + 1;
sprite_show(&sp[i]);
vx[i] = (i & 1) ? 1 : -1;
vy[i] = (i & 2) ? 1 : -1;
}
}
#if FPS_METER
/* Кадров за последнюю RTC-секунду — белым в чёрной плашке слева-сверху
* на ТЕКУЩЕЙ draw-странице (рисуем ДО sprite_update, чтобы шары легли
* поверх и heal учитывал плашку через ОЗУ-копию). */
static void show_fps(uint8_t v)
{
char buf[4];
buf[0] = (char)('0' + (v / 100) % 10);
buf[1] = (char)('0' + (v / 10) % 10);
buf[2] = (char)('0' + v % 10);
buf[3] = 0;
setcolor(WHITE);
setfillstyle(SOLID_FILL, BLACK);
bar(0, 0, 27, 9);
outtextxy(2, 1, buf);
}
#endif
int main(void)
{
uint8_t i, page, hidden;
#if FPS_METER
datetime_t dt;
uint8_t frames = 0, last_sec;
#endif
build_sprites();
initgraph();
gfx_sprite_clip(0);
/* Палитра страницы 1 = палитре страницы 0. */
gfx_pal_get(0, 0, 16, egapal);
gfx_pal_load(1, 0, 16, egapal);
init_balls();
/* Обе страницы: шахматка (в ОЗУ-копию — бэкап для heal) + шары в
* стартовых позициях (sprite_update рисует на текущей draw-странице
* и снимает её dirty-бит). */
for (page = 0; page < 2; page++) {
gfx_set_draw_page(page);
draw_board();
sprite_update(sp, NBALLS);
}
gfx_set_visible_page(0);
#if FPS_METER
getdatetime(&dt);
last_sec = dt.second;
#endif
for (;;) {
if (kbhit() && getch() == 27)
break;
/* Физика: обновить позиции (только состояние спрайтов). */
for (i = 0; i < NBALLS; i++) {
int nx = sp[i].x + vx[i];
int ny = sp[i].y + vy[i];
if (nx <= MINX || nx >= MAXX) vx[i] = -vx[i];
if (ny <= MINY || ny >= MAXY) vy[i] = -vy[i];
sprite_move(&sp[i], nx, ny);
}
/* Кадр: рисуем на скрытой странице. sprite_flip развёрнут, чтобы
* плашку FPS нарисовать на скрытой странице ДО sprite_update. */
hidden = gfx_get_visible_page() ^ 1;
gfx_set_draw_page(hidden);
#if FPS_METER
frames++;
getdatetime(&dt);
if (dt.second != last_sec) {
last_sec = dt.second;
show_fps(frames);
frames = 0;
}
#endif
sprite_update(sp, NBALLS);
gfx_wait_vsync();
gfx_set_visible_page(hidden); /* флип в vblank — без мерцания */
}
closegraph();
return 0;
}