«Колонизаторы» на SQL

от автора

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

В этой небольшой статье мы нарисуем игровое поле для Колонизаторов с помощью SQL.

Для начала, заглянем в правила.
В игре используется шестиугольное игровое поле. Игровое поле — это остров. Естественно, остров окружен морем. Поле заполняется 19-ю шестиугольными плитками «суши», которые должны быть распределены о полю случайным образом. Плитки «суши» могут быть пяти типов, соответственно типам ресурсов, которые на них находятся: «дерево», «глина», «шерсть», «зерно» и «руда» (3 + 4 + 5 + 4 + 3).
Кроме того, на 18 из этих плиток надо разложить карточки с очками. 19-я плитка — это «Пустыня».
Наконец, поскольку остров окружен морем, по краям надо расположить 9 плиток с гаванями. Плитки с гаванями должны быть случайным образом распределены по своим местам на поле.

Плитки суши

Каждая плитка будет определена своей позицией на двух осях, где оси расположены под 60 градусов по отношению друг к другу, с началом в левом верхнем углу. Мы будем называть это трапециевидным кодированием для плиток. Ось х будет диагональной (из левого нижнего угла в правый верхний угол), ось у будет горизонтальной.

Напишем маленькое табличное выражение, чтобы определить координаты каждой плитки суши.

CTE для координат суши:
WITH    tiles (rn, x, y) AS         (         VALUES                 (1, 0, 0),                 (2, 1, 0),                 (3, 2, 0),                 (4, 3, 1),                 (5, 4, 2),                 (6, 4, 3),                 (7, 4, 4),                 (8, 3, 4),                 (9, 2, 4),                 (10, 1, 3),                 (11, 0, 2),                 (12, 0, 1),                 (13, 1, 1),                 (14, 2, 1),                 (15, 3, 2),                 (16, 3, 3),                 (17, 2, 3),                 (18, 1, 2),                 (19, 2, 2)         ) SELECT  * FROM    tiles 
Вывод СTE:
rn      x       y 1       0       0 2       1       0 3       2       0 4       3       1 5       4       2 6       4       3 7       4       4 8       3       4 9       2       4 10      1       3 11      0       2 12      0       1 13      1       1 14      2       1 15      3       2 16      3       3 17      2       3 18      1       2 19      2       2 

Ресурсы

Каждой плитке суши надо присвоить тип ресурсов — один из пяти доступных.
Закодируем ресурсы символами, по одному символу на каждый тип ресурсов.
Мы будем использовать / для зерна, # для дерева, > для руды, » для шерсти и . для глины. Пустыню обозначим, как пустое место.
Хотя эти символы и не очень наглядные, они хорошо подходят для ASCII рисования.

Мы сделаем просто: запишем все доступные ресурсы в одну строку, а затем перемешаем символы в строке с помощью ORDER BY RANDOM().

Запрос для таблицы ресурсов
WITH    tiles (rn, x, y) AS         (         VALUES                 (1, 0, 0),                 (2, 1, 0),                 (3, 2, 0),                 (4, 3, 1),                 (5, 4, 2),                 (6, 4, 3),                 (7, 4, 4),                 (8, 3, 4),                 (9, 2, 4),                 (10, 1, 3),                 (11, 0, 2),                 (12, 0, 1),                 (13, 1, 1),                 (14, 2, 1),                 (15, 3, 2),                 (16, 3, 3),                 (17, 2, 3),                 (18, 1, 2),                 (19, 2, 2)         ),         resources AS         (         SELECT  '////####^^^' || CHR(34) || CHR(34) || CHR(34) || CHR(34) || '... '::TEXT tile_resources         ) SELECT  * FROM    tiles JOIN    (         SELECT  ROW_NUMBER() OVER (ORDER BY RANDOM()) rn,                 resource         FROM    resources         CROSS JOIN                 LATERAL                 REGEXP_SPLIT_TO_TABLE(tile_resources, '') q (resource)         ) tr USING   (rn) 
Вывод таблицы ресурсов:
rn      x       y       resource 1       0       0       ^ 2       1       0       # 3       2       0         4       3       1       # 5       4       2       # 6       4       3       " 7       4       4       " 8       3       4       / 9       2       4       . 10      1       3       " 11      0       2       ^ 12      0       1       . 13      1       1       # 14      2       1       ^ 15      3       2       / 16      3       3       / 17      2       3       " 18      1       2       / 19      2       2       . 

