Евгений Кучерявый

Как написать шахматную доску на Larana

Что такое Larana

Larana — это прежде всего философия в разработке графических интерфейсов и только потом фреймворк. И эта философия предполагает наличие нескольких важных компонентов:

Фреймворк сейчас находится в стадии глубокого переосмысления, но остальные компоненты уже можно использовать в своих проектах. Например, есть библиотека chartography, которая позволяет рисовать графики.

Скоро тут будет ссылка на более подробный текст, но пока можно вдохновиться философией из записи доклада «LaranaJS: Настоящий SSR» на HolyJS Autumn 2024

Как работает очередь

Lareq представляет API для составления очереди команд. Если вы просто разрабатываете какой-то интерфейс, то об этом даже не нужно задумываться. Но если вам нужно сделать что-то на более низком уровне, то такая возможность у вас всё ещё есть — просто составьте очередь самостоятельно.

Этот низкоуровневый доступ полезен, если вы создаёте не классическое веб-приложение, а, например, какую-нибудь игру. Вот пример, где на lareq + colorana была написана имплементация Doom.

Doorana: имплементация Doom на Larana

Смотрите запись доклада «Larana vs CSS» на HolyJS 2025 spring

Рисуем шахматную доску

Но а мы начнём погружение с чего-то более простого — шахматной доски:

Шахматная доска на компонентах Larana

Самый простой сособ сделать это — цикл внутри цикла и отрисовка каждого квадрата по отдельности. Для каждой клетки нам нужно дать три команды:

  1. Указать цвет.
  2. Разметить фигуру.
  3. Закрасить.

Вот как это выглядит в коде:

 1const q = new RenderQueue()
 2
 3for (let i = 0; i < 8; i ++) {
 4	for (let j = 0; j < 8; j++) {
 5		// Определяем цвет для каждой клетки отдельно
 6		q.command.setCtx({ fillStyle: getRectColor(i, j) })
 7		q.command.rect(getRectBox(i, j))
 8		q.command.fill()
 9	}
10}

И вот какая из этого получается очередь:

1[
2	{ "c": 25, "o": { "fillStyle": "white" } },
3	{ "c": 15, "o": { "x": 64, "y": 64, "w": 64, "h": 64 } },
4	{ "c": 9 },
5	// ...
6]

Итого получается 3 × 64 = 192 команды. Кажется, мы можем придумать что-то получше.

Рисуем сначала все белые, потом все чёрные

Мы сократим количество команд, если чутка изменим алгоритм — укажем цвет, разметим все клетки этого цвета, а потом закрасим:

 1q.command.setCtx({ fillStyle: LIGHT_CELL_COLOR })
 2for (...) {
 3	q.command.rect(getRectBox(...))
 4}
 5q.command.fill()
 6
 7q.command.setCtx({ fillStyle: DARK_CELL_COLOR })
 8for (...) {
 9	q.command.rect(getRectBox(...))
10}
11q.command.fill()

С таким подходом мы сократили количество команд почти в 3 раза: (32 + 2) × 2 = 68. Можем ли мы придумать что-то ещё более эффективное?

Рисуем белый фон, а потом только чёрные

Зачем рисовать отдельно каждую клетку, если можно закрасить один большой квадрат, а потом просто рисовать поверх него?

 1// Рисуем фон
 2q.command.setCtx({ fillStyle: LIGHT_CELL_COLOR })
 3q.command.rect({ x: 0, y: 0, w: WIDTH, h: HEIGHT })
 4q.command.fill()
 5
 6// Рисуем всё остальное
 7q.command.setCtx({ fillStyle: DARK_CELL_COLOR })
 8q.command.beginPath()
 9
10for (let i = 0; i < 8; i++) {
11	for (let j = 0; j < 8; j++) {
12		if (isLight(i, j)) {
13			continue
14		}
15		q.command.rect(getRectBox(i, j))
16	}
17}
18q.command.fill()

Итого: 3 + 3 + 32 = 38. Сокращение почти в 2 раза по сравнению предыдущим способом.

Можно ли выжать ещё хоть что-то?

В последнем примере мало что можно оптимизировать, но с посленего релиза v.1.1.6 lareq научился вырезать дублирующиеся параметры. Вот как это влияет на каждый из этих способов рисования доски:

Метод Длина очереди Размер (КБ) Сжатая длина Сжатый размер (КБ)
Рисуем каждую ячейку отдельно 192 5 505 130 3 335
Рисуем сначала белые, потом чёрные 68 2 839 68 2 839
Рисуем чёрные на белом фоне 38 1 513 38 1 513

Чем меньше значения, тем лучше.

Как видно, на последних двух вариантах алгоритм проигрывает человеческому интеллекту, но это большое обновление для тех, кто не готов оптимизировать свои приложения на более низком уровне.

За написание алгоритма спасибо @iamshvetsov