%% polyomino.sty
%% Copyright 2024-2025 Matthias Floré
%
% This work may be distributed and/or modified under the
% conditions of the LaTeX Project Public License, either version 1.3c
% of this license or (at your option) any later version.
% The latest version of this license is in
%
http://www.latex-project.org/lppl.txt
% and version 1.3c or later is part of all distributions of LaTeX
% version 2005/12/01 or later.
%
% This work has the LPPL maintenance status `maintained'.
%
% The Current Maintainer of this work is Matthias Floré.
%
% This work consists of the files polyomino.pdf, polyomino.sty,
% polyomino.tex and README.md.
\NeedsTeXFormat{LaTeX2e}
\RequirePackage{tikz}
\ProvidesExplPackage{polyomino}{2025/07/24}{2.0}{Polyominoes using TikZ and LaTeX3}
%%> \subsection{Variables and variants}
\bool_new:N \l__polyomino_grid_bool
\bool_new:N \l__polyomino_pic_bool
\int_new:N \l__polyomino_col_int
\int_new:N \l__polyomino_dir_int
\int_new:N \l__polyomino_max_int
\int_new:N \l__polyomino_row_int
\int_new:N \l__polyomino_x_int
\int_new:N \l__polyomino_y_int
\seq_new:N \l__polyomino_add_seq
\seq_new:N \l__polyomino_cols_seq
\tl_new:N \l__polyomino_path_tl
\tl_new:N \l__polyomino_pic_tl
\cs_generate_variant:Nn \tl_map_inline:nn { en }
%%> \subsection{Pgfkeys}
\pgfkeys
{
/ polyomino /. is~family ,
/ polyomino /. search~also = / polyomino / p_2 ,
/ polyomino ,
at /. initial = { ( 0 , 0 ) } ,
empty~cell /. initial = . ,
grid /. code = \bool_set:Nn \l__polyomino_grid_bool { \cs:w c_#1_bool \cs_end: } ,
grid /. default = true ,
grid = false ,
grid~style /. style = { grid_style /. style = {#1} } ,
grid_style /. style = {} ,
row~sep /. initial = { , } ,
}
\pgfkeys
{%a separate key family so that the second argument of the key p only accepts keys which apply to a separate polyomino
/ polyomino / p_2 /. is~family ,
/ polyomino / p_2 ,
connected /. code = \bool_set_false:N \l__polyomino_pic_bool ,
connected /. value~forbidden ,
discrete /. code = \bool_set_true:N \l__polyomino_pic_bool ,
discrete /. value~forbidden ,
p /. style~2~args = { #1__style /. style = {#2} } ,%2 underscores to avoid the same name as for example the key style_style
pic /. code =
{
\bool_set_true:N \l__polyomino_pic_bool
\tl_set:Nn \l__polyomino_pic_tl {#1}
} ,
style /. style = { style_style /. style = {#1} } ,
style_style /. style = {} ,
}
%%> \subsection{The command \textbackslash polyomino}
\NewDocumentCommand \polyomino { O {} m }
{
{%note the double braces {{...}} so that the contents is in a group and in particular, \pgfkeys is applied locally
\pgfkeys { / polyomino , #1 }
\int_zero:N \l__polyomino_col_int
\int_set:Nn \l__polyomino_row_int { 1 }
\seq_clear:N \l__polyomino_cols_seq
\tl_map_inline:en {#2}
%it is convenient that this ignores spaces in #2
%e argument specifier for the case that #2 is given by a command or contains a command
{
\tl_if_eq:neTF {##1} { \pgfkeysvalueof { / polyomino / row~sep } }
{
\seq_put_right:NV \l__polyomino_cols_seq \l__polyomino_col_int
\int_incr:N \l__polyomino_row_int
\int_zero:N \l__polyomino_col_int
}
{
\int_incr:N \l__polyomino_col_int
\tl_clear_new:c { l__polyomino_\int_use:N \l__polyomino_row_int _\int_use:N \l__polyomino_col_int _tl }
\tl_if_eq:neF {##1} { \pgfkeysvalueof { / polyomino / empty~cell } }
{ \tl_set:cn { l__polyomino_\int_use:N \l__polyomino_row_int _\int_use:N \l__polyomino_col_int _tl } {##1} }
\tl_gclear_new:c { g__polyomino_\int_use:N \l__polyomino_row_int _\int_use:N \l__polyomino_col_int _tl }
}
}
\seq_put_right:NV \l__polyomino_cols_seq \l__polyomino_col_int
\int_set:Nn \l__polyomino_max_int { \fp_eval:n { max ( \seq_use:Nn \l__polyomino_cols_seq { , } ) } }
\seq_map_indexed_inline:Nn \l__polyomino_cols_seq
{
\tl_clear_new:c { l__polyomino_##1_0_tl }
\int_step_inline:nnn { ##2 + 1 } { \l__polyomino_max_int + 1 }
{ \tl_clear_new:c { l__polyomino_##1_####1_tl } }
}
\int_step_inline:nnn { 0 } { \l__polyomino_max_int + 1 }
{
\tl_clear_new:c { l__polyomino_0_##1_tl }
\tl_clear_new:c { l__polyomino_\int_eval:n { \l__polyomino_row_int + 1 }_##1_tl }
}
\pgfkeys
{
/ tikz ,
shift /. expanded = { \pgfkeysvalueof { / polyomino / at } } ,
shift = { ( 0 , \seq_count:N \l__polyomino_cols_seq ) }
}
\seq_map_indexed_inline:Nn \l__polyomino_cols_seq
{
\int_step_inline:nn {##2}
{
\tl_if_empty:cF { l__polyomino_##1_####1_tl }
{
{%note the double braces {{...}} so that \pgfkeys is applied locally
\pgfkeys { / polyomino / p_2 , \cs:w l__polyomino_##1_####1_tl \cs_end: __style }
\bool_if:NTF \l__polyomino_pic_bool
{ \pic [ / polyomino / p_2 / style_style ] at ( ####1 - 0.5 , 0.5 - ##1 ) { code = { \l__polyomino_pic_tl } } ; }
{
\seq_clear:N \l__polyomino_add_seq
\tl_if_eq:ccF { l__polyomino_##1_####1_tl } { l__polyomino_##1_\int_eval:n { ####1 - 1 }_tl }
{
\tl_if_empty:cT { g__polyomino_##1_####1_tl }
{
\int_set:Nn \l__polyomino_dir_int { 1 }
\int_set:Nn \l__polyomino_col_int {####1}
\int_set:Nn \l__polyomino_row_int {##1}
\int_set:Nn \l__polyomino_x_int {####1}
\int_set:Nn \l__polyomino_y_int { 1 - ##1 }
\tl_build_begin:N \l__polyomino_path_tl
\fp_do_until:nn { ####1 - 1 = \l__polyomino_x_int && 1 - ##1 = \l__polyomino_y_int }
{
%concerning \tl_build_put_right:Ne \l__polyomino_path_tl,
%for example (0,0)--(0,1)--(0,2) results in a larger file size than (0,0)--(0,2)
\tl_if_eq:ccTF
{ l__polyomino_##1_####1_tl }
{
l__polyomino
_\int_eval:n
{ \l__polyomino_row_int + \clist_item:nn { 0 , 1 , 0 , -1 } { \l__polyomino_dir_int } }
_\int_eval:n
{ \l__polyomino_col_int + \clist_item:nn { 1 , 0 , -1 , 0 } { \l__polyomino_dir_int } }
_tl
}
{
\tl_if_eq:ccTF
{ l__polyomino_##1_####1_tl }
{
l__polyomino
_\int_eval:n
{ \l__polyomino_row_int + \clist_item:nn { -1 , 1 , 1 , -1 } { \l__polyomino_dir_int } }
_\int_eval:n
{ \l__polyomino_col_int + \clist_item:nn { 1 , 1 , -1 , -1 } { \l__polyomino_dir_int } }
_tl
}
{
\tl_build_put_right:Ne \l__polyomino_path_tl
{ -- ( \int_use:N \l__polyomino_x_int , \int_use:N \l__polyomino_y_int ) }
\int_add:Nn \l__polyomino_row_int
{ \clist_item:nn { -1 , 1 , 1 , -1 } { \l__polyomino_dir_int } }
\int_add:Nn \l__polyomino_col_int
{ \clist_item:nn { 1 , 1 , -1 , -1 } { \l__polyomino_dir_int } }
\int_compare:nNnTF { \l__polyomino_dir_int } = { 1 }
{ \int_set:Nn \l__polyomino_dir_int { 4 } }
{ \int_decr:N \l__polyomino_dir_int }
}
{
\int_add:Nn \l__polyomino_row_int
{ \clist_item:nn { 0 , 1 , 0 , -1 } { \l__polyomino_dir_int } }
\int_add:Nn \l__polyomino_col_int
{ \clist_item:nn { 1 , 0 , -1 , 0 } { \l__polyomino_dir_int } }
}
\tl_if_empty:cTF
{ g__polyomino_\int_use:N \l__polyomino_row_int _\int_use:N \l__polyomino_col_int _tl }
{
\seq_put_right:Ne \l__polyomino_add_seq
{ \int_use:N \l__polyomino_row_int _\int_use:N \l__polyomino_col_int }
}
{
\bool_set_true:N \l__polyomino_pic_bool
\int_set:Nn \l__polyomino_x_int { ####1 - 1 }
\int_set:Nn \l__polyomino_y_int { 1 - ##1 }
}
}
{
\tl_build_put_right:Ne \l__polyomino_path_tl
{ -- ( \int_use:N \l__polyomino_x_int , \int_use:N \l__polyomino_y_int ) }
\int_compare:nNnTF { \l__polyomino_dir_int } = { 4 }
{ \int_set:Nn \l__polyomino_dir_int { 1 } }
{ \int_incr:N \l__polyomino_dir_int }
}
\bool_if:NF \l__polyomino_pic_bool
{
\int_add:Nn \l__polyomino_x_int { \clist_item:nn { 1 , 0 , -1 , 0 } { \l__polyomino_dir_int } }
\int_add:Nn \l__polyomino_y_int { \clist_item:nn { 0 , -1 , 0 , 1 } { \l__polyomino_dir_int } }
}
}
\tl_build_end:N \l__polyomino_path_tl
\bool_if:NF \l__polyomino_pic_bool
{ \fill [ / polyomino / p_2 / style_style ] ( ####1 - 1 , 1 - ##1 ) \l__polyomino_path_tl -- cycle ; }
}
}
\tl_gset:cn { g__polyomino_##1_####1_tl } { c }
\seq_map_inline:Nn \l__polyomino_add_seq
{ \tl_gset:cn { g__polyomino_########1_tl } { c } }
}
}
}
}
}
\bool_if:NT \l__polyomino_grid_bool
{
\int_step_inline:nn { \seq_count:N \l__polyomino_cols_seq - 1 }
{
\int_zero:N \l__polyomino_col_int
\int_zero:N \l__polyomino_x_int
\int_set:Nn \l__polyomino_y_int
{ \int_min:nn { \seq_item:Nn \l__polyomino_cols_seq {##1} } { \seq_item:Nn \l__polyomino_cols_seq { ##1 + 1 } } }
\int_while_do:nNnn { \l__polyomino_x_int } < { \l__polyomino_y_int }
{
\bool_do_while:nn
{
\tl_if_eq_p:cc
{ l__polyomino_##1_\int_use:N \l__polyomino_x_int _tl }
{ l__polyomino_\int_eval:n { ##1 + 1 }_\int_use:N \l__polyomino_x_int _tl }
&&
! \tl_if_empty_p:c { g__polyomino_##1_\int_use:N \l__polyomino_x_int _tl }
&&
\int_compare_p:nNn { \l__polyomino_x_int } < { \l__polyomino_y_int + 1 }
}
{ \int_incr:N \l__polyomino_x_int }
\int_compare:nNnT { \l__polyomino_x_int } > { \l__polyomino_col_int + 1 }
{
\draw [ / polyomino / grid_style ]
( \int_use:N \l__polyomino_col_int , -##1 ) -- ( \int_use:N \l__polyomino_x_int - 1 , -##1 ) ;
}
\int_set_eq:NN \l__polyomino_col_int \l__polyomino_x_int
}
}
\int_set:Nn \l__polyomino_x_int { \seq_count:N \l__polyomino_cols_seq }
\int_step_inline:nn { \l__polyomino_max_int - 1 }
{
\int_zero:N \l__polyomino_row_int
\int_zero:N \l__polyomino_y_int
\int_while_do:nNnn { \l__polyomino_y_int } < { \l__polyomino_x_int }
{
\bool_do_while:nn
{
\tl_if_eq_p:cc
{ l__polyomino_\int_use:N \l__polyomino_y_int _##1_tl }
{ l__polyomino_\int_use:N \l__polyomino_y_int _\int_eval:n { ##1 + 1 }_tl }
&&
! \tl_if_empty_p:c { g__polyomino_\int_use:N \l__polyomino_y_int _##1_tl }
&&
\int_compare_p:nNn { \l__polyomino_y_int } < { \l__polyomino_x_int + 1 }
&&
\int_compare_p:nNn {##1} < { \seq_item:Nn \l__polyomino_cols_seq { \l__polyomino_y_int } + 0 }
}
{ \int_incr:N \l__polyomino_y_int }
\int_compare:nNnT { \l__polyomino_y_int } > { \l__polyomino_row_int + 1 }
{
\draw [ / polyomino / grid_style ]
( ##1 , -\int_use:N \l__polyomino_row_int ) -- ( ##1 , 1 - \int_use:N \l__polyomino_y_int ) ;
}
\int_set_eq:NN \l__polyomino_row_int \l__polyomino_y_int
}
}
}
}
}
\endinput