Теперь каждой координате соответствует тип ресурсов, а одна координата оставлена пустой — там пустыня.

Очки

Каждая плитка суши (кроме пустыни) должна иметь карточку очков. Количество очков находится в промежутке от 2 до 12 (так чтобы можно было выбросить двумя игральными костями). Число 7 не используется; очкам от 2 до 12 соответствуют по одной карточке, а остальным очкам по две карточки. Получается 18 карточек с очками в сумме.

Хитрость в том, что по правилам плитки с очками 6 и 8 не могут находиться рядом. Это означает, что чисто случайное распределение (такое же, как мы использовали для плиток с ресурсами) не работает.

Если верить правилам, то «нам возможно потребуется поменять карточки очков местами так, чтобы никакие два красных номера не были рядом». Однако соответствующие алгоритмы, если не аккуратно проработаны, известны тем, что вносят предвзятость, так что некоторые перестановки могут быть более вероятны, чем другие.

Поэтому, если мы определим какую-нибудь проблему с разложением карточек очков, мы не будем ничего менять местами (исправлять), а просто будем гененрировать новую расстановку до тех пор, пока она нас не устроит.

Для того чтобы это сделать, мы сначала сгенерируем случайный массив из значений очков. Затем мы проверим этот массив и соединим (join) с плитками суши и таблицей ресурсов, которые мы сгенерировали на предыдущих шагах. Одна хитрость состоит в том, что у нас 19 плиток суши, но 18 карточек очков, поэтому нам нужно будет соединять их, пропуская плитку пустыни. Мы обойдём эту проблему с помощью простой аналитической функции, которая будет пропускать число после плитки пустыни.

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

Как нам понять, что две плитки являются соседними? В первую очередь, они должны быть на расстоянии 1 друг от друга по каждой из осей. Это значит, что разница значений координат по каждой из осей должна быть от -1 до 1. Во-вторых, координаты не должны полностью совпадать, т.е. по крайней мере одна координата должна отличаться. В-третьих, (и это главное отличие шестиугольной сетки от прямоугольной) в шестиугольной сетке каждая плитка имеет 6 соседей, тогда как в прямоугольной восемь. Это означает, что даже если две координаты отличаются не более чем на единицу, плитки всё равно могут не быть соседями. Это действительно так для плиток (-1, 1) и (1, -1).

Таким образом, для кадой плитки список возможных соседей следующий (-1, -1), (-1, 0), (0, -1), (0, 1), (1, 0), (1, 1). Естественно, здесь имеются ввиду сдвиги относительной текущей координаты. Список довольно небольшой, поэтому мы будем использовать парные предикаты в условии соединения. Мы будем отбраковывать раскладку плиток, где встретятся две 6-ки или две 8-ки, разницы между координатами плиток входят в список соседей выше.

«Отбраковывать» раскладку — означает перетасовать массив очков и дать его на вход следующей итерации рекурсивного табличного выражения (CTE).

