% Copyright (C) 2026 by Sicheng Du % This project is distributed under the LaTeX Project Public License, version 1.3c. %-------------------------% \NeedsTeXFormat{LaTeX2e} \ProvidesPackage{maze}[1.4] \RequirePackage{xcolor} \ExplSyntaxOn \int_new:N\l_maze_rand_int \int_new:N\l_maze_old_int \int_new:N\l_maze_new_int \int_new:N\l_maze_size_int \tl_new:N\l_maze_cell_tl \dim_const:Nn\g_maze_size_dim{\linewidth} \intarray_new:Nn\g_maze_map_intarray{10000} \intarray_new:Nn\g_walls_v_intarray{9900} \intarray_new:Nn\g_walls_h_intarray{9900} \intarray_new:Nn\g_maze_parent_intarray{10000} \box_new:N\l_maze_store_box \bool_new:N\l_maze_size_valid_bool \bool_new:N\l_maze_solution_found_bool \bool_new:N\l_maze_solution_first_bool \bool_new:N\l_maze_use_solution_bool \seq_new:N\l_maze_solution_seq \seq_new:N\l_maze_stack_seq \tl_new:N\l_maze_solution_view_tl \int_new:N\l_maze_previous_index_int \msg_new:nnn{maze}{undefined-maze}{Maze~`#1'~has~not~been~stored.} \msg_new:nnn{maze}{invalid-size}{Maze~size~`#1'~must~be~an~integer~in~the~range~2--99.} \cs_new_protected:Npn\maze_validate_size:n#1{ \bool_set_true:N\l_maze_size_valid_bool \int_compare:nNnTF{#1}<{2}{ \bool_set_false:N\l_maze_size_valid_bool \msg_error:nnn{maze}{invalid-size}{#1} }{ \int_compare:nNnTF{#1}>{99}{ \bool_set_false:N\l_maze_size_valid_bool \msg_error:nnn{maze}{invalid-size}{#1} }{} } } \cs_new_protected:Npn\maze_generate:nn #1#2{ \sys_gset_rand_seed:n{#2} \intarray_gzero:N\g_maze_map_intarray \intarray_gzero:N\g_walls_v_intarray \intarray_gzero:N\g_walls_h_intarray \int_step_inline:nn{#1*#1}{ \intarray_gset:Nnn\g_maze_map_intarray{##1}{##1} } \int_step_inline:nn{#1*(#1-1)}{ \intarray_gset:Nnn\g_walls_v_intarray{##1}{1} \intarray_gset:Nnn\g_walls_h_intarray{##1}{1} } \bool_do_until:nn{ \int_compare_p:nNn{\intarray_item:Nn\g_maze_map_intarray{1}} ={\intarray_item:Nn\g_maze_map_intarray{#1*#1}} }{ \int_set:Nn\l_maze_rand_int{\int_rand:n{#1*(#1-1)}} \int_compare:nNnTF{0}={\intarray_item:Nn\g_walls_v_intarray{\l_maze_rand_int}}{}{ \int_compare:nNnTF{ \intarray_item:Nn\g_maze_map_intarray{ \l_maze_rand_int+\int_div_truncate:nn{\l_maze_rand_int-1}{#1-1} } }={ \intarray_item:Nn\g_maze_map_intarray{ 1+\l_maze_rand_int+\int_div_truncate:nn{\l_maze_rand_int-1}{#1-1} } }{}{ \int_set:Nn\l_maze_new_int{ \intarray_item:Nn\g_maze_map_intarray{ 1+\l_maze_rand_int+\int_div_truncate:nn{\l_maze_rand_int-1}{#1-1} } } \int_set:Nn\l_maze_old_int{ \intarray_item:Nn\g_maze_map_intarray{ \l_maze_rand_int+\int_div_truncate:nn{\l_maze_rand_int-1}{#1-1} } } \intarray_gset:Nnn\g_walls_v_intarray{\l_maze_rand_int}{0} \int_step_inline:nn{#1*#1}{ \int_compare:nNnTF{\l_maze_old_int}={\intarray_item:Nn\g_maze_map_intarray{##1}} {\intarray_gset:Nnn\g_maze_map_intarray{##1}{\l_maze_new_int}}{} } } } \int_set:Nn\l_maze_rand_int{\int_rand:n{#1*(#1-1)}} \int_compare:nNnTF{0}={\intarray_item:Nn\g_walls_h_intarray{\l_maze_rand_int}}{}{ \int_compare:nNnTF{\intarray_item:Nn\g_maze_map_intarray{\l_maze_rand_int}} ={\intarray_item:Nn\g_maze_map_intarray{#1+\l_maze_rand_int}}{}{ \int_set:Nn\l_maze_new_int{ \intarray_item:Nn\g_maze_map_intarray{#1+\l_maze_rand_int} } \int_set:Nn\l_maze_old_int{ \intarray_item:Nn\g_maze_map_intarray{\l_maze_rand_int} } \intarray_gset:Nnn\g_walls_h_intarray{\l_maze_rand_int}{0} \int_step_inline:nn{#1*#1}{ \int_compare:nNnTF{\l_maze_old_int}={\intarray_item:Nn\g_maze_map_intarray{##1}} {\intarray_gset:Nnn\g_maze_map_intarray{##1}{\l_maze_new_int}}{} } } } } } \cs_new_protected:Npn\maze_draw_picture:nn#1#2{ \setlength{\unitlength}{\fp_eval:n{.4/#1}\g_maze_size_dim} \begin{picture}(#1,#1)(0,0) \put(0,0){\line(0,1){#1}} \put(#1,#1){\line(0,-1){#1}} \put(\int_eval:n{#1-1},#1){\line(-1,0){\int_eval:n{#1-1}}} \put(1,0){\line(1,0){\int_eval:n{#1-1}}} \int_step_inline:nn{#1*(#1-1)}{ \int_compare:nNnTF{0}={\intarray_item:Nn\g_walls_h_intarray{##1}}{}{ \put( \int_mod:nn{##1-1}{#1}, \int_eval:n{1+\int_div_truncate:nn{##1-1}{#1}} ){\line(1,0){1}} } \int_compare:nNnTF{0}={\intarray_item:Nn\g_walls_v_intarray{##1}}{}{ \put( \int_mod:nn{##1}{#1-1}, \int_div_truncate:nn{##1-1}{#1-1} ){\line(0,1){1}} } } #2 \end{picture} } \cs_new_protected:Npn\maze_draw:nn#1#2{ \maze_generate:nn{#1}{#2} \maze_draw_picture:nn{#1}{} } \cs_new_protected:Npn\maze_find_solution:n#1{ \int_set:Nn\l_maze_size_int{#1} \intarray_gzero:N\g_maze_parent_intarray \seq_clear:N\l_maze_solution_seq \seq_clear:N\l_maze_stack_seq \bool_set_false:N\l_maze_solution_found_bool \seq_put_right:Nn\l_maze_stack_seq{1} \intarray_gset:Nnn\g_maze_parent_intarray{1}{0} \bool_do_until:nn{\seq_if_empty_p:N\l_maze_stack_seq}{ \seq_pop_right:NN\l_maze_stack_seq\l_maze_cell_tl \int_set:Nn\l_maze_old_int{\l_maze_cell_tl} \int_compare:nNnTF{\l_maze_old_int}={#1*#1}{ \bool_set_true:N\l_maze_solution_found_bool \seq_clear:N\l_maze_stack_seq }{ \int_set:Nn\l_maze_rand_int{\int_div_truncate:nn{\l_maze_old_int-1}{#1}} \int_compare:nNnT{\int_mod:nn{\l_maze_old_int-1}{#1}}>{0}{ \int_set:Nn\l_maze_new_int{\l_maze_old_int-1} \int_compare:nNnT{0}={\intarray_item:Nn\g_walls_v_intarray{\l_maze_old_int-\l_maze_rand_int-1}}{ \int_compare:nNnT{0}={\intarray_item:Nn\g_maze_parent_intarray{\l_maze_new_int}}{ \intarray_gset:Nnn\g_maze_parent_intarray{\l_maze_new_int}{\l_maze_old_int} \seq_put_right:Nx\l_maze_stack_seq{\int_use:N\l_maze_new_int} } } } \int_compare:nNnT{\int_mod:nn{\l_maze_old_int}{#1}}>{0}{ \int_set:Nn\l_maze_new_int{\l_maze_old_int+1} \int_compare:nNnT{0}={\intarray_item:Nn\g_walls_v_intarray{\l_maze_old_int-\l_maze_rand_int}}{ \int_compare:nNnT{0}={\intarray_item:Nn\g_maze_parent_intarray{\l_maze_new_int}}{ \intarray_gset:Nnn\g_maze_parent_intarray{\l_maze_new_int}{\l_maze_old_int} \seq_put_right:Nx\l_maze_stack_seq{\int_use:N\l_maze_new_int} } } } \int_compare:nNnT{\l_maze_old_int}>{#1}{ \int_set:Nn\l_maze_new_int{\l_maze_old_int-#1} \int_compare:nNnT{0}={\intarray_item:Nn\g_walls_h_intarray{\l_maze_new_int}}{ \int_compare:nNnT{0}={\intarray_item:Nn\g_maze_parent_intarray{\l_maze_new_int}}{ \intarray_gset:Nnn\g_maze_parent_intarray{\l_maze_new_int}{\l_maze_old_int} \seq_put_right:Nx\l_maze_stack_seq{\int_use:N\l_maze_new_int} } } } \int_compare:nNnT{\l_maze_old_int}<{#1*(#1-1)+1}{ \int_set:Nn\l_maze_new_int{\l_maze_old_int+#1} \int_compare:nNnT{0}={\intarray_item:Nn\g_walls_h_intarray{\l_maze_old_int}}{ \int_compare:nNnT{0}={\intarray_item:Nn\g_maze_parent_intarray{\l_maze_new_int}}{ \intarray_gset:Nnn\g_maze_parent_intarray{\l_maze_new_int}{\l_maze_old_int} \seq_put_right:Nx\l_maze_stack_seq{\int_use:N\l_maze_new_int} } } } } } \bool_if:NT\l_maze_solution_found_bool{ \int_set:Nn\l_maze_old_int{#1*#1} \seq_put_right:Nx\l_maze_solution_seq{\int_use:N\l_maze_old_int} \bool_do_until:nn{\int_compare_p:nNn{\l_maze_old_int}={1}}{ \int_set:Nn\l_maze_old_int{\intarray_item:Nn\g_maze_parent_intarray{\l_maze_old_int}} \seq_put_right:Nx\l_maze_solution_seq{\int_use:N\l_maze_old_int} } } } \cs_new_protected:Npn\maze_draw_solution_segment:nn#1#2{ \int_set:Nn\l_maze_new_int{\int_min:nn{#1}{#2}} \group_begin:\color{green!50!black} \int_compare:nNnTF{\int_abs:n{#2-#1}}={1}{ \put( \fp_eval:n{\int_mod:nn{\l_maze_new_int-1}{\l_maze_size_int}+0.5}, \fp_eval:n{\int_div_truncate:nn{\l_maze_new_int-1}{\l_maze_size_int}+0.5} ){\line(1,0){1}} }{ \put( \fp_eval:n{\int_mod:nn{\l_maze_new_int-1}{\l_maze_size_int}+0.5}, \fp_eval:n{\int_div_truncate:nn{\l_maze_new_int-1}{\l_maze_size_int}+0.5} ){\line(0,1){1}} } \group_end: } \cs_new_protected:Npn\maze_draw_solution_path:n#1{ \maze_find_solution:n{#1} \bool_if:NT\l_maze_solution_found_bool{ \seq_map_indexed_inline:Nn\l_maze_solution_seq{ \int_compare:nNnTF{##1}>{1}{ \maze_draw_solution_segment:nn{##2}{\l_maze_previous_index_int} }{} \int_set:Nn\l_maze_previous_index_int{##2} } } } \cs_new_protected:Npn\maze_mark_solution_cells:n#1{ \maze_find_solution:n{#1} \bool_if:NT\l_maze_solution_found_bool{ \group_begin:\color{green!50!black} \seq_map_inline:Nn\l_maze_solution_seq{ \int_set:Nn\l_maze_old_int{##1} \put( \fp_eval:n{\int_mod:nn{\l_maze_old_int-1}{\l_maze_size_int}+0.5}, \fp_eval:n{\int_div_truncate:nn{\l_maze_old_int-1}{\l_maze_size_int}+0.5} ){\circle*{0.25}} } \group_end: } } \cs_new_protected:Npn\maze_draw_solution:nn#1#2{ \maze_generate:nn{#1}{#2} \maze_draw_picture:nn{#1}{\maze_draw_solution_path:n{#1}} } \cs_new_protected:Npn\maze_save:nnn#1#2#3{ \maze_validate_size:n{#2} \bool_if:NT\l_maze_size_valid_bool{ \maze_generate:nn{#2}{#3} \hbox_set:Nn\l_maze_store_box{\maze_draw_picture:nn{#2}{}} \cs_if_exist:cF{maze_saved_#1}{\box_new:c{maze_saved_#1}} \box_gset_eq:cN{maze_saved_#1}\l_maze_store_box \hbox_set:Nn\l_maze_store_box{\maze_draw_picture:nn{#2}{\maze_draw_solution_path:n{#2}}} \cs_if_exist:cF{maze_saved_#1_solution}{\box_new:c{maze_saved_#1_solution}} \box_gset_eq:cN{maze_saved_#1_solution}\l_maze_store_box \hbox_set:Nn\l_maze_store_box{\maze_draw_picture:nn{#2}{\maze_mark_solution_cells:n{#2}}} \cs_if_exist:cF{maze_saved_#1_solution_cells}{\box_new:c{maze_saved_#1_solution_cells}} \box_gset_eq:cN{maze_saved_#1_solution_cells}\l_maze_store_box } } \cs_new_protected:Npn\maze_use:n#1{ \cs_if_exist:cTF{maze_saved_#1}{\box_use:c{maze_saved_#1}}{ \msg_error:nnn{maze}{undefined-maze}{#1} } } \cs_new_protected:Npn\maze_use_solution_line:n#1{ \cs_if_exist:cTF{maze_saved_#1_solution}{\box_use:c{maze_saved_#1_solution}}{ \maze_use:n{#1} } } \cs_new_protected:Npn\maze_use_solution_cells:n#1{ \cs_if_exist:cTF{maze_saved_#1_solution_cells}{\box_use:c{maze_saved_#1_solution_cells}}{ \maze_use:n{#1} } } \keys_define:nn{maze/use}{ solution .choice:, solution / line .code:n={ \bool_set_true:N\l_maze_use_solution_bool \tl_set:Nn\l_maze_solution_view_tl{line} }, solution / cells .code:n={ \bool_set_true:N\l_maze_use_solution_bool \tl_set:Nn\l_maze_solution_view_tl{cells} }, solution .default:n=line } \cs_new_protected:Npn\maze_use_with_view:nn#1#2{ \bool_set_false:N\l_maze_use_solution_bool \tl_clear:N\l_maze_solution_view_tl \keys_set:nn{maze/use}{#1} \bool_if:NTF\l_maze_use_solution_bool{ \tl_if_eq:NnTF\l_maze_solution_view_tl{cells}{ \maze_use_solution_cells:n{#2} }{ \maze_use_solution_line:n{#2} } }{ \maze_use:n{#2} } } \NewDocumentCommand\maze{mO{\c_sys_minute_int}}{ \maze_validate_size:n{#1} \bool_if:NT\l_maze_size_valid_bool{ \maze_draw:nn{#1}{#2} } } \NewDocumentCommand\mazesave{m m O{\c_sys_minute_int}}{ \maze_save:nnn{#1}{#2}{#3} } \NewDocumentCommand\mazeuse{o m}{ \mode_leave_vertical: \IfNoValueTF{#1}{ \maze_use:n{#2} }{ \maze_use_with_view:nn{#1}{#2} } \tex_ignorespaces:D } \ExplSyntaxOff % End of package code