Создаем таблицу распределения очков:
SELECT  SETSEED(0.201703);   WITH    RECURSIVE         resources AS         (         SELECT  '////####^^^' || CHR(34) || CHR(34) || CHR(34) || CHR(34) || '... '::TEXT tile_resources         ),         tiles (rn, x, y) AS         (         VALUES                 (1, 0, 0),                 (2, 1, 0),                 (3, 2, 0),                 (4, 3, 1),                 (5, 4, 2),                 (6, 4, 3),                 (7, 4, 4),                 (8, 3, 4),                 (9, 2, 4),                 (10, 1, 3),                 (11, 0, 2),                 (12, 0, 1),                 (13, 1, 1),                 (14, 2, 1),                 (15, 3, 2),                 (16, 3, 3),                 (17, 2, 3),                 (18, 1, 2),                 (19, 2, 2)         ),         layout AS         (         SELECT  *,                 CASE resource                 WHEN ' ' THEN                         NULL                 ELSE                         rn + SUM(CASE resource WHEN ' ' THEN -1 ELSE 0 END) OVER (ORDER BY rn)                 END score_rn         FROM    tiles         JOIN    (                 SELECT  ROW_NUMBER() OVER (ORDER BY RANDOM()) rn,                         resource                 FROM    resources                 CROSS JOIN                         LATERAL                         REGEXP_SPLIT_TO_TABLE(tile_resources, '') q (resource)                 ) tr         USING   (rn)         ),         score AS         (         SELECT  1 attempt,                 ARRAY_AGG(s ORDER BY RANDOM()) score_array,                 NULL::BIGINT desert         FROM    generate_series(2, 12) s         CROSS JOIN                 generate_series(1, 2) r         WHERE   s <> 7                 AND NOT (r = 2 AND s IN (2, 12))         UNION ALL         SELECT  attempt + 1 attempt,                 sa.score_array,                 (                 SELECT  rn                 FROM    layout                 WHERE   score_rn IS NULL                 ) desert         FROM    (                 SELECT  *                 FROM    score                 WHERE   EXISTS                         (                         SELECT  NULL                         FROM    (                                 SELECT  *                                 FROM    UNNEST(score_array) WITH ORDINALITY q(s1, score_rn)                                 JOIN    layout                                 USING   (score_rn)                                 ) sc1                         JOIN    (                                 SELECT  *                                 FROM    UNNEST(score_array) WITH ORDINALITY q(s2, score_rn)                                 JOIN    layout                                 USING   (score_rn)                                 ) sc2                         ON      s1 IN (6, 8)                                 AND s2 IN (6, 8)                                 AND ((sc1.x - sc2.x), (sc1.y - sc2.y)) IN ((-1, -1), (-1, 0), (0, -1), (0, 1), (1, 0), (1, 1))                         )                 ) s         CROSS JOIN                 LATERAL                 (                 SELECT  ARRAY_AGG(score ORDER BY RANDOM()) score_array                 FROM    UNNEST(score_array) WITH ORDINALITY q(score, score_rn)                 ) sa         ),         score_good AS         (         SELECT  score, score_rn         FROM    (                 SELECT  *                 FROM    score                 ORDER BY                         attempt DESC                 LIMIT 1                 ) s         CROSS JOIN                 LATERAL                 UNNEST(score_array) WITH ORDINALITY q (score, score_rn)         ) SELECT  * FROM    score_good 
Вывод таблицы очков:
score   score_rn 8       1 2       2 6       3 10      4 11      5 11      6 9       7 3       8 4       9 8       10 9       11 4       12 12      13 5       14 10      15 5       16 3       17 6       18 

Мы добавили вызов SETSEED для того чтобы результаты были воспроизводимы.

Можно заметить, что запросу понадобилось 7 попыток на то,чтобы сгенерировать подходящую раскладку очков. В первой перестановке 6 и 8 были на соседних плитках 7 и 16; во второй дыло две 8-ки рядом на плитках 17 и 18 и т.д.

Как только мы получили корректную расстановку для карточек очков мы можем соединить её с остальными таблицами:

Совмещаем очки, координаты и ресурсы:
SELECT  SETSEED(0.201703);   WITH    RECURSIVE         resources AS         (         SELECT  '////####^^^' || CHR(34) || CHR(34) || CHR(34) || CHR(34) || '... '::TEXT tile_resources         ),         tiles (rn, x, y) AS         (         VALUES                 (1, 0, 0),                 (2, 1, 0),                 (3, 2, 0),                 (4, 3, 1),                 (5, 4, 2),                 (6, 4, 3),                 (7, 4, 4),                 (8, 3, 4),                 (9, 2, 4),                 (10, 1, 3),                 (11, 0, 2),                 (12, 0, 1),                 (13, 1, 1),                 (14, 2, 1),                 (15, 3, 2),                 (16, 3, 3),                 (17, 2, 3),                 (18, 1, 2),                 (19, 2, 2)         ),         layout AS         (         SELECT  *,                 CASE resource                 WHEN ' ' THEN                         NULL                 ELSE                         rn + SUM(CASE resource WHEN ' ' THEN -1 ELSE 0 END) OVER (ORDER BY rn)                 END score_rn         FROM    tiles         JOIN    (                 SELECT  ROW_NUMBER() OVER (ORDER BY RANDOM()) rn,                         resource                 FROM    resources                 CROSS JOIN                         LATERAL                         REGEXP_SPLIT_TO_TABLE(tile_resources, '') q (resource)                 ) tr         USING   (rn)         ),         score AS         (         SELECT  1 attempt,                 ARRAY_AGG(s ORDER BY RANDOM()) score_array,                 NULL::BIGINT desert         FROM    generate_series(2, 12) s         CROSS JOIN                 generate_series(1, 2) r         WHERE   s <> 7                 AND NOT (r = 2 AND s IN (2, 12))         UNION ALL         SELECT  attempt + 1 attempt,                 sa.score_array,                 (                 SELECT  rn                 FROM    layout                 WHERE   score_rn IS NULL                 ) desert         FROM    (                 SELECT  *                 FROM    score                 WHERE   EXISTS                         (                         SELECT  NULL                         FROM    (                                 SELECT  *                                 FROM    UNNEST(score_array) WITH ORDINALITY q(s1, score_rn)                                 JOIN    layout                                 USING   (score_rn)                                 ) sc1                         JOIN    (                                 SELECT  *                                 FROM    UNNEST(score_array) WITH ORDINALITY q(s2, score_rn)                                 JOIN    layout                                 USING   (score_rn)                                 ) sc2                         ON      s1 IN (6, 8)                                 AND s2 IN (6, 8)                                 AND ((sc1.x - sc2.x), (sc1.y - sc2.y)) IN ((-1, -1), (-1, 0), (0, -1), (0, 1), (1, 0), (1, 1))                         )                 ) s         CROSS JOIN                 LATERAL                 (                 SELECT  ARRAY_AGG(score ORDER BY RANDOM()) score_array                 FROM    UNNEST(s.score_array) WITH ORDINALITY q(score, score_rn)                 ) sa         ),         score_good AS         (         SELECT  score, score_rn, attempt         FROM    (                 SELECT  *                 FROM    score                 ORDER BY                         attempt DESC                 LIMIT 1                 ) s         CROSS JOIN                 LATERAL                 UNNEST(score_array) WITH ORDINALITY q (score, score_rn)         ) SELECT  * FROM    layout LEFT JOIN         score_good USING   (score_rn) ORDER BY         rn; 
Вывод соответствующей таблицы:
score_rn        rn      x       y       resource        score   attempt 1                1      0       0       #               11      5 2                2      1       0       "               11      5 3                3      2       0       .               8       5 4                4      3       1       #               10      5 5                5      4       2       /               8       5 6                6      4       3       "               3       5 7                7      4       4       ^               3       5 None             8      3       4                       None    None 8                9      2       4       /               4       5 9               10      1       3       /               5       5 10              11      0       2       #               9       5 11              12      0       1       #               6       5 12              13      1       1       "               9       5 13              14      2       1       ^               4       5 14              15      3       2       .               2       5 15              16      3       3       ^               5       5 16              17      2       3       "               10      5 17              18      1       2       /               12      5 18              19      2       2       .               6       5 

Гавани

С карточками гаваней нет таких проблем — их расположение полностью случайно. Нам надо только записать их позиции в одну строку, разделить строку на отдельные символы и случайным образом перемешать получившуюся таблицу.

Формально гавани не должны располагаться в шестиугольниках, однако они находятся точно там, где должны быть центры шестиугольников, если бы они были за пределами основного игрового поля. Мы можем использовать координаты в нашей сетке, для того чтобы закодировать местоположение гаваней. Также мы добавим два числа для ориентировки причалов (это поможет нам с отрисовкой в дальнейшем).

Запрос для расположения гаваней:
WITH    RECURSIVE         resources AS         (         SELECT  '////####^^^' || CHR(34) || CHR(34) || CHR(34) || CHR(34) || '... '::TEXT tile_resources,                 '/#^' || CHR(34) || '.????'::TEXT harbor_resources         ),         harbors (rn, x, y, pier1, pier2) AS         (         VALUES                 (1, -1, -1, 0, 1),                 (2, 1, -1, 1, 2),                 (3, 3, 0, 1, 2),                 (4, 5, 2, 2, 3),                 (5, 5, 4, 3, 4),                 (6, 4, 5, 3, 4),                 (7, 2, 5, 4, 5),                 (8, 0, 3, 5, 0),                 (9, -1, 1, 5, 0)         ),         harbor_resources AS         (         SELECT  '/#>".????'::TEXT harbor_resources         ) SELECT  resource, rn, x, y, pier1, pier2 FROM    harbors CROSS JOIN         resources JOIN    LATERAL         (         SELECT  resource, ROW_NUMBER() OVER (ORDER BY RANDOM()) rn         FROM    REGEXP_SPLIT_TO_TABLE(harbor_resources, '') q (resource)         ) q USING   (rn) ORDER BY         RANDOM() 
Вывод таблицы с расположением гаваней:
resource        rn      x       y       pier1   pier2 #               3       3       0       1       2 "               5       5       4       3       4 ?               1       -1      -1      0       1 ?               8       0       3       5       0 ^               4       5       2       2       3 /               9       -1      1       5       0 ?               7       2       5       4       5 ?               6       4       5       3       4 .               2       1       -1      1       2 

Собираем всё это вместе

Теперь у нас есть вся информация, для того чтобы запустить игру. Однако, было бы здорово отрисовать получившееся игровое поле.

Мы будем отрисовывать шестиугольники, используя различные символы для каждого вида ресурсов. Внутри каждого шестиугольника мы расположим карточку с очками. Внутри каждого шестиугольника будет ещё один маленький шестиугольник, чтобы очки было видно. Наконец, мы отрисуем гавани и причалы.

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

Для шестиугольников мы сначала сгенерируем квадрат с помощью перекрестного соединения (cross-join) двух таблиц GENERATE_SERIES, а затем отфильтруем результат, чтобы получился шестиугольник.

Центры каждого шестиугольника рассчитываются из их координат х и у: х берется как есть, а у сдвигается на половину соответствующего значения х (поскольку координатные оси находятся по углом 60 градусов друг к другу).

Условие фильтра для шестиугольника следующее: если символ находится в промежутке -1/4 до 1/4 высоты, считая от центра, мы выводим этот символ; если символ в промежутке -1/2 до -1/4 (или 1/4 до 1/2 с другой стороны), мы выводим его только в том случае, если его горизонтальная координата не больше чем два расстояния с верху до низу, соответственно. Первая часть формирует прямоугольник внутри шестиугольника; вторая часть формирует два треугольника — сверху и снизу.

Маленькие шестиугольники строятся по тому же принципу, только их высота и широта меньше.

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

Нам также надо как-то вывести гавани и причалы. Символы для гавани выводятся по соответствующим координатам на сетке (учитывая предыдущие рассуждения). Причалы будут сдвинуты на приблизительные значения синусов и косинусов их соответствующих углов (записанные как множители 60 градусов)ю Мы не будем считать синусы и косинусами, а вместо это воспользуемся таблицами приближенных значений.

Некоторые из символов будут накладываться друг на друга: например, центр каждой плитки будет содержать символ из большого шестиугольника, символ из маленького шестиугольника и символ ресурса. Чтобы решить эту проблему, каждому символу мы присвоим номер слоя, на котором он находится. Большие шестиугольники будут на слое № 1, маленькие шестиугольники на слое № 2, а символы очков на самом верхнем 3 слое. Если несколько символов имеют одни координаты, выводится символ с наибольшим номером слоя. Мы будем выводить их все в одном запросе, поэтому гаваням и причалам тоже понадобится слой, даже если они ни с чем не пересекаются. Мы дадим им 4 слой.

Когда карты символов готовы, мы можем сгенерировать поле с помощью перекрестного соединения двух таблиц GENERATE_SERIES, сделать левое соединение с картами символов и заменить все NULL значения на пробелы. Затем мы сгруппируем все символы в строки и выведем все строки по порядку.

Вот, что получается:

Запрос для построения поля целиком:
SELECT  SETSEED(0.201704);   WITH    RECURSIVE         resources AS         (         SELECT  '////####^^^' || CHR(34) || CHR(34) || CHR(34) || CHR(34) || '... '::TEXT tile_resources,                 '/#^' || CHR(34) || '.????'::TEXT harbor_resources         ),         tiles (rn, x, y) AS         (         VALUES                 (1, 0, 0),                 (2, 1, 0),                 (3, 2, 0),                 (4, 3, 1),                 (5, 4, 2),                 (6, 4, 3),                 (7, 4, 4),                 (8, 3, 4),                 (9, 2, 4),                 (10, 1, 3),                 (11, 0, 2),                 (12, 0, 1),                 (13, 1, 1),                 (14, 2, 1),                 (15, 3, 2),                 (16, 3, 3),                 (17, 2, 3),                 (18, 1, 2),                 (19, 2, 2)         ),         harbors (rn, x, y, pier1, pier2) AS         (         VALUES                 (1, -1, -1, 0, 1),                 (2, 1, -1, 1, 2),                 (3, 3, 0, 1, 2),                 (4, 5, 2, 2, 3),                 (5, 5, 4, 3, 4),                 (6, 4, 5, 3, 4),                 (7, 2, 5, 4, 5),                 (8, 0, 3, 5, 0),                 (9, -1, 1, 5, 0)         ),         score AS         (         SELECT  1 attempt,                 ARRAY_AGG(s ORDER BY RANDOM()) score_array         FROM    generate_series(2, 12) s         CROSS JOIN                 generate_series(1, 2) r         WHERE   s <> 7                 AND NOT (r = 2 AND s IN (2, 12))         UNION ALL         SELECT  attempt + 1 attempt,                 sa.score_array         FROM    (                 SELECT  *                 FROM    score                 WHERE   EXISTS                         (                         SELECT  NULL                         FROM    (                                 SELECT  *                                 FROM    UNNEST(score_array) WITH ORDINALITY q(s1, rn)                                 JOIN    tiles                                 USING   (rn)                                 ) sc1                         JOIN    (                                 SELECT  *                                 FROM    UNNEST(score_array) WITH ORDINALITY q(s2, rn)                                 JOIN    tiles t                                 USING   (rn)                                 ) sc2                         ON      s1 IN (6, 8)                                 AND s2 IN (6, 8)                                 AND ((sc1.x - sc2.x), (sc1.y - sc2.y)) IN ((-1, -1), (-1, 0), (0, -1), (0, 1), (1, 0), (1, 1))                         )                 ) s         CROSS JOIN                 LATERAL                 (                 SELECT  ARRAY_AGG(score ORDER BY RANDOM()) score_array                 FROM    UNNEST(score_array) WITH ORDINALITY q(score, score_rn)                 ) sa         ),         score_good AS         (         SELECT  score, score_rn         FROM    (                 SELECT  *                 FROM    score                 ORDER BY                         attempt DESC                 LIMIT 1                 ) s         CROSS JOIN                 LATERAL                 UNNEST(score_array) WITH ORDINALITY q (score, score_rn)         ),         layout AS         (         SELECT  *         FROM    (                 SELECT  *,                         CASE resource                         WHEN ' ' THEN                                 NULL                         ELSE                                 rn + SUM(CASE resource WHEN ' ' THEN -1 ELSE 0 END) OVER (ORDER BY rn)                         END score_rn                 FROM    tiles                 JOIN    (                         SELECT  ROW_NUMBER() OVER (ORDER BY RANDOM()) rn,                                 resource                         FROM    resources                         CROSS JOIN                                 LATERAL                                 REGEXP_SPLIT_TO_TABLE(tile_resources, '') q (resource)                         ) tr                 USING   (rn)                 ) t         LEFT JOIN                 score_good         USING   (score_rn)         ORDER BY                 rn         ) SELECT  row FROM    (         SELECT  r,                 STRING_AGG(COALESCE(letter, ' '), '' ORDER BY c) AS row         FROM    generate_series(0, 70) r         CROSS JOIN                 generate_series(0, 89) c         LEFT JOIN                 (                 SELECT  *                 FROM    (                         SELECT  *,                                 ROW_NUMBER() OVER (PARTITION BY r, c ORDER BY layer DESC) rn                         FROM    (                                 SELECT  10 height,                                         16 width                                 ) d                         CROSS JOIN                                 LATERAL                                 (                                 SELECT  letter, r, c, layer                                 FROM    layout                                 CROSS JOIN                                         LATERAL                                         (                                         SELECT  height * x + 15 center_r,                                                 width * y - (width / 2)::INT * x + 24 center_c                                         ) c                                 CROSS JOIN                                         LATERAL                                         (                                         SELECT  *                                         FROM    (                                                 SELECT  1 layer, resource letter, center_r + rs r, center_c + cs c                                                 FROM    (                                                         SELECT  height * 1.5 * 0.8 th, width * 0.9 tw                                                         ) t                                                 CROSS JOIN                                                         generate_series(-(th / 2)::INT, (th / 2)::INT) rs                                                 CROSS JOIN                                                         generate_series(-(tw / 2)::INT, (tw / 2)::INT ) cs                                                 CROSS JOIN                                                         LATERAL                                                         (                                                         SELECT  rs::FLOAT / th rsf, cs::FLOAT / tw csf                                                         ) f                                                 WHERE   rsf BETWEEN -0.25 AND 0.25                                                         OR                                                         ABS(csf) BETWEEN 0 AND 1 - ABS(rsf * 2)                                                 UNION ALL                                                 SELECT  2 layer, ' ', center_r + rs r, center_c + cs c                                                 FROM    (                                                         SELECT  height * 1.5 * 0.35 th, width * 0.35 tw                                                         ) t                                                 CROSS JOIN                                                         generate_series(-(th / 2)::INT, (th / 2)::INT) rs                                                 CROSS JOIN                                                         generate_series(-(tw / 2)::INT, (tw / 2)::INT ) cs                                                 CROSS JOIN                                                         LATERAL                                                         (                                                         SELECT  rs::FLOAT / th rsf, cs::FLOAT / tw csf                                                         ) f                                                 WHERE   rsf BETWEEN -0.25 AND 0.25                                                         OR                                                         ABS(csf) BETWEEN 0 AND 1 - ABS(rsf * 2)                                                 UNION ALL                                                 SELECT  3 layer, score_letter letter, center_r r, center_c + pos - 1 c                                                 FROM    REGEXP_SPLIT_TO_TABLE(score::TEXT, '') WITH ORDINALITY l(score_letter, pos)                                                 ) q                                         ) q2                                 UNION ALL                                 SELECT  letter, r, c, 4 layer                                 FROM    harbors                                 JOIN    LATERAL                                         (                                         SELECT  resource, ROW_NUMBER() OVER (ORDER BY RANDOM()) rn                                         FROM    resources                                         CROSS JOIN                                                 LATERAL                                                 REGEXP_SPLIT_TO_TABLE(harbor_resources, '') q (resource)                                         ) q2                                 USING   (rn)                                 CROSS JOIN                                         LATERAL                                         (                                         SELECT  height * x + 15 center_r,                                                 width * y - (width / 2)::INT * x + 25 center_c                                         ) c                                 CROSS JOIN                                         LATERAL                                         (                                         SELECT  resource letter, center_r r, center_c c                                         UNION ALL                                         SELECT  letter, r, c                                         FROM    (                                                 SELECT  pier1                                                 UNION ALL                                                 SELECT  pier2                                                 ) p (pier)                                         CROSS JOIN                                                 LATERAL                                                 (                                                 SELECT  SUBSTRING('|\/|\/', (pier + 1), 1) letter,                                                         center_r + ((ARRAY[0.4, 0.2, -0.2, -0.4, -0.2, 0.2])[pier + 1] * height * 1.5 * 0.8)::INT r,                                                         center_c + ((ARRAY[0, 0.3, 0.3, 0, -0.3, -0.3])[pier + 1] * width * 0.9)::INT c                                                 ) pl                                         ) p2                                 ) q3                         ) l                         WHERE   rn = 1                 ) t         USING   (r, c)         GROUP BY                 r         ) q ORDER BY         r 
Игровое поле:
                 .                               ^                                                                                                                                                         \                       /                                                                                                                                                                /               .               "                                                   |    /////           .....      |    """""                                                    /////////       .........       """""""""                                               /////////////// ............... """""""""""""""                                            //////   ////// ......   ...... """"""   """"""                                            ////       //// ....       .... """"       """"                                            ////   5   //// ....   11  .... """"   8   """"         "                                  ////       //// ....       .... """"       """"                                            //////   ////// ......   ...... """"""   """"""     /                                      /////////////// ............... """""""""""""""                                           "   /////////   "   .........   ^   """""""""   "                                        """""   /////   """""   .....   ^^^^^   """""   """""      |                             """""""""   /   """""""""   .   ^^^^^^^^^   "   """""""""                               """"""""""""""" """"""""""""""" ^^^^^^^^^^^^^^^ """""""""""""""                        /   """"""   """""" """"""   """""" ^^^^^^   ^^^^^^ """"""   """"""                            """"       """" """"       """" ^^^^       ^^^^ """"       """"                    ?       """"   9   """" """"   5   """" ^^^^   6   ^^^^ """"   4   """"                            """"       """" """"       """" ^^^^       ^^^^ """"       """"                        \   """"""   """""" """"""   """""" ^^^^^^   ^^^^^^ """"""   """"""                            """"""""""""""" """"""""""""""" ^^^^^^^^^^^^^^^ """""""""""""""                           ^   """""""""       """""""""   /   ^^^^^^^^^   .   """""""""   #                        ^^^^^   """""           """""   /////   ^^^^^   .....   """""   #####                    ^^^^^^^^^   "               "   /////////   ^   .........   "   #########               ^^^^^^^^^^^^^^^                 /////////////// ............... ###############            ^^^^^^   ^^^^^^                 //////   ////// ......   ...... ######   ######     \      ^^^^       ^^^^                 ////       //// ....       .... ####       ####            ^^^^   11  ^^^^                 ////   3   //// ....   9   .... ####   12  ####         ?  ^^^^       ^^^^                 ////       //// ....       .... ####       ####            ^^^^^^   ^^^^^^                 //////   ////// ......   ...... ######   ######     /      ^^^^^^^^^^^^^^^                 /////////////// ............... ###############               ^^^^^^^^^   #               /   /////////   #   .........   #   #########                    ^^^^^   #####           /////   /////   #####   .....   #####   #####                        ^   #########       /////////   /   #########   .   #########   #                           ############### /////////////// ############### ###############                        /   ######   ###### //////   ////// ######   ###### ######   ######                            ####       #### ////       //// ####       #### ####       ####                    /       ####   6   #### ////   10  //// ####   4   #### ####   2   ####                            ####       #### ////       //// ####       #### ####       ####                        \   ######   ###### //////   ////// ######   ###### ######   ######                            ############### /////////////// ############### ###############                               #########   /   /////////   .   #########   ^   #########                                    #####   /////   /////   .....   #####   ^^^^^   #####      |                                 #   /////////   /   .........   #   ^^^^^^^^^   #                                           /////////////// ............... ^^^^^^^^^^^^^^^                                            //////   ////// ......   ...... ^^^^^^   ^^^^^^     \                                      ////       //// ....       .... ^^^^       ^^^^                                            ////   10  //// ....   8   .... ^^^^   3   ^^^^         #                                  ////       //// ....       .... ^^^^       ^^^^                                            //////   ////// ......   ...... ^^^^^^   ^^^^^^                                            /////////////// ............... ^^^^^^^^^^^^^^^                                               /////////       .........       ^^^^^^^^^                                               |    /////           .....      |    ^^^^^                                                        /               .               ^                                                                                                                                                  /                       \                                                                                                                                                         ?                               ?                                         

Итак, мы построили игровое поле для игры Колонизаторы, учитывая, что карточки суши могут иметь пять типов ресурсов, очки надо распределять рандомно по всему игровому полю, одну карточку суши надо оставить пустой, потому что там пустыня. Также мы учли карточки гаваней и расположение портов в каждой из гаваней. И всё это на диагональной сетке, поскольку поле и карточки шестиугольные.

Новый Год уже не за горами, поэтому пусть он принесет всем читателям много приятных часов, проведенных за этой игрой с друзьями или семьёй!


ссылка на оригинал статьи https://habr.com/ru/articles/866586/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *