diff --git a/.github/workflows/generate-and-publish-docs.yml b/.github/workflows/generate-and-publish-docs.yml new file mode 100644 index 0000000000..07dfcc7263 --- /dev/null +++ b/.github/workflows/generate-and-publish-docs.yml @@ -0,0 +1,76 @@ +name: Generate and Publish PG POD and Sample Problem Documentation + +on: + # Execute the workflow anytime something is merged into or pushed to main. + push: + branches: + - main + + # This allows this workflow to be triggered manually from the actions tab. + workflow_dispatch: + +jobs: + generate-documentation: + runs-on: ubuntu-24.04 + + steps: + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends --no-install-suggests \ + pandoc \ + libmojolicious-perl \ + libpandoc-wrapper-perl \ + libpod-parser-perl + + - name: Checkout pg code + uses: actions/checkout@v4 + with: + path: pg + + - name: Create output directory + run: mkdir /home/runner/work/pg/pg/documentation + + - name: Generate sample problem documentation + run: | + perl pg/bin/parse-problem-doc.pl \ + --problem-dir=/home/runner/work/pg/pg/pg/tutorial/sample-problems \ + --out-dir=/home/runner/work/pg/pg/documentation/sampleproblems \ + --pod-base-url=/pg/pod \ + --sample-problem-base-url=/pg/sampleproblems + + - name: Generate POD + run: perl pg/bin/generate-pg-pod.pl --output-dir=documentation/pod --base-url=/pg/pod/ --home-url=/pg + + - name: Generate search data + run: perl pg/bin/generate-search-data.pl --out-file=documentation/sample-problem-search-data.json + + - name: Copy assets + run: | + cp /home/runner/work/pg/pg/pg/tutorial/templates/index.html \ + /home/runner/work/pg/pg/documentation/ + cp /home/runner/work/pg/pg/pg/htdocs/js/SampleProblemViewer/documentation-search.js \ + /home/runner/work/pg/pg/documentation/ + + - name: Upload documentation html + uses: actions/upload-pages-artifact@v3 + with: + path: documentation + + publish-documentation: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + # Set the permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages. + permissions: + pages: write + id-token: write + + runs-on: ubuntu-24.04 + needs: generate-documentation + + steps: + - name: Publish to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/LICENSE b/LICENSE index f079e7c087..2367dd2c5a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,8 +1,8 @@ - WeBWorK - Online Homework Delivery System + WeBWorK PG Version 2.* Copyright 2000-2025, The WeBWorK Project + All rights reserved. This program is free software; you can redistribute it and/or modify @@ -12,19 +12,19 @@ Software Foundation; either version 2, or (at your option) any later version, or - b) the "Artistic License" which comes with this package. + b) the "Artistic License" 1.0 which comes with this package. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the Artistic License for more details. - You should have received a copy of the Artistic License with this - package, in the file named "Artistic". If not, we'll be glad to provide - one. + You should have received a copy of the Artistic License 1.0 with this + package, in the file named "Artistic" inside the `doc` folder. If not, + you can find a copy at https://github.com/openwebwork/pg/blob/main/doc/Artistic + or https://perlfoundation.org/artistic-license-10.html. You should also have received a copy of the GNU General Public License - along with this program in the file named "Copying". If not, write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - 02111-1307, USA or visit their web page on the internet at - http://www.gnu.org/copyleft/gpl.html. + along with this program in the file named "Copying" inside the `doc` folder. + If not, you can find a copy at https://github.com/openwebwork/pg/blob/main/doc/Copying + or https://www.gnu.org/licenses/old-licenses/gpl-2.0.html. diff --git a/README b/README deleted file mode 100644 index 9a8d30bb13..0000000000 --- a/README +++ /dev/null @@ -1,11 +0,0 @@ - WeBWorK - Program Generation Language - Version 2.* - Branch: https://github.com/openwebwork - - - http://webwork.maa.org/wiki/Category:Release_Notes - - Copyright 2000-2025, The WeBWorK Project - http://webwork.maa.org - All rights reserved. diff --git a/README.md b/README.md index d40515112f..ddfa947f8a 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,20 @@ # Welcome to WeBWorK -![main workflow](https://github.com/pstaabp/pg/actions/workflows/coverage.yml/badge.svg) -[![codecov](https://codecov.io/gh/pstaabp/pg/branch/unit-test/graph/badge.svg?token=H7WYHBDB9S)](https://codecov.io/gh/pstaabp/pg) -![GitHub last commit](https://img.shields.io/github/last-commit/pstaabp/pg/unit-test) - -WeBWorK is an open-source online homework system for math and sciences courses. WeBWorK is supported by the MAA and the NSF and comes with an Open Problem Library (OPL) of over 30,000 homework problems. Problems in the OPL target most lower division undergraduate math courses and some advanced courses. Supported courses include college algebra, discrete mathematics, probability and statistics, single and multivariable calculus, differential equations, linear algebra and complex analysis. Find out more at the main WeBWorK [webpage](http://webwork.maa.org). +WeBWorK is an open-source online homework system for math and sciences courses. WeBWorK is supported by the MAA and the NSF and comes with an Open Problem Library (OPL) of over 30,000 homework problems. Problems in the OPL target most lower division undergraduate math courses and some advanced courses. Supported courses include college algebra, discrete mathematics, probability and statistics, single and multivariable calculus, differential equations, linear algebra and complex analysis. Find out more at the main WeBWorK [webpage](https://openwebwork.org). ## Information for Users New users interested in getting started with their own WeBWorK server, or instructors looking to learn more about how to use WeBWorK in their classes, should take a look at one of the following resources: -* [WeBWorK wiki](http://webwork.maa.org/wiki/Main_Page) - The main WeBWorK wiki - * [Instructors](http://webwork.maa.org/wiki/Instructors) - Information for Instructors - * [Problem Authors](http://webwork.maa.org/wiki/Authors) - Information for Problem Authors -* [WW_Install](http://github.com/aubreyja/ww_install) - Information for using the WW_install script -* [Forum](http://webwork.maa.org/moodle/mod/forum/index.php?id=3) - The WeBWorK Forum +* [WeBWorK wiki](https://wiki.openwebwork.org/wiki/WeBWorK_Main_Page) - The main WeBWorK wiki + * [Instructors](https://wiki.openwebwork.org/wiki/Instructors) - Information for Instructors + * [Problem Authors](https://wiki.openwebwork.org/wiki/Authors) - Information for Problem Authors +* [Forum](https://forums.openwebwork.org/mod/forum/index.php?id=3) - The WeBWorK Forum * [Frequently Asked Questions](https://github.com/openwebwork/webwork2/wiki/Frequently-Asked-Questions) - A list of frequently asked questions. ## Information For Developers -People interested in developing new features for WeBWorK should take a look at the following resources. People interested in developing new problems for WeBWorK should visit [Problem Authors](http://webwork.maa.org/wiki/Authors). +People interested in developing new features for WeBWorK should take a look at the following resources. People interested in developing new problems for WeBWorK should visit [Problem Authors](https://wiki.openwebwork.org/wiki/Authors). * [First Time Setup](https://github.com/openwebwork/webwork2/wiki/First-Time-Setup) - Setting up your clone of this github repo for the first time. * [Coding and Workflow](https://github.com/openwebwork/webwork2/wiki/Coding-and-Workflow) - Our suggested workflow processes. Following this will make it much easier to get code accepted into the repo. diff --git a/VERSION b/VERSION index fd27d43097..62f68fdd37 100644 --- a/VERSION +++ b/VERSION @@ -1,4 +1,4 @@ -$PG_VERSION ='2.20'; +$PG_VERSION ='2.20+develop'; $PG_COPYRIGHT_YEARS = '1996-2025'; 1; diff --git a/assets/pod-templates/category-index.mt b/assets/pod-templates/category-index.mt new file mode 100644 index 0000000000..61fe3b450c --- /dev/null +++ b/assets/pod-templates/category-index.mt @@ -0,0 +1,94 @@ + + +% + + + + <%= $title %> + + + + + +% + + + % + % my ($index, $macro_index, $content, $macro_content) = ('', '', '', ''); + % for my $macro (@$macros_order) { + % my $new_index = begin + <%= $macro_names->{$macro} // $macro %> + % end + % $macro_index .= $new_index->(); + % my $new_content = begin +

<%= $macro_names->{$macro} // $macro %>

+
+ % for my $file (sort { $a->[1] cmp $b->[1] } @{ $macros->{$macro} }) { + <%= $file->[1] %> + % } +
+ % end + % $macro_content .= $new_content->(); + % } + % for my $section (@$section_order) { + % next unless defined $pod_index->{$section}; + % my $new_index = begin + <%= $sections->{$section} %> + % if ($section eq 'macros') { + + % } + % end + % $index .= $new_index->(); + % my $new_content = begin +

<%= $sections->{$section} %>

+
+ % if ($section eq 'macros') { + <%= $macro_content =%> + % } else { + % for my $file (sort { $a->[1] cmp $b->[1] } @{ $pod_index->{$section} }) { + + <%= $file->[1] %> + + % } + % } +
+ % end + % $content .= $new_content->(); + % } + % + +
+
+ <%= $content =%> +

Generated <%= $date %>

+
+
+ +% + diff --git a/assets/pod-templates/pod.mt b/assets/pod-templates/pod.mt new file mode 100644 index 0000000000..53cc4a4898 --- /dev/null +++ b/assets/pod-templates/pod.mt @@ -0,0 +1,55 @@ + + +% + + + + <%= $title %> + + + + + +% + + + +
+
+ <%= $content =%> +
+
+ +% + diff --git a/assets/stop-words-en.txt b/assets/stop-words-en.txt new file mode 100644 index 0000000000..cc09be2ec7 --- /dev/null +++ b/assets/stop-words-en.txt @@ -0,0 +1,1320 @@ +# Stop words from https://github.com/Alir3z4/stop-words. + +'ll +'tis +'twas +'ve +a +a's +able +ableabout +about +above +abroad +abst +accordance +according +accordingly +across +act +actually +ad +added +adj +adopted +ae +af +affected +affecting +affects +after +afterwards +ag +again +against +ago +ah +ahead +ai +ain't +aint +al +all +allow +allows +almost +alone +along +alongside +already +also +although +always +am +amid +amidst +among +amongst +amoungst +amount +an +and +announce +another +any +anybody +anyhow +anymore +anyone +anything +anyway +anyways +anywhere +ao +apart +apparently +appear +appreciate +appropriate +approximately +aq +ar +are +area +areas +aren +aren't +arent +arise +around +arpa +as +aside +ask +asked +asking +asks +associated +at +au +auth +available +aw +away +awfully +az +b +ba +back +backed +backing +backs +backward +backwards +bb +bd +be +became +because +become +becomes +becoming +been +before +beforehand +began +begin +beginning +beginnings +begins +behind +being +beings +believe +below +beside +besides +best +better +between +beyond +bf +bg +bh +bi +big +bill +billion +biol +bj +bm +bn +bo +both +bottom +br +brief +briefly +bs +bt +but +buy +bv +bw +by +bz +c +c'mon +c's +ca +call +came +can +can't +cannot +cant +caption +case +cases +cause +causes +cc +cd +certain +certainly +cf +cg +ch +changes +ci +ck +cl +clear +clearly +click +cm +cmon +cn +co +co. +com +come +comes +computer +con +concerning +consequently +consider +considering +contain +containing +contains +copy +corresponding +could +could've +couldn +couldn't +couldnt +course +cr +cry +cs +cu +currently +cv +cx +cy +cz +d +dare +daren't +darent +date +de +dear +definitely +describe +described +despite +detail +did +didn +didn't +didnt +differ +different +differently +directly +dj +dk +dm +do +does +doesn +doesn't +doesnt +doing +don +don't +done +dont +doubtful +down +downed +downing +downs +downwards +due +during +dz +e +each +early +ec +ed +edu +ee +effect +eg +eh +eight +eighty +either +eleven +else +elsewhere +empty +end +ended +ending +ends +enough +entirely +er +es +especially +et +et-al +etc +even +evenly +ever +evermore +every +everybody +everyone +everything +everywhere +ex +exactly +example +except +f +face +faces +fact +facts +fairly +far +farther +felt +few +fewer +ff +fi +fifteen +fifth +fifty +fify +fill +find +finds +fire +first +five +fix +fj +fk +fm +fo +followed +following +follows +for +forever +former +formerly +forth +forty +forward +found +four +fr +free +from +front +full +fully +further +furthered +furthering +furthermore +furthers +fx +g +ga +gave +gb +gd +ge +general +generally +get +gets +getting +gf +gg +gh +gi +give +given +gives +giving +gl +gm +gmt +gn +go +goes +going +gone +good +goods +got +gotten +gov +gp +gq +gr +great +greater +greatest +greetings +group +grouped +grouping +groups +gs +gt +gu +gw +gy +h +had +hadn't +hadnt +half +happens +hardly +has +hasn +hasn't +hasnt +have +haven +haven't +havent +having +he +he'd +he'll +he's +hed +hell +hello +help +hence +her +here +here's +hereafter +hereby +herein +heres +hereupon +hers +herself +herse” +hes +hi +hid +high +higher +highest +him +himself +himse” +his +hither +hk +hm +hn +home +homepage +hopefully +how +how'd +how'll +how's +howbeit +however +hr +ht +htm +html +http +hu +hundred +i +i'd +i'll +i'm +i've +i.e. +id +ie +if +ignored +ii +il +ill +im +immediate +immediately +importance +important +in +inasmuch +inc +inc. +indeed +index +indicate +indicated +indicates +information +inner +inside +insofar +instead +int +interest +interested +interesting +interests +into +invention +inward +io +iq +ir +is +isn +isn't +isnt +it +it'd +it'll +it's +itd +itll +its +itself +itse” +ive +j +je +jm +jo +join +jp +just +k +ke +keep +keeps +kept +keys +kg +kh +ki +kind +km +kn +knew +know +known +knows +kp +kr +kw +ky +kz +l +la +large +largely +last +lately +later +latest +latter +latterly +lb +lc +least +length +less +lest +let +let's +lets +li +like +liked +likely +likewise +line +little +lk +ll +long +longer +longest +look +looking +looks +low +lower +lr +ls +lt +ltd +lu +lv +ly +m +ma +made +mainly +make +makes +making +man +many +may +maybe +mayn't +maynt +mc +md +me +mean +means +meantime +meanwhile +member +members +men +merely +mg +mh +microsoft +might +might've +mightn't +mightnt +mil +mill +million +mine +minus +miss +mk +ml +mm +mn +mo +more +moreover +most +mostly +move +mp +mq +mr +mrs +ms +msie +mt +mu +much +mug +must +must've +mustn't +mustnt +mv +mw +mx +my +myself +myse” +mz +n +na +name +namely +nay +nc +nd +ne +near +nearly +necessarily +necessary +need +needed +needing +needn't +neednt +needs +neither +net +netscape +never +neverf +neverless +nevertheless +new +newer +newest +next +nf +ng +ni +nine +ninety +nl +no +no-one +nobody +non +none +nonetheless +noone +nor +normally +nos +not +noted +nothing +notwithstanding +novel +now +nowhere +np +nr +nu +null +number +numbers +nz +o +obtain +obtained +obviously +of +off +often +oh +ok +okay +old +older +oldest +om +omitted +on +once +one +one's +ones +only +onto +open +opened +opening +opens +opposite +or +ord +order +ordered +ordering +orders +org +other +others +otherwise +ought +oughtn't +oughtnt +our +ours +ourselves +out +outside +over +overall +owing +own +p +pa +page +pages +part +parted +particular +particularly +parting +parts +past +pe +per +perhaps +pf +pg +ph +pk +pl +place +placed +places +please +plus +pm +pmid +pn +point +pointed +pointing +points +poorly +possible +possibly +potentially +pp +pr +predominantly +present +presented +presenting +presents +presumably +previously +primarily +probably +problem +problems +promptly +proud +provided +provides +pt +put +puts +pw +py +q +qa +que +quickly +quite +qv +r +ran +rather +rd +re +readily +really +reasonably +recent +recently +ref +refs +regarding +regardless +regards +related +relatively +research +reserved +respectively +resulted +resulting +results +right +ring +ro +room +rooms +round +ru +run +rw +s +sa +said +same +saw +say +saying +says +sb +sc +sd +se +sec +second +secondly +seconds +section +see +seeing +seem +seemed +seeming +seems +seen +sees +self +selves +sensible +sent +serious +seriously +seven +seventy +several +sg +sh +shall +shan't +shant +she +she'd +she'll +she's +shed +shell +shes +should +should've +shouldn +shouldn't +shouldnt +show +showed +showing +shown +showns +shows +si +side +sides +significant +significantly +similar +similarly +since +sincere +site +six +sixty +sj +sk +sl +slightly +sm +small +smaller +smallest +sn +so +some +somebody +someday +somehow +someone +somethan +something +sometime +sometimes +somewhat +somewhere +soon +sorry +specifically +specified +specify +specifying +sr +st +state +states +still +stop +strongly +su +sub +substantially +successfully +such +sufficiently +suggest +sup +sure +sv +sy +system +sz +t +t's +take +taken +taking +tc +td +tell +ten +tends +test +text +tf +tg +th +than +thank +thanks +thanx +that +that'll +that's +that've +thatll +thats +thatve +the +their +theirs +them +themselves +then +thence +there +there'd +there'll +there're +there's +there've +thereafter +thereby +thered +therefore +therein +therell +thereof +therere +theres +thereto +thereupon +thereve +these +they +they'd +they'll +they're +they've +theyd +theyll +theyre +theyve +thick +thin +thing +things +think +thinks +third +thirty +this +thorough +thoroughly +those +thou +though +thoughh +thought +thoughts +thousand +three +throug +through +throughout +thru +thus +til +till +tip +tis +tj +tk +tm +tn +to +today +together +too +took +top +toward +towards +tp +tr +tried +tries +trillion +truly +try +trying +ts +tt +turn +turned +turning +turns +tv +tw +twas +twelve +twenty +twice +two +tz +u +ua +ug +uk +um +un +under +underneath +undoing +unfortunately +unless +unlike +unlikely +until +unto +up +upon +ups +upwards +us +use +used +useful +usefully +usefulness +uses +using +usually +uucp +uy +uz +v +va +value +various +vc +ve +versus +very +vg +vi +via +viz +vn +vol +vols +vs +vu +w +want +wanted +wanting +wants +was +wasn +wasn't +wasnt +way +ways +we +we'd +we'll +we're +we've +web +webpage +website +wed +welcome +well +wells +went +were +weren +weren't +werent +weve +wf +what +what'd +what'll +what's +what've +whatever +whatll +whats +whatve +when +when'd +when'll +when's +whence +whenever +where +where'd +where'll +where's +whereafter +whereas +whereby +wherein +wheres +whereupon +wherever +whether +which +whichever +while +whilst +whim +whither +who +who'd +who'll +who's +whod +whoever +whole +wholl +whom +whomever +whos +whose +why +why'd +why'll +why's +widely +width +will +willing +wish +with +within +without +won +won't +wonder +wont +words +work +worked +working +works +world +would +would've +wouldn +wouldn't +wouldnt +ws +www +x +y +ye +year +years +yes +yet +you +you'd +you'll +you're +you've +youd +youll +young +younger +youngest +your +youre +yours +yourself +yourselves +youve +yt +yu +z +za +zero +zm +zr + +# Additional specific stop words specific to POD and sample problem documentation. +constructor +description +error +errors +macro +macros +pod +podlink +problink +synopsis +usage +funciton +functions +method +methods +option +options +todo +fixme +_ diff --git a/assets/tex/CAPA.tex b/assets/tex/CAPA.tex deleted file mode 100644 index 61d1ffa208..0000000000 --- a/assets/tex/CAPA.tex +++ /dev/null @@ -1,15 +0,0 @@ -% capa tex macros - -\newcommand{\capa}{{\sl C\kern-.10em\raise-.00ex\hbox{\rm A}\kern-.22em% -{\sl P}\kern-.14em\kern-.01em{\rm A}}} - -\newenvironment{choicelist} -{\begin{list}{} - {\setlength{\rightmargin}{0in}\setlength{\leftmargin}{0.13in} - \setlength{\topsep}{0.05in}\setlength{\itemsep}{0.022in} - \setlength{\parsep}{0in}\setlength{\belowdisplayskip}{0.04in} - \setlength{\abovedisplayskip}{0.05in} - \setlength{\abovedisplayshortskip}{-0.04in} - \setlength{\belowdisplayshortskip}{0.04in}} - } -{\end{list}} diff --git a/assets/tex/pg.sty b/assets/tex/pg.sty index 7d8ce2c51b..0f895b87ff 100644 --- a/assets/tex/pg.sty +++ b/assets/tex/pg.sty @@ -22,11 +22,10 @@ \fi % PG macro collections -\input{CAPA.tex} \input{PGML.tex} % The macro alternatives for < should be used in math in PG problems to help -% avoid issus with a bare < in HMTL or XML output. The alternatives for > are +% avoid issues with a bare < in HTML or XML output. The alternatives for > are % provided for parity. \newcommand{\lt}{<} \newcommand{\gt}{>} @@ -36,7 +35,7 @@ % semantic macro definitions used by PG \newcommand{\answerRule}[2][]{\raisebox{-3pt}{\parbox[t]{#2ex}{\hrulefill}}} -% height of a strut, used for example to possition the top border of an inline image +% height of a strut, used for example to position the top border of an inline image % unit is initialized here, but value is set locally where needed \newlength{\strutheight} diff --git a/bin/generate-pg-pod.pl b/bin/generate-pg-pod.pl new file mode 100755 index 0000000000..98f73a3458 --- /dev/null +++ b/bin/generate-pg-pod.pl @@ -0,0 +1,80 @@ +#!/usr/bin/env perl + +=head1 NAME + +generate-pg-pod.pl - Convert PG POD into HTML form. + +=head1 SYNOPSIS + +generate-pg-pod.pl [options] + + Options: + -o|--output-dir Directory to save the output files to. (required) + -b|--base-url Base url location used on server. (default: /) + This is needed for internal POD links to work correctly. + -h|--home-url Home page url on the server. (default: /) + -v|--verbose Increase the verbosity of the output. + (Use multiple times for more verbosity.) + +=head1 DESCRIPTION + +Convert PG POD into HTML form. + +=cut + +use strict; +use warnings; + +use Getopt::Long qw(:config bundling); +use Pod::Usage; + +my ($output_dir, $base_url, $home_url); +my $verbose = 0; +GetOptions( + 'o|output-dir=s' => \$output_dir, + 'b|base-url=s' => \$base_url, + 'h|home-url=s' => \$home_url, + 'v|verbose+' => \$verbose +); + +pod2usage(2) unless $output_dir; + +$base_url = "/" if !$base_url; +$home_url = "/" if !$home_url; + +use Mojo::Template; +use IO::File; +use File::Copy; +use File::Path qw(make_path remove_tree); +use File::Basename qw(dirname); +use Cwd qw(abs_path); + +use lib abs_path(dirname(dirname(__FILE__))) . '/lib'; + +use WeBWorK::Utils::PODtoHTML; + +my $pg_root = abs_path(dirname(dirname(__FILE__))); + +print "Reading: $pg_root\n" if $verbose; + +remove_tree($output_dir); +make_path($output_dir); + +my $htmldocs = WeBWorK::Utils::PODtoHTML->new( + source_root => $pg_root, + dest_root => $output_dir, + template_dir => "$pg_root/assets/pod-templates", + dest_url => $base_url, + home_url => $home_url, + home_url_link_name => 'PG Documentation Home', + verbose => $verbose +); +$htmldocs->convert_pods; + +make_path("$output_dir/assets"); +copy("$pg_root/htdocs/js/PODViewer/podviewer.css", "$output_dir/assets/podviewer.css"); +print "copying $pg_root/htdocs/js/PODViewer/podviewer.css to $output_dir/assets/podviewer.css\n" if $verbose; +copy("$pg_root/htdocs/js/PODViewer/podviewer.js", "$output_dir/assets/podviewer.js"); +print "copying $pg_root/htdocs/js/PODViewer/podviewer.css to $output_dir/assets/podviewer.js\n" if $verbose; + +1; diff --git a/bin/generate-search-data.pl b/bin/generate-search-data.pl new file mode 100755 index 0000000000..c3ab227eec --- /dev/null +++ b/bin/generate-search-data.pl @@ -0,0 +1,42 @@ +#!/usr/bin/env perl + +=head1 NAME + +generate-search-data.pl - Generate search data for macro and sample problem +documentation. + +=head1 SYNOPSIS + +generate-search-data.pl [options] + + Options: + -o|--out-file File to save the search data to. (required) + +=head1 DESCRIPTION + +Generate search data for macro and sample problem documentation. + +=cut + +use strict; +use warnings; + +my $pgRoot; + +use Mojo::File qw(curfile); +BEGIN { $pgRoot = curfile->dirname->dirname; } + +use lib "$pgRoot/lib"; + +use Getopt::Long; +use Pod::Usage; + +use WeBWorK::PG::SampleProblemParser qw(getSearchData); + +my $outFile; +GetOptions("o|out-file=s" => \$outFile); +pod2usage(2) unless $outFile; + +getSearchData($outFile); + +1; diff --git a/bin/parse-problem-doc.pl b/bin/parse-problem-doc.pl index 84f9a303c7..818416093d 100755 --- a/bin/parse-problem-doc.pl +++ b/bin/parse-problem-doc.pl @@ -1,5 +1,29 @@ #!/usr/bin/env perl +=head1 NAME + +parse-problem-doc.pl - Parse sample problem documentation. + +=head1 SYNOPSIS + +parse-problem-doc.pl [options] + + Options: + -d|--problem-dir Directory containing sample problems to be parsed. + This defaults to the tutorial/sample-problems directory + in the PG root directory if not given. + -o|--out-dir Directory to save the output files to. (required) + -p|--pod-base-url Base URL location for POD on server. (required) + -s|--sample-problem-base-url + Base URL location for sample problems on server. (required) + -v|--verbose Give verbose feedback. + +=head1 DESCRIPTION + +Parse sample problem documentation. + +=cut + use strict; use warnings; use experimental 'signatures'; @@ -17,26 +41,26 @@ BEGIN use Mojo::Template; use File::Basename qw(basename); use Getopt::Long; +use Pod::Usage; use File::Copy qw(copy); use Pod::Simple::Search; -use SampleProblemParser qw(parseSampleProblem generateMetadata); +use WeBWorK::PG::SampleProblemParser qw(parseSampleProblem generateMetadata); my $problem_dir = "$pg_root/tutorial/sample-problems"; -my ($out_dir, $pod_root, $pg_doc_home); +my ($out_dir, $pod_base_url, $sample_problem_base_url); my $verbose = 0; GetOptions( - "d|problem_dir=s" => \$problem_dir, - "o|out_dir=s" => \$out_dir, - "v|verbose" => \$verbose, - "p|pod_root=s" => \$pod_root, - "h|pg_doc_home=s" => \$pg_doc_home, + "d|problem-dir=s" => \$problem_dir, + "o|out-dir=s" => \$out_dir, + "p|pod-base-url=s" => \$pod_base_url, + "s|sample-problem-base-url=s" => \$sample_problem_base_url, + "v|verbose" => \$verbose ); -die "out_dir, pod_root, and pg_doc_home must be provided.\n" - unless $out_dir && $pod_root && $pg_doc_home; +pod2usage(2) unless $out_dir && $pod_base_url && $sample_problem_base_url; my $mt = Mojo::Template->new(vars => 1); my $template_dir = "$pg_root/tutorial/templates"; @@ -46,7 +70,7 @@ BEGIN my @problem_types = qw(sample technique snippet); -$pod_root .= '/pg/macros'; +$pod_base_url .= '/macros'; mkdir $out_dir unless -d $out_dir; # Build a hash of all PG files for linking. @@ -55,16 +79,16 @@ BEGIN for (keys %$index_table) { renderSampleProblem( $_ =~ s/.pg$//r, - metadata => $index_table, - macro_locations => $macro_locations, - pod_root => $pod_root, - pg_doc_home => $pg_doc_home, - url_extension => '.html', - problem_dir => $problem_dir, - out_dir => $out_dir, - template_dir => $template_dir, - mt => $mt, - verbose => $verbose + metadata => $index_table, + macro_locations => $macro_locations, + pod_base_url => $pod_base_url, + sample_problem_base_url => $sample_problem_base_url, + url_extension => '.html', + problem_dir => $problem_dir, + out_dir => $out_dir, + template_dir => $template_dir, + mt => $mt, + verbose => $verbose ); } @@ -92,7 +116,7 @@ ($filename, %global) return; } -# Ouput index files. +# Output index files. for (qw(categories subjects macros techniques)) { my $options = { metadata => $index_table, diff --git a/bin/pg-critic.pl b/bin/pg-critic.pl new file mode 100755 index 0000000000..2c4431d952 --- /dev/null +++ b/bin/pg-critic.pl @@ -0,0 +1,140 @@ +#!/usr/bin/env perl + +=head1 NAME + +pg-critic.pl - Command line interface to critque PG problem code. + +=head1 SYNOPSIS + + pg-critic.pl [options] file1 file2 ... + +Options: + + -f|--format Format of the output, either 'text' or 'json'. + 'text' is the default and will output a plain text + listing of the results. 'json' will output results in + JavaScript Object Notation. + -o|--output-file Filename to write output to. If not provided output will + be printed to STDOUT. + -n|--no-details Only show the filename and badness score and do not + include the details in the output for each file. + -s|--strict Disable "## no critic" annotations and force all + policies to be enforced. + -p|--pg-only Only include PG critic policy violations and ignore + general Perl critic policy violations (both for the + score and display). + -h|--help Show the help message. + +=head1 DESCRIPTION + +C is a PG problem source code analyzer. It is the executable +front-end to the L module, which attempts to identify usage +of old or deprecated PG features and code that does not conform to current +best-practices. + +=cut + +use Mojo::Base -signatures; + +use Mojo::File qw(curfile path); +use Mojo::JSON qw(encode_json); +use Getopt::Long; +use Pod::Usage; + +use lib curfile->dirname->dirname . '/lib'; + +use WeBWorK::PG::Critic qw(critiquePGFile); + +GetOptions( + 'f|format=s' => \my $format, + 'o|output-file=s' => \my $filename, + 'n|no-details' => \my $noDetails, + 's|strict' => \my $force, + 'p|pg-only' => \my $pgOnly, + 'h|help' => \my $show_help +); +pod2usage(2) if $show_help; + +$format //= 'text'; + +$format = lc($format); + +unless (@ARGV) { + say 'A list of pg problem files must be provided.'; + pod2usage(2); +} +unless ($format eq 'text' || $format eq 'json') { + say 'The output format must be "text" or "json"'; + pod2usage(2); +} + +sub scoreProblem (@violations) { + my $score = 0; + for (@violations) { + if ($_->policy =~ /^Perl::Critic::Policy::PG::/) { + $score += $_->explanation->{score} // 0; + } else { + # Add 5 points for any of the default Perl::Critic::Policy violations. + # These will not have a score in the explanation. + $score += 5; + } + } + return $score; +} + +my @results; + +for (@ARGV) { + my @violations = critiquePGFile($_, $force); + @violations = grep { $_->policy =~ /^Perl::Critic::Policy::PG::/ } @violations if $pgOnly; + + my (@pgCriticViolations, @perlCriticViolations); + if (!$noDetails) { + @pgCriticViolations = grep { $_->policy =~ /^Perl::Critic::Policy::PG::/ } @violations; + @perlCriticViolations = grep { $_->policy !~ /^Perl::Critic::Policy::PG::/ } @violations; + } + + push( + @results, + { + file => $_, + score => scoreProblem(@violations), + $noDetails + ? () + : ( + @pgCriticViolations ? (pgCriticViolations => \@pgCriticViolations) : (), + @perlCriticViolations ? (perlCriticViolations => \@perlCriticViolations) : () + ) + } + ); +} + +Perl::Critic::Violation::set_format('%m at line %l, column %c. (%p)'); + +my $outputMethod = $format eq 'json' ? \&encode_json : sub { + my $results = shift; + + return join( + "\n", + map { ( + "Filename: $_->{file}", + "Score: $_->{score}", + @{ $_->{pgCriticViolations} // [] } + ? ('PG critic violations:', map { "\t" . $_->to_string } @{ $_->{pgCriticViolations} }) + : (), + @{ $_->{perlCriticViolations} // [] } + ? ('Perl critic violations:', map { "\t" . $_->to_string } @{ $_->{perlCriticViolations} }) + : () + ) } @$results + ); +}; + +if ($filename) { + eval { path($filename)->spew($outputMethod->(\@results), 'UTF-8') }; + if ($@) { say "Unable to write results to $filename: $@"; } + else { say "Results written in $format format to $filename"; } +} else { + say $outputMethod->(\@results); +} + +1; diff --git a/bin/run-perltidy.pl b/bin/run-perltidy.pl index 9f2eeff8cb..a755f0d845 100755 --- a/bin/run-perltidy.pl +++ b/bin/run-perltidy.pl @@ -64,8 +64,8 @@ =head1 OPTIONS # Validate options that were passed. my %options; my $err = Perl::Tidy::perltidy(argv => \@args, dump_options => \%options); -exit $err if $err; -die "The -pro option is not suppored by this script.\n" if defined $options{profile}; +exit $err if $err; +die "The -pro option is not supported by this script.\n" if defined $options{profile}; unshift(@args, '-bext=/') unless defined $options{'backup-file-extension'}; diff --git a/bin/update-localization-files b/bin/update-localization-files index 50da33991a..05e1d0a746 100755 --- a/bin/update-localization-files +++ b/bin/update-localization-files @@ -45,7 +45,7 @@ if [ -z "$PG_ROOT" ]; then fi command -v xgettext.pl >/dev/null 2>&1 || { - echo >&2 "xgettext.pl needs to be installed. It is inlcuded in the perl package Locale::Maketext::Extract. Aborting."; + echo >&2 "xgettext.pl needs to be installed. It is included in the perl package Locale::Maketext::Extract. Aborting."; exit 1; } diff --git a/conf/pg_config.dist.yml b/conf/pg_config.dist.yml index 3e4c9ab080..5224755077 100644 --- a/conf/pg_config.dist.yml +++ b/conf/pg_config.dist.yml @@ -42,7 +42,6 @@ directories: - . - $pg_root/macros - $pg_root/macros/answers - - $pg_root/macros/capa - $pg_root/macros/contexts - $pg_root/macros/core - $pg_root/macros/graph @@ -141,16 +140,10 @@ specialPGEnvironmentVars: # To enable Rserve (the R statistical server), uncomment the following two # lines. The R server needs to be installed and running in order for this to - # work. See http://webwork.maa.org/wiki/R_in_WeBWorK for more info. + # work. See https://wiki.openwebwork.org/wiki/R_in_WeBWorK for more info. #Rserve: # host: localhost - # Locations of CAPA resources. (Only necessary if you need to use converted CAPA problems.) - CAPA_Tools: $Contrib_dir/CAPA/macros/CAPA_Tools/ - CAPA_MCTools: $Contrib_dir/Contrib/CAPA/macros/CAPA_MCTools/ - CAPA_GraphicsDirectory: $Contrib_dir/Contrib/CAPA/CAPA_Graphics/ - CAPA_Graphics_URL: $pg_root_url/CAPA_Graphics/ - # Answer evaluatior defaults ansEvalDefaults: functAbsTolDefault: 0.001 @@ -236,7 +229,7 @@ modules: - [Multiple] - [PGrandom] - [Regression] - - ['Plots::Plot', 'Plots::Axes', 'Plots::Data', 'Plots::Tikz', 'Plots::JSXGraph', 'Plots::GD'] + - ['Plots::Plot', 'Plots::Axes', 'Plots::Data', 'Plots::Tikz', 'Plots::JSXGraph'] - [Select] - [Units] - [VectorField] diff --git a/cpanfile b/cpanfile index 12c7e39b75..00b469c470 100644 --- a/cpanfile +++ b/cpanfile @@ -24,6 +24,12 @@ on runtime => sub { # Needed for Rserve recommends 'IO::Handle'; + + # Needed for WeBWorK::PG::Tidy + recommends 'Perl::Tidy'; + + # Needed for WeBWorK::PG::PGProblemCritic + recommends 'Perl::Critic'; }; on test => sub { diff --git a/doc/Artistic b/doc/Artistic new file mode 100644 index 0000000000..5f221241e8 --- /dev/null +++ b/doc/Artistic @@ -0,0 +1,131 @@ + + + + + The "Artistic License" + + Preamble + +The intent of this document is to state the conditions under which a +Package may be copied, such that the Copyright Holder maintains some +semblance of artistic control over the development of the package, +while giving the users of the package the right to use and distribute +the Package in a more-or-less customary fashion, plus the right to make +reasonable modifications. + +Definitions: + + "Package" refers to the collection of files distributed by the + Copyright Holder, and derivatives of that collection of files + created through textual modification. + + "Standard Version" refers to such a Package if it has not been + modified, or has been modified in accordance with the wishes + of the Copyright Holder as specified below. + + "Copyright Holder" is whoever is named in the copyright or + copyrights for the package. + + "You" is you, if you're thinking about copying or distributing + this Package. + + "Reasonable copying fee" is whatever you can justify on the + basis of media cost, duplication charges, time of people involved, + and so on. (You will not be required to justify it to the + Copyright Holder, but only to the computing community at large + as a market that must bear the fee.) + + "Freely Available" means that no fee is charged for the item + itself, though there may be fees involved in handling the item. + It also means that recipients of the item may redistribute it + under the same conditions they received it. + +1. You may make and give away verbatim copies of the source form of the +Standard Version of this Package without restriction, provided that you +duplicate all of the original copyright notices and associated disclaimers. + +2. You may apply bug fixes, portability fixes and other modifications +derived from the Public Domain or from the Copyright Holder. A Package +modified in such a way shall still be considered the Standard Version. + +3. You may otherwise modify your copy of this Package in any way, provided +that you insert a prominent notice in each changed file stating how and +when you changed that file, and provided that you do at least ONE of the +following: + + a) place your modifications in the Public Domain or otherwise make them + Freely Available, such as by posting said modifications to Usenet or + an equivalent medium, or placing the modifications on a major archive + site such as uunet.uu.net, or by allowing the Copyright Holder to include + your modifications in the Standard Version of the Package. + + b) use the modified Package only within your corporation or organization. + + c) rename any non-standard executables so the names do not conflict + with standard executables, which must also be provided, and provide + a separate manual page for each non-standard executable that clearly + documents how it differs from the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +4. You may distribute the programs of this Package in object code or +executable form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and library files, + together with instructions (in the manual page or equivalent) on where + to get the Standard Version. + + b) accompany the distribution with the machine-readable source of + the Package with your modifications. + + c) give non-standard executables non-standard names, and clearly + document the differences in manual pages (or equivalent), together + with instructions on where to get the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +5. You may charge a reasonable copying fee for any distribution of this +Package. You may charge any fee you choose for support of this +Package. You may not charge a fee for this Package itself. However, +you may distribute this Package in aggregate with other (possibly +commercial) programs as part of a larger (possibly commercial) software +distribution provided that you do not advertise this Package as a +product of your own. You may embed this Package's interpreter within +an executable of yours (by linking); this shall be construed as a mere +form of aggregation, provided that the complete Standard Version of the +interpreter is so embedded. + +6. The scripts and library files supplied as input to or produced as +output from the programs of this Package do not automatically fall +under the copyright of this Package, but belong to whoever generated +them, and may be sold commercially, and may be aggregated with this +Package. If such scripts or library files are aggregated with this +Package via the so-called "undump" or "unexec" methods of producing a +binary executable image, then distribution of such an image shall +neither be construed as a distribution of this Package nor shall it +fall under the restrictions of Paragraphs 3 and 4, provided that you do +not represent such an executable image as a Standard Version of this +Package. + +7. C subroutines (or comparably compiled subroutines in other +languages) supplied by you and linked into this Package in order to +emulate subroutines and variables of the language defined by this +Package shall not be considered part of this Package, but are the +equivalent of input as in Paragraph 6, provided these subroutines do +not change the language in any way that would cause it to fail the +regression tests for the language. + +8. Aggregation of this Package with a commercial distribution is always +permitted provided that the use of this Package is embedded; that is, +when no overt attempt is made to make this Package's interfaces visible +to the end user of the commercial distribution. Such use shall not be +construed as a distribution of this Package. + +9. The name of the Copyright Holder may not be used to endorse or promote +products derived from this software without specific prior written permission. + +10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED +WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + + The End diff --git a/doc/Copying b/doc/Copying new file mode 100644 index 0000000000..d6400a2c8a --- /dev/null +++ b/doc/Copying @@ -0,0 +1,344 @@ + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software + interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Program does not specify a +version number of this License, you may choose any version ever +published by the Free Software Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/htdocs/generate-assets.js b/htdocs/generate-assets.js index 91647b2c6f..445eff3677 100755 --- a/htdocs/generate-assets.js +++ b/htdocs/generate-assets.js @@ -2,7 +2,8 @@ /* eslint-env node */ -const yargs = require('yargs'); +const yargs = require('yargs/yargs'); +const { hideBin } = require('yargs/helpers'); const chokidar = require('chokidar'); const path = require('path'); const { minify } = require('terser'); @@ -14,7 +15,7 @@ const postcss = require('postcss'); const rtlcss = require('rtlcss'); const cssMinify = require('cssnano'); -const argv = yargs +const argv = yargs(hideBin(process.argv)) .usage('$0 Options') .version(false) .alias('help', 'h') diff --git a/htdocs/helpFiles/Entering-Angles.html b/htdocs/helpFiles/Entering-Angles.html index bfa15541e0..b0837609f1 100644 --- a/htdocs/helpFiles/Entering-Angles.html +++ b/htdocs/helpFiles/Entering-Angles.html @@ -43,7 +43,7 @@

Entering Angles

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Entering-Equations.html b/htdocs/helpFiles/Entering-Equations.html index bd758fdda8..b05fd50386 100644 --- a/htdocs/helpFiles/Entering-Equations.html +++ b/htdocs/helpFiles/Entering-Equations.html @@ -45,7 +45,7 @@

    Entering Equations

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Entering-Formulas.html b/htdocs/helpFiles/Entering-Formulas.html index 60e8dea8b6..21b40231db 100644 --- a/htdocs/helpFiles/Entering-Formulas.html +++ b/htdocs/helpFiles/Entering-Formulas.html @@ -107,7 +107,7 @@

    Entering Formulas

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Entering-Formulas10.html b/htdocs/helpFiles/Entering-Formulas10.html index 7f60e91f56..8b00add2f9 100644 --- a/htdocs/helpFiles/Entering-Formulas10.html +++ b/htdocs/helpFiles/Entering-Formulas10.html @@ -107,7 +107,7 @@

    Entering Formulas

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Entering-Logarithms.html b/htdocs/helpFiles/Entering-Logarithms.html index ed055d68fc..b38374e0b0 100644 --- a/htdocs/helpFiles/Entering-Logarithms.html +++ b/htdocs/helpFiles/Entering-Logarithms.html @@ -54,7 +54,7 @@

    Entering Logarithms

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Entering-Logarithms10.html b/htdocs/helpFiles/Entering-Logarithms10.html index 39b764f85e..de0bba372f 100644 --- a/htdocs/helpFiles/Entering-Logarithms10.html +++ b/htdocs/helpFiles/Entering-Logarithms10.html @@ -55,7 +55,7 @@

    Entering Logarithms

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Entering-Points.html b/htdocs/helpFiles/Entering-Points.html index 077cf66fa0..a5b23d448c 100644 --- a/htdocs/helpFiles/Entering-Points.html +++ b/htdocs/helpFiles/Entering-Points.html @@ -37,7 +37,7 @@

    Entering Points

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Entering-Syntax.html b/htdocs/helpFiles/Entering-Syntax.html index c8793fd408..ccca4ce042 100644 --- a/htdocs/helpFiles/Entering-Syntax.html +++ b/htdocs/helpFiles/Entering-Syntax.html @@ -246,4 +246,4 @@

    Other Mathematical Func For more information see the -list of all available functions. +list of all available functions. diff --git a/htdocs/helpFiles/Entering-Units.html b/htdocs/helpFiles/Entering-Units.html index 9ac3fc9616..e8a4912770 100644 --- a/htdocs/helpFiles/Entering-Units.html +++ b/htdocs/helpFiles/Entering-Units.html @@ -219,5 +219,5 @@

    Units Available in WeBWorK

    - More details on units in WeBWorK + More details on units in WeBWorK

    diff --git a/htdocs/helpFiles/Entering-Vectors.html b/htdocs/helpFiles/Entering-Vectors.html index 7a1523b13b..7d7d2339cc 100644 --- a/htdocs/helpFiles/Entering-Vectors.html +++ b/htdocs/helpFiles/Entering-Vectors.html @@ -64,7 +64,7 @@

    Entering Vectors

  • - + Link to a list of all available functions
  • diff --git a/htdocs/helpFiles/Syntax.html b/htdocs/helpFiles/Syntax.html index c8793fd408..ccca4ce042 100644 --- a/htdocs/helpFiles/Syntax.html +++ b/htdocs/helpFiles/Syntax.html @@ -246,4 +246,4 @@

    Other Mathematical Func For more information see the -list of all available functions. +list of all available functions. diff --git a/htdocs/helpFiles/Units.html b/htdocs/helpFiles/Units.html index 456bc1ada4..02cea15387 100644 --- a/htdocs/helpFiles/Units.html +++ b/htdocs/helpFiles/Units.html @@ -199,5 +199,5 @@

    Units Available in WeBWorK

    - More details on units in WeBWorK + More details on units in WeBWorK

    diff --git a/htdocs/js/AppletSupport/ww_applet_support.js b/htdocs/js/AppletSupport/ww_applet_support.js index 4dfd18a01e..e9d599d8c3 100644 --- a/htdocs/js/AppletSupport/ww_applet_support.js +++ b/htdocs/js/AppletSupport/ww_applet_support.js @@ -95,7 +95,6 @@ class ww_applet { if (typeof newState === 'undefined') newState = 'restart_applet'; const stateInput = ww_applet_list[this.appletName].stateInput; getQE(stateInput).value = newState; - getQE(`previous_${stateInput}`).value = newState; } // STATE: @@ -108,7 +107,7 @@ class ww_applet { // Communication with the applet is in plain text, not in base64 code. if (state) { - if (this.debug) console.log(`${this.appletName}: Obtain state from calling paramater`); + if (this.debug) console.log(`${this.appletName}: Obtain state from calling parameter`); } else { if (this.debug) console.log(`${this.appletName}: Obtain state from ${this.stateInput}`); // Hidden answer box preserving applet state @@ -275,12 +274,14 @@ class ww_applet { if (form.submitHandlerInitialized) return; form.submitHandlerInitialized = true; - // Connect the submit action handler to the form. - form.addEventListener('submit', () => { - for (const appletName in ww_applet_list) { - ww_applet_list[appletName].submitAction(); - } - }); + // Connect the submit action handler to the form submit buttons. + for (const button of form.querySelectorAll('input[type="submit"]')) { + button.addEventListener('click', () => { + for (const appletName in ww_applet_list) { + ww_applet_list[appletName].submitAction(); + } + }); + } }; // Initialize applet support and the applets. diff --git a/htdocs/js/Feedback/feedback.js b/htdocs/js/Feedback/feedback.js index 5328334ec2..731dcb8c55 100644 --- a/htdocs/js/Feedback/feedback.js +++ b/htdocs/js/Feedback/feedback.js @@ -47,12 +47,17 @@ if (feedbackPopover.tip) feedbackPopover.tip.dataset.iframeHeight = '1'; const revealCorrectBtn = feedbackPopover.tip?.querySelector('.reveal-correct-btn'); - if (revealCorrectBtn && feedbackPopover.correctRevealed) { - revealCorrectBtn.nextElementSibling?.classList.remove('d-none'); - revealCorrectBtn.remove(); - } else { - revealCorrectBtn?.addEventListener('click', () => { - feedbackPopover.correctRevealed = true; + if (revealCorrectBtn) { + const fragment = new DocumentFragment(); + const container = document.createElement('div'); + container.innerHTML = feedbackBtn.dataset.bsContent; + const button = container.querySelector('.reveal-correct-btn'); + button?.nextElementSibling?.classList.remove('d-none'); + button?.remove(); + fragment.append(container); + const feedbackNoRevealBtn = fragment.firstElementChild.innerHTML; + + revealCorrectBtn.addEventListener('click', () => { revealCorrectBtn.classList.add('fade-out'); revealCorrectBtn.parentElement.classList.add('resize-transition'); revealCorrectBtn.parentElement.style.maxWidth = `${revealCorrectBtn.parentElement.offsetWidth}px`; @@ -65,6 +70,12 @@ revealCorrectBtn.remove(); feedbackPopover.update(); }); + + feedbackBtn.addEventListener( + 'hidden.bs.popover', + () => feedbackPopover.setContent({ '.popover-body': feedbackNoRevealBtn }), + { once: true } + ); }); } }); diff --git a/htdocs/js/GraphTool/circletool.js b/htdocs/js/GraphTool/circletool.js index 50b9c663fc..af51d27acf 100644 --- a/htdocs/js/GraphTool/circletool.js +++ b/htdocs/js/GraphTool/circletool.js @@ -188,7 +188,7 @@ const center = this.center; delete this.center; - center.setAttribute(gt.definingPointAttributes); + center.setAttribute(gt.definingPointAttributes()); center.on('down', () => gt.onPointDown(center)); center.on('up', () => gt.onPointUp(center)); diff --git a/htdocs/js/GraphTool/cubictool.js b/htdocs/js/GraphTool/cubictool.js index c65983a3f4..46b4e23b27 100644 --- a/htdocs/js/GraphTool/cubictool.js +++ b/htdocs/js/GraphTool/cubictool.js @@ -12,7 +12,7 @@ constructor(point1, point2, point3, point4, solid) { for (const point of [point1, point2, point3, point4]) { - point.setAttribute(gt.definingPointAttributes); + point.setAttribute(gt.definingPointAttributes()); if (!gt.isStatic) { point.on('down', () => gt.onPointDown(point)); point.on('up', () => gt.onPointUp(point)); diff --git a/htdocs/js/GraphTool/graphtool.js b/htdocs/js/GraphTool/graphtool.js index dcfcfebf81..0991e15fee 100644 --- a/htdocs/js/GraphTool/graphtool.js +++ b/htdocs/js/GraphTool/graphtool.js @@ -39,7 +39,7 @@ window.graphTool = (containerId, options) => { underConstructionFixed: JXG.palette.red // defined to be '#d55e00' }; - gt.definingPointAttributes = { + gt.definingPointAttributes = () => ({ size: 3, fixed: false, highlight: true, @@ -49,8 +49,9 @@ window.graphTool = (containerId, options) => { fillColor: gt.color.point, highlightStrokeWidth: 1, highlightStrokeColor: gt.color.focusCurve, - highlightFillColor: gt.color.pointHighlight - }; + highlightFillColor: gt.color.pointHighlight, + tabindex: gt.isStatic ? -1 : 0 + }); gt.options = options; gt.snapSizeX = options.snapSizeX ? options.snapSizeX : 1; @@ -69,7 +70,6 @@ window.graphTool = (containerId, options) => { if ('htmlInputId' in options) gt.html_input = document.getElementById(options.htmlInputId); const cfgOptions = { title: 'WeBWorK Graph Tool', - description: options.ariaDescription ?? 'Interactively graph objects', showCopyright: false, pan: { enabled: false }, zoom: { enabled: false }, @@ -116,6 +116,14 @@ window.graphTool = (containerId, options) => { const setupBoard = () => { gt.board = JXG.JSXGraph.initBoard(`${containerId}_graph`, cfgOptions); + + const descriptionSpan = document.createElement('span'); + descriptionSpan.id = `${containerId}_description`; + descriptionSpan.classList.add('visually-hidden'); + descriptionSpan.textContent = options.ariaDescription ?? 'Interactively graph objects'; + gt.board.containerObj.after(descriptionSpan); + gt.board.containerObj.setAttribute('aria-describedby', descriptionSpan.id); + gt.board.suspendUpdate(); // Move the axes defining points to the end so that the arrows go to the board edges. @@ -198,7 +206,7 @@ window.graphTool = (containerId, options) => { } if (this.visProp.useunicodeminus) labelText = labelText.replace(/-/g, '\u2212'); - return addTeXDelims ?? this.visProp.label.usemathjax ? `\\(${labelText}\\)` : labelText; + return (addTeXDelims ?? this.visProp.label.usemathjax) ? `\\(${labelText}\\)` : labelText; }; gt.board.defaultAxes.x.defaultTicks.generateLabelText = generateLabelText; @@ -255,6 +263,113 @@ window.graphTool = (containerId, options) => { gt.board.highlightInfobox = (_x, _y, el) => gt.board.highlightCustomInfobox('', el); if (!gt.isStatic) { + // FIXME: Delete this after upgrading to the next version of JSXGraph as this has been fixed in their + // develop branch and will be in that release. + // This is a mess to work around an issue with JSXGraph versions 1.11.1 or later. Their keyDownListener + // calls preventDefault on the keydown event when shift-tab is pressed. That prevents keyboard focus from + // moving backward in the tab order. So this removes the JSXGraph keyboard event handlers, then overrides + // the board's keyDownListener with essentially the same code with the exception that when the tab key is + // pressed, preventDefault is not called. Then the keyboard event listeners are added back, using this + // keydownListener. + gt.board.removeKeyboardEventHandlers(); + gt.board.keyDownListener = function (evt) { + const id_node = evt.target.id; + let done = true; + + if (!this.attr.keyboard.enabled || id_node === '' || evt.keyCode === 9) return false; + + const doc = this.containerObj.shadowRoot || document; + if (doc.activeElement) { + if (doc.activeElement.tagName === 'INPUT' || doc.activeElement.tagName === 'textarea') return false; + } + + const id = id_node.replace(this.containerObj.id + '_', ''); + const el = this.select(id); + + if ( + (JXG.evaluate(this.attr.keyboard.panshift) && evt.shiftKey) || + (JXG.evaluate(this.attr.keyboard.panctrl) && evt.ctrlKey) + ) { + const doZoom = JXG.evaluate(this.attr.zoom.enabled) === true; + if (evt.keyCode === 38) this.clickUpArrow(); + else if (evt.keyCode === 40) this.clickDownArrow(); + else if (evt.keyCode === 37) this.clickLeftArrow(); + else if (evt.keyCode === 39) this.clickRightArrow(); + else if (doZoom && evt.keyCode === 171) this.zoomIn(); + else if (doZoom && evt.keyCode === 173) this.zoomOut(); + else if (doZoom && evt.keyCode === 79) this.zoom100(); + else done = false; + } else if (!evt.shiftKey && !evt.ctrlKey) { + let dx = JXG.evaluate(this.attr.keyboard.dx) / this.unitX; + let dy = JXG.evaluate(this.attr.keyboard.dy) / this.unitY; + if (JXG.exists(el.visProp)) { + if ( + JXG.exists(el.visProp.snaptogrid) && + el.visProp.snaptogrid && + el.evalVisProp('snapsizex') && + el.evalVisProp('snapsizey') + ) { + const res = el.getSnapSizes(); + dx = res[0]; + dy = res[1]; + } else if ( + JXG.exists(el.visProp.attracttogrid) && + el.visProp.attracttogrid && + el.evalVisProp('attractordistance') && + el.evalVisProp('attractorunit') + ) { + let sX = 1.1 * el.evalVisProp('attractordistance'); + let sY = sX; + if (el.evalVisProp('attractorunit') === 'screen') { + sX /= this.unitX; + sY /= this.unitX; + } + dx = Math.max(sX, dx); + dy = Math.max(sY, dy); + } + } + + let dir; + if (evt.keyCode === 38) dir = [0, dy]; + else if (evt.keyCode === 40) dir = [0, -dy]; + else if (evt.keyCode === 37) dir = [-dx, 0]; + else if (evt.keyCode === 39) dir = [dx, 0]; + else done = false; + + if ( + dir && + el.isDraggable && + el.visPropCalc.visible && + ((this.geonextCompatibilityMode && + (JXG.isPoint(el) || el.elementClass === Const.OBJECT_CLASS_TEXT)) || + !this.geonextCompatibilityMode) && + !el.evalVisProp('fixed') + ) { + this.mode = this.BOARD_MODE_DRAG; + if (JXG.exists(el.coords)) { + const actPos = el.coords.usrCoords.slice(1); + dir[0] += actPos[0]; + dir[1] += actPos[1]; + el.setPosition(JXG.COORDS_BY_USER, dir); + this.updateInfobox(el); + } else { + this.displayInfobox(false); + el.setPositionDirectly(Const.COORDS_BY_USER, dir, [0, 0]); + } + + this.triggerEventHandlers(['keymove', 'move'], [evt, this.mode]); + el.triggerEventHandlers(['keydrag', 'drag'], [evt]); + this.mode = this.BOARD_MODE_NONE; + } + } + + this.update(); + + if (done && JXG.exists(evt.preventDefault)) evt.preventDefault(); + return done; + }; + gt.board.addKeyboardEventHandlers(); + gt.graphContainer.tabIndex = -1; gt.board.containerObj.tabIndex = -1; @@ -798,7 +913,7 @@ window.graphTool = (containerId, options) => { const point = gt.board.create('point', [gt.snapRound(x, gt.snapSizeX), gt.snapRound(y, gt.snapSizeY)], { snapSizeX: gt.snapSizeX, snapSizeY: gt.snapSizeY, - ...gt.definingPointAttributes + ...gt.definingPointAttributes() }); point.setAttribute({ snapToGrid: true }); if (!gt.isStatic) { diff --git a/htdocs/js/GraphTool/intervaltools.js b/htdocs/js/GraphTool/intervaltools.js index 8839e82dcb..8fa9a720cb 100644 --- a/htdocs/js/GraphTool/intervaltools.js +++ b/htdocs/js/GraphTool/intervaltools.js @@ -454,7 +454,8 @@ highlightStrokeWidth: 3, highlightStrokeColor: gt.color.pointHighlightDarker, // highlightFillColor is gt.color.pointHighlight if not included. - highlightFillColor: gt.color.pointHighlightDarker + highlightFillColor: gt.color.pointHighlightDarker, + tabindex: gt.isStatic ? -1 : 0 }; } diff --git a/htdocs/js/GraphTool/linetool.js b/htdocs/js/GraphTool/linetool.js index acf6cb03c1..9e97d1a3c3 100644 --- a/htdocs/js/GraphTool/linetool.js +++ b/htdocs/js/GraphTool/linetool.js @@ -188,7 +188,7 @@ const point1 = this.point1; delete this.point1; - point1.setAttribute(gt.definingPointAttributes); + point1.setAttribute(gt.definingPointAttributes()); point1.on('down', () => gt.onPointDown(point1)); point1.on('up', () => gt.onPointUp(point1)); diff --git a/htdocs/js/GraphTool/parabolatool.js b/htdocs/js/GraphTool/parabolatool.js index c1da9bb0da..e0d65cce5c 100644 --- a/htdocs/js/GraphTool/parabolatool.js +++ b/htdocs/js/GraphTool/parabolatool.js @@ -237,7 +237,7 @@ const vertex = this.vertex; delete this.vertex; - vertex.setAttribute(gt.definingPointAttributes); + vertex.setAttribute(gt.definingPointAttributes()); vertex.on('down', () => gt.onPointDown(vertex)); vertex.on('up', () => gt.onPointUp(vertex)); diff --git a/htdocs/js/GraphTool/pointtool.js b/htdocs/js/GraphTool/pointtool.js index 2420b41a24..eea92d4fde 100644 --- a/htdocs/js/GraphTool/pointtool.js +++ b/htdocs/js/GraphTool/pointtool.js @@ -22,7 +22,8 @@ strokeColor: gt.color.curve, fixed: gt.isStatic, highlightStrokeColor: gt.color.underConstruction, - highlightFillColor: gt.color.pointHighlight + highlightFillColor: gt.color.pointHighlight, + tabindex: gt.isStatic ? -1 : 0 }) ); diff --git a/htdocs/js/GraphTool/quadratictool.js b/htdocs/js/GraphTool/quadratictool.js index a5a8d94203..bef7eb2893 100644 --- a/htdocs/js/GraphTool/quadratictool.js +++ b/htdocs/js/GraphTool/quadratictool.js @@ -12,7 +12,7 @@ constructor(point1, point2, point3, solid) { for (const point of [point1, point2, point3]) { - point.setAttribute(gt.definingPointAttributes); + point.setAttribute(gt.definingPointAttributes()); if (!gt.isStatic) { point.on('down', () => gt.onPointDown(point)); point.on('up', () => gt.onPointUp(point)); diff --git a/htdocs/js/GraphTool/quadrilateral.js b/htdocs/js/GraphTool/quadrilateral.js index 9ec263626b..b9211b9680 100644 --- a/htdocs/js/GraphTool/quadrilateral.js +++ b/htdocs/js/GraphTool/quadrilateral.js @@ -12,7 +12,7 @@ constructor(point1, point2, point3, point4, solid) { for (const point of [point1, point2, point3, point4]) { - point.setAttribute(gt.definingPointAttributes); + point.setAttribute(gt.definingPointAttributes()); if (!gt.isStatic) { point.on('down', () => gt.onPointDown(point)); point.on('up', () => gt.onPointUp(point)); diff --git a/htdocs/js/GraphTool/sinewavetool.js b/htdocs/js/GraphTool/sinewavetool.js index 057559d9f5..a46e597a13 100644 --- a/htdocs/js/GraphTool/sinewavetool.js +++ b/htdocs/js/GraphTool/sinewavetool.js @@ -12,7 +12,7 @@ constructor(shiftPoint, periodPoint, amplitudePoint, solid) { for (const point of [shiftPoint, periodPoint, amplitudePoint]) { - point.setAttribute(gt.definingPointAttributes); + point.setAttribute(gt.definingPointAttributes()); if (!gt.isStatic) { point.on('down', () => gt.onPointDown(point)); point.on('up', () => gt.onPointUp(point)); diff --git a/htdocs/js/GraphTool/triangle.js b/htdocs/js/GraphTool/triangle.js index e68f15aea6..757b5eb665 100644 --- a/htdocs/js/GraphTool/triangle.js +++ b/htdocs/js/GraphTool/triangle.js @@ -12,7 +12,7 @@ constructor(point1, point2, point3, solid) { for (const point of [point1, point2, point3]) { - point.setAttribute(gt.definingPointAttributes); + point.setAttribute(gt.definingPointAttributes()); if (!gt.isStatic) { point.on('down', () => gt.onPointDown(point)); point.on('up', () => gt.onPointUp(point)); diff --git a/htdocs/js/ImageView/imageview.js b/htdocs/js/ImageView/imageview.js index 2d99e2b985..3e30ae4736 100644 --- a/htdocs/js/ImageView/imageview.js +++ b/htdocs/js/ImageView/imageview.js @@ -31,6 +31,12 @@ modal.setAttribute('aria-label', 'image view dialog'); modal.tabIndex = -1; + // Force the dialog into light mode. This is needed for a webwork2 page in dark mode since the dialog is outside + // of the problem content. At least until PG is updated to honor dark mode. Further discussion on this will + // also be needed at that time since many images have transparent backgrounds that will not work with a dark + // background. + modal.dataset.bsTheme = 'light'; + const dialog = document.createElement('div'); dialog.classList.add('modal-dialog'); @@ -219,7 +225,7 @@ if (graphDiv) { graphDiv.style.width = width + 'px'; graphDiv.style.height = height + 'px'; - this.dispatchEvent(new Event('resized.imageview')); + graphDiv.dispatchEvent(new Event('resized.imageview')); } // Re-position the modal. @@ -312,7 +318,7 @@ backdrop.style.opacity = '0.2'; }); modal.addEventListener('hidden.bs.modal', () => { - if (imgType == 'div') this.dispatchEvent(new Event('hidden.imageview')); + if (graphDiv) graphDiv.dispatchEvent(new Event('hidden.imageview')); bsModal.dispose(); modal.remove(); window.removeEventListener('resize', onWinResize); diff --git a/htdocs/js/Knowls/knowl.js b/htdocs/js/Knowls/knowl.js index 496b42a7f6..78a933827f 100644 --- a/htdocs/js/Knowls/knowl.js +++ b/htdocs/js/Knowls/knowl.js @@ -24,6 +24,10 @@ knowl.knowlModal.setAttribute('aria-labelledby', `${knowl.knowlModal.id}-title`); knowl.knowlModal.setAttribute('aria-hidden', 'true'); + // Force the dialog into light mode. This is needed for a webwork2 page in dark mode since the dialog is + // outside of the problem content. At least until PG and the help files are updated to honor dark mode. + knowl.knowlModal.dataset.bsTheme = 'light'; + const knowlDialog = document.createElement('div'); knowlDialog.classList.add( 'knowl-dialog', diff --git a/htdocs/js/MathQuill/mqeditor.js b/htdocs/js/MathQuill/mqeditor.js index 7faa181b2c..7d2de503fa 100644 --- a/htdocs/js/MathQuill/mqeditor.js +++ b/htdocs/js/MathQuill/mqeditor.js @@ -555,7 +555,15 @@ toolbarEnabled = !toolbarEnabled; localStorage.setItem('MQEditorToolbarEnabled', toolbarEnabled); if (!toolbarEnabled && answerQuill.toolbar) toolbarRemove(); - menu.hide(); + // Bootstrap tries to focus the triggering element after hiding the menu. However, the menu gets + // disposed of and the hidden link which is the triggering element removed too quickly in the + // hidden.bs.dropdown event, and that causes an exception. So ignore that exception so that the + // answerQuill textarea is focused instead. + try { + menu.hide(); + } catch { + /* ignore */ + } answerQuill.textarea.focus(); }, { once: true } diff --git a/htdocs/js/MathView/mathview.js b/htdocs/js/MathView/mathview.js index 92e3acb10a..1162363876 100644 --- a/htdocs/js/MathView/mathview.js +++ b/htdocs/js/MathView/mathview.js @@ -410,7 +410,12 @@ if (node.dataset.mvInitialized === 'true') continue; if (node.classList.contains('codeshard')) new MathViewer(node); - else node.querySelectorAll('.codeshard').forEach((input) => new MathViewer(input)); + else { + for (const input of node.querySelectorAll('.codeshard')) { + if (input.dataset.mvInitialized === 'true') continue; + new MathViewer(input); + } + } if (node.classList.contains('latexentryfield')) new MathViewer(node, { renderingMode: 'LATEX', @@ -418,14 +423,14 @@ includeDelimiters: true }); else - node.querySelectorAll('.latexentryfield').forEach( - (input) => - new MathViewer(input, { - renderingMode: 'LATEX', - decoratedTextBoxAsInput: false, - includeDelimiters: true - }) - ); + for (const input of node.querySelectorAll('.latexentryfield')) { + if (input.dataset.mvInitialized === 'true') continue; + new MathViewer(input, { + renderingMode: 'LATEX', + decoratedTextBoxAsInput: false, + includeDelimiters: true + }); + } } } } diff --git a/htdocs/js/PODViewer/podviewer.css b/htdocs/js/PODViewer/podviewer.css new file mode 100644 index 0000000000..e4f17811d2 --- /dev/null +++ b/htdocs/js/PODViewer/podviewer.css @@ -0,0 +1,66 @@ +.main-index-header, +.pod-header { + height: 65px; + top: 0; + left: 0; + right: 0; + z-index: 2; +} + +#sidebar { + --bs-offcanvas-width: 300px; + overflow-y: auto; +} + +#sidebar ul.nav ul.nav li { + border-left: 1px solid #e1e4e8; + padding-left: 10px; +} + +#sidebar ul.nav ul.nav li:hover { + border-left: 6px solid #e1e4e8; + padding-left: 5px; +} + +.main-index-container, +.pod-page-container { + margin-top: 65px; +} + +@media only screen and (min-width: 768px) { + #sidebar { + height: calc(100vh - 65px); + width: 300px; + } + + .pod-page-container { + margin-left: 300px; + } +} + +#_podtop_ pre { + border: 1px solid #ccc; + border-radius: 5px; + background: #f6f6f6; + padding: 0.75rem; +} + +#_podtop_, +#_podtop_ *[id] { + scroll-margin-top: calc(65px + 1rem); +} + +@media only screen and (max-width: 768px) { + .pod-header { + height: 100px; + } + + .pod-page-container { + margin-top: 100px; + } + + #_podtop_, + #_podtop_ *[id] { + scroll-margin-top: calc(100px + 1rem); + } +} diff --git a/htdocs/js/PODViewer/podviewer.js b/htdocs/js/PODViewer/podviewer.js new file mode 100644 index 0000000000..795093205a --- /dev/null +++ b/htdocs/js/PODViewer/podviewer.js @@ -0,0 +1,8 @@ +(() => { + const offcanvas = bootstrap.Offcanvas.getOrCreateInstance(document.getElementById('sidebar')); + for (const link of document.querySelectorAll('#sidebar .nav-link')) { + // The timeout is to workaround an issue in Chrome. If the offcanvas hides before the window scrolls to the + // fragment in the page, scrolling stops before it gets there. + link.addEventListener('click', () => setTimeout(() => offcanvas.hide(), 500)); + } +})(); diff --git a/htdocs/js/Plots/plots.js b/htdocs/js/Plots/plots.js new file mode 100644 index 0000000000..e53dc99fc9 --- /dev/null +++ b/htdocs/js/Plots/plots.js @@ -0,0 +1,597 @@ +/* global JXG */ + +'use strict'; + +const PGplots = { + async plot(boardContainerId, plotContents, options) { + const drawBoard = (id) => { + const boundingBox = options.board?.boundingBox ?? [-5, 5, 5, -5]; + + // Disable highlighting for all elements. + JXG.Options.elements.highlight = false; + + // Adjust layers to match standard TikZ layers. The "axis" is bumped up a layer so it is above "axis + // ticks". The rest are on layer 3 by default, so they are moved up to main layer. The remaining layer + // settings should be okay for now. + JXG.Options.layer.axis = 3; + JXG.Options.layer.polygon = 5; + JXG.Options.layer.sector = 5; + JXG.Options.layer.angle = 5; + JXG.Options.layer.integral = 5; + + const board = JXG.JSXGraph.initBoard( + id, + JXG.merge( + { + title: options.board?.title ?? 'Graph', + boundingBox, + showCopyright: false, + axis: false, + drag: { enabled: false }, + showNavigation: options.board?.showNavigation ?? false, + pan: { enabled: options.board?.showNavigation ?? false }, + zoom: { enabled: options.board?.showNavigation ?? false } + }, + options.board?.overrideOptions ?? {} + ) + ); + + // The board now has its own clone of the options with the custom settings above which will apply for + // anything created on the board. So reset the JSXGraph defaults so that other JSXGraph images on the page + // don't get these settings. + JXG.Options.elements.highlight = true; + JXG.Options.layer.axis = 2; + JXG.Options.layer.polygon = 3; + JXG.Options.layer.sector = 3; + JXG.Options.layer.angle = 3; + JXG.Options.layer.integral = 3; + + const descriptionSpan = document.createElement('span'); + descriptionSpan.id = `${id}_description`; + descriptionSpan.classList.add('visually-hidden'); + descriptionSpan.textContent = options.ariaDescription ?? 'Generated graph'; + board.containerObj.after(descriptionSpan); + board.containerObj.setAttribute('aria-describedby', descriptionSpan.id); + + // This object is passed to the plotContents method as its second argument and exposes these methods (and + // potentially other things in the future) to the code that is called in that method. So the JavaScript code + // generated in JSXGraph.pm can use these methods. + const plot = { + // Convert a decimal number into a fraction or mixed number. This is basically the JXG.toFraction method + // except that the "mixed" parameter is added, and it returns an improper fraction if mixed is false. + toFraction(x, useTeX, mixed, order) { + const arr = JXG.Math.decToFraction(x, order); + + if (arr[1] === 0 && arr[2] === 0) { + return '0'; + } else { + let str = ''; + // Sign + if (arr[0] < 0) str += '-'; + if (arr[2] === 0) { + // Integer + str += arr[1]; + } else if (!(arr[2] === 1 && arr[3] === 1)) { + // Proper fraction + if (mixed) { + if (arr[1] !== 0) str += arr[1] + ' '; + if (useTeX) str += `\\frac{${arr[2]}}{${arr[3]}}`; + else str += `${arr[2]}/${arr[3]}`; + } else { + if (useTeX) str += `\\frac{${arr[3] * arr[1] + arr[2]}}{${arr[3]}}`; + else str += `${arr[3] * arr[1] + arr[2]}/${arr[3]}`; + } + } + return str; + } + }, + + // Override the default axis generateLabelText method so that 0 is displayed + // using MathJax if the axis is configured to show tick labels using MathJax. + generateLabelText(tick, zero, value) { + if (JXG.exists(value)) return this.formatLabelText(value); + const distance = this.getDistanceFromZero(zero, tick); + return this.formatLabelText(Math.abs(distance) < JXG.Math.eps ? 0 : distance / this.visProp.scale); + }, + + trimTrailingZeros(value) { + if (value.indexOf('.') > -1 && value.endsWith('0')) { + value = value.replace(/0+$/, ''); + // Remove the decimal if it is now at the end. + value = value.replace(/\.$/, ''); + } + return value; + }, + + // Override the formatLabelText method for the axes ticks so that + // better number formats can be used for tick labels. + formatLabelText(value) { + let labelText; + + if (JXG.isNumber(value)) { + if (this.visProp.label.format === 'fraction' || this.visProp.label.format === 'mixed') { + labelText = plot.toFraction( + value, + this.visProp.label.usemathjax, + this.visProp.label.format === 'mixed' + ); + } else if (this.visProp.label.format === 'scinot') { + const [mantissa, exponent] = value.toExponential(this.visProp.digits).toString().split('e'); + labelText = this.visProp.label.usemathjax + ? `${plot.trimTrailingZeros(mantissa)}\\cdot 10^{${exponent}}` + : `${plot.trimTrailingZeros(mantissa)} x 10^${exponent}`; + } else { + labelText = plot.trimTrailingZeros(value.toFixed(this.visProp.digits).toString()); + } + } else { + labelText = value.toString(); + } + + if (this.visProp.scalesymbol.length > 0) { + if (labelText === '1') labelText = this.visProp.scalesymbol; + else if (labelText === '-1') labelText = `-${this.visProp.scalesymbol}`; + else if (labelText !== '0') labelText = labelText + this.visProp.scalesymbol; + } + + return this.visProp.label.usemathjax ? `\\(${labelText}\\)` : labelText; + }, + + createLabel(x, y, text, options) { + const anchor = options.angleAnchor; + delete options.angleAnchor; + const rotate = options.rotate; + delete options.rotate; + + const textElement = board.create('text', [x, y, text], options); + + if (typeof anchor !== 'undefined') { + const cosA = Math.cos((anchor * Math.PI) / 180); + const sinA = Math.sin((anchor * Math.PI) / 180); + + const transform = board.create( + 'transform', + [ + () => { + const [w, h] = textElement.getSize(); + return ( + (w * Math.abs(sinA) > h * Math.abs(cosA) + ? (-h / 2 / Math.abs(sinA)) * cosA + : ((cosA < 0 ? 1 : -1) * w) / 2) / board.unitX + ); + }, + () => { + const [w, h] = textElement.getSize(); + return ( + (w * Math.abs(sinA) > h * Math.abs(cosA) + ? ((sinA < 0 ? 1 : -1) * h) / 2 + : (-w / 2 / Math.abs(cosA)) * sinA) / board.unitY + ); + } + ], + { type: 'translate' } + ); + transform.bindTo(textElement); + } + if (rotate) textElement.addRotation(rotate); + + return textElement; + } + }; + + board.suspendUpdate(); + + // This axis provides the vertical grid lines. + if (options.grid?.x) { + plot.xGrid = board.create( + 'axis', + [ + [options.xAxis?.min ?? -5, options.xAxis?.position ?? 0], + [options.xAxis?.max ?? 5, options.xAxis?.position ?? 0] + ], + JXG.merge( + { + anchor: + options.xAxis?.location === 'top' + ? 'left' + : options.xAxis?.location === 'bottom' || options.xAxis?.location === 'box' + ? 'right' + : 'right left', + position: + options.xAxis?.location === 'middle' + ? options.board?.showNavigation + ? 'sticky' + : 'static' + : 'fixed', + firstArrow: false, + lastArrow: false, + straightFirst: options.board?.showNavigation ? true : false, + straightLast: options.board?.showNavigation ? true : false, + highlight: false, + strokeOpacity: 0, + ticks: { + drawLabels: false, + drawZero: true, + majorHeight: -1, + minorHeight: -1, + strokeColor: options.grid.color ?? '#808080', + strokeOpacity: options.grid.opacity ?? 0.2, + insertTicks: false, + ticksDistance: options.xAxis?.ticks?.distance ?? 2, + scale: options.xAxis?.ticks?.scale ?? 1, + minorTicks: options.grid.x.minorGrids ? (options.xAxis?.ticks?.minorTicks ?? 3) : 0, + ignoreInfiniteTickEndings: false, + majorTickEndings: [ + !options.board?.showNavigation && boundingBox[1] > (options.yAxis?.max ?? 5) + ? 0 + : 1, + !options.board?.showNavigation && boundingBox[3] < (options.yAxis?.min ?? -5) + ? 0 + : 1 + ], + tickEndings: [ + !options.board?.showNavigation && boundingBox[1] > (options.yAxis?.max ?? 5) + ? 0 + : 1, + !options.board?.showNavigation && boundingBox[3] < (options.yAxis?.min ?? -5) + ? 0 + : 1 + ] + }, + withLabel: false + }, + options.grid.x.overrideOptions ?? {} + ) + ); + } + + // This axis provides the horizontal grid lines. + if (options.grid?.y) { + plot.yGrid = board.create( + 'axis', + [ + [options.yAxis?.position ?? 0, options.yAxis?.min ?? -5], + [options.yAxis?.position ?? 0, options.yAxis?.max ?? -5] + ], + JXG.merge( + { + anchor: + options.yAxis?.location === 'right' + ? 'right' + : options.yAxis?.location === 'left' || options.yAxis?.location === 'box' + ? 'left' + : 'right left', + position: + options.yAxis?.location === 'center' + ? options.board?.showNavigation + ? 'sticky' + : 'static' + : 'fixed', + firstArrow: false, + lastArrow: false, + straightFirst: options.board?.showNavigation ? true : false, + straightLast: options.board?.showNavigation ? true : false, + highlight: false, + strokeOpacity: 0, + ticks: { + drawLabels: false, + drawZero: true, + majorHeight: -1, + minorHeight: -1, + strokeColor: options.grid.color ?? '#808080', + strokeOpacity: options.grid.opacity ?? 0.2, + insertTicks: false, + ticksDistance: options.yAxis?.ticks?.distance ?? 2, + scale: options.yAxis?.ticks?.scale ?? 1, + minorTicks: options.grid.y.minorGrids ? (options.yAxis?.ticks?.minorTicks ?? 3) : 0, + ignoreInfiniteTickEndings: false, + majorTickEndings: [ + !options.board?.showNavigation && boundingBox[0] < (options.xAxis?.min ?? -5) + ? 0 + : 1, + !options.board?.showNavigation && boundingBox[2] > (options.xAxis?.max ?? 5) ? 0 : 1 + ], + tickEndings: [ + !options.board?.showNavigation && boundingBox[0] < (options.xAxis?.min ?? -5) + ? 0 + : 1, + !options.board?.showNavigation && boundingBox[2] > (options.xAxis?.max ?? 5) ? 0 : 1 + ] + }, + withLabel: 0 + }, + options.grid.y.overrideOptions ?? {} + ) + ); + } + + if (options.xAxis?.visible) { + const xAxis = (plot.xAxis = board.create( + 'axis', + [ + [options.xAxis.min ?? -5, options.xAxis.position ?? 0], + [options.xAxis.max ?? 5, options.xAxis.position ?? 0] + ], + JXG.merge( + { + name: options.xAxis.name ?? '\\(x\\)', + anchor: + options.xAxis?.location === 'top' + ? 'left' + : options.xAxis?.location === 'bottom' || options.xAxis?.location === 'box' + ? 'right' + : 'right left', + position: + options.xAxis.location === 'middle' + ? options.board?.showNavigation + ? 'sticky' + : 'static' + : 'fixed', + firstArrow: options.xAxis.arrowsBoth ? { size: 7 } : false, + lastArrow: { size: 7 }, + highlight: false, + straightFirst: options.board?.showNavigation ? true : false, + straightLast: options.board?.showNavigation ? true : false, + withLabel: options.xAxis.location === 'middle' ? true : false, + label: { + anchorX: 'right', + anchorY: 'middle', + highlight: false, + offset: [-5, -3], + position: '100% left', + useMathJax: true + }, + ticks: { + drawLabels: options.xAxis.ticks?.labels && options.xAxis.ticks?.show ? true : false, + drawZero: + options.board?.showNavigation || + !options.yAxis?.visible || + (options.yAxis.location === 'center' && (options.yAxis.position ?? 0) != 0) || + ((options.yAxis.location === 'left' || options.yAxis.location === 'box') && + (options.xAxis.min ?? -5) != 0) || + (options.yAxis.location === 'right' && (options.xAxis.max ?? 5) != 0) + ? true + : false, + insertTicks: false, + ticksDistance: options.xAxis.ticks?.distance ?? 2, + scale: options.xAxis.ticks?.scale ?? 1, + scaleSymbol: options.xAxis.ticks?.scaleSymbol ?? '', + minorTicks: options.xAxis.ticks?.minorTicks ?? 3, + majorHeight: options.xAxis.ticks?.show ? 8 : 0, + minorHeight: options.xAxis.ticks?.show ? 5 : 0, + strokeWidth: 1.5, + majorTickEndings: [1, options.xAxis.location === 'box' ? 0 : 1], + tickEndings: [1, options.xAxis.location === 'box' ? 0 : 1], + digits: options.xAxis.ticks?.labelDigits ?? 2, + label: { + anchorX: 'middle', + anchorY: options.xAxis.location === 'top' ? 'bottom' : 'top', + offset: options.xAxis.location === 'top' ? [0, 4] : [0, -4], + highlight: 0, + ...(options.mathJaxTickLabels ? { useMathJax: true, display: 'html' } : {}), + format: options.xAxis.ticks?.labelFormat ?? 'decimal' + } + } + }, + options.xAxis.overrideOptions ?? {} + ) + )); + xAxis.defaultTicks.generateLabelText = plot.generateLabelText; + xAxis.defaultTicks.formatLabelText = plot.formatLabelText; + + if (options.xAxis.location !== 'middle' && options.xAxis.name !== '') { + plot.xLabel = board.create( + 'text', + [ + () => (xAxis.point1.X() + xAxis.point2.X()) / 2, + () => + options.xAxis.location === 'top' + ? board.getBoundingBox()[1] - 2 / board.unitY + : board.getBoundingBox()[3] + 2 / board.unitY, + options.xAxis.name ?? '\\(x\\)' + ], + { + anchorX: 'middle', + anchorY: options.xAxis.location === 'top' ? 'top' : 'bottom', + highlight: false, + color: 'black', + fixed: true, + useMathJax: true, + cssStyle: 'line-height: 1;' + } + ); + } + } + + if (options.yAxis?.visible) { + const yAxis = (plot.yAxis = board.create( + 'axis', + [ + [options.yAxis.position ?? 0, options.yAxis.min ?? -5], + [options.yAxis.position ?? 0, options.yAxis.max ?? -5] + ], + JXG.merge( + { + name: options.yAxis.name ?? '\\(y\\)', + anchor: + options.yAxis?.location === 'right' + ? 'right' + : options.yAxis?.location === 'left' || options.yAxis?.location === 'box' + ? 'left' + : 'right left', + position: + options.yAxis.location === 'center' + ? options.board?.showNavigation + ? 'sticky' + : 'static' + : 'fixed', + firstArrow: options.yAxis.arrowsBoth ? { size: 7 } : false, + lastArrow: { size: 7 }, + highlight: false, + straightFirst: options.board?.showNavigation ? true : false, + straightLast: options.board?.showNavigation ? true : false, + withLabel: options.yAxis.location === 'center' ? true : false, + label: { + anchorX: 'middle', + anchorY: 'top', + highlight: false, + distance: 1, + offset: [5, 1], + position: '100% right', + useMathJax: true + }, + ticks: { + drawLabels: options.yAxis.ticks?.labels && options.yAxis.ticks?.show ? true : false, + drawZero: + options.board?.showNavigation || + !options.xAxis?.visible || + (options.xAxis.location === 'middle' && (options.xAxis.position ?? 0) != 0) || + ((options.xAxis.location === 'bottom' || options.xAxis.location === 'box') && + (options.yAxis.min ?? -5) != 0) || + (options.xAxis.location === 'top' && (options.yAxis.max ?? 5) != 0) + ? true + : false, + insertTicks: false, + ticksDistance: options.yAxis.ticks?.distance ?? 2, + scale: options.yAxis.ticks?.scale ?? 1, + scaleSymbol: options.yAxis.ticks?.scaleSymbol ?? '', + minorTicks: options.yAxis.ticks?.minorTicks ?? 3, + majorHeight: options.yAxis.ticks?.show ? 8 : 0, + minorHeight: options.yAxis.ticks?.show ? 5 : 0, + strokeWidth: 1.5, + majorTickEndings: [options.yAxis.location === 'box' ? 0 : 1, 1], + tickEndings: [options.yAxis.location === 'box' ? 0 : 1, 1], + digits: options.yAxis.ticks?.labelDigits ?? 2, + label: { + anchorX: options.yAxis.location === 'right' ? 'left' : 'right', + anchorY: 'middle', + offset: options.yAxis.location === 'right' ? [6, 0] : [-6, 0], + highlight: false, + ...(options.mathJaxTickLabels ? { useMathJax: true, display: 'html' } : {}), + format: options.yAxis.ticks?.labelFormat ?? 'decimal' + } + } + }, + options.yAxis.overrideOptions ?? {} + ) + )); + yAxis.defaultTicks.generateLabelText = plot.generateLabelText; + yAxis.defaultTicks.formatLabelText = plot.formatLabelText; + + if (options.yAxis.location !== 'center' && options.yAxis.name !== '') { + plot.yLabel = board.create('text', [0, 0, options.yAxis.name ?? '\\(y\\)'], { + anchorX: 'middle', + anchorY: options.yAxis.location === 'right' ? 'bottom' : 'top', + rotate: 90, + highlight: 0, + color: 'black', + fixed: 1, + useMathJax: 1, + cssStyle: 'line-height: 1;' + }); + const transform = board.create( + 'transform', + [ + () => + options.yAxis.location === 'right' + ? board.getBoundingBox()[2] - 2 / board.unitX + : board.getBoundingBox()[0] + 2 / board.unitX, + () => (yAxis.point1.Y() + yAxis.point2.Y()) / 2 + ], + { type: 'translate' } + ); + transform.bindTo(plot.yLabel); + } + } + + plotContents(board, plot); + + board.unsuspendUpdate(); + + plot.updateBoundingBox = () => { + if (options.board?.showNavigation || (!plot.xAxis && !plot.yAxis)) return; + + const adjustLeft = options.yAxis?.visible && boundingBox[0] < (options.xAxis?.min ?? -5); + const adjustRight = options.yAxis?.visible && boundingBox[2] > (options.xAxis?.max ?? 5); + const adjustBottom = options.xAxis?.visible && boundingBox[3] < (options.yAxis?.min ?? 5); + const adjustTop = options.xAxis?.visible && boundingBox[1] > (options.yAxis?.max ?? -5); + if (!adjustLeft && !adjustRight && !adjustTop && !adjustBottom) return; + + let width = 0; + if (plot.yAxis) { + for (const label of plot.yAxis.defaultTicks.labels) { + const rect = label.rendNode.getBoundingClientRect(); + if (rect.width > width) width = rect.width; + } + if (plot.yLabel) width += plot.yLabel.rendNode.getBoundingClientRect().width + 4; + width += 12; + } + + let height = 0; + if (plot.xAxis) { + for (const label of plot.xAxis.defaultTicks.labels) { + const rect = label.rendNode.getBoundingClientRect(); + if (rect.height > height) height = rect.height; + } + if (plot.xLabel) height += plot.xLabel.rendNode.getBoundingClientRect().height + 4; + height += 8; + } + + const currentBoundingBox = board.getBoundingBox(); + board.setBoundingBox([ + adjustLeft ? options.xAxis.min - width / board.unitX : currentBoundingBox[0], + adjustTop ? options.yAxis.max + height / board.unitY : currentBoundingBox[1], + adjustRight ? options.xAxis.max + width / board.unitX : currentBoundingBox[2], + adjustBottom ? options.yAxis.min - height / board.unitY : currentBoundingBox[3] + ]); + + if (options.yAxis.location !== 'center' && (adjustLeft || adjustRight)) { + const anchorDist = adjustLeft + ? (options.xAxis?.min ?? -5) - board.getBoundingBox()[0] + : board.getBoundingBox()[2] - (options.xAxis?.max ?? -5); + plot.yAxis?.setAttribute({ anchorDist }); + plot.yGrid?.setAttribute({ anchorDist }); + } + if (options.xAxis.location !== 'center' && (adjustBottom || adjustTop)) { + const anchorDist = adjustBottom + ? (options.yAxis?.min ?? -5) - board.getBoundingBox()[3] + : board.getBoundingBox()[1] - (options.yAxis?.max ?? -5); + plot.xAxis?.setAttribute({ anchorDist }); + plot.xGrid?.setAttribute({ anchorDist }); + } + }; + + if (id.startsWith('magnified-')) { + board.containerObj.addEventListener('resized.imageview', () => { + board.resizeContainer(board.containerObj.clientWidth, board.containerObj.clientHeight, true); + setTimeout(plot.updateBoundingBox); + }); + board.containerObj.addEventListener('hidden.imageview', () => JXG.JSXGraph.freeBoard(board)); + } else { + setTimeout(plot.updateBoundingBox); + } + + return board; + }; + + const container = document.getElementById(boardContainerId); + if (!container) return; + + const drawPromise = (id) => + new Promise((resolve) => { + if (container.offsetWidth === 0) { + setTimeout(async () => resolve(await drawPromise(id)), 100); + return; + } + resolve(drawBoard(id)); + }); + + await drawPromise(boardContainerId); + + container.addEventListener('shown.imageview', async () => { + document + .getElementById(`magnified-${boardContainerId}`) + ?.classList.add(...Array.from(container.classList).filter((c) => c !== 'image-view-elt')); + await drawPromise(`magnified-${boardContainerId}`); + }); + } +}; diff --git a/htdocs/js/Plots/plots.scss b/htdocs/js/Plots/plots.scss index 19c3586878..116a1b3a76 100644 --- a/htdocs/js/Plots/plots.scss +++ b/htdocs/js/Plots/plots.scss @@ -1,4 +1,8 @@ .plots-jsxgraph { display: inline-block; - border-radius: 0px; + vertical-align: middle; + + &:not(.plots-jsxgraph-rounded) { + border-radius: 0px; + } } diff --git a/htdocs/js/SampleProblemViewer/documentation-search.js b/htdocs/js/SampleProblemViewer/documentation-search.js new file mode 100644 index 0000000000..e43c878ef7 --- /dev/null +++ b/htdocs/js/SampleProblemViewer/documentation-search.js @@ -0,0 +1,78 @@ +(async () => { + const searchBox = document.getElementById('search-box'); + const resultList = document.getElementById('result-list'); + if (!resultList || !searchBox) return; + + const rootURL = window.pgDocConfig?.rootURL ?? '.'; + const htmlSuffixMutation = window.pgDocConfig?.htmlSuffixMutation ?? [/\.p[gl]$/, '.html']; + const searchDataURL = window.pgDocConfig?.searchDataURL ?? 'sample-problem-search-data.json'; + + let searchData; + try { + const result = await fetch(searchDataURL); + searchData = await result.json(); + } catch (e) { + console.log(e); + return; + } + + const miniSearch = new MiniSearch({ + fields: ['filename', 'name', 'description', 'terms', 'macros', 'subjects'], + storeFields: ['type', 'filename', 'dir', 'description'] + }); + miniSearch.addAll(searchData); + + const searchMacrosCheck = document.getElementById('search-macros'); + const searchSampleProblemsCheck = document.getElementById('search-sample-problems'); + + document.getElementById('clear-search-button')?.addEventListener('click', () => { + searchBox.value = ''; + while (resultList.firstChild) resultList.firstChild.remove(); + }); + + const searchDocumentation = () => { + const searchMacros = searchMacrosCheck?.checked; + const searchSampleProblems = searchSampleProblemsCheck?.checked; + + while (resultList.firstChild) resultList.firstChild.remove(); + + if (!searchBox.value) return; + + for (const result of miniSearch.search(searchBox.value, { prefix: true })) { + if ( + (searchSampleProblems && result.type === 'sample problem') || + (searchMacros && result.type === 'macro') + ) { + const link = document.createElement('a'); + link.classList.add('list-group-item', 'list-group-item-action'); + link.href = `${rootURL}/${ + result.type === 'sample problem' ? 'sampleproblems' : result.type === 'macro' ? 'pod' : '' + }/${result.dir}/${result.filename.replace(...htmlSuffixMutation)}`; + + const linkText = document.createElement('span'); + linkText.classList.add('h4'); + linkText.textContent = `${result.filename} (${result.type})`; + link.append(linkText); + + if (result.description) { + const summary = document.createElement('div'); + summary.textContent = result.description; + link.append(summary); + } + + resultList.append(link); + } + } + + if (resultList.children.length == 0) { + const item = document.createElement('div'); + item.classList.add('alert', 'alert-info'); + item.innerHTML = 'No results found'; + resultList.append(item); + } + }; + + searchBox.addEventListener('keyup', searchDocumentation); + searchMacrosCheck?.addEventListener('change', searchDocumentation); + searchSampleProblemsCheck?.addEventListener('change', searchDocumentation); +})(); diff --git a/htdocs/js/Scaffold/scaffold.scss b/htdocs/js/Scaffold/scaffold.scss index a4528e2323..5393d530b5 100644 --- a/htdocs/js/Scaffold/scaffold.scss +++ b/htdocs/js/Scaffold/scaffold.scss @@ -15,6 +15,13 @@ min-width: 1.25rem; font-weight: bold; } + + // Force the accordion button to a dark color so that it has sufficient contrast even if the page is in dark + // mode. Bootstrap wants to use the dark mode color for this if the page is in dark mode. + &::after { + --bs-accordion-btn-icon: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='%23212529' stroke-linecap='round' stroke-linejoin='round' viewBox='0 0 16 16'%3E%3Cpath d='m2 5 6 6 6-6'/%3E%3C/svg%3E"); + --bs-accordion-btn-active-icon: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='%23001436' stroke-linecap='round' stroke-linejoin='round' viewBox='0 0 16 16'%3E%3Cpath d='m2 5 6 6 6-6'/%3E%3C/svg%3E"); + } } &.cannotopen > button.accordion-button::after { diff --git a/htdocs/package-lock.json b/htdocs/package-lock.json index d6ef7b0e4e..b47c393e54 100644 --- a/htdocs/package-lock.json +++ b/htdocs/package-lock.json @@ -8,36 +8,33 @@ "license": "GPL-2.0+", "dependencies": { "@openwebwork/mathquill": "^0.11.1", - "jsxgraph": "^1.10.1", + "jsxgraph": "^1.12.2", "jszip": "^3.10.1", "jszip-utils": "^0.1.0", - "plotly.js-dist-min": "^2.32.0", - "sortablejs": "^1.15.2" + "plotly.js-dist-min": "^3.1.0", + "sortablejs": "^1.15.6" }, "devDependencies": { - "autoprefixer": "^10.4.19", - "chokidar": "^3.6.0", - "cssnano": "^6.1.2", - "postcss": "^8.4.38", - "prettier": "^3.2.5", - "rtlcss": "^4.1.1", - "sass": "^1.75.0", - "terser": "^5.30.4", - "yargs": "^17.7.2" + "autoprefixer": "^10.4.21", + "chokidar": "^4.0.3", + "cssnano": "^7.1.0", + "postcss": "^8.5.6", + "prettier": "^3.6.2", + "rtlcss": "^4.3.0", + "sass": "^1.90.0", + "terser": "^5.43.1", + "yargs": "^18.0.0" } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -45,40 +42,35 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz", + "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -90,11 +82,322 @@ "integrity": "sha512-NLmu+AQ8i4bXX6Zuv5YMOkxhLsZisIW9pB0OvIIajVtAIsUu9ES7aG5Bhq823Ohc9xxA+2S7YrtmRaiDbp+GKA==", "license": "MPL-2.0" }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -103,46 +406,35 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", "dev": true, "funding": [ { @@ -158,12 +450,13 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -176,26 +469,20 @@ "postcss": "^8.1.0" } }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -204,9 +491,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "dev": true, "funding": [ { @@ -222,11 +509,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -239,13 +527,15 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", "dev": true, + "license": "MIT", "dependencies": { "browserslist": "^4.0.0", "caniuse-lite": "^1.0.0", @@ -254,9 +544,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001723", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz", - "integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==", + "version": "1.0.30001759", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz", + "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==", "dev": true, "funding": [ { @@ -271,89 +561,69 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, + "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" } }, "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", "dev": true, + "license": "ISC", "dependencies": { - "color-name": "~1.1.4" + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=20" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 10" + "node": ">=16" } }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" }, "node_modules/css-declaration-sorter": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", "dev": true, + "license": "ISC", "engines": { "node": "^14 || ^16 || >=18" }, @@ -362,10 +632,11 @@ } }, "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", @@ -378,12 +649,13 @@ } }, "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", "dev": true, + "license": "MIT", "dependencies": { - "mdn-data": "2.0.30", + "mdn-data": "2.12.2", "source-map-js": "^1.0.1" }, "engines": { @@ -391,10 +663,11 @@ } }, "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">= 6" }, @@ -407,6 +680,7 @@ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, + "license": "MIT", "bin": { "cssesc": "bin/cssesc" }, @@ -415,79 +689,82 @@ } }, "node_modules/cssnano": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", - "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.0.tgz", + "integrity": "sha512-Pu3rlKkd0ZtlCUzBrKL1Z4YmhKppjC1H9jo7u1o4qaKqyhvixFgu5qLyNIAOjSTg9DjVPtUqdROq2EfpVMEe+w==", "dev": true, + "license": "MIT", "dependencies": { - "cssnano-preset-default": "^6.1.2", - "lilconfig": "^3.1.1" + "cssnano-preset-default": "^7.0.8", + "lilconfig": "^3.1.3" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/cssnano" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/cssnano-preset-default": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", - "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.8.tgz", + "integrity": "sha512-d+3R2qwrUV3g4LEMOjnndognKirBZISylDZAF/TPeCWVjEwlXS2e4eN4ICkoobRe7pD3H6lltinKVyS1AJhdjQ==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "css-declaration-sorter": "^7.2.0", - "cssnano-utils": "^4.0.2", - "postcss-calc": "^9.0.1", - "postcss-colormin": "^6.1.0", - "postcss-convert-values": "^6.1.0", - "postcss-discard-comments": "^6.0.2", - "postcss-discard-duplicates": "^6.0.3", - "postcss-discard-empty": "^6.0.3", - "postcss-discard-overridden": "^6.0.2", - "postcss-merge-longhand": "^6.0.5", - "postcss-merge-rules": "^6.1.1", - "postcss-minify-font-values": "^6.1.0", - "postcss-minify-gradients": "^6.0.3", - "postcss-minify-params": "^6.1.0", - "postcss-minify-selectors": "^6.0.4", - "postcss-normalize-charset": "^6.0.2", - "postcss-normalize-display-values": "^6.0.2", - "postcss-normalize-positions": "^6.0.2", - "postcss-normalize-repeat-style": "^6.0.2", - "postcss-normalize-string": "^6.0.2", - "postcss-normalize-timing-functions": "^6.0.2", - "postcss-normalize-unicode": "^6.1.0", - "postcss-normalize-url": "^6.0.2", - "postcss-normalize-whitespace": "^6.0.2", - "postcss-ordered-values": "^6.0.2", - "postcss-reduce-initial": "^6.1.0", - "postcss-reduce-transforms": "^6.0.2", - "postcss-svgo": "^6.0.3", - "postcss-unique-selectors": "^6.0.4" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" + "cssnano-utils": "^5.0.1", + "postcss-calc": "^10.1.1", + "postcss-colormin": "^7.0.4", + "postcss-convert-values": "^7.0.6", + "postcss-discard-comments": "^7.0.4", + "postcss-discard-duplicates": "^7.0.2", + "postcss-discard-empty": "^7.0.1", + "postcss-discard-overridden": "^7.0.1", + "postcss-merge-longhand": "^7.0.5", + "postcss-merge-rules": "^7.0.6", + "postcss-minify-font-values": "^7.0.1", + "postcss-minify-gradients": "^7.0.1", + "postcss-minify-params": "^7.0.4", + "postcss-minify-selectors": "^7.0.5", + "postcss-normalize-charset": "^7.0.1", + "postcss-normalize-display-values": "^7.0.1", + "postcss-normalize-positions": "^7.0.1", + "postcss-normalize-repeat-style": "^7.0.1", + "postcss-normalize-string": "^7.0.1", + "postcss-normalize-timing-functions": "^7.0.1", + "postcss-normalize-unicode": "^7.0.4", + "postcss-normalize-url": "^7.0.1", + "postcss-normalize-whitespace": "^7.0.1", + "postcss-ordered-values": "^7.0.2", + "postcss-reduce-initial": "^7.0.4", + "postcss-reduce-transforms": "^7.0.1", + "postcss-svgo": "^7.1.0", + "postcss-unique-selectors": "^7.0.4" + }, + "engines": { + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/cssnano-utils": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", - "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.1.tgz", + "integrity": "sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==", "dev": true, + "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/csso": { @@ -495,6 +772,7 @@ "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", "dev": true, + "license": "MIT", "dependencies": { "css-tree": "~2.2.0" }, @@ -508,6 +786,7 @@ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", "dev": true, + "license": "MIT", "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" @@ -521,13 +800,29 @@ "version": "2.0.28", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", - "dev": true + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "dev": true, + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -547,13 +842,15 @@ "type": "github", "url": "https://github.com/sponsors/fb55" } - ] + ], + "license": "BSD-2-Clause" }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" }, @@ -565,10 +862,11 @@ } }, "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -579,22 +877,25 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.747", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.747.tgz", - "integrity": "sha512-+FnSWZIAvFHbsNVmUxhEqWiaOiPMcfum1GQzlWCg/wLigVtshOsjXHyEFfmt6cFK6+HkS3QOJBv6/3OPumbBfw==", - "dev": true + "version": "1.5.183", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.183.tgz", + "integrity": "sha512-vCrDBYjQCAEefWGjlK3EpoSKfKbT10pR4XXPdn65q7snuNOZnthoVpBfZPykmDapOKfoD+MMIPG8ZjKyyc9oHA==", + "dev": true, + "license": "ISC" }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -603,10 +904,11 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -616,6 +918,8 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -628,6 +932,7 @@ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "dev": true, + "license": "MIT", "engines": { "node": "*" }, @@ -636,92 +941,66 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" }, "node_modules/immutable": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.8.tgz", - "integrity": "sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==", - "dev": true + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", + "dev": true, + "license": "MIT" }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", + "optional": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -734,6 +1013,8 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", + "optional": true, "engines": { "node": ">=0.12.0" } @@ -741,13 +1022,17 @@ "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" }, "node_modules/jsxgraph": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/jsxgraph/-/jsxgraph-1.10.1.tgz", - "integrity": "sha512-N7WQmjeiiGKiJPr4iGUHgf8uRazOo9qaGLjX/tLWvrup67FUVD4eEctSLO1HuNcOSthwl16aTc692TKf/vZxNw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/jsxgraph/-/jsxgraph-1.12.2.tgz", + "integrity": "sha512-7kTscexFVBHirsBrxZFQg2hA6Mf/Pa2piojNgxHZ9i/rQfxDAaq7p8oD9/009clQTFQ9fNLUpmDKwV+84zA2Gg==", "license": "(MIT OR LGPL-3.0-or-later)", + "dependencies": { + "jsxgraph": "^1.11.0-beta2" + }, "engines": { "node": ">=0.6.0" } @@ -756,6 +1041,7 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", @@ -766,21 +1052,24 @@ "node_modules/jszip-utils": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/jszip-utils/-/jszip-utils-0.1.0.tgz", - "integrity": "sha512-tBNe0o3HAf8vo0BrOYnLPnXNo5A3KsRMnkBFYjh20Y3GPYGfgyoclEMgvVchx0nnL+mherPi74yLPIusHUQpZg==" + "integrity": "sha512-tBNe0o3HAf8vo0BrOYnLPnXNo5A3KsRMnkBFYjh20Y3GPYGfgyoclEMgvVchx0nnL+mherPi74yLPIusHUQpZg==", + "license": "(MIT OR GPL-3.0)" }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", "dependencies": { "immediate": "~3.0.5" } }, "node_modules/lilconfig": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", - "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true, + "license": "MIT", "engines": { "node": ">=14" }, @@ -792,24 +1081,42 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -825,26 +1132,27 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", "dev": true, - "engines": { - "node": ">=0.10.0" - } + "license": "MIT", + "optional": true + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" }, "node_modules/normalize-range": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -854,6 +1162,7 @@ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0" }, @@ -864,19 +1173,23 @@ "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, + "license": "MIT", + "optional": true, "engines": { "node": ">=8.6" }, @@ -885,14 +1198,15 @@ } }, "node_modules/plotly.js-dist-min": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-2.32.0.tgz", - "integrity": "sha512-UVznwUQVc7NeFih0tnIbvCpxct+Jxt6yxOGTYJF4vkKIUyujvyiTrH+XazglvcXdybFLERMu/IKt6Lhz3+BqMQ==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-3.1.0.tgz", + "integrity": "sha512-aihvA/+SnwEQxSufaPn8AWDUzdHFAbsCk2+w/IJResDafK3E2tvCvzW+ZV6JlMciJc7hQ3kCILS5Ao22OZ6kWA==", + "license": "MIT" }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -908,396 +1222,427 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/postcss-calc": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", - "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz", + "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==", "dev": true, + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.11", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12 || ^20.9 || >=22.0" }, "peerDependencies": { - "postcss": "^8.2.2" + "postcss": "^8.4.38" } }, "node_modules/postcss-colormin": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", - "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.4.tgz", + "integrity": "sha512-ziQuVzQZBROpKpfeDwmrG+Vvlr0YWmY/ZAk99XD+mGEBuEojoFekL41NCsdhyNUtZI7DPOoIWIR7vQQK9xwluw==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "caniuse-api": "^3.0.0", "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-convert-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", - "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.6.tgz", + "integrity": "sha512-MD/eb39Mr60hvgrqpXsgbiqluawYg/8K4nKsqRsuDX9f+xN1j6awZCUv/5tLH8ak3vYp/EMXwdcnXvfZYiejCQ==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-discard-comments": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", - "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.4.tgz", + "integrity": "sha512-6tCUoql/ipWwKtVP/xYiFf1U9QgJ0PUvxN7pTcsQ8Ns3Fnwq1pU5D5s1MhT/XySeLq6GXNvn37U46Ded0TckWg==", "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.1.0" + }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-discard-duplicates": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", - "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.2.tgz", + "integrity": "sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==", "dev": true, + "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-discard-empty": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", - "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.1.tgz", + "integrity": "sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==", "dev": true, + "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-discard-overridden": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", - "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.1.tgz", + "integrity": "sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==", "dev": true, + "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-merge-longhand": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", - "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.5.tgz", + "integrity": "sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.1.1" + "stylehacks": "^7.0.5" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-merge-rules": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", - "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.6.tgz", + "integrity": "sha512-2jIPT4Tzs8K87tvgCpSukRQ2jjd+hH6Bb8rEEOUDmmhOeTcqDg5fEFK8uKIu+Pvc3//sm3Uu6FRqfyv7YF7+BQ==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.2", - "postcss-selector-parser": "^6.0.16" + "cssnano-utils": "^5.0.1", + "postcss-selector-parser": "^7.1.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-minify-font-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", - "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.1.tgz", + "integrity": "sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-minify-gradients": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", - "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.1.tgz", + "integrity": "sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==", "dev": true, + "license": "MIT", "dependencies": { "colord": "^2.9.3", - "cssnano-utils": "^4.0.2", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-minify-params": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", - "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.4.tgz", + "integrity": "sha512-3OqqUddfH8c2e7M35W6zIwv7jssM/3miF9cbCSb1iJiWvtguQjlxZGIHK9JRmc8XAKmE2PFGtHSM7g/VcW97sw==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "cssnano-utils": "^4.0.2", + "browserslist": "^4.25.1", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-minify-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", - "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.5.tgz", + "integrity": "sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==", "dev": true, + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.16" + "cssesc": "^3.0.0", + "postcss-selector-parser": "^7.1.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-charset": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", - "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.1.tgz", + "integrity": "sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==", "dev": true, + "license": "MIT", "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-display-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", - "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.1.tgz", + "integrity": "sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-positions": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", - "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.1.tgz", + "integrity": "sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-repeat-style": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", - "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.1.tgz", + "integrity": "sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-string": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", - "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.1.tgz", + "integrity": "sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-timing-functions": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", - "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.1.tgz", + "integrity": "sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-unicode": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", - "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.4.tgz", + "integrity": "sha512-LvIURTi1sQoZqj8mEIE8R15yvM+OhbR1avynMtI9bUzj5gGKR/gfZFd8O7VMj0QgJaIFzxDwxGl/ASMYAkqO8g==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-url": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", - "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.1.tgz", + "integrity": "sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-normalize-whitespace": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", - "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.1.tgz", + "integrity": "sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-ordered-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", - "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.2.tgz", + "integrity": "sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==", "dev": true, + "license": "MIT", "dependencies": { - "cssnano-utils": "^4.0.2", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-reduce-initial": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", - "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.4.tgz", + "integrity": "sha512-rdIC9IlMBn7zJo6puim58Xd++0HdbvHeHaPgXsimMfG1ijC5A9ULvNLSE0rUKVJOvNMcwewW4Ga21ngyJjY/+Q==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "caniuse-api": "^3.0.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-reduce-transforms": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", - "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.1.tgz", + "integrity": "sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-selector-parser": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", - "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -1307,47 +1652,51 @@ } }, "node_modules/postcss-svgo": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", - "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.1.0.tgz", + "integrity": "sha512-KnAlfmhtoLz6IuU3Sij2ycusNs4jPW+QoFE5kuuUOK8awR6tMxZQrs5Ey3BUz7nFCzT3eqyFgqkyrHiaU2xx3w==", "dev": true, + "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", - "svgo": "^3.2.0" + "svgo": "^4.0.0" }, "engines": { - "node": "^14 || ^16 || >= 18" + "node": "^18.12.0 || ^20.9.0 || >= 18" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-unique-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", - "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.4.tgz", + "integrity": "sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==", "dev": true, + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.16" + "postcss-selector-parser": "^7.1.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -1361,12 +1710,14 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1378,31 +1729,25 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/rtlcss": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz", - "integrity": "sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", + "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", "dev": true, + "license": "MIT", "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0", @@ -1419,16 +1764,18 @@ "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" }, "node_modules/sass": { - "version": "1.75.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz", - "integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz", + "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", "dev": true, + "license": "MIT", "dependencies": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", + "chokidar": "^4.0.0", + "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { @@ -1436,6 +1783,9 @@ }, "engines": { "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" } }, "node_modules/sax": { @@ -1443,6 +1793,7 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz", "integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==", "dev": true, + "license": "BlueOak-1.0.0", "engines": { "node": ">=11.0.0" } @@ -1450,27 +1801,31 @@ "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" }, "node_modules/sortablejs": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.2.tgz", - "integrity": "sha512-FJF5jgdfvoKn1MAKSdGs33bIqLi3LmsgVTliuX6iITj834F+JRQZN90Z93yql8h0K2t0RwDPBmxwlbZfDcxNZA==" + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.6.tgz", + "integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==", + "license": "MIT" }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -1480,6 +1835,7 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -1489,34 +1845,43 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } }, "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, + "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/strip-json-comments": { @@ -1524,6 +1889,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -1532,40 +1898,42 @@ } }, "node_modules/stylehacks": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", - "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.6.tgz", + "integrity": "sha512-iitguKivmsueOmTO0wmxURXBP8uqOO+zikLGZ7Mm9e/94R4w5T999Js2taS/KBOnQ/wdC3jN3vNSrkGDrlnqQg==", "dev": true, + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "postcss-selector-parser": "^6.0.16" + "browserslist": "^4.25.1", + "postcss-selector-parser": "^7.1.0" }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": "^18.12.0 || ^20.9.0 || >=22.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "postcss": "^8.4.32" } }, "node_modules/svgo": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.3.tgz", - "integrity": "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz", + "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==", "dev": true, + "license": "MIT", "dependencies": { - "commander": "^7.2.0", + "commander": "^11.1.0", "css-select": "^5.1.0", - "css-tree": "^2.3.1", + "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", - "picocolors": "^1.0.0", + "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": { - "svgo": "bin/svgo" + "svgo": "bin/svgo.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=16" }, "funding": { "type": "opencollective", @@ -1573,13 +1941,14 @@ } }, "node_modules/terser": { - "version": "5.30.4", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.4.tgz", - "integrity": "sha512-xRdd0v64a8mFK9bnsKVdoNP9GQIKUAaJPTaqEQDL4w/J8WaW4sWXXoMZ+6SimPkfT5bElreXf8m9HnmPc3E1BQ==", + "version": "5.43.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.14.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -1594,13 +1963,16 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { "is-number": "^7.0.0" }, @@ -1609,9 +1981,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { @@ -1627,9 +1999,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -1644,17 +2017,18 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -1665,47 +2039,48 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", "dev": true, + "license": "MIT", "dependencies": { - "cliui": "^8.0.1", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", + "string-width": "^7.2.0", "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "yargs-parser": "^22.0.0" }, "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "dev": true, + "license": "ISC", "engines": { - "node": ">=12" + "node": "^20.19.0 || ^22.12.0 || >=23" } } }, "dependencies": { "@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", "dev": true, "requires": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, @@ -1715,16 +2090,10 @@ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true }, - "@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true - }, "@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.10.tgz", + "integrity": "sha512-0pPkgz9dY+bijgistcTTJ5mR+ocqRXLuhXHYdzoMmmoJ2C9S46RCm2GMUbatPEUK9Yjy26IrAy8D/M00lLkv+Q==", "dev": true, "requires": { "@jridgewell/gen-mapping": "^0.3.5", @@ -1732,15 +2101,15 @@ } }, "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1752,57 +2121,155 @@ "resolved": "https://registry.npmjs.org/@openwebwork/mathquill/-/mathquill-0.11.1.tgz", "integrity": "sha512-NLmu+AQ8i4bXX6Zuv5YMOkxhLsZisIW9pB0OvIIajVtAIsUu9ES7aG5Bhq823Ohc9xxA+2S7YrtmRaiDbp+GKA==" }, + "@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "optional": true, + "requires": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1", + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + } + }, + "@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "dev": true, + "optional": true + }, + "@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "dev": true, + "optional": true + }, + "@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "dev": true, + "optional": true + }, + "@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "dev": true, + "optional": true + }, + "@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "dev": true, + "optional": true + }, + "@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "dev": true, + "optional": true + }, + "@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "dev": true, + "optional": true + }, + "@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "dev": true, + "optional": true + }, "acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true }, "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true }, "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true }, "autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", "dev": true, "requires": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" } }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -1814,20 +2281,21 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "optional": true, "requires": { "fill-range": "^7.1.1" } }, "browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" } }, "buffer-from": { @@ -1849,53 +2317,31 @@ } }, "caniuse-lite": { - "version": "1.0.30001723", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz", - "integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==", + "version": "1.0.30001759", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz", + "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==", "dev": true }, "chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" } }, "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", "dev": true, "requires": { - "color-name": "~1.1.4" + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", @@ -1903,9 +2349,9 @@ "dev": true }, "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "dev": true }, "core-util-is": { @@ -1921,9 +2367,9 @@ "requires": {} }, "css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "dev": true, "requires": { "boolbase": "^1.0.0", @@ -1934,19 +2380,19 @@ } }, "css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", "dev": true, "requires": { - "mdn-data": "2.0.30", + "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "dev": true }, "cssesc": { @@ -1956,57 +2402,57 @@ "dev": true }, "cssnano": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", - "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.0.tgz", + "integrity": "sha512-Pu3rlKkd0ZtlCUzBrKL1Z4YmhKppjC1H9jo7u1o4qaKqyhvixFgu5qLyNIAOjSTg9DjVPtUqdROq2EfpVMEe+w==", "dev": true, "requires": { - "cssnano-preset-default": "^6.1.2", - "lilconfig": "^3.1.1" + "cssnano-preset-default": "^7.0.8", + "lilconfig": "^3.1.3" } }, "cssnano-preset-default": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", - "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.8.tgz", + "integrity": "sha512-d+3R2qwrUV3g4LEMOjnndognKirBZISylDZAF/TPeCWVjEwlXS2e4eN4ICkoobRe7pD3H6lltinKVyS1AJhdjQ==", "dev": true, "requires": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "css-declaration-sorter": "^7.2.0", - "cssnano-utils": "^4.0.2", - "postcss-calc": "^9.0.1", - "postcss-colormin": "^6.1.0", - "postcss-convert-values": "^6.1.0", - "postcss-discard-comments": "^6.0.2", - "postcss-discard-duplicates": "^6.0.3", - "postcss-discard-empty": "^6.0.3", - "postcss-discard-overridden": "^6.0.2", - "postcss-merge-longhand": "^6.0.5", - "postcss-merge-rules": "^6.1.1", - "postcss-minify-font-values": "^6.1.0", - "postcss-minify-gradients": "^6.0.3", - "postcss-minify-params": "^6.1.0", - "postcss-minify-selectors": "^6.0.4", - "postcss-normalize-charset": "^6.0.2", - "postcss-normalize-display-values": "^6.0.2", - "postcss-normalize-positions": "^6.0.2", - "postcss-normalize-repeat-style": "^6.0.2", - "postcss-normalize-string": "^6.0.2", - "postcss-normalize-timing-functions": "^6.0.2", - "postcss-normalize-unicode": "^6.1.0", - "postcss-normalize-url": "^6.0.2", - "postcss-normalize-whitespace": "^6.0.2", - "postcss-ordered-values": "^6.0.2", - "postcss-reduce-initial": "^6.1.0", - "postcss-reduce-transforms": "^6.0.2", - "postcss-svgo": "^6.0.3", - "postcss-unique-selectors": "^6.0.4" + "cssnano-utils": "^5.0.1", + "postcss-calc": "^10.1.1", + "postcss-colormin": "^7.0.4", + "postcss-convert-values": "^7.0.6", + "postcss-discard-comments": "^7.0.4", + "postcss-discard-duplicates": "^7.0.2", + "postcss-discard-empty": "^7.0.1", + "postcss-discard-overridden": "^7.0.1", + "postcss-merge-longhand": "^7.0.5", + "postcss-merge-rules": "^7.0.6", + "postcss-minify-font-values": "^7.0.1", + "postcss-minify-gradients": "^7.0.1", + "postcss-minify-params": "^7.0.4", + "postcss-minify-selectors": "^7.0.5", + "postcss-normalize-charset": "^7.0.1", + "postcss-normalize-display-values": "^7.0.1", + "postcss-normalize-positions": "^7.0.1", + "postcss-normalize-repeat-style": "^7.0.1", + "postcss-normalize-string": "^7.0.1", + "postcss-normalize-timing-functions": "^7.0.1", + "postcss-normalize-unicode": "^7.0.4", + "postcss-normalize-url": "^7.0.1", + "postcss-normalize-whitespace": "^7.0.1", + "postcss-ordered-values": "^7.0.2", + "postcss-reduce-initial": "^7.0.4", + "postcss-reduce-transforms": "^7.0.1", + "postcss-svgo": "^7.1.0", + "postcss-unique-selectors": "^7.0.4" } }, "cssnano-utils": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", - "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.1.tgz", + "integrity": "sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==", "dev": true, "requires": {} }, @@ -2037,6 +2483,13 @@ } } }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "optional": true + }, "dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -2064,9 +2517,9 @@ } }, "domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "dev": true, "requires": { "dom-serializer": "^2.0.0", @@ -2075,15 +2528,15 @@ } }, "electron-to-chromium": { - "version": "1.4.747", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.747.tgz", - "integrity": "sha512-+FnSWZIAvFHbsNVmUxhEqWiaOiPMcfum1GQzlWCg/wLigVtshOsjXHyEFfmt6cFK6+HkS3QOJBv6/3OPumbBfw==", + "version": "1.5.183", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.183.tgz", + "integrity": "sha512-vCrDBYjQCAEefWGjlK3EpoSKfKbT10pR4XXPdn65q7snuNOZnthoVpBfZPykmDapOKfoD+MMIPG8ZjKyyc9oHA==", "dev": true }, "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", "dev": true }, "entities": { @@ -2093,9 +2546,9 @@ "dev": true }, "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true }, "fill-range": { @@ -2103,6 +2556,7 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "optional": true, "requires": { "to-regex-range": "^5.0.1" } @@ -2113,27 +2567,17 @@ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "dev": true }, - "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "optional": true - }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } + "get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true }, "immediate": { "version": "3.0.6", @@ -2141,9 +2585,9 @@ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" }, "immutable": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.8.tgz", - "integrity": "sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", "dev": true }, "inherits": { @@ -2151,32 +2595,19 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "dev": true, + "optional": true }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "optional": true, "requires": { "is-extglob": "^2.1.1" } @@ -2185,7 +2616,8 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "dev": true, + "optional": true }, "isarray": { "version": "1.0.0", @@ -2193,9 +2625,12 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "jsxgraph": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/jsxgraph/-/jsxgraph-1.10.1.tgz", - "integrity": "sha512-N7WQmjeiiGKiJPr4iGUHgf8uRazOo9qaGLjX/tLWvrup67FUVD4eEctSLO1HuNcOSthwl16aTc692TKf/vZxNw==" + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/jsxgraph/-/jsxgraph-1.12.2.tgz", + "integrity": "sha512-7kTscexFVBHirsBrxZFQg2hA6Mf/Pa2piojNgxHZ9i/rQfxDAaq7p8oD9/009clQTFQ9fNLUpmDKwV+84zA2Gg==", + "requires": { + "jsxgraph": "^1.11.0-beta2" + } }, "jszip": { "version": "3.10.1", @@ -2222,9 +2657,9 @@ } }, "lilconfig": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", - "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true }, "lodash.memoize": { @@ -2240,27 +2675,39 @@ "dev": true }, "mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", "dev": true }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "optional": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, "nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true }, - "node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true + "node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "optional": true }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true }, "normalize-range": { @@ -2284,268 +2731,272 @@ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, "picomatch": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true + "dev": true, + "optional": true }, "plotly.js-dist-min": { - "version": "2.32.0", - "resolved": "https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-2.32.0.tgz", - "integrity": "sha512-UVznwUQVc7NeFih0tnIbvCpxct+Jxt6yxOGTYJF4vkKIUyujvyiTrH+XazglvcXdybFLERMu/IKt6Lhz3+BqMQ==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-3.1.0.tgz", + "integrity": "sha512-aihvA/+SnwEQxSufaPn8AWDUzdHFAbsCk2+w/IJResDafK3E2tvCvzW+ZV6JlMciJc7hQ3kCILS5Ao22OZ6kWA==" }, "postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "requires": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" } }, "postcss-calc": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", - "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz", + "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==", "dev": true, "requires": { - "postcss-selector-parser": "^6.0.11", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.2.0" } }, "postcss-colormin": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", - "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.4.tgz", + "integrity": "sha512-ziQuVzQZBROpKpfeDwmrG+Vvlr0YWmY/ZAk99XD+mGEBuEojoFekL41NCsdhyNUtZI7DPOoIWIR7vQQK9xwluw==", "dev": true, "requires": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "caniuse-api": "^3.0.0", "colord": "^2.9.3", "postcss-value-parser": "^4.2.0" } }, "postcss-convert-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", - "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.6.tgz", + "integrity": "sha512-MD/eb39Mr60hvgrqpXsgbiqluawYg/8K4nKsqRsuDX9f+xN1j6awZCUv/5tLH8ak3vYp/EMXwdcnXvfZYiejCQ==", "dev": true, "requires": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "postcss-value-parser": "^4.2.0" } }, "postcss-discard-comments": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", - "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.4.tgz", + "integrity": "sha512-6tCUoql/ipWwKtVP/xYiFf1U9QgJ0PUvxN7pTcsQ8Ns3Fnwq1pU5D5s1MhT/XySeLq6GXNvn37U46Ded0TckWg==", "dev": true, - "requires": {} + "requires": { + "postcss-selector-parser": "^7.1.0" + } }, "postcss-discard-duplicates": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", - "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.2.tgz", + "integrity": "sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==", "dev": true, "requires": {} }, "postcss-discard-empty": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", - "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.1.tgz", + "integrity": "sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==", "dev": true, "requires": {} }, "postcss-discard-overridden": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", - "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.1.tgz", + "integrity": "sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==", "dev": true, "requires": {} }, "postcss-merge-longhand": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", - "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.5.tgz", + "integrity": "sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.1.1" + "stylehacks": "^7.0.5" } }, "postcss-merge-rules": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", - "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.6.tgz", + "integrity": "sha512-2jIPT4Tzs8K87tvgCpSukRQ2jjd+hH6Bb8rEEOUDmmhOeTcqDg5fEFK8uKIu+Pvc3//sm3Uu6FRqfyv7YF7+BQ==", "dev": true, "requires": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.2", - "postcss-selector-parser": "^6.0.16" + "cssnano-utils": "^5.0.1", + "postcss-selector-parser": "^7.1.0" } }, "postcss-minify-font-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", - "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.1.tgz", + "integrity": "sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-minify-gradients": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", - "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.1.tgz", + "integrity": "sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==", "dev": true, "requires": { "colord": "^2.9.3", - "cssnano-utils": "^4.0.2", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" } }, "postcss-minify-params": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", - "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.4.tgz", + "integrity": "sha512-3OqqUddfH8c2e7M35W6zIwv7jssM/3miF9cbCSb1iJiWvtguQjlxZGIHK9JRmc8XAKmE2PFGtHSM7g/VcW97sw==", "dev": true, "requires": { - "browserslist": "^4.23.0", - "cssnano-utils": "^4.0.2", + "browserslist": "^4.25.1", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" } }, "postcss-minify-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", - "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.5.tgz", + "integrity": "sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==", "dev": true, "requires": { - "postcss-selector-parser": "^6.0.16" + "cssesc": "^3.0.0", + "postcss-selector-parser": "^7.1.0" } }, "postcss-normalize-charset": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", - "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.1.tgz", + "integrity": "sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==", "dev": true, "requires": {} }, "postcss-normalize-display-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", - "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.1.tgz", + "integrity": "sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-positions": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", - "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.1.tgz", + "integrity": "sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-repeat-style": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", - "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.1.tgz", + "integrity": "sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-string": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", - "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.1.tgz", + "integrity": "sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-timing-functions": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", - "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.1.tgz", + "integrity": "sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-unicode": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", - "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.4.tgz", + "integrity": "sha512-LvIURTi1sQoZqj8mEIE8R15yvM+OhbR1avynMtI9bUzj5gGKR/gfZFd8O7VMj0QgJaIFzxDwxGl/ASMYAkqO8g==", "dev": true, "requires": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-url": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", - "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.1.tgz", + "integrity": "sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-whitespace": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", - "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.1.tgz", + "integrity": "sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-ordered-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", - "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.2.tgz", + "integrity": "sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==", "dev": true, "requires": { - "cssnano-utils": "^4.0.2", + "cssnano-utils": "^5.0.1", "postcss-value-parser": "^4.2.0" } }, "postcss-reduce-initial": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", - "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.4.tgz", + "integrity": "sha512-rdIC9IlMBn7zJo6puim58Xd++0HdbvHeHaPgXsimMfG1ijC5A9ULvNLSE0rUKVJOvNMcwewW4Ga21ngyJjY/+Q==", "dev": true, "requires": { - "browserslist": "^4.23.0", + "browserslist": "^4.25.1", "caniuse-api": "^3.0.0" } }, "postcss-reduce-transforms": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", - "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.1.tgz", + "integrity": "sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-selector-parser": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", - "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "requires": { "cssesc": "^3.0.0", @@ -2553,22 +3004,22 @@ } }, "postcss-svgo": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", - "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.1.0.tgz", + "integrity": "sha512-KnAlfmhtoLz6IuU3Sij2ycusNs4jPW+QoFE5kuuUOK8awR6tMxZQrs5Ey3BUz7nFCzT3eqyFgqkyrHiaU2xx3w==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0", - "svgo": "^3.2.0" + "svgo": "^4.0.0" } }, "postcss-unique-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", - "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.4.tgz", + "integrity": "sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==", "dev": true, "requires": { - "postcss-selector-parser": "^6.0.16" + "postcss-selector-parser": "^7.1.0" } }, "postcss-value-parser": { @@ -2578,9 +3029,9 @@ "dev": true }, "prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true }, "process-nextick-args": { @@ -2603,24 +3054,15 @@ } }, "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true }, "rtlcss": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.1.1.tgz", - "integrity": "sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", + "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", "dev": true, "requires": { "escalade": "^3.1.1", @@ -2635,13 +3077,14 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "sass": { - "version": "1.75.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz", - "integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==", + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz", + "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", "dev": true, "requires": { - "chokidar": ">=3.0.0 <4.0.0", - "immutable": "^4.0.0", + "@parcel/watcher": "^2.4.1", + "chokidar": "^4.0.0", + "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" } }, @@ -2657,9 +3100,9 @@ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" }, "sortablejs": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.2.tgz", - "integrity": "sha512-FJF5jgdfvoKn1MAKSdGs33bIqLi3LmsgVTliuX6iITj834F+JRQZN90Z93yql8h0K2t0RwDPBmxwlbZfDcxNZA==" + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.6.tgz", + "integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==" }, "source-map": { "version": "0.6.1", @@ -2668,9 +3111,9 @@ "dev": true }, "source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true }, "source-map-support": { @@ -2692,23 +3135,23 @@ } }, "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" } }, "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "requires": { - "ansi-regex": "^5.0.1" + "ansi-regex": "^6.0.1" } }, "strip-json-comments": { @@ -2718,38 +3161,38 @@ "dev": true }, "stylehacks": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", - "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.6.tgz", + "integrity": "sha512-iitguKivmsueOmTO0wmxURXBP8uqOO+zikLGZ7Mm9e/94R4w5T999Js2taS/KBOnQ/wdC3jN3vNSrkGDrlnqQg==", "dev": true, "requires": { - "browserslist": "^4.23.0", - "postcss-selector-parser": "^6.0.16" + "browserslist": "^4.25.1", + "postcss-selector-parser": "^7.1.0" } }, "svgo": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.3.tgz", - "integrity": "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz", + "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==", "dev": true, "requires": { - "commander": "^7.2.0", + "commander": "^11.1.0", "css-select": "^5.1.0", - "css-tree": "^2.3.1", + "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", - "picocolors": "^1.0.0", + "picocolors": "^1.1.1", "sax": "^1.5.0" } }, "terser": { - "version": "5.30.4", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.4.tgz", - "integrity": "sha512-xRdd0v64a8mFK9bnsKVdoNP9GQIKUAaJPTaqEQDL4w/J8WaW4sWXXoMZ+6SimPkfT5bElreXf8m9HnmPc3E1BQ==", + "version": "5.43.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", "dev": true, "requires": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.14.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -2767,18 +3210,19 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "optional": true, "requires": { "is-number": "^7.0.0" } }, "update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" } }, "util-deprecate": { @@ -2787,14 +3231,14 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" } }, "y18n": { @@ -2804,24 +3248,23 @@ "dev": true }, "yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", "dev": true, "requires": { - "cliui": "^8.0.1", + "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", + "string-width": "^7.2.0", "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "yargs-parser": "^22.0.0" } }, "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "dev": true } } diff --git a/htdocs/package.json b/htdocs/package.json index abdea0fb07..6de2eed492 100644 --- a/htdocs/package.json +++ b/htdocs/package.json @@ -14,22 +14,22 @@ }, "dependencies": { "@openwebwork/mathquill": "^0.11.1", - "jsxgraph": "^1.10.1", + "jsxgraph": "^1.12.2", "jszip": "^3.10.1", "jszip-utils": "^0.1.0", - "plotly.js-dist-min": "^2.32.0", - "sortablejs": "^1.15.2" + "plotly.js-dist-min": "^3.1.0", + "sortablejs": "^1.15.6" }, "devDependencies": { - "autoprefixer": "^10.4.19", - "chokidar": "^3.6.0", - "cssnano": "^6.1.2", - "postcss": "^8.4.38", - "prettier": "^3.2.5", - "rtlcss": "^4.1.1", - "sass": "^1.75.0", - "terser": "^5.30.4", - "yargs": "^17.7.2" + "autoprefixer": "^10.4.21", + "chokidar": "^4.0.3", + "cssnano": "^7.1.0", + "postcss": "^8.5.6", + "prettier": "^3.6.2", + "rtlcss": "^4.3.0", + "sass": "^1.90.0", + "terser": "^5.43.1", + "yargs": "^18.0.0" }, "browserslist": [ "last 10 Chrome versions", diff --git a/lib/AnswerHash.pm b/lib/AnswerHash.pm old mode 100755 new mode 100644 diff --git a/lib/Applet.pm b/lib/Applet.pm index 78f7e9789b..12b42a4678 100644 --- a/lib/Applet.pm +++ b/lib/Applet.pm @@ -270,8 +270,8 @@ JavaScript "submitAction()" which then asks each of the applets on the page to p submit action which consists of -- If the applet is to be reinitialized (appletName_state contains - restart_applet) then the HTML elements appletName_state and - previous_appletName_state are set to restart_applet to be interpreted by the + restart_applet) then the HTML element appletName_state + is set to restart_applet to be interpreted by the next setState command. -- Otherwise getState() from the applet and save it to the HTML input element appletName_state. diff --git a/lib/LaTeXImage.pm b/lib/LaTeXImage.pm index 8c818cd3a3..b04fd368df 100644 --- a/lib/LaTeXImage.pm +++ b/lib/LaTeXImage.pm @@ -183,8 +183,17 @@ sub draw { system "cd $working_dir && " . WeBWorK::PG::IO::externalCommand('latex') . " --interaction=nonstopmode image-dvisvgm.tex > latex.stdout 2> /dev/null"; - move("$working_dir/image-dvisvgm.dvi", "$working_dir/image.dvi"); - chmod(oct(664), "$working_dir/image.dvi"); + + if (-r "$working_dir/image-dvisvgm.dvi") { + move("$working_dir/image-dvisvgm.dvi", "$working_dir/image.dvi"); + chmod(oct(664), "$working_dir/image.dvi"); + } else { + warn 'The dvi file was not created.'; + if (open(my $err_fh, '<', "$working_dir/latex.stdout")) { + while (my $error = <$err_fh>) { warn $error; } + close($err_fh); + } + } } else { warn "Can't open $working_dir/image-dvisvgm.tex for writing."; return ''; @@ -200,7 +209,16 @@ sub draw { system "cd $working_dir && " . WeBWorK::PG::IO::externalCommand('latex2pdf') . " --interaction=nonstopmode image.tex > latex.stdout 2> /dev/null"; - chmod(oct(664), "$working_dir/image.pdf"); + + if (-r "$working_dir/image.pdf") { + chmod(oct(664), "$working_dir/image.pdf"); + } else { + warn 'The pdf file was not created.'; + if (open(my $err_fh, '<', "$working_dir/latex.stdout")) { + while (my $error = <$err_fh>) { warn $error; } + close($err_fh); + } + } } else { warn "Can't open $working_dir/image.tex for writing."; return ''; @@ -208,39 +226,19 @@ sub draw { } # Make derivatives of the dvi - if (($ext eq 'svg' || $ext eq 'tgz') && $svgMethod eq 'dvisvgm') { - if (-r "$working_dir/image.dvi") { - $self->use_svgMethod($working_dir); - } else { - warn "The dvi file was not created."; - if (open(my $err_fh, "<", "$working_dir/latex.stdout")) { - while (my $error = <$err_fh>) { - warn $error; - } - close($err_fh); - } - } + if (($ext eq 'svg' || $ext eq 'tgz') && $svgMethod eq 'dvisvgm' && -r "$working_dir/image.dvi") { + $self->use_svgMethod($working_dir); } # Make derivatives of the pdf - if (($svgMethod ne 'dvisvgm' || $ext ne 'svg') && $ext ne 'pdf') { - if (-r "$working_dir/image.pdf") { - if (($ext eq 'svg' || $ext eq 'tgz') && $svgMethod ne 'dvisvgm') { - $self->use_svgMethod($working_dir); - } - if ($ext eq 'tgz') { - $self->use_convert($working_dir, "png"); - } elsif ($ext ne 'svg' && $ext ne 'pdf') { - $self->use_convert($working_dir, $ext); - } - } else { - warn "The pdf file was not created."; - if (open(my $err_fh, "<", "$working_dir/latex.stdout")) { - while (my $error = <$err_fh>) { - warn $error; - } - close($err_fh); - } + if (($svgMethod ne 'dvisvgm' || $ext ne 'svg') && $ext ne 'pdf' && -r "$working_dir/image.pdf") { + if (($ext eq 'svg' || $ext eq 'tgz') && $svgMethod ne 'dvisvgm') { + $self->use_svgMethod($working_dir); + } + if ($ext eq 'tgz') { + $self->use_convert($working_dir, 'png'); + } elsif ($ext ne 'svg' && $ext ne 'pdf') { + $self->use_convert($working_dir, $ext); } } diff --git a/lib/PGUtil.pm b/lib/PGUtil.pm index c6a7c08cff..9338c29002 100644 --- a/lib/PGUtil.pm +++ b/lib/PGUtil.pm @@ -89,7 +89,7 @@ sub pretty_print_html { # provides html output -- NOT a method if (!$ref) { return $r_input =~ s/' + return '
    ' . ($ref eq 'HASH' ? '' : '
    $answergroup_label, # enclosing answergroup that created this responsegroup response_order => [], # response labels responses => {}, # response label/response value pair, # value could be an arrayref in the case of radio or checkbox groups + WARNING_messages => $pg->{WARNING_messages} }, $class; $self->append_responses(@responses); return $self; @@ -45,12 +47,11 @@ sub append_response { ? [ map { [ $_ => $response_value->{$_} ] } keys %$response_value ] : $response_value; } else { - $self->internal_debug_message( - "PGresponsegroup::append_response error: there is already an answer labeled $response_label", - caller(2), "\n"); + $self->warning_message( + qq{PGresponsegroup::append_response error: There is already an answer labeled "$response_label".}); } } else { - $self->internal_debug_message('PGresponsegroup::append_response error: undefined or empty response label'); + $self->warning_message('PGresponsegroup::append_response error: Undefined or empty response label.'); } return; } @@ -82,13 +83,14 @@ sub replace_response { sub extend_response { my ($self, $response_label, $new_value_key, $selected) = @_; - if (defined $self->{responses}{$response_label}) { + if (defined $response_label && defined $self->{responses}{$response_label}) { my $response_value = $self->{responses}{$response_label}; $response_value //= []; if (ref($response_value) !~ /^(HASH|ARRAY)$/) { - $self->internal_debug_message("PGresponsegroup::extend_response: error in extending response ", - ref($response_value), $response_value); + $self->warning_message('PGresponsegroup::extend_response error: Invalid value type "' + . (ref($response_value) || 'scalar') + . qq{" for $response_label.}); $response_value = [ [ $response_value => $selected ] ]; } @@ -99,7 +101,12 @@ sub extend_response { $self->{responses}{$response_label} = $response_value; return $response_value; } else { - $self->internal_debug_message("PGresponsegroup::extend_response: response label |$response_label| not defined"); + if (defined $response_label) { + $self->warning_message( + qq{PGresponsegroup::extend_response error: Response label "$response_label" not defined.}); + } else { + $self->warning_message('PGresponsegroup::extend_response error: Response label not provided.'); + } return; } } diff --git a/lib/Parser.pm b/lib/Parser.pm index c943441e33..c72651be78 100644 --- a/lib/Parser.pm +++ b/lib/Parser.pm @@ -191,7 +191,7 @@ sub push { push(@{ (shift)->{stack} }, @_) } sub state { (shift)->top->{type} } # -# Report an error at a given possition (if possible) +# Report an error at a given position (if possible) # sub Error { my $self = shift; diff --git a/lib/Parser/Context/Default.pm b/lib/Parser/Context/Default.pm index c182fdcd9c..d66af413ba 100644 --- a/lib/Parser/Context/Default.pm +++ b/lib/Parser/Context/Default.pm @@ -435,6 +435,7 @@ $flags = { reduceConstantFunctions => 1, # 1 = compute function values of constants showExtraParens => 1, # 1 = add useful parens, 2 = make things painfully unambiguous stringifyNoBrackets => 0, # 1 = only use parentheses not brackets when stringifying + stringifyAbsAsFunction => 0, # 1 = abs(x) is stringified as abs(x) instead of |x| formatStudentAnswer => 'evaluated', # or 'parsed' or 'reduced' allowMissingOperands => 0, # 1 is used by Typeset context allowMissingFunctionInputs => 0, # 1 is used by Typeset context diff --git a/lib/Parser/Function/numeric.pm b/lib/Parser/Function/numeric.pm index d9540e4198..79edcc75a3 100644 --- a/lib/Parser/Function/numeric.pm +++ b/lib/Parser/Function/numeric.pm @@ -82,6 +82,8 @@ $Parser::reduce->{'ln(e^x)'} = 1; # sub string { my $self = shift; + return 'abs(' . $self->{params}[0]->string . ')' + if $self->{name} eq 'abs' && $self->context->flag('stringifyAbsAsFunction'); return '|' . $self->{params}[0]->string . '|' if $self->{name} eq 'abs'; return $self->SUPER::string(@_); } diff --git a/lib/Perl/Critic/Policy/PG/ProhibitBeginproblem.pm b/lib/Perl/Critic/Policy/PG/ProhibitBeginproblem.pm new file mode 100644 index 0000000000..ee2e300b96 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitBeginproblem.pm @@ -0,0 +1,36 @@ +package Perl::Critic::Policy::PG::ProhibitBeginproblem; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); + +use constant DESCRIPTION => 'The beingproblem function is called'; +use constant EXPLANATION => 'The beingproblem function no longer does anything and should be removed.'; +use constant SCORE => 5; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $) { + return unless $element eq 'beginproblem' && is_function_call($element); + return $self->violation(DESCRIPTION, { score => SCORE, explanation => EXPLANATION }, $element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitBeginproblem - The C function is +deprecated, no longer does anything, and should be removed from all problems. + +=head1 DESCRIPTION + +The C function is deprecated, no longer does anything, and should +be removed from all problems. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitContextStrings.pm b/lib/Perl/Critic/Policy/PG/ProhibitContextStrings.pm new file mode 100644 index 0000000000..309ee1a832 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitContextStrings.pm @@ -0,0 +1,40 @@ +package Perl::Critic::Policy::PG::ProhibitContextStrings; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); + +use constant DESCRIPTION => 'Context()->%s is called'; +use constant EXPLANATION => 'Context()->%s no longer necessary and should be removed.'; +use constant SCORE => 5; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $) { + return + unless ($element eq 'texStrings' || $element eq 'normalStrings') + && is_method_call($element) + && $element->parent =~ /^Context/; + return $self->violation(sprintf(DESCRIPTION, $element), + { score => SCORE, explanation => sprintf(EXPLANATION, $element) }, $element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::RequireContextStrings - C<< Context()->texStrings >> +and C<< Context->normalStrings >> calls are not necessary and should be removed. + +=head1 DESCRIPTION + +Calling C<< Context()->texStrings >> and C<< Context->normalStrings >> is no +longer necessary and should be removed from problems. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedCmp.pm b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedCmp.pm new file mode 100644 index 0000000000..4af90b468b --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedCmp.pm @@ -0,0 +1,85 @@ +package Perl::Critic::Policy::PG::ProhibitDeprecatedCmp; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'The deprecated %s method is called'; +use constant EXPLANATION => 'Convert the answer into a MathObject and use the cmp method of the object.'; +use constant SCORE => 55; + +use constant CMP_METHODS => { + str_cmp => 1, + std_str_cmp => 1, + std_str_cmp_list => 1, + std_cs_str_cmp => 1, + std_cs_str_cmp_list => 1, + strict_str_cmp => 1, + strict_str_cmp_list => 1, + unordered_str_cmp => 1, + unordered_str_cmp_list => 1, + unordered_cs_str_cmp => 1, + unordered_cs_str_cmp_list => 1, + ordered_str_cmp => 1, + ordered_str_cmp_list => 1, + ordered_cs_str_cmp => 1, + ordered_cs_str_cmp_list => 1, + num_cmp => 1, + num_rel_cmp => 1, + std_num_cmp => 1, + std_num_cmp_list => 1, + std_num_cmp_abs => 1, + std_num_cmp_abs_list => 1, + frac_num_cmp => 1, + frac_num_cmp_list => 1, + frac_num_cmp_abs => 1, + frac_num_cmp_abs_list => 1, + arith_num_cmp => 1, + arith_num_cmp_list => 1, + arith_num_cmp_abs => 1, + arith_num_cmp_abs_list => 1, + strict_num_cmp => 1, + strict_num_cmp_list => 1, + strict_num_cmp_abs => 1, + strict_num_cmp_abs_list => 1, + std_num_str_cmp => 1, + fun_cmp => 1, + function_cmp => 1, + function_cmp_up_to_constant => 1, + function_cmp_abs => 1, + function_cmp_up_to_constant_abs => 1, + adaptive_function_cmp => 1, + multivar_function_cmp => 1, + cplx_cmp => 1, + multi_cmp => 1, + radio_cmp => 1, + checkbox_cmp => 1, +}; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $document) { + return unless CMP_METHODS->{$element} && is_function_call($element); + return $self->violation(sprintf(DESCRIPTION, $element), { score => SCORE, explanation => EXPLANATION }, $element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitDeprecatedCmp - Use C instead of +the deprecated C methods. + +=head1 DESCRIPTION + +Convert answers into a C and use the C method of the object +instead of using any of the deprecated C methods such as C from +the L macro, C from the +L macro, or C from the +L macro. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMacros.pm b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMacros.pm new file mode 100644 index 0000000000..27bbd26512 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMacros.pm @@ -0,0 +1,45 @@ +package Perl::Critic::Policy::PG::ProhibitDeprecatedMacros; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); + +use constant DESCRIPTION => 'The deprecated macro %s is loaded'; +use constant EXPLANATION => 'Remove this macro and replace methods used from this macro with modern alternatives.'; +use constant SCORE => 10; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $) { + return unless $element eq 'loadMacros' && is_function_call($element); + my $deprecatedMacros = getDeprecatedMacros; + return unless $deprecatedMacros; + return map { + $self->violation( + sprintf(DESCRIPTION, $_->[0]->string), + { score => SCORE, explanation => EXPLANATION }, + $_->[0] + ) + } + grep { $deprecatedMacros->{ $_->[0]->string } } parse_arg_list($element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitDeprecatedMacros - Replace deprecated macro +usage with modern alternatives. + +=head1 DESCRIPTION + +All problems that use a deprecated macro (those in the C directory) +should be rewritten to use modern alternatives. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMultipleChoice.pm b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMultipleChoice.pm new file mode 100644 index 0000000000..bc38344461 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedMultipleChoice.pm @@ -0,0 +1,55 @@ +package Perl::Critic::Policy::PG::ProhibitDeprecatedMultipleChoice; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); + +use constant DESCRIPTION => 'The deprecated %s function is called'; +use constant EXPLANATION => 'The deprecated %s function should be replaced with a modern alternative.'; +use constant SCORE => 20; +use constant SAMPLE_PROBLEMS => [ + [ 'Multiple Choice with Checkbox' => 'Misc/MultipleChoiceCheckbox' ], + [ 'Multiple Choice with Popup' => 'Misc/MultipleChoicePopup' ], + [ 'Multiple Choice with Radio Buttons' => 'Misc/MultipleChoiceRadio' ] +]; + +# Note that new_match_list is not in this list because there is not a modern alternative yet. +# The qa method is also not listed because it is needed with new_match_list. +use constant MULTIPLE_CHOICE_METHODS => { + new_checkbox_multiple_choice => 1, + new_multiple_choice => 1, + new_pop_up_select_list => 1, + new_select_list => 1 +}; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $) { + return unless MULTIPLE_CHOICE_METHODS->{$element} && is_function_call($element); + return $self->violation(sprintf(DESCRIPTION, $element), + { score => SCORE, explanation => sprintf(EXPLANATION, $element), sampleProblems => SAMPLE_PROBLEMS }, + $element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitDeprecatedMultipleChoice - Replace usage of +L multiple choice methods with the appropriate MathObject +multiple choice +macro. + +=head1 DESCRIPTION + +Replace usage of L multiple choice methods with the +appropriate modern multiple choice macro. For example, consider using +L, L, or L. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedWeightedGrader.pm b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedWeightedGrader.pm new file mode 100644 index 0000000000..77a0755ef2 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitDeprecatedWeightedGrader.pm @@ -0,0 +1,55 @@ +package Perl::Critic::Policy::PG::ProhibitDeprecatedWeightedGrader; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'The deprecated %s function is called'; +use constant EXPLANATION => 'The deprecated %s function should be replaced with a modern alternative.'; +use constant SCORE => 5; +use constant SAMPLE_PROBLEMS => [ [ 'Weighted Grader' => 'ProblemTechniques/WeightedGrader' ] ]; +use constant WEIGHTED_GRADER_METHODS => { + install_weighted_grader => 1, + WEIGHTED_ANS => 1, + NAMED_WEIGHTED_ANS => 1, + weight_ans => 1, + CREDIT_ANS => 1, +}; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $) { + return unless WEIGHTED_GRADER_METHODS->{$element} && is_function_call($element); + return $self->violation(sprintf(DESCRIPTION, $element), + { score => SCORE, explanation => sprintf(EXPLANATION, $element), sampleProblems => SAMPLE_PROBLEMS }, + $element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitDeprecatedWeightedGrader - The +L functionality is now included in the default + L, and +this macros is no longer needed. + +=head1 DESCRIPTION + +The default L +includes all the functionality of the L, and use of +this macro should be removed. Remove calling the function +C. Instead of calling C or +C, pass C<< weight => n >> to the C method. +In PGML use the following: + + [_]{$answer}{ cmp_options => { weight => n } } + +Instead of calling C pass C<< credit => $answer1 >> or +C<< credit => [$answer1, $answer2, ...] >> to the C method. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitEnddocumentMatter.pm b/lib/Perl/Critic/Policy/PG/ProhibitEnddocumentMatter.pm new file mode 100644 index 0000000000..0267989b31 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitEnddocumentMatter.pm @@ -0,0 +1,41 @@ +package Perl::Critic::Policy::PG::ProhibitEnddocumentMatter; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'There is content after the ENDDOCUMENT call'; +use constant EXPLANATION => 'Remove this content. The ENDDOCUMENT call should be at the end of the problem.'; +use constant SCORE => 5; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub default_maximum_violations_per_document ($) { return 1; } + +sub violates ($self, $element, $document) { + return $self->violation(DESCRIPTION, { score => SCORE, explanation => EXPLANATION }, $element) + if $element eq 'ENDDOCUMENT' + && is_function_call($element) + && ($document->{_doc}{untranslatedCode} // '') =~ /ENDDOCUMENT[^\n]*\n(.*)/s + && $1 =~ /\S/; + return; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitEnddocumentMatter - There should not be any +content after the C call in a problem. + +=head1 DESCRIPTION + +The C call is intended to signify the end of the problem code. +Although all content after the C call is ignored, there should not +be any content (text or code) in this area. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitGraphMacros.pm b/lib/Perl/Critic/Policy/PG/ProhibitGraphMacros.pm new file mode 100644 index 0000000000..437951b0d0 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitGraphMacros.pm @@ -0,0 +1,42 @@ +package Perl::Critic::Policy::PG::ProhibitGraphMacros; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); + +use constant DESCRIPTION => 'The init_graph function from PGgraphmacros.pl is called'; +use constant EXPLANATION => 'PGgraphmacros.pl generates poor quality graphics. Consider using a modern alternative.'; +use constant SCORE => 20; +use constant SAMPLE_PROBLEMS => [ + [ 'TikZ Graph Images' => 'ProblemTechniques/TikZImages' ], + [ 'Inserting Images in PGML' => 'ProblemTechniques/Images' ], + [ 'Function Plot' => 'Algebra/FunctionPlot' ] +]; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Word) } + +sub violates ($self, $element, $) { + return unless $element eq 'init_graph' && is_function_call($element); + return $self->violation(DESCRIPTION, + { score => SCORE, explanation => EXPLANATION, sampleProblems => SAMPLE_PROBLEMS }, $element); +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitGraphMacros - L generates +poor quality graphics. Modern alternatives should be used instead. + +=head1 DESCRIPTION + +L generates poor quality graphics. Replace its usage with a +modern alternative such as L, L, or L. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitMultipleLoadMacrosCalls.pm b/lib/Perl/Critic/Policy/PG/ProhibitMultipleLoadMacrosCalls.pm new file mode 100644 index 0000000000..6dd18ca2e2 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitMultipleLoadMacrosCalls.pm @@ -0,0 +1,38 @@ +package Perl::Critic::Policy::PG::ProhibitMultipleLoadMacrosCalls; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'loadMacros is called multiple times'; +use constant EXPLANATION => 'Consolidate multiple loadMacros calls into a single call.'; +use constant SCORE => 20; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Document) } + +sub violates ($self, $element, $document) { + my $tokens = $document->find('PPI::Token'); + return unless $tokens; + my @loadMacrosCalls = grep { $_ eq 'loadMacros' && is_function_call($_) } @$tokens; + shift @loadMacrosCalls; + return map { $self->violation(DESCRIPTION, { score => SCORE, explanation => EXPLANATION }, $_) } @loadMacrosCalls; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitMultipleLoadMacrosCalls - The C +function should only be called once in each problem. + +=head1 DESCRIPTION + +The C function should only be called once in each problem. +Consolidate multiple C calls into a single call and make sure that +all macros that are loaded are actually used in the problem. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitOldText.pm b/lib/Perl/Critic/Policy/PG/ProhibitOldText.pm new file mode 100644 index 0000000000..b46ccbe8e1 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitOldText.pm @@ -0,0 +1,53 @@ +package Perl::Critic::Policy::PG::ProhibitOldText; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'A BEGIN_%1$s/END_%1$s block is used for problem text'; +use constant EXPLANATION => 'Load the macro PGML.pl and replace the BEGIN_%1$s/END_%1$s ' + . 'block with a BEGIN_PGML%2$s/END_PGML%2$s block.'; +use constant SCORE => 20; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::HereDoc) } + +sub violates ($self, $element, $document) { + if ($element->terminator =~ /^END_(TEXT|SOLUTION|HINT)?$/ + && $element->parent + && $element->parent->parent + && $element->parent->parent->parent + && $element->parent->parent->parent->first_element eq 'EV3P' + && is_function_call($element->parent->parent->parent->first_element) + && $element->parent->parent->parent->parent + && $element->parent->parent->parent->parent->parent + && $element->parent->parent->parent->parent->parent->first_element =~ /^(STATEMENT|HINT|SOLUTION)$/ + && is_function_call($element->parent->parent->parent->parent->parent->first_element)) + { + my $oldType = $1 eq 'STATEMENT' ? 'TEXT' : $1; + return $self->violation( + sprintf(DESCRIPTION, $oldType), + { score => SCORE, explanation => sprintf(EXPLANATION, $oldType, $1 eq 'STATEMENT' ? '' : "_$1") }, + $element->parent->parent->parent->parent->parent + ); + } + return; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitOldText - Replace old PG text usage with PGML. + +=head1 DESCRIPTION + +Load the C macro and replace all C/C, +C/C, and C/C blocks with +C/C, C/C, and +C/C blocks. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/ProhibitUnnecessarilySettingShowPartialCorrectAnswers.pm b/lib/Perl/Critic/Policy/PG/ProhibitUnnecessarilySettingShowPartialCorrectAnswers.pm new file mode 100644 index 0000000000..2faac1d9ff --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/ProhibitUnnecessarilySettingShowPartialCorrectAnswers.pm @@ -0,0 +1,42 @@ +package Perl::Critic::Policy::PG::ProhibitUnnecessarilySettingShowPartialCorrectAnswers; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(getDeprecatedMacros); + +use constant DESCRIPTION => '$showPartialCorrectAnswers is set to 1'; +use constant EXPLANATION => 'The value of $showPartialCorrectAnswers is 1 by default, ' + . 'so it should only ever be set to 0 to change the value.'; +use constant SCORE => 5; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::Operator) } + +sub violates ($self, $element, $) { + return unless is_assignment_operator($element); + my $left = $element->sprevious_sibling; + my $right = $element->snext_sibling; + return $self->violation(DESCRIPTION, { score => SCORE, explanation => EXPLANATION }, $left) + if $left && $left eq '$showPartialCorrectAnswers' && $element eq '=' && $right && $right eq '1'; + return; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::ProhibitUnnecessarilySettingShowPartialCorrectAnswers +- There is no need to set C<$showPartialCorrectAnswers> to 1 since that is the +default value. + +=head1 DESCRIPTION + +The value of C<$showPartialCorrectAnswers> is 1 by default, so it should only +ever be set to 0 to change the value. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/RequireImageAltAttribute.pm b/lib/Perl/Critic/Policy/PG/RequireImageAltAttribute.pm new file mode 100644 index 0000000000..7fbc026577 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/RequireImageAltAttribute.pm @@ -0,0 +1,96 @@ +package Perl::Critic::Policy::PG::RequireImageAltAttribute; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use WeBWorK::PG::Critic::Utils qw(parsePGMLBlock parseTextBlock); + +use constant DESCRIPTION => 'An image is missing the alt attribute'; +use constant EXPLANATION => 'Add an alt attribute that describes the image content.'; +use constant SCORE => 10; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Token::HereDoc PPI::Token::Word) } + +sub hasAltTag ($self, $element) { + my @args = + map { $_->[0]->isa('PPI::Token::Quote') ? $_->[0]->string : $_->[0]->content } parse_arg_list($element); + shift @args; # Remove the image argument. + my %args = @args % 2 ? () : @args; + return $args{alt} || ($args{extra_html_tags} && $args{extra_html_tags} =~ /alt/); +} +use Mojo::Util qw(dumper); + +sub violates ($self, $element, $) { + my @violations; + if ($element->isa('PPI::Token::Word') && $element eq 'image' && is_function_call($element)) { + push(@violations, $self->violation(DESCRIPTION, { score => SCORE, explanation => EXPLANATION }, $element)) + unless $self->hasAltTag($element); + } elsif ( + $element->isa('PPI::Token::HereDoc') + && $element->terminator =~ /^END_(PGML|PGML_SOLUTION|PGML_HINT|TEXT|HINT|SOLUTION)?$/ + && $element->parent + && $element->parent->parent + && $element->parent->parent->parent + && ($element->parent->parent->parent->first_element eq 'PGML::Format2' + || $element->parent->parent->parent->first_element eq 'EV3P') + && is_function_call($element->parent->parent->parent->first_element) + && $element->parent->parent->parent->parent + && $element->parent->parent->parent->parent->parent + && $element->parent->parent->parent->parent->parent->first_element =~ /^(STATEMENT|HINT|SOLUTION)$/ + && is_function_call($element->parent->parent->parent->parent->parent->first_element) + ) + { + for my $command ( + @{ + ( + $element->terminator =~ /PGML/ + ? parsePGMLBlock($element->heredoc)->{commands} + : parseTextBlock($element->heredoc)->{commands} + ) // [] + } + ) + { + for (grep { $_ eq 'image' && is_function_call($_) } @{ $command->find('PPI::Token::Word') || [] }) { + next if $self->hasAltTag($_); + push( + @violations, + $self->violation( + DESCRIPTION + . ' inside the ' + . ($element->terminator =~ s/END/BEGIN/r) . '/' + . ($element->terminator) + . ' block', + { score => SCORE, explanation => EXPLANATION }, + $element->parent->parent->parent->parent->parent + ) + ); + } + } + } + + return @violations; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::RequireImageAltAttribute - Images created with the +C method should have the C attribute set. + +=head1 DESCRIPTION + +The C attribute is crucial for accessibility, especially for visually +impaired users who rely on screen readers to understand the content of the +problem. So all images added to a problem should have the C attribute set. +Note that it can be set to the empty string to indicate that the image is not +essential to the meaning of the problem content. Generally it is better to use +the PGML syntax for images C<[!alternate text!]{$image}> rather than using the +C method. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/RequireMetadata.pm b/lib/Perl/Critic/Policy/PG/RequireMetadata.pm new file mode 100644 index 0000000000..ab081f9dfa --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/RequireMetadata.pm @@ -0,0 +1,51 @@ +package Perl::Critic::Policy::PG::RequireMetadata; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'The %s metadata tag is required'; +use constant EXPLANATION => 'Include the required metadata tags at the beginning of the problem file.'; +use constant SCORE => 5; +use constant REQUIRED_METADATA => [ 'DBsubject', 'DBchapter', 'DBsection', 'KEYWORDS' ]; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Document) } + +sub violates ($self, $element, $document) { + my $comments = $document->find('PPI::Token::Comment'); + my %foundMetadata; + if ($comments) { + for my $comment (@$comments) { + my ($metadataType) = grep { $comment =~ /#\s*$_\(/i } @{ REQUIRED_METADATA() }; + $foundMetadata{$metadataType} = 1 if $metadataType; + } + } + + my @violations; + for (@{ REQUIRED_METADATA() }) { + push(@violations, + $self->violation(sprintf(DESCRIPTION, $_), { score => SCORE, explanation => EXPLANATION }, $document)) + unless $foundMetadata{$_}; + } + return @violations; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::RequireMetadata - All problems should have the +appropriate OPL metadata tags set. + +=head1 DESCRIPTION + +All problems should have the appropriate OPL metadata tags set. The required +metadata attributes should be set at the beginning of the problem file before +the C call. The metadata tags that are required for all problems are +C, C, C, and C. + +=cut diff --git a/lib/Perl/Critic/Policy/PG/RequireSolution.pm b/lib/Perl/Critic/Policy/PG/RequireSolution.pm new file mode 100644 index 0000000000..eb14e1a647 --- /dev/null +++ b/lib/Perl/Critic/Policy/PG/RequireSolution.pm @@ -0,0 +1,61 @@ +package Perl::Critic::Policy::PG::RequireSolution; +use Mojo::Base 'Perl::Critic::Policy', -signatures; + +use Perl::Critic::Utils qw(:severities :classification :ppi); + +use constant DESCRIPTION => 'A solution is not included in this problem'; +use constant EXPLANATION => 'A solution should be included in all problems.'; +use constant SCORE => 25; + +sub supported_parameters ($) {return} +sub default_severity ($) { return $SEVERITY_HIGHEST } +sub default_themes ($) { return qw(pg) } +sub applies_to ($) { return qw(PPI::Document) } + +sub default_maximum_violations_per_document ($) { return 1; } + +sub violates ($self, $element, $document) { + my $solutionFound = 0; + if (my $heredocs = $document->find('PPI::Token::HereDoc')) { + for (@$heredocs) { + if ( + $_->terminator =~ /^END_(PGML_)?SOLUTION$/ + && $_->parent + && $_->parent->parent + && $_->parent->parent->parent + && ($_->parent->parent->parent->first_element eq 'PGML::Format2' + || $_->parent->parent->parent->first_element eq 'EV3P') + && is_function_call($_->parent->parent->parent->first_element) + && $_->parent->parent->parent->parent + && $_->parent->parent->parent->parent->parent + && $_->parent->parent->parent->parent->parent->first_element eq 'SOLUTION' + && is_function_call($_->parent->parent->parent->first_element) + ) + { + $solutionFound = 1; + last; + } + } + } + return $self->violation(DESCRIPTION, { score => SCORE, explanation => EXPLANATION }, $document) + unless $solutionFound; + return; +} + +1; + +__END__ + +=head1 NAME + +Perl::Critic::Policy::PG::RequireSolution - All problems should provide a +solution. + +=head1 DESCRIPTION + +A solution should be included in all problems. Note that a solution should +demonstrate all steps to solve a problem, and should certainly not just give the +answers to questions in the problem. This is one of the most challenging parts +of PG problem authoring, but the solution should not be omitted. + +=cut diff --git a/lib/Plots/Axes.pm b/lib/Plots/Axes.pm index f1da7cb454..5141a255ee 100644 --- a/lib/Plots/Axes.pm +++ b/lib/Plots/Axes.pm @@ -87,19 +87,52 @@ The maximum value the axis shows. Default is 5. This is the number of major tick marks to include on the axis. This number is used to compute the C as the difference between the C and C values -and the number of ticks. Default: 5. +and the number of ticks. Note that this is only used if C is zero +and C is undefined. Default: 5. =item tick_delta -This is the distance between each major tick mark, starting from the origin. -If this is set to 0, this distance is set by using the number of ticks, C. -Default is 0. +This is the distance between each major tick mark, starting from the origin. If +this is set to 0 and C is not 0, then this distance is computed +to be the product of the C and the C, and if this is +set to 0 and C is undefined then this is computed to be the +difference between the C and C divided by the C. Default: 0 =item tick_labels This can be either 1 (show) or 0 (don't show) the labels for the major ticks. Default: 1 +=item tick_label_format + +This can be one of "decimal", "fraction", "multiple", or "scinot". If this is +"decimal", then tick labels will be displayed in decimal format. If this is +"fraction", then tick labels will be displayed as (improper) fractions. If this +is "mixed", then tick labels will be displayed as mixed numbers. If this is +"scinot", then tick labels will be displayed in scientific notation. Default: +"decimal" + +=item tick_label_digits + +The number of decimal places to round tick labels to when the +C is "decimal" or "scinot". Default: 2 + +=item tick_distance + +This is the unscaled distance between each major tick mark starting from the +origin when the axis is scaled by the C factor. If this is 0, then +this will be computed to be the C divided by the C. +Default: 0 + +=item tick_scale + +This is used in combination with the C above to calculate the +C. Default: 1 + +=item tick_scale_symbol + +This is appended to major tick labels. Default: '' + =item show_ticks This can be either 1 (show) or 0 (don't show) the tick lines. If ticks are @@ -115,8 +148,13 @@ Show (1) or don't show (0) grid lines at the tick marks. Default is 1. =item minor -This sets the number of minor grid lines per major grid line. If this is -set to 0, no minor grid lines are shown. Default is 3. +This sets the number of minor ticks (and minor grid lines if minor_grids is 1) +per major tick. If this is set to 0, no minor ticks are shown. Default: 3 + +=item minor_grids + +If this is 1, then grid lines are shown at minor ticks, and if this is 0, then +grid lines are not shown at minor ticks. Default: 1 =item visible @@ -139,9 +177,23 @@ Default 'middle' or 'center'. The position in terms of the appropriate variable to draw the axis if the location is set to 'middle' or 'center'. Default is 0. +=item arrows_both + +Configures if arrows should be drawn in both directions (1) or only in the +positive direction (0) at the axis ends. In other words, this is a choice +between the convention that arrows are meant to indicate that the axis lines +continue forever, or the convention that arrows are meant to indicate the +positive direction of the axis only. Default: 0 + =item jsx_options -A hash reference of options to be passed to the JSXGraph axis objects. +A hash reference of options to be passed to the JSXGraph axis object. + +=item jsx_grid_options + +A hash reference of options to be passed to the JSXGraph grid object. Note that +the grid is implemented as an axis with ticks the extend to infinity. So the +options are really JSXGraph axis options. =back @@ -185,6 +237,21 @@ Configures if the Tikz axis should be drawn on top of the graph (1) or below the Useful when filling a region that covers an axis, if the axis are on top they will still be visible after the fill, otherwise the fill will cover the axis. Default: 0 +Note that this setting is not honored for the JSXGraph image type. + +This is not the best way of ensuring that axis elements are not covered by a +fill. If this is used, then not only is the fill region placed behind the axis +and the grid, but all graphed elements are behind the axis and the grid which is +usually not desirable. A better way is to use the "axis background" C to +only place the fill on the "axis background" layer, and leave everything else on +top of the axis. + +=item mathjax_tick_labels + +If this is 1, then tick labels will be displayed using MathJax. If this is 0, +then ticks will be displayed as basic text. This only applies to the JSXGraph +output type. Default: 1 + =item jsx_navigation Either allow (1) or don't allow (0) the user to pan and zoom the view port of the @@ -196,6 +263,13 @@ of the graph that can be zoomed in or out. Default: 0 A hash reference of options to be passed to the JSXGraph board object. +=item tikz_options + +Additional options to be passed to the pgfplots axis definition. This should be +a single string. For example, to make longer and thicker x axis ticks use + + tikz_options => 'x tick style={line width=2pt},major tick length=0.6cm' + =back =cut @@ -206,18 +280,20 @@ use strict; use warnings; sub new { - my $class = shift; - my $self = bless { + my ($class, @options) = @_; + my $self = bless { xaxis => {}, yaxis => {}, styles => { - aria_label => 'Graph', - aria_description => 'Generated graph', - grid_color => 'gray', - grid_alpha => 40, - show_grid => 1, + aria_label => 'Graph', + aria_description => 'Generated graph', + grid_color => 'gray', + grid_alpha => 40, + show_grid => 1, + axis_on_top => 0, + mathjax_tick_labels => 1, }, - @_ + @options }, $class; $self->xaxis($self->axis_defaults('x')); @@ -228,18 +304,25 @@ sub new { sub axis_defaults { my ($self, $axis) = @_; return ( - visible => 1, - min => -5, - max => 5, - label => $axis eq 'y' ? '\(y\)' : '\(x\)', - location => $axis eq 'y' ? 'center' : 'middle', - position => 0, - tick_labels => 1, - show_ticks => 1, - tick_delta => 0, - tick_num => 5, - major => 1, - minor => 3, + visible => 1, + min => -5, + max => 5, + label => $axis eq 'y' ? '\(y\)' : '\(x\)', + location => $axis eq 'y' ? 'center' : 'middle', + position => 0, + tick_labels => 1, + tick_label_format => 'decimal', + tick_label_digits => 2, + tick_distance => 0, + tick_scale => 1, + tick_scale_symbol => '', + show_ticks => 1, + tick_delta => 0, + tick_num => 5, + major => 1, + minor => 3, + minor_grids => 1, + arrows_both => 0, ); } @@ -256,8 +339,11 @@ sub axis { map { $self->{$axis}{$_} = $item->{$_}; } (keys %$item); return; } - # Deal with ticks individually since they may need to be generated. - return $item eq 'tick_delta' ? $self->tick_delta($self->{$axis}) : $self->{$axis}{$item}; + # Deal with the tick_delta and tick_distance individually since they may need to be computed. + return + $item eq 'tick_delta' ? $self->tick_delta($self->{$axis}) + : $item eq 'tick_distance' ? $self->tick_distance($self->{$axis}) + : $self->{$axis}{$item}; } sub xaxis { @@ -322,14 +408,28 @@ sub style { sub tick_delta { my ($self, $axis) = @_; return $axis->{tick_delta} if $axis->{tick_delta}; - return 2 unless $axis->{tick_num}; - $axis->{tick_delta} = ($axis->{max} - $axis->{min}) / $axis->{tick_num} if $axis->{tick_num}; + if ($axis->{tick_distance}) { + $axis->{tick_delta} = $axis->{tick_distance} * ($axis->{tick_scale} || 1); + } elsif ($axis->{tick_num}) { + $axis->{tick_delta} = ($axis->{max} - $axis->{min}) / $axis->{tick_num}; + } else { + $axis->{tick_delta} = 2; + } return $axis->{tick_delta}; } +sub tick_distance { + my ($self, $axis) = @_; + return $axis->{tick_distance} if $axis->{tick_distance}; + my $tick_delta = $self->tick_delta($axis); + $axis->{tick_distance} = $axis->{tick_delta} / ($axis->{tick_scale} || 1); + return $axis->{tick_distance}; +} + sub grid { my $self = shift; - return $self->get('xmajor', 'xminor', 'xtick_delta', 'ymajor', 'yminor', 'ytick_delta'); + return $self->get('xmajor', 'xminor_grids', 'xminor', 'xtick_delta', 'ymajor', 'yminor_grids', 'yminor', + 'ytick_delta'); } sub bounds { diff --git a/lib/Plots/Data.pm b/lib/Plots/Data.pm index 35c10f7f24..cdd1840d56 100644 --- a/lib/Plots/Data.pm +++ b/lib/Plots/Data.pm @@ -217,13 +217,11 @@ sub set_function { }; for my $key ('Fx', 'Fy', 'xvar', 'yvar', 'xmin', 'xmax', 'ymin', 'ymax', 'xsteps', 'ysteps') { next unless defined $options{$key}; - $f->{$key} = $options{$key}; - delete $options{$key}; + $f->{$key} = delete $options{$key}; } for my $key ('var', 'min', 'max', 'steps') { next unless defined $options{$key}; - $f->{"x$key"} = $options{$key}; - delete $options{$key}; + $f->{"x$key"} = delete $options{$key}; } return unless $f->{Fy} ne ''; @@ -257,13 +255,15 @@ sub function_string { # Ensure -x^2 gets print as -(x^2), since JavaScript finds this ambiguous. my $extraParens = $formula->context->flag('showExtraParens'); - my $format = $formula->context->{format}{number}; - $formula->context->flags->set(showExtraParens => 2); + # Ensure that abs(x) is stringified as abs(x) instead of |x|. + my $stringifyAbsAsFunction = $formula->context->flag('stringifyAbsAsFunction'); + my $format = $formula->context->{format}{number}; + $formula->context->flags->set(showExtraParens => 2, stringifyAbsAsFunction => 1); $formula->context->{format}{number} = "%f#"; # Get no bracket string for $formula my $func = $formula . ""; $func =~ s/\s//g; - $formula->context->flags->set(showExtraParens => $extraParens); + $formula->context->flags->set(showExtraParens => $extraParens, stringifyAbsAsFunction => $stringifyAbsAsFunction); $formula->context->{format}{number} = $format; my %tokens; @@ -318,8 +318,8 @@ sub function_string { ceil => 'Math.ceil', sign => 'Math.sign', int => 'Math.trunc', - log => 'Math.ln', - ln => 'Math.ln', + log => 'Math.log', + ln => 'Math.log', cos => 'Math.cos', sin => 'Math.sin', tan => 'Math.tan', @@ -334,11 +334,11 @@ sub function_string { sinh => 'Math.sinh', tanh => 'Math.tanh', acosh => 'Math.acosh', - arccosh => 'Math.arccosh', + arccosh => 'Math.acosh', asinh => 'Math.asinh', arcsinh => 'Math.asinh', atanh => 'Math.atanh', - arctanh => 'Math.arctanh', + arctanh => 'Math.atanh', min => 'Math.min', max => 'Math.max', random => 'Math.random', diff --git a/lib/Plots/GD.pm b/lib/Plots/GD.pm deleted file mode 100644 index 5b75c34640..0000000000 --- a/lib/Plots/GD.pm +++ /dev/null @@ -1,362 +0,0 @@ - -=head1 DESCRIPTION - -This is the code that takes a C and creates the GD code for generation. - -See L for more details. - -=cut - -package Plots::GD; - -use GD; - -use strict; -use warnings; - -sub new { - my ($class, $plots) = @_; - return bless { - image => '', - plots => $plots, - position => [ 0, 0 ], - colors => {}, - image => GD::Image->new($plots->size) - }, $class; -} - -sub plots { - my $self = shift; - return $self->{plots}; -} - -sub im { - my $self = shift; - return $self->{image}; -} - -sub position { - my ($self, $x, $y) = @_; - return wantarray ? @{ $self->{position} } : $self->{position} unless (defined($x) && defined($y)); - $self->{position} = [ $x, $y ]; - return; -} - -sub color { - my ($self, $color) = @_; - $self->{colors}{$color} = $self->im->colorAllocate(@{ $self->plots->colors($color) }) - unless $self->{colors}{$color}; - return $self->{colors}{$color}; -} - -# Translate x and y coordinates to pixels on the graph. -sub im_x { - my ($self, $x) = @_; - return unless defined($x); - my $plots = $self->plots; - my ($xmin, $xmax) = ($plots->axes->xaxis('min'), $plots->axes->xaxis('max')); - return int(($x - $xmin) * $plots->{width} / ($xmax - $xmin)); -} - -sub im_y { - my ($self, $y) = @_; - return unless defined($y); - my $plots = $self->plots; - my ($ymin, $ymax) = ($plots->axes->yaxis('min'), $plots->axes->yaxis('max')); - return int(($ymax - $y) * $plots->{height} / ($ymax - $ymin)); -} - -sub moveTo { - my ($self, $x, $y) = @_; - $x = $self->im_x($x); - $y = $self->im_y($y); - $self->position($x, $y); - return; -} - -sub lineTo { - my ($self, $x, $y, $color, $width, $dashed) = @_; - $color = 'default_color' unless defined($color); - $color = $self->color($color); - $width = 1 unless defined($width); - $dashed = 0 unless defined($dashed); - $x = $self->im_x($x); - $y = $self->im_y($y); - - $self->im->setThickness($width); - if ($dashed =~ /dash/) { - my @dashing = ($color) x (4 * $width * $width); - my @spacing = (GD::gdTransparent) x (3 * $width * $width); - $self->im->setStyle(@dashing, @spacing); - $self->im->line($self->position, $x, $y, GD::gdStyled); - } elsif ($dashed =~ /dot/) { - my @dashing = ($color) x (1 * $width * $width); - my @spacing = (GD::gdTransparent) x (2 * $width * $width); - $self->im->setStyle(@dashing, @spacing); - $self->im->line($self->position, $x, $y, GD::gdStyled); - } else { - $self->im->line($self->position, $x, $y, $color); - } - $self->im->setThickness(1); - $self->position($x, $y); - return; -} - -# Draw functions / lines / arrows -sub draw_data { - my ($self, $pass) = @_; - my $plots = $self->plots; - $pass = 0 unless $pass; - for my $data ($plots->data('function', 'dataset')) { - $data->gen_data; - my $n = $data->size - 1; - my $x = $data->x; - my $y = $data->y; - my $color = $data->style('color'); - my $width = $data->style('width'); - $self->moveTo($x->[0], $y->[0]); - for (1 .. $n) { - $self->lineTo($x->[$_], $y->[$_], $color, $width, $data->style('linestyle')); - } - - if ($pass == 2) { - my $r = int(3 + $width); - my $start = $data->style('start_mark') || 'none'; - if ($start eq 'circle' || $start eq 'closed_circle') { - $self->draw_circle_stamp($data->x(0), $data->y(0), $r, $color, 1); - } elsif ($start eq 'open_circle') { - $self->draw_circle_stamp($data->x(0), $data->y(0), $r, $color); - } elsif ($start eq 'arrow') { - $self->draw_arrow_head($data->x(1), $data->y(1), $data->x(0), $data->y(0), $color, $width); - } - - my $end = $data->style('end_mark') || 'none'; - if ($end eq 'circle' || $end eq 'closed_circle') { - $self->draw_circle_stamp($data->x($n), $data->y($n), $r, $color, 1); - } elsif ($end eq 'open_circle') { - $self->draw_circle_stamp($data->x($n), $data->y($n), $r, $color); - } elsif ($end eq 'arrow') { - $self->draw_arrow_head($data->x($n - 1), $data->y($n - 1), $data->x($n), $data->y($n), $color, $width); - } - } - } - return; -} - -# Label helpers -sub get_gd_font { - my ($self, $font) = @_; - if ($font eq 'tiny') { return GD::gdTinyFont; } - elsif ($font eq 'small') { return GD::gdSmallFont; } - elsif ($font eq 'large') { return GD::gdLargeFont; } - elsif ($font eq 'giant') { return GD::gdGiantFont; } - return GD::gdMediumBoldFont; -} - -sub label_offset { - my ($self, $loc, $str, $fontsize) = @_; - my $offset = 0; - # Add an additional 2px offset for the edges 'right', 'bottom', 'left', and 'top'. - if ($loc eq 'right') { $offset -= length($str) * $fontsize + 2; } - elsif ($loc eq 'bottom') { $offset -= $fontsize + 2; } - elsif ($loc eq 'center') { $offset -= length($str) * $fontsize / 2; } - elsif ($loc eq 'middle') { $offset -= $fontsize / 2; } - else { $offset = 2; } # Both 'left' and 'top'. - return $offset; -} - -sub draw_label { - my ($self, $str, $x, $y, %options) = @_; - my $font = $self->get_gd_font($options{fontsize} || 'medium'); - my $color = $self->color($options{color} || 'default_color'); - my $xoff = $self->label_offset($options{h_align} || 'center', $str, $font->width); - my $yoff = $self->label_offset($options{v_align} || 'middle', $str, $font->height); - - if ($options{orientation} && $options{orientation} eq 'vertical') { - $self->im->stringUp($font, $self->im_x($x) + $xoff, $self->im_y($y) + $yoff, $str, $color); - } else { - $self->im->string($font, $self->im_x($x) + $xoff, $self->im_y($y) + $yoff, $str, $color); - } - return; -} - -sub draw_arrow_head { - my ($self, $x1, $y1, $x2, $y2, $color, $w) = @_; - return unless @_ > 4; - $color = $self->color($color || 'default_color'); - $w = 1 unless $w; - ($x1, $y1) = ($self->im_x($x1), $self->im_y($y1)); - ($x2, $y2) = ($self->im_x($x2), $self->im_y($y2)); - - my $dx = $x2 - $x1; - my $dy = $y2 - $y1; - my $len = sqrt($dx * $dx + $dy * $dy); - my $ux = $dx / $len; # Unit vector in direction of arrow. - my $uy = $dy / $len; - my $px = -1 * $uy; # Unit vector perpendicular to arrow. - my $py = $ux; - my $hbx = $x2 - 7 * $w * $ux; - my $hby = $y2 - 7 * $w * $uy; - my $head = GD::Polygon->new; - $head->addPt($x2, $y2); - $head->addPt($hbx + 3 * $w * $px, $hby + 3 * $w * $py); - $head->addPt($hbx - 3 * $w * $px, $hby - 3 * $w * $py); - $self->im->setThickness($w); - $self->im->filledPolygon($head, $color); - $self->im->setThickness(1); - return; -} - -sub draw_circle_stamp { - my ($self, $x, $y, $r, $color, $filled) = @_; - my $d = $r ? 2 * $r : 8; - $color = $self->color($color || 'default_color'); - $self->im->filledArc($self->im_x($x), $self->im_y($y), $d, $d, 0, 360, $self->color('white')); - $self->im->filledArc($self->im_x($x), $self->im_y($y), $d, $d, 0, 360, $color, $filled ? () : GD::gdNoFill); - return; -} - -sub draw { - my $self = shift; - my $plots = $self->plots; - my $axes = $plots->axes; - my $grid = $axes->grid; - my $width = $plots->{width}; - my $height = $plots->{height}; - - # Initialize image - $self->im->interlaced('true'); - $self->im->fill(1, 1, $self->color('white')); - - # Plot data first, then fill in regions before adding axes, grid, etc. - $self->draw_data(1); - - # Fill regions - for my $region ($plots->data('fill_region')) { - $self->im->fill($self->im_x($region->x(0)), $self->im_y($region->y(0)), $self->color($region->style('color'))); - } - - # Gridlines - my ($xmin, $ymin, $xmax, $ymax) = $axes->bounds; - my $grid_color = $axes->style('grid_color'); - my $grid_style = $axes->style('grid_style'); - my $show_grid = $axes->style('show_grid'); - if ($show_grid && $grid->{xmajor}) { - my $xminor = $grid->{xminor} || 0; - my $dx = $grid->{xtick_delta} || 1; - my $x = (int($xmax / $dx) + 1) * $dx; - my $end = (int($xmin / $dx) - 1) * $dx; - while ($x >= $end) { - $self->moveTo($x, $ymin); - $self->lineTo($x, $ymax, $grid_color, 0.5, 1); - for (0 .. $xminor) { - my $tmp_x = $x + $_ * $dx / ($xminor + 1); - $self->moveTo($tmp_x, $ymin); - $self->lineTo($tmp_x, $ymax, $grid_color, 0.5, 1); - } - $x -= $dx; - } - } - if ($show_grid && $grid->{ymajor}) { - my $yminor = $grid->{yminor} || 0; - my $dy = $grid->{ytick_delta} || 1; - my $y = (int($ymax / $dy) + 1) * $dy; - my $end = (int($ymin / $dy) - 1) * $dy; - while ($y >= $end) { - $self->moveTo($xmin, $y); - $self->lineTo($xmax, $y, $grid_color, 0.5, 1); - for (0 .. $yminor) { - my $tmp_y = $y + $_ * $dy / ($yminor + 1); - $self->moveTo($xmin, $tmp_y); - $self->lineTo($xmax, $tmp_y, $grid_color, 0.5, 1); - } - $y -= $dy; - } - } - - # Plot axes - my $xloc = $axes->xaxis('location') || 'middle'; - my $yloc = $axes->yaxis('location') || 'center'; - my $xpos = ($yloc eq 'box' || $yloc eq 'left') ? $xmin : $yloc eq 'right' ? $xmax : $axes->yaxis('position'); - my $ypos = ($xloc eq 'box' || $xloc eq 'bottom') ? $ymin : $xloc eq 'top' ? $ymax : $axes->xaxis('position'); - $xpos = $xmin if $xpos < $xmin; - $xpos = $xmax if $xpos > $xmax; - $ypos = $ymin if $ypos < $ymin; - $ypos = $ymax if $ypos > $ymax; - - if ($axes->xaxis('visible')) { - my $xlabel = $axes->xaxis('label') =~ s/\\[\(\[\)\]]//gr; - my $tick_align = ($self->im_y($ymin) - $self->im_y($ypos) < 5) ? 'bottom' : 'top'; - my $label_align = ($self->im_y($ypos) - $self->im_y($ymax) < 5) ? 'top' : 'bottom'; - my $label_loc = $yloc eq 'right' && ($xloc eq 'top' || $xloc eq 'bottom') ? $xmin : $xmax; - - $self->moveTo($xmin, $ypos); - $self->lineTo($xmax, $ypos, 'black', 1.5, 0); - $self->draw_label( - $xlabel, $label_loc, $ypos, - fontsize => 'large', - v_align => $label_align, - h_align => $label_loc == $xmin ? 'left' : 'right' - ); - my $dx = $grid->{xtick_delta} || 1; - my $x = int($xmax / $dx) * $dx; - my $end = int($xmin / $dx) * $dx; - - while ($x >= $end) { - $self->draw_label($x, $x, $ypos, font => 'large', v_align => $tick_align, h_align => 'center') - unless $x == $xpos && $axes->yaxis('visible'); - $x -= $dx; - } - } - if ($axes->yaxis('visible')) { - my $ylabel = $axes->yaxis('label') =~ s/\\[\(\[\)\]]//gr; - my $tick_align = ($self->im_x($xpos) - $self->im_x($xmin) < 5) ? 'left' : 'right'; - my $label_align = ($self->im_x($xmax) - $self->im_x($xpos) < 5) ? 'right' : 'left'; - my $label_loc = ($yloc eq 'left' && $xloc eq 'top') || ($yloc eq 'right' && $xloc eq 'top') ? $ymin : $ymax; - - $self->moveTo($xpos, $ymin); - $self->lineTo($xpos, $ymax, 'black', 1.5, 0); - $self->draw_label( - $ylabel, $xpos, $label_loc, - fontsize => 'large', - v_align => $label_loc == $ymin ? 'bottom' : 'top', - h_align => $label_align - ); - - my $dy = $grid->{ytick_delta} || 1; - my $y = int($ymax / $dy) * $dy; - my $end = int($ymin / $dy) * $dy; - while ($y >= $end) { - $self->draw_label($y, $xpos, $y, font => 'large', v_align => 'middle', h_align => $tick_align) - unless $y == $ypos && $axes->xaxis('visible'); - $y -= $dy; - } - } - - # Draw data a second time to cleanup any issues with the grid and axes. - $self->draw_data(2); - - # Print Labels - for my $label ($plots->data('label')) { - $self->draw_label($label->style('label'), $label->x(0), $label->y(0), %{ $label->style }); - } - - # Draw stamps - for my $stamp ($plots->data('stamp')) { - my $symbol = $stamp->style('symbol'); - my $color = $stamp->style('color'); - my $r = $stamp->style('radius') || 4; - if ($symbol eq 'circle' || $symbol eq 'closed_circle') { - $self->draw_circle_stamp($stamp->x(0), $stamp->y(0), $r, $color, 1); - } elsif ($symbol eq 'open_circle') { - $self->draw_circle_stamp($stamp->x(0), $stamp->y(0), $r, $color); - } - } - - # Put a black frame around the picture - $self->im->rectangle(0, 0, $width - 1, $height - 1, $self->color('black')); - - return $plots->ext eq 'gif' ? $self->im->gif : $self->im->png; -} - -1; diff --git a/lib/Plots/JSXGraph.pm b/lib/Plots/JSXGraph.pm index f4d386360f..cc65c8d42a 100644 --- a/lib/Plots/JSXGraph.pm +++ b/lib/Plots/JSXGraph.pm @@ -17,9 +17,10 @@ sub new { $plots->add_css_file('node_modules/jsxgraph/distrib/jsxgraph.css'); $plots->add_css_file('js/Plots/plots.css'); - $plots->add_js_file('node_modules/jsxgraph/distrib/jsxgraphcore.js'); + $plots->add_js_file('node_modules/jsxgraph/distrib/jsxgraphcore.js', { defer => undef }); + $plots->add_js_file('js/Plots/plots.js', { defer => undef }); - return bless { plots => $plots }, $class; + return bless { plots => $plots, names => { xaxis => 1 } }, $class; } sub plots { @@ -29,57 +30,132 @@ sub plots { sub HTML { my $self = shift; - my $name = $self->{name}; - my ($width, $height) = $self->plots->size; - - my $imageviewClass = $self->plots->axes->style('jsx_navigation') ? '' : ' image-view-elt'; - my $tabindex = $self->plots->axes->style('jsx_navigation') ? '' : ' tabindex="0"'; - my $details = $self->plots->{description_details} =~ s/LONG-DESCRIPTION-ID/${name}_details/r; - my $aria_details = $details ? qq! aria-details="${name}_details"! : ''; - my $divs = qq!
    plots; + my ($width, $height) = $plots->size; + + my $imageviewClass = $plots->axes->style('jsx_navigation') ? '' : ' image-view-elt'; + my $tabindex = $plots->axes->style('jsx_navigation') ? '' : ' tabindex="0"'; + my $roundedCornersClass = $plots->{rounded_corners} ? ' plots-jsxgraph-rounded' : ''; + my $details = $plots->{description_details} =~ s/LONG-DESCRIPTION-ID/$self->{name}_details/r; + my $aria_details = $details ? qq! aria-details="$self->{name}_details"! : ''; + + my $divs = + qq!
    !; - $divs = qq!
    $divs$details
    ! if ($details); + $divs = qq!
    $divs$details
    ! if $details; + + my $axes = $plots->axes; + my $xaxis_loc = $axes->xaxis('location'); + my $yaxis_loc = $axes->yaxis('location'); + my $xaxis_pos = $axes->xaxis('position'); + my $yaxis_pos = $axes->yaxis('position'); + my $show_grid = $axes->style('show_grid'); + my $grid = $axes->grid; + my ($xmin, $ymin, $xmax, $ymax) = $axes->bounds; + + my ($xvisible, $yvisible) = ($axes->xaxis('visible'), $axes->yaxis('visible')); + + my $options = {}; + + $options->{ariaDescription} = $axes->style('aria_description') if defined $axes->style('aria_description'); + + $options->{board}{title} = $axes->style('aria_label'); + $options->{board}{showNavigation} = $axes->style('jsx_navigation') ? 1 : 0; + $options->{board}{overrideOptions} = $axes->style('jsx_options') if $axes->style('jsx_options'); + + # Set the bounding box. Add padding for the axes at the edge of graph if needed. Note that the padding set here is + # not the final padding used in the end result. The plots.js JavaScript adjusts the padding to fit the axis label + # content. This just needs to add enough padding so that the label content has enough room to render, and so that + # the JavaScript knows where the adjustments are needed. + $options->{board}{boundingBox} = [ + $xmin - ( + $yvisible + && ($yaxis_loc eq 'left' || $yaxis_loc eq 'box' || $xmin == $yaxis_pos) ? 0.11 * ($xmax - $xmin) : 0 + ), + $ymax + ($xvisible && ($xaxis_loc eq 'top' || $ymax == $xaxis_pos) ? 0.11 * ($ymax - $ymin) : 0), + $xmax + ($yvisible && ($yaxis_loc eq 'right' || $xmax == $yaxis_pos) ? 0.11 * ($xmax - $xmin) : 0), + $ymin - ( + $xvisible + && ($xaxis_loc eq 'bottom' || $xaxis_loc eq 'box' || $ymin == $xaxis_pos) ? 0.11 * ($ymax - $ymin) : 0 + ) + ]; + + $options->{xAxis}{visible} = $xvisible; + ($options->{xAxis}{min}, $options->{xAxis}{max}) = ($xmin, $xmax); + if ($xvisible || ($show_grid && $grid->{xmajor})) { + $options->{xAxis}{position} = $xaxis_pos; + $options->{xAxis}{location} = $xaxis_loc; + $options->{xAxis}{ticks}{scale} = $axes->xaxis('tick_scale'); + $options->{xAxis}{ticks}{distance} = $axes->xaxis('tick_distance'); + $options->{xAxis}{ticks}{minorTicks} = $grid->{xminor}; + } + + $options->{yAxis}{visible} = $yvisible; + ($options->{yAxis}{min}, $options->{yAxis}{max}) = ($ymin, $ymax); + if ($yvisible || ($show_grid && $grid->{ymajor})) { + $options->{yAxis}{position} = $yaxis_pos; + $options->{yAxis}{location} = $yaxis_loc; + $options->{yAxis}{ticks}{scale} = $axes->yaxis('tick_scale'); + $options->{yAxis}{ticks}{distance} = $axes->yaxis('tick_distance'); + $options->{yAxis}{ticks}{minorTicks} = $grid->{yminor}; + } + + if ($show_grid) { + if ($grid->{xmajor} || $grid->{ymajor}) { + $options->{grid}{color} = $self->get_color($axes->style('grid_color')); + $options->{grid}{opacity} = $axes->style('grid_alpha') / 200; + } + + if ($grid->{xmajor}) { + $options->{grid}{x}{minorGrids} = $grid->{xminor_grids}; + $options->{grid}{x}{overrideOptions} = $axes->xaxis('jsx_grid_options') if $axes->xaxis('jsx_grid_options'); + } + + if ($grid->{ymajor}) { + $options->{grid}{y}{minorGrids} = $grid->{yminor_grids}; + $options->{grid}{y}{overrideOptions} = $axes->yaxis('jsx_grid_options') if $axes->yaxis('jsx_grid_options'); + } + } + + $options->{mathJaxTickLabels} = $axes->style('mathjax_tick_labels') if $xvisible || $yvisible; + + if ($xvisible) { + $options->{xAxis}{name} = $axes->xaxis('label'); + $options->{xAxis}{ticks}{show} = $axes->xaxis('show_ticks'); + $options->{xAxis}{ticks}{labels} = $axes->xaxis('tick_labels'); + $options->{xAxis}{ticks}{labelFormat} = $axes->xaxis('tick_label_format'); + $options->{xAxis}{ticks}{labelDigits} = $axes->xaxis('tick_label_digits'); + $options->{xAxis}{ticks}{scaleSymbol} = $axes->xaxis('tick_scale_symbol'); + $options->{xAxis}{arrowsBoth} = $axes->xaxis('arrows_both'); + $options->{xAxis}{overrideOptions} = $axes->xaxis('jsx_options') if $axes->xaxis('jsx_options'); + } + if ($yvisible) { + $options->{yAxis}{name} = $axes->yaxis('label'); + $options->{yAxis}{ticks}{show} = $axes->yaxis('show_ticks'); + $options->{yAxis}{ticks}{labels} = $axes->yaxis('tick_labels'); + $options->{yAxis}{ticks}{labelFormat} = $axes->yaxis('tick_label_format'); + $options->{yAxis}{ticks}{labelDigits} = $axes->yaxis('tick_label_digits'); + $options->{yAxis}{ticks}{scaleSymbol} = $axes->yaxis('tick_scale_symbol'); + $options->{yAxis}{arrowsBoth} = $axes->yaxis('arrows_both'); + $options->{yAxis}{overrideOptions} = $axes->yaxis('jsx_options') if $axes->yaxis('jsx_options'); + } + + $self->{JS} //= ''; + $plots->{extra_js_code} //= ''; return <<~ "END_HTML"; $divs END_HTML @@ -88,7 +164,9 @@ sub HTML { sub get_color { my ($self, $color) = @_; $color = 'default_color' unless $color; - return sprintf("#%02x%02x%02x", @{ $self->plots->colors($color) }); + my $colorParts = $self->plots->colors($color); + return $color unless ref $colorParts eq 'ARRAY'; # Try to use the color by name if it wasn't defined. + return sprintf("#%02x%02x%02x", @$colorParts); } sub get_linestyle { @@ -107,39 +185,95 @@ sub get_linestyle { || 0; } +# Translate pgfplots layers to JSXGraph layers. +# FIXME: JSXGraph layers work rather differently than pgfplots layers. So this is a bit fuzzy, and may need adjustment. +# The layers chosen are as close as possible to the layers that JSXGraph uses by default, although "pre main" and "main" +# don't really have an equivalent. See https://jsxgraph.uni-bayreuth.de/docs/symbols/JXG.Options.html#layer. +# This also does not honor the "axis_on_top" setting. +sub get_layer { + my ($self, $data, $useFillLayer) = @_; + my $layer = $data->style($useFillLayer ? 'fill_layer' : 'layer'); + return unless $layer; + return { + 'axis background' => 0, + 'axis grid' => 1, + 'axis ticks' => 2, + 'axis lines' => 3, + 'pre main' => 4, + 'main' => 5, + 'axis tick labels' => 9, + 'axis descriptions' => 9, + 'axis foreground' => 10 + }->{$layer} // undef; +} + sub get_options { my ($self, $data, %extra_options) = @_; - my $options = Mojo::JSON::encode_json({ - highlight => 0, - strokeColor => $self->get_color($data->style('color')), - strokeWidth => $data->style('width'), - $data->style('start_mark') eq 'arrow' - ? (firstArrow => { type => 4, size => $data->style('arrow_size') || 8 }) - : (), - $data->style('end_mark') eq 'arrow' ? (lastArrow => { type => 4, size => $data->style('arrow_size') || 8 }) - : (), - $data->style('fill') eq 'self' - ? ( - fillColor => $self->get_color($data->style('fill_color') || $data->style('color')), - fillOpacity => $data->style('fill_opacity') - || 0.5 - ) - : (), - dash => $self->get_linestyle($data), - %extra_options, - }); - return $data->style('jsx_options') - ? "JXG.merge($options, " . Mojo::JSON::encode_json($data->style('jsx_options')) . ')' - : $options; + + my $fill = $data->style('fill') || 'none'; + my $drawLayer = $self->get_layer($data); + my $fillLayer = $self->get_layer($data, 1) // $drawLayer; + + my $drawFillSeparate = + $fill eq 'self' + && $data->style('linestyle') ne 'none' + && defined $fillLayer + && (!defined $drawLayer || $drawLayer != $fillLayer); + + my (%drawOptions, %fillOptions); + + if ($data->style('linestyle') ne 'none') { + $drawOptions{layer} = $drawLayer if defined $drawLayer; + $drawOptions{dash} = $self->get_linestyle($data); + $drawOptions{strokeColor} = $self->get_color($data->style('color')); + $drawOptions{strokeWidth} = $data->style('width'); + $drawOptions{firstArrow} = { type => 2, size => $data->style('arrow_size') || 8 } + if $data->style('start_mark') eq 'arrow'; + $drawOptions{lastArrow} = { type => 2, size => $data->style('arrow_size') || 8 } + if $data->style('end_mark') eq 'arrow'; + } + + if ($drawFillSeparate) { + $fillOptions{strokeWidth} = 0; + $fillOptions{layer} = $fillLayer; + $fillOptions{fillColor} = $self->get_color($data->style('fill_color') || $data->style('color')); + $fillOptions{fillOpacity} = $data->style('fill_opacity') || 0.5; + @fillOptions{ keys %extra_options } = values %extra_options; + } elsif ($fill eq 'self') { + if (!%drawOptions) { + $drawOptions{strokeWidth} = 0; + $drawOptions{layer} = $fillLayer if defined $fillLayer; + } + $drawOptions{fillColor} = $self->get_color($data->style('fill_color') || $data->style('color')); + $drawOptions{fillOpacity} = $data->style('fill_opacity') || 0.5; + } elsif ($data->style('name') && $data->style('linestyle') eq 'none') { + # This forces the curve to be drawn invisibly if it has been named, but the linestyle is 'none'. + $drawOptions{strokeWidth} = 0; + } + + @drawOptions{ keys %extra_options } = values %extra_options if %drawOptions; + + my $drawOptions = %drawOptions ? Mojo::JSON::encode_json(\%drawOptions) : ''; + my $fillOptions = $drawFillSeparate ? Mojo::JSON::encode_json(\%fillOptions) : ''; + return ( + $drawOptions && $data->style('jsx_options') + ? "JXG.merge($drawOptions, " . Mojo::JSON::encode_json($data->style('jsx_options')) . ')' + : $drawOptions, + $fillOptions && $data->style('jsx_options') + ? "JXG.merge($fillOptions, " . Mojo::JSON::encode_json($data->style('jsx_options')) . ')' + : $fillOptions + ); } sub add_curve { my ($self, $data) = @_; - return if $data->style('linestyle') eq 'none'; - my $curve_name = $data->style('name'); - my $fill = $data->style('fill') || 'none'; - my $plotOptions = $self->get_options($data, $data->style('polar') ? (curveType => 'polar') : ()); + my $curve_name = $data->style('name'); + warn 'Duplicate plot name detected. This will most likely cause issues. Make sure that all names used are unique.' + if $curve_name && $self->{names}{$curve_name}; + $self->{names}{$curve_name} = 1 if $curve_name; + + my ($plotOptions, $fillOptions) = $self->get_options($data, $data->style('polar') ? (curveType => 'polar') : ()); my $type = 'curve'; my $data_points; @@ -172,68 +306,97 @@ sub add_curve { $data_points = '[[' . join(',', $data->x) . '],[' . join(',', $data->y) . ']]'; } - $self->{JS} .= "const curve_${curve_name} = " if $curve_name; - $self->{JS} .= "board.create('$type', $data_points, $plotOptions);"; - $self->add_point($data, $data->get_start_point, $data->style('width'), $data->style('start_mark')) - if $data->style('start_mark') =~ /circle/; - $self->add_point($data, $data->get_end_point, $data->style('width'), $data->style('end_mark')) - if $data->style('end_mark') =~ /circle/; - + $self->{JS} .= "const curve_${curve_name} = " if $curve_name; + $self->{JS} .= "board.create('$type', $data_points, $plotOptions);" if $plotOptions; + $self->{JS} .= "board.create('$type', $data_points, $fillOptions);" if $fillOptions; + $self->add_point( + $data, $data->get_start_point, + 1.1 * ($data->style('width') || 2), + $data->style('width') || 2, + $data->style('start_mark') + ) if $data->style('linestyle') ne 'none' && $data->style('start_mark') =~ /circle/; + $self->add_point( + $data, $data->get_end_point, + 1.1 * ($data->style('width') || 2), + $data->style('width') || 2, + $data->style('end_mark') + ) if $data->style('linestyle') ne 'none' && $data->style('end_mark') =~ /circle/; + + my $fill = $data->style('fill') || 'none'; if ($fill ne 'none' && $fill ne 'self') { - if ($curve_name) { - my $fill_min = $data->str_to_real($data->style('fill_min')); - my $fill_max = $data->str_to_real($data->style('fill_max')); - my $fillOptions = Mojo::JSON::encode_json({ - strokeWidth => 0, - fillColor => $self->get_color($data->style('fill_color') || $data->style('color')), - fillOpacity => $data->style('fill_opacity') || 0.5, - highlight => 0, - }); - - if ($fill eq 'xaxis') { - $self->{JSend} .= - "const fill_${curve_name} = board.create('curve', [[], []], $fillOptions);" - . "fill_${curve_name}.updateDataArray = function () {" - . "const points = curve_${curve_name}.points"; - if ($fill_min ne '' && $fill_max ne '') { - $self->{JSend} .= - ".filter(p => {" - . "return p.usrCoords[1] >= $fill_min && p.usrCoords[1] <= $fill_max ? true : false" . "})"; + if ($self->{names}{$fill}) { + if ($curve_name) { + my $fill_min = $data->str_to_real($data->style('fill_min')); + my $fill_max = $data->str_to_real($data->style('fill_max')); + my $fill_min_y = $data->str_to_real($data->style('fill_min_y')); + my $fill_max_y = $data->str_to_real($data->style('fill_max_y')); + my $fill_layer = $self->get_layer($data, 1) // $self->get_layer($data); + my $fillOptions = Mojo::JSON::encode_json({ + strokeWidth => 0, + fillColor => $self->get_color($data->style('fill_color') || $data->style('color')), + fillOpacity => $data->style('fill_opacity') || 0.5, + defined $fill_layer ? (layer => $fill_layer) : (), + }); + + if ($fill eq 'xaxis') { + $self->{JS} .= + "const fill_${curve_name} = board.create('curve', [[], []], $fillOptions);" + . "fill_${curve_name}.updateDataArray = function () {" + . "const points = curve_${curve_name}.points"; + if ($fill_min ne '' && $fill_max ne '') { + $self->{JS} .= + ".filter(p => {" + . "return p.usrCoords[1] >= $fill_min && p.usrCoords[1] <= $fill_max ? true : false" . "})"; + } + $self->{JS} .= + ";this.dataX = points.map( p => p.usrCoords[1] );" + . "this.dataY = points.map( p => p.usrCoords[2] );" + . "this.dataX.push(points[points.length - 1].usrCoords[1], " + . "points[0].usrCoords[1], points[0].usrCoords[1]);" + . "this.dataY.push(0, 0, points[0].usrCoords[2]);" . "};" + . "board.update();"; + } else { + $self->{JS} .= + "const fill_${curve_name} = board.create('curve', [[], []], $fillOptions);" + . "fill_${curve_name}.updateDataArray = function () {" + . "const points1 = curve_${curve_name}.points"; + if ($fill_min ne '' && $fill_max ne '') { + $self->{JS} .= + ".filter(p => {" + . "return p.usrCoords[1] >= $fill_min && p.usrCoords[1] <= $fill_max ? true : false" . "})"; + } + if ($fill_min_y ne '' && $fill_max_y ne '') { + $self->{JS} .= + ".filter(p => {" + . "return p.usrCoords[2] >= $fill_min_y && p.usrCoords[2] <= $fill_max_y ? true : false" + . "})"; + } + $self->{JS} .= ";const points2 = curve_${fill}.points"; + if ($fill_min ne '' && $fill_max ne '') { + $self->{JS} .= + ".filter(p => {" + . "return p.usrCoords[1] >= $fill_min && p.usrCoords[1] <= $fill_max ? true : false" . "})"; + } + if ($fill_min_y ne '' && $fill_max_y ne '') { + $self->{JS} .= + ".filter(p => {" + . "return p.usrCoords[2] >= $fill_min_y && p.usrCoords[2] <= $fill_max_y ? true : false" + . "})"; + } + $self->{JS} .= + ";this.dataX = points1.map( p => p.usrCoords[1] ).concat(" + . "points2.map( p => p.usrCoords[1] ).reverse());" + . "this.dataY = points1.map( p => p.usrCoords[2] ).concat(" + . "points2.map( p => p.usrCoords[2] ).reverse());" + . "this.dataX.push(points1[0].usrCoords[1]);" + . "this.dataY.push(points1[0].usrCoords[2]);" . "};" + . "board.update();"; } - $self->{JSend} .= - ";this.dataX = points.map( p => p.usrCoords[1] );" - . "this.dataY = points.map( p => p.usrCoords[2] );" - . "this.dataX.push(points[points.length - 1].usrCoords[1], " - . "points[0].usrCoords[1], points[0].usrCoords[1]);" - . "this.dataY.push(0, 0, points[0].usrCoords[2]);" . "};" - . "board.update();"; } else { - $self->{JSend} .= - "const fill_${curve_name} = board.create('curve', [[], []], $fillOptions);" - . "fill_${curve_name}.updateDataArray = function () {" - . "const points1 = curve_${curve_name}.points"; - if ($fill_min ne '' && $fill_max ne '') { - $self->{JSend} .= - ".filter(p => {" - . "return p.usrCoords[1] >= $fill_min && p.usrCoords[1] <= $fill_max ? true : false" . "})"; - } - $self->{JSend} .= ";const points2 = curve_${fill}.points"; - if ($fill_min ne '' && $fill_max ne '') { - $self->{JSend} .= - ".filter(p => {" - . "return p.usrCoords[1] >= $fill_min && p.usrCoords[1] <= $fill_max ? true : false" . "})"; - } - $self->{JSend} .= - ";this.dataX = points1.map( p => p.usrCoords[1] ).concat(" - . "points2.map( p => p.usrCoords[1] ).reverse());" - . "this.dataY = points1.map( p => p.usrCoords[2] ).concat(" - . "points2.map( p => p.usrCoords[2] ).reverse());" - . "this.dataX.push(points1[0].usrCoords[1]);" - . "this.dataY.push(points1[0].usrCoords[2]);" . "};" - . "board.update();"; + warn q{Unable to create fill. Missing 'name' attribute.}; } } else { - warn "Unable to create fill. Missing 'name' attribute."; + warn q{Unable to fill between curves. Other graph has not yet been drawn.}; } } return; @@ -241,41 +404,78 @@ sub add_curve { sub add_multipath { my ($self, $data) = @_; - return if $data->style('linestyle') eq 'none'; - my @paths = @{ $data->{paths} }; - my $n = scalar(@paths); - my $var = $data->{function}{var}; - my $curve_name = $data->style('name'); - my $plotOptions = $self->get_options($data); - my $jsFunctionx = 'function (x){'; - my $jsFunctiony = 'function (x){'; + my @paths = @{ $data->{paths} }; + my $var = $data->{function}{var}; + my $curve_name = $data->style('name'); + warn 'Duplicate plot name detected. This will most likely cause issues. Make sure that all names used are unique.' + if $curve_name && $self->{names}{$curve_name}; + $self->{names}{$curve_name} = 1 if $curve_name; + my ($plotOptions, $fillOptions) = $self->get_options($data); + + my $count = 0; + unless ($curve_name) { + ++$count while ($self->{names}{"_plots_internal_$count"}); + $curve_name = "_plots_internal_$count"; + $self->{names}{$curve_name} = 1; + } + + $count = 0; + ++$count while ($self->{names}{"${curve_name}_$count"}); + my $curve_parts_name = "${curve_name}_$count"; + $self->{names}{$curve_parts_name} = 1; + + $self->{JS} .= "const $curve_parts_name = [\n"; + + my $cycle = $data->style('cycle'); + my ($start_x, $start_y) = ('', ''); for (0 .. $#paths) { my $path = $paths[$_]; - my $a = $_ / $n; - my $b = ($_ + 1) / $n; - my $tmin = $path->{tmin}; - my $tmax = $path->{tmax}; - my $m = ($tmax - $tmin) / ($b - $a); - my $tmp = $a < 0 ? 'x+' . (-$a) : "x-$a"; - my $t = $m < 0 ? "($tmin$m*($tmp))" : "($tmin+$m*($tmp))"; - - my $xfunction = $data->function_string($path->{Fx}, 'js', $var, undef, $t); - my $yfunction = $data->function_string($path->{Fy}, 'js', $var, undef, $t); - $jsFunctionx .= "if(x<=$b){return $xfunction;}"; - $jsFunctiony .= "if(x<=$b){return $yfunction;}"; + + if (ref $path eq 'ARRAY') { + ($start_x, $start_y) = (', ' . $path->[0], ', ' . $path->[1]) if $cycle && $_ == 0; + $self->{JS} .= "board.create('curve', [[$path->[0]], [$path->[1]]], { visible: false }),\n"; + next; + } + + ($start_x, $start_y) = + (', ' . $path->{Fx}->eval($var => $path->{tmin}), ', ' . $path->{Fy}->eval($var => $path->{tmin})) + if $cycle && $_ == 0; + + my $xfunction = $data->function_string($path->{Fx}, 'js', $var); + my $yfunction = $data->function_string($path->{Fy}, 'js', $var); + + $self->{JS} .= + "board.create('curve', " + . "[(x) => $xfunction, (x) => $yfunction, $path->{tmin}, $path->{tmax}], { visible: false }),\n"; } - $jsFunctionx .= 'return 0;}'; - $jsFunctiony .= 'return 0;}'; - $self->{JS} .= "const curve_${curve_name} = " if $curve_name; - $self->{JS} .= "board.create('curve', [$jsFunctionx, $jsFunctiony, 0, 1], $plotOptions);"; + $self->{JS} .= "];\n"; + + if ($plotOptions) { + $self->{JS} .= <<~ "END_JS"; + const curve_$curve_name = board.create('curve', [[], []], $plotOptions); + curve_$curve_name.updateDataArray = function () { + this.dataX = [].concat(...$curve_parts_name.map((c) => c.points.map((p) => p.usrCoords[1]))$start_x); + this.dataY = [].concat(...$curve_parts_name.map((c) => c.points.map((p) => p.usrCoords[2]))$start_y); + }; + END_JS + } + if ($fillOptions) { + $self->{JS} .= <<~ "END_JS"; + const fill_$curve_name = board.create('curve', [[], []], $fillOptions); + fill_$curve_name.updateDataArray = function () { + this.dataX = [].concat(...$curve_parts_name.map((c) => c.points.map((p) => p.usrCoords[1]))); + this.dataY = [].concat(...$curve_parts_name.map((c) => c.points.map((p) => p.usrCoords[2]))); + }; + END_JS + } return; } sub add_point { - my ($self, $data, $x, $y, $size, $mark) = @_; + my ($self, $data, $x, $y, $size, $strokeWidth, $mark) = @_; my $color = $self->get_color($data->style('color')); my $fill = $color; @@ -318,7 +518,7 @@ sub add_point { strokeColor => $color, fillColor => $fill, size => $size, - highlight => 0, + strokeWidth => $strokeWidth, showInfoBox => 0, }); $pointOptions = "JXG.merge($pointOptions, " . Mojo::JSON::encode_json($data->style('jsx_options')) . ')' @@ -337,170 +537,77 @@ sub add_points { $data->gen_data if $data->name eq 'function'; for (0 .. $data->size - 1) { - $self->add_point($data, $data->x($_), $data->y($_), $data->style('mark_size') || $data->style('width'), $mark); + $self->add_point( + $data, $data->x($_), $data->y($_), + $data->style('mark_size') || 2, + $data->style('width') || 2, $mark + ); } return; } +sub add_vectorfield { + my ($self, $data) = @_; + my $f = $data->{function}; + my $xfunction = $data->function_string($f->{Fx}, 'js', $f->{xvar}, $f->{yvar}); + my $yfunction = $data->function_string($f->{Fy}, 'js', $f->{xvar}, $f->{yvar}); + + if ($xfunction ne '' && $yfunction ne '') { + my ($options) = $self->get_options( + $data, + scale => $data->style('scale') || 1, + ($data->style('slopefield') ? (arrowhead => { enabled => 0 }) : ()), + ); + $data->update_min_max; + + if ($data->style('normalize') || $data->style('slopefield')) { + my $xtmp = "($xfunction)/Math.sqrt(($xfunction)**2 + ($yfunction)**2)"; + $yfunction = "($yfunction)/Math.sqrt(($xfunction)**2 + ($yfunction)**2)"; + $xfunction = $xtmp; + } + + $self->{JS} .= "board.create('vectorfield', [[(x,y) => $xfunction, (x,y) => $yfunction], " + . "[$f->{xmin}, $f->{xsteps}, $f->{xmax}], [$f->{ymin}, $f->{ysteps}, $f->{ymax}]], $options);"; + } else { + warn 'Vector field not created due to missing JavaScript functions.'; + } +} + sub add_circle { my ($self, $data) = @_; - my $x = $data->x(0); - my $y = $data->y(0); - my $r = $data->style('radius'); - my $linestyle = $self->get_linestyle($data); - my $circleOptions = $self->get_options($data); + my $x = $data->x(0); + my $y = $data->y(0); + my $r = $data->style('radius'); + my ($circleOptions, $fillOptions) = $self->get_options($data); - $self->{JS} .= "board.create('circle', [[$x, $y], $r], $circleOptions);"; + $self->{JS} .= "board.create('circle', [[$x, $y], $r], $circleOptions);" if $circleOptions; + $self->{JS} .= "board.create('circle', [[$x, $y], $r], $fillOptions);" if $fillOptions; return; } sub add_arc { - my ($self, $data) = @_; - my ($x1, $y1) = ($data->x(0), $data->y(0)); - my ($x2, $y2) = ($data->x(1), $data->y(1)); - my ($x3, $y3) = ($data->x(2), $data->y(2)); - my $arcOptions = $self->get_options( + my ($self, $data) = @_; + my ($x1, $y1) = ($data->x(0), $data->y(0)); + my ($x2, $y2) = ($data->x(1), $data->y(1)); + my ($x3, $y3) = ($data->x(2), $data->y(2)); + my ($arcOptions, $fillOptions) = $self->get_options( $data, anglePoint => { visible => 0 }, center => { visible => 0 }, radiusPoint => { visible => 0 }, ); - $self->{JS} .= "board.create('arc', [[$x1, $y1], [$x2, $y2], [$x3, $y3]], $arcOptions);"; - return; -} + # JSXGraph arcs cannot make a 360 degree revolution. So in the case that the start and end point are the same, + # move the end point back around the circle a tiny amount. + if ($x2 == $x3 && $y2 == $y3) { + my $theta = atan2($y2 - $y1, $x2 - $x1) + 2 * 3.14159265358979 - 0.0001; + $x3 = $x1 + cos($theta); + $y3 = $y1 + sin($theta); + } -sub init_graph { - my $self = shift; - my $plots = $self->plots; - my $axes = $plots->axes; - my $xaxis_loc = $axes->xaxis('location'); - my $yaxis_loc = $axes->yaxis('location'); - my $xaxis_pos = $axes->xaxis('position'); - my $yaxis_pos = $axes->yaxis('position'); - my $show_grid = $axes->style('show_grid'); - my $allow_navigation = $axes->style('jsx_navigation') ? 1 : 0; - my ($xmin, $ymin, $xmax, $ymax) = $axes->bounds; - $xaxis_loc = 'bottom' if $xaxis_loc eq 'box'; - $yaxis_loc = 'left' if $yaxis_loc eq 'box'; - - # Determine if zero should be drawn on the axis. - my $x_draw_zero = - $allow_navigation - || ($yaxis_loc eq 'center' && $yaxis_pos != 0) - || ($yaxis_loc eq 'left' && $ymin != 0) - || ($yaxis_loc eq 'right' && $ymax != 0) ? 1 : 0; - my $y_draw_zero = - $allow_navigation - || ($xaxis_loc eq 'middle' && $xaxis_pos != 0) - || ($xaxis_loc eq 'bottom' && $xmin != 0) - || ($xaxis_loc eq 'top' && $xmax != 0) ? 1 : 0; - - # Adjust bounding box to add padding for axes at edge of graph. - $xmin -= 0.11 * ($xmax - $xmin) if $yaxis_loc eq 'left' || $xmin == $yaxis_pos; - $xmax += 0.11 * ($xmax - $xmin) if $yaxis_loc eq 'right' || $xmax == $yaxis_pos; - $ymin -= 0.11 * ($ymax - $ymin) if $xaxis_loc eq 'bottom' || $ymin == $xaxis_pos; - $ymax += 0.11 * ($ymax - $ymin) if $xaxis_loc eq 'top' || $ymax == $xaxis_pos; - - my $JSXOptions = Mojo::JSON::encode_json({ - title => $axes->style('aria_label'), - description => $axes->style('aria_description'), - boundingBox => [ $xmin, $ymax, $xmax, $ymin ], - axis => 0, - showNavigation => $allow_navigation, - pan => { enabled => $allow_navigation }, - zoom => { enabled => $allow_navigation }, - showCopyright => 0, - drag => { enabled => 0 }, - }); - $JSXOptions = "JXG.merge($JSXOptions, " . Mojo::JSON::encode_json($axes->style('jsx_options')) . ')' - if $axes->style('jsx_options'); - my $XAxisOptions = Mojo::JSON::encode_json({ - name => $axes->xaxis('label'), - withLabel => 1, - position => $xaxis_loc eq 'middle' ? ($allow_navigation ? 'sticky' : 'static') : 'fixed', - anchor => $xaxis_loc eq 'top' ? 'left' : $xaxis_loc eq 'bottom' ? 'right' : 'right left', - visible => $axes->xaxis('visible') ? 1 : 0, - highlight => 0, - firstArrow => 0, - lastArrow => { size => 7 }, - straightFirst => $allow_navigation, - straightLast => $allow_navigation, - label => { - anchorX => 'middle', - anchorY => 'middle', - position => '100% left', - offset => [ -10, 0 ], - highlight => 0, - useMathJax => 1 - }, - ticks => { - drawLabels => $axes->xaxis('tick_labels') && $axes->xaxis('show_ticks') ? 1 : 0, - drawZero => $x_draw_zero, - strokeColor => $self->get_color($axes->style('grid_color')), - strokeOpacity => $axes->style('grid_alpha') / 200, - insertTicks => 0, - ticksDistance => $axes->xaxis('tick_delta'), - majorHeight => $axes->xaxis('show_ticks') ? ($show_grid && $axes->xaxis('major') ? -1 : 10) : 0, - minorTicks => $axes->xaxis('minor'), - minorHeight => $axes->xaxis('show_ticks') ? ($show_grid && $axes->xaxis('major') ? -1 : 7) : 0, - label => { - highlight => 0, - anchorX => 'middle', - anchorY => $xaxis_loc eq 'top' ? 'bottom' : 'top', - offset => $xaxis_loc eq 'top' ? [ 0, 3 ] : [ 0, -3 ] - }, - }, - }); - $XAxisOptions = "JXG.merge($XAxisOptions, " . Mojo::JSON::encode_json($axes->xaxis('jsx_options')) . ')' - if $axes->xaxis('jsx_options'); - my $YAxisOptions = Mojo::JSON::encode_json({ - name => $axes->yaxis('label'), - withLabel => 1, - position => $yaxis_loc eq 'center' ? ($allow_navigation ? 'sticky' : 'static') : 'fixed', - anchor => $yaxis_loc eq 'center' ? 'right left' : $yaxis_loc, - visible => $axes->yaxis('visible') ? 1 : 0, - highlight => 0, - firstArrow => 0, - lastArrow => { size => 7 }, - straightFirst => $allow_navigation, - straightLast => $allow_navigation, - label => { - anchorX => 'middle', - anchorY => 'middle', - position => '100% right', - offset => [ 6, -10 ], - highlight => 0, - useMathJax => 1 - }, - ticks => { - drawLabels => $axes->yaxis('tick_labels') && $axes->yaxis('show_ticks') ? 1 : 0, - drawZero => $y_draw_zero, - strokeColor => $self->get_color($axes->style('grid_color')), - strokeOpacity => $axes->style('grid_alpha') / 200, - insertTicks => 0, - ticksDistance => $axes->yaxis('tick_delta'), - majorHeight => $axes->yaxis('show_ticks') ? ($show_grid && $axes->yaxis('major') ? -1 : 10) : 0, - minorTicks => $axes->yaxis('minor'), - minorHeight => $axes->yaxis('show_ticks') ? ($show_grid && $axes->yaxis('major') ? -1 : 7) : 0, - label => { - highlight => 0, - anchorX => $yaxis_loc eq 'right' ? 'left' : 'right', - anchorY => 'middle', - offset => $yaxis_loc eq 'right' ? [ 6, 0 ] : [ -6, 0 ] - }, - }, - }); - $YAxisOptions = "JXG.merge($YAxisOptions, " . Mojo::JSON::encode_json($axes->yaxis('jsx_options')) . ')' - if $axes->yaxis('jsx_options'); - - $self->{JSend} = ''; - $self->{JS} = <<~ "END_JS"; - const board = JXG.JSXGraph.initBoard(id, $JSXOptions); - board.suspendUpdate(); - board.create('axis', [[$xmin, $xaxis_pos], [$xmax, $xaxis_pos]], $XAxisOptions); - board.create('axis', [[$yaxis_pos, $ymin], [$yaxis_pos, $ymax]], $YAxisOptions); - END_JS + $self->{JS} .= "board.create('arc', [[$x1, $y1], [$x2, $y2], [$x3, $y3]], $arcOptions);" if $arcOptions; + $self->{JS} .= "board.create('arc', [[$x1, $y1], [$x2, $y2], [$x3, $y3]], $fillOptions);" if $fillOptions; + return; } sub draw { @@ -508,49 +615,25 @@ sub draw { my $plots = $self->plots; $self->{name} = $plots->get_image_name =~ s/-/_/gr; - $self->init_graph; - - # Plot Data - for my $data ($plots->data('function', 'dataset', 'circle', 'arc', 'multipath')) { + # Plot data, vector/slope fields, and points. Note that points + # are in a separate data call so that they are drawn last. + for my $data ($plots->data('function', 'dataset', 'circle', 'arc', 'multipath', 'vectorfield'), + $plots->data('point')) + { if ($data->name eq 'circle') { $self->add_circle($data); } elsif ($data->name eq 'arc') { $self->add_arc($data); } elsif ($data->name eq 'multipath') { $self->add_multipath($data); + } elsif ($data->name eq 'vectorfield') { + $self->add_vectorfield($data); } else { - $self->add_curve($data); + $self->add_curve($data) unless $data->name eq 'point'; $self->add_points($data); } } - # Vector/Slope Fields - for my $data ($plots->data('vectorfield')) { - my $f = $data->{function}; - my $xfunction = $data->function_string($f->{Fx}, 'js', $f->{xvar}, $f->{yvar}); - my $yfunction = $data->function_string($f->{Fy}, 'js', $f->{xvar}, $f->{yvar}); - - if ($xfunction ne '' && $yfunction ne '') { - my $options = $self->get_options( - $data, - scale => $data->style('scale') || 1, - ($data->style('slopefield') ? (arrowhead => { enabled => 0 }) : ()), - ); - $data->update_min_max; - - if ($data->style('normalize') || $data->style('slopefield')) { - my $xtmp = "($xfunction)/Math.sqrt(($xfunction)**2 + ($yfunction)**2)"; - $yfunction = "($yfunction)/Math.sqrt(($xfunction)**2 + ($yfunction)**2)"; - $xfunction = $xtmp; - } - - $self->{JS} .= "board.create('vectorfield', [[(x,y) => $xfunction, (x,y) => $yfunction], " - . "[$f->{xmin}, $f->{xsteps}, $f->{xmax}], [$f->{ymin}, $f->{ysteps}, $f->{ymax}]], $options);"; - } else { - warn "Vector field not created due to missing JavaScript functions."; - } - } - # Stamps for my $stamp ($plots->data('stamp')) { my $mark = $stamp->style('symbol'); @@ -561,7 +644,7 @@ sub draw { my $y = $stamp->y(0); my $size = $stamp->style('radius') || 4; - $self->add_point($stamp, $x, $y, $size, $mark); + $self->add_point($stamp, $x, $y, $size, $stamp->style('width') || 2, $mark); } # Labels @@ -569,24 +652,37 @@ sub draw { my $str = $label->style('label'); my $x = $label->x(0); my $y = $label->y(0); - my $fontsize = $label->style('fontsize') || 'medium'; + my $fontsize = $label->style('fontsize') || 'normalsize'; my $h_align = $label->style('h_align') || 'center'; my $v_align = $label->style('v_align') || 'middle'; - my $anchor = $v_align eq 'top' ? 'north' : $v_align eq 'bottom' ? 'south' : ''; + my $anchor = $label->style('anchor'); + my $rotate = $label->style('rotate'); + my $padding = $label->style('padding') || 4; my $textOptions = Mojo::JSON::encode_json({ - highlight => 0, - fontSize => { tiny => 8, small => 10, medium => 12, large => 14, giant => 16 }->{$fontsize}, - rotate => $label->style('rotate') || 0, + fontSize => { + tiny => 8, + small => 10, + normalsize => 12, + medium => 12, # deprecated + large => 14, + Large => 16, + giant => 16, # deprecated + Large => 16, + huge => 20, + Huge => 23 + }->{$fontsize}, strokeColor => $self->get_color($label->style('color')), - anchorX => $h_align eq 'center' ? 'middle' : $h_align, - anchorY => $v_align, - cssStyle => 'padding: 3px;', - useMathJax => 1, + $anchor ne '' + ? (angleAnchor => $anchor, anchorX => 'middle', anchorY => 'middle') + : (anchorX => $h_align eq 'center' ? 'middle' : $h_align, anchorY => $v_align), + $rotate ? (rotate => $rotate) : (), + cssStyle => "line-height: 1; padding: ${padding}px;", + useMathJax => 1, }); $textOptions = "JXG.merge($textOptions, " . Mojo::JSON::encode_json($label->style('jsx_options')) . ')' if $label->style('jsx_options'); - $self->{JS} .= "board.create('text', [$x, $y, '$str'], $textOptions);"; + $self->{JS} .= "plot.createLabel($x, $y, '$str', $textOptions);"; } # JSXGraph only produces HTML graphs and uses TikZ for hadrcopy. diff --git a/lib/Plots/Plot.pm b/lib/Plots/Plot.pm index 660edba168..b03262739f 100644 --- a/lib/Plots/Plot.pm +++ b/lib/Plots/Plot.pm @@ -16,27 +16,24 @@ use Plots::Axes; use Plots::Data; use Plots::Tikz; use Plots::JSXGraph; -use Plots::GD; sub new { my ($class, %options) = @_; my $self = bless { - imageName => {}, - width => eval('$main::envir{onTheFlyImageSize}') || 350, - height => undef, - tex_size => 600, - axes => Plots::Axes->new, - colors => {}, - data => [], + imageName => {}, + width => eval('$main::envir{onTheFlyImageSize}') || 350, + height => undef, + tex_size => 600, + rounded_corners => 0, + axes => Plots::Axes->new, + colors => {}, + data => [], }, $class; # Besides for these core options, pass everything else to the Axes object. - for ('width', 'height', 'tex_size') { - if ($options{$_}) { - $self->{$_} = $options{$_}; - delete $options{$_}; - } + for ('width', 'height', 'tex_size', 'rounded_corners') { + $self->{$_} = delete $options{$_} if $options{$_}; } $self->axes->set(%options) if %options; @@ -48,13 +45,12 @@ sub new { sub pgCall { my ($call, @args) = @_; - WeBWorK::PG::Translator::PG_restricted_eval('\&' . $call)->(@args); - return; + return WeBWorK::PG::Translator::PG_restricted_eval('\&' . $call)->(@args); } sub add_js_file { - my ($self, $file) = @_; - pgCall('ADD_JS_FILE', $file); + my ($self, $file, $attributes) = @_; + pgCall('ADD_JS_FILE', $file, 0, $attributes); return; } @@ -164,6 +160,17 @@ sub image_type { my ($self, $type, $ext) = @_; return $self->{type} unless $type; + # Hardcopy uses the Tikz 'pdf' extension and PTX uses the Tikz 'tgz' extension. + if ($self->{pg}{displayMode} eq 'TeX') { + $self->{type} = 'Tikz'; + $self->{ext} = 'pdf'; + return; + } elsif ($self->{pg}{displayMode} eq 'PTX') { + $self->{type} = 'Tikz'; + $self->{ext} = 'tgz'; + return; + } + # Check type and extension are valid. The first element of @validExt is used as default. my @validExt; $type = lc($type); @@ -173,9 +180,6 @@ sub image_type { } elsif ($type eq 'tikz') { $self->{type} = 'Tikz'; @validExt = ('svg', 'png', 'pdf', 'gif', 'tgz'); - } elsif ($type eq 'gd') { - $self->{type} = 'GD'; - @validExt = ('png', 'gif'); } else { warn "Plots: Invalid image type $type."; return; @@ -191,14 +195,6 @@ sub image_type { $self->{ext} = $validExt[0]; } - # Hardcopy uses the Tikz 'pdf' extension and PTX uses the Tikz 'tgz' extension. - if ($self->{pg}{displayMode} eq 'TeX') { - $self->{type} = 'Tikz'; - $self->{ext} = 'pdf'; - } elsif ($self->{pg}{displayMode} eq 'PTX') { - $self->{type} = 'Tikz'; - $self->{ext} = 'tgz'; - } return; } @@ -215,7 +211,7 @@ sub tikz_code { # Add functions to the graph. sub _add_function { - my ($self, $Fx, $Fy, $var, $min, $max, @rest) = @_; + my ($self, $Fx, $Fy, $var, $min, $max, %rest) = @_; $var = 't' unless $var; $Fx = $var unless defined($Fx); @@ -229,9 +225,10 @@ sub _add_function { xmax => $max, color => 'default_color', width => 2, + mark_size => 2, dashed => 0, tikz_smooth => 1, - @rest + %rest ); $self->add_data($data); @@ -297,22 +294,23 @@ sub add_function { sub add_multipath { my ($self, $paths, $var, %options) = @_; my $data = Plots::Data->new(name => 'multipath'); - my $steps = 500; # Steps set high to help Tikz deal with boundaries of paths. - if ($options{steps}) { - $steps = $options{steps}; - delete $options{steps}; - } + my $steps = (delete $options{steps}) || 30; $data->{context} = $self->context; $data->{paths} = [ - map { { - Fx => $data->get_math_object($_->[0], $var), - Fy => $data->get_math_object($_->[1], $var), - tmin => $data->str_to_real($_->[2]), - tmax => $data->str_to_real($_->[3]) - } } @$paths + map { + @$_ == 2 + ? [@$_] + : { + Fx => $data->get_math_object($_->[0], $var), + Fy => $data->get_math_object($_->[1], $var), + tmin => $data->str_to_real($_->[2]), + tmax => $data->str_to_real($_->[3]), + @$_[ 4 .. $#$_ ] + } + } @$paths ]; $data->{function} = { var => $var, steps => $steps }; - $data->style(color => 'default_color', width => 2, %options); + $data->style(color => 'default_color', width => 2, mark_size => 2, %options); $self->add_data($data); return $data; @@ -329,8 +327,9 @@ sub _add_dataset { $data->add(@{ shift(@points) }); } $data->style( - color => 'default_color', - width => 2, + color => 'default_color', + width => 2, + mark_size => 2, @points ); @@ -351,9 +350,10 @@ sub _add_circle { my $data = Plots::Data->new(name => 'circle'); $data->add(@$point); $data->style( - radius => $radius, - color => 'default_color', - width => 2, + radius => $radius, + color => 'default_color', + width => 2, + mark_size => 2, @options ); @@ -374,8 +374,9 @@ sub _add_arc { my $data = Plots::Data->new(name => 'arc'); $data->add($point1, $point2, $point3); $data->style( - color => 'default_color', - width => 2, + color => 'default_color', + width => 2, + mark_size => 2, @options ); @@ -396,18 +397,19 @@ sub add_vectorfield { my $data = Plots::Data->new(name => 'vectorfield'); $data->set_function( $self->context, - Fx => '', - Fy => '', - xvar => 'x', - yvar => 'y', - xmin => -5, - xmax => 5, - ymin => -5, - ymax => 5, - xsteps => 15, - ysteps => 15, - width => 1, - color => 'default_color', + Fx => '', + Fy => '', + xvar => 'x', + yvar => 'y', + xmin => -5, + xmax => 5, + ymin => -5, + ymax => 5, + xsteps => 15, + ysteps => 15, + width => 1, + mark_size => 1, + color => 'default_color', @options ); @@ -417,15 +419,17 @@ sub add_vectorfield { sub _add_label { my ($self, $x, $y, @options) = @_; - my $data = Plots::Data->new(name => 'label'); + my $data = Plots::Data->new(name => 'label'); + my $label = @options % 2 ? shift @options : ''; $data->add($x, $y); $data->style( color => 'default_color', fontsize => 'medium', orientation => 'horizontal', + rotate => 0, h_align => 'center', v_align => 'middle', - label => '', + label => $label, @options ); @@ -438,22 +442,17 @@ sub add_label { return ref($labels[0]) eq 'ARRAY' ? [ map { $self->_add_label(@$_); } @labels ] : $self->_add_label(@labels); } -# Fill regions only work with GD and are ignored in TikZ images. -sub _add_fill_region { - my ($self, $x, $y, $color) = @_; - my $data = Plots::Data->new(name => 'fill_region'); - $data->add($x, $y); - $data->style(color => $color || 'default_color'); - $self->add_data($data); +sub _add_point { + my ($self, $x, $y, %options) = @_; + $options{marks} = delete $options{mark} if $options{mark} && !defined $options{marks}; + my $data = $self->_add_dataset([ $x, $y ], marks => 'circle', %options); + $data->{name} = 'point'; return $data; } -sub add_fill_region { - my ($self, @regions) = @_; - return - ref($regions[0]) eq 'ARRAY' - ? [ map { $self->_add_fill_region(@$_); } @regions ] - : $self->_add_fill_region(@regions); +sub add_point { + my ($self, @points) = @_; + return ref($points[0]) eq 'ARRAY' ? [ map { $self->_add_point(@$_); } @points ] : $self->_add_point(@points); } sub _add_stamp { @@ -462,7 +461,7 @@ sub _add_stamp { $data->add($x, $y); $data->style( color => 'default_color', - size => 4, + radius => 4, symbol => 'circle', @options ); @@ -485,8 +484,6 @@ sub draw { $image = Plots::Tikz->new($self); } elsif ($type eq 'JSXGraph') { $image = Plots::JSXGraph->new($self); - } elsif ($type eq 'GD') { - $image = Plots::GD->new($self); } else { warn "Undefined image type: $type"; return; diff --git a/lib/Plots/Tikz.pm b/lib/Plots/Tikz.pm index 37cbcd6b78..611273f297 100644 --- a/lib/Plots/Tikz.pm +++ b/lib/Plots/Tikz.pm @@ -15,19 +15,23 @@ use warnings; sub new { my ($class, $plots) = @_; my $image = LaTeXImage->new; - $image->environment([ 'tikzpicture', 'framed' ]); + $image->environment(['tikzpicture']); $image->svgMethod(eval('$main::envir{latexImageSVGMethod}') // 'dvisvgm'); $image->convertOptions(eval('$main::envir{latexImageConvertOptions}') // { input => {}, output => {} }); $image->ext($plots->ext); - $image->tikzLibraries('arrows.meta,plotmarks,backgrounds'); + $image->tikzLibraries('arrows.meta,plotmarks,calc,spath3'); $image->texPackages(['pgfplots']); - # Set the pgfplots compatibility, add the pgfplots fillbetween library, set a nice rectangle frame with white - # background for the backgrounds library, and redefine standard layers since the backgrounds library uses layers - # that conflict with the layers used by the fillbetween library. + # Set the pgfplots compatibility, add the pgfplots fillbetween library, define a save + # box that is used to wrap the axes in a nice rectangle frame with a white background, and redefine + # standard layers to include a background layer for the background. + # Note that "axis tick labels" is moved after "pre main" and "main" in the standard layer set. That is different + # than the pgfplots defaults, but is consistent with where JSXGraph places them, and is better than what pgplots + # does. Axis tick labels are textual elements that should be in front of the things that are drawn and together + # with the "axis descriptions". $image->addToPreamble( <<~ 'END_PREAMBLE'); \usepgfplotslibrary{fillbetween} - \tikzset{inner frame sep = 0pt, background rectangle/.style = { thick, draw = DarkBlue, fill = white }} + \newsavebox{\axesBox} \pgfplotsset{ compat = 1.18, layers/standard/.define layer set = { @@ -36,9 +40,9 @@ sub new { axis grid, axis ticks, axis lines, - axis tick labels, pre main, main, + axis tick labels, axis descriptions, axis foreground }{ @@ -68,7 +72,7 @@ sub new { } END_PREAMBLE - return bless { image => $image, plots => $plots, colors => {} }, $class; + return bless { image => $image, plots => $plots, colors => {}, names => { xaxis => 1 } }, $class; } sub plots { @@ -84,7 +88,9 @@ sub im { sub get_color { my ($self, $color) = @_; return '' if $self->{colors}{$color}; - my ($r, $g, $b) = @{ $self->plots->colors($color) }; + my $colorParts = $self->plots->colors($color); + return '' unless ref $colorParts eq 'ARRAY'; # Try to use the color by name if it wasn't defined. + my ($r, $g, $b) = @$colorParts; $self->{colors}{$color} = 1; return "\\definecolor{$color}{RGB}{$r,$g,$b}\n"; } @@ -94,183 +100,465 @@ sub get_mark { return { circle => '*', closed_circle => '*', - open_circle => 'o', + open_circle => '*, mark options={fill=white}', square => 'square*', - open_square => 'square', + open_square => 'square*, mark options={fill=white}', plus => '+', times => 'x', bar => '|', dash => '-', triangle => 'triangle*', - open_triangle => 'triangle', + open_triangle => 'triangle*, mark options={fill=white}', diamond => 'diamond*', - open_diamond => 'diamond', + open_diamond => 'diamond*, mark options={fill=white}', }->{$mark}; } -sub configure_axes { - my $self = shift; +# This is essentially copied from contextFraction.pl, and is exactly copied from parserGraphTool.pl. +# FIXME: Clearly there needs to be a single version of this somewhere that all three can use. +sub continuedFraction { + my ($x) = @_; + + my $step = $x; + my $n = int($step); + my ($h0, $h1, $k0, $k1) = (1, $n, 0, 1); + + while ($step != $n) { + $step = 1 / ($step - $n); + $n = int($step); + my ($newh, $newk) = ($n * $h1 + $h0, $n * $k1 + $k0); + last if $newk > 10**8; # Bail if the denominator is skyrocketing out of control. + ($h0, $h1, $k0, $k1) = ($h1, $newh, $k1, $newk); + } + + return ($h1, $k1); +} + +sub formatTickLabelText { + my ($self, $value, $axis) = @_; + my $tickFormat = $self->plots->axes->$axis('tick_label_format'); + if ($tickFormat eq 'fraction' || $tickFormat eq 'mixed') { + my ($num, $den) = continuedFraction(abs($value)); + if ($num && $den != 1 && !($num == 1 && $den == 1)) { + if ($tickFormat eq 'fraction' || $num < $den) { + $value = ($value < 0 ? '-' : '') . "\\frac{$num}{$den}"; + } else { + my $int = int($num / $den); + my $properNum = $num % $den; + $value = ($value < 0 ? '-' : '') . "$int\\frac{$properNum}{$den}"; + } + } + } elsif ($tickFormat eq 'scinot') { + my ($mantissa, $exponent) = split('e', sprintf('%e', $value)); + $value = + Plots::Plot::pgCall('Round', $mantissa, $self->plots->axes->$axis('tick_label_digits') // 2) + . "\\cdot 10^{$exponent}"; + } else { + $value = + sprintf('%f', Plots::Plot::pgCall('Round', $value, $self->plots->axes->$axis('tick_label_digits') // 2)); + if ($value =~ /\./) { + $value =~ s/0*$//; + $value =~ s/\.$//; + } + } + my $scaleSymbol = $self->plots->axes->$axis('tick_scale_symbol'); + return '\\(' + . ($value eq '0' ? '0' + : $scaleSymbol ? ($value eq '1' ? $scaleSymbol : $value eq '-1' ? "-$scaleSymbol" : "$value$scaleSymbol") + : $value) . '\\)'; +} + +sub generate_axes { + my ($self, $plotContents) = @_; my $plots = $self->plots; my $axes = $plots->axes; my $grid = $axes->grid; my ($xmin, $ymin, $xmax, $ymax) = $axes->bounds; my ($axes_width, $axes_height) = $plots->size; - my $show_grid = $axes->style('show_grid'); - my $xvisible = $axes->xaxis('visible'); - my $yvisible = $axes->yaxis('visible'); - my $xmajor = $show_grid && $xvisible && $grid->{xmajor} && $axes->xaxis('show_ticks') ? 'true' : 'false'; - my $xminor_num = $grid->{xminor}; - my $xminor = $show_grid && $xvisible && $xmajor eq 'true' && $xminor_num > 0 ? 'true' : 'false'; - my $ymajor = $show_grid && $yvisible && $grid->{ymajor} && $axes->yaxis('show_ticks') ? 'true' : 'false'; - my $yminor_num = $grid->{yminor}; - my $yminor = $show_grid && $yvisible && $ymajor eq 'true' && $yminor_num > 0 ? 'true' : 'false'; - my $xticks = $axes->xaxis('show_ticks') ? "xtick distance=$grid->{xtick_delta}" : 'xtick=\empty'; - my $yticks = $axes->yaxis('show_ticks') ? "ytick distance=$grid->{ytick_delta}" : 'ytick=\empty'; - my $xtick_labels = $axes->xaxis('tick_labels') ? '' : "\nxticklabel=\\empty,"; - my $ytick_labels = $axes->yaxis('tick_labels') ? '' : "\nyticklabel=\\empty,"; - my $grid_color = $axes->style('grid_color'); - my $grid_color2 = $self->get_color($grid_color); - my $grid_alpha = $axes->style('grid_alpha'); - my $xlabel = $axes->xaxis('label'); - my $axis_x_line = $axes->xaxis('location'); - my $axis_x_pos = $axes->xaxis('position'); - my $ylabel = $axes->yaxis('label'); - my $axis_y_line = $axes->yaxis('location'); - my $axis_y_pos = $axes->yaxis('position'); - my $axis_on_top = $axes->style('axis_on_top') ? "axis on top,\n" : ''; - my $hide_x_axis = ''; - my $hide_y_axis = ''; - my $xaxis_plot = ($xmin <= 0 && $xmax >= 0) ? "\\path[name path=xaxis] ($xmin, 0) -- ($xmax,0);\n" : ''; - $axis_x_pos = $axis_x_pos ? ",\naxis x line shift=" . (-$axis_x_pos) : ''; - $axis_y_pos = $axis_y_pos ? ",\naxis y line shift=" . (-$axis_y_pos) : ''; - - unless ($xvisible) { - $xlabel = ''; - $hide_x_axis = "\nx axis line style={draw=none},\n" . "x tick style={draw=none},\n" . "xticklabel=\\empty,"; + my $show_grid = $axes->style('show_grid'); + my $xvisible = $axes->xaxis('visible'); + my $yvisible = $axes->yaxis('visible'); + my $xmajor = $show_grid && $grid->{xmajor} ? 'true' : 'false'; + my $xminor = $show_grid && $xmajor eq 'true' && $grid->{xminor_grids} && $grid->{xminor} > 0 ? 'true' : 'false'; + my $ymajor = $show_grid && $grid->{ymajor} ? 'true' : 'false'; + my $yminor = $show_grid && $ymajor eq 'true' && $grid->{yminor_grids} && $grid->{yminor} > 0 ? 'true' : 'false'; + my $grid_color = $axes->style('grid_color'); + my $grid_color_def = $self->get_color($grid_color); + my $grid_alpha = $axes->style('grid_alpha') / 100; + my $xaxis_location = $axes->xaxis('location'); + my $xaxis_pos = $xaxis_location eq 'middle' ? $axes->xaxis('position') : 0; + my $yaxis_location = $axes->yaxis('location'); + my $yaxis_pos = $yaxis_location eq 'center' ? $axes->yaxis('position') : 0; + my $axis_on_top = $axes->style('axis_on_top') ? "axis on top,\n" : ''; + my $xNegativeArrow = $axes->xaxis('arrows_both') ? 'Latex[{round,scale=1.6}]' : ''; + my $yNegativeArrow = $axes->yaxis('arrows_both') ? 'Latex[{round,scale=1.6}]' : ''; + my $tikz_options = $axes->style('tikz_options') // ''; + + my $xlabel = $xvisible ? $axes->xaxis('label') : ''; + my $xaxis_style = + $xvisible + ? ",\nx axis line style={$xNegativeArrow-Latex[{round,scale=1.6}]}" + : ",\nx axis line style={draw=none},\nextra y ticks={0}"; + my $xtick_style = + $xvisible && $axes->xaxis('show_ticks') ? ",\nx tick style={line width=0.6pt}" : ",\nx tick style={draw=none}"; + + my $ylabel = $yvisible ? $axes->yaxis('label') : ''; + my $yaxis_style = + $yvisible + ? ",\ny axis line style={$yNegativeArrow-Latex[{round,scale=1.6}]}" + : ",\ny axis line style={draw=none},\nextra x ticks={0}"; + my $ytick_style = + $yvisible && $axes->yaxis('show_ticks') ? ",\ny tick style={line width=0.6pt}" : ",\ny tick style={draw=none}"; + + my $x_tick_distance = $axes->xaxis('tick_distance'); + my $x_tick_scale = $axes->xaxis('tick_scale') || 1; + + my @xticks = + grep { $_ > $xmin && $_ < $xmax } + map { -$_ * $x_tick_distance * $x_tick_scale } + reverse(1 .. -$xmin / ($x_tick_distance * $x_tick_scale)); + push(@xticks, 0) if $xmin < 0 && $xmax > 0; + push(@xticks, + grep { $_ > $xmin && $_ < $xmax } + map { $_ * $x_tick_distance * $x_tick_scale } (1 .. $xmax / ($x_tick_distance * $x_tick_scale))); + + my $xtick_labels = + $xvisible + && $axes->xaxis('show_ticks') + && $axes->xaxis('tick_labels') + ? (",\nxticklabel shift=9pt,\nxticklabel style={anchor=center},\nxticklabels={" + . join(',', map { $self->formatTickLabelText($_ / $x_tick_scale, 'xaxis') } @xticks) . '}') + : ",\nxticklabel=\\empty"; + + my @xminor_ticks; + if ($grid->{xminor} > 0) { + my @majorTicks = @xticks; + unshift(@majorTicks, ($majorTicks[0] // $xmin) - $x_tick_distance * $x_tick_scale); + push(@majorTicks, ($majorTicks[-1] // $xmax) + $x_tick_distance * $x_tick_scale); + my $x_minor_delta = $x_tick_distance * $x_tick_scale / ($grid->{xminor} + 1); + for my $tickIndex (0 .. $#majorTicks - 1) { + push(@xminor_ticks, + grep { $_ > $xmin && $_ < $xmax } + map { $majorTicks[$tickIndex] + $_ * $x_minor_delta } 1 .. $grid->{xminor}); + } } - unless ($yvisible) { - $ylabel = ''; - $hide_y_axis = "\ny axis line style={draw=none},\n" . "y tick style={draw=none},\n" . "yticklabel=\\empty,"; + + my $y_tick_distance = $axes->yaxis('tick_distance'); + my $y_tick_scale = $axes->yaxis('tick_scale') || 1; + + my @yticks = + grep { $_ > $ymin && $_ < $ymax } + map { -$_ * $y_tick_distance * $y_tick_scale } + reverse(1 .. -$ymin / ($y_tick_distance * $y_tick_scale)); + push(@yticks, 0) if $ymin < 0 && $ymax > 0; + push(@yticks, + grep { $_ > $ymin && $_ < $ymax } + map { $_ * $y_tick_distance * $y_tick_scale } (1 .. $ymax / ($y_tick_distance * $y_tick_scale))); + + my $ytick_labels = + $yvisible + && $axes->yaxis('show_ticks') + && $axes->yaxis('tick_labels') + ? (",\nyticklabel shift=-3pt,\nyticklabels={" + . join(',', map { $self->formatTickLabelText($_ / $y_tick_scale, 'yaxis') } @yticks) . '}') + : ",\nyticklabel=\\empty"; + + my @yminor_ticks; + if ($grid->{yminor} > 0) { + my @majorTicks = @yticks; + unshift(@majorTicks, ($majorTicks[0] // $ymin) - $y_tick_distance * $y_tick_scale); + push(@majorTicks, ($majorTicks[-1] // $ymax) + $y_tick_distance * $y_tick_scale); + my $y_minor_delta = $y_tick_distance * $y_tick_scale / ($grid->{yminor} + 1); + for my $tickIndex (0 .. $#majorTicks - 1) { + push(@yminor_ticks, + grep { $_ > $ymin && $_ < $ymax } + map { $majorTicks[$tickIndex] + $_ * $y_minor_delta } 1 .. $grid->{yminor}); + } } + + my $xaxis_plot = ($ymin <= 0 && $ymax >= 0) ? "\\path[name path=xaxis] ($xmin, 0) -- ($xmax, 0);" : ''; + $xaxis_pos = $xaxis_pos ? ",\naxis x line shift=" . (($ymin > 0 ? $ymin : $ymax < 0 ? $ymax : 0) - $xaxis_pos) : ''; + $yaxis_pos = $yaxis_pos ? ",\naxis y line shift=" . (($xmin > 0 ? $xmin : $xmax < 0 ? $xmax : 0) - $yaxis_pos) : ''; + + my $roundedCorners = $plots->{rounded_corners} ? 'rounded corners = 10pt' : ''; + my $left = + $yvisible && ($yaxis_location eq 'left' || $yaxis_location eq 'box' || $xmin == $axes->yaxis('position')) + ? 'outer west' + : 'west'; + my $right = $yvisible && ($yaxis_location eq 'right' || $xmax == $axes->yaxis('position')) ? 'outer east' : 'east'; + my $lower = + $xvisible && ($xaxis_location eq 'bottom' || $xaxis_location eq 'box' || $ymin == $axes->xaxis('position')) + ? 'outer south' + : 'south'; + my $upper = $xvisible && ($xaxis_location eq 'top' || $ymax == $axes->xaxis('position')) ? 'outer north' : 'north'; + + # The savebox only actually saves the main layer. All other layers are actually drawn when the savebox is saved. + # So clipping of anything drawn on any other layer has to be done when things are drawn on the other layers. The + # axisclippath is used for this. The main layer is clipped at the end when the savebox is used. my $tikzCode = <<~ "END_TIKZ"; - \\begin{axis} - [ - trig format plots=rad, - view={0}{90}, - scale only axis, - height=$axes_height, - width=$axes_width, - ${axis_on_top}axis x line=$axis_x_line$axis_x_pos, - axis y line=$axis_y_line$axis_y_pos, - xlabel={$xlabel}, - ylabel={$ylabel}, - $xticks,$xtick_labels - $yticks,$ytick_labels - xmajorgrids=$xmajor, - xminorgrids=$xminor, - minor x tick num=$xminor_num, - ymajorgrids=$ymajor, - yminorgrids=$yminor, - minor y tick num=$yminor_num, - grid style={$grid_color!$grid_alpha}, - xmin=$xmin, - xmax=$xmax, - ymin=$ymin, - ymax=$ymax,$hide_x_axis$hide_y_axis - ] - $grid_color2$xaxis_plot + \\pgfplotsset{set layers=${\($axes->style('axis_on_top') ? 'axis on top' : 'standard')}}% + $grid_color_def + \\savebox{\\axesBox}{ + \\Large + \\begin{axis} + [ + trig format plots=rad, + scale only axis, + height=$axes_height, + width=$axes_width, + ${axis_on_top}axis x line=$xaxis_location$xaxis_pos$xaxis_style, + axis y line=$yaxis_location$yaxis_pos$yaxis_style, + xlabel={$xlabel}, + ylabel={$ylabel}, + xtick={${\(join(',', @xticks))}}$xtick_style$xtick_labels, + minor xtick={${\(join(',', @xminor_ticks))}}, + ytick={${\(join(',', @yticks))}}$ytick_style$ytick_labels, + minor ytick={${\(join(',', @yminor_ticks))}}, + xtick scale label code/.code={}, + ytick scale label code/.code={}, + major tick length=0.3cm, + minor tick length=0.2cm, + xmajorgrids=$xmajor, + xminorgrids=$xminor, + ymajorgrids=$ymajor, + yminorgrids=$yminor, + grid style={$grid_color, opacity=$grid_alpha}, + xmin=$xmin, + xmax=$xmax, + ymin=$ymin, + ymax=$ymax,$tikz_options + ] + $xaxis_plot + \\newcommand{\\axisclippath}{(current axis.south west) [${\( + $roundedCorners && ($lower !~ /^outer/ || $right !~ /^outer/) ? $roundedCorners : 'sharp corners' + )}] -- (current axis.south east) [${\( + $roundedCorners && ($upper !~ /^outer/ || $right !~ /^outer/) ? $roundedCorners : 'sharp corners' + )}] -- (current axis.north east) [${\( + $roundedCorners && ($upper !~ /^outer/ || $left !~ /^outer/) ? $roundedCorners : 'sharp corners' + )}] -- (current axis.north west) [${\( + $roundedCorners && ($lower !~ /^outer/ || $left !~ /^outer/) ? $roundedCorners : 'sharp corners' + )}] -- cycle} + END_TIKZ + + $tikzCode .= $plotContents; + $tikzCode .= $plots->{extra_tikz_code} if $plots->{extra_tikz_code}; + + $tikzCode .= <<~ "END_TIKZ"; + \\end{axis} + } + \\pgfresetboundingbox + \\begin{pgfonlayer}{background} + \\filldraw[draw = DarkBlue, fill = white, $roundedCorners, line width = 0.5pt] + (\$(current axis.$left |- current axis.$lower)-(0.25pt,0.25pt)\$) + rectangle + (\$(current axis.$right |- current axis.$upper)+(0.25pt,0.25pt)\$); + \\end{pgfonlayer} + \\begin{scope} + \\clip[$roundedCorners] + (\$(current axis.$left |- current axis.$lower)-(0.25pt,0.25pt)\$) + rectangle + (\$(current axis.$right |- current axis.$upper)+(0.25pt,0.25pt)\$); + \\usebox{\\axesBox} + \\end{scope} + \\begin{pgfonlayer}{axis foreground} + \\draw[draw = DarkBlue, $roundedCorners, line width = 0.5pt, use as bounding box] + (\$(current axis.$left |- current axis.$lower)-(0.25pt,0.25pt)\$) + rectangle + (\$(current axis.$right |- current axis.$upper)+(0.25pt,0.25pt)\$); + \\end{pgfonlayer} END_TIKZ chop($tikzCode); - return $tikzCode =~ s/^\t//gr; + return $tikzCode; } -sub get_plot_opts { +sub get_options { my ($self, $data) = @_; - my $color = $data->style('color') || 'default_color'; - my $width = $data->style('width'); - my $linestyle = $data->style('linestyle') || 'solid'; - my $marks = $data->style('marks') || 'none'; - my $mark_size = $data->style('mark_size') || 0; - my $start = $data->style('start_mark') || 'none'; - my $end = $data->style('end_mark') || 'none'; - my $name = $data->style('name'); - my $fill = $data->style('fill') || 'none'; - my $fill_color = $data->style('fill_color') || $data->style('color') || 'default_color'; - my $fill_opacity = $data->style('fill_opacity') || 0.5; - my $tikz_options = $data->style('tikz_options') ? ', ' . $data->style('tikz_options') : ''; - my $smooth = $data->style('tikz_smooth') ? 'smooth, ' : ''; - - if ($start =~ /circle/) { - $start = '{Circle[sep=-1.196825pt -1.595769' . ($start eq 'open_circle' ? ', open' : '') . ']}'; - } elsif ($start eq 'arrow') { - my $arrow_width = $data->style('arrow_size') || 10; - my $arrow_length = int(1.5 * $arrow_width); - $start = "{Stealth[length=${arrow_length}pt,width=${arrow_width}pt]}"; - } else { - $start = ''; - } - if ($end =~ /circle/) { - $end = '{Circle[sep=-1.196825pt -1.595769' . ($end eq 'open_circle' ? ', open' : '') . ']}'; - } elsif ($end eq 'arrow') { - my $arrow_width = $data->style('arrow_size') || 10; - my $arrow_length = int(1.5 * $arrow_width); - $end = "{Stealth[length=${arrow_length}pt,width=${arrow_width}pt]}"; - } else { - $end = ''; + + my $fill = $data->style('fill') || 'none'; + my $drawLayer = $data->style('layer'); + my $fillLayer = $data->style('fill_layer') || $drawLayer; + my $marks = $self->get_mark($data->style('marks')); + + my $drawFillSeparate = + $fill eq 'self' + && ($data->style('linestyle') ne 'none' || $marks) + && defined $fillLayer + && (!defined $drawLayer || $drawLayer ne $fillLayer); + + my (@drawOptions, @fillOptions); + + if ($data->style('linestyle') ne 'none' || $marks) { + my $linestyle = { + none => 'draw=none', + solid => 'solid', + dashed => 'dash={on 11pt off 8pt phase 6pt}', + short_dashes => 'dash pattern={on 6pt off 3pt}', + long_dashes => 'dash={on 20pt off 15pt phase 10pt}', + dotted => 'dotted', + long_medium_dashes => 'dash={on 20pt off 7pt on 11pt off 7pt phase 10pt}', + }->{ ($data->style('linestyle') || 'solid') =~ s/ /_/gr } + || 'solid'; + push(@drawOptions, $linestyle); + + my $width = $data->style('width'); + push(@drawOptions, "line width=${width}pt", "color=" . ($data->style('color') || 'default_color')); + + if ($linestyle ne 'draw=none') { + my $start = $data->style('start_mark') || ''; + if ($start =~ /circle/) { + $start = + '{Circle[sep=-1.196825pt -1.595769' . ($start eq 'open_circle' ? ', open,fill=white' : '') . ']}'; + } elsif ($start eq 'arrow') { + my $arrow_width = $width * ($data->style('arrow_size') || 8); + $start = "{Stealth[length=${arrow_width}pt 1,width'=0pt 1,inset'=0pt 0.5]}"; + } else { + $start = ''; + } + + my $end = $data->style('end_mark') || ''; + if ($end =~ /circle/) { + $end = '{Circle[sep=-1.196825pt -1.595769' . ($end eq 'open_circle' ? ', open,fill=white' : '') . ']}'; + } elsif ($end eq 'arrow') { + my $arrow_width = $width * ($data->style('arrow_size') || 8); + $end = "{Stealth[length=${arrow_width}pt 1,width'=0pt 1,inset'=0pt 0.5]}"; + } else { + $end = ''; + } + + push(@drawOptions, "$start-$end") if $start || $end; + } + + if ($marks) { + push(@drawOptions, "mark=$marks"); + + my $mark_size = $data->style('mark_size') || 0; + if ($mark_size) { + $mark_size = $mark_size + $width / 2 if $marks =~ /^[*+]/; + $mark_size = $mark_size + $width if $marks eq 'x'; + push(@drawOptions, "mark size=${mark_size}pt"); + } + } + + push(@drawOptions, 'smooth') if $data->style('tikz_smooth'); } - my $end_markers = ($start || $end) ? ", $start-$end" : ''; - $marks = $self->get_mark($marks); - $marks = $marks ? $mark_size ? ", mark=$marks, mark size=${mark_size}px" : ", mark=$marks" : ''; - - $linestyle =~ s/ /_/g; - $linestyle = { - none => ', only marks', - solid => ', solid', - dashed => ', dash={on 11pt off 8pt phase 6pt}', - short_dashes => ', dash pattern={on 6pt off 3pt}', - long_dashes => ', dash={on 20pt off 15pt phase 10pt}', - dotted => ', dotted', - long_medium_dashes => ', dash={on 20pt off 7pt on 11pt off 7pt phase 10pt}', - }->{$linestyle} - || ', solid'; - - if ($fill eq 'self') { - $fill = ", fill=$fill_color, fill opacity=$fill_opacity"; - } else { - $fill = ''; + + my $tikz_options = $data->style('tikz_options'); + + if ($drawFillSeparate) { + my $fill_color = $data->style('fill_color') || $data->style('color') || 'default_color'; + my $fill_opacity = $data->style('fill_opacity') || 0.5; + push(@fillOptions, 'draw=none', "fill=$fill_color", "fill opacity=$fill_opacity"); + push(@fillOptions, 'smooth') if $data->style('tikz_smooth'); + push(@fillOptions, $tikz_options) if $tikz_options; + } elsif ($fill eq 'self') { + if (!@drawOptions) { + push(@drawOptions, 'draw=none'); + $drawLayer = $fillLayer if defined $fillLayer; + } + my $fill_color = $data->style('fill_color') || $data->style('color') || 'default_color'; + my $fill_opacity = $data->style('fill_opacity') || 0.5; + push(@drawOptions, "fill=$fill_color", "fill opacity=$fill_opacity"); + } elsif (!@drawOptions) { + push(@drawOptions, 'draw=none'); } - $name = ", name path=$name" if $name; - return "${smooth}color=$color, line width=${width}pt$marks$linestyle$end_markers$fill$name$tikz_options"; + push(@drawOptions, $tikz_options) if $tikz_options; + + return ([ join(', ', @drawOptions), $drawLayer ], @fillOptions ? [ join(', ', @fillOptions), $fillLayer ] : undef); +} + +sub draw_on_layer { + my ($self, $plot, $layer) = @_; + my $tikzCode; + $tikzCode .= "\\begin{scope}[on layer=$layer]\\begin{pgfonlayer}{$layer}\\clip\\axisclippath;\n" if $layer; + $tikzCode .= $plot; + $tikzCode .= "\\end{pgfonlayer}\\end{scope}\n" if $layer; + return $tikzCode; } sub draw { - my $self = shift; - my $plots = $self->plots; - my $tikzFill = ''; + my $self = shift; + my $plots = $self->plots; # Reset colors just in case. $self->{colors} = {}; - # Add Axes - my $tikzCode = $self->configure_axes; + my $tikzCode = ''; + + # Plot data, vector/slope fields, and points. Note that points + # are in a separate data call so that they are drawn last. + for my $data ($plots->data('function', 'dataset', 'circle', 'arc', 'multipath', 'vectorfield'), + $plots->data('point')) + { + my $color = $data->style('color') || 'default_color'; + my $layer = $data->style('layer'); - # Plot Data - for my $data ($plots->data('function', 'dataset', 'circle', 'arc', 'multipath')) { - my $n = $data->size; - my $color = $data->style('color') || 'default_color'; - my $fill = $data->style('fill') || 'none'; - my $fill_color = $data->style('fill_color') || $data->style('color') || 'default_color'; - my $tikz_options = $self->get_plot_opts($data); $tikzCode .= $self->get_color($color); + + if ($data->name eq 'vectorfield') { + my $f = $data->{function}; + my $xfunction = $data->function_string($f->{Fx}, 'PGF', $f->{xvar}, $f->{yvar}); + my $yfunction = $data->function_string($f->{Fy}, 'PGF', $f->{xvar}, $f->{yvar}); + if ($xfunction ne '' && $yfunction ne '') { + my $width = $data->style('width'); + my $scale = $data->style('scale'); + my $arrows = $data->style('slopefield') ? '' : ', -stealth'; + my $tikz_options = $data->style('tikz_options') ? ', ' . $data->style('tikz_options') : ''; + $data->update_min_max; + + if ($data->style('normalize') || $data->style('slopefield')) { + my $xtmp = "($xfunction)/sqrt(($xfunction)^2 + ($yfunction)^2)"; + $yfunction = "($yfunction)/sqrt(($xfunction)^2 + ($yfunction)^2)"; + $xfunction = $xtmp; + } + + my $yDelta = ($f->{ymax} - $f->{ymin}) / $f->{ysteps}; + my $next = $f->{ymin} + $yDelta; + my $last = $f->{ymax} + $yDelta / 2; # Adjust upward incase of rounding error in the foreach. + my $xSamples = $f->{xsteps} + 1; + $tikzCode .= $self->draw_on_layer( + "\\foreach \\i in {$f->{ymin}, $next, ..., $last}\n" + . "\\addplot[color=$color, line width=${width}pt$arrows, " + . "quiver={u=$xfunction, v=$yfunction, scale arrows=$scale}, samples=$xSamples, " + . "domain=$f->{xmin}:$f->{xmax}$tikz_options] {\\i};\n", + $layer + ); + } else { + warn "Vector field not created due to missing PGF functions."; + } + next; + } + + my $curve_name = $data->style('name'); + warn 'Duplicate plot name detected. This will most likely cause issues. ' + . 'Make sure that all names used are unique.' + if $curve_name && $self->{names}{$curve_name}; + $self->{names}{$curve_name} = 1 if $curve_name; + + my $count = 0; + unless ($curve_name) { + ++$count while ($self->{names}{"_plots_internal_$count"}); + $curve_name = "_plots_internal_$count"; + $self->{names}{$curve_name} = 1; + } + + my $fill = $data->style('fill') || 'none'; + my $fill_color = $data->style('fill_color') || $data->style('color') || 'default_color'; $tikzCode .= $self->get_color($fill_color) unless $fill eq 'none'; + my ($draw_options, $fill_options) = $self->get_options($data); + if ($data->name eq 'circle') { my $x = $data->x(0); my $y = $data->y(0); my $r = $data->style('radius'); - $tikzCode .= "\\draw[$tikz_options] (axis cs:$x,$y) circle [radius=$r];\n"; + $tikzCode .= $self->draw_on_layer( + "\\draw[name path=$curve_name, $draw_options->[0]] (axis cs:$x,$y) circle[radius=$r];\n", + $draw_options->[1]); + $tikzCode .= + $self->draw_on_layer("\\fill[$fill_options->[0]] [spath/use=$curve_name];\n", $fill_options->[1]) + if $fill_options; next; } if ($data->name eq 'arc') { @@ -280,21 +568,32 @@ sub draw { my $r = sqrt(($x2 - $x1)**2 + ($y2 - $y1)**2); my $theta1 = 180 * atan2($y2 - $y1, $x2 - $x1) / 3.14159265358979; my $theta2 = 180 * atan2($y3 - $y1, $x3 - $x1) / 3.14159265358979; - $theta1 += 360 if $theta1 < 0; - $theta2 += 360 if $theta2 < 0; - $tikzCode .= "\\draw[$tikz_options] (axis cs:$x2,$y2) arc ($theta1:$theta2:$r);\n"; + $theta2 += 360 if $theta2 <= $theta1; + $tikzCode .= $self->draw_on_layer( + "\\draw[name path=$curve_name, $draw_options->[0]] (axis cs:$x2,$y2) " + . "arc[start angle=$theta1, end angle=$theta2, radius = $r];\n", + $draw_options->[1] + ); + $tikzCode .= + $self->draw_on_layer("\\fill[$fill_options->[0]] [spath/use=$curve_name];\n", $fill_options->[1]) + if $fill_options; next; } my $plot; + my $plot_options = ''; + if ($data->name eq 'function') { my $f = $data->{function}; if (ref($f->{Fx}) ne 'CODE' && $f->{xvar} eq $f->{Fx}->string) { my $function = $data->function_string($f->{Fy}, 'PGF', $f->{xvar}); if ($function ne '') { $data->update_min_max; - $tikz_options .= ", data cs=polar" if $data->style('polar'); - $tikz_options .= ", domain=$f->{xmin}:$f->{xmax}, samples=$f->{xsteps}"; + my ($axes_xmin, undef, $axes_xmax) = $plots->axes->bounds; + my $min = $data->style('continue') || $data->style('continue_left') ? $axes_xmin : $f->{xmin}; + my $max = $data->style('continue') || $data->style('continue_right') ? $axes_xmax : $f->{xmax}; + $plot_options .= ", data cs=polar" if $data->style('polar'); + $plot_options .= ", domain=$min:$max, samples=$f->{xsteps}"; $plot = "{$function}"; } } else { @@ -302,100 +601,136 @@ sub draw { my $yfunction = $data->function_string($f->{Fy}, 'PGF', $f->{xvar}); if ($xfunction ne '' && $yfunction ne '') { $data->update_min_max; - $tikz_options .= ", domain=$f->{xmin}:$f->{xmax}, samples=$f->{xsteps}"; + $plot_options .= ", domain=$f->{xmin}:$f->{xmax}, samples=$f->{xsteps}"; $plot = "({$xfunction}, {$yfunction})"; } } - } - if ($data->name eq 'multipath') { + } elsif ($data->name eq 'multipath') { my $var = $data->{function}{var}; my @paths = @{ $data->{paths} }; - my $n = scalar(@paths); my @tikzFunctionx; my @tikzFunctiony; + + # This saves the internal path names and the endpoints of the paths. The endpoints are used to determine if + # the paths meet at the endpoints. If the end of one path is not at the same place that the next path + # starts, then the line segment from the first path end to the next path start is inserted. + my @pathData; + + my $count = 0; + for (0 .. $#paths) { my $path = $paths[$_]; - my $a = $_ / $n; - my $b = ($_ + 1) / $n; - my $tmin = $path->{tmin}; - my $tmax = $path->{tmax}; - my $m = ($tmax - $tmin) / ($b - $a); - my $tmp = $a < 0 ? 'x+' . (-$a) : "x-$a"; - my $t = $m < 0 ? "($tmin$m*($tmp))" : "($tmin+$m*($tmp))"; - - my $xfunction = $data->function_string($path->{Fx}, 'PGF', $var, undef, $t); - my $yfunction = $data->function_string($path->{Fy}, 'PGF', $var, undef, $t); - my $last = $_ == $#paths ? '=' : ''; - push(@tikzFunctionx, "(x>=$a)*(x<$last$b)*($xfunction)"); - push(@tikzFunctiony, "(x>=$a)*(x<$last$b)*($yfunction)"); + + ++$count while $self->{names}{"${curve_name}_$count"}; + push(@pathData, ["${curve_name}_$count"]); + $self->{names}{ $pathData[-1][0] } = 1; + + if (ref $path eq 'ARRAY') { + $tikzCode .= + "\\addplot[name path=$pathData[-1][0], draw=none] coordinates {($path->[0], $path->[1])};\n"; + push(@{ $pathData[-1] }, @$path, @$path); + next; + } + + push( + @{ $pathData[-1] }, + $path->{Fx}->eval($var => $path->{tmin}), + $path->{Fy}->eval($var => $path->{tmin}), + $path->{Fx}->eval($var => $path->{tmax}), + $path->{Fy}->eval($var => $path->{tmax}) + ); + + my $xfunction = $data->function_string($path->{Fx}, 'PGF', $var); + my $yfunction = $data->function_string($path->{Fy}, 'PGF', $var); + + my $steps = $path->{steps} // $data->{function}{steps}; + + $tikzCode .= + "\\addplot[name path=$pathData[-1][0], draw=none, domain=$path->{tmin}:$path->{tmax}, " + . "samples=$steps] ({$xfunction}, {$yfunction});\n"; } - $tikz_options .= ", domain=0:1, samples=$data->{function}{steps}"; - $plot = "\n({" . join("\n+", @tikzFunctionx) . "},\n{" . join("\n+", @tikzFunctiony) . '})'; + + $tikzCode .= "\\path[name path=$curve_name] " . join( + ' ', + map { + ( + $_ == 0 || ($pathData[ $_ - 1 ][3] == $pathData[$_][1] + && $pathData[ $_ - 1 ][4] == $pathData[$_][2]) + ? '' + : "-- (spath cs:$pathData[$_ - 1][0] 1) -- (spath cs:$pathData[$_][0] 0) " + ) + . "[spath/append no move=$pathData[$_][0]]" + } 0 .. $#pathData + ) . ($data->style('cycle') ? '-- cycle' : '') . ";\n"; + + $plot = 'skip'; + $tikzCode .= + $self->draw_on_layer("\\draw[$draw_options->[0], spath/use=$curve_name];\n", $draw_options->[1]); } + unless ($plot) { $data->gen_data; - my $tikzData = join(' ', map { '(' . $data->x($_) . ',' . $data->y($_) . ')'; } (0 .. $n - 1)); - $plot = "coordinates {$tikzData}"; + $plot = 'coordinates {' + . join(' ', map { '(' . $data->x($_) . ',' . $data->y($_) . ')'; } (0 .. $data->size - 1)) . '}'; } - $tikzCode .= "\\addplot[$tikz_options] $plot;\n"; + + # 'skip' is a special value of $plot for a multipath which has already been drawn. + $tikzCode .= $self->draw_on_layer("\\addplot[name path=$curve_name, $draw_options->[0]$plot_options] $plot;\n", + $draw_options->[1]) + unless $plot eq 'skip'; + $tikzCode .= $self->draw_on_layer("\\fill[$fill_options->[0]] [spath/use=$curve_name];\n", $fill_options->[1]) + if $fill_options; unless ($fill eq 'none' || $fill eq 'self') { - my $name = $data->style('name'); - if ($name) { - my $opacity = $data->style('fill_opacity') || 0.5; - my $fill_min = $data->style('fill_min'); - my $fill_max = $data->style('fill_max'); - my $fill_range = $fill_min ne '' && $fill_max ne '' ? ", soft clip={domain=$fill_min:$fill_max}" : ''; - $opacity *= 100; - $tikzFill .= "\\addplot[$fill_color!$opacity] fill between[of=$name and $fill$fill_range];\n"; + if ($self->{names}{$fill}) { + # Make sure this is the name from the data style attribute, and not an internal name. + my $name = $data->style('name'); + if ($name) { + my $opacity = $data->style('fill_opacity') || 0.5; + my $fill_min = $data->style('fill_min'); + my $fill_max = $data->style('fill_max'); + my $fill_min_y = $data->style('fill_min_y'); + my $fill_max_y = $data->style('fill_max_y'); + my $fill_reverse = $data->style('fill_reverse'); + my $fill_range = + $fill_min ne '' && $fill_max ne '' && $fill_min_y ne '' && $fill_max_y ne '' + ? ", soft clip={($fill_min, $fill_min_y) rectangle ($fill_max, $fill_max_y)}" + : $fill_min ne '' && $fill_max ne '' ? ", soft clip={domain=$fill_min:$fill_max}" + : $fill_min_y ne '' && $fill_max_y ne '' ? ", soft clip={domain y=$fill_min_y:$fill_max_y}" + : ''; + my $fill_layer = $data->style('fill_layer') || $layer; + my $reverse = $fill_reverse eq '' ? '' : $fill_reverse ? ', reverse' : 'reverse=false'; + $tikzCode .= + "\\begin{scope}[/tikz/fill between/on layer=$fill_layer]\\begin{pgfonlayer}{$fill_layer}" + . "\\clip\\axisclippath;\n" + if $fill_layer; + $tikzCode .= + "\\addplot[$fill_color, fill opacity=$opacity] " + . "fill between[of=$name and $fill$fill_range$reverse];\n"; + $tikzCode .= "\\end{pgfonlayer}\\end{scope}\n" if $fill_layer; + } else { + warn q{Unable to create fill. Missing 'name' attribute.}; + } } else { - warn "Unable to create fill. Missing 'name' attribute."; + warn q{Unable to fill between curves. Other graph has not yet been drawn.}; } } } - # Add fills last to ensure all named graphs have been plotted first. - $tikzCode .= $tikzFill; - - # Vector/Slope Fields - for my $data ($plots->data('vectorfield')) { - my $f = $data->{function}; - my $xfunction = $data->function_string($f->{Fx}, 'PGF', $f->{xvar}, $f->{yvar}); - my $yfunction = $data->function_string($f->{Fy}, 'PGF', $f->{xvar}, $f->{yvar}); - my $arrows = $data->style('slopefield') ? '' : ', -stealth'; - if ($xfunction ne '' && $yfunction ne '') { - my $color = $data->style('color'); - my $width = $data->style('width'); - my $scale = $data->style('scale'); - my $tikz_options = $data->style('tikz_options') ? ', ' . $data->style('tikz_options') : ''; - $data->update_min_max; - - if ($data->style('normalize') || $data->style('slopefield')) { - my $xtmp = "($xfunction)/sqrt(($xfunction)^2 + ($yfunction)^2)"; - $yfunction = "($yfunction)/sqrt(($xfunction)^2 + ($yfunction)^2)"; - $xfunction = $xtmp; - } - - $tikzCode .= $self->get_color($color); - $tikzCode .= - "\\addplot3[color=$color, line width=${width}pt$arrows, " - . "quiver={u=$xfunction, v=$yfunction, scale arrows=$scale}, samples=$f->{xsteps}, " - . "domain=$f->{xmin}:$f->{xmax}, domain y=$f->{ymin}:$f->{ymax}$tikz_options] {1};\n"; - } else { - warn "Vector field not created due to missing PGF functions."; - } - } # Stamps for my $stamp ($plots->data('stamp')) { - my $mark = $self->get_mark($stamp->style('symbol')); + my $mark = $self->get_mark($stamp->style('symbol')) // '*'; next unless $mark; - my $color = $stamp->style('color') || 'default_color'; - my $x = $stamp->x(0); - my $y = $stamp->y(0); - my $r = $stamp->style('radius') || 4; - $tikzCode .= $self->get_color($color) - . "\\addplot[$color, mark=$mark, mark size=${r}pt, only marks] coordinates {($x,$y)};\n"; + my $color = $stamp->style('color') || 'default_color'; + my $x = $stamp->x(0); + my $y = $stamp->y(0); + my $lineWidth = $stamp->style('width') || 2; + my $r = ($stamp->style('radius') || 4) + ($mark =~ /^[*+]/ ? $lineWidth / 2 : $mark eq 'x' ? $lineWidth : 0); + $tikzCode .= + $self->get_color($color) + . "\\addplot[$color, mark=$mark, mark size=${r}pt, line width=${lineWidth}pt, only marks] " + . "coordinates {($x,$y)};\n"; } # Labels @@ -404,30 +739,38 @@ sub draw { my $x = $label->x(0); my $y = $label->y(0); my $color = $label->style('color') || 'default_color'; - my $fontsize = $label->style('fontsize') || 'medium'; + my $fontsize = $label->style('fontsize') || 'normalsize'; my $rotate = $label->style('rotate'); my $tikz_options = $label->style('tikz_options'); my $h_align = $label->style('h_align') || 'center'; my $v_align = $label->style('v_align') || 'middle'; - my $anchor = $v_align eq 'top' ? 'north' : $v_align eq 'bottom' ? 'south' : ''; + my $anchor = $label->style('anchor'); + $anchor = join(' ', + $v_align eq 'top' ? 'north' : $v_align eq 'bottom' ? 'south' : (), + $h_align eq 'left' ? 'west' : $h_align eq 'right' ? 'east' : ()) + if $anchor eq ''; + my $padding = $label->style('padding') || 4; $str = { - tiny => '\tiny ', - small => '\small ', - medium => '', - large => '\large ', - giant => '\Large ', + tiny => '\tiny ', + small => '\small ', + normalsize => '', + medium => '', # deprecated + large => '\large ', + Large => '\Large ', + giant => '\Large ', # deprecated + huge => '\huge ', + Huge => '\Huge ' }->{$fontsize} . $str; - $anchor .= $h_align eq 'left' ? ' west' : $h_align eq 'right' ? ' east' : ''; $tikz_options = $tikz_options ? "$color, $tikz_options" : $color; $tikz_options = "anchor=$anchor, $tikz_options" if $anchor; $tikz_options = "rotate=$rotate, $tikz_options" if $rotate; + $tikz_options = "inner sep=${padding}pt, $tikz_options"; $tikzCode .= $self->get_color($color) . "\\node[$tikz_options] at (axis cs: $x,$y) {$str};\n"; } - $tikzCode .= '\end{axis}'; - $plots->{tikzCode} = $tikzCode; - $self->im->tex($tikzCode); + $plots->{tikzCode} = $self->generate_axes($tikzCode); + $self->im->tex($plots->{tikzCode}); return $plots->{tikzDebug} ? '' : $self->im->draw; } diff --git a/lib/SampleProblemParser.pm b/lib/SampleProblemParser.pm deleted file mode 100644 index 30b5f8398b..0000000000 --- a/lib/SampleProblemParser.pm +++ /dev/null @@ -1,253 +0,0 @@ -package SampleProblemParser; -use parent qw(Exporter); - -use strict; -use warnings; -use experimental 'signatures'; -use feature 'say'; - -use File::Basename qw(dirname basename); -use File::Find qw(find); -use Pandoc; - -our @EXPORT_OK = qw(parseSampleProblem generateMetadata getSampleProblemCode); - -=head1 NAME - -SampleProblemParser - Parse the documentation in a sample problem in the /doc -directory. - -=head2 C - -Parse a PG file with extra documentation comments. The input is the file and a -hash of global variables: - -=over - -=item C: A reference to a hash which has information (name, directory, -types, subjects, categories) of every sample problem file. - -=item C: A reference to a hash of macros to include as links -within a problem. - -=item C: The root directory of the POD. - -=item C: The url of the pg_doc home. - -=item C: The html url extension (including the dot) to use for pg -doc links. The default is the empty string. - -=back - -=cut - -sub parseSampleProblem ($file, %global) { - my $filename = basename($file); - open(my $FH, '<:encoding(UTF-8)', $file) or do { - warn qq{Could not open file "$file": $!}; - return {}; - }; - my @file_contents = <$FH>; - close $FH; - - my (@blocks, @doc_rows, @code_rows, @description); - my (%options, $descr, $type, $name); - - $global{url_extension} //= ''; - - while (my $row = shift @file_contents) { - chomp($row); - $row =~ s/\t/ /g; - if ($row =~ /^#:%\s*(categor(y|ies)|types?|subjects?|see_also|name)\s*=\s*(.*)\s*$/) { - # skip this, already parsed. - } elsif ($row =~ /^#:%\s*(.*)?/) { - # The row has the form #:% section = NAME. - # This should parse the previous named section and then reset @doc_rows and @code_rows. - push( - @blocks, - { - %options, - doc => pandoc->convert(markdown => 'html', join("\n", @doc_rows)), - code => join("\n", @code_rows) - } - ) if %options; - %options = split(/\s*:\s*|\s*,\s*|\s*=\s*|\s+/, $1); - @doc_rows = (); - @code_rows = (); - } elsif ($row =~ /^#:/) { - # This section is documentation to be parsed. - $row = $row =~ s/^#:\s?//r; - - # Parse any LINK/PODLINK/PROBLINK commands in the documentation. - if ($row =~ /(POD|PROB)?LINK\('(.*?)'\s*(,\s*'(.*)')?\)/) { - my $link_text = defined($1) ? $1 eq 'POD' ? $2 : $global{metadata}{$2}{name} : $2; - my $url = - defined($1) - ? $1 eq 'POD' - ? "$global{pod_root}/" . $global{macro_locations}{ $4 // $2 } - : "$global{pg_doc_home}/$global{metadata}{$2}{dir}/" . ($2 =~ s/.pg$/$global{url_extension}/r) - : $4; - $row = $row =~ s/(POD|PROB)?LINK\('(.*?)'\s*(,\s*'(.*)')?\)/[$link_text]($url)/gr; - } - - push(@doc_rows, $row); - } elsif ($row =~ /^##\s*(END)?DESCRIPTION\s*$/) { - $descr = $1 ? 0 : 1; - } elsif ($row =~ /^##/ && $descr) { - push(@description, $row =~ s/^##\s*//r); - push(@code_rows, $row); - } else { - push(@code_rows, $row); - } - } - - # The last @doc_rows must be parsed then added to the @blocks. - push( - @blocks, - { - %options, - doc => pandoc->convert(markdown => 'html', join("\n", @doc_rows)), - code => join("\n", @code_rows) - } - ); - - return { - name => $global{metadata}{$filename}{name}, - blocks => \@blocks, - code => join("\n", map { $_->{code} } @blocks), - description => join("\n", @description) - }; -} - -=head2 C - -Build a hash of metadata for all PG files in the given directory. A reference -to the hash that is built is returned. - -=cut - -sub generateMetadata ($problem_dir, %options) { - my $index_table = {}; - - find( - { - wanted => sub { - say "Reading file: $File::Find::name" if $options{verbose}; - - if ($File::Find::name =~ /\.pg$/) { - my $metadata = parseMetadata($File::Find::name, $problem_dir); - unless (@{ $metadata->{types} }) { - warn "The type of sample problem is missing for $File::Find::name."; - return; - } - unless ($metadata->{name}) { - warn "The name attribute is missing for $File::Find::name."; - return; - } - $index_table->{ basename($File::Find::name) } = $metadata; - } - } - }, - $problem_dir - ); - - return $index_table; -} - -my @macros_to_skip = qw( - PGML.pl - PGcourse.pl - PGstandard.pl -); - -sub parseMetadata ($path, $problem_dir) { - open(my $FH, '<:encoding(UTF-8)', $path) or do { - warn qq{Could not open file "$path": $!}; - return {}; - }; - my @file_contents = <$FH>; - close $FH; - - my @problem_types = qw(sample technique snippet); - - my $metadata = { dir => (dirname($path) =~ s/$problem_dir\/?//r) =~ s/\/*$//r }; - - while (my $row = shift @file_contents) { - if ($row =~ /^#:%\s*(categor(y|ies)|types?|subjects?|see_also|name)\s*=\s*(.*)\s*$/) { - # The row has the form #:% categories = [cat1, cat2, ...]. - my $label = lc($1); - my @opts = $3 =~ /\[(.*)\]/ ? map { $_ =~ s/^\s*|\s*$//r } split(/,/, $1) : ($3); - if ($label =~ /types?/) { - for my $opt (@opts) { - warn "The type of problem must be one of @problem_types" - unless grep { lc($opt) eq $_ } @problem_types; - } - $metadata->{types} = [ map { lc($_) } @opts ]; - } elsif ($label =~ /^categor/) { - $metadata->{categories} = \@opts; - } elsif ($label =~ /^subject/) { - $metadata->{subjects} = [ map { lc($_) } @opts ]; - } elsif ($label eq 'name') { - $metadata->{name} = $opts[0]; - } elsif ($label eq 'see_also') { - $metadata->{related} = \@opts; - } - } elsif ($row =~ /loadMacros\(/) { - chomp($row); - # Parse the macros, which may be on multiple rows. - my $macros = $row; - while ($row && $row !~ /\);\s*$/) { - $row = shift @file_contents; - chomp($row); - $macros .= $row; - } - # Split by commas and pull out the quotes. - my @macros = map {s/['"\s]//gr} split(/\s*,\s*/, $macros =~ s/loadMacros\((.*)\)\;$/$1/r); - $metadata->{macros} = []; - for my $macro (@macros) { - push(@{ $metadata->{macros} }, $macro) unless grep { $_ eq $macro } @macros_to_skip; - } - } - } - - return $metadata; -} - -=head2 C - -Parse a PG file with extra documentation comments and strip that all out -returning the clean problem code. This returns the same code that the -C returns, except at much less expense as it does not parse -the documentation, it does not require that the metadata be parsed first, and it -does not need macro POD information. - -=cut - -sub getSampleProblemCode ($file) { - my $filename = basename($file); - open(my $FH, '<:encoding(UTF-8)', $file) or do { - warn qq{Could not open file "$file": $!}; - return ''; - }; - my @file_contents = <$FH>; - close $FH; - - my (@code_rows, $inCode); - - while (my $row = shift @file_contents) { - chomp($row); - $row =~ s/\t/ /g; - if ($row =~ /^#:(.*)?/) { - # This is documentation so skip it. - } elsif ($row =~ /^\s*(END)?DOCUMENT.*$/) { - $inCode = $1 ? 0 : 1; - push(@code_rows, $row); - } elsif ($inCode) { - push(@code_rows, $row); - } - } - - return join("\n", @code_rows); -} - -1; diff --git a/lib/Value/AnswerChecker.pm b/lib/Value/AnswerChecker.pm index fb4dacb06c..3ed4f18535 100644 --- a/lib/Value/AnswerChecker.pm +++ b/lib/Value/AnswerChecker.pm @@ -555,57 +555,57 @@ sub format_matrix_HTML { my ($rows, $cols) = (scalar(@{$array}), scalar(@{ $array->[0] })); my $HTML = ""; my $class = 'class="ans_array_cell"'; - my $cell = "display:table-cell;vertical-align:middle;"; + my $cell = "display:table-cell;vertical-align:middle;text-align:center;"; my $pad = "padding:4px 0;"; - if ($sep) { $sep = '' . $sep . '' } - else { $sep = '' } - $sep = '' . $sep . ''; + if ($sep) { $sep = '
    ' . $sep . '
    ' } + else { $sep = '
    ' } + $sep = '
    ' . $sep . '
    '; if ($options{top_labels}) { $HTML .= - '
    ' . join($sep, @{ $options{top_labels} }) - . ''; + . '
    '; } foreach my $i (0 .. $rows - 1) { $HTML .= - '
    ' . join($sep, EVALUATE(@{ $array->[$i] })) - . ''; + . '
    '; } - $HTML = '' . $HTML . ''; + $HTML = '
    ' . $HTML . '
    '; $open = $self->format_delimiter($open, $rows, $options{tth_delims}); $close = $self->format_delimiter($close, $rows, $options{tth_delims}); if ($open ne '' || $close ne '') { my $delim = "display:inline-block; vertical-align:middle;"; $HTML = - '' . $open - . '' + . '
    ' . $HTML - . '' . $close - . ''; + . ''; } - return '' . $HTML - . ''; + . ''; } sub EVALUATE { @@ -1333,7 +1333,7 @@ sub cmp_compare { } # -# Check for wrong enpoints and wrong type of endpoints +# Check for wrong endpoints and wrong type of endpoints # sub cmp_postprocess { my $self = shift; diff --git a/lib/Value/Matrix.pm b/lib/Value/Matrix.pm index 18b7498153..12a36bf495 100644 --- a/lib/Value/Matrix.pm +++ b/lib/Value/Matrix.pm @@ -12,11 +12,11 @@ This is the Math Object code for a Matrix. =over -=item L +=item L -=item L +=item L -=item L +=item L =back @@ -117,7 +117,7 @@ Examples: =head3 Update values (these need to be added) - see C in MatrixReduce and L + see C in MatrixReduce and L =head3 Advanced @@ -174,7 +174,7 @@ One can use fractions in Matrices by including C. For exam and operations will be done using rational arithmetic. Also helpful is the method C in the L macro. Some additional information can be -found at L. +found at L. =head2 methods diff --git a/lib/WeBWorK/PG.pm b/lib/WeBWorK/PG.pm index 594abb3a1b..cef9335687 100644 --- a/lib/WeBWorK/PG.pm +++ b/lib/WeBWorK/PG.pm @@ -115,7 +115,7 @@ sub new_helper ($invocant, %options) { translator => $translator, head_text => '', post_header_text => '', - body_text => "Unabled to read problem source file:\n$@\n", + body_text => "Unable to read problem source file:\n$@\n", answers => {}, result => {}, state => {}, @@ -160,19 +160,12 @@ sub new_helper ($invocant, %options) { ); } - # HTML_dpng uses an ImageGenerator. We have to render the queued equations. This must be done before the post - # processing, since the image tags output by the image generator initially include markers which are invalid html. - # Mojo::DOM will change these markers into attributes with values and this will fail. - if ($image_generator) { - $image_generator->render( - refresh => $options{refreshMath2img} // 0, - body_text => $translator->r_text, - ); - } - $translator->post_process_content if ref($translator->{rh_pgcore}) eq 'PGcore'; $translator->stringify_answers; + $image_generator->render(body_text => $translator->r_text, refresh => $options{refreshMath2img} // 0) + if $image_generator; + # Add the result summary set in post processing into the result. $result->{summary} = $translator->{rh_pgcore}{result_summary} if ref($translator->{rh_pgcore}) eq 'PGcore' diff --git a/lib/WeBWorK/PG/Constants.pm b/lib/WeBWorK/PG/Constants.pm index 7e83e511b3..835687d9f1 100644 --- a/lib/WeBWorK/PG/Constants.pm +++ b/lib/WeBWorK/PG/Constants.pm @@ -11,7 +11,7 @@ use warnings; # ImageGenerator -# Arguments to pass to dvipng. This is dependant on the version of dvipng. +# Arguments to pass to dvipng. This is dependent on the version of dvipng. # # For dvipng versions 0.x # $ImageGenerator::DvipngArgs = "-x4000.5 -bgTransparent -Q6 -mode toshiba -D180"; diff --git a/lib/WeBWorK/PG/Critic.pm b/lib/WeBWorK/PG/Critic.pm new file mode 100644 index 0000000000..0a656d494c --- /dev/null +++ b/lib/WeBWorK/PG/Critic.pm @@ -0,0 +1,94 @@ + +=head1 NAME + +WeBWorK::PG::Critic - Critique PG problem source code for best-practices. + +=head1 DESCRIPTION + +Analyze a pg file for use of old and current methods. + +=head1 FUNCTIONS + +=head2 critiquePGCode + + my @violations = critiquePGCode($code, $force = 0); + +Parses and critiques the given PG problem source provided in C<$code>. An array +of violations that are found is returned. Note that the elements of this return +array are L objects. The C method can be +called for each element, and that will either return a string or a reference to +a hash. The string return type will occur for a violation of a default +L policy. The last return type will occur with a +C policy, and the hash will contain a C key and +an C key containing the actual explanation. Note that the greater +the score, the worse the violation is. In some cases the C return +hash will also contain the key C which will be a reference to an +array each of whose entries will be a reference to a two element array whose +first element is the title of a sample problem and whose second element is the +path for that sample problem where the sample problem demonstrates a way to fix +the policy violation. + +Note that C<## no critic> annotations can be used in the code to disable a +violation for a line or the entire file. See L<"BENDING THE +RULES"|https://metacpan.org/pod/Perl::Critic#BENDING-THE-RULES>. However, if +C<$force> is true, then C<## no critic> annotations are ignored, and all +policies are enforced regardless. + +=head2 critiquePGFile + + my @violations = critiquePGFile($file, $force); + +This just executes C on the contents of C<$file> and returns +the violations found. +=cut + +package WeBWorK::PG::Critic; +use Mojo::Base 'Exporter', -signatures; + +use Mojo::File qw(path); +use PPI; +use Perl::Critic; + +require WeBWorK::PG::Translator; + +our @EXPORT_OK = qw(critiquePGFile critiquePGCode); + +sub critiquePGCode ($code, $force = 0) { + my $critic = Perl::Critic->new( + -severity => 4, + -exclude => [ + 'Perl::Critic::Policy::Modules::ProhibitMultiplePackages', + 'Perl::Critic::Policy::Modules::RequireEndWithOne', + 'Perl::Critic::Policy::Modules::RequireExplicitPackage', + 'Perl::Critic::Policy::Modules::RequireFilenameMatchesPackage', + 'Perl::Critic::Policy::Subroutines::ProhibitBuiltinHomonyms', + 'Perl::Critic::Policy::Subroutines::RequireArgUnpacking', + 'Perl::Critic::Policy::TestingAndDebugging::RequireUseStrict', + 'Perl::Critic::Policy::TestingAndDebugging::RequireUseWarnings', + 'Perl::Critic::Policy::Variables::RequireLocalizedPunctuationVars', + + # Make sure that Community and Freenode policies are not used if installed on the system. + '.*::Community::.*', '.*::Freenode::.*' + ], + -force => $force + ); + + my $translatedCode = WeBWorK::PG::Translator::default_preprocess_code($code); + + my $document = PPI::Document->new(\$translatedCode); + + # Provide the untranslated code so that policies can access it. It will be in the _doc key of the $document that is + # passed as the third argument to the violates method. See Perl::Critic::Policy::PG::ProhibitEnddocumentMatter which + # uses this for example. + $document->{untranslatedCode} = $code; + + return $critic->critique($document); +} + +sub critiquePGFile ($file, $force = 0) { + my $code = eval { path($file)->slurp('UTF-8') }; + die qq{Unable to read contents of "$file": $@} if $@; + return critiquePGCode($code, $force); +} + +1; diff --git a/lib/WeBWorK/PG/Critic/Utils.pm b/lib/WeBWorK/PG/Critic/Utils.pm new file mode 100644 index 0000000000..425233aeaf --- /dev/null +++ b/lib/WeBWorK/PG/Critic/Utils.pm @@ -0,0 +1,153 @@ + +=head1 NAME + +WeBWorK::PG::Critic::Utils - Utility methods for PG Critic policies. + +=head1 DESCRIPTION + +Utility methods for PG Critic policies. + +=head1 FUNCTIONS + +=head2 getDeprecatedMacros + + my @deprecatedMacros = getDeprecatedMacros(); + +Returns a list of deprecated macros. These are the macros found in the +C directory. + +=head2 parsePGMLBlock + + my $pgmlElements = parsePGMLBlock(@lines); + +Parses the given C<@lines> of code from a PGML block and returns a reference to +a hash containing details of the PGML blocks found. + +If any C<[@ ... @]> blocks are found in the PGML code, then the return hash will +contain the key C which will be a reference to an array of +L objects representing the Perl code within the C<[@ ... @]> +blocks found. + +If the PGML content or a block within fails to parse, then the return hash will +contain the key C with a reference to an array of errors that occurred. + +Also if there are any warnings that occur in the parsing, those will be in the +C key of the return hash. + +=head2 parseTextBlock + + my $textElements = parseTextBlock(@lines); + +Parses the given C<@lines> of code from a C/C, +C/C, or C/C block and +returns a reference to a hash containing details of the elements found. + +If any C<\{ ... \}> blocks are found in the code, then the return hash will +contain the key C which will be a reference to an array of +L objects representing the Perl code within the C<\{ ... \}> +blocks found. + +If a block within fails to parse, then the return hash will contain the key +C which is a reference to an array of errors that occurred. + +=cut + +package WeBWorK::PG::Critic::Utils; +use Mojo::Base 'Exporter', -signatures; + +use Mojo::File qw(curfile path); +use PPI; +use Perl::Critic::Utils qw(:classification :ppi); +use Scalar::Util qw(blessed); +use Mojo::Util qw(md5_sum encode); +use Env qw(PG_ROOT); + +use lib curfile->dirname->dirname->dirname->dirname->dirname->child('lib'); + +require Value; + +our @EXPORT_OK = qw(getDeprecatedMacros parsePGMLBlock parseTextBlock); + +$PG_ROOT = curfile->dirname->dirname->dirname->dirname->dirname; + +sub getDeprecatedMacros () { + state $deprecatedMacros; + return $deprecatedMacros if $deprecatedMacros; + return $deprecatedMacros = + { map { $_->basename => 1 } @{ path($PG_ROOT)->child('macros', 'deprecated')->list } }; +} + +# Mock methods used by PGML. +sub main::PG_restricted_eval ($code) { return $code; } +sub main::loadMacros(@macros) { return; } +sub main::Context() { return; } + +do "$PG_ROOT/macros/core/PGML.pl"; + +sub walkPGMLTree ($block, $results //= {}) { + for my $item (@{ $block->{stack} }) { + next unless blessed $item && $item->isa('PGML::Block'); + if ($item->{type} eq 'command') { + my $command = PPI::Document->new(\($item->{text})); + if ($command->errstr) { + push(@{ $results->{errors} }, $command->errstr); + } else { + push(@{ $results->{commands} }, $command); + } + } + walkPGMLTree($item, $results); + } + return $results; +} + +# For now, only command blocks are returned. Add other PGML elements as needed. +sub parsePGMLBlock (@lines) { + state %processedBlocks; + + my $source = join('', @lines); + + # Cache the results of parsing particular PGML blocks so that if multiple policies + # use the same PGML block the parsing does not need to be done again. + my $sourceHash = md5_sum(encode('UTF-8', $source)); + return $processedBlocks{$sourceHash} if defined $processedBlocks{$sourceHash}; + + PGML::ClearWarnings(); + my $parser = eval { PGML::Parse->new($source =~ s/\\\\/\\/gr) }; + return { errors => [$@], warnings => \@PGML::warnings } if $@; + + return $processedBlocks{$sourceHash} = + WeBWorK::PG::Critic::Utils::walkPGMLTree($parser->{root}, { warnings => \@PGML::warnings }); +} + +# For now, only contents of \{ .. \} blocks are returned. Add other text elements as needed. +sub parseTextBlock (@lines) { + state %processedBlocks; + + my $source = join('', @lines); + + # Cache the results of parsing particular text blocks so that if multiple policies + # use the same text block the parsing does not need to be done again. + my $sourceHash = md5_sum(encode('UTF-8', $source)); + return $processedBlocks{$sourceHash} if defined $processedBlocks{$sourceHash}; + + my $results = {}; + + while ($source ne '') { + if ($source =~ /\Q\\{\E/s) { + $source =~ s/^(.*?)\Q\\{\E//s; + $source =~ s/^(.*?)\Q\\}\E//s; + my $command = PPI::Document->new(\($1)); + if ($command->errstr) { + push(@{ $results->{errors} }, $command->errstr); + } else { + push(@{ $results->{commands} }, $command); + } + } else { + last; + } + + } + + return $processedBlocks{$sourceHash} = $results; +} +1; diff --git a/lib/WeBWorK/PG/IO.pm b/lib/WeBWorK/PG/IO.pm index e1134b1f9a..6e944db2ff 100644 --- a/lib/WeBWorK/PG/IO.pm +++ b/lib/WeBWorK/PG/IO.pm @@ -38,9 +38,8 @@ our $pg_envir; =head2 includePGtext -This is used in processing some of the sample CAPA files and in creating aliases -to redirect calls to duplicate problems so that they go to the original problem -instead. It is called by includePGproblem. +This is used in creating aliases to redirect calls to duplicate problems so that +they go to the original problem instead. It is called by includePGproblem. Usage: C @@ -77,9 +76,8 @@ Read the contents of a pg file. Usage: C Don't use for huge files. The file name will have .pg appended to it if it -doesn't already end in .pg. This is used in importing additional .pg files as -is done in the sample problems translated from CAPA. Returns a reference to a -string containing the contents of the file. +doesn't already end in .pg. This is used in importing additional .pg files. +Returns a reference to a string containing the contents of the file. =cut diff --git a/lib/WeBWorK/PG/ImageGenerator.pm b/lib/WeBWorK/PG/ImageGenerator.pm index a5d09c86b3..d30424af26 100644 --- a/lib/WeBWorK/PG/ImageGenerator.pm +++ b/lib/WeBWorK/PG/ImageGenerator.pm @@ -250,8 +250,8 @@ sub add ($self, $string, $mode = 'inline') { # Determine what the image's "number" is. if ($useCache) { $imageNum = $self->{equationCache}->lookup($realString); - $aligntag = 'MaRkEr' . $imageNum if $self->{useMarkers}; - $depths->{$imageNum} = 'none' if $self->{store_depths}; + $aligntag = qq{data-imagegen-alignment-marker="$imageNum"} if $self->{useMarkers}; + $depths->{$imageNum} = 'none' if $self->{store_depths}; # Insert a slash after 2 characters. This effectively divides the images into 16^2 = 256 subdirectories. substr($imageNum, 2, 0) = '/'; } else { @@ -461,11 +461,16 @@ sub fix_markers ($self) { my %depths = %{ $self->{depths} }; for my $depthkey (keys %depths) { + # The data-imagegen-alignment-marker value may be quoted with double quotes or with " if the image is + # inside another HTML element attribute (such as for images in the feedback button). So both quote types need + # to be checked, and the replaced style attribute needs to use the same quoting that it comes in with. if ($depths{$depthkey} eq 'none') { - ${ $self->{body_text} } =~ s/MaRkEr$depthkey/style="vertical-align:$self->{dvipng_align}"/g; + ${ $self->{body_text} } =~ + s/data-imagegen-alignment-marker=("|")$depthkey\1/style=$1vertical-align:$self->{dvipng_align}$1/g; } else { my $ndepth = 0 - $depths{$depthkey}; - ${ $self->{body_text} } =~ s/MaRkEr$depthkey/style="vertical-align:${ndepth}px"/g; + ${ $self->{body_text} } =~ + s/data-imagegen-alignment-marker=("|")$depthkey\1/style=$1vertical-align:${ndepth}px$1/g; } } return; diff --git a/lib/WeBWorK/PG/Localize/pg.pot b/lib/WeBWorK/PG/Localize/pg.pot index f7d4fdab2b..42d57b429e 100644 --- a/lib/WeBWorK/PG/Localize/pg.pot +++ b/lib/WeBWorK/PG/Localize/pg.pot @@ -15,60 +15,64 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1455 +#: /opt/webwork/pg/macros/PG.pl:1456 msgid "%1 of the answers %plural(%1,has,have) been graded." msgstr "" #. (@answerNames - $numBlank - $numCorrect - $numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1427 +#: /opt/webwork/pg/macros/PG.pl:1428 msgid "%1 of the answers %plural(%1,is,are) NOT correct." msgstr "" #. ($numManuallyGraded) -#: /opt/webwork/pg/macros/PG.pl:1451 +#: /opt/webwork/pg/macros/PG.pl:1452 msgid "%1 of the answers will be graded later." msgstr "" #. ($options{resultTitle}) -#: /opt/webwork/pg/macros/PG.pl:1224 +#: /opt/webwork/pg/macros/PG.pl:1225 msgid "%1 with message" msgstr "" #. (round($answerScore * 100) -#: /opt/webwork/pg/macros/PG.pl:1117 +#: /opt/webwork/pg/macros/PG.pl:1118 msgid "%1% correct" msgstr "" #. ($numBlank) -#: /opt/webwork/pg/macros/PG.pl:1440 +#: /opt/webwork/pg/macros/PG.pl:1441 msgid "%quant(%1,of the questions remains,of the questions remain) unanswered." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1414 +#: /opt/webwork/pg/macros/PG.pl:1415 msgid "All of the answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1405 +#: /opt/webwork/pg/macros/PG.pl:1406 msgid "All of the computer gradable answers are correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1060 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1260 +msgid "An edge traversed by this path does not exist in the graph." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1061 msgid "Answer Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1240 /opt/webwork/pg/macros/PG.pl:1316 +#: /opt/webwork/pg/macros/PG.pl:1241 /opt/webwork/pg/macros/PG.pl:1317 msgid "Close" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2894 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2908 msgid "Close image description" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1109 +#: /opt/webwork/pg/macros/PG.pl:1110 msgid "Correct" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1322 +#: /opt/webwork/pg/macros/PG.pl:1323 msgid "Correct Answer" msgstr "" @@ -96,11 +100,11 @@ msgstr "" msgid "Hardcopy will always print the original version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1302 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1305 msgid "Hint" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1301 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1304 msgid "Hint:" msgstr "" @@ -108,7 +112,7 @@ msgstr "" msgid "If you come back to it later, it may revert to its original version." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1113 +#: /opt/webwork/pg/macros/PG.pl:1114 msgid "Incorrect" msgstr "" @@ -120,7 +124,7 @@ msgstr "" msgid "Preview" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1285 +#: /opt/webwork/pg/macros/PG.pl:1286 msgid "Preview of Your Answer" msgstr "" @@ -128,7 +132,11 @@ msgstr "" msgid "Quick Entry" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1328 +#: /opt/webwork/pg/macros/graph/AppletObjects.pl:115 +msgid "Return this question to its initial state" +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1329 msgid "Reveal" msgstr "" @@ -136,11 +144,11 @@ msgstr "" msgid "Set random seed to:" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1294 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1297 msgid "Solution" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1293 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:1296 msgid "Solution:" msgstr "" @@ -148,27 +156,31 @@ msgstr "" msgid "Submit your answers again to go on to the next part." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1375 +#: /opt/webwork/pg/macros/PG.pl:1376 msgid "The answer has been graded." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1393 +#: /opt/webwork/pg/macros/PG.pl:1394 msgid "The answer is NOT correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1364 +#: /opt/webwork/pg/macros/PG.pl:1365 msgid "The answer is correct." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1374 +#: /opt/webwork/pg/macros/PG.pl:1375 msgid "The answer will be graded later." msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1384 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1221 +msgid "The degrees of the vertices in this graph are not all even." +msgstr "" + +#: /opt/webwork/pg/macros/PG.pl:1385 msgid "The question has not been answered." msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1113 /opt/webwork/pg/macros/core/PGanswermacros.pl:1673 +#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1110 /opt/webwork/pg/macros/core/PGanswermacros.pl:1671 msgid "This answer was marked correct because the primary answer is correct." msgstr "" @@ -176,11 +188,31 @@ msgstr "" msgid "This answer will be graded at a later time." msgstr "" +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1244 +msgid "This graph does not have two vertices of odd degree and all other vertices of even degree." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1313 /opt/webwork/pg/macros/math/SimpleGraph.pl:1320 +msgid "This graph has a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1218 /opt/webwork/pg/macros/math/SimpleGraph.pl:1231 /opt/webwork/pg/macros/math/SimpleGraph.pl:1314 +msgid "This graph is not connected." +msgstr "" + #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:409 msgid "This is a new (re-randomized) version of the problem." msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3086 +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1268 +msgid "This path does not traverse all edges." +msgstr "" + +#: /opt/webwork/pg/macros/math/SimpleGraph.pl:1280 +msgid "This path is not a circuit." +msgstr "" + +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3100 msgid "This problem contains a video which must be viewed online." msgstr "" @@ -196,14 +228,10 @@ msgstr "" msgid "Ungraded" msgstr "" -#: /opt/webwork/pg/macros/PG.pl:1280 +#: /opt/webwork/pg/macros/PG.pl:1281 msgid "You Entered" msgstr "" -#: /opt/webwork/pg/lib/WeBWorK/PG/Translator.pm:1080 /opt/webwork/pg/macros/core/PGanswermacros.pl:1641 -msgid "You can earn partial credit on this problem." -msgstr "" - #: /opt/webwork/pg/macros/deprecated/problemRandomize.pl:383 msgid "You can get a new version of this problem after the due date." msgstr "" @@ -212,7 +240,7 @@ msgstr "" msgid "You may not change your answers when going on to the next part!" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3079 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:3093 msgid "Your browser does not support the video tag." msgstr "" @@ -222,11 +250,11 @@ msgstr "" #. ($name) #. ($1) -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:623 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:528 /opt/webwork/pg/macros/core/PGbasicmacros.pl:539 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:634 msgid "answer %1 " msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2964 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2978 msgid "end image description" msgstr "" @@ -237,43 +265,43 @@ msgstr "" msgid "i" msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:826 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:818 msgid "if" msgstr "" -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2879 /opt/webwork/pg/macros/core/PGbasicmacros.pl:2962 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:2893 /opt/webwork/pg/macros/core/PGbasicmacros.pl:2976 msgid "image description" msgstr "" #. (1) #. ($count) #. ($i + 1) -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:556 /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:565 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:611 /opt/webwork/pg/macros/core/PGbasicmacros.pl:665 /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:558 /opt/webwork/pg/macros/parsers/parserCheckboxList.pl:567 msgid "option %1 " msgstr "" -#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:829 +#: /opt/webwork/pg/macros/contexts/contextPiecewiseFunction.pl:821 msgid "otherwise" msgstr "" #. ($2 + 1) #. ($radioIndex) -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:625 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:545 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:636 msgid "part %1 " msgstr "" #. ($1) -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:618 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:534 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:629 msgid "problem %1 " msgstr "" #. ($1 + 1, $2 + 1) -#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:631 +#: /opt/webwork/pg/macros/core/PGbasicmacros.pl:551 /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:642 msgid "row %1 column %2 " msgstr "" #. ($partIndex) -#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:626 +#: /opt/webwork/pg/macros/parsers/parserRadioMultiAnswer.pl:637 msgid "subpart %1 " msgstr "" diff --git a/lib/WeBWorK/PG/SampleProblemParser.pm b/lib/WeBWorK/PG/SampleProblemParser.pm new file mode 100644 index 0000000000..42fd73ffc3 --- /dev/null +++ b/lib/WeBWorK/PG/SampleProblemParser.pm @@ -0,0 +1,474 @@ +package WeBWorK::PG::SampleProblemParser; +use parent qw(Exporter); + +use strict; +use warnings; +use experimental 'signatures'; +use feature 'say'; + +my $pgRoot; + +use Mojo::File qw(curfile); +BEGIN { $pgRoot = curfile->dirname->dirname->dirname->dirname; } + +use File::Basename qw(dirname basename); +use File::Find qw(find); +use Mojo::File qw(path); +use Mojo::JSON qw(decode_json encode_json); +use Pandoc; +use Pod::Simple::Search; +use Pod::Simple::SimpleTree; + +our @EXPORT_OK = qw(parseSampleProblem generateMetadata getSampleProblemCode getSearchData); + +=head1 NAME + +WeBWorK::PG::SampleProblemParser - Parse sample problems and extract metadata, +documentation, and code. + +=head2 parseSampleProblem + +Parse a PG file with extra documentation comments. The input is the file and a +hash of global variables: + +=over + +=item * + +C: A reference to a hash which has information (name, directory, +types, subjects, categories) of every sample problem file. + +=item * + +C: A reference to a hash of macros to include as links within a +problem. + +=item * + +C: The base URL for the POD HTML files. + +=item * + +C: The base URL for the sample problem HTML files. + +=item * + +C: The html url extension (including the dot) to use for pg doc +links. The default is the empty string. + +=back + +=cut + +sub parseSampleProblem ($file, %global) { + my $filename = basename($file); + open(my $FH, '<:encoding(UTF-8)', $file) or do { + warn qq{Could not open file "$file": $!}; + return {}; + }; + my @file_contents = <$FH>; + close $FH; + + my (@blocks, @doc_rows, @code_rows, @description); + my (%options, $descr, $type, $name); + + $global{url_extension} //= ''; + + while (my $row = shift @file_contents) { + chomp($row); + $row =~ s/\t/ /g; + if ($row =~ /^#:%\s*(categor(y|ies)|types?|subjects?|see_also|name)\s*=\s*(.*)\s*$/) { + # skip this, already parsed. + } elsif ($row =~ /^#:%\s*(.*)?/) { + # The row has the form #:% section = NAME. + # This should parse the previous named section and then reset @doc_rows and @code_rows. + push( + @blocks, + { + %options, + doc => pandoc->convert(markdown => 'html', join("\n", @doc_rows)), + code => join("\n", @code_rows) + } + ) if %options; + %options = split(/\s*:\s*|\s*,\s*|\s*=\s*|\s+/, $1); + @doc_rows = (); + @code_rows = (); + } elsif ($row =~ /^#:/) { + # This section is documentation to be parsed. + $row = $row =~ s/^#:\s?//r; + + # Parse any LINK/PODLINK/PROBLINK commands in the documentation. + if ($row =~ /(POD|PROB)?LINK\('(.*?)'\s*(,\s*'(.*)')?\)/) { + my $link_text = defined($1) ? $1 eq 'POD' ? $2 : $global{metadata}{$2}{name} : $2; + my $url = + defined($1) + ? $1 eq 'POD' + ? "$global{pod_base_url}/" . $global{macro_locations}{ $4 // $2 } + : "$global{sample_problem_base_url}/$global{metadata}{$2}{dir}/" + . ($2 =~ s/.pg$/$global{url_extension}/r) + : $4; + $row = $row =~ s/(POD|PROB)?LINK\('(.*?)'\s*(,\s*'(.*)')?\)/[$link_text]($url)/gr; + } + + push(@doc_rows, $row); + } elsif ($row =~ /^##\s*(END)?DESCRIPTION\s*$/) { + $descr = $1 ? 0 : 1; + } elsif ($row =~ /^##/ && $descr) { + push(@description, $row =~ s/^##\s*//r); + push(@code_rows, $row); + } else { + push(@code_rows, $row); + } + } + + # The last @doc_rows must be parsed then added to the @blocks. + push( + @blocks, + { + %options, + doc => pandoc->convert(markdown => 'html', join("\n", @doc_rows)), + code => join("\n", @code_rows) + } + ); + + return { + name => $global{metadata}{$filename}{name}, + blocks => \@blocks, + code => join("\n", map { $_->{code} } @blocks), + description => join("\n", @description) + }; +} + +=head2 generateMetadata + +Build a hash of metadata for all PG files in the given directory. A reference +to the hash that is built is returned. + +=cut + +sub generateMetadata ($problem_dir, %options) { + my $index_table = {}; + + find( + { + wanted => sub { + say "Reading file: $File::Find::name" if $options{verbose}; + + if ($File::Find::name =~ /\.pg$/) { + my $metadata = parseMetadata($File::Find::name, $problem_dir); + unless (@{ $metadata->{types} }) { + warn "The type of sample problem is missing for $File::Find::name."; + return; + } + unless ($metadata->{name}) { + warn "The name attribute is missing for $File::Find::name."; + return; + } + $index_table->{ basename($File::Find::name) } = $metadata; + } + } + }, + $problem_dir + ); + + return $index_table; +} + +my @macros_to_skip = qw( + PGML.pl + PGcourse.pl + PGstandard.pl +); + +sub parseMetadata ($path, $problem_dir) { + open(my $FH, '<:encoding(UTF-8)', $path) or do { + warn qq{Could not open file "$path": $!}; + return {}; + }; + my @file_contents = <$FH>; + close $FH; + + my @problem_types = qw(sample technique snippet); + + my $metadata = { dir => (dirname($path) =~ s/$problem_dir\/?//r) =~ s/\/*$//r }; + + while (my $row = shift @file_contents) { + if ($row =~ /^#:%\s*(categor(y|ies)|types?|subjects?|see_also|name)\s*=\s*(.*)\s*$/) { + # The row has the form #:% categories = [cat1, cat2, ...]. + my $label = lc($1); + my @opts = $3 =~ /\[(.*)\]/ ? map { $_ =~ s/^\s*|\s*$//r } split(/,/, $1) : ($3); + if ($label =~ /types?/) { + for my $opt (@opts) { + warn "The type of problem must be one of @problem_types" + unless grep { lc($opt) eq $_ } @problem_types; + } + $metadata->{types} = [ map { lc($_) } @opts ]; + } elsif ($label =~ /^categor/) { + $metadata->{categories} = \@opts; + } elsif ($label =~ /^subject/) { + $metadata->{subjects} = [ map { lc($_) } @opts ]; + } elsif ($label eq 'name') { + $metadata->{name} = $opts[0]; + } elsif ($label eq 'see_also') { + $metadata->{related} = \@opts; + } + } elsif ($row =~ /loadMacros\(/) { + chomp($row); + # Parse the macros, which may be on multiple rows. + my $macros = $row; + while ($row && $row !~ /\);\s*$/) { + $row = shift @file_contents; + chomp($row); + $macros .= $row; + } + # Split by commas and pull out the quotes. + my @macros = map {s/['"\s]//gr} split(/\s*,\s*/, $macros =~ s/loadMacros\((.*)\)\;$/$1/r); + $metadata->{macros} = []; + for my $macro (@macros) { + push(@{ $metadata->{macros} }, $macro) unless grep { $_ eq $macro } @macros_to_skip; + } + } + } + + return $metadata; +} + +=head2 getSampleProblemCode + +Parse a PG file with extra documentation comments and strip that all out +returning the clean problem code. This returns the same code that +C returns, except at much less expense as it does not parse +the documentation, it does not require that the metadata be parsed first, and it +does not need macro POD information. + +=cut + +sub getSampleProblemCode ($file) { + my $filename = basename($file); + open(my $FH, '<:encoding(UTF-8)', $file) or do { + warn qq{Could not open file "$file": $!}; + return ''; + }; + my @file_contents = <$FH>; + close $FH; + + my (@code_rows, $inCode); + + while (my $row = shift @file_contents) { + chomp($row); + $row =~ s/\t/ /g; + if ($row =~ /^#:(.*)?/) { + # This is documentation so skip it. + } elsif ($row =~ /^\s*(END)?DOCUMENT.*$/) { + $inCode = $1 ? 0 : 1; + push(@code_rows, $row); + } elsif ($inCode) { + push(@code_rows, $row); + } + } + + return join("\n", @code_rows); +} + +=head2 getSearchData + +Generate search data for sample problem files and macro POD. The only argument +is required and should be a file name to write the search data to. If the file +does not exist, then a new file containing the generated search data will be +written. If the file exists and contains search data from previously using this +method, then the data will be updated based on file modification times of the +sample problem files and macros. In any case an array reference containing the +generated search data will be returned. + +=cut + +my $stopWordsCache; + +sub getSearchData ($searchDataFileName) { + my $searchDataFile = path($searchDataFileName); + my %files = map { $_->{filename} => $_ } @{ (eval { decode_json($searchDataFile->slurp('UTF-8')) } // []) }; + my @updatedFiles; + + my $stopWords = sub ($word) { + return $stopWordsCache->{$word} if $stopWordsCache; + $stopWordsCache = {}; + + my $contents = eval { path("$pgRoot/assets/stop-words-en.txt")->slurp('UTF-8') }; + return $stopWordsCache if $@; + + for my $line (split("\n", $contents)) { + chomp $line; + next if $line =~ /^#/ || !$line; + $stopWordsCache->{$line} = 1; + } + + return $stopWordsCache->{$word}; + }; + + my $processLine = sub ($line) { + my %words; + + # Extract linked macros and problems. + my @linkedFiles = $line =~ /(?:PODLINK|PROBLINK)\('([\w.]+)'\)/g; + $words{$_} = 1 for @linkedFiles; + + # Replace any non-word characters with spaces. + $line =~ s/\W/ /g; + + for my $word (split(/\s+/, $line)) { + next if $word =~ /^\d*$/; + $word = lc($word); + $words{$word} = 1 if !$stopWords->($word); + } + return keys %words; + }; + + # Extract the text for a section from the given POD with a section header title. + my $extractHeadText = sub ($root, $title) { + my @index = grep { ref($root->[$_]) eq 'ARRAY' && $root->[$_][2] eq $title } 0 .. $#$root; + return unless @index == 1; + + my $node = $root->[ $index[0] + 1 ]; + my $str = ''; + for (2 .. $#$node) { + $str .= ref($node->[$_]) eq 'ARRAY' ? $node->[$_][2] : $node->[$_]; + } + return $str; + }; + + # Extract terms form POD headers. + my $extractHeaders = sub ($root) { + my %terms = + map { $_ => 1 } + grep { $_ && !$stopWords->($_) } + map { split(/\s+/, $_) } + map { lc($_) =~ s/\W/ /gr } + map { + grep { !ref($_) } + @$_[ 2 .. $#$_ ] + } grep { ref($_) eq 'ARRAY' && $_->[0] =~ /^head\d+$/ } @$root; + return [ keys %terms ]; + }; + + # Process the sample problems in the sample problem directory. + find( + { + wanted => sub { + return unless $_ =~ /\.pg$/; + + my $file = path($File::Find::name); + my $lastModified = $file->stat->mtime; + + if ($files{$_}) { + push(@updatedFiles, $files{$_}); + return if $files{$_}{lastModified} >= $lastModified; + } + + my @fileContents = eval { split("\n", $file->slurp('UTF-8')) }; + return if $@; + + if (!$files{$_}) { + $files{$_} = { + type => 'sample problem', + filename => $_, + dir => $file->dirname->basename + }; + push(@updatedFiles, $files{$_}); + } + $files{$_}{lastModified} = $lastModified; + + my (%words, @kw, @macros, @subjects, $description); + + while (@fileContents) { + my $line = shift @fileContents; + if ($line =~ /^#:%\s*(\w+)\s*=\s*(.*)\s*$/) { + # Store the name and subjects. + $files{$_}{name} = $2 if $1 eq 'name'; + if ($1 eq 'subject') { + @subjects = split(',\s*', $2 =~ s/\[(.*)\]/$1/r); + } + } elsif ($line =~ /^#:\s*(.*)?/) { + my @newWords = $processLine->($1); + @words{@newWords} = (1) x @newWords if @newWords; + } elsif ($line =~ /loadMacros\(/) { + my $macros = $line; + while ($line && $line !~ /\);\s*$/) { + $line = shift @fileContents; + $macros .= $line; + } + my @usedMacros = + map {s/['"\s]//gr} split(/\s*,\s*/, $macros =~ s/loadMacros\((.*)\)\;$/$1/r); + + # Get the macros other than PGML.pl, PGstandard.pl, and PGcourse.pl. + for my $m (@usedMacros) { + push(@macros, $m) unless $m =~ /^(PGML|PGstandard|PGcourse)\.pl$/; + } + } elsif ($line =~ /##\s*KEYWORDS\((.*)\)/) { + @kw = map {s/^'(.*)'$/$1/r} split(/,\s*/, $1); + } elsif ($line =~ /^##\s*DESCRIPTION/) { + $line = shift(@fileContents); + while ($line && $line !~ /^##\s*ENDDESCRIPTION/) { + $description .= ($line =~ s/^##\s+//r) . ' '; + $line = shift(@fileContents); + } + $description =~ s/\s+$//; + } + } + + $files{$_}{description} = $description; + $files{$_}{subjects} = \@subjects; + $files{$_}{terms} = [ keys %words ]; + $files{$_}{keywords} = \@kw; + $files{$_}{macros} = \@macros; + + return; + } + }, + "$pgRoot/tutorial/sample-problems" + ); + + # Process the POD in macros in the macros directory. + (undef, my $macroFiles) = Pod::Simple::Search->new->inc(0)->survey("$pgRoot/macros"); + for my $macroFile (sort keys %$macroFiles) { + next if $macroFile =~ /deprecated/; + + my $file = path($macroFile); + my $fileName = $file->basename; + my $lastModified = $file->stat->mtime; + + if ($files{$fileName}) { + push(@updatedFiles, $files{$fileName}); + next if $files{$fileName}{lastModified} >= $lastModified; + } + + if (!$files{$fileName}) { + $files{$fileName} = { + type => 'macro', + id => scalar(keys %files) + 1, + filename => $fileName, + dir => $file->dirname->to_rel($pgRoot)->to_string + }; + push(@updatedFiles, $files{$fileName}); + } + $files{$fileName}{lastModified} = $lastModified; + + my $root = Pod::Simple::SimpleTree->new->parse_file($file->to_string)->root; + + $files{$fileName}{terms} = $extractHeaders->($root); + + if (my $nameDescription = $extractHeadText->($root, 'NAME')) { + (undef, my $description) = split(/\s*-\s*/, $nameDescription, 2); + $files{$fileName}{description} = $description if $description; + } + } + + # Re-index in case files were added or removed. + my $count = 0; + $_->{id} = ++$count for @updatedFiles; + + $searchDataFile->spew(encode_json(\@updatedFiles), 'UTF-8'); + + return \@updatedFiles; +} + +1; diff --git a/lib/WeBWorK/PG/Translator.pm b/lib/WeBWorK/PG/Translator.pm index ef66ad5a70..c4ba09bd0c 100644 --- a/lib/WeBWorK/PG/Translator.pm +++ b/lib/WeBWorK/PG/Translator.pm @@ -1077,9 +1077,6 @@ sub avg_problem_grader { my %problem_result = (score => 0, errors => '', type => 'avg_problem_grader', msg => ''); - $problem_result{msg} = eval('main::maketext("You can earn partial credit on this problem.")') - if keys %$answers > 1; - # Return unless answers have been submitted. return (\%problem_result, $problem_state) unless $form_options{answers_submitted} == 1; diff --git a/lib/WeBWorK/Utils/PODParser.pm b/lib/WeBWorK/Utils/PODParser.pm new file mode 100644 index 0000000000..57950d46b0 --- /dev/null +++ b/lib/WeBWorK/Utils/PODParser.pm @@ -0,0 +1,66 @@ +package WeBWorK::Utils::PODParser; +use parent qw(Pod::Simple::XHTML); + +use strict; +use warnings; + +use Pod::Simple::XHTML; +use File::Basename qw(basename); + +# $podFiles must be provided in order for pod links to local files to work. It should be the +# first return value of the POD::Simple::Search survey method. +sub new { + my ($invocant, $podFiles) = @_; + my $class = ref $invocant || $invocant; + my $self = $class->SUPER::new(@_); + $self->perldoc_url_prefix('https://metacpan.org/pod/'); + $self->index(1); + $self->backlink(1); + $self->html_charset('UTF-8'); + $self->{podFiles} = $podFiles // {}; + return bless $self, $class; +} + +# Attempt to resolve links to local files. If a local file is not found, then +# let Pod::Simple::XHTML resolve to a cpan link. +sub resolve_pod_page_link { + my ($self, $target, $section) = @_; + + unless (defined $target) { + print "Using internal page link.\n" if $self->{verbose} > 2; + return $self->SUPER::resolve_pod_page_link($target, $section); + } + + my $podFound; + for (keys %{ $self->{podFiles} }) { + if ($target eq $_ =~ s/lib:://r || $target eq basename($self->{podFiles}{$_}) =~ s/\.pod$//r) { + $podFound = + $self->{assert_html_ext} ? ($self->{podFiles}{$_} =~ s/\.(pm|pl|pod)$/.html/r) : $self->{podFiles}{$_}; + last; + } + } + + if ($podFound) { + my $pod_url = $self->encode_entities($podFound =~ s/^$self->{source_root}/$self->{base_url}/r) + . ($section ? '#' . $self->idify($self->encode_entities($section), 1) : ''); + print "Resolved local pod link for $target" . ($section ? "/$section" : '') . " to $pod_url\n" + if $self->{verbose} > 2; + return $pod_url; + } + + print "Using cpan pod link for $target" . ($section ? "/$section" : '') . "\n" if $self->{verbose} > 2; + return $self->SUPER::resolve_pod_page_link($target, $section); +} + +# Trim spaces from the beginning of each line in code blocks. This attempts to +# trim spaces from all lines in the code block in the same amount as there are +# spaces at the beginning of the first line. Note that Pod::Simple::XHTML has +# already converted tab characters into 8 spaces. +sub handle_code { + my ($self, $code) = @_; + my $start_spaces = length(($code =~ /^( *)/)[0]) || ''; + $self->SUPER::handle_code($code =~ s/^( {1,$start_spaces})//gmr); + return; +} + +1; diff --git a/lib/WeBWorK/Utils/PODtoHTML.pm b/lib/WeBWorK/Utils/PODtoHTML.pm new file mode 100644 index 0000000000..19c30e7f50 --- /dev/null +++ b/lib/WeBWorK/Utils/PODtoHTML.pm @@ -0,0 +1,212 @@ +package WeBWorK::Utils::PODtoHTML; + +use strict; +use warnings; +use utf8; + +use Pod::Simple::Search; +use Mojo::Template; +use Mojo::DOM; +use Mojo::Collection qw(c); +use File::Path qw(make_path); +use File::Basename qw(dirname); +use IO::File; +use POSIX qw(strftime); + +use WeBWorK::Utils::PODParser; + +our @sections = ( + doc => 'Documentation', + bin => 'Scripts', + macros => 'Macros', + lib => 'Libraries', +); +our %macro_names = ( + answers => 'Answers', + contexts => 'Contexts', + core => 'Core', + deprecated => 'Deprecated', + graph => 'Graph', + math => 'Math', + misc => 'Miscellaneous', + parsers => 'Parsers', + ui => 'User Interface' +); + +sub new { + my ($invocant, %o) = @_; + my $class = ref $invocant || $invocant; + + my @section_list = ref($o{sections}) eq 'ARRAY' ? @{ $o{sections} } : @sections; + my $section_hash = {@section_list}; + my $section_order = [ map { $section_list[ 2 * $_ ] } 0 .. $#section_list / 2 ]; + delete $o{sections}; + + my $self = { + %o, + idx => {}, + section_hash => $section_hash, + section_order => $section_order, + macros_hash => {}, + }; + return bless $self, $class; +} + +sub convert_pods { + my $self = shift; + my $source_root = $self->{source_root}; + my $dest_root = $self->{dest_root}; + + my $regex = join('|', map {"^$_"} @{ $self->{section_order} }); + + my ($name2path, $path2name) = Pod::Simple::Search->new->inc(0)->limit_re(qr!$regex!)->survey($self->{source_root}); + for (keys %$path2name) { + print "Processing file: $_\n" if $self->{verbose} > 1; + $self->process_pod($_, $name2path); + } + + $self->write_index("$dest_root/index.html"); + + return; +} + +sub process_pod { + my ($self, $pod_path, $pod_files) = @_; + + my $pod_name; + + my ($subdir, $filename) = $pod_path =~ m|^$self->{source_root}/(?:(.*)/)?(.*)$|; + + my ($subdir_first, $subdir_rest) = ('', ''); + + if (defined $subdir) { + if ($subdir =~ m|/|) { + ($subdir_first, $subdir_rest) = $subdir =~ m|^([^/]*)/(.*)|; + } else { + $subdir_first = $subdir; + } + } + + $pod_name = (defined $subdir_rest ? "$subdir_rest/" : '') . $filename; + if ($filename =~ /\.pl$/) { + $filename =~ s/\.pl$/.html/; + } elsif ($filename =~ /\.pod$/) { + $pod_name =~ s/\.pod$//; + $filename =~ s/\.pod$/.html/; + } elsif ($filename =~ /\.pm$/) { + $pod_name =~ s/\.pm$//; + $pod_name =~ s|/+|::|g; + $filename =~ s/\.pm$/.html/; + } elsif ($filename !~ /\.html$/) { + $filename .= '.html'; + } + + $pod_name =~ s/^(\/|::)//; + + my $html_dir = $self->{dest_root} . (defined $subdir ? "/$subdir" : ''); + my $html_path = "$html_dir/$filename"; + my $html_rel_path = defined $subdir ? "$subdir/$filename" : $filename; + + $self->update_index($subdir, $html_rel_path, $pod_name); + make_path($html_dir); + my $html = $self->do_pod2html( + pod_path => $pod_path, + pod_name => $pod_name, + pod_files => $pod_files + ); + my $fh = IO::File->new($html_path, '>:encoding(UTF-8)') + or die "Failed to open file '$html_path' for writing: $!\n"; + print $fh $html; + + return; +} + +sub update_index { + my ($self, $subdir, $html_rel_path, $pod_name) = @_; + + $subdir =~ s|/.*$||; + my $idx = $self->{idx}; + my $sections = $self->{section_hash}; + if ($subdir eq 'macros') { + $idx->{macros} = []; + if ($pod_name =~ m!^(.+)/(.+)$!) { + push @{ $self->{macros_hash}{$1} }, [ $html_rel_path, $2 ]; + } else { + push @{ $idx->{doc} }, [ $html_rel_path, $pod_name ]; + } + } elsif (exists $sections->{$subdir}) { + push @{ $idx->{$subdir} }, [ $html_rel_path, $pod_name ]; + } else { + warn "no section for subdir '$subdir'\n"; + } + + return; +} + +sub write_index { + my ($self, $out_path) = @_; + + my $fh = IO::File->new($out_path, '>:encoding(UTF-8)') or die "Failed to open index '$out_path' for writing: $!\n"; + print $fh Mojo::Template->new(vars => 1)->render_file( + "$self->{template_dir}/category-index.mt", + { + title => 'POD for ' . ($self->{source_root} =~ s|^.*/||r), + dest_url => $self->{dest_url}, + home_url => $self->{home_url}, + home_url_link_name => $self->{home_url_link_name}, + pod_index => $self->{idx}, + sections => $self->{section_hash}, + section_order => $self->{section_order}, + macros => $self->{macros_hash}, + macros_order => [ sort keys %{ $self->{macros_hash} } ], + macro_names => \%macro_names, + date => strftime('%a %b %e %H:%M:%S %Z %Y', localtime) + } + ); + + return; +} + +sub do_pod2html { + my ($self, %o) = @_; + + my $psx = WeBWorK::Utils::PODParser->new($o{pod_files}); + $psx->{source_root} = $self->{source_root}; + $psx->{verbose} = $self->{verbose}; + $psx->{assert_html_ext} = 1; + $psx->{base_url} = $self->{page_url} // $self->{dest_url} // ''; + $psx->output_string(\my $html); + $psx->html_header(''); + $psx->html_footer(''); + $psx->parse_file($o{pod_path}); + + my $dom = Mojo::DOM->new($html); + my $podIndexUL = $dom->at('ul[id="index"]'); + my $podIndex = $podIndexUL ? $podIndexUL->find('ul[id="index"] > li') : c(); + for (@$podIndex) { + $_->attr({ class => 'nav-item' }); + $_->at('a')->attr({ class => 'nav-link p-0' }); + for (@{ $_->find('ul') }) { + $_->attr({ class => 'nav flex-column w-100' }); + } + for (@{ $_->find('li') }) { + $_->attr({ class => 'nav-item' }); + $_->at('a')->attr({ class => 'nav-link p-0' }); + } + } + my $podHTML = $podIndexUL ? $podIndexUL->remove : $html; + + return Mojo::Template->new(vars => 1)->render_file( + "$self->{template_dir}/pod.mt", + { + title => $o{pod_name}, + dest_url => $self->{dest_url}, + home_url => $self->{home_url}, + home_url_link_name => $self->{home_url_link_name}, + index => $podIndex, + content => $podHTML + } + ); +} + +1; diff --git a/macros/PG.pl b/macros/PG.pl index ef398570db..d363e39487 100644 --- a/macros/PG.pl +++ b/macros/PG.pl @@ -111,14 +111,13 @@ =head2 DOCUMENT sub DOCUMENT { # get environment - $rh_envir = \%envir; #KLUDGE FIXME - # warn "rh_envir is ",ref($rh_envir); - $PG = new PGcore( - $rh_envir, # can add key/value options to modify + $rh_envir = \%envir; #KLUDGE FIXME + + $PG = new PGcore( + $rh_envir, # can add key/value options to modify ); - $PG->clear_internal_debug_messages; - # initialize main:: variables + # initialize main:: variables $ANSWER_PREFIX = $PG->{ANSWER_PREFIX}; $QUIZ_PREFIX = $PG->{QUIZ_PREFIX}; $showPartialCorrectAnswers = $PG->{flags}->{showPartialCorrectAnswers}; @@ -620,7 +619,7 @@ sub NEW_ANS_ARRAY_NAME_EXTENSION { } my $ans_label = $PG->new_ans_name(); my $element_ans_label = $PG->new_array_element_label($ans_label, $row_num, $col_num, vec_num => $vecnum); - my $response = new PGresponsegroup($ans_label, $element_ans_label, undef); + my $response = PGresponsegroup->new($ans_label, $element_ans_label, undef); $PG->extend_ans_group($ans_label, $response); return $element_ans_label; } @@ -632,7 +631,7 @@ sub CLEAR_RESPONSES { if (ref($responsegroup)) { $responsegroup->clear; } else { - $responsegroup = $PG->{PG_ANSWERS_HASH}{$ans_label}{response} = new PGresponsegroup($label); + $responsegroup = $PG->{PG_ANSWERS_HASH}{$ans_label}{response} = PGresponsegroup->new($ans_label); } } return; @@ -994,7 +993,8 @@ sub ENDDOCUMENT { if ($ansHash->{correct_value}) { for (keys %{ $ansHash->{correct_value}->context->flag('mathQuillOpts') }) { - $mq_part_opts->{$_} = 0 unless defined $mq_part_opts->{$_}; + $mq_part_opts->{$_} = $ansHash->{correct_value}->context->flag('mathQuillOpts')->{$_} + unless defined $mq_part_opts->{$_}; } } diff --git a/macros/README.md b/macros/README.md index e9211e5cc0..692d678835 100644 --- a/macros/README.md +++ b/macros/README.md @@ -3,7 +3,6 @@ This directory now has a subdirectory structure for clarification. - **answers** answer and evaluator macros -- **capa** any CAPA related macros - **contexts** context related macros - **core** macros related to core PG functionality - **deprecated** macros that have been labelled deprecated @@ -178,7 +177,7 @@ This directory now has a subdirectory structure for clarification. ## Macros fixed - `math/algebraMacros.pl` (removed all code, already in `customizeLaTeX.pl`) -- `parsers/parserUtils.pl` (removed loadMacros call due to errors. ) +- `parsers/parserUtils.pl` (removed loadMacros call due to errors.) - `contexts/contextFiniteSolutionSets.pl` (fixed warnings) - `contexts/contextLimitedRadicalComplex.pl` (fixed warnings) - `contexts/contextRationalExponent.pl` (fixed warnings) diff --git a/macros/answers/PGasu.pl b/macros/answers/PGasu.pl index 781c346b57..64e855fcf0 100644 --- a/macros/answers/PGasu.pl +++ b/macros/answers/PGasu.pl @@ -116,7 +116,7 @@ =head2 no_trig_fun A similar effect can be accomplished with Contexts() by undefining the trig functions. -See http://webwork.maa.org/wiki/Modifying_contexts_%28advanced%29#.282.29_Functions +See https://wiki.openwebwork.org/wiki/DisableFunctions =cut @@ -202,7 +202,7 @@ =head2 must_have_filter A similar effect can be accomplished with Contexts() by undefining the trig functions. -See http://webwork.maa.org/wiki/Modifying_contexts_%28advanced%29 +See https://wiki.openwebwork.org/wiki/DisableFunctions =cut diff --git a/macros/capa/PG_CAPAmacros.pl b/macros/capa/PG_CAPAmacros.pl deleted file mode 100644 index f279fe5d1a..0000000000 --- a/macros/capa/PG_CAPAmacros.pl +++ /dev/null @@ -1,208 +0,0 @@ - -BEGIN { strict->import; } - -sub CAPA_ans { - my $ans = shift; - my %options = @_; - my $answer_evaluator = 0; - - #TEXT("answerCounter =". ++$problemCounter,"$BR"); # checking whether values are reinitialized - - # explicitlty delete options which are meaningless to WeBWorK - if (defined($options{'sig'})) { delete($options{'sig'}); } - if (defined($options{'wgt'})) { delete($options{'wgt'}); } - if (defined($options{'tries'})) { delete($options{'tries'}); } - - # $options{'allow_unknown_options'} = 1; ## if uncommented, this is a fast and possibly dangerous - ## way to prevent warning message about unknown options - - if (defined($options{'reltol'}) or defined($options{'tol'}) or defined($options{'unit'})) { - - if (defined($options{'unit'})) { - #$ans = "$ans $options{'unit'}"; - $answer_evaluator = num_cmp( - $ans, - 'format' => $options{format}, - reltol => (defined($options{reltol})) ? $options{reltol} : undef, - tol => (defined($options{tol})) ? $options{tol} : undef, - unit => $options{unit}, - ); - } else { # numerical compare: - if (defined($options{'reltol'})) { #relative tolerance is given with a percent sign - my $reltol = $options{'reltol'}; - my $format = $options{'format'} if defined($options{'format'}); - $answer_evaluator = num_cmp($ans, reltol => $reltol, 'format' => $format); - } elsif (defined($options{'tol'})) { - my $format = $options{'format'} if defined($options{'format'}); - $answer_evaluator = num_cmp($ans, tol => $options{'tol'}, 'format' => $format); - } else { - my $tol = $ans * $main::numRelPercentTolDefault; - my $format = $options{'format'} if defined($options{'format'}); - $answer_evaluator = num_cmp($ans, reltol => $tol, 'format' => $format); - } - } - } else { - # string comparisons - if (defined($options{'str'}) and $options{'str'} =~ /CS/i) { - $answer_evaluator = str_cmp($ans, filters => ['compress_whitespace']); - } elsif (defined($options{'str'}) and $options{'str'} =~ /MC/i) { - $answer_evaluator = str_cmp($ans, filters => [qw( compress_whitespace ignore_case ignore_order )]); - } else { - $answer_evaluator = str_cmp($ans, filters => [qw( compress_whitespace ignore_case )]); - } - } - - $answer_evaluator; -} - -sub CAPA_import { - my $filePath = shift; - my %save_envir = %main::envir; - my $r_string = read_whole_problem_file($filePath); - - $main::envir{'probFileName'} = $filePath; - includePGtext($r_string); - %main::envir = %save_envir; -} - -sub CAPA_map { - my $seed = shift; - my $array_var_ref = shift; - my $PGrand = new PGrandom($seed); - local $main::array_values_ref = shift; # this must be local since it must be passed to PG_restricted_eval - my $size = @$main::array_values_ref; # get number of values - my @array = 0 .. ($size - 1); - my @slice = (); - while (@slice < $size) { - push(@slice, splice(@array, $PGrand->random(0, $#array), 1)); - } - my $string = ""; - my $var; - my $i = 0; - foreach $var (@$array_var_ref) { - $string .= "\$$var = \$\$main::array_values_ref[ $slice[$i++]]; "; - } - - # it is important that PG-restriced eval can accesss the $array_values_ref - my ($val, $PG_eval_errors, $PG_full_error_report) = PG_restricted_eval($string); - my $out = ''; - $string =~ s/\$/\\\$/g; # protect variables for error message - $out = "Error in MAP subroutine: $PG_eval_errors
    \n" . $string . "
    \n" if $PG_eval_errors; - $out; -} - -sub compare_units { - -} - -sub CAPA_hint { - my $hint = shift; - TEXT(hint(qq{ HINT: $hint $main::BR})); -} - -sub CAPA_explanation { - TEXT(solution(qq{ $main::BR$main::BR EXPLANATION: @_ $main::BR$main::BR})) if solution(@_); -} - -sub pow { - my ($base, $exponent) = @_; - $base**$exponent; -} - -sub CAPA_tex { - my $tex = shift; - # $tex =~ s|/*|\$|g; - #$tex =~ s/\\/\\\\/g; #protect backslashes??? - my $nontex = shift; - &M3($tex, $nontex, $nontex); -} - -sub CAPA_web { - my $text = shift; - my $tex = shift; - my $html = shift; - &M3($tex, "\\begin{rawhtml}$html\\end{rawhtml}", $html); -} - -sub CAPA_html { - my $html = shift; - &M3("", "\\begin{rawhtml}$html\\end{rawhtml}", $html); -} - -sub var_in_tex { - my ($tex) = $_[0]; - &M3("$tex", "$tex", ""); -} - -sub isNumberQ { # determine whether the input is a number - my $in = shift; - $in =~ /^[\d\.\+\-Ee]+$/; -} - -sub choose { - # my($in)=join(" ",@_); - my ($var) = $_[0]; - $_[$var]; -} - -sub problem { - $main::probNum; -} - -sub pin { - $main::psvn; -} - -sub section { - $main::sectionNumber; -} - -sub name { - $main::studentName; -} - -sub set { - $main::setNumber; -} - -sub question { - $main::probNum; -} - -sub due_date { - $main::formattedDueDate; -} - -sub answer_date { - $main::formattedAnswerDate; -} - -sub open_date { - $main::formattedOpenDate; -} - -sub to_string { - $_[0]; -} - -sub CAPA_EV { - - my $out = &EV3(@_); - $out =~ s/\n\n/\n/g; # hack to prevent introduction of paragraphs in TeX?? - # HACK TO DO THE RIGHT THING WITH DOLLAR SIGNS - $out = ev_substring($out, "/*/*", "/*/*", \&display_math_ev3); - $out = ev_substring($out, "/*", "/*", \&math_ev3); - # TEXT($main::BR, $main::BR, $out,$main::BR,$main::BR); - $out; -} - -# these are very commonly needed files -CAPA_import("${main::CAPA_Tools}StdMacros"); -CAPA_import("${main::CAPA_Tools}StdUnits"); -CAPA_import("${main::CAPA_Tools}StdConst"); -##################### - -$main::prob_val = ""; # gets rid of spurious errors. -$main::prob_try = ""; - -1; diff --git a/macros/capa/StdConst.pg b/macros/capa/StdConst.pg deleted file mode 100644 index f19fdae6e1..0000000000 --- a/macros/capa/StdConst.pg +++ /dev/null @@ -1,36 +0,0 @@ - -## -## macros for Fundamental Constants, in SI units! -## -## Gravitational constant CapG: Units-> m3/kgs2 or Nm2/kg2 -$CapG = 6.67e-11 ; -## -## Speed of light in m/s: -$smallc = 2.99792458e+8 ; -## -## Electron charge in C: -$smalle = 1.602177e-19 ; -## -## Planck constant, smallh: Units ->Js -$smallh = 6.626075e-34 ; -## -## Boltzmann constant, smallk: Units ->J/K -$smallk = 1.38066e-23 ; -## -## standardgrav. accel sea level, smallg: Units ->m/s2 -$smallg = 9.8 ; - -## -## Pi: -$pi = 3.1415926536 ; -## -## From degrees to radians: -$degrad = $pi / 180.0 ; -## -## From radians to degrees: -$raddeg = 180.0 / $pi ; - -1; -################################################# -## Processing time = 2 secs ( 1.96 usr 0.41 sys = 2.38 cpu) -################################################# diff --git a/macros/capa/StdUnits.pg b/macros/capa/StdUnits.pg deleted file mode 100644 index 80238f24d5..0000000000 --- a/macros/capa/StdUnits.pg +++ /dev/null @@ -1,79 +0,0 @@ - -## Define some macros for the units: -## Note: P means per in this notation, except for Pa =Pascal) -## u is the unit indication -## -$m_u = CAPA_tex( "/*m/*" , "m" ) ; # angle degree -$km_u = CAPA_tex( "/*km/*" , "km" ) ; -$cm_u = CAPA_tex( "/*cm/*" , "cm" ) ; -$mm_u = CAPA_tex( "/*mm/*" , "mm" ) ; -$um_u = CAPA_tex( "/*\micro m/*" , "um" ) ; -$nm_u = CAPA_tex( "/*nm/*" , "nm" ) ; -$fm_u = CAPA_tex( "/*fm/*" , "fm" ) ; -$m2_u = CAPA_tex( "/*m^2/*" , "m^2" ) ; -$m3_u = CAPA_tex( "/*m^3/*" , "m^3" ) ; -$cm2_u = CAPA_tex( "/*cm^2/*" , "cm^2" ) ; -$cm3_u = CAPA_tex( "/*cm^3/*" , "cm^3" ) ; -$mi_u = CAPA_tex( "/*mi/*" , "mi" ) ; -$ft_u = CAPA_tex( "/*ft/*" , "ft" ) ; -$in_u = CAPA_tex( "/*in./*" , "in." ) ; -$kg_u = CAPA_tex( "/*kg/*" , "kg" ) ; -$amu_u = CAPA_tex( "/*u/*" , "u" ) ; -$lb_u = CAPA_tex( "/*lb/*" , "lb" ) ; -$s_u = CAPA_tex( "/*s/*" , "s" ) ; -$min_u = CAPA_tex( "/*min/*" , "min" ) ; -$K_u = CAPA_tex( "/*K/*" , "K" ) ; -$degC_u = CAPA_tex( "/*^{\circ}\hspace*{-.015in}C/*" , "degC" ) ; -$degF_unit = CAPA_tex( "/*^{\circ}\hspace*{-.025in}F/*" , "degF" ) ; -$kmol_u = CAPA_tex( "/*kmol/*" , "kmol" ) ; -$Hz_u = CAPA_tex( "/*Hz/*" , "Hz" ) ; -$mPs_u = CAPA_tex( "/*m/s/*" , "m/s" ) ; -$cmPs_u = CAPA_tex( "/*cm/s/*" , "cm/s" ) ; -$kmPh_u = CAPA_tex( "/*km/h/*" , "km/h" ) ; -$miPh_u = CAPA_tex( "/*mi/h/*" , "mi/h" ) ; -$radPs_u = CAPA_tex( "/*rad/s/*" , "rad/s" ) ; -$Ps_u = CAPA_tex( "/*s^{-1}/*" , "s^-1" ) ; -$mPs2_u = CAPA_tex( "/*m/s^2/*" , "m/s2" ) ; -$cmPs2_u = CAPA_tex( "/*cm/s^2/*" , "cm/s2" ) ; -$radPs2_u = CAPA_tex( "/*rad/s^2/*" , "rad/s2" ) ; -$N_unit = CAPA_tex( "/*N/*" , "N" ) ; -$Pa_u = CAPA_tex( "/*Pa/*" , "Pa" ) ; -$atm_u = CAPA_tex( "/*atm/*" , "atm" ) ; -$NPm2_u = CAPA_tex( "/*N/m^2/*" , "N/m2" ) ; -$J_u = CAPA_tex( "/*J/*" , "J" ) ; -$hp_u = CAPA_tex( "/*hp/*" , "hp" ) ; -$eV_u = CAPA_tex( "/*eV/*" , "eV" ) ; -$cal_u = CAPA_tex( "/*cal/*" , "cal" ) ; -$kcal_u = CAPA_tex( "/*kcal/*" , "kcal" ) ; -$W_u = CAPA_tex( "/*W/*" , "W" ) ; -$kW_u = CAPA_tex( "/*kW/*" , "kW" ) ; -$Js_u = CAPA_tex( "/*J s/*" , "J s" ) ; -$C_u = CAPA_tex( "/*C/*" , "C" ) ; -$V_u = CAPA_tex( "/*V/*" , "V" ) ; -$ohm_u = CAPA_tex( "/*\Omega/*" , "ohm" ) ; -$F_u = CAPA_tex( "/*F/*" , "F" ) ; -$Wb_u = CAPA_tex( "/*Wb/*" , "Wb" ) ; -$H_u = CAPA_tex( "/*H/*" , "H" ) ; -$T_u = CAPA_tex( "/*T/*" , "T" ) ; -$N_u = CAPA_tex( "/*N/*" , "N" ) ; -$JPK_u = CAPA_tex( "/*J\over K/*" , "J/K" ) ; -$Nm2Pkg2_u = CAPA_tex( "/*Nm2\over kg^2/*" , "Nm^2/kg^2" ) ; -$kgPm3_u = CAPA_tex( "/*kg / m^3/*" , "kg/m3" ) ; -$gPm3_u = CAPA_tex( "/*g / m^3/*" , "g/m3" ) ; -$gPcm3_u = CAPA_tex( "/*g / cm^3/*" , "g/cm3" ) ; -$NPm_u = CAPA_tex( "/*N/m/*" , "N/m" ) ; -$kgm2_u = CAPA_tex( "/*kg\cdot m^2/*" , "kgm^2" ) ; -$deg_u = CAPA_tex( "/*^{\circ}/*" , "deg" ) ; -$rev_u = CAPA_tex( "/*rev/*" , "rev" ) ; -$rad_u = CAPA_tex( "/*rad/*" , "rad" ) ; -$G_u = CAPA_tex( "/*G/*" , "G" ) ; -$g_u = CAPA_tex( "/*g/*" , "g" ) ; -$h_u = CAPA_tex( "/*h/*" , "h" ) ; -$ton_u = CAPA_tex( "/*ton/*" , "ton" ) ; -$knots_u = CAPA_tex( "/*knots/*" , "knots" ) ; -$kgPs_u = CAPA_tex( "/*kg/s/*" , "kg/s" ) ; - -1; -################################################# -## Processing time = 5 secs ( 4.60 usr 0.37 sys = 4.97 cpu) -################################################# diff --git a/macros/contexts/contextFiniteSolutionSets.pl b/macros/contexts/contextFiniteSolutionSets.pl index bf98737afd..e5452a9ac0 100644 --- a/macros/contexts/contextFiniteSolutionSets.pl +++ b/macros/contexts/contextFiniteSolutionSets.pl @@ -283,6 +283,7 @@ sub _contextFiniteSolutionSets_init { . join(',', map { $_->TeX } (@correctanswers)) . "\\right\\}\\)"); } + $score = 0 if $score < 0; return ($score, @errors); }; } diff --git a/macros/contexts/contextInteger.pl b/macros/contexts/contextInteger.pl index b9fbf2fc3c..bab7c7ef07 100644 --- a/macros/contexts/contextInteger.pl +++ b/macros/contexts/contextInteger.pl @@ -74,7 +74,7 @@ =head2 isPrime =cut -loadMacros('MathObjects.pl'); +loadMacros('PGbasicmacros.pl', 'MathObjects.pl'); sub _contextInteger_init { context::Integer::Init() } @@ -124,7 +124,7 @@ sub Init { sub _divisor { my $power = abs(shift); my $a = abs(shift); - $self->Error("Cannot perform divisor function on Zero") if $a == 0; + Value::Error('Cannot perform divisor function on Zero') if $a == 0; $result = 1; $sqrt_a = int(sqrt($a)); for (my $i = 2; $i < $sqrt_a; $i++) { @@ -146,9 +146,9 @@ sub _divisor { sub _getPrimesInRange { my $index = shift; my $end = shift; - $self->Error("Start of range must be a positive number.") if $index < 0; - $self->Error("End of range must be greater than or equal to 2") if $end < 2; - $self->Error("Start or range must be before end of range") if $index > $end; + Value::Error('Start of range must be a positive number.') if $index < 0; + Value::Error('End of range must be greater than or equal to 2') if $end < 2; + Value::Error('Start of range must be before end of range') if $index > $end; @primes = (); # consider switching to set upper limit and static array of primes @@ -156,7 +156,7 @@ sub _getPrimesInRange { push(@primes, 2) if $index <= 2; # ensure index is odd $index++ if $index % 2 == 0; - while ($index < $end) { + while ($index <= $end) { push(@primes, $index) if context::Integer::Function::Numeric::isPrime($index); $index += 2; } @@ -172,8 +172,8 @@ package context::Integer::Function::Numeric; # sub primeFactorization { my $a = abs(shift); - $self->Error("Cannot factor Zero into primes.") if $a == 0; - $self->Error("Cannot factor One into primes.") if $a == 1; + Value::Error('Cannot factor Zero into primes.') if $a == 0; + Value::Error('Cannot factor One into primes.') if $a == 1; my %factors; my $n = $a; @@ -200,7 +200,7 @@ sub primeFactorization { # sub phi { my $a = abs(shift); - $self->Error("Cannot phi on Zero.") if $a == 0; + Value::Error('Cannot phi on Zero.') if $a == 0; $result = $a; $n = $a; for (my $i = 2; ($i**2) <= $n; $i++) { @@ -235,9 +235,8 @@ sub isPrime { sub randomPrime { my ($start, $end) = @_; my @primes = context::Integer::_getPrimesInRange($start, $end); - $self->Error("Could not find any prime numbers in range.") if $#primes == 0; - my $primeIndex = $main::PG_random_generator->random(0, ($#primes - 1), 1); - return $primes[$primeIndex]; + Value::Error('Could not find any prime numbers in range.') unless @primes; + return main::list_random(@primes); } package context::Integer::Function::Numeric2; diff --git a/macros/contexts/contextReaction.pl b/macros/contexts/contextReaction.pl index 057783ad5e..5d589e593e 100644 --- a/macros/contexts/contextReaction.pl +++ b/macros/contexts/contextReaction.pl @@ -25,10 +25,15 @@ =head1 DESCRIPTION $R = Formula("4P + 5O_2 --> 2P_2O_5"); -Ions can be specified using C<^> to produce superscripts, as in C or -C. Note that the charge may be listed with prefix notation (+2) -or postfix notation (2+). A sign by itself is assumed to have number -1, so that C is equivalent to C. +Ions can be specified using C<^> to produce superscripts, as in +C or C. Note that the charge may be listed with prefix +notation (+2) or postfix notation (2+). By default, TeX will display +only the sign of the charge of singly charged ions, e.g., C and +C. However, the context flag C<< showUnity=>1 >> can be used to +direct TeX to use an alternative notation for singly charged ions, by +which the numerical magnitude (1) is displayed along with the sign of +the charge, e.g., C and C. The default value of +C<> is zero. States can be appended to compounds, as in C. So you can make reactions like the following: @@ -44,9 +49,9 @@ =head1 DESCRIPTION These can be used in reactions as with any other compound. -Reactions know how to create their own TeX versions (via C<< $R->TeX >>), and -know how to check student answers (via C<< $R->cmp >>), just like any other -MathObject. +Reactions know how to create their own TeX versions (via C<< $R->TeX >>), +and know how to check student answers (via C<< $R->cmp >>), just like +any other MathObject. The Reaction Context also allows you to create parts of reactions. E.g., you could create @@ -108,6 +113,48 @@ =head1 DESCRIPTION to allow a state of C<(x)> for a compound. +By default, the Reaction context checks the student answer against the +correct answer in the form the correct answer is given (molecular or +condensed structural form). If you want to allow either form, then +you can set the flag C to C<1>, in which case, +the student's answer is compared first to the original correct answer +and then to its molecular form. For example, if the correct answer is +C<(NH_4)_3PO_4>, then a student answer of C would also be +considered correct when this flag is set to C<1>. + +You can convert a compound (or every term of a complete reaction) to +molecular form using the C method of any Reaction +object. For example + + $R = Compute("CH_3CH_2CH_3")->molecularForm; + +would make C<$R> be the equivalent of C. + +Note that student answers aren't reduced to molecular form when +C is true. If you want to allow students to +enter compounds in any form, you can set the C flag +to C<1>, in which case both the correct and student answers are +formatted in molecular form internally before they are compared. + +The molecular form will have the elements in the order that they first +appeared in the compound, and the student must use the same order for +their answer to be considered correct. If you want to allow the +student to enter the elements in any order, you can set the +C flag to C<0> which will cause the molecular form +to be in alphabetical order. That way, if the student has the correct +number of each element, in any order, the two answers will match. + +The Reaction context can perform an automatic reduction that combines +adjacent elements that are the same. This is controlled by the +C flag, which is C<1> by default. For +example, + + $R1 = Compute("COOH"); + $R2 = Compute("CO_2H"); + +would equal each other when the flag is set, even when +C is not set. + =cut ###################################################################### @@ -142,19 +189,16 @@ sub Init { $context->functions->clear(); $context->strings->clear(); $context->constants->clear(); - $context->lists->clear(); - $context->lists->add( + $context->lists->are( 'List' => { class => 'context::Reaction::List::List', open => '', close => '', separator => ' + ' }, 'Complex' => { class => 'context::Reaction::List::Complex', open => '[', close => ']', separator => '' }, ); - $context->parens->clear(); - $context->parens->add( + $context->parens->are( '(' => { close => ')', type => 'List', formList => 1, removable => 1 }, '{' => { close => '}', type => 'List', removable => 1 }, '[' => { close => ']', type => 'Complex' }, ); - $context->operators->clear(); - $context->operators->set( + $context->operators->are( '-->' => { precedence => 1, associativity => 'left', @@ -259,8 +303,15 @@ sub Init { ); $context->variables->add(map { $_ => $STATE } ('(aq)', '(s)', '(l)', '(g)', '(ppt)')); $context->reductions->clear(); - $context->flags->set(studentsMustUseStates => 1); - $context->flags->set(reduceConstants => 0); + $context->flags->set( + showUnity => 0, + studentsMustUseStates => 1, + reduceConstants => 1, + combineAdjacentElements => 1, + acceptMolecularForm => 0, + compareMolecular => 0, + keepElementOrder => 1, + ); $context->{parser}{Number} = 'context::Reaction::Number'; $context->{parser}{Variable} = 'context::Reaction::Variable'; $context->{parser}{Formula} = 'context::Reaction'; @@ -277,14 +328,14 @@ sub Init { # Handle postfix - and + in superscripts # sub Op { - my $self = shift; - my $name = shift; - my $ref = $self->{ref} = shift; + my $self = shift; + my $name = shift; + my $ref = $self->{ref} = shift; + my $context = $self->{context}; + my $op; + ($name, $op) = $context->operators->resolve($name); + ($name, $op) = $context->operators->resolve($op->{space}) if $self->{space} && defined($op->{space}); if ($self->state eq 'operand') { - my $context = $self->{context}; - my $op; - ($name, $op) = $context->operators->resolve($name); - ($name, $op) = $context->operators->resolve($op->{space}) if $self->{space} && defined($op->{space}); if ($op->{type} eq 'both' && $context->{operators}{"p$name"} && $self->top->{value}->class eq 'Number' @@ -292,23 +343,32 @@ sub Op { && $self->prev->{name} eq '^') { ($name, $op) = $context->operators->resolve("p$name"); - $self->pushOperand($self->Item('UOP')->new($self, $name, $self->pop->{value}, $ref)); + $self->pushCharge($name, $self->pop->{value}); return; } - } elsif ($self->state eq 'operator' && $self->top->{name} =~ m/^u/) { - $self->SimpleCharge; } $self->SUPER::Op($name, $ref); } # -# Handle superscripts of just + or - +# Handle superscripts of just + or - or postscript + or - # sub Close { my $self = shift; my $type = shift; my $ref = $self->{ref} = shift; $self->SimpleCharge if $self->state eq 'operator' && $self->top->{name} =~ m/^u/; + my $name = $self->top->{name}; + my $context = $self->{context}; + if ($self->state eq 'operator' + && $context->{operators}{"p$name"} + && $self->prev->{type} eq 'operand' + && $self->top(-2)->{type} eq 'open') + { + $self->pop; + ($name) = $context->operators->resolve("p$name"); + $self->pushCharge($name, $self->pop->{value}); + } $self->SUPER::Close($type, $ref); } @@ -322,11 +382,18 @@ sub pushOperand { $self->push({ type => 'operand', ref => $self->{ref}, value => $value }); } +sub pushCharge { + my $self = shift; + my $op = shift; + my $value = shift; + $self->pushOperand($self->Item('UOP')->new($self, $op, $value, $self->{ref})); +} + sub SimpleCharge { my $self = shift; my $top = $self->pop; my $one = $self->Item('Number')->new($self, 1, $self->{ref}); - $self->pushOperand($self->Item('UOP')->new($self, $top->{name}, $one, $self->{ref})); + $self->pushCharge($top->{name}, $one); } # @@ -341,11 +408,20 @@ sub compare { } # -# Don't allow evaluation +# Don't allow evaluation except for numbers # sub eval { my $self = shift; - $self->Error("Can't evaluate " . $self->TYPE); + return $self->Package('Real')->new($self->{tree}{value}) if $self->{tree}->class eq 'Number'; + $self->Error("Can't evaluate " . $self->{tree}->TYPE); +} + +# +# Get the molecular form for everything in the reaction +# +sub molecularForm { + my $self = shift; + return $self->new($self->{tree}->molecularForm); } # @@ -383,12 +459,71 @@ sub typeMatch { return 1; } +###################################################################### +# +# Common functions to multiple classes +# +package context::Reaction::common; + +# +# Shorthand for creating parser items +# +sub ITEM { + my $self = shift; + return $self->Item(shift)->new($self->{equation}, @_); +} + +# +# Convert an item to a compound +# +sub COMPOUND { + my $self = shift; + return $self->ITEM('BOP', ' ', $self->ITEM('Number', 1), $self->copy); +} + +# +# Make a charge item from a number +# +sub CHARGE { + my ($self, $n) = @_; + return $self->ITEM('UOP', $n < 0 ? 'u-' : 'u+', $self->ITEM('Number', CORE::abs($n))); +} + +# +# Add a term into an item's data +# +sub combineData { + my ($self, $other) = @_; + return unless $other->{order}; + my ($order, $elements) = ($self->{order}, $self->{elements}); + for my $element (@{ $other->{order} }) { + if (!$elements->{$element}) { + push(@$order, $element); + $elements->{$element} = 0; + } + $elements->{$element} += $other->{elements}{$element}; + } + $self->{charge} += $other->{charge}; + $self->{factor} *= $other->{factor}; +} + +# +# Molecular form is same as original +# +sub molecularForm { shift->copy } + +# +# Default is not a checmicla or sum +# +sub isChemical {0} +sub isSum {0} + ###################################################################### # # The replacement for the Parser:Number class # package context::Reaction::Number; -our @ISA = ('Parser::Number'); +our @ISA = ('Parser::Number', 'context::Reaction::common'); # # Equivalent is equal @@ -400,8 +535,6 @@ sub equivalent { return $self->eval == $other->eval; } -sub isChemical {0} - sub class {'Number'} sub TYPE {'a Number'} @@ -410,7 +543,21 @@ sub equivalent { # The replacement for Parser::Variable. We hold the elements here. # package context::Reaction::Variable; -our @ISA = ('Parser::Variable'); +our @ISA = ('Parser::Variable', 'context::Reaction::common'); + +# +# Save the element data +# +sub new { + my $self = shift->SUPER::new(@_); + if ($self->type ne 'State') { + $self->{order} = [ $self->{name} ]; + $self->{elements} = { $self->{name} => 1 }; + $self->{charge} = 0; + $self->{factor} = 1; + } + return $self; +} # # Two elements are equivalent if their names are equal @@ -452,7 +599,7 @@ sub TYPE { # are subclasses of this). # package context::Reaction::BOP; -our @ISA = ('Parser::BOP'); +our @ISA = ('Parser::BOP', 'context::Reaction::common'); # # Binary operators produce chemicals (unless overridden, as in arrow) @@ -461,6 +608,17 @@ package context::Reaction::BOP; sub eval { context::Reaction::eval(@_) } +# +# Form molecular form by combining molecular forms of the operands +# +sub molecularForm { + my $self = shift; + $self = bless {%$self}, ref($self); + $self->{lop} = $self->{lop}->molecularForm; + $self->{rop} = $self->{rop}->molecularForm; + return $self; +} + # # Two nodes are equivalent if their operands are equivalent # and they have the same operator @@ -468,13 +626,19 @@ package context::Reaction::BOP; sub equivalent { my $self = shift; my $other = shift; - # return $other->equivalent($self) - # if $other->class eq 'BOP' && $other->{rop}->type eq 'State' && $self->{rop}->type ne 'State'; - return 0 unless $other->class eq 'BOP'; - return 0 unless $self->{bop} eq $other->{bop}; + return 0 unless $other->class eq 'BOP' && $self->{bop} eq $other->{bop}; return $self->{lop}->equivalent($other->{lop}) && $self->{rop}->equivalent($other->{rop}); } +# +# Check for equivalence using string representations of compounds +# +sub equivalentTo { + my ($self, $other, $states) = @_; + $other = $other->string; + return $self->string eq $other || (!$states && $self->{hasState} && $self->{lop}->string eq $other); +} + ###################################################################### # # Implements the --> operator @@ -492,10 +656,11 @@ package context::Reaction::BOP::arrow; # sub _check { my $self = shift; - $self->Error("The left-hand side of '-->' must be a (sum of) reactants, not %s", $self->{lop}->TYPE) - unless $self->{lop}->isChemical; - $self->Error("The right-hand side of '-->' must be a (sum of) products, not %s", $self->{rop}->TYPE) - unless $self->{rop}->isChemical; + my ($lop, $rop) = ($self->{lop}, $self->{rop}); + $self->Error("The left-hand side of '-->' must be a (sum of) reactants, not %s", $lop->TYPE) + unless $lop->isChemical || $lop->isSum; + $self->Error("The right-hand side of '-->' must be a (sum of) products, not %s", $rop->TYPE) + unless $rop->isChemical || $rop->isSum; $self->{type} = $REACTION->{type}; } @@ -514,8 +679,9 @@ package context::Reaction::BOP::add; # sub _check { my $self = shift; - $self->Error("Can't add %s and %s", $self->{lop}->TYPE, $self->{rop}->TYPE) - unless $self->{lop}->isChemical && $self->{rop}->isChemical; + my ($lop, $rop) = ($self->{lop}, $self->{rop}); + $self->Error("Can't combine %s and %s", $lop->TYPE, $rop->TYPE) + unless ($lop->isChemical || $lop->isSum) && ($rop->isChemical || $rop->isSum); $self->SUPER::_check(@_); } @@ -534,6 +700,11 @@ sub equivalent { sub TYPE {'a sum of Compounds'} +# +# It is a sum +# +sub isSum {1} + ###################################################################### # # Implements concatenation, which produces compounds or integer @@ -548,55 +719,186 @@ package context::Reaction::BOP::multiply; sub _check { my $self = shift; my ($lop, $rop) = ($self->{lop}, $self->{rop}); - $self->Error("Can't combine %s and %s", $lop->TYPE, $rop->TYPE) + $self->Error("Can't use a numeric prefix with %s", $rop->TYPE) unless ($lop->class eq 'Number' || $lop->isChemical) && $rop->isChemical; $self->Error("Compound already has a state") if $lop->{hasState} && $rop->type eq 'State'; $self->Error("Can't combine %s with %s", $lop->TYPE, $rop->TYPE) if $lop->{hasState} && $rop->isChemical; $self->Error("Can't combine %s with %s", $lop->TYPE, $rop->TYPE) if $rop->{hasState} && $lop->isChemical; $self->Error("Can't combine %s with %s", $lop->{name}, $rop->TYPE) if $lop->type eq 'Constant'; $self->Error("Can't combine %s with %s", $lop->TYPE, $rop->{name}) if $rop->type eq 'Constant'; + $self->Error("Can't combine %s with %s", $lop->TYPE, $rop->TYPE) + if $lop->class ne 'Number' && $rop->{hasNumber}; $self->{type} = $COMPOUND->{type}; - if ($self->{lop}{hasNumber}) { - my $n = $self->{lop}{lop}; - $self->{lop}{lop} = $self->{lop}{rop}; - $self->{lop}{rop} = $self->{rop}; - $self->{lop}{hasState} = 1 if $self->{rop}->type eq 'State'; - delete $self->{lop}{hasNumber}; - $self->{rop} = $self->{lop}; - $self->{lop} = $n; + $self->getCompound; + $self->{hasState} = 1 if $rop->type eq 'State'; + $self->{hasNumber} = 1 if $lop->type eq 'Number' || $lop->{hasNumber}; + + if ($lop->{hasNumber} && $rop->type ne 'State') { + my $n = $lop->{lop}; + $lop->{lop} = $lop->{rop}; + $lop->{rop} = $rop; + delete $lop->{hasNumber}; + $rop = $self->{rop} = $lop; + $lop = $self->{lop} = $n; + } + + $self->combineAdjacentNumbers if $self->context->flag('reduceConstants'); + $self->combineAdjacentElements if $self->context->flag('combineAdjacentElements'); +} + +# +# Get the order/element/charge data for a compound +# +sub getCompound { + my $self = shift; + my ($lop, $rop) = ($self->{lop}, $self->{rop}); + $self->{order} = []; + $self->{elements} = {}; + $self->{charge} = 0; + $self->{factor} = $lop->type eq 'Number' ? $lop->eval : 1; + $self->combineData($lop); + $self->combineData($rop); +} + +# +# Combine adjacent numbers (e.g., 2(3CO)) +# +sub combineAdjacentNumbers { + my $self = shift; + my ($lop, $rop) = ($self->{lop}, $self->{rop}); + if ($rop->{hasNumber} && $lop->class eq 'Number') { + if ($rop->{hasState}) { + $lop->{value} *= $rop->{lop}{lop}{value}; + $rop->{lop} = $rop->{lop}{rop}; + } else { + $lop->{value} *= $rop->{lop}{value}; + $rop = $self->{rop} = $rop->{rop}; + } + } +} + +# +# Combine adjacent elements if they are the same +# +sub combineAdjacentElements { + my $self = shift; + my ($lop, $rop) = ($self->{lop}, $self->{rop}); + return unless $lop->{order} && $rop->{order} && scalar(@{ $rop->{order} }) == 1; + my $name = $rop->{order}[0]; + if ($lop->type eq 'Compound') { + $self->combineCompound($lop, $rop); + } else { + $self->combineSimple($lop, $rop); } - $self->{hasState} = 1 if $self->{rop}->type eq 'State'; - $self->{hasNumber} = 1 if $self->{lop}->class eq 'Number'; } # -# Remove ground state, if needed +# Combine elements when the left operand is a compound +# +sub combineCompound { + my ($self, $lop, $rop) = @_; + my $last = $lop->{rop}; + my $name = $rop->{order}[0]; + return unless $last->{order} && scalar(@{ $last->{order} }) == 1 && $last->{order}[0] eq $name; + $self->{lop} = $lop->{lop}; + my $n = $self->ITEM('Number', $last->{elements}{$name} + $rop->{elements}{$name}); + $self->{rop} = $self->ITEM('BOP', '_', $self->ITEM('Variable', $name), $n); + my $charge = $last->{charge} + $rop->{charge}; + $self->{rop} = $self->ITEM('BOP', '^', $self->{rop}, $self->CHARGE($charge)) if $charge; +} + +# +# Combine elements when the left is not a compound +# +sub combineSimple { + my ($self, $lop, $rop) = @_; + my $name = $rop->{order}[0]; + return unless scalar(@{ $lop->{order} }) == 1 && $lop->{order}[0] eq $name; + my $n = $self->ITEM('Number', $lop->{elements}{$name} + $rop->{elements}{$name}); + my $element = $self->ITEM('Variable', $name); + my $charge = $lop->{charge} + $rop->{charge}; + if ($charge) { + $self->mutate('^', $self->ITEM('BOP', '_', $element, $n), $self->CHARGE($charge)); + } else { + $self->mutate('_', $element, $n); + } +} + +# +# Mutate the BOP into a different one +# +sub mutate { + my $self = shift; + my $other = $self->ITEM('BOP', @_); + delete $self->{$_} for (keys %$self); + $self->{$_} = $other->{$_} for (keys %$other); + bless $self, ref($other); +} + +# +# Create the molecular form for a compound +# +sub molecularForm { + my $self = shift; + my $elements = $self->{elements}; + my $state = $self->{hasState} ? $self->{rop} : undef; + my $number = $self->{factor} > 1 ? $self->ITEM('Number', $self->{factor}) : undef; + my @order = @{ $self->{order} }; + @order = main::lex_sort(@order) if !$self->context->flag('keepElementOrder'); + my $compound; + for (@order) { + my $term = $self->ITEM('Variable', $_); + $term = $self->ITEM('BOP', '_', $term, $self->ITEM('Number', $elements->{$_})) if $elements->{$_} > 1; + $compound = $compound ? $self->ITEM('BOP', ' ', $compound, $term) : $term; + } + $compound = $self->ITEM('BOP', '^', $compound, $self->CHARGE($self->{charge})) if $self->{charge}; + $compound = $self->ITEM('BOP', ' ', $number, $compound) if $number; + $compound = $self->ITEM('BOP', ' ', $compound, $state->copy) if $state; + return $compound; +} + +# +# Check for equivalence of two compounds +# removing states if needed and student answers can be without them # sub equivalent { my $self = shift; my $other = shift; if ($other->class eq 'List') { my $parens = $self->context->parens->get('('); - my $list = $self->Item('List')->new($self->{equation}, [$self], 1, $parens); + my $list = $self->ITEM('List', $self->{equation}, [$self], 1, $parens); return $list->equivalent($other); } - my $states = $self->context->flags->get('studentsMustUseStates'); - my $equiv = $self->SUPER::equivalent($other); - return ($equiv || !$self->{hasState} || $states ? $equiv : $self->{lop}->equivalent($other)); + return 0 unless $other->isChemical; + $other = $other->COMPOUND unless $other->type eq 'Compound'; + my $states = $self->context->flag('studentsMustUseStates'); + my $molecular = $self->context->flag('compareMolecular'); + my $both = $self->context->flag('acceptMolecularForm'); + return $self->molecularForm->equivalentTo($other->molecularForm, $states) if $molecular; + return $self->equivalentTo($other, $states) || ($both && $self->molecularForm->equivalentTo($other, $states)); } # # No space in output for implied multiplication +# and add parentheses for compounds that need it # sub string { my $self = shift; - return $self->{lop}->string . $self->{rop}->string; + my ($l, $r) = ($self->{lop}->string, $self->{rop}->string); + $l = '' if $l eq '1'; + $r = "($r)" + if $l && $self->{rop}->type eq 'Compound' && ($self->{rop}{hasNumber} || $self->{lop}->class ne 'Number'); + return $l . $r; } sub TeX { my $self = shift; - return $self->{lop}->TeX . $self->{rop}->TeX; + my ($l, $r) = ($self->{lop}->TeX, $self->{rop}->TeX); + $l = '' if $l eq '1'; + $r = "($r)" + if $l && $self->{rop}->type eq 'Compound' && ($self->{rop}{hasNumber} || $self->{lop}->class ne 'Number'); + return $l . $r; } # @@ -604,7 +906,9 @@ sub TeX { # sub TYPE { my $self = shift; - return $self->{rop}->TYPE eq 'a state' ? $self->{lop}->TYPE . ' with state' : 'a compound'; + return $self->{rop}->TYPE eq 'a state' + ? $self->{lop}->TYPE . ' with state' + : 'a compound' . ($self->{hasNumber} ? ' with number' : ''); } ###################################################################### @@ -621,10 +925,33 @@ sub _check { my $self = shift; my ($lop, $rop) = ($self->{lop}, $self->{rop}); $self->Error("The left-hand side of '_' must be an element, compound, or complex, not %s", $lop->TYPE) - unless $lop->type =~ m/Element|Compound|Complex/ && !$lop->{hasState}; + unless $lop->type =~ m/Element|Compound|Complex/ && !$lop->{hasState} && !$lop->{hasNumber}; $self->Error("The right-hand side of '_' must be a number, not %s", $rop->TYPE) unless $rop->class eq 'Number'; $self->{type} = $MOLECULE->{type}; + $self->getMolecule; +} + +# +# Get the order/elements/charge data for a molecule +# +sub getMolecule { + my $self = shift; + my $lop = $self->{lop}; + my $n = $self->{rop}->eval; + $self->{order} = [ @{ $lop->{order} } ]; + $self->{elements} = { %{ $lop->{elements} } }; + $self->{elements}{$_} *= $n for (keys %{ $self->{elements} }); + $self->{charge} = $n * $lop->{charge}; + $self->{factor} = $lop->{factor}; +} + +# +# Use compound check +# +sub equivalent { + my ($self, $other) = @_; + return $self->COMPOUND->equivalent($other); } # @@ -662,11 +989,32 @@ package context::Reaction::BOP::superscript; sub _check { my $self = shift; my ($lop, $rop) = ($self->{lop}, $self->{rop}); - $self->Error("The left-hand side of '^' must be an element, molecule, or complex, not %s", $lop->TYPE) - unless $lop->type =~ m/Element|Molecule|Compound|Complex/ && !$lop->{hasState}; + $self->Error("The left-hand side of '^' must be an element, molecule, or compound, not %s", $lop->TYPE) + unless $lop->type =~ m/Element|Molecule|Compound|Complex/ && !$lop->{hasState} && !$lop->{hasNumber}; $self->Error("The right-hand side of '^' must be %s, not %s", context::Reaction::UOP->TYPE, $rop->TYPE) - unless $rop->class eq 'UOP'; + unless $rop->TYPE eq 'a charge'; $self->{type} = $ION->{type}; + $self->getIon; +} + +# +# Get the order/elements/charge data for an ion +# +sub getIon { + my $self = shift; + my $lop = $self->{lop}; + $self->{order} = [ @{ $lop->{order} } ]; + $self->{elements} = { %{ $lop->{elements} } }; + $self->{charge} = $self->{rop}->eval; + $self->{factor} = $lop->{factor}; +} + +# +# Use compound check +# +sub equivalent { + my ($self, $other) = @_; + return $self->COMPOUND->equivalent($other); } # @@ -675,7 +1023,6 @@ sub _check { sub TeX { my $self = shift; my $left = $self->{lop}->TeX; - $left = "\\left($left\\right)" if $self->{lop}->type eq 'Compound'; return $left . '^{' . $self->{rop}->TeX . '}'; } @@ -686,7 +1033,6 @@ sub string { my $self = shift; my $left = $self->{lop}->string; my $right = $self->{rop}->string; - $left = "($left)" if $self->{lop}->type eq 'Compound'; $right = "($right)" unless $right eq '-' || $right eq '+'; return "$left^$right"; } @@ -698,7 +1044,7 @@ sub string { # General unary operator (minus and plus are subclasses of this). # package context::Reaction::UOP; -our @ISA = ('Parser::UOP'); +our @ISA = ('Parser::UOP', 'context::Reaction::common'); sub _check { my $self = shift; @@ -709,13 +1055,6 @@ sub _check { $self->Error("Can't use unary '%s' with %s", $name, $self->{op}->TYPE); } -# -# Unary operators produce numbers -# -sub isChemical {0} - -sub eval { context::Reaction::eval(@_) } - # # Two nodes are equivalent if their operands are equivalent # and they have the same operator @@ -731,23 +1070,35 @@ sub equivalent { return $self->{op}->equivalent($other->{op}); } +# +# Don't allow reduction of UOP plus (or minus) +# +sub isNeg {1} + # # Always put signs on the right # sub string { - my $self = shift; - my $n = $self->{op}->string($uop->{precedence}); - return ($n eq '1' ? '' : $n) . $self->{def}{string}; + my $self = shift; + my $uop = $self->{def}; + my $op = $uop->{string}; + my $isUnity = $self->context->flags->get('showUnity'); + my $mag = $self->{op}->string($uop->{precedence}); #magnitude + $mag = '' if ($mag eq '1' && !$isUnity); + return $mag . $op; } # # Always put signs on the right # sub TeX { - my $self = shift; - my $uop = $self->{def}; - my $op = (defined($uop->{TeX}) ? $uop->{TeX} : $uop->{string}); - return $self->{op}->TeX($uop->{precedence}) . $op; + my $self = shift; + my $uop = $self->{def}; + my $op = (defined($uop->{TeX}) ? $uop->{TeX} : $uop->{string}); + my $mag = $self->{op}->TeX($uop->{precedence}); + my $isUnity = $self->context->flags->get('showUnity'); + $mag = '' if ($mag eq '1' && !$isUnity); + return $mag . $op; } sub TYPE {'a charge'} @@ -759,6 +1110,8 @@ sub TeX { package context::Reaction::UOP::minus; our @ISA = ('context::Reaction::UOP'); +sub eval { -(shift->{op}{value}) } + ###################################################################### # # Positive numbers (for ion exponents) @@ -766,12 +1119,14 @@ package context::Reaction::UOP::minus; package context::Reaction::UOP::plus; our @ISA = ('context::Reaction::UOP'); +sub eval { shift->{op}{value} } + ###################################################################### # # Implements sums of compounds as a list # package context::Reaction::List::List; -our @ISA = ('Parser::List::List'); +our @ISA = ('Parser::List::List', 'context::Reaction::common'); # # Two sums are equivalent if their terms agree in any order. @@ -841,9 +1196,19 @@ sub TeX { return join(' + ', @coords); } +# +# Get the molecular form of each item in the list +# +sub molecularForm { + my $self = shift; + my $coords = [ map { $_->molecularForm } @{ $self->{coords} } ]; + my $paren = $self->context->parens->get('('); + return $self->new($self->{equation}, $coords, 0, $paren); +} + sub eval { context::Reaction::eval(@_) } -sub isChemical {1} +sub isSum {1} sub TYPE {'a sum of compounds'} @@ -852,7 +1217,7 @@ sub TeX { # Implements complexes as a list # package context::Reaction::List::Complex; -our @ISA = ('Parser::List::List'); +our @ISA = ('Parser::List::List', 'context::Reaction::common'); sub _check { my $self = shift; @@ -862,6 +1227,16 @@ sub _check { $self->Error("The contents of a complex must be an element, molecule, ion, or compound, not %s", $arg->TYPE) unless $arg->type =~ /Element|Molecule|Ion|Compound/ && !$arg->{hasState}; $self->{type} = $COMPLEX->{type}; + $self->getComplex; +} + +sub getComplex { + my $self = shift; + $self->{order} = []; + $self->{elements} = {}; + $self->{charge} = 0; + $self->{factor} = 1; + $self->combineData($self->{coords}[0]); } # @@ -875,6 +1250,14 @@ sub equivalent { return $self->{coords}[0]->equivalent($other->{coords}[0]); } +# +# Get the molecular form of each item in the list +# +sub molecularForm { + my $self = shift; + return $self->{coords}[0]->molecularForm; +} + sub eval { context::Reaction::eval(@_) } sub isChemical {1} diff --git a/macros/core/PGML.pl b/macros/core/PGML.pl index cbeb92fabd..96b0cb455f 100644 --- a/macros/core/PGML.pl +++ b/macros/core/PGML.pl @@ -1266,43 +1266,22 @@ sub format { } sub string { - my $self = shift; - my $block = shift; - my @strings = (); - my $string; - foreach my $item (@{ $block->{stack} }) { - $self->{item} = $item; - $self->{nl} = (!defined($strings[-1]) || $strings[-1] =~ m/\n$/ ? "" : "\n"); - for ($item->{type}) { - /indent/ && do { $string = $self->Indent($item); last }; - /align/ && do { $string = $self->Align($item); last }; - /par/ && do { $string = $self->Par($item); last }; - /list/ && do { $string = $self->List($item); last }; - /bullet/ && do { $string = $self->Bullet($item); last }; - /text/ && do { $string = $self->Text($item); last }; - /variable/ && do { $string = $self->Variable($item, $block); last }; - /command/ && do { $string = $self->Command($item); last }; - /math/ && do { $string = $self->Math($item); last }; - /answer/ && do { $string = $self->Answer($item); last }; - /bold/ && do { $string = $self->Bold($item); last }; - /italic/ && do { $string = $self->Italic($item); last }; - /heading/ && do { $string = $self->Heading($item); last }; - /quote/ && do { $string = $self->Quote($item, $strings[-1] || ''); last }; - /rule/ && do { $string = $self->Rule($item); last }; - /code/ && do { $string = $self->Code($item); last }; - /pre/ && do { $string = $self->Pre($item); last }; - /verbatim/ && do { $string = $self->Verbatim($item); last }; - /image/ && do { $string = $self->Image($item); last }; - /break/ && do { $string = $self->Break($item); last }; - /forced/ && do { $string = $self->Forced($item); last }; - /comment/ && do { $string = $self->Comment($item); last }; - /table/ && do { $string = $self->Table($item); last }; - /tag/ && do { $string = $self->Tag($item); last }; - PGML::Warning "Warning: unknown block type '$item->{type}' in " . ref($self) . "::format\n"; + my ($self, $block) = @_; + my $stack = $block->{stack}; + my @strings; + my $state = { block => $block, strings => \@strings, i => 0 }; + while ($state->{i} < @$stack) { + $state->{item} = $stack->[ $state->{i}++ ]; + $self->{nl} = (!defined($strings[-1]) || $strings[-1] =~ m/\n$/ ? '' : "\n"); + my $method = ucfirst($state->{item}{type}); + if (Value::can($self, $method)) { + my $string = $self->$method($state); + push(@strings, $string) unless !defined $string || $string eq ''; + } else { + PGML::Warning "Warning: unknown block type '$state->{item}{type}' in " . ref($self) . "::format\n"; } - push(@strings, $string) unless (!defined $string || $string eq ''); } - $self->{nl} = (!defined($strings[-1]) || $strings[-1] =~ m/\n$/ ? "" : "\n"); + $self->{nl} = (!defined($strings[-1]) || $strings[-1] =~ m/\n$/ ? '' : "\n"); return join('', @strings); } @@ -1313,7 +1292,7 @@ sub nl { return $nl; } -sub Escape { shift; shift } +sub escape { shift; shift } sub Indent { return "" } sub Align { return "" } @@ -1333,8 +1312,8 @@ sub nl { sub Comment { return "" } sub Table { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; return "[misplaced $item->{type}]" if $item->{hasWarning}; my @options; foreach my $option (@{ $item->{options} || [] }) { @@ -1344,7 +1323,7 @@ sub Table { my $table = []; my $row = []; for my $cell (@{ $item->{stack} }) { - push(@$row, $self->Table($cell)); + push(@$row, $self->Table({ item => $cell })); if ($cell->{hasStar}) { push(@$table, $row); $row = []; @@ -1355,13 +1334,13 @@ sub Table { } sub Tag { - my ($self, $item) = @_; - return $self->string($item); + my ($self, $state) = @_; + return $self->string($state->{item}); } sub Math { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $math = $item->{text}; if ($item->{parsed}) { my $context = $main::context{Typeset}; @@ -1386,8 +1365,8 @@ sub Math { } sub Answer { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $ans = $item->{answer}; my $rule; $item->{width} = length($item->{token}) - 2 if (!defined($item->{width})); @@ -1456,34 +1435,33 @@ sub Answer { } sub Command { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $text = $self->{parser}->replaceCommand($item); $text = PGML::LaTeX($text) if ($item->{hasStar} || 0) == 3; - $text = $self->Escape($text) unless $item->{hasStar}; + $text = $self->escape($text) unless $item->{hasStar}; return $text; } sub Variable { - my $self = shift; - my $item = shift; - my $cur = shift; - my $text = $self->{parser}->replaceVariable($item, $cur); + my ($self, $state) = @_; + my $item = $state->{item}; + my $text = $self->{parser}->replaceVariable($item, $state->{block}); $text = PGML::LaTeX($text) if ($item->{hasStar} || 0) == 3; - $text = $self->Escape($text) unless $item->{hasStar}; + $text = $self->escape($text) unless $item->{hasStar}; return $text; } sub Text { - my $self = shift; - my $item = shift; - my $text = $self->{parser}->replaceText($item); + my ($self, $state) = @_; + my $text = $self->{parser}->replaceText($state->{item}); $text =~ s/^\n+// if substr($text, 0, 1) eq "\n" && $self->nl eq ""; - return $self->Escape($text); + return $self->escape($text); } sub Image { - my ($self, $item) = @_; + my ($self, $state) = @_; + my $item = $state->{item}; my $text = $item->{text}; my $source = $item->{source}; my $width = $item->{width} || ''; @@ -1508,10 +1486,9 @@ sub Image { package PGML::Format::html; our @ISA = ('PGML::Format'); -sub Escape { - my $self = shift; - my $string = shift; - return "" unless defined $string; +sub escape { + my ($self, $string) = @_; + return '' unless defined $string; $string =~ s/&/\&/g; $string =~ s//>/g; @@ -1523,120 +1500,116 @@ sub Escape { } sub Indent { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; return $self->string($item) if $item->{indent} == 0; - my $em = 2.25 * $item->{indent}; - return - $self->nl - . '
    ' . "\n" - . $self->string($item) - . $self->nl - . "
    \n"; + my $em = 2.25 * $item->{indent}; + my $bottom = '0'; + my $next = $state->{block}{stack}[ $state->{i} ]; + if (defined $next && $next->{type} eq 'par') { + $bottom = '1em'; + $state->{i}++; + } + return main::tag('div', style => "margin:0 0 $bottom ${em}em", $self->string($item)); } sub Align { - my $self = shift; - my $item = shift; - return - $self->nl - . '
    ' . "\n" - . $self->string($item) - . $self->nl - . "
    \n"; + my ($self, $state) = @_; + my $item = $state->{item}; + return main::tag('div', style => "text-align:$item->{align};margin:0", $self->string($item)); } our %bullet = ( - bullet => 'ul type="disc"', - numeric => 'ol type="1"', - alpha => 'ol type="a"', - Alpha => 'ol type="A"', - roman => 'ol type="i"', - Roman => 'ol type="I"', - disc => 'ul type="disc"', - circle => 'ul type="circle"', - square => 'ul type="square"', + bullet => [ 'ul', 'list-style-type:disc;' ], + numeric => [ 'ol', 'list-style-type:decimal;' ], + alpha => [ 'ol', 'list-style-type:lower-alpha;' ], + Alpha => [ 'ol', 'list-style-type:upper-alpha;' ], + roman => [ 'ol', 'list-style-type:lower-roman;' ], + Roman => [ 'ol', 'list-style-type:upper-roman;' ], + disc => [ 'ul', 'list-style-type:disc;' ], + circle => [ 'ul', 'list-style-type:circle;' ], + square => [ 'ul', 'list-style-type:square;' ], ); sub List { - my $self = shift; - my $item = shift; - my $list = $bullet{ $item->{bullet} }; - return - $self->nl . '<' - . $list - . ' style="margin:0; padding-left:2.25em">' . "\n" - . $self->string($item) - . $self->nl . "\n"; + my ($self, $state) = @_; + my $item = $state->{item}; + my $list = $bullet{ $item->{bullet} }; + my $margin = '0'; + my $next = $state->{block}{stack}[ $state->{i} ]; + if (defined $next && $next->{type} eq 'par') { + $margin = '0 0 1em'; + $state->{i}++; + } + return main::tag($list->[0], style => "margin:$margin;padding-left:2.25em;$list->[1]", $self->string($item)); } sub Bullet { - my $self = shift; - my $item = shift; - return $self->nl . '
  • ' . $self->string($item) . "
  • \n"; + my ($self, $state) = @_; + my $next = $state->{block}{stack}[ $state->{i} ]; + my $style; + if (defined $next && $next->{type} eq 'par') { + $state->{i}++; + $style = 'margin-bottom:1em'; + } + return main::tag('li', $style ? (style => $style) : (), $self->string($state->{item})); } sub Code { - my $self = shift; - my $item = shift; - my $class = ($item->{class} ? ' class="' . $item->{class} . '"' : ""); - return $self->nl . '
    ' . $self->string($item) . "
    \n"; + my ($self, $state) = @_; + my $item = $state->{item}; + return main::tag( + 'pre', + style => 'margin:0', + main::tag('code', $item->{class} ? (class => $item->{class}) : (), $self->string($item)) + ); } sub Pre { - my $self = shift; - my $item = shift; - return $self->nl . '
    ' . $self->string($item) . "
    \n"; + my ($self, $state) = @_; + return main::tag('pre', style => 'margin:0', main::tag('code', $self->string($state->{item}))); } sub Heading { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $n = $item->{n}; my $text = $self->string($item); $text =~ s/^ +| +$//gm; - $text =~ s! +(
    )!$1!g; - return '' . $text . "\n"; + $text =~ s! +(
    )!$1!g; + return main::tag("h$n", style => 'margin:0', $text); } sub Par { - my $self = shift; - my $item = shift; - return $self->nl . '
    ' . "\n"; + my ($self, $state) = @_; + return main::tag('div', style => 'margin-top:1em', ''); } -sub Break {"
    \n"} +sub Break {'
    '} sub Bold { - my $self = shift; - my $item = shift; - return '' . $self->string($item) . ''; + my ($self, $state) = @_; + return main::tag('strong', $self->string($state->{item})); } sub Italic { - my $self = shift; - my $item = shift; - return '' . $self->string($item) . ''; + my ($self, $state) = @_; + return main::tag('em', $self->string($state->{item})); } our %openQuote = ('"' => "“", "'" => "‘"); our %closeQuote = ('"' => "”", "'" => "’"); sub Quote { - my $self = shift; - my $item = shift; - my $string = shift; - return $openQuote{ $item->{token} } if $string eq "" || $string =~ m/(^|[ ({\[\s])$/; - return $closeQuote{ $item->{token} }; + my ($self, $state) = @_; + my $string = $state->{strings}[-1] // ''; + return $openQuote{ $state->{item}{token} } if $string eq '' || $string =~ m/(^|[ ({\[\s])$/; + return $closeQuote{ $state->{item}{token} }; } sub Rule { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $width = '100%;'; my $height = '1px'; if (defined $item->{width}) { @@ -1650,34 +1623,34 @@ sub Rule { $height = $item->{size}; $height .= 'px' if ($height =~ /^\d*\.?\d+$/); } - return $self->nl - . main::tag( + return main::tag( + 'div', + main::tag( 'div', + style => "width:$width;display:inline-block;margin:0.3em auto", main::tag( - 'span', - style => "width:$width; display:inline-block; margin:0.3em auto", - main::tag( - 'hr', style => "width:$width; height:$height; background-color:currentColor; margin:0.3em auto;" - ) + 'hr', style => "width:$width;height:$height;background-color:currentColor;margin:0.3em auto;" ) - ); + ) + ); } sub Verbatim { - my $self = shift; - my $item = shift; - my $text = $self->Escape($item->{text}); - $text = "$text" if $item->{hasStar}; + my ($self, $state) = @_; + my $item = $state->{item}; + my $text = $self->escape($item->{text}); + $text = main::tag('code', $text) if $item->{hasStar}; return $text; } sub Math { - my $self = shift; - return main::general_math_ev3($self->SUPER::Math(@_)); + my ($self, $state) = @_; + return main::general_math_ev3($self->SUPER::Math($state)); } sub Tag { - my ($self, $item) = @_; + my ($self, $state) = @_; + my $item = $state->{item}; my %whitelist = (div => 1, span => 1); my @attributes = ref($item->{html}) eq 'ARRAY' ? @{ $item->{html} } : $item->{html}; my $tag = @attributes % 2 ? (shift @attributes // 'div') : 'div'; @@ -1720,32 +1693,31 @@ package PGML::Format::tex; '~' => '{\ttfamily\char126}', ); -sub Escape { - my $self = shift; - my $string = shift; - return "" unless defined($string); +sub escape { + my ($self, $string) = @_; + return '' unless defined($string); $string =~ s/(["\#\$%&<>\\^_\{|\}~])/$escape{$1}/eg; return $string; } sub Indent { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; return $self->string($item) if $item->{indent} == 0; my $em = 2.25 * $item->{indent}; return $self->nl . "{\\pgmlIndent\n" . $self->string($item) . $self->nl . "\\par}%\n"; } sub Align { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $align = uc(substr($item->{align}, 0, 1)) . substr($item->{align}, 1); return $self->nl . "{\\pgml${align}{}" . $self->string($item) . $self->nl . "\\par}%\n"; } sub List { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; return $self->nl . "{\\pgmlIndent\\let\\pgmlItem=\\pgml$item->{bullet}Item\n" @@ -1755,26 +1727,23 @@ sub List { } sub Bullet { - my $self = shift; - my $item = shift; - return $self->nl . "\\pgmlItem{}" . $self->string($item) . "\n"; + my ($self, $state) = @_; + return $self->nl . "\\pgmlItem{}" . $self->string($state->{item}) . "\n"; } sub Code { - my $self = shift; - my $item = shift; - return $self->nl . "{\\pgmlPreformatted\\ttfamily%\n" . $self->string($item) . "\\par}%\n"; + my ($self, $state) = @_; + return $self->nl . "{\\pgmlPreformatted\\ttfamily%\n" . $self->string($state->{item}) . "\\par}%\n"; } sub Pre { - my $self = shift; - my $item = shift; - return $self->nl . "{\\pgmlPreformatted%\n" . $self->string($item) . "\\par}%\n"; + my ($self, $state) = @_; + return $self->nl . "{\\pgmlPreformatted%\n" . $self->string($state->{item}) . "\\par}%\n"; } sub Heading { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $n = $item->{n}; my $text = $self->string($item); $text =~ s/^ +| +$//gm; @@ -1783,39 +1752,35 @@ sub Heading { } sub Par { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; return $self->nl . "\\vskip\\baselineskip\n"; } sub Break {"\\pgmlBreak\n"} sub Bold { - my $self = shift; - my $item = shift; - return "{\\bfseries{}" . $self->string($item) . "}"; + my ($self, $state) = @_; + return "{\\bfseries{}" . $self->string($state->{item}) . "}"; } sub Italic { - my $self = shift; - my $item = shift; - return "{\\itshape{}" . $self->string($item) . "}"; + my ($self, $state) = @_; + return "{\\itshape{}" . $self->string($state->{item}) . "}"; } our %openQuote = ('"' => "``", "'" => "`"); our %closeQuote = ('"' => "''", "'" => "'"); sub Quote { - my $self = shift; - my $item = shift; - my $string = shift; - return $openQuote{ $item->{token} } if $string eq "" || $string =~ m/(^|[ ({\[\s])$/; - return $closeQuote{ $item->{token} }; + my ($self, $state) = @_; + my $string = $state->{strings}[-1] // ''; + return $openQuote{ $state->{item}{token} } if $string eq '' || $string =~ m/(^|[ ({\[\s])$/; + return $closeQuote{ $state->{item}{token} }; } sub Rule { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $width = "100%"; my $height = "1"; $width = $item->{width} if defined $item->{width}; @@ -1829,20 +1794,21 @@ sub Rule { } sub Verbatim { - my $self = shift; - my $item = shift; - my $text = $self->Escape($item->{text}); + my ($self, $state) = @_; + my $item = $state->{item}; + my $text = $self->escape($item->{text}); $text = "{\\tt{}$text}" if $item->{hasStar}; return $text; } sub Math { - my $self = shift; - return main::general_math_ev3($self->SUPER::Math(@_)); + my ($self, $state) = @_; + return main::general_math_ev3($self->SUPER::Math($state)); } sub Tag { - my ($self, $item) = @_; + my ($self, $state) = @_; + my $item = $state->{item}; my ($tex_begin, $tex_end); if (ref($item->{tex}) eq 'ARRAY') { ($tex_begin, $tex_end) = @{ $item->{tex} }; @@ -1858,10 +1824,9 @@ sub Tag { package PGML::Format::ptx; our @ISA = ('PGML::Format'); -sub Escape { - my $self = shift; - my $string = shift; - return "" unless defined $string; +sub escape { + my ($self, $string) = @_; + return '' unless defined $string; $string =~ s/&/&/g; $string =~ s//>/g; @@ -1870,15 +1835,14 @@ sub Escape { # No indentation for PTX sub Indent { - my $self = shift; - my $item = shift; - return $self->string($item); + my ($self, $state) = @_; + return $self->string($state->{item}); } # No align for PTX sub Align { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; return "\n" . $self->string($item); } @@ -1895,21 +1859,20 @@ sub Align { ); sub List { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $list = $bullet{ $item->{bullet} }; return $self->nl . '<' . $list . '>' . "\n" . $self->string($item) . $self->nl . "\n"; } sub Bullet { - my $self = shift; - my $item = shift; - return $self->nl . '
  • ' . $self->string($item) . '
  • '; + my ($self, $state) = @_; + return $self->nl . '
  • ' . $self->string($state->{item}) . '
  • '; } sub Code { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $code = ($self->string($item) =~ /\n/) ? $self->nl @@ -1925,8 +1888,7 @@ sub Code { } sub Pre { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; ## PGML pre can have stylized contents like bold, ## and PTX pre cannot have element children return "\n"; @@ -1934,52 +1896,47 @@ sub Pre { # PreTeXt can't use headings. sub Heading { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; return "\n" . $self->string($item); } sub Par { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; return $self->nl . "\n"; } sub Break {"\n\n"} sub Bold { - my $self = shift; - my $item = shift; - return '' . $self->string($item) . ''; + my ($self, $state) = @_; + return '' . $self->string($state->{item}) . ''; } sub Italic { - my $self = shift; - my $item = shift; - return '' . $self->string($item) . ''; + my ($self, $state) = @_; + return '' . $self->string($state->{item}) . ''; } our %openQuote = ('"' => "", "'" => ""); our %closeQuote = ('"' => "", "'" => ""); sub Quote { - my $self = shift; - my $item = shift; - my $string = shift; - return $openQuote{ $item->{token} } if $string eq "" || $string =~ m/(^|[ ({\[\s])$/; - return $closeQuote{ $item->{token} }; + my ($self, $state) = @_; + my $string = $state->{strings}[-1] // ''; + return $openQuote{ $state->{item}{token} } if $string eq '' || $string =~ m/(^|[ ({\[\s])$/; + return $closeQuote{ $state->{item}{token} }; } # No rule for PTX sub Rule { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; return "\n"; } sub Verbatim { - my $self = shift; - my $item = shift; + my ($self, $state) = @_; + my $item = $state->{item}; my $text = $item->{text}; if ($item->{hasStar}) { #Don't escape most content. Just < and & @@ -1995,12 +1952,13 @@ sub Verbatim { } sub Math { - my $self = shift; - return main::general_math_ev3($self->SUPER::Math(@_)); + my ($self, $state) = @_; + return main::general_math_ev3($self->SUPER::Math($state)); } sub Tag { - my ($self, $item) = @_; + my ($self, $state) = @_; + my $item = $state->{item}; my @args = ref($item->{ptx}) eq 'ARRAY' ? @{ $item->{ptx} } : $item->{ptx}; if (my $tag = shift @args) { return NiceTables::tag($self->string($item), $tag, @args); diff --git a/macros/core/PGanswermacros.pl b/macros/core/PGanswermacros.pl index d858e432c1..5ff746d85b 100644 --- a/macros/core/PGanswermacros.pl +++ b/macros/core/PGanswermacros.pl @@ -1638,8 +1638,6 @@ sub avg_problem_grader { my %problem_result = (score => 0, errors => '', type => 'avg_problem_grader', msg => ''); - $problem_result{msg} = maketext('You can earn partial credit on this problem.') if keys %$answers > 1; - # Return unless answers have been submitted. return (\%problem_result, $problem_state) unless $form_options{answers_submitted} == 1; diff --git a/macros/core/PGbasicmacros.pl b/macros/core/PGbasicmacros.pl index 8e0ca91060..6437d71e92 100644 --- a/macros/core/PGbasicmacros.pl +++ b/macros/core/PGbasicmacros.pl @@ -223,7 +223,7 @@ =head2 Answer blank macros: When entering radio buttons using the "NAMED" format, you should use NAMED_ANS_RADIO button for the first button and then use NAMED_ANS_RADIO_EXTENSION for the remaining buttons. NAMED_ANS_RADIO requires a -matching answer evalutor, while NAMED_ANS_RADIO_EXTENSION does not. The name +matching answer evaluator, while NAMED_ANS_RADIO_EXTENSION does not. The name used for NAMED_ANS_RADIO_EXTENSION should match the name used for NAMED_ANS_RADIO (and the associated answer evaluator). @@ -1319,8 +1319,8 @@ sub SPACE { sub EBOLD { MODES(TeX => '}', HTML => '', PTX => ''); } sub BLABEL { MODES(TeX => '', HTML => '', PTX => ''); } -sub BITALIC { MODES(TeX => '{\\it ', HTML => '', PTX => ''); } -sub EITALIC { MODES(TeX => '} ', HTML => '', PTX => ''); } +sub BITALIC { MODES(TeX => '{\\it ', HTML => '', PTX => ''); } +sub EITALIC { MODES(TeX => '} ', HTML => '', PTX => ''); } sub BUL { MODES(TeX => '\\underline{', HTML => '', PTX => ''); } sub EUL { MODES(TeX => '}', HTML => '', PTX => ''); } @@ -2634,13 +2634,9 @@ =head2 Sorting and other list macros # uniq gives unique elements of a list: sub uniq { - my @in = @_; - my %temp = (); - while (@in) { - $temp{ shift(@in) }++; - } - my @out = keys %temp; # sort is causing trouble with Safe.?? - @out; + my @in = @_; + my %seen; + return grep { !$seen{$_}++ } @in; } sub lex_sort { @@ -2932,9 +2928,9 @@ sub image { $image_item->{width} = $width if $out_options{width}; $image_item->{height} = $height if $out_options{height}; $image_item->{tex_size} = $tex_size if $out_options{tex_size}; - $image_item->axes->style(aria_description => shift @alt_list) if $out_options{alt}; if ($image_item->ext eq 'html') { + $image_item->axes->style(aria_description => shift @alt_list) if $out_options{alt}; $image_item->{description_details} = $description_details; push(@output_list, $image_item->draw); next; diff --git a/macros/graph/AppletObjects.pl b/macros/graph/AppletObjects.pl index e8ad53a762..fc2409f4f8 100644 --- a/macros/graph/AppletObjects.pl +++ b/macros/graph/AppletObjects.pl @@ -69,54 +69,23 @@ =head2 insertAll # Inserts both header text and object text. sub insertAll { - my $self = shift; - my %options = @_; - - my $includeAnswerBox = (defined($options{includeAnswerBox}) && $options{includeAnswerBox} == 1) ? 1 : 0; - - my $reset_button = $options{reinitialize_button} || 0; - - # Get data to be interpolated into the HTML code defined in this subroutine. - # This consists of the name of the applet and the names of the routines to get and set State - # of the applet (which is done every time the question page is refreshed and to get and set - # Config which is the initial configuration the applet is placed in when the question is - # first viewed. It is also the state which is returned to when the reset button is pressed. + my ($self, %options) = @_; # Prepare html code for storing state. my $appletName = $self->appletName; # The name of the hidden "answer" blank storing state. $self->{stateInput} = "$main::PG->{QUIZ_PREFIX}${appletName}_state"; - my $appletStateName = $self->{stateInput}; - - # Names of routines for this applet - my $getState = $self->getStateAlias; - my $setState = $self->setStateAlias; - my $getConfig = $self->getConfigAlias; - my $setConfig = $self->setConfigAlias; - my $base64_initialState = $self->base64_encode($self->initialState); # This insures that the state will be saved from one invocation to the next. - # FIXME -- with PGcore the persistant data mechanism can be used instead - main::RECORD_FORM_LABEL($appletStateName); - my $answer_value = ''; - - # Implement the sticky answer mechanism for maintaining the applet state when the question - # page is refreshed This is important for guest users for whom no permanent record of - # answers is recorded. - if (defined(${$main::inputs_ref}{$appletStateName}) && ${$main::inputs_ref}{$appletStateName} =~ /\S/) { - $answer_value = ${$main::inputs_ref}{$appletStateName}; - } elsif (defined($main::rh_sticky_answers->{$appletStateName})) { - $answer_value = shift(@{ $main::rh_sticky_answers->{$appletStateName} }); + my $answer_value = ${$main::inputs_ref}{ $self->{stateInput} } // ''; + if ($answer_value !~ /\S/ && defined(my $persistent_data = main::persistent_data($self->{stateInput}))) { + $answer_value = $persistent_data; } $answer_value =~ tr/\\$@`//d; # Make sure student answers cannot be interpolated by e.g. EV3 $answer_value =~ s/\s+/ /g; # Remove excessive whitespace from student answer # Regularize the applet's state which could be in either XML format or in XML format encoded by base64. - # In rare cases it might be simple string. Protect against that by putting xml tags around the state. - # The result: - # $base_64_encoded_answer_value -- a base64 encoded xml string - # $decoded_answer_value -- an xml string - + # In rare cases it might be a simple string. Protect against that by putting xml tags around the state. my $base_64_encoded_answer_value; my $decoded_answer_value; if ($answer_value =~ /<\??xml/i) { @@ -125,10 +94,8 @@ sub insertAll { } else { $decoded_answer_value = $self->base64_decode($answer_value); if ($decoded_answer_value =~ /<\??xml/i) { - # Great, we've decoded the answer to obtain an xml string $base_64_encoded_answer_value = $answer_value; } else { - #WTF?? apparently we don't have XML tags $answer_value = "$answer_value"; $base_64_encoded_answer_value = $self->base64_encode($answer_value); $decoded_answer_value = $answer_value; @@ -136,25 +103,33 @@ sub insertAll { } $base_64_encoded_answer_value =~ s/\r|\n//g; # Get rid of line returns + main::persistent_data($self->{stateInput} => $base_64_encoded_answer_value); + # Construct the reset button string (this is blank if the button is not to be displayed). - my $reset_button_str = $reset_button - ? qq!
    ! + my $reset_button_str = $options{reinitialize_button} + ? main::tag( + 'button', + type => 'button', + class => 'btn btn-primary applet-reset-btn mt-3', + 'data-applet-name' => $appletName, + $main::PG->maketext('Return this question to its initial state') + ) : ''; - # Combine the state_input_button and the reset button into one string. - my $state_storage_html_code = qq!! - . qq!! - . $reset_button_str; + # Construct the state storage hidden input. + my $state_storage_html_code = main::tag( + 'input', + type => 'hidden', + name => $self->{stateInput}, + id => $self->{stateInput}, + value => $base_64_encoded_answer_value + ); # Construct the answerBox (if it is requested). This is a default input box for interacting # with the applet. It is separate from maintaining state but it often contains similar # data. Additional answer boxes or buttons can be defined but they must be explicitly # connected to the applet with additional JavaScript commands. - my $answerBox_code = $includeAnswerBox - ? $answerBox_code = main::NAMED_HIDDEN_ANS_RULE($self->{answerBoxAlias}, 50) - : ''; + my $answerBox_code = $options{includeAnswerBox} ? main::NAMED_HIDDEN_ANS_RULE($self->{answerBoxAlias}, 50) : ''; # Insert header material main::HEADER_TEXT($self->insertHeader()); @@ -162,7 +137,7 @@ sub insertAll { # Return HTML or TeX strings to be included in the body of the page return main::MODES( TeX => ' {\bf ' . $self->{type} . ' applet } ', - HTML => $self->insertObject . $main::BR . $state_storage_html_code . $answerBox_code, + HTML => $self->insertObject . $state_storage_html_code . $reset_button_str . $answerBox_code, PTX => ' applet ' ); } diff --git a/macros/graph/parserGraphTool.pl b/macros/graph/parserGraphTool.pl index 0b05d1e3ec..ee27badee3 100644 --- a/macros/graph/parserGraphTool.pl +++ b/macros/graph/parserGraphTool.pl @@ -331,8 +331,12 @@ =head1 OPTIONS =item scaleX, scaleY (Default: C<< scaleX => 1, scaleY => 1 >>) -These are the scale of the ticks on the x and y axes. That is the distance between two -successive ticks on the axis (including both major and minor ticks). +These are the scale of the tick distances on the x and y axes. That is the distance between two +successive major ticks on the axis will be the product of the ticks distance and the scale. +This is usually used in conjunction with the C and C options. For +example, if C is 2, C is 3, and C is 'a', then the first +positive major x axis tick will occur 6 units to the right and be labeled '2a', and the next +major x axis tick will occur 12 units to the right and be labeled '4a'. =item scaleSymbolX, scaleSymbolY (Default: C<< scaleSymbolX => '', scaleSymbolY => '' >>) @@ -1195,6 +1199,47 @@ sub generateHTMLAnswerGraph { END_SCRIPT } +# This is essentially copied from contextFraction.pl. +sub continuedFraction { + my ($x) = @_; + + my $step = $x; + my $n = int($step); + my ($h0, $h1, $k0, $k1) = (1, $n, 0, 1); + + while ($step != $n) { + $step = 1 / ($step - $n); + $n = int($step); + my ($newh, $newk) = ($n * $h1 + $h0, $n * $k1 + $k0); + last if $newk > 10**8; # Bail if the denominator is skyrocketing out of control. + ($h0, $h1, $k0, $k1) = ($h1, $newh, $k1, $newk); + } + + return ($h1, $k1); +} + +sub formatTickLabelText { + my ($self, $value, $axis) = @_; + my $coordinateHintsType = $self->{"coordinateHintsType$axis"} // $self->{coordinateHintsType}; + if ($coordinateHintsType eq 'fraction' || $coordinateHintsType eq 'mixed') { + my ($num, $den) = continuedFraction(abs($value)); + if ($num && $den != 1 && !($num == 1 && $den == 1)) { + if ($coordinateHintsType eq 'fraction' || $num < $den) { + $value = ($value < 0 ? '-' : '') . "\\frac{$num}{$den}"; + } else { + my $int = int($num / $den); + my $properNum = $num % $den; + $value = ($value < 0 ? '-' : '') . "$int\\frac{$properNum}{$den}"; + } + } + } + my $scaleSymbol = $self->{"scaleSymbol$axis"} // ''; + return + $value eq '0' ? '0' + : $scaleSymbol ? ($value eq '1' ? $scaleSymbol : $value eq '-1' ? "-$scaleSymbol" : "$value$scaleSymbol") + : $value; +} + sub generateTeXGraph { my ($self, %options) = @_; @@ -1203,7 +1248,7 @@ sub generateTeXGraph { return &{ $self->{printGraph} } if ref($self->{printGraph}) eq 'CODE'; - my @size = $self->{numberLine} ? (500, 100) : (500, 500); + my @size = $self->{numberLine} ? (500, 110) : (500, 500); my $graph = main::createTikZImage(); $graph->tikzLibraries('arrows.meta'); @@ -1264,32 +1309,71 @@ sub generateTeXGraph { } # Horizontal axis ticks and labels - my @xTicks = grep { $_ < $self->{bBox}[2] } - map { $_ * $self->{ticksDistanceX} } (1 .. $self->{bBox}[2] / $self->{ticksDistanceX}); - push(@xTicks, + my @xTicks = grep { $_ > $self->{bBox}[0] } - map { -$_ * $self->{ticksDistanceX} } (1 .. -$self->{bBox}[0] / $self->{ticksDistanceX})); + map { -$_ * $self->{ticksDistanceX} * $self->{scaleX} } + reverse(1 .. -$self->{bBox}[0] / ($self->{ticksDistanceX} * $self->{scaleX})); + my $numNegative = @xTicks; # Add zero if this is a number line and 0 is in the given range. - push(@xTicks, 0) if ($self->{numberLine} && $self->{bBox}[2] > 0 && $self->{bBox}[0] < 0); + push(@xTicks, 0) if $self->{numberLine} && $self->{bBox}[2] > 0 && $self->{bBox}[0] < 0; + push(@xTicks, + grep { $_ < $self->{bBox}[2] } + map { $_ * $self->{ticksDistanceX} * $self->{scaleX} } + (1 .. $self->{bBox}[2] / ($self->{ticksDistanceX} * $self->{scaleX}))); my $tickSize = $self->{numberLine} ? '9' : '5'; $tikz .= - "\\foreach \\x in {" - . join(',', @xTicks) - . "}{\\draw[thin] (\\x,${tickSize}pt) -- (\\x,-${tickSize}pt) node[below]{\\(\\x\\)};}\n" + "\\foreach \\x/\\label in {" + . join(',', map { "$_/" . $self->formatTickLabelText($_ / $self->{scaleX}, 'X') } @xTicks) + . "}{\\draw[thin, opacity = 0.5] (\\x,${tickSize}pt) -- (\\x,-${tickSize}pt) " + . "node[baseline, yshift = -15pt, opacity = 1]{\\(\\label\\)};}\n" if (@xTicks); + # Add horizontal axis minor ticks. + splice(@xTicks, $numNegative, 0, 0) if !$self->{numberLine} || ($self->{bBox}[0] <= 0 && $self->{bBox}[2] >= 0); + unshift(@xTicks, $xTicks[0] - $self->{ticksDistanceX} * $self->{scaleX}) if $self->{bBox}[0] < 0; + push(@xTicks, $xTicks[-1] + $self->{ticksDistanceX} * $self->{scaleX}) if $self->{bBox}[2] > 0; + my @xMinorTicks; + my $xMinorTickDelta = $self->{ticksDistanceX} * $self->{scaleX} / ($self->{minorTicksX} + 1); + for my $tickIndex (0 .. $#xTicks - 1) { + push(@xMinorTicks, map { $xTicks[$tickIndex] + $_ * $xMinorTickDelta } 1 .. $self->{minorTicksX}); + } + $tikz .= + "\\foreach \\x in {" + . join(',', @xMinorTicks) + . "}{\\draw[thin, opacity = 0.5] (\\x,0) -- (\\x,-${tickSize}pt);}\n" + if (@xMinorTicks); + # Vertical axis ticks and labels unless ($self->{numberLine}) { - my @yTicks = grep { $_ < $self->{bBox}[1] } - map { $_ * $self->{ticksDistanceY} } (1 .. $self->{bBox}[1] / $self->{ticksDistanceY}); - push(@yTicks, + my @yTicks = grep { $_ > $self->{bBox}[3] } - map { -$_ * $self->{ticksDistanceY} } (1 .. -$self->{bBox}[3] / $self->{ticksDistanceY})); + map { -$_ * $self->{ticksDistanceY} * $self->{scaleY} } + reverse(1 .. -$self->{bBox}[3] / ($self->{ticksDistanceY} * $self->{scaleY})); + my $numNegative = @yTicks; + push(@yTicks, + grep { $_ < $self->{bBox}[1] } + map { $_ * $self->{ticksDistanceY} * $self->{scaleY} } + (1 .. $self->{bBox}[1] / ($self->{ticksDistanceY} * $self->{scaleY}))); $tikz .= - "\\foreach \\y in {" - . join(',', @yTicks) - . "}{\\draw[thin] (5pt,\\y) -- (-5pt,\\y) node[left]{\$\\y\$};}\n" + "\\foreach \\y/\\label in {" + . join(',', map { "$_/" . $self->formatTickLabelText($_ / $self->{scaleY}, 'Y') } @yTicks) + . "}{\\draw[thin, opacity = 0.5] (5pt,\\y) -- (-5pt,\\y) node[left, opacity = 1]{\$\\label\$};}\n" if (@yTicks); + + # Add vertical axis minor ticks. + splice(@yTicks, $numNegative, 0, 0); + unshift(@yTicks, $yTicks[0] - $self->{ticksDistanceY} * $self->{scaleY}) if $self->{bBox}[3] < 0; + push(@yTicks, $yTicks[-1] + $self->{ticksDistanceY} * $self->{scaleY}) if $self->{bBox}[1] > 0; + my @yMinorTicks; + my $yMinorTickDelta = $self->{ticksDistanceY} * $self->{scaleY} / ($self->{minorTicksY} + 1); + for my $tickIndex (0 .. $#yTicks - 1) { + push(@yMinorTicks, map { $yTicks[$tickIndex] + $_ * $yMinorTickDelta } 1 .. $self->{minorTicksY}); + } + $tikz .= + "\\foreach \\y in {" + . join(',', @yMinorTicks) + . "}{\\draw[thin, opacity = 0.5] (0, \\y) -- (-5pt, \\y);}\n" + if @yMinorTicks; } # Border box diff --git a/macros/graph/plots.pl b/macros/graph/plots.pl index 64ad60e51a..de245bd660 100644 --- a/macros/graph/plots.pl +++ b/macros/graph/plots.pl @@ -7,9 +7,9 @@ =head1 DESCRIPTION This macro creates a Plots object that is used to add data of different elements of a 2D plot, then draw the plot. The plots can be drawn using different -formats. Currently C (using PGFplots), C, and the legacy C -graphics format are available. Default is to use C for HTML output and -C for hardcopy. +formats. Currently C (using PGFplots) and C graphics format are +available. The default is to use C for HTML output and C for +hardcopy. Note, due to differences in features between C and C, not all options work with both. @@ -36,6 +36,41 @@ =head1 USAGE Options that start with C configure the xaxis, options that start with C configure the yaxis, and all other options are Axes styles. +In addition to the options for configuring the L, the +following options may be passed. + +=over + +=item width + +The width of the image. The default value of this option is the +C value in the environment, or C<350> if that is not set or +is C<0>. + +=item height + +The height of the image. The default value of this option is the C. If +this is explicitly set to a positive integer value, then that height will be +used. If this is C, then the height of the image will be automatically +determined. If the C style setting for the C object +is C<1>, then the height will be computed to maintain the aspect ratio of the +image. Otherwise it will be set to value of the C option. + +=item tex_size + +The size of the image in hardcopy. See L +for more details on this setting. + +=item rounded_corners + +Determines if the image will be displayed in a rectangle with rounded corners +or sharp corners. The default value for this option is C<0> which means that +sharp corners will be used. If this is set to C<1>, then rounded corners will +be used. Note that this may not work well for images that have elements of the +plot near or in the corners. + +=back + Add a function and other objects to the plot. $plot->add_function('-16t^2 + 80t + 384', 't', 0, 8, color => 'blue', width => 3); @@ -59,11 +94,11 @@ =head2 DATASETS can be added individually, or multiple at once as shown: # Add a single dataset - $plot->add_dataset([$x1, $y1], [$x2, $y2], ..., [$xn, $yn], @options); + $plot->add_dataset([$x1, $y1], [$x2, $y2], ..., [$xn, $yn], %options); # Add multiple datasets with single call $plot->add_dataset( - [[$x11, $y11], [$x12, $y12], ..., [$x1n, $y1n], @options1], - [[$x21, $y21], [$x22, $y22], ..., [$x2m, $y2m], @options2], + [[$x11, $y11], [$x12, $y12], ..., [$x1n, $y1n], %options1], + [[$x21, $y21], [$x22, $y22], ..., [$x2m, $y2m], %options2], ... ); @@ -90,11 +125,11 @@ =head2 PLOT FUNCTIONS functions can be added individually or multiple at once: # Add a single function - $plot->add_function($function, $variable, $min, $max, @options) + $plot->add_function($function, $variable, $min, $max, %options) # Add multiple functions $plot->add_function( - [$function1, $variable1, $min1, $max1, @options1], - [$function2, $variable2, $min2, $max2, @options2], + [$function1, $variable1, $min1, $max1, %options1], + [$function2, $variable2, $min2, $max2, %options2], ... ); @@ -162,41 +197,81 @@ =head2 PLOT FUNCTIONS =head2 PLOT MULTIPATH FUNCTIONS -A multipath function is defined using multiple parametric paths pieced together into into a single -curve, whose primary use is to create a closed region to be filled using multiple boundaries. -This is done by providing a list of parametric functions, the name of the parameter, and a list -of options. +A multipath function is defined using multiple parametric paths and points +pieced together into into a single curve. This is done by providing a list of +parametric functions and points, the name of the parameter, and a list of +options. A parametric function is specified by a reference to an array +containing an x function, a y function, the minimum value for the parameter, and +the maximum value for the parameter, followed by options. A point is specified +by a reference to an array containing the coordinates of the point. One reason +for creating a multipath is to create a closed region to be filled using +multiple boundaries. $plot->add_multipath( [ - [ $function_x1, $function_y1, $min1, $max1 ], - [ $function_x2, $function_y2, $min2, $max2 ], + [ $function_x1, $function_y1, $min1, $max1, %path_options1 ], + [ $function_x2, $function_y2, $min2, $max2, %path_options2 ], + [ $point_x1, $point_x2 ] + [ $function_x3, $function_y3, $min3, $max3, %path_options3 ], ... ], $variable, %options ); -The paths have to be listed in the order they are followed, but the minimum/maximum values -of the parameter can match the parametrization. The following example creates a sector of -radius 5 between pi/4 and 3pi/4, by first drawing the line (0,0) to (5sqrt(2),5/sqrt(2)), -then the arc of the circle of radius 5 from pi/4 to 3pi/4, followed by the final line from -(-5sqrt(2), 5sqrt(2)) back to the origin. +Note that C<%path_options> can be specified for each path. At this point, the +only supported individual path option is C, if specified, then that +number of steps will be used for that path in the TikZ format. If not specified +the number of steps for the multipath will be used. That defaults to 30, but can +be changed by passing the C option in the general C<%options> for the +multipath. + +The paths have to be listed in the order they are followed, but the +minimum/maximum values of the parameter can match the parametrization. The +following example creates a sector of radius 5 between pi/4 and 3pi/4, by first +drawing the arc of the circle of radius 5 from pi/4 to 3pi/4, followed by the +line from (-5 sqrt(2), 5 sqrt(2)) to the origin, and then drawing the line from +the origin to (5 sqrt(2), 5 sqrt(2)). $plot->add_multipath( [ - [ 't', 't', 0, '5/sqrt(2)' ], - [ '5cos(t)', '5sin(t)', 'pi/4', '3pi/4' ], - [ '-t', 't', '5/sqrt(2)', 0 ], + [ '5cos(t)', '5sin(t)', 'pi/4', '3pi/4' ], + [ 0, 0 ], ], 't', color => 'green', fill => 'self', + cycle => 1 + ); + +Note that the ending point of one path does not need to be the same as the +starting point of the next. In this case a line segment will connect the end of +the first path to the start of the next. Additionally, if C<< cycle => 1 >> is +added to the C<%options> for the multipath, and the last path does not end where +the first path starts, then a line segment will connect the end of the last path +to the start of the first path. For example, the following path draws the top +half of a circle of radius two centered at the point (0, 2), followed by the +line segment from (-2, 0) to (2, 0). The line segment from (-2, 2) to (-2, 0) is +implicitly added to connect the end of the first path to the beginning of the +second path. The cycle option is added to close the path with the line segment +from (2, 0) to (2, 2). Note that drawing of the line is optimized by using only +2 steps, and the fill region is drawn on the "axis background" layer. + + $plot->add_multipath( + [ + [ '2 cos(t) + 5', '2 sin(t) - 5', '0', 'pi' ], + [ 't', '-8', '3', '7', steps => 2 ] + ], + 't', + color => 'green', + fill => 'self', + fill_layer => 'axis background', + cycle => 1 ); =head2 PLOT CIRCLES -Circles can be added to the plot by specifing its center and radius using the +Circles can be added to the plot by specifying its center and radius using the C<< $plot->add_circle >> method. This can either be done either one at a time or multiple at once. @@ -209,15 +284,17 @@ =head2 PLOT CIRCLES =head2 PLOT ARCS -Arcs (or a portion of a circle) can be plotted using the C<< $plot->add_arc >> method. -This method takes three points. The first point is where the arc starts, the second point -is the center of the circle, and the third point specifies the ray from the center of -the circle the arc ends. Arcs always go in the counter clockwise direction. +Arcs (or a portion of a circle) can be plotted using the C<< $plot->add_arc >> +method. This method takes three points. The first point is the center of the +circle, the second point is where the arc starts, and the arc ends at the point +on the circle that intersects the ray from the center of the circle pointing in +the direction of the third point. Arcs always go in the counter clockwise +direction. $plot->add_arc([$start_x, $start_y], [$center_x, $center_y], [$end_x, $end_y], %options); $plot->add_arc( - [[$start_x1, $start_y1], [$center_x1, $center_y1], [$end_x1, $end_y1], %options1], - [[$start_x2, $start_y2], [$center_x2, $center_y2], [$end_x2, $end_y2], %options2], + [[$center_x1, $center_y1], [$start_x1, $start_y1], [$end_x1, $end_y1], %options1], + [[$center_x2, $center_y2], [$start_x2, $start_y2], [$end_x2, $end_y2], %options2], ... ); @@ -262,8 +339,9 @@ =head2 PLOT VECTOR FIELDS =item xsteps, ysteps -The number of arrows drawn in each direction. Note, that in TikZ output, this cannot be -set individually so only C is used. Default: 15 +The number of steps from the domain minimum to the domain maximum at which to +draw arrows. The number of arrows drawn will be one more than the number of +steps. Default: 15 =item scale @@ -303,14 +381,17 @@ =head2 DATASET OPTIONS =item width -The line width of the plot. Default: 1 +The line width of the plot. Default: 2 =item linestyle -Linestyle can be one of 'solid', 'dashed', 'dotted', 'short dashes', 'long dashes', -'long medium dashes' (alternates between long and medium dashes), or 'none'. If set -to 'none', only the points are shown (see marks for point options) For convince -underscores can also be used, such as 'long_dashes'. Default: 'solid' +Linestyle can be one of 'solid', 'dashed', 'dotted', 'short dashes', 'long +dashes', 'long medium dashes' (alternates between long and medium dashes), or +'none'. If set to 'none', then the curve will not be drawn. This can be used to +show only points by setting the C option (see C for point +options), or to only show a fill region by setting the C option. For +convenience underscores can also be used, such as 'long_dashes'. +Default: 'solid' =item marks @@ -322,8 +403,7 @@ =head2 DATASET OPTIONS =item mark_size Configure the size of the marks (if shown). The size is a natural number, -and represents the point (pt) size of the mark. If the size is 0, the -default size is used. Default: 0 +and represents the point (pt) size of the mark. Default: 2 =item start_mark @@ -338,7 +418,7 @@ =head2 DATASET OPTIONS =item arrow_size Sets the arrow head size for C or C arrows. -Default: 10 +Default: 8 =item name @@ -352,8 +432,9 @@ =head2 DATASET OPTIONS If set to 'self', the object fills within itself, best used with closed datasets. If set to 'xaxis', this will fill the area between the curve and the x-axis. If set to another non-empty string, this is the name of -the other dataset to fill against. The C attribute must be set to -fill between the 'xaxis' or another curve. +the other dataset to fill against. Note that the other dataset must be +created first before attempting to fill against it. The C attribute +must be set to fill between the 'xaxis' or another curve. The following creates a filled rectangle: @@ -392,6 +473,23 @@ =head2 DATASET OPTIONS fill_opacity => 0.2, ); +The following fills the area between the two curves x = 4 - y^2 and x = y^2 - 4, +and only fills in the area between y=-2 and y=2: + + $plot->add_function(['4 - y^2', 'y'], 'y', -3, 3, + color => 'blue', + name => 'A' + ); + $plot->add_function(['y^2 - 4', 'y'] 'y', -3, 3, + color => 'blue', + name => 'B', + fill => 'A', + fill_min_y => -2, + fill_max_y => 2, + fill_color => 'green', + fill_opacity => 0.2, + ); + =item fill_color The color used when filling the region. Default: C @@ -406,6 +504,49 @@ =head2 DATASET OPTIONS not defined, then the fill will use the full domain of the function. Default: undefined +=item fill_min_y, fill_max_y + +The minimum and maximum y-values to fill between. If either of these are +not defined, then the fill will use the full y range of the function. +Default: undefined + +Note that fill_min, fill_max, fill_min_y, and fill_max_y can be defined, and +this will result in the region filled being restricted to the rectangle defined +by those ranges. + +=item fill_reverse + +This should only be used if the TikZ output is filling the wrong region when +filling between two curves. By default the pgfplots fillbetween library +attempts to autodetect if one of the curves needs to be reversed in order to +obtain the fill region between the curves. However, the heuristic used for the +autodetection is sometimes wrong. Particularly when filling between two +functions x of y using the C and C options. In that case +set this option to either 0 or 1. If set to 1, then a reversal of one of the +curves is forced. If set to 0, then no reversal will occur. If this is unset, +then the pgfplots fillbetween library will attempt to autodetect if a reversal +is needed. If the correct region is being filled as compared to the JSXGraph +output, then leave this unset. Default: undefined + +=item layer + +The layer to draw on. Available layers are "axis background", "axis grid", +"axis ticks", "axis lines", "axis tick labels", "pre main", "main", +"axis descriptions", and "axis foreground". Note that the default order is the +order just given (from back to front). However, if C is true for +the axes, then "pre main" and "main" are after "axis background" and before +"axis grid". If this is undefined, then the default drawing layer will be used. +Default: undefined + +=item fill_layer + +The layer to place the fill region on. The curves will be drawn on the default +layer (or the layer specified by the C option) and the fill region will +be drawn on the layer specified by this option. Note that if this option is not +specified and the C option, then the curve and the fill region will both +be drawn on the specified C. See the C option above regarding +available layers to choose from. Default: undefined + =item steps This defines the number of points to generate for a dataset from a function. @@ -448,13 +589,16 @@ =head2 LABELS Similar to datasets this can be added individually or multiple at once. # Add a label at the point ($x, $y). - $plot->add_label($x, $y, label => $label, @options)> + $plot->add_label($x, $y, $label, %options) # Add multiple labels at once. $plot->add_label( - [$x1, $y1, label => $label1, @options1], - [$x2, $y2, label => $label2, @options2], + [$x1, $y1, $label1, %options1], + [$x2, $y2, $label2, %options2], ... - ); + ); + + # Deprecated way of adding a label with an option instead of the third argument. + $plot->add_label($x, $y, label => $label, %options) Labels can be configured using the following options: @@ -470,8 +614,11 @@ =head2 LABELS =item fontsize -The font size of the label used. This can be one of 'tiny', 'small', 'medium', -'large', or 'giant'. Default: 'medium' +The font size of the label used. This can be one of 'tiny', 'small', +'normalsize', 'large', 'Large', 'huge', or 'Huge' which correspond to the same +named TeX font sizes. Note that this list used to include 'medium' and 'giant' +which still work, but are deprecated. Instead of 'medium' use 'normalsize', and +instead of 'giant' use 'Large'. Default: 'normalsize' =item rotate @@ -489,6 +636,21 @@ =head2 LABELS that states which end of the label is placed at the label's position. Can be one of 'top', 'middle', or 'bottom'. Default: 'middle' +=item anchor + +The angle in degrees of the label anchor relative to the center of the text. In +other words, the text will be positioned relative to the point on the rectangle +encompassing the label text (including C) where a ray shot from the +text center with the given angle hits the rectangle. This is an alternate method +for positioning the text relative to the label position. If this is set, then +C and C are not used. This is particularly useful for +positioning text when labeling angles. Default: '' + +=item padding + +This is the horizontal and vertical padding applied to the text of the label (in +pixels for the JSXGraph format, and in points for the TikZ format). Default: 4 + =item jsx_options An hash reference of options to pass to JSXGraph text object. @@ -499,8 +661,36 @@ =head2 LABELS =back +=head2 POINTS + +Points are really dataset marks (with no associated curve). Note that points +are drawn after all of the other graph objects except labels are drawn. Thus +points will always appear to be on top of everything else (except labels). + +Note that the C, C, C, and C dataset options are +valid for points. The C option is also a valid option that is an alias for +the C dataset option. The C or C options can be used to +change the symbol that is used for the point. By default the symbol is a +C. + + # Add a single point. + $plot->add_point($x1, $y1, color => $color, mark_size => $mark_size); + + # Add multiple points. + $plot->add_point( + [$x1, $y1, color => $color1, mark_size => $mark_size1], + [$x2, $y2, color => $color2, mark_size => $mark_size2], + ... + ); + + # Add a single open point. + $plot->add_point($x1, $y1, color => $color, mark => 'open_circle'); + =head2 STAMPS +Stamps and the C method are deprecated. DO NOT USE THEM. Use the +C or C methods instead. + Stamps are a single point with a mark drawn at the given point. Stamps can be added individually or multiple at once: @@ -513,24 +703,8 @@ =head2 STAMPS ... ); -Stamps are here for backwards compatibility with WWplot and GD output, and are -equivalent to creating a dataset with one point when not using GD output (with -the small difference that stamps are added after all other datasets have been added). - -=head2 FILL REGIONS - -Fill regions define a point which GD will fill with a color until it hits a boundary curve. -This is only here for backwards comparability with WWplot and GD output. This will not -work with TikZ output, instead using the fill methods mentioned above. - - # Add a single fill region. - $plot->add_fill_region($x1, $y1, $color); - # Add multiple fill regions. - $plot->add_fill_region( - [$x1, $y1, $color1], - [$x2, $y2, $color2], - ... - ); +Adding a stamp is equivalent to creating a dataset with one point with the +exception that stamps are added after all other datasets have been added. =head2 COLORS @@ -564,6 +738,45 @@ =head2 COLORS ... ); +Note that SVG colors can also be used directly by name without being defined via +the C method. See section 4.3 of the +L +documentation for a list of available color names. + +=head2 EXTRA CODE + +Additional JavaScript and TikZ code may be added to draw elements that are not +provided for by this macro and its underlying modules. To add JavaScript code +set the C key on the C<$plot> object, and to add TikZ code set +the C key on the C<$plot> object. The JavaScript code will have +access to the C object via the variable C, and will be +inserted after all of the other code generated by this macro, and before the +C call is executed. The TikZ code will be inserted +after all of the other code generated by this macro, and before the pgfplots +C environment is ended. + +Note that if one of these is used, then both should be used to ensure that both +the JavaScript plot image (used in HTML) and the TikZ plot image (used in +hardcopy) are the same (or at least as close as possible). + +For example, + + $plot = Plot(); + + $plot->{extra_js_code} = << 'END_JS_CODE'; + board.create( + 'line', + [[0, 0], [1, 1]], + { straightLast: false, straightFirst: false, color: 'blue' } + ); + END_JS_CODE + + $plot->{extra_tikz_code} = "\draw[line width = 2pt, blue] (axis cs: 0, 0) -- (axis cs: 1, 1);"; + +Note that the above code is not an actual example that should be used as those +lines could be created using this macro directly. It is only included here to +demonstrate how to use these options. + =head1 TIKZ DEBUGGING When using Tikz output, the pgfplots code used to create the plot is stored in C<< $plot->{tikzCode} >>, diff --git a/macros/math/MatrixReduce.pl b/macros/math/MatrixReduce.pl index a545655b45..920e4ee090 100644 --- a/macros/math/MatrixReduce.pl +++ b/macros/math/MatrixReduce.pl @@ -201,7 +201,7 @@ sub rref_perl_array { } # This was written by Davide Cervone. -# http://webwork.maa.org/moodle/mod/forum/discuss.php?d=2970 +# https://forums.openwebwork.org/mod/forum/discuss.php?d=2970 sub change_matrix_entry { my $self = shift; diff --git a/macros/math/SimpleGraph.pl b/macros/math/SimpleGraph.pl new file mode 100644 index 0000000000..d498a735a9 --- /dev/null +++ b/macros/math/SimpleGraph.pl @@ -0,0 +1,2686 @@ +BEGIN { strict->import; } + +loadMacros('PGauxiliaryFunctions.pl', 'PGbasicmacros.pl', 'MathObjects.pl', 'plots.pl'); + +sub _SimpleGraph_init { + my $context = $main::context{EdgeSet} = Parser::Context->getCopy('Numeric'); + $context->{name} = 'EdgeSet'; + $context->{value}{EdgeSet} = 'GraphTheory::SimpleGraph::Value::EdgeSet'; + $context->{value}{Edge} = 'GraphTheory::SimpleGraph::Value::Edge'; + $context->lists->set( + # The "List" type list is set to use the GraphTheory::SimpleGraph::Parser::List class so that implicit lists can + # be turned into edges or edge sets, and empty lists can be interpreted as edge sets for a graph with no edges. + # All explicit lists that open with something other than a brace are untouched. + List => { %{ $context->lists->get('List') }, class => 'GraphTheory::SimpleGraph::Parser::List' }, + EdgeSet => + { class => 'GraphTheory::SimpleGraph::Parser::List', open => '{', close => '}', separator => ', ' }, + Edge => { class => 'GraphTheory::SimpleGraph::Parser::List', open => '{', close => '}', separator => ', ' }, + ); + $context->parens->set( + '{' => { close => '}', type => 'Edge', formList => 1, formMatrix => 0, removable => 0, emptyOK => 1 }); +} + +sub SimpleGraph { GraphTheory::SimpleGraph->new(@_) } +sub EdgeSet { GraphTheory::SimpleGraph::Value::EdgeSet->new(@_) } +sub Edge { GraphTheory::SimpleGraph::Value::Edge->new(@_) } + +sub randomSimpleGraph { + my ($size, %options) = @_; + + my $graph; + my $edgeCount = 0; + my $edgeProbability = $options{edgeProbability} // 0.5; + + if (ref $size eq 'ARRAY') { + $graph = + GraphTheory::SimpleGraph->new($size->[0] * $size->[1], %options, gridLayout => [ $size->[0], $size->[1] ]); + + for my $i (0 .. $size->[0] - 1) { + for my $j (0 .. $size->[1] - 1) { + my $location = $j * $size->[0] + $i; + + if ($j < $size->[1] - 1 && main::random(0, 100) <= 100 * $edgeProbability) { + $graph->addEdge($location, $location + $size->[0]); + ++$edgeCount; + } + if ($i < $size->[0] - 1 && main::random(0, 100) <= 100 * $edgeProbability) { + $graph->addEdge($location, $location + 1); + ++$edgeCount; + } + if ($i < $size->[0] - 1 && $j < $size->[1] - 1 && main::random(0, 100) <= 100 * $edgeProbability) { + $graph->addEdge($location, $location + $size->[0] + 1); + ++$edgeCount; + } + if ($i >= 1 && $j < $size->[1] - 1 && main::random(0, 100) <= 100 * $edgeProbability) { + $graph->addEdge($location, $location + $size->[0] - 1); + ++$edgeCount; + } + } + } + } else { + $graph = GraphTheory::SimpleGraph->new($size, %options); + + for my $i (0 .. $size - 1) { + for my $j (0 .. $i - 1) { + if (main::random(0, 100) <= 100 * $edgeProbability) { + $graph->addEdge($i, $j); + ++$edgeCount; + } + } + } + } + + return $graph->setRandomWeights(%options, edgeCount => $edgeCount); +} + +sub randomGraphWithEulerCircuit { + my ($size, %options) = @_; + + die 'A graph with an Euler circuit must have at least 5 vertices.' if $size < 5; + + # Remove these from the options so that setting weights is deferred until the return. + my ($startEdgeWeight, $edgeWeightIncrement, $edgeWeightRange) = + delete @options{qw(startEdgeWeight edgeWeightIncrement edgeWeightRange)}; + + my $graph; + + do { + $graph = simpleGraphWithDegreeSequence([ map { main::random(2, $size - 1, 2) } 0 .. $size - 1 ], %options); + } while !defined $graph || $graph->numComponents > 1; + + return $graph->setRandomWeights( + %options, + startEdgeWeight => $startEdgeWeight, + edgeWeightIncrement => $edgeWeightIncrement, + edgeWeightRange => $edgeWeightRange + )->shuffle; +} + +sub randomGraphWithEulerTrail { + my ($size, %options) = @_; + + die 'A graph with an Euler trail must have at least 5 vertices.' if $size < 5; + + # Remove these from the options so that setting weights is deferred until the return. + my ($startEdgeWeight, $edgeWeightIncrement, $edgeWeightRange) = + delete @options{qw(startEdgeWeight edgeWeightIncrement edgeWeightRange)}; + + my $graph = randomGraphWithEulerCircuit($size, %options); + + my ($vertex1, $vertex2); + + do { + ($vertex1, $vertex2) = main::random_subset(2, 0 .. $size - 1); + } until $graph->hasEdge($vertex1, $vertex2); + + $graph->removeEdge($vertex1, $vertex2); + + return $graph->setRandomWeights( + %options, + startEdgeWeight => $startEdgeWeight, + edgeWeightIncrement => $edgeWeightIncrement, + edgeWeightRange => $edgeWeightRange + ); +} + +sub randomGraphWithoutEulerTrail { + my ($size, %options) = @_; + + die 'A graph without an Euler trail must have at least 5 vertices.' if $size < 5; + + # Remove these from the options so that setting weights is deferred until the return. + my ($startEdgeWeight, $edgeWeightIncrement, $edgeWeightRange) = + delete @options{qw(startEdgeWeight edgeWeightIncrement edgeWeightRange)}; + + my $graph; + + do { + $graph = simpleGraphWithDegreeSequence([ map { main::random(2, $size - 1, 1) } 0 .. $size - 1 ], %options); + } while !defined $graph || $graph->hasEulerTrail; + + return $graph->setRandomWeights( + %options, + startEdgeWeight => $startEdgeWeight, + edgeWeightIncrement => $edgeWeightIncrement, + edgeWeightRange => $edgeWeightRange + )->shuffle; +} + +sub randomBipartiteGraph { + my ($size, %options) = @_; + + my ($s1, $s2); + + if (ref $size eq 'ARRAY' && @$size == 2) { + ($s1, $s2) = @$size; + die 'A bipartite graph must have at least 1 vertex in each partition.' unless $s1 > 0 && $s2 > 0; + } else { + die 'A bipartite graph must have at least 2 vertices.' if $size < 2; + $s1 = main::random(1, $size - 1); + $s2 = $size - $s1; + } + + my $graph = GraphTheory::SimpleGraph->new($s1 + $s2, %options); + + my $edgeProbability = $options{edgeProbability} // 0.5; + my $edgeCount = 0; + + for my $i (0 .. $s1 - 1) { + for my $j ($s1 .. $s1 + $s2 - 1) { + next unless main::random(0, 100) <= 100 * $edgeProbability; + $graph->addEdge($i, $j); + ++$edgeCount; + } + } + + return $graph->setRandomWeights(%options, edgeCount => $edgeCount)->shuffle; +} + +sub randomTreeGraph { + my ($size, %options) = @_; + + die 'A tree graph must have at least 2 vertices.' if $size < 2; + + my $graph = GraphTheory::SimpleGraph->new($size, %options); + + my @available = main::random_subset($size, 0 .. $size - 1); + + my @used; + push @used, pop @available; + do { + my $j = pop @available; + my $i = main::random(0, $#used); + $graph->addEdge($used[$i], $j); + push @used, $j; + } while @available > 0; + + return $graph->setRandomWeights(%options, edgeCount => @used - 1); +} + +sub randomForestGraph { + my ($size, %options) = @_; + + die 'A forest graph must have at least 2 vertices.' if $size < 2; + + # Remove these from the options so that setting weights is deferred until the return. + my ($startEdgeWeight, $edgeWeightIncrement, $edgeWeightRange) = + delete @options{qw(startEdgeWeight edgeWeightIncrement edgeWeightRange)}; + + my $graph = randomTreeGraph($size, %options); + + my ($vertex1, $vertex2); + do { + ($vertex1, $vertex2) = main::random_subset(2, 0 .. $size - 1); + } until $graph->hasEdge($vertex1, $vertex2); + $graph->removeEdge($vertex1, $vertex2); + + return $graph->setRandomWeights( + %options, + startEdgeWeight => $startEdgeWeight, + edgeWeightIncrement => $edgeWeightIncrement, + edgeWeightRange => $edgeWeightRange + ); +} + +# Returns a Hamiltonian graph of the given $size. Note that $size must be 5 or greater. +sub randomHamiltonianGraph { + my ($size, %options) = @_; + + die 'A Hamiltonian graph must have at least 5 vertices.' if $size < 5; + + my $graph = GraphTheory::SimpleGraph->new($size, %options); + + my $comp = $size * ($size - 1) / 2; + + my ($low, $high) = $size <= 5 ? ($size + 1, $comp - 1) : (int($comp / 3) + 1, int($comp / 2) + 1); + + for my $i (0 .. $size - 1) { + $graph->addEdge($i, ($i + 1) % $size); + } + + my $edges = int main::random($low, $high); + + my $edgeCount = $size; + while ($edgeCount < $edges) { + my ($i, $j) = main::random_subset(2, 0 .. $size - 1); + unless ($graph->hasEdge($i, $j)) { + $graph->addEdge($i, $j); + ++$edgeCount; + } + } + + return $graph->setRandomWeights(%options, edgeCount => $edgeCount)->shuffle; +} + +sub randomNonHamiltonianGraph { + my ($size, $type, %options) = @_; + + my $graph = GraphTheory::SimpleGraph->new($size); + my $edgeCount = 0; + + if ($type % 2 == 0) { + die 'A non Hamiltonian graph with a degree 1 vertex must have at least 5 vertices.' if $size < 5; + + my $numEdges = main::random($size + 1, ($size - 1) * ($size - 2) / 3 + 1); + + for my $i (0 .. $size - 2) { + $graph->addEdge($i, $i + 1); + } + $graph->addEdge($size - 2, 0); + + $edgeCount = $size; + do { + my ($i, $j) = main::random_subset(2, 0 .. $size - 2); + unless ($graph->hasEdge($i, $j)) { + $graph->addEdge($i, $j); + ++$edgeCount; + } + } while $edgeCount < $numEdges; + } else { + die 'A non Hamiltonian graph with two cycles must have at least 6 vertices.' if $size < 6; + + my $split = int($size / 2); + + for my $i (0 .. $split - 1) { + $graph->addEdge($i, ($i + 1) % $split); + } + for my $i ($split .. $size - 1) { + $graph->addEdge($i, ($i + 1) % $size); + } + $graph->addEdge($size - 1, $split); + + my $numEdges = 2 * $size - 5; + $edgeCount = $size + 1; + + my $maxtry = 4; # Protection against a possibly infinite loop. + while ($edgeCount < $numEdges && $maxtry > 0) { + --$maxtry; + my $v1 = main::random(0, $split - 1); + my $v2 = ($v1 + 2) % $split; + unless ($graph->hasEdge($v1, $v2)) { + $graph->addEdge($v1, $v2); + ++$edgeCount; + } + unless ($graph->hasEdge($v1 + $split, $v2 + $split)) { + $graph->addEdge($v1 + $split, $v2 + $split); + ++$edgeCount; + } + } + } + + return $graph->setRandomWeights(%options, edgeCount => $edgeCount)->shuffle; +} + +sub simpleGraphWithDegreeSequence { + my ($degrees, %options) = @_; + + my @degrees = reverse num_sort(@$degrees); + + return if $degrees[0] >= @degrees; + + my $graph = GraphTheory::SimpleGraph->new(scalar @degrees, %options); + + my $value = 0; + my $vertex = 0; + my $edgeCount = 0; + + while ($vertex < @degrees && $value == 0) { + $value = $degrees[$vertex] - $graph->vertexDegree($vertex); + my $otherVertex = $vertex + 1; + while ($value > 0 && $otherVertex < @degrees) { + if ($graph->vertexDegree($otherVertex) < $degrees[$otherVertex]) { + $graph->addEdge($vertex, $otherVertex); + ++$edgeCount; + --$value; + } + ++$otherVertex; + } + ++$vertex; + } + + return if $value != 0; + + return $graph->setRandomWeights(%options, edgeCount => $edgeCount); +} + +sub cycleGraph { + my ($size, %options) = @_; + + my $graph = GraphTheory::SimpleGraph->new($size, %options); + + for (0 .. $graph->lastVertexIndex) { + $graph->addEdge($_, ($_ + 1) % $graph->numVertices); + } + + return $graph->setRandomWeights(%options, edgeCount => $graph->numVertices); +} + +sub completeGraph { + my ($size, %options) = @_; + + my $graph = GraphTheory::SimpleGraph->new($size, %options); + + for my $i (0 .. $size - 1) { + for my $j ($i + 1 .. $size - 1) { + $graph->addEdge($i, $j); + } + } + + return $graph->setRandomWeights(%options, edgeCount => $graph->numVertices * ($graph->numVertices - 1) / 2); +} + +sub wheelGraph { + my ($size, %options) = @_; + + my $graph = GraphTheory::SimpleGraph->new($size, %options, wheelLayout => 0); + + for my $i (1 .. $size - 2) { + $graph->addEdge($i, $i + 1); + $graph->addEdge(0, $i); + } + $graph->addEdge($size - 1, 1); + $graph->addEdge(0, $size - 1); + + return $graph->setRandomWeights(%options, edgeCount => ($graph->numVertices - 1) * 2); +} + +sub completeBipartiteGraph { + my ($m, $n, %options) = @_; + + my $graph = + GraphTheory::SimpleGraph->new($m + $n, %options, bipartiteLayout => [ [ 0 .. $m - 1 ], [ $m .. $m + $n - 1 ] ]); + + for my $i (0 .. $m - 1) { + for my $j ($m .. $m + $n - 1) { + $graph->addEdge($i, $j); + } + } + + return $graph->setRandomWeights(%options, edgeCount => $m * $n); +} + +package GraphTheory::SimpleGraph; + +sub new { + my ($class, $definition, %options) = @_; + my $self = bless {}, ref($class) || $class; + + die 'A graph definition in the form of a numeric size, adjacency matrix, ' + . 'edge set, or another simple graph object is required.' + unless defined $definition; + + if (ref $definition eq 'GraphTheory::SimpleGraph') { + $self->{adjacencyMatrix} = [ map { [@$_] } @{ $definition->adjacencyMatrix } ]; + $self->{labels} = [ @{ $definition->{labels} } ]; + $options{gridLayout} //= $definition->{gridLayout}; + $options{bipartiteLayout} //= $definition->{bipartiteLayout}; + $options{wheelLayout} //= $definition->{wheelLayout}; + } elsif (Value::classMatch($definition, 'Matrix') + || Value::classMatch($definition, 'EdgeSet') + || ref $definition eq 'ARRAY') + { + $definition = [ map { [ $_->value ] } @{ $definition->data } ] if Value::classMatch($definition, 'Matrix'); + $definition = $definition->data if Value::classMatch($definition, 'EdgeSet'); + + my $haveLabels = ref $options{labels} eq 'ARRAY' && @{ $options{labels} }; + die 'Graphs with no vertices are not supported.' unless @$definition || $haveLabels; + + my @edgeSet; + for (@$definition) { + if (ref $_ ne 'GraphTheory::SimpleGraph::Value::Edge') { @edgeSet = (); last; } + push(@edgeSet, $_->data); + } + if (!@edgeSet) { + for (@$definition) { + if ( + ref $_ ne 'ARRAY' + || @$_ < 2 + || @$_ > 3 + || (!Value::classMatch($_->[0], 'String') + && (Value::isReal($_->[0]) || ($_->[0] ^ $_->[0]) eq '0')) + || (!Value::classMatch($_->[1], 'String') + && (Value::isReal($_->[1]) || ($_->[1] ^ $_->[1]) eq '0')) + ) + { + @edgeSet = (); + last; + } + push(@edgeSet, $_); + } + } + + if (@edgeSet || (!@$definition && $haveLabels)) { + die 'Labels must be provided when using an edgeset definition.' unless $haveLabels; + + $definition = [ map { $_->{data} } @$definition ] + if ref $definition->[0] eq 'GraphTheory::SimpleGraph::Value::Edge'; + + $self->{labels} = [ @{ $options{labels} } ]; + $self->{adjacencyMatrix} = [ map { [ (0) x @{ $self->{labels} } ] } @{ $self->{labels} } ]; + + my %labelIndices = map { $self->{labels}[$_] => $_ } 0 .. $#{ $self->{labels} }; + + for my $i (0 .. $#$definition) { + die 'Invalid edge set format.' unless ref $definition->[$i] eq 'ARRAY'; + my @edge = @{ $definition->[$i] }; + die 'Invalid edge format.' unless @edge >= 2; + die "Invalid vertex $edge[0] in edge set." unless defined $labelIndices{ $edge[0] }; + die "Invalid vertex $edge[1] in edge set." unless defined $labelIndices{ $edge[1] }; + $self->edgeWeight($labelIndices{ $edge[0] }, $labelIndices{ $edge[1] }, $edge[2] // 1); + } + } else { + $self->{adjacencyMatrix} = []; + for my $i (0 .. $#$definition) { + die 'Invalid adjacency matrix format.' unless ref $definition->[$i] eq 'ARRAY'; + die 'The adjacency matrix for a graph must be a square matrix.' + unless @{ $definition->[$i] } == @$definition; + die 'The diagonal entries of the adjacency matrix must be zero.' if $definition->[$i][$i]; + for my $j ($i + 1 .. $#{ $definition->[$i] }) { + die 'The adjacency matrix for a graph must be symmetric.' + unless $definition->[$i][$j] == $definition->[$j][$i]; + } + push(@{ $self->{adjacencyMatrix} }, [ @{ $definition->[$i] } ]); + } + } + } else { + die 'Graphs with no vertices are not supported.' unless $definition > 0; + $self->{adjacencyMatrix} = [ map { [ (0) x $definition ] } 0 .. ($definition - 1) ]; + } + + if (ref $options{labels} eq 'ARRAY') { + die 'Not enough vertex labels provided.' if @{ $options{labels} } < $self->numVertices; + for (0 .. $self->lastVertexIndex) { + die 'Labels cannot be undefined.' unless defined $options{labels}[$_]; + } + $self->{labels} = [ @{ $options{labels} }[ 0 .. $self->lastVertexIndex ] ]; + } + + unless (defined $self->{labels}) { + my $alphaOffset = main::random(0, 25 - $self->lastVertexIndex); + $self->{labels} = [ ('A' .. 'Z')[ $alphaOffset .. $alphaOffset + $self->lastVertexIndex ] ]; + } + + $self->{gridLayout} = [ @{ $options{gridLayout} } ] + if ref $options{gridLayout} eq 'ARRAY' && @{ $options{gridLayout} } == 2; + + if (ref $options{bipartiteLayout} eq 'ARRAY' + && @{ $options{bipartiteLayout} } == 2 + && !grep { ref $_ ne 'ARRAY' } @{ $options{bipartiteLayout} }) + { + $self->{bipartiteLayout} = [ map { [@$_] } @{ $options{bipartiteLayout} } ]; + } elsif ($options{bipartiteLayout}) { + $self->{bipartiteLayout} = 1; + } + + $self->{wheelLayout} = $options{wheelLayout} if defined $options{wheelLayout}; + + return $self; +} + +sub adjacencyMatrix { + my $self = shift; + return $self->{adjacencyMatrix}; +} + +sub edgeSet { + my ($self, %options) = @_; + + my $context = Value::isContext($options{context}) ? $options{context} : Parser::Context->getCopy('EdgeSet'); + $self->addVerticesToContext($options{caseSensitive} // 1, $context); + + my @edgeSet; + for my $i (0 .. $self->lastVertexIndex) { + for my $j ($i + 1 .. $self->lastVertexIndex) { + next unless $self->hasEdge($i, $j); + push( + @edgeSet, + GraphTheory::SimpleGraph::Value::Edge->new( + $context, $self->vertexLabel($i), $self->vertexLabel($j) + ) + ); + $edgeSet[-1]->{open} = '{'; + $edgeSet[-1]->{close} = '}'; + } + } + + my $edgeSet = GraphTheory::SimpleGraph::Value::EdgeSet->new($context, @edgeSet); + $edgeSet->{open} = '{'; + $edgeSet->{close} = '}'; + return $edgeSet; +} + +sub addVerticesToContext { + my ($self, $caseSensitive, $context) = @_; + $context = Value::isContext($context) ? $context : main::Context(); + $context->strings->add( + map { $_ => { caseSensitive => $caseSensitive // 1 } } + grep { !defined $context->strings->all->{$_} } @{ $self->labels } + ); + $context->strings->all->{$_}{isVertex} = 1 for @{ $self->labels }; + return; +} + +sub numVertices { + my $self = shift; + return scalar @{ $self->{adjacencyMatrix} }; +} + +sub lastVertexIndex { + my $self = shift; + return $#{ $self->{adjacencyMatrix} }; +} + +sub numEdges { + my $self = shift; + my $edgeCount = 0; + for my $i (0 .. $self->lastVertexIndex) { + for my $j (0 .. $i - 1) { + next unless $self->hasEdge($i, $j); + ++$edgeCount; + } + } + return $edgeCount; +} + +sub labels { + my ($self, $labels) = @_; + if (ref $labels eq 'ARRAY') { + die 'Not enough vertex labels provided.' if @$labels < $self->numVertices; + $self->{labels} = [ @$labels[ 0 .. $self->numVertices ] ]; + } + return $self->{labels}; +} + +sub labelsString { + my $self = shift; + return join(', ', @{ $self->{labels} }); +} + +sub vertexLabel { + my ($self, $vertexIndex) = @_; + return $self->{labels}[$vertexIndex]; +} + +sub vertexIndex { + my ($self, $vertexLabel) = @_; + for (0 .. $#{ $self->{labels} }) { + return $_ if $vertexLabel eq $self->{labels}[$_]; + } + return -1; +} + +sub vertexDegree { + my ($self, $vertex) = @_; + my $degree = 0; + for my $j (0 .. $self->lastVertexIndex) { + ++$degree if $self->hasEdge($vertex, $j); + } + return $degree; +} + +sub degrees { + my $self = shift; + return map { $self->vertexDegree($_) } 0 .. $self->lastVertexIndex; +} + +sub numComponents { + my $self = shift; + + my @adjacencyMatrix = map { [@$_] } @{ $self->{adjacencyMatrix} }; + + my $result = @adjacencyMatrix; + for my $i (0 .. $#adjacencyMatrix) { + my $connected = 0; + for my $j ($i + 1 .. $#adjacencyMatrix) { + if ($adjacencyMatrix[$i][$j] != 0) { + ++$connected; + for my $k (0 .. $#adjacencyMatrix) { + $adjacencyMatrix[$j][$k] += $adjacencyMatrix[$i][$k]; + $adjacencyMatrix[$k][$j] += $adjacencyMatrix[$k][$i]; + } + } + } + --$result if $connected > 0; + } + return $result; +} + +sub edgeWeight { + my ($self, $i, $j, $weight) = @_; + if (defined $weight) { + $self->{adjacencyMatrix}[$i][$j] = $weight; + $self->{adjacencyMatrix}[$j][$i] = $weight; + } + return $self->{adjacencyMatrix}[$i][$j]; +} + +sub addEdge { + my ($self, $i, $j, $weight) = @_; + $self->edgeWeight($i, $j, $weight || 1); + return; +} + +sub removeEdge { + my ($self, $i, $j) = @_; + $self->edgeWeight($i, $j, 0); + return; +} + +sub hasEdge { + my ($self, $i, $j) = @_; + return $self->edgeWeight($i, $j) != 0; +} + +sub setRandomWeights { + my ($self, %options) = @_; + + my $incrementalRandom = + defined $options{startEdgeWeight} + && $options{startEdgeWeight} > 0 + && ($options{edgeWeightIncrement} // 1) > 0; + + return $self + unless $incrementalRandom + || (ref $options{edgeWeightRange} eq 'ARRAY' && @{ $options{edgeWeightRange} } >= 2); + + my $edgeCount = $options{edgeCount} // $self->numEdges; + + my @weights = + $incrementalRandom + ? main::random_subset($edgeCount, + map { $options{startEdgeWeight} + $_ * ($options{edgeWeightIncrement} // 1) } 0 .. $edgeCount - 1) + : map { main::random(@{ $options{edgeWeightRange} }) } 1 .. $edgeCount; + + for my $i (0 .. $self->lastVertexIndex) { + for my $j ($i + 1 .. $self->lastVertexIndex) { + $self->edgeWeight($i, $j, shift(@weights) // 1) if $self->hasEdge($i, $j); + } + } + + return $self; +} + +sub isEqual { + my ($self, $other) = @_; + return 0 unless ref $other eq 'GraphTheory::SimpleGraph'; + return 0 if @{ $self->{adjacencyMatrix} } != @{ $other->{adjacencyMatrix} }; + for my $i (0 .. $#{ $self->{adjacencyMatrix} }) { + return 0 if @{ $self->{adjacencyMatrix}[$i] } != @{ $other->{adjacencyMatrix}[$i] }; + for my $j (0 .. $i - 1) { + return 0 if $self->{adjacencyMatrix}[$i][$j] != $other->{adjacencyMatrix}[$i][$j]; + } + } + return 1; +} + +sub isIsomorphic { + my ($self, $other) = @_; + + return 0 unless ref $other eq 'GraphTheory::SimpleGraph' && $self->numVertices == $other->numVertices; + + my @degrees = main::num_sort($self->degrees); + my @otherDegrees = main::num_sort($other->degrees); + for (0 .. $#degrees) { + return 0 unless $degrees[$_] == $otherDegrees[$_]; + } + + my $permutations = [ [0] ]; + + for my $i (1 .. $self->lastVertexIndex) { + my @newPermutations; + for my $permutation (@$permutations) { + for my $j (0 .. @$permutation) { + my @new = @$permutation; + splice(@new, $j, 0, $i); + push(@newPermutations, \@new); + } + } + $permutations = \@newPermutations; + } + + # The last permutation is the original vertex order, so remove it. + pop @$permutations; + + for my $permutation (@$permutations) { + my @shuffledGraph; + for my $i (0 .. $other->lastVertexIndex) { + for my $j (0 .. $other->lastVertexIndex) { + $shuffledGraph[ $permutation->[$i] ][ $permutation->[$j] ] = $other->edgeWeight($i, $j); + } + } + return 1 if $self->isEqual($self->new(\@shuffledGraph)); + } + + return 0; +} + +sub image { + my ($self, %options) = @_; + + return $self->gridLayoutImage(%options) if $self->{gridLayout}; + if ($self->{bipartiteLayout}) { + return $self->bipartiteLayoutImage(%options) if ref $self->{bipartiteLayout} eq 'ARRAY'; + # Attempt to partition the graph into two sets in which no edge connects vertices in the same set. + # If not found, then fall through and use the default circle layout. + my ($top, $bottom) = $self->bipartitePartition; + if (ref $top eq 'ARRAY' && ref $bottom eq 'ARRAY' && @$top && @$bottom) { + $self->{bipartiteLayout} = [ $top, $bottom ]; + return $self->bipartiteLayoutImage(%options); + } + } + return $self->wheelLayoutImage(%options) if defined $self->{wheelLayout}; + + my %graphOptions; + @graphOptions{qw(showLabels showWeights)} = delete @options{qw(showLabels showWeights)}; + $graphOptions{showLabels} //= 1; + + my $plot = main::Plot( + xmin => -1.5, + xmax => 1.5, + ymin => -1.5, + ymax => 1.5, + xlabel => '', + ylabel => '', + xvisible => 0, + yvisible => 0, + show_grid => 0, + width => 250, + aspect_ratio => 1, + %options + ); + + my $gap = 2 * $main::PI / ($self->numVertices || 1); + + for my $i (0 .. $self->lastVertexIndex) { + my $iVertex = [ cos($i * $gap), sin($i * $gap) ]; + $plot->add_point(@$iVertex, color => 'blue', mark_size => 3); + + $plot->add_label( + 1.25 * $iVertex->[0], 1.25 * $iVertex->[1], + label => "\\\\($self->{labels}[$i]\\\\)", + color => 'blue', + h_align => 'center', + v_align => 'middle' + ) if $graphOptions{showLabels}; + + my $u = 0.275; + my $v = 1 - $u; + + for my $j ($i + 1 .. $self->lastVertexIndex) { + if ($self->hasEdge($i, $j)) { + my $jVertex = [ cos($j * $gap), sin($j * $gap) ]; + $plot->add_dataset($iVertex, $jVertex, color => 'black'); + + if ($graphOptions{showWeights}) { + my @vector = ($jVertex->[0] - $iVertex->[0], $jVertex->[1] - $iVertex->[1]); + my $norm = sqrt($vector[0]**2 + $vector[1]**2); + my @perp = ($vector[1] / $norm, -$vector[0] / $norm); + $plot->add_label( + $u * $iVertex->[0] + $v * $jVertex->[0] + $perp[0] * 0.06, + $u * $iVertex->[1] + $v * $jVertex->[1] + $perp[1] * 0.06, + label => "\\\\($self->{adjacencyMatrix}->[$i][$j]\\\\)", + color => 'red', + rotate => ($perp[0] < 0 ? 1 : -1) * + atan2(sqrt(1 - $perp[1] * $perp[1]), $perp[1]) * 180 / + $main::PI - ($perp[1] < 0 ? 180 : 0) + ); + } + } + } + } + + return $plot; +} + +sub gridLayoutImage { + my ($self, %options) = @_; + + die 'Grid layout is not defined, or is but does not have a row and column dimension.' + unless ref $self->{gridLayout} eq 'ARRAY' && @{ $self->{gridLayout} } == 2; + + my %graphOptions; + @graphOptions{qw(showLabels showWeights)} = delete @options{qw(showLabels showWeights)}; + $graphOptions{showLabels} //= 1; + + my $gridGap = 20; + my $gridShift = $gridGap / 2; + my $labelShift = $gridGap / 15; + + my $plot = main::Plot( + xmin => -$gridShift, + xmax => $self->{gridLayout}[1] * $gridGap - $gridShift, + ymin => -$gridShift, + ymax => $self->{gridLayout}[0] * $gridGap - $gridShift, + width => 7 * ($self->{gridLayout}[1] - 1) * $gridGap, + aspect_ratio => 1, + xlabel => '', + ylabel => '', + xvisible => 0, + yvisible => 0, + show_grid => 0, + %options + ); + + for my $i (0 .. $self->{gridLayout}[0] - 1) { + for my $j (0 .. $self->{gridLayout}[1] - 1) { + my $x = $gridGap * $j; + my $y = $gridGap * ($self->{gridLayout}[0] - $i - 1); + $plot->add_point($x, $y, color => 'blue', mark_size => 3); + $plot->add_label( + $x - $labelShift, $y + 2 * $labelShift, + label => "\\\\($self->{labels}[$i + $self->{gridLayout}[0] * $j]\\\\)", + color => 'blue', + h_align => 'center', + v_align => 'middle' + ) if $graphOptions{showLabels}; + } + } + + my $u = 0.6666; + my $v = 1 - $u; + + for my $i (0 .. $self->lastVertexIndex) { + my $iVertex = [ + int($i / $self->{gridLayout}[0]) * $gridGap, + ($self->{gridLayout}[0] - ($i % $self->{gridLayout}[0]) - 1) * $gridGap + ]; + for my $j ($i + 1 .. $self->lastVertexIndex) { + if ($self->hasEdge($i, $j)) { + my $jVertex = [ + int($j / $self->{gridLayout}[0]) * $gridGap, + ($self->{gridLayout}[0] - ($j % $self->{gridLayout}[0]) - 1) * $gridGap + ]; + $plot->add_dataset($iVertex, $jVertex, color => 'black', width => 1); + my $vector = [ $jVertex->[0] - $iVertex->[0], $jVertex->[1] - $iVertex->[1] ]; + if ($graphOptions{showWeights}) { + my $norm = sqrt($vector->[0]**2 + $vector->[1]**2); + $plot->add_label( + $u * $iVertex->[0] + $v * $jVertex->[0] - $vector->[1] / $norm * 2, + $u * $iVertex->[1] + $v * $jVertex->[1] + $vector->[0] / $norm * 2, + label => "\\\\($self->{adjacencyMatrix}[$i][$j]\\\\)", + color => 'red' + ); + } + } + } + } + + return $plot; +} + +sub bipartiteLayoutImage { + my ($self, %options) = @_; + + my ($top, $bottom); + + if (ref $self->{bipartiteLayout} eq 'ARRAY' + && @{ $self->{bipartiteLayout} } == 2 + && !grep { ref $_ ne 'ARRAY' } @{ $self->{bipartiteLayout} }) + { + ($top, $bottom) = @{ $self->{bipartiteLayout} }; + } elsif ($self->{bipartiteLayout}) { + ($top, $bottom) = $self->bipartitePartition; + die 'Graph is not bipartite.' unless ref $top eq 'ARRAY' && ref $bottom eq 'ARRAY' && @$top && @$bottom; + } else { + die 'Bipartite layout is not defined.'; + } + + my %graphOptions; + @graphOptions{qw(showLabels showWeights)} = delete @options{qw(showLabels showWeights)}; + $graphOptions{showLabels} //= 1; + + my ($low, $high, $width) = (0, 15, 20); + my @shift = (0, 0); + + my $diff = @$top - @$bottom; + + my $x_max; + if ($diff == 0) { + $x_max = @$top * $width - 10; + } elsif ($diff % 2 == 0 && $diff > 0) { + $x_max = @$top * $width - 10; + $shift[1] += $width * $diff / 2; + } elsif ($diff % 2 == 0) { + $x_max = @$bottom * $width - 10; + $shift[0] += -$width * $diff / 2; + } elsif ($diff > 0) { + $x_max = @$top * $width - 10; + $shift[1] += ($width / 2) * $diff; + } else { + $x_max = @$bottom * $width - 10; + $shift[0] += (-$width / 2) * $diff; + } + + my $plot = main::Plot( + xmin => -10, + xmax => $x_max, + ymin => -5, + ymax => 20, + width => 250, + aspect_ratio => 1, + xlabel => '', + ylabel => '', + xvisible => 0, + yvisible => 0, + show_grid => 0, + %options + ); + + for my $i (0 .. $#$top) { + $plot->add_point($i * $width + $shift[0], $high, color => 'blue', mark_size => 3); + $plot->add_label( + $i * $width + $shift[0], $high + 2 / 3, + label => "\\\\($self->{labels}[$top->[$i]]\\\\)", + color => 'blue', + h_align => 'center', + v_align => 'bottom' + ) if $graphOptions{showLabels}; + } + for my $j (0 .. $#$bottom) { + $plot->add_point($j * $width + $shift[1], $low, color => 'blue', mark_size => 3); + $plot->add_label( + $j * $width + $shift[1], $low - 2 / 3, + label => "\\\\($self->{labels}[$bottom->[$j]]\\\\)", + color => 'blue', + h_align => 'center', + v_align => 'top' + ) if $graphOptions{showLabels}; + } + + my ($u, $v) = $diff >= 0 ? (2 / 3, 1 / 3) : (1 / 3, 2 / 3); + + for my $i (0 .. $#$top) { + for my $j (0 .. $#$bottom) { + next unless $self->hasEdge($top->[$i], $bottom->[$j]); + my $point1 = [ $i * $width + $shift[0], $high ]; + my $point2 = [ $j * $width + $shift[1], $low ]; + $plot->add_dataset($point1, $point2, color => 'black'); + if ($graphOptions{showWeights}) { + my $vector = [ $point2->[0] - $point1->[0], $point2->[1] - $point1->[1] ]; + my $norm = sqrt($vector->[0]**2 + $vector->[1]**2); + $plot->add_label( + $u * $point1->[0] + $v * $point2->[0] - $vector->[1] / $norm * 5 / 4, + $u * $point1->[1] + $v * $point2->[1] + $vector->[0] / $norm * 5 / 4, + label => "\\\\($self->{adjacencyMatrix}[ $top->[$i] ][ $bottom->[$j] ]\\\\)", + color => 'red' + ); + } + } + } + + return $plot; +} + +sub wheelLayoutImage { + my ($self, %options) = @_; + + die 'Wheel layout is not defined.' unless defined $self->{wheelLayout}; + + my %graphOptions; + @graphOptions{qw(showLabels showWeights)} = delete @options{qw(showLabels showWeights)}; + $graphOptions{showLabels} //= 1; + + my $plot = main::Plot( + xmin => -1.5, + xmax => 1.5, + ymin => -1.5, + ymax => 1.5, + width => 250, + aspect_ratio => 1, + xlabel => '', + ylabel => '', + xvisible => 0, + yvisible => 0, + show_grid => 0, + %options + ); + + my $gap = 2 * $main::PI / ($self->lastVertexIndex || 1); + + $plot->add_point(0, 0, color => 'blue', mark_size => 3); + $plot->add_label( + 0.1, 0.2, + label => "\\\\($self->{labels}[ $self->{wheelLayout} ]\\\\)", + color => 'blue', + h_align => 'center', + v_align => 'middle' + ) if $graphOptions{showLabels}; + + for my $i (0 .. $self->lastVertexIndex) { + next if $i == $self->{wheelLayout}; + + my $iRel = $i > $self->{wheelLayout} ? $i - 1 : $i; + + my $iVertex = [ cos($iRel * $gap), sin($iRel * $gap) ]; + $plot->add_point(@$iVertex, color => 'blue', mark_size => 3); + + $plot->add_label( + 1.25 * $iVertex->[0], 1.25 * $iVertex->[1], + label => "\\\\($self->{labels}[$i]\\\\)", + color => 'blue', + h_align => 'center', + v_align => 'middle' + ) if $graphOptions{showLabels}; + + if ($self->hasEdge($self->{wheelLayout}, $i)) { + $plot->add_dataset([ 0, 0 ], $iVertex, color => 'black'); + if ($graphOptions{showWeights}) { + my $norm = sqrt($iVertex->[0]**2 + $iVertex->[1]**2); + my @perp = ($iVertex->[1] / $norm, -$iVertex->[0] / $norm); + $plot->add_label( + 0.5 * $iVertex->[0] + $iVertex->[1] / $norm * 0.1, + 0.5 * $iVertex->[1] - $iVertex->[0] / $norm * 0.1, + label => "\\\\($self->{adjacencyMatrix}->[ $self->{wheelLayout} ][$i]\\\\)", + color => 'red', + rotate => ($perp[0] < 0 ? 1 : -1) * + atan2(sqrt(1 - $perp[1] * $perp[1]), $perp[1]) * 180 / + $main::PI - ($perp[1] < 0 ? 180 : 0) + ); + } + } + + for my $j ($i + 1 .. $self->lastVertexIndex) { + next if $j == $self->{wheelLayout}; + + my $jRel = $j > $self->{wheelLayout} ? $j - 1 : $j; + + if ($self->hasEdge($i, $j)) { + my $jVertex = [ cos($jRel * $gap), sin($jRel * $gap) ]; + $plot->add_dataset($iVertex, $jVertex, color => 'black'); + + if ($graphOptions{showWeights}) { + my @vector = ($jVertex->[0] - $iVertex->[0], $jVertex->[1] - $iVertex->[1]); + my $norm = sqrt($vector[0]**2 + $vector[1]**2); + my @perp = ($vector[1] / $norm, -$vector[0] / $norm); + $plot->add_label( + 0.5 * $iVertex->[0] + 0.5 * $jVertex->[0] + $vector[1] / $norm * 0.1, + 0.5 * $iVertex->[1] + 0.5 * $jVertex->[1] - $vector[0] / $norm * 0.1, + label => "\\\\($self->{adjacencyMatrix}->[$i][$j]\\\\)", + color => 'red', + rotate => ($perp[0] < 0 ? 1 : -1) * + atan2(sqrt(1 - $perp[1] * $perp[1]), $perp[1]) * 180 / + $main::PI - ($perp[1] < 0 ? 180 : 0) + ); + } + } + } + } + + return $plot; +} + +sub copy { + my $self = shift; + return $self->new($self); +} + +sub shuffle { + my ($self, $permuteLabels) = @_; + my @shuffledGraph; + my @vertexPermutation = main::random_subset($self->numVertices, 0 .. $self->lastVertexIndex); + my @inverseVertexPermutation; # Only needed if labels are also permuted. + @inverseVertexPermutation[@vertexPermutation] = 0 .. $#vertexPermutation; + for my $i (0 .. $self->lastVertexIndex) { + for my $j (0 .. $self->lastVertexIndex) { + $shuffledGraph[ $vertexPermutation[$i] ][ $vertexPermutation[$j] ] = $self->edgeWeight($i, $j); + } + } + return $self->new( + \@shuffledGraph, + labels => $permuteLabels ? [ @{ $self->{labels} }[@inverseVertexPermutation] ] : $self->{labels}, + ref $self->{bipartiteLayout} eq 'ARRAY' ? () : (bipartiteLayout => $self->{bipartiteLayout}), + defined $self->{wheelLayout} ? (wheelLayout => $vertexPermutation[ $self->{wheelLayout} ]) : () + ); +} + +sub nearestNeighborPath { + my ($self, $vertex) = @_; + + my @visited = (undef) x $self->numVertices; + $visited[$vertex] = 1; + + my @path = ($vertex); + my $weight = 0; + my $currentVertex = $vertex; + + while (@path < $self->numVertices) { + my $nearest; + my $min = 0; + for my $i (0 .. $self->lastVertexIndex) { + next if $i == $currentVertex || defined $visited[$i] || !$self->hasEdge($currentVertex, $i); + if ($min == 0 || $self->edgeWeight($currentVertex, $i) < $min) { + $min = $self->edgeWeight($currentVertex, $i); + $nearest = $i; + } + } + last unless defined $nearest; + push @path, $nearest; + $visited[$nearest] = 1; + $weight += $self->edgeWeight($currentVertex, $nearest); + $currentVertex = $nearest; + } + + if ($self->hasEdge($currentVertex, $vertex)) { + push @path, $vertex; + $weight += $self->edgeWeight($currentVertex, $vertex); + } + + return (\@path, $weight); +} + +sub kruskalGraph { + my $self = shift; + + my $graph = $self->copy; + my $tree = GraphTheory::SimpleGraph->new($graph->numVertices, labels => $graph->labels); + my $numTreeComponents = $tree->numComponents; + + my $treeWeight = 0; + + my $weight = 0; + my @treeWeights; + + my @weights; + for my $i (0 .. $graph->lastVertexIndex) { + for my $j ($i + 1 .. $graph->lastVertexIndex) { + push(@weights, $graph->edgeWeight($i, $j)) if $graph->hasEdge($i, $j); + } + } + @weights = main::num_sort(@weights); + + while (@weights > 0) { + $weight = shift @weights; + for my $i (0 .. $graph->lastVertexIndex) { + for my $j ($i + 1 .. $graph->lastVertexIndex) { + if ($graph->edgeWeight($i, $j) == $weight) { + $graph->removeEdge($i, $j); + $tree->addEdge($i, $j, $weight); + my $currentTreeNumComponents = $tree->numComponents; + if ($currentTreeNumComponents < $numTreeComponents) { + $numTreeComponents = $currentTreeNumComponents; + $treeWeight += $weight; + push @treeWeights, $weight; + } else { + $tree->removeEdge($i, $j); + } + last; + } + } + } + } + + return ($tree, $treeWeight, \@treeWeights); +} + +sub hasEulerCircuit { + my $self = shift; + + return wantarray ? (0, $main::PG->maketext('This graph is not connected.')) : 0 if $self->numComponents != 1; + + for my $degree ($self->degrees) { + return wantarray ? (0, $main::PG->maketext('The degrees of the vertices in this graph are not all even.')) : 0 + if $degree % 2 != 0; + } + + return 1; +} + +sub hasEulerTrail { + my $self = shift; + + return wantarray ? (0, $main::PG->maketext('This graph is not connected.')) : 0 if $self->numComponents != 1; + + my ($odd, $even) = (0, 0); + + for my $degree ($self->degrees) { + if ($degree % 2 == 0) { ++$even } + else { ++$odd } + } + + return wantarray + ? ( + 0, + $main::PG->maketext( + 'This graph does not have two vertices of odd degree and all other vertices of even degree.') + ) + : 0 + if $even != $self->numVertices && $odd != 2; + return 1; +} + +sub pathIsEulerTrail { + my ($self, @path) = @_; + + my $graph = $self->copy; + + my $i = shift @path; + do { + my $j = shift @path; + return + wantarray ? (0, $main::PG->maketext('An edge traversed by this path does not exist in the graph.')) : 0 + unless $graph->hasEdge($i, $j); + $graph->removeEdge($i, $j); + $i = $j; + } while @path > 0; + + for my $i (0 .. $graph->lastVertexIndex) { + for my $j (0 .. $i - 1) { + return wantarray ? (0, $main::PG->maketext('This path does not traverse all edges.')) : 0 + if $graph->hasEdge($i, $j); + } + } + + return 1; +} + +sub pathIsEulerCircuit { + my ($self, @path) = @_; + my @isEulerTrail = $self->pathIsEulerTrail(@path); + return wantarray ? @isEulerTrail : 0 unless $isEulerTrail[0]; + return wantarray ? (0, $main::PG->maketext('This path is not a circuit.')) : 0 unless $path[0] == $path[-1]; + return 1; +} + +sub hasCircuit { + my $self = shift; + + my $graph = $self->copy; + + for (my $i = 0; $i < $graph->numVertices; ++$i) { + my $sum = 0; + for my $j (0 .. $graph->lastVertexIndex) { + $sum += $graph->hasEdge($i, $j) ? 1 : 0; + } + if ($sum == 1) { + for my $j (0 .. $graph->lastVertexIndex) { + $graph->removeEdge($i, $j); + } + $i = -1; + } + } + + for my $i (0 .. $graph->lastVertexIndex) { + for my $j ($i + 1 .. $graph->lastVertexIndex) { + return 1 if $graph->hasEdge($i, $j); + } + } + + return 0; +} + +sub isTree { + my $self = shift; + return wantarray ? (0, $main::PG->maketext('This graph has a circuit.')) : 0 if $self->hasCircuit; + return wantarray ? (0, $main::PG->maketext('This graph is not connected.')) : 0 if $self->numComponents > 1; + return 1; +} + +sub isForest { + my $self = shift; + return wantarray ? (0, $main::PG->maketext('This graph has a circuit.')) : 0 if $self->hasCircuit; + return 1; +} + +# Returns 1 if the given $graph is bipartite, and 0 otherwise. +sub isBipartite { + my $self = shift; + + my @vertexColors = (0) x $self->numVertices; + my @color = (1, 2); + + my $done; + do { + my $i = 0; + while ($vertexColors[$i] != 0) { ++$i; } + $vertexColors[$i] = $color[0]; + + my @verticesToClassify; + push @verticesToClassify, $i; + + do { + my $vertex = shift @verticesToClassify; + my $currentColor = $vertexColors[$vertex]; + for my $i (0 .. $self->lastVertexIndex) { + if ($self->hasEdge($vertex, $i)) { + return 0 if $currentColor == $vertexColors[$i]; + if ($vertexColors[$i] == 0) { + push @verticesToClassify, $i; + $vertexColors[$i] = $color[ 2 - $currentColor ]; + } + } + } + } while @verticesToClassify; + + $done = 1; + for my $i (0 .. $self->lastVertexIndex) { + $done *= $vertexColors[$i]; + } + } while $done == 0; + + return 1; +} + +sub bipartitePartition { + my $self = shift; + + my @partition = ([], []); + + my %partitionAssignments; + + my $done; + do { + my $i = 0; + ++$i while defined $partitionAssignments{$i}; + $partitionAssignments{$i} = 0; + push(@{ $partition[0] }, $i); + + my @vertices; + push @vertices, $i; + + do { + my $vertex = shift @vertices; + my $currentPartition = $partitionAssignments{$vertex}; + for my $i (0 .. $self->lastVertexIndex) { + if ($self->hasEdge($vertex, $i)) { + return if defined $partitionAssignments{$i} && $currentPartition == $partitionAssignments{$i}; + if (!defined $partitionAssignments{$i}) { + push @vertices, $i; + $partitionAssignments{$i} = $currentPartition ? 0 : 1; + push(@{ $partition[ $partitionAssignments{$i} ] }, $i); + } + } + } + } while @vertices; + + $done = 1; + for my $i (0 .. $self->lastVertexIndex) { + unless (defined $partitionAssignments{$i}) { + $done = 0; + last; + } + } + } while !$done; + + $partition[0] = [ main::num_sort(@{ $partition[0] }) ]; + $partition[1] = [ main::num_sort(@{ $partition[1] }) ]; + + return @partition; +} + +sub dijkstraPath { + my ($self, $start, $end) = @_; + + my ($ind, $new, @dist, @path, @prev, @used); + + for my $i (0 .. $self->lastVertexIndex) { + $dist[$i] = -1; + $prev[$i] = -1; + } + $dist[$start] = 0; + + my @available = (0 .. $self->lastVertexIndex); + + while (@available) { + my $min = 1000000000; # Infinity (for all practical purposes) + for my $i (0 .. $#available) { + my $loc = $available[$i]; + if ($dist[$loc] >= 0 && $dist[$loc] < $min) { + $new = $loc; + $ind = $i; + $min = $dist[$new]; + } + } + push @used, $new; + splice @available, $ind, 1; + @used = main::num_sort(@used); + + for my $i (0 .. $self->lastVertexIndex) { + my $weight = $self->edgeWeight($new, $i); + if ($weight != 0) { + if ($dist[$i] > $min + $weight || $dist[$i] < 0) { + $dist[$i] = $min + $weight; + $prev[$i] = $new; + } + } + } + } + + my $loc = $end; + while ($loc != $start) { + unshift @path, $loc; + $loc = $prev[$loc]; + } + unshift @path, $start; + + return ($dist[$end], @path); +} + +sub sortedEdgesPath { + my $self = shift; + + my @weights; + my $sortedGraph = GraphTheory::SimpleGraph->new($self->numVertices, labels => $self->labels); + + for my $i (0 .. $self->lastVertexIndex) { + for my $j ($i + 1 .. $self->lastVertexIndex) { + next unless $self->hasEdge($i, $j); + push @weights, $self->edgeWeight($i, $j); + } + } + + @weights = main::num_sort(@weights); + + # Returns 1 if an edge can be added to the sorted edges based graph and 0 otherwise. An edge can be added if it does + # not make a vertex have more than two edges connected to it, and it does not create a circuit in the graph (unless + # it is the last vertex in which case that is okay since it completes the circuit). + my $goodEdge = sub { + my $graph = shift; + + my $sum = 0; + + for my $i (0 .. $graph->lastVertexIndex) { + my $degree = $graph->vertexDegree($i); + return 0 if $degree > 2; + $sum += $degree; + } + + return $sum < 2 * $graph->numVertices && $graph->hasCircuit ? 0 : 1; + }; + + my @pathWeights; + + do { + my $weight = shift @weights; + for my $i (0 .. $sortedGraph->lastVertexIndex) { + for my $j ($i + 1 .. $sortedGraph->lastVertexIndex) { + if ($weight == $self->edgeWeight($i, $j)) { + $sortedGraph->addEdge($i, $j, $self->edgeWeight($i, $j)); + if ($goodEdge->($sortedGraph)) { + push @pathWeights, $weight; + } else { + $sortedGraph->removeEdge($i, $j); + } + } + } + } + } while @pathWeights < $sortedGraph->numVertices && @weights > 0; + + return ($sortedGraph, \@pathWeights); +} + +sub chromaticNumber { + my $self = shift; + return Chromatic::computeBestColoring(@{ $self->{adjacencyMatrix} }); +} + +sub kColoring { + my ($self, $k) = @_; + + my @colors = (-1) x $self->numVertices; + my @nextColor = (0) x $self->numVertices; + my $v = 0; + + while ($v >= 0) { + my $assigned = 0; + while ($nextColor[$v] < $k) { + my $c = ++$nextColor[$v]; + my $ok = 1; + for my $u (0 .. $self->lastVertexIndex) { + if ($self->hasEdge($v, $u) && $colors[$u] == $c) { + $ok = 0; + last; + } + } + if ($ok) { + $colors[$v] = $c; + ++$v; + return @colors if $v == $self->numVertices; + $nextColor[$v] = 0; + $assigned = 1; + last; + } + } + unless ($assigned) { + $colors[$v] = -1; + $nextColor[$v] = 0; + --$v; + } + } + + return; +} + +# The GraphTheory::SimpleGraph::Parser::List, GraphTheory::SimpleGraph::EdgeSet and GraphTheory::SimpleGraph::Edge +# packages are special lists for the edgeSet return value context. + +package GraphTheory::SimpleGraph::Parser::List; +our @ISA = qw(Parser::List::List); + +sub _check { + my $self = shift; + $self->SUPER::_check; + + # Only handle implicit lists or lists explicitly opened with a brace. + return if $self->{open} && $self->{open} ne '{'; + + my $entryType = $self->typeRef->{entryType}; + + # Since there can only be one brace paren, the distinction between an edge and an edge set needs to be made here. + # An empty list or a list that contains another list is an edge set. All other lists are edges. + my $isEdgeSet = $self->length ? 0 : 1; + for (@{ $self->{coords} }) { + if ($_->{type}{list} && $_->{type}{list} == 1) { $isEdgeSet = 1; last; } + } + + if ($isEdgeSet) { + $self->{type} = Value::Type('EdgeSet', scalar(@{ $self->{coords} }), $entryType, list => 1); + } elsif ($self->{type}{name} ne 'Edge') { + $self->{type} = Value::Type('Edge', scalar(@{ $self->{coords} }), $entryType, list => 1); + } + + if ($self->{type}{name} eq 'Edge') { + my $strings = $self->context->strings->all; + for (@{ $self->{coords} }) { + $self->Error('An edge may only contain vertices.') + unless ref $_ eq 'Parser::String' + && defined $strings->{ $_->{value} } + && $strings->{ $_->{value} }{isVertex}; + } + $self->Error('An edge must contain exactly two vertices.') if $self->length != 2; + } elsif ($self->{type}{name} eq 'EdgeSet') { + for (@{ $self->{coords} }) { + $self->Error('An edge set may only contain edges.') unless $_->{type}{name} eq 'Edge'; + } + } +} + +package GraphTheory::SimpleGraph::Value::EdgeSet; +our @ISA = qw(Value::List); + +sub cmp_defaults { + my ($self, %options) = @_; + return ( + $self->SUPER::cmp_defaults(%options), + entry_type => 'edge', + list_type => 'edge set', + removeParens => 0, + showParenHints => 1, + implicitList => 0 + ); +} + +sub compare { + my ($l, $r, $flag) = @_; + my $self = $l; + $r = $self->promote($r); + if ($flag) { my $tmp = $l; $l = $r; $r = $tmp } + my @l = main::num_sort($l->value); + my @r = main::num_sort($r->value); + while (@l && @r) { + my $cmp = shift(@l) <=> shift(@r); + return $cmp if $cmp; + } + return @l - @r; +} + +package GraphTheory::SimpleGraph::Value::Edge; +our @ISA = qw(Value::List); + +sub cmp_defaults { + my ($self, %options) = @_; + return ( + $self->SUPER::cmp_defaults(%options), + entry_type => 'vertex', + list_type => 'edge', + removeParens => 0, + showParenHints => 1, + implicitList => 0 + ); +} + +sub compare { + my ($l, $r, $flag) = @_; + my $self = $l; + $r = $self->promote($r); + if ($flag) { my $tmp = $l; $l = $r; $r = $tmp } + my @l = main::num_sort($l->value); + my @r = main::num_sort($r->value); + while (@l && @r) { + my $cmp = shift(@l) <=> shift(@r); + return $cmp if $cmp; + } + return @l - @r; +} + +1; + +=head1 NAME + +SimpleGraph.pl - Tools for displaying and manipulating simple graphs from graph +theory. + +=head1 DESCRIPTION + +The core of this macro is the C object which +represents simple graphs from graph theory via an adjacency matrix. + +=head1 FUNCTIONS + +The following functions can be used to construct a C +object. + +=head2 SimpleGraph + + $graph = SimpleGraph($definition, %options); + +This is an alias for the C constructor. + +The C<$definition> argument is required and can be the number of vertices in the +graph, another C object, a reference to an array of +arrays of numbers, a reference to a MathObject C, a reference to an +array of arrays containing two strings and possibly a number (an edge set with +optional edge weights), a reference to an array of +C objects, or a reference to a +C object. + +If C<$definition> is the number of vertices in the graph, then the graph that is +returned will have that number of vertices and no edges. Note that the number of +vertices must be greater than 0. + +If C<$definition> is another C object, then a copy of +the graph represented by that object will be returned. + +If C<$definition> is a one of the matrix forms (a reference to an array of +arrays of numbers or a reference to a MathObject C), then that matrix +will be used for the adjacency matrix of the graph. Note that it must be a +square symmetric matrix with zero entries along the diagonal and must have size +greater than or equal to 1. For example, + + [ + [ 0, 1, 0], + [ 1, 0, 2], + [ 0, 2, 0] + ] + +represents a graph that has three vertices, an edge between the first and +second vertices with weight one, and an edge between the second and third +vertices with weight 2. + +If C<$definition> is one of the edge set definition forms (a reference to an +array of arrays containing two strings and possibly a number, a reference to an +array of C objects, or a reference to a +C object), then the C option +is required. For the reference to an array of arrays form each edge in the edge +set must be an array containing two strings and possibly a third numeric +element. The two strings are the labels for the vertices connected by the edge. +The optional third element is the weight of the edge. If the optional third +element is not given, then the edge will have a weight of one. Note that all +labels provided in the C option will be used with this definition, and +will all be vertices in the graph. For example, if C<$definition> is given as + + [ + [ 'A', 'B' ], + [ 'A', 'C', 2 ], + [ 'B', 'C', 3 ], + ] + +and the C option is C<['A', 'B', 'C', 'D']>, then the return object will +represent a graph that has at four vertices labeled 'A', 'B', 'C', and 'D', +respectively, an edge between 'A' and 'B' with weight one, an edge between 'A' +and 'C' with weight 2, an edge between 'B' and 'C' with weight 3, and no edges +connecting to the vertex 'D'. Note that setting weights when a graph is +constructed via a reference to an array of C +objects or a reference to a C object is not +supported. However, weights can still be set after construction with the +L method or the L method. + +The arguments that may be passed in C<%options> are as follows. Note that all of +these options are undefined by default. + +=over + +=item labels + +The value of this option must be a reference to an array of strings. These are +the labels for the vertices of the graph. The array must contain at least as +many strings as there are vertices of the graph. Any extraneous strings will not +be used (except in the edge set definition case as mentioned above). If this +option is not given, then a random set of consecutive letters from the alphabet +will be used for the labels of the vertices. Note that if an edge set version of +the C<$definition> argument is used, then this option must be provided. + +=item gridLayout + +If this option is given, then it must be a reference to an array containing two +numbers, for example, C<[3, 4]>. In this case when the L method is +called to create the image of the graph, the vertices will be arranged into a +grid with the number of rows equal to the first number in the array, and the +number of columns equal to the second number in the array. Note that many graphs +will not work well in a grid layout. This is primarily intended for graphs +created via the L function where its first argument is used +as the value for this option. This option is generally intended for internal use +in this macro specifically for that function. However, there may be other graphs +that may also work well in a grid layout. + +=item bipartiteLayout + +If this option is given, then it must either be 1 or a reference to an array +containing two arrays of vertex indices. If this is 1, then a partition of the +vertices into two sets in which no two vertices in the same set are connected by +an edge will attempt to be obtained internally via the L +method. If the graph is not bipartite then no such partition will be found and +the L method will not use the bipartite layout. Instead the image will +be displayed in the default circle layout. If this is an array containing two +arrays of vertex indices, then the two arrays must be the partition of the +vertices of the graph in which no two vertices in the same set are connected by +an edge. In this case when the L method is called to create the image of +the graph, the vertices will be arranged into to rows with the vertices in the +first set in the top row, and the vertices in the second set in the bottom row. +Note that an exception will be thrown if this option is 1 and the graph is not +bipartite. + +=item wheelLayout + +If this option is given, then it must be an index of one of the vertices in the +graph (0, 1, 2, ...), and when the L method is called to create the image +of the graph, that vertex will be placed in the center of a circle, and the +other vertices will be evenly spaced around the perimeter of the circle. + +=back + +=head2 randomSimpleGraph + + $graph = randomSimpleGraph($size, %options); + +This function returns a C object in which the +existence of an edge between any two vertices is randomly determined. + +The C<$size> argument is required and must either be an integer that is greater +than zero, or a reference to an array containing two integers, both of which are +greater than zero. If this argument is an integer that is greater than zero, +then that will be the number of vertices of the graph. If this argument is a +reference to an array containing two integers that are greater than zero, then +the number of vertices in the graph will be the product of those two integers. +Furthermore, this array reference will be passed as the C option to +the C constructor, and the image of the graph will +have the vertices arranged into rows and columns. Furthermore, in the graph will +be generated such that the only possible edges will connect vertices that are +adjacent in that grid (including diagonal adjacency). + +The arguments that may be passed in C<%options> are as follows. + +=over + +=item labels + +The value of this option, if given, must be a reference to an array of strings. +These are the labels for the vertices of the graph. The array must contain at +least as many strings as there are vertices of the graph. Any extraneous strings +will not be used (except in the edge set definition case as mentioned above). If +this option is not given, then a random set of consecutive letters from the +alphabet will be used for the labels of the vertices. + +=item edgeProbability + +This is the probability that there will be an edge between any two vertices. By +default this is 0.5. Note that if C<$size> is a reference to an array of +integers, then this only applies to vertices that are adjacent in the grid, and +there will never be edges between vertices that are not adjacent in the grid. + +=item edgeWeightRange + +If this is given, then it must be a reference to an array of two or three +numbers. In this case the weights of the edges will be a random number from the +first number to the second number with increments of the optional third number +(the increment defaults to 1). In fact the elements of this array are passed +directly to the C function (see L). Note that the C option takes precedence over this +option, and if it is given and is an integer greater than one, then it will be +used instead of this option. Furthermore, if neither this option nor the +C option are given, then all edge weights will be 1. + +=item startEdgeWeight + +If this option is given, then it must be an integer that is greater than zero. +In this case this will be the first edge weight used in the generated graph, and +the other edge weights will be determined by adding increments of the +following C option. + +=item edgeWeightIncrement + +If this option is given, then it must be an integer that is greater than zero. +Note that this is only used if C is also given and is greater +than zero, and in that case increments of this number will be added to the +C and used for the weights of the edges. If the +C is given, is an integer greater than 0, and this option is +undefined, then an edge weight increment of 1 will be used. + +=back + +The examples below demonstrate the usage of this function and its options in +more details. + + $graph = randomSimpleGraph(random(3, 5), edgeProbability => 1); + +will return a complete graph with 3, 4, or 5 vertices and all edge weights equal +to 1. + + $graph = randomSimpleGraph(random(3, 5), edgeProbability => 0); + +will return a graph with 3, 4, or 5 vertices that has no edges. Note that +C<$graph = SimpleGraph(random(3, 5))> is equivalent and is slightly more +efficient. + + $graph = randomSimpleGraph( + 5, + edgeProbability => 0.3, + edgeWeightRange => [1, 10] + ); + +will return a graph with 5 vertices with probability 0.3 of an edge between any +two vertices, and random edge weights from 1 to 10 (in increments of 1). + + $graph = randomSimpleGraph( + 5, + startEdgeWeight => 2, + edgeWeightIncrement => 3 + ); + +will return a graph with 5 vertices with probability 0.5 of an edge between any +two vertices, and the edge weights will be 2, 5, 8, 11, ... (which will be +randomly assigned to the edges in the graph). + + $graph = randomSimpleGraph([3, 4], edgeProbability => 0.6); + +will return a graph with 12 vertices and all edge weights equal to 1. When the +image of this graph is displayed the vertices will be arranged into a grid with +3 rows and 4 columns. The probability of an edge between vertices that are +adjacent in the grid is 0.6. There will be no edges between vertices that are +not adjacent in the grid. + +=head2 randomGraphWithEulerCircuit + + $graph = randomGraphWithEulerCircuit($size, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that has an Euler circuit. This function +also accepts the C, C, C, and +C options accepted by the L function. + +=head2 randomGraphWithEulerTrail + + $graph = randomGraphWithEulerTrail($size, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that has an Euler trail, but does not have +an Euler circuit. This function also accepts the C, C, +C, and C options accepted by the +L function. + +=head2 randomGraphWithoutEulerTrail + + $graph = randomGraphWithoutEulerTrail($size, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that does not have an Euler trail (or +circuit). This function also accepts the C, C, +C, and C options accepted by the +L function. + +=head2 randomBipartiteGraph + + $graph = randomBipartiteGraph($size, %options); + +This function returns a C object that represents a +random bipartite graph. + +The C<$size> argument can either be a number or a reference to an array of two +integers that are greater than zero. If it is a number, then the graph will have +C<$size> vertices, and the number of vertices in the two parts of the bipartite +partition will be randomly determined. If it is a reference to an array of two +integers, then graph will have the number of vertices equal to the sum of those +two numbers, and the two sets in the bipartite partition will have those numbers +of elements. + +This function also accepts the C, C, +C, C, and C options +accepted by the L function. + +=head2 randomTreeGraph + + $graph = randomTreeGraph($size, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that is a tree. This function also accepts +the C, C, C, and +C options accepted by the L function. + +=head2 randomForestGraph + + $graph = randomForestGraph($size, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that is a forest, but is not a tree. This +function also accepts the C, C, C, +and C options accepted by the L function. + +=head2 randomHamiltonianGraph + + $graph = randomHamiltonianGraph($size, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that is Hamiltonian. Note that C<$size> must +be 5 or more, or an exception will be thrown. The C may be passed in +C<%options>, and is the same as for the above functions. + +=head2 randomNonHamiltonianGraph + + $graph = randomNonHamiltonianGraph($size, $type, %options); + +This function returns a C object that represents a +random graph with C<$size> vertices that is not Hamiltonian. + +If C<$type> is even, then the graph will have a vertex of degree one. If +C<$type> is odd, then the graph will consist of two cycles joined by a single +edge. Note that for the odd C<$type> graph, the C<$size> must be at least 6, or +an exception will be thrown. + +This function also accepts the C, C, +C, and C options accepted by the +L function. + +=head2 randomGraphWithDegreeSequence + + $graph = randomGraphWithDegreeSequence($degrees, %options); + +This function returns a C object that represents a +random graph that has the given C<$degree> sequence, if such a graph is +possible, and undefined otherwise. + +The C<$degrees> argument must be a reference to an array containing the desired +vertex degrees. + +This function also accepts the C, C, +C, and C options accepted by the +L function. + +=head2 cycleGraph + + $graph = cycleGraph($size, %options); + +This function returns a C object that represents a +graph with C<$size> vertices that is a cycle. This function also accepts the +C, C, C, and C +options accepted by the L function. + +=head2 completeGraph + + $graph = completeGraph($size, %options); + +This function returns a C object that represents a +graph with C<$size> vertices that is a complete graph. This function also +accepts the C, C, C, and +C options accepted by the L function. + +=head2 wheelGraph + + $graph = wheelGraph($size, %options); + +This function returns a C object that represents a +graph with C<$size> vertices that is a wheel. This function also accepts the +C, C, C, and C +options accepted by the L function. + +Note that the returned graph object will have the C graph option +set to the vertex at the hub of the wheel, and that vertex will be displayed in +the center in the graph image. + +=head2 completeBipartiteGraph + + $graph = completeBipartiteGraph($m, $n, %options); + +This function returns a C object that represents a +graph with C<$m * $n> vertices that is a complete bipartite graph with C<$m> +vertices in first set in the bipartite partition, and C<$n> vertices in the +other set in the bipartite partition. This function also accepts the C, +C, C, and C options +accepted by the L function. + +Note that the returned graph object will have the C graph +option set so that the graph image is nicely displayed with the vertices in the +first set in the bipartite partition in a row on the top, and the vertices in +the other set in a row on the bottom. + +=head2 EdgeSet + + $edgeSet = EdgeSet($edges); + $edgeSet = EdgeSet($edge1, $edge2, ...); + +This method is an alias for the C +MathObject constructor. It can be passed a reference to an array of edges or +just a list of edges. An edges can be a reference to an array containing two +vertices, or can be a C MathObject. As +with all MathObject constructors, the first argument can optionally be a +context. Note that vertices must be strings in the current context or the +context that is passed. + +Since this derives from a MathObject C, a C must be used for +a custom checker routine. + +Usually this would only be used in the C context. Otherwise the +returned value will not display correctly in the problem and will not work well +as an answer. However, the object can still be passed to the C +function to create a graph with that edge set. + +=head2 Edge + + $edge = Edge($vertex1, $vertex2); + $edge = EdgeSet([ $vertex1, $vertex2 ]); + +This method is an alias for the C +MathObject constructor. It can be passed a reference to an array containing two +vertices or a list of two vertices. As with all MathObject constructors, the +first argument can optionally be a context. Note that vertices must be strings +in the current context or the context that is passed. + +Since this derives from a MathObject C, a C must be used for +a custom checker routine. + +Usually this would only be used in the C context. Otherwise the +returned value will not display correctly in the problem and will not work well +as an answer. However, the object can still be passed in an array reference to +the C function to create a graph with that edge set. + +=head1 GraphTheory::SimpleGraph Methods + +The C package is the heart of this macro. All of the +functions described above return an object that is an instance of this package +class. See the L function for the constructor usage. In addition, +the following object methods are available. + +=head2 adjacencyMatrix + + $matrix = $graph->adjacencyMatrix; + +This method returns the adjacency matrix that defines the graph. That is a +reference to an array of array references each containing numbers. The matrix +will be a symmetric square matrix with zero entries along the diagonal. The +entry in the i-th row and j-th column will be zero if there is not an edge +connecting the i-th and j-th vertex, and will be nonzero if there is such an +edge. Furthermore, the entry represents the edge weight in the case that it is +not zero. + +Note that indexing of vertices is zero based. So the first vertex has index 0, +and the last vertex has index one less than the number of vertices in the graph. + +=head2 edgeSet + + $edgeSet = $graph->edgeSet(%options); + +This method returns the edge set for the graph as a MathObject C +containing MathObject Cs. For example, if a graph has adjacency matrix + + [ + [ 0, 1, 0, 0 ], + [ 1, 0, 1, 0 ], + [ 0, 1, 0, 1 ] + [ 0, 0, 1, 0 ] + ] + +and labels 'A', 'B', 'C', and 'D', then this method would return + + {{A, B}, {B, C}, {C, D}} + +where the outer set is a MathObject C, the elements of that set are +MathObject Cs, and the elements of those sets are MathObject Cs. +The returned C will be either be in a created C context with +the vertex labels of the graph defined as strings and marked as being vertices +in that context, or in a context provided in the C<%options> as described below. + +This return value can be used as an answer to a question, and will display in +output as shown above. + +Note, that edge weights are not represented in the return value of this method. + +The following options that may be passed in C<%options>. + +=over + +=item context (Default: C<< context => undef >>) + +The context to use for the returned MathObject C object. If this is not +provided, then a C context will be created, the vertex labels added as +strings to the context and marked as being vertices and the returned object will +be in the created context. + +=item caseSensitive (Default: C<< caseSensitive => 1 >>) + +Whether vertex labels that are added to the context are case sensitive or not. + +=back + +=head2 addVerticesToContext + + $graph->addVerticesToContext($caseSensitive, $context); + +This adds the vertex labels of the graph to the context and marks them as being +vertices (by adding the C flag). + +The C<$caseSensitive> and C<$context> arguments are optional. + +If C<$caseSensitive> is 1 or the argument is not provided, then the vertices +will be case sensitive. So entering "a" will not be accepted as correct for the +vertex labeled "A". If C<$caseSensitive> is 0, then vertices will not be case +sensitive. So "a" will be accepted as correct for the vertex labeled "A". + +If C<$context> is provided, then the strings will be added to that context. +Otherwise the strings will be added to the current context. + +=head2 numVertices + + $n = $graph->numVertices; + +This method returns the number of vertices in the graph. + +=head2 lastVertex + + $n = $graph->lastVertexIndex; + +This method returns the index of the last vertex in the graph (i.e., it is one +less then the number of vertices). + +=head2 numEdges + + $n = $graph->numEdges; + +This method returns the number of edges in the graph. + +=head2 labels + + $labels = $graph->labels; + $graph->labels($labels); + +Get or set the vertex labels for the graph. In both examples above, C<$labels> +is a reference to an array of strings, for example, C<[ 'M', 'N', 'O', 'P' ]>. + +=head2 labelsString + + $labelsString = $graph->labelsString; + +This returns the vertex labels joined with a comma. This is a convenience method +for displaying labels in a problem. For example, the labels string can be +inserted into a PGML block with C<< [`[$graph->labelsString]`] >>. + +=head2 vertexLabel + + $label = $graph->vertexLabel($index); + +This method returns the label of the vertex at index C<$index> in the graph. + +=head2 vertexIndex + + $index = $graph->vertexIndex($label); + +This method returns the index of the vertex in the graph that is labeled +C<$label> if the given C<$label> exists for a vertex, and -1 otherwise. + +=head2 vertexDegree + + $degree = $graph->vertexDegree($index); + +This method returns the degree of the vertex at index C<$index> in the graph. + +=head2 degrees + + @degrees = $graph->degrees; + +This method returns an array of the degrees of the vertices in the graph. + +=head2 numComponents + + $c = $graph->numComponents; + +This method returns the number of connected components in the graph. + +=head2 edgeWeight + + $c = $graph->edgeWeight($i, $j); + $graph->edgeWeight($i, $j, $weight); + +Get or set the weight of the edge between the vertices at indices C<$i> and +C<$i>. If the optional third C<$weight> argument is provided, then the weight +of the edge between the vertices at indices C<$i> and C<$j> will be set to +C<$weight>. In any case, the weight of the edge between the vertices at indices +C<$i> and C<$j> will be returned. Note that an edge weight of zero means that +there is no edge connecting the vertices. + +Setting the edge weight to zero is equivalent to removing the edge if it was +nonzero, and setting the edge weight to a nonzero number when it was zero adds +an edge. However, if your only intent is to add or remove an edge, then it is +recommended to use the C or C methods instead for clarity +in the code. + +=head2 addEdge + + $graph->addEdge($i, $j, $weight); + +Add and edge that connects the vertices at indices C<$i> and C<$j>. The +C<$weight> argument is optional, and if not provided the weight of the edge will +be set to 1. + +=head2 removeEdge + + $graph->removeEdge($i, $j); + +Remove the edge that connects the vertices at indices C<$i> and C<$j>. This is +accomplished by setting the weight of the edge between the two vertices to 0. + +=head2 hasEdge + + $graph->hasEdge($i, $j); + +This method returns a true value if the graph has an edge that connects the +vertices at indices C<$i> and C<$j>, and a false value otherwise. + +=head2 setRandomWeights + + $graph->setRandomWeights(%options); + +Set random weights for the edges of the graph. This method does not add or +remove any edges, but randomly sets the weights of the existing edges. + +The arguments that may be passed in C<%options> are as follows. If neither of +the C or C options is given, then this +method does not change the current edge weights of the graph. + +=over + +=item startEdgeWeight + +If this option is given, then it must be an integer that is greater than zero. +In this case this will be the first edge weight used in the generated graph, and +the other edge weights will be determined by adding increments of the +following C option. + +=item edgeWeightIncrement + +If this option is given, then it must be an integer that is greater than zero. +Note that this is only used if C is also given and is greater +than zero, and in that case increments of this number will be added to the +C and used for the weights of the edges. If the +C is given, is an integer greater than 0, and this option is +undefined, then an edge weight increment of 1 will be used. + +=item edgeWeightRange + +If this is given, then it must be a reference to an array of two or three +numbers. In this case the weights of the edges will be a random number from the +first number to the second number with increments of the optional third number +(the increment defaults to 1). In fact the elements of this array are passed +directly to the C function (see L). Note that the C option takes precedence over this +option, and if it is given and is an integer greater than one, then it will be +used instead of this option. + +=item edgeCount + +The number of edges in the graph for which to set random weights. If this +option is not given, then the number of edges in the graph will be computed, and +all the weights of all existing edges will be randomized. If the number of edges +in the graph is already known, then set this option to that value for efficiency +so that this method does not need to compute that number again. + +=back + +=head2 isEqual + + $graph->isEqual($other); + +This method returns 1 if the graph object represented by C<$graph> and the graph +represented by C<$other> are the same. This does not necessarily return 1 for +isomorphic graphs. The two graphs must literally have the same adjacency matrix. + +=head2 isIsomorphic + + $graph->isIsomorphic($other); + +This method returns 1 if the graph represented by C<$graph> and the graph +represented by C<$other> are isomorphic. + +WARNING: This method uses a brute force approach and compares the first graph to +all possible permutations of the other graph, and so should not be used for +graphs with a large number of vertices (probably no more than 8). + +=head2 image + + $graph->image(%options); + +Constructs and returns a L object via the L macro that +provides a pictorial representation of the graph. The returned object may be +inserted into the problem via the C method (see L), L, or using the PGML image syntax +(for example, C<< [!alt text!]{$graph->image} >>). + +Note that the C, C, and C options that +can be set when the C object is created affect how the +graph is displayed. See the L function for details on those +options. Note that those options can also be set as properties of the object +anytime after construction and before this method is called (for example, +C<< $self->{bipartiteLayout = 1; >>). If none of those layout options are used, +then the vertices of the graph will be evenly spaced around the perimeter of a +circle with the vertex at index 0 on the right. + +The following options can be set via the C<%options> argument. + +=over + +=item showLabels + +If this is 1, then vertex labels will be shown. If this is 0, then vertex labels +will not be shown. Default is 1 (so labels must be explicitly hidden by setting +this to 0). + +=item showWeights + +If this is 1, then edge weights will be shown. If this is 0, then edge weights +will not be shown. Default is 0. + +Note that the display of edge weights often does not work well as it can be +unclear which edge a given weight belongs to in the image. Particularly if a +graph has a large number of edges. Graphs that are created via the +C function using the row and column C<$size> argument (and +hence are displayed using the grid layout) do work quite well for this. + +=item width + +This is the width of the image. Default is 250 (except in the case that the +C is used, in which case the default is the product of 140 and one +less than the number of columns in the grid layout). + +Note that the width can also be set via the L C +method, or via the width option for the PGML image syntax. + +=item height + +This is the height of the image. Default is undefined. + +Note that the height can also be set via the L C +method, or via the height option for the PGML image syntax. + +=item aspect_ratio + +If this is set and the C option above is not set, then the height of the +image will be computed from the width using this aspect_ratio. Default is 1. + +=back + +Note that aside from the C and C options, all other +options (including any not listed above) that are provided are actually just +passed to the underlying C object. + +=head2 gridLayoutImage + + $graph->gridLayoutImage(%options); + +This method is not intended to be used externally. It is called by the L +method if the C property is set for the graph object. If this method +is called directly and the C property is set, then it will still +work. Otherwise an exception will be thrown. It accepts the same options as the +L method. Note that the default value for the C option is the +product of 140 and one less than the number of columns in the grid layout. + +=head2 bipartiteLayoutImage + + $graph->bipartiteLayoutImage(%options); + +This method is not intended to be used externally. It is called by the L +method if the C property is set for the graph object. If this +method is called directly and the C property is set, then it +will still work. Otherwise an exception will be thrown. Note that an exception +will also be thrown if the C property is set to 1, and the +graph is not bipartite. It accepts the same options as the L method. + +=head2 wheelLayoutImage + + $graph->wheelLayoutImage(%options); + +This method is not intended to be used externally. It is called by the L +method if the C property is set for the graph object. If this +method is called directly and the C property is set, then it will +still work. Otherwise an exception will be thrown. It accepts the same options +as the L method. + +=head2 copy + + $copy = $graph->copy; + +This method returns a copy of C<$graph>. This is an exact copy with all +properties duplicated to the returned object. + +=head2 shuffle + + $shuffledGraph = $graph->shuffle($permuteLabels); + +This method returns a randomization of C<$graph> obtained by permuting the +vertices. The edges that connected the vertices in the original graph will still +connect the permuted vertices in the resulting graph. In other words the +returned graph is isomorphic to the original. + +If the optional C<$permuteLabels> argument is provided and is true, then the +labels will also be permuted with the vertices. Otherwise the labels will remain +in the same order that they were in the original graph, but the shuffled graph +will still have the same labels. + +If the C property is set to 1 for the original graph, then the +shuffled graph will also have also have that property set to 1. However, the +array form of the C property will not be preserved. + +The C property is not preserved in the shuffled graph in any case. + +The C property is preserved in the shuffled graph, and its value +will be permuted so that the vertex it marked as the hub of the wheel is still +the hub. This means that the hub of the wheel will still be displayed in the +center when the L method is used to display the graph. If that is not +desired, then delete the C property for the result. For example, + + $shuffledGraph = $graph->shuffle; + delete $shuffledGraph->{wheelLayout}; + +=head2 nearestNeighborPath + + ($path, $weight) = $graph->nearestNeighborPath($vertex); + +This is an implementation of the nearest neighbor algorithm. It attempts to find +the shortest path starting at the vertex indexed by C<$vertex> that visits all +vertices in the graph exactly once and returns to the starting vertex. Note that +if such a path is not possible, then the algorithm will go as far as it can, but +the path that is returned may not visit all vertices and may not be a circuit. +This method will always succeed for complete graphs. + +The method returns a list whose first entry is a reference to an array +containing the path found, and whose second entry is the total weight of that +path. + +=head2 kruskalGraph + + ($tree, $treeWeight, $treeWeights) = $graph->kruskalGraph($vertex); + +This is an implementation of Kruskal's algorithm. It attempts to find a minimum +spanning tree or forest for the graph. Note that if the graph is connected, then +the result will be a tree, and otherwise it will be a forest consisting of +minimal spanning trees for each component. + +The method returns a list with three entries. The first entry is a +C object representing the tree or forest found. The +second entry is the total weight of that tree or forest. The last entry is a +reference to an array containing the weights of the edges in the tree or forest +in the order that they are added by the algorithm. + +=head2 hasEulerCircuit + + $hasEulerCircuit = $graph->hasEulerCircuit; + ($hasEulerCircuit, $message) = $graph->hasEulerCircuit; + +In scalar context this method returns 1 if the graph has an Euler circuit, and 0 +otherwise. In list context this method returns 1 if the graph has an Euler +circuit, and if the graph does not have an Euler circuit it returns a list whose +first entry is 0 and whose second entry is a message stating the reason that the +graph does not have an Euler circuit. + +=head2 hasEulerTrail + + $hasEulerTrail = $graph->hasEulerTrail; + ($hasEulerTrail, $message) = $graph->hasEulerTrail; + +In scalar context this method returns 1 if the graph has an Euler trail, and 0 +otherwise. In list context this method returns 1 if the graph has an Euler +trail, and if the graph does not have an Euler trail it returns a list whose +first entry is 0 and whose second entry is a message stating the reason that the +graph does not have an Euler trail. + +=head2 pathIsEulerTrail + + $pathIsEulerTrail = $graph->pathIsEulerTrail(@path); + ($pathIsEulerTrail, $message) = $graph->pathIsEulerTrail(@path); + +The C<@path> argument should contain a list of vertex indices in the graph +representing a path in the graph. In scalar context this method returns 1 if the +path forms an Euler trail in the graph, and 0 otherwise. In list context this +method returns 1 if the path forms an Euler trail in the graph, and if the path +does not form an Euler trail in the graph it returns a list whose first entry is +0 and whose second entry is a message stating the reason that the path does not +form an Euler trail. + +=head2 pathIsEulerCircuit + + $pathIsEulerCircuit = $graph->pathIsEulerCircuit(@path); + ($pathIsEulerCircuit, $message) = $graph->pathIsEulerCircuit(@path); + +The C<@path> argument should contain a list of vertex indices in the graph +representing a path in the graph. In scalar context this method returns 1 if the +path forms an Euler circuit in the graph, and 0 otherwise. In list context this +method returns 1 if the path forms an Euler circuit in the graph, and if the +path does not form an Euler circuit in the graph it returns a list whose first +entry is 0 and whose second entry is a message stating the reason that the path +does not form an Euler circuit. + +=head2 hasCircuit + + $hasCircuit = $graph->hasCircuit; + +This method returns 1 if the graph has a circuit, and 0 otherwise. + +=head2 isTree + + $isTree = $graph->isTree; + ($isTree, $message) = $graph->isTree; + +In scalar context this method returns 1 if the graph is a tree, and 0 otherwise. +In list context this method returns 1 if the graph is a tree, and if the graph +is not a tree it returns a list whose first entry is 0 and whose second entry is +a message stating the reason that the graph is not a tree. + +=head2 isForest + + $isForest = $graph->isForest; + ($isForest, $message) = $graph->isForest; + +In scalar context this method returns 1 if the graph is a forest, and 0 +otherwise. In list context this method returns 1 if the graph is a forest, and +if the graph is not a forest it returns a list whose first entry is 0 and whose +second entry is a message stating the reason that the graph is not a forest. + +=head2 isBipartite + + $isBipartite = $graph->isBipartite; + +This method returns 1 if the graph is bipartite, and 0 otherwise. + +=head2 bipartitePartition + + ($upper, $lower) = $graph->bipartitePartition; + +If the graph is bipartite, then this method returns a list containing two +entries that form a partition of the vertices of the graph into two sets for +which no two vertices in the same set have an edge connecting them. If the graph +is not bipartite, then this method returns undefined. + +=head2 dijkstraPath + + ($distance, @path) = $graph->dijkstraPath($start, $end); + +This is an implementation of Dijkstra's algorithm for finding the shortest path +between nodes in a weighted graph. + +The C<$start> and C<$end> arguments are required and should be indices of two +vertices in the graph representing the start vertex and end vertex for which to +find the shortest path between. + +The return value will be a list whose first entry is the shortest distance from +the start vertex to the end vertex, and whose remaining entries are the indices +of the vertices that form the shortest path from the start vertex to the end +vertex. + +=head2 sortedEdgesPath + + ($sortedEdgesPath, $edgeWeights) = $graph->sortedEdgesPath; + +This is an implementation of the sorted edges algorithm for finding the shortest +Hamiltonian circuit in a graph. That is a path that visits each vertex in the +graph exactly once. The return value will be a list with two entries The first +entry is the resulting sorted edges graph, and the second entry is a reference +to an array containing the weights of the edges in the path in the order that +they are chosen by the algorithm. Note that the returned graph will contain a +Hamiltonian circuit from the original graph if one exists. In any case the graph +will contain all edges chosen in the algorithm. + +=head2 chromaticNumber + + $graph->chromaticNumber; + +This method returns the chromatic number of the graph. That is the minimum +number of colors required to color the vertices such that no two adjacent +vertices share the same color. + +=head2 kColoring + + $graph->kColoring($k); + +The argument C<$k> is required and is the desired number of colors for which to +find a k-coloring. That is a coloring of the graph consisting of at most k +colors such that no two adjacent vertices share the same color. If such a +coloring is possible, then this method will return a list with the number of +entries equal to the number of vertices in the graph, and whose i-th entry will +be an integer from 1 to k where the integers represent a coloring for the vertex +with index i in the graph and such that the list forms a k-coloring. If a +k-coloring is not possible, then this method returns undefined. + +=head1 EdgeSet Context + +A context for edge sets is provided. To activate the context Call + + Context('EdgeSet'); + +Then C and +C objects can be constructed with + + $edge = Compute('{B, E}'); + $edgeSet = Compute('{{A, B}, {A, E}, {A, F}, {E, F}}'); + +or by using the C or C methods described before (those methods +can also be used outside of this context). Note that the vertices must be added +as strings to the context and marked as being vertices with the C +flag before constructing the C or C. For example, by calling + + Context()->strings->add( + map { $_ => { isVertex => 1, caseSensitive => 1 } } 'A' .. 'F' + ); + +If it is prefered that the vertices not be case sensitive, then remove +C<< caseSensitive => 1 >> from the above call. + +If the vertices in the C or C belong to a C +object that has already been constructed, then the L +method can instead be used to add the vertices to the context. + +=cut diff --git a/macros/math/SimpleGraphCatalog.pl b/macros/math/SimpleGraphCatalog.pl new file mode 100644 index 0000000000..c90fd2c711 --- /dev/null +++ b/macros/math/SimpleGraphCatalog.pl @@ -0,0 +1,10735 @@ + +=head1 NAME + +SimpleGraphCatalog.pl - A catalog of all simple graphs with fewer than 8 +vertices. + +=head1 DESCRIPTION + +This macro defines the C<$graphCatalog> hash. It has 8 keys (0, 1, ..., 8). the +value of each key is a reference to an array containing all possible graphs (up +to isomorphism) with that number of vertices. + +Graphs are represented as a reference to an array of arrays which are suitable +for passing to L to create a +C object. + +For example, C<$graphCatalog{3}[3]> is + + [ [ 0, 1, 1 ], [ 1, 0, 1 ], [ 1, 1, 0 ] ] + +=cut + +BEGIN { strict->import; } + +sub _GraphCatalog_init { } + +%main::graphCatalog = ( + 1 => [ [ [0] ] ], + 2 => [ [ [ 0, 0 ], [ 0, 0 ] ], [ [ 0, 1 ], [ 1, 0 ] ] ], + 3 => [ + [ [ 0, 0, 0 ], [ 0, 0, 0 ], [ 0, 0, 0 ] ], + [ [ 0, 0, 1 ], [ 0, 0, 0 ], [ 1, 0, 0 ] ], + [ [ 0, 0, 1 ], [ 0, 0, 1 ], [ 1, 1, 0 ] ], + [ [ 0, 1, 1 ], [ 1, 0, 1 ], [ 1, 1, 0 ] ], + ], + 4 => [ + [ [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ] ], + [ [ 0, 0, 0, 1 ], [ 0, 0, 0, 0 ], [ 0, 0, 0, 0 ], [ 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 1 ], [ 0, 0, 0, 1 ], [ 0, 0, 0, 0 ], [ 1, 1, 0, 0 ] ], + [ [ 0, 0, 0, 1 ], [ 0, 0, 0, 1 ], [ 0, 0, 0, 1 ], [ 1, 1, 1, 0 ] ], + [ [ 0, 0, 1, 0 ], [ 0, 0, 0, 1 ], [ 1, 0, 0, 0 ], [ 0, 1, 0, 0 ] ], + [ [ 0, 0, 1, 1 ], [ 0, 0, 0, 1 ], [ 1, 0, 0, 0 ], [ 1, 1, 0, 0 ] ], + [ [ 0, 0, 1, 1 ], [ 0, 0, 0, 0 ], [ 1, 0, 0, 1 ], [ 1, 0, 1, 0 ] ], + [ [ 0, 0, 1, 1 ], [ 0, 0, 0, 1 ], [ 1, 0, 0, 1 ], [ 1, 1, 1, 0 ] ], + [ [ 0, 0, 1, 1 ], [ 0, 0, 1, 1 ], [ 1, 1, 0, 0 ], [ 1, 1, 0, 0 ] ], + [ [ 0, 0, 1, 1 ], [ 0, 0, 1, 1 ], [ 1, 1, 0, 1 ], [ 1, 1, 1, 0 ] ], + [ [ 0, 1, 1, 1 ], [ 1, 0, 1, 1 ], [ 1, 1, 0, 1 ], [ 1, 1, 1, 0 ] ] + ], + 5 => [ + [ [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ], [ 1, 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0 ], [ 1, 1, 1, 0, 0 ] ], + [ [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1 ], [ 1, 1, 1, 1, 0 ] ], + [ [ 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 0 ], [ 1, 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1 ], [ 1, 0, 0, 0, 0 ], [ 0, 1, 1, 0, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 1 ], [ 1, 0, 0, 1, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1 ], [ 1, 0, 0, 0, 0 ], [ 1, 1, 1, 0, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 0 ], [ 1, 0, 0, 0, 1 ], [ 1, 1, 0, 1, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 1 ], [ 0, 0, 0, 0, 1 ], [ 1, 0, 0, 0, 1 ], [ 1, 1, 1, 1, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 0 ], [ 1, 1, 0, 0, 0 ], [ 1, 1, 0, 0, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 1 ], [ 1, 1, 0, 0, 0 ], [ 1, 1, 1, 0, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 0 ], [ 1, 1, 0, 0, 1 ], [ 1, 1, 0, 1, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 1, 0 ], [ 0, 0, 0, 0, 1 ], [ 1, 1, 0, 0, 1 ], [ 1, 0, 1, 1, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 1 ], [ 1, 1, 0, 0, 1 ], [ 1, 1, 1, 1, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 1, 1, 1, 0, 0 ], [ 1, 1, 1, 0, 0 ] ], + [ [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 1, 1, 1, 0, 1 ], [ 1, 1, 1, 1, 0 ] ], + [ [ 0, 0, 1, 0, 1 ], [ 0, 0, 0, 1, 1 ], [ 1, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0 ], [ 1, 1, 0, 0, 0 ] ], + [ [ 0, 0, 1, 0, 1 ], [ 0, 0, 0, 1, 0 ], [ 1, 0, 0, 0, 1 ], [ 0, 1, 0, 0, 0 ], [ 1, 0, 1, 0, 0 ] ], + [ [ 0, 0, 1, 0, 1 ], [ 0, 0, 0, 1, 1 ], [ 1, 0, 0, 0, 1 ], [ 0, 1, 0, 0, 0 ], [ 1, 1, 1, 0, 0 ] ], + [ [ 0, 0, 1, 0, 1 ], [ 0, 0, 0, 1, 1 ], [ 1, 0, 0, 0, 1 ], [ 0, 1, 0, 0, 1 ], [ 1, 1, 1, 1, 0 ] ], + [ [ 0, 0, 1, 1, 0 ], [ 0, 0, 0, 1, 1 ], [ 1, 0, 0, 0, 1 ], [ 1, 1, 0, 0, 0 ], [ 0, 1, 1, 0, 0 ] ], + [ [ 0, 0, 1, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 1, 0, 0, 0, 1 ], [ 1, 1, 0, 0, 0 ], [ 1, 1, 1, 0, 0 ] ], + [ [ 0, 0, 1, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 1, 0, 0, 0, 1 ], [ 1, 1, 0, 0, 1 ], [ 1, 1, 1, 1, 0 ] ], + [ [ 0, 0, 1, 1, 1 ], [ 0, 0, 0, 0, 1 ], [ 1, 0, 0, 1, 1 ], [ 1, 0, 1, 0, 0 ], [ 1, 1, 1, 0, 0 ] ], + [ [ 0, 0, 1, 1, 1 ], [ 0, 0, 0, 0, 0 ], [ 1, 0, 0, 1, 1 ], [ 1, 0, 1, 0, 1 ], [ 1, 0, 1, 1, 0 ] ], + [ [ 0, 0, 1, 1, 1 ], [ 0, 0, 0, 0, 1 ], [ 1, 0, 0, 1, 1 ], [ 1, 0, 1, 0, 1 ], [ 1, 1, 1, 1, 0 ] ], + [ [ 0, 0, 1, 1, 1 ], [ 0, 0, 0, 1, 1 ], [ 1, 0, 0, 1, 1 ], [ 1, 1, 1, 0, 1 ], [ 1, 1, 1, 1, 0 ] ], + [ [ 0, 0, 1, 1, 1 ], [ 0, 0, 1, 1, 1 ], [ 1, 1, 0, 0, 1 ], [ 1, 1, 0, 0, 0 ], [ 1, 1, 1, 0, 0 ] ], + [ [ 0, 0, 1, 1, 1 ], [ 0, 0, 1, 1, 1 ], [ 1, 1, 0, 0, 1 ], [ 1, 1, 0, 0, 1 ], [ 1, 1, 1, 1, 0 ] ], + [ [ 0, 0, 1, 1, 1 ], [ 0, 0, 1, 1, 1 ], [ 1, 1, 0, 1, 1 ], [ 1, 1, 1, 0, 1 ], [ 1, 1, 1, 1, 0 ] ], + [ [ 0, 1, 1, 1, 1 ], [ 1, 0, 1, 1, 1 ], [ 1, 1, 0, 1, 1 ], [ 1, 1, 1, 0, 1 ], [ 1, 1, 1, 1, 0 ] ] + ], + 6 => [ + [ + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 0, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1 ], + [ 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 0, 1 ], + [ 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0 ], + [ 0, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1 ], + [ 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1 ], + [ 1, 0, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1 ], + [ 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 0 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 0, 1, 1, 1 ], + [ 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 1, 1, 1, 1, 1 ], + [ 1, 0, 1, 1, 1, 1 ], + [ 1, 1, 0, 1, 1, 1 ], + [ 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 0 ] + ] + ], + 7 => [ + [ + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 0 ], + [ 1, 0, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 0 ], + [ 0, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 0, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 0, 0, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 0, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 0, 1, 1, 1, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 0, 1, 1, 1, 0, 1 ], + [ 1, 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 0, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 0, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 0, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 0, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 0, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 0, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 0, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 0, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 0, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 0, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 0, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 0, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 1, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 0, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 0, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 0, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 0, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 0, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 1, 1 ], + [ 0, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 0, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 0, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 0, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 0 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 1, 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 0, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 0, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 0, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 0, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 0 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 0, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 0 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 0, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 1, 1, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 0, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 0, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 0, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 0, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 0, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 0, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 0, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 0, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 0 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 0, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 0 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 0, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 0, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 0, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 0 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 0 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 0, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 0, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1, 1 ], + [ 1, 0, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 0, 1, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 1, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 0, 1, 1, 1, 1 ], + [ 1, 0, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 0 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 0, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1, 0 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 0, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 0, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 0, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 0, 1, 1 ], + [ 1, 1, 1, 0, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 0 ], + [ 1, 1, 1, 1, 1, 0, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 0, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 0, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 0, 0, 1, 1, 1, 1, 1 ], + [ 1, 1, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ], + [ + [ 0, 1, 1, 1, 1, 1, 1 ], + [ 1, 0, 1, 1, 1, 1, 1 ], + [ 1, 1, 0, 1, 1, 1, 1 ], + [ 1, 1, 1, 0, 1, 1, 1 ], + [ 1, 1, 1, 1, 0, 1, 1 ], + [ 1, 1, 1, 1, 1, 0, 1 ], + [ 1, 1, 1, 1, 1, 1, 0 ] + ] + ] +); + +1; diff --git a/macros/math/algebraMacros.pl b/macros/math/algebraMacros.pl index bbe70005de..da5772496b 100644 --- a/macros/math/algebraMacros.pl +++ b/macros/math/algebraMacros.pl @@ -38,7 +38,7 @@ =head2 xgcd =cut -# by Dick Lane http://webwork.maa.org/moodle/mod/forum/discuss.php?d=2286 +# by Dick Lane https://forums.openwebwork.org/mod/forum/discuss.php?d=2286 sub xgcd ($$) { my ($a, $b, $x, $y, $s, $t) = (@_, 1, 0, 0, 1); diff --git a/macros/math/draggableProof.pl b/macros/math/draggableProof.pl index 9a79a7fb33..1877a4d22f 100644 --- a/macros/math/draggableProof.pl +++ b/macros/math/draggableProof.pl @@ -160,8 +160,8 @@ =head1 CUSTOM CHECKERS Custom checkers can also be used by passing the C or C options to the C method. See -L, and -L for details on +L, and +L for details on how to use these. Note that if using a standard C the correct and student answers diff --git a/macros/math/draggableSubsets.pl b/macros/math/draggableSubsets.pl index 53328cb414..0c0c7bc77e 100644 --- a/macros/math/draggableSubsets.pl +++ b/macros/math/draggableSubsets.pl @@ -188,7 +188,7 @@ =head1 SYNOPSIS =head1 CUSTOM CHECKERS A custom checkers can also be used by passing the C option to the -C method. See L +C method. See L for details on how to use a custom list checker. This follows the usual rules for the return value of the C method. diff --git a/macros/math/tableau.pl b/macros/math/tableau.pl index 91d33a0855..06fb4ef6b5 100644 --- a/macros/math/tableau.pl +++ b/macros/math/tableau.pl @@ -157,9 +157,9 @@ =head2 new =head1 REFERENCES -MathObject Matrix methods: L -MathObject Contexts: L -CPAN RealMatrix docs: L +MathObject Matrix methods: L +MathObject Contexts: L +CPAN RealMatrix docs: L More references: L @@ -1477,7 +1477,7 @@ sub submatrix { =cut # This was written by Davide Cervone. -# http://webwork.maa.org/moodle/mod/forum/discuss.php?d=2970 +# https://forums.openwebwork.org/mod/forum/discuss.php?d=2970 # taken from MatrixReduce.pl from Paul Pearson sub change_matrix_entry { diff --git a/macros/misc/randomPerson.pl b/macros/misc/randomPerson.pl index d3bf6b66a8..06ee34a7ab 100644 --- a/macros/misc/randomPerson.pl +++ b/macros/misc/randomPerson.pl @@ -49,7 +49,7 @@ =head2 Usage Depending on the he/she/they pronoun, the methods C, C, C and C (with or without capitalization) will select the correct subject, object, -possession, possesive forms of the pronoun. Also, note that C is one of a +possession, possessive forms of the pronoun. Also, note that C is one of a few special verbs with irregular conjugation. If you would like multiple people to be randomly choosen with unique names, use diff --git a/macros/parsers/parserCheckboxList.pl b/macros/parsers/parserCheckboxList.pl index 593192d271..9a6c86c4ea 100644 --- a/macros/parsers/parserCheckboxList.pl +++ b/macros/parsers/parserCheckboxList.pl @@ -137,7 +137,8 @@ =head1 DESCRIPTION =item C string >>> -Specifies the text to put between the checkboxes. Default: $BR +Specifies the text to put between the checkboxes. Note that this is only used in +HTML display modes, and is ignored for PTX and TeX display modes. Default: $BR =item C choice >>> @@ -157,6 +158,7 @@ =head1 DESCRIPTION the literal correct answer, not an index to it. Default: 0 =item C 0 or 1 >>> + In static output, such as PDF or PTX, this controls whether or not the list of answer options is displayed. (The text preceding the list of answer options might make printing the answer option list @@ -253,7 +255,7 @@ sub new { $self->getCheckedChoices($self->{checked}); $context->strings->add(map { ($self->{values}[$_] => {}) } (0 .. ($self->{n} - 1))); - $_ = Value::makeValue($_, context => $context) for @{ $self->data }; + $_ = $context->Package('String')->make($context, $_) for @{ $self->data }; return $self; } @@ -614,7 +616,10 @@ sub CHECKS { $checks[-1] .= ""; } - return wantarray ? @checks : join($main::displayMode eq 'PTX' ? '' : $self->{separator}, @checks); + return + wantarray + ? @checks + : join($main::displayMode eq 'PTX' || $main::displayMode eq 'TeX' ? '' : $self->{separator}, @checks); } sub protect { diff --git a/macros/parsers/parserRadioButtons.pl b/macros/parsers/parserRadioButtons.pl index 88a1122f28..83551ad62e 100644 --- a/macros/parsers/parserRadioButtons.pl +++ b/macros/parsers/parserRadioButtons.pl @@ -140,7 +140,9 @@ =head1 DESCRIPTION =item C string >>> -Specifies the text to put between the radio buttons. Default: $BR +Specifies the text to put between the radio buttons. Note that this is only used +in HTML display modes, and is ignored for PTX and TeX display modes. +Default: $BR =item C choice >>> @@ -699,7 +701,9 @@ sub BUTTONS { . qq{data-feedback-btn-add-class="ms-3">$radio[0]}; $radio[-1] .= ""; } - (wantarray) ? @radio : join(($main::displayMode eq 'PTX') ? '' : $self->{separator}, @radio); + wantarray + ? @radio + : join($main::displayMode eq 'PTX' || $main::displayMode eq 'TeX' ? '' : $self->{separator}, @radio); } sub protect { diff --git a/macros/parsers/parserRadioMultiAnswer.pl b/macros/parsers/parserRadioMultiAnswer.pl index 3cee48a56f..57163dc1bf 100644 --- a/macros/parsers/parserRadioMultiAnswer.pl +++ b/macros/parsers/parserRadioMultiAnswer.pl @@ -16,7 +16,7 @@ =head1 DESCRIPTION $rma = RadioMultiAnswer([ ['The unique solution is \(x=\) %s and \(y=\) %s.', 5, 6], ['There are an infinite number of solutions parameterized by ' - . '\(x=\) %s and \(y=\) %s.', '23-3t', 't'] + . '\(x=\) %s and \(y=\) %s.', '23-3x', 'x'], ['There are no solutions.'] ], 0); @@ -233,6 +233,14 @@ =head1 OPTIONS =back +=item showInStatic (Default: showInStatic => 1) + +In static output, such as PDF or PTX, this controls whether or not the list of answer options is +displayed. (The text preceding the list of answer options might make printing the answer option +list unnecessary in a static output format.) + +=back + =cut BEGIN { strict->import } @@ -288,6 +296,7 @@ sub new { size => undef, checked => undef, uncheckable => 0, + showInStatic => 1, @inputs ); @@ -593,6 +602,7 @@ sub ANS_NAME { # Produce the label for a part of the radio answer. sub label { my ($self, $i) = @_; + $self->{originalLabels} //= $self->{labels}; return $self->{labels}[$i] if ref($self->{labels}) eq 'ARRAY' && $#{ $self->{labels} } >= $i; $self->{labels} = [ @main::ALPHABET[ 0 .. $#{ $self->{data} } ] ] if uc($self->{labels}) eq 'ABC'; @@ -638,6 +648,7 @@ sub generate_aria_label { # Produce the answer rule. sub ans_rule { my ($self, $size, @options) = @_; + $size ||= 20; my @data = @{ $self->{data} }; my @rules; @@ -705,18 +716,44 @@ sub ans_rule { push(@rules, $rule); } + return '' if !$self->{showInStatic} && ($main::displayMode eq 'TeX' || $main::displayMode eq 'PTX'); + + my $ptx_list_type = 'ul'; + my $ptx_sub_type = ' form="buttons"'; + if ($main::displayMode eq 'PTX') { + # Do we want an ol, ul, or dl? + if ($self->{displayLabels}) { + my $originalLabels = $self->{originalLabels}; + if (ref $originalLabels eq 'ARRAY') { + $ptx_list_type = 'dl'; + $ptx_sub_type = ' width = "narrow"'; + } elsif ($originalLabels =~ m/^(123|abc)$/i) { + my $marker = ''; + $marker = '1' if $originalLabels eq '123'; + $marker = 'a' if $originalLabels eq 'abc'; + $marker = 'A' if uc($originalLabels) eq 'ABC' && $originalLabels ne 'abc'; + $ptx_list_type = 'ol'; + $ptx_sub_type = qq( marker="$marker"); + } + } + } + return main::MODES( TeX => '\\begin{itemize}', - HTML => '
    ' + HTML => '
    ', + PTX => qq(<${ptx_list_type}${ptx_sub_type} name="$radio_name">) ) - . join(main::MODES(TeX => '\vskip\baselineskip', HTML => main::tag('div', style => 'margin-top:1rem')), @rules) - . main::MODES(TeX => '\\end{itemize}', HTML => '
    '); + . join( + main::MODES(TeX => '\vskip\baselineskip', HTML => main::tag('div', style => 'margin-top:1rem'), PTX => ''), + @rules + ) . main::MODES(TeX => '\\end{itemize}', HTML => '
    ', PTX => ""); } # Format a label. sub label_format { my ($self, $label) = @_; return '' unless $self->{displayLabels} && defined $label && $label ne ''; + return '' . $self->quoteXML($label) . '' if $main::displayMode eq 'PTX'; return sprintf($self->{labelFormat}, main::MODES(TeX => $self->quoteTeX($label), HTML => $self->quoteHTML($label))); } @@ -734,6 +771,8 @@ sub begin_radio { if ($extend) { main::EXTEND_RESPONSE($name, $name, $value, $checked) } else { $name = main::RECORD_ANS_NAME($name, { $value => $checked }) } + return '' if !$self->{showInStatic} && ($main::displayMode eq 'TeX' || $main::displayMode eq 'PTX'); + my $idSuffix = $extend ? "_$value" : ''; return main::MODES( @@ -754,7 +793,7 @@ sub begin_radio { $checked ? (checked => undef) : () ) . main::tag('label', for => "$name$idSuffix", $tag), - PTX => "
  • $tag", + PTX => ref $self->{originalLabels} eq 'ARRAY' ? "
  • $tag" : '
  • ', ); } diff --git a/macros/ui/unionLists.pl b/macros/ui/unionLists.pl index 4ba214cd88..5189253dd4 100644 --- a/macros/ui/unionLists.pl +++ b/macros/ui/unionLists.pl @@ -54,7 +54,7 @@ =head1 unionLists.pl sub BeginList { my $LIST = 'OL'; - $LIST = shift if (uc($_[0]) eq "OL" or uc($_[0]) eq "UL"); + $LIST = uc(shift) if (defined($_[0]) && (uc($_[0]) eq "OL" or uc($_[0]) eq "UL")); my $enum = ($LIST eq 'OL' ? "enumerate" : "itemize"); my %options = @_; $LIST .= ' TYPE="' . $options{type} . '"' if defined($options{type}); diff --git a/t/README.md b/t/README.md index 2cf778d150..728135f5cb 100644 --- a/t/README.md +++ b/t/README.md @@ -16,7 +16,7 @@ and a working test can be better than documentation because it shows how the code currently works in practice. Old references can be found on the WebWork wiki page -[Unit Testing](https://webwork.maa.org/wiki/Unit_Testing) +[Unit Testing](https://wiki.openwebwork.org/wiki/Unit_Testing) ## Unit Tests diff --git a/t/macros/load_macros.t b/t/macros/load_macros.t index 59d37ed799..c8ab7e6575 100644 --- a/t/macros/load_macros.t +++ b/t/macros/load_macros.t @@ -33,8 +33,7 @@ my %baseMacros = ( 'PGcommonFunctions.pl' => 1 ); -# PG_CAPAmacros.pl is not really broken, but it depends on files that are in the OPL and not in the PG repository. -my %brokenMacros = ('answerDiscussion.pl' => 1, 'PG_CAPAmacros.pl' => 1); +my %brokenMacros = ('answerDiscussion.pl' => 1); # Find all macro files inside the $ENV{PG_ROOT}/macros directory. my @macro_files; diff --git a/t/macros/numerical_methods.t b/t/macros/numerical_methods.t index b5a00a2a31..bfcbd89635 100644 --- a/t/macros/numerical_methods.t +++ b/t/macros/numerical_methods.t @@ -101,8 +101,8 @@ subtest 'Quadrature' => sub { is trapezoid($f, 0, 2, steps => 4), 2.75, 'Trapezoid rule of x^2 on [0,2]'; - is romberg($f, 0, 2), 8 / 3, 'Romberg interation for x^2 on [0,2]'; - is romberg($g, 0, 1), exp(1) - 1, 'Romberg interation on e^x on [0,1]'; + is romberg($f, 0, 2), 8 / 3, 'Romberg integration for x^2 on [0,2]'; + is romberg($g, 0, 1), exp(1) - 1, 'Romberg integration on e^x on [0,1]'; is inv_romberg($g, 0, exp(1) - 1), 1.0, 'Inverse Romberg to find b with int of e^x on [0,b] returns 1'; }; diff --git a/t/math_objects/matrix.t b/t/math_objects/matrix.t index dfdc890c35..dbf03b02e1 100644 --- a/t/math_objects/matrix.t +++ b/t/math_objects/matrix.t @@ -183,7 +183,7 @@ subtest 'Test if Matrix is in (R)REF' => sub { subtest 'Transpose a Matrix' => sub { my $A = Matrix([ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]); my $B = Matrix([ [ 1, 5, 9 ], [ 2, 6, 10 ], [ 3, 7, 11 ], [ 4, 8, 12 ] ]); - is $A->transpose->TeX, $B->TeX, 'Test the tranpose of a matrix'; + is $A->transpose->TeX, $B->TeX, 'Test the transpose of a matrix'; my $row = Matrix([ 1, 2, 3, 4 ]); my $row_trans = Matrix([ [1], [2], [3], [4] ]); @@ -191,7 +191,7 @@ subtest 'Transpose a Matrix' => sub { my $C = Matrix([ [ [ 1, 2 ], [ 3, 4 ] ], [ [ 5, 6 ], [ 7, 8 ] ] ]); my $D = Matrix([ [ [ 1, 3 ], [ 2, 4 ] ], [ [ 5, 7 ], [ 6, 8 ] ] ]); - is $C->transpose->TeX, $D->TeX, 'Test the tranpose of a degree 3 tensor'; + is $C->transpose->TeX, $D->TeX, 'Test the transpose of a degree 3 tensor'; }; subtest 'Extract an element' => sub { diff --git a/t/pg_problems/problem_file.t b/t/pg_problems/problem_file.t index a9c6b4605d..47a70e34ff 100644 --- a/t/pg_problems/problem_file.t +++ b/t/pg_problems/problem_file.t @@ -21,8 +21,8 @@ is($pg->{post_header_text}, '', 'post_header_text is empty'); is( $pg->{body_text}, qq{
    \n} - . qq{Enter a value for .\n} - . qq{
    \n} + . qq{Enter a value for .} + . qq{
    } . qq{
    } . qq{} diff --git a/t/units/electron_volts.t b/t/units/electron_volts.t index 6b5b7c4c3a..36ae87c8b1 100644 --- a/t/units/electron_volts.t +++ b/t/units/electron_volts.t @@ -26,7 +26,7 @@ is \%tev, by_factor(10**12, \%electron_volt), 'tera is factor 10^1 done_testing(); # this sub is useful when reusing units for testing -# NumberWithUnits is mutable and test order dependant +# NumberWithUnits is mutable and test order dependent sub by_factor { my ($value, $unit) = @_; my $new_unit = {%$unit}; # shallow copy hash values diff --git a/tutorial/sample-problems/Algebra/GraphToolNumberLine.pg b/tutorial/sample-problems/Algebra/GraphToolNumberLine.pg index 7b4d5fac23..d9b3d2177e 100644 --- a/tutorial/sample-problems/Algebra/GraphToolNumberLine.pg +++ b/tutorial/sample-problems/Algebra/GraphToolNumberLine.pg @@ -24,7 +24,7 @@ DOCUMENT(); loadMacros('PGstandard.pl', 'PGML.pl', 'parserGraphTool.pl', 'PGcourse.pl'); #:% section = setup -#: Two intervals are created with random enpoints. The first +#: Two intervals are created with random endpoints. The first #: one is a bounded interval, and the second an unbounded interval. #: #: The `GraphTool` method creates the graph tool object. The only argument is diff --git a/tutorial/sample-problems/DiffEq/HeavisideStep.pg b/tutorial/sample-problems/DiffEq/HeavisideStep.pg index baa13bf846..fe69f522ea 100644 --- a/tutorial/sample-problems/DiffEq/HeavisideStep.pg +++ b/tutorial/sample-problems/DiffEq/HeavisideStep.pg @@ -30,7 +30,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'parserFunction.pl', 'PGcourse.pl'); #: #: For more details on adding the Heaviside function to the context, see the #: forum discussion on the -#: [Heaviside step function](https://webwork.maa.org/moodle/mod/forum/discuss.php?d=458). +#: [Heaviside step function](https://wiki.openwebwork.org/moodle/mod/forum/discuss.php?d=458). #: #: For the second question, since answers are checked numerically by comparing #: the student answer to the correct answer at several random points in the diff --git a/tutorial/sample-problems/Geometry/LineSegmentGraphTool.pg b/tutorial/sample-problems/Geometry/LineSegmentGraphTool.pg index d99cd17659..97670090ed 100644 --- a/tutorial/sample-problems/Geometry/LineSegmentGraphTool.pg +++ b/tutorial/sample-problems/Geometry/LineSegmentGraphTool.pg @@ -29,7 +29,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'parserGraphTool.pl', 'PGcourse.pl'); #: `($sx1, $sy1)` and `($sx2, $sy2)`. For a correct answer, the line segment #: must have the given the endpoints and be solid. #: -#: Mulitple line segments can be added to the graph tool, however, if a +#: Multiple line segments can be added to the graph tool, however, if a #: triangle or quadrilateral is desired, see PROBLINK('TriangleGraphTool.pg') #: and PROBLINK('QuadrilateralGraphTool.pg') for more appropriate tools. $sx1 = random(-8, 8); diff --git a/tutorial/sample-problems/IntegralCalc/GraphShadingPlot.pg b/tutorial/sample-problems/IntegralCalc/GraphShadingPlot.pg index 84a691e5f6..b0d42277c6 100644 --- a/tutorial/sample-problems/IntegralCalc/GraphShadingPlot.pg +++ b/tutorial/sample-problems/IntegralCalc/GraphShadingPlot.pg @@ -28,7 +28,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'plots.pl', 'PGcourse.pl'); #: #: First create a `Plot` object with a plotting window that will capture the #: transformed function. The function is then added to the plot with fill -#: characteristics. Note: it is important that the `name` atrribute is defined. +#: characteristics. Note: it is important that the `name` attribute is defined. $a = random(-4, 2); $b = random( 1, 4); diff --git a/tutorial/sample-problems/Misc/RandomPerson.pg b/tutorial/sample-problems/Misc/RandomPerson.pg index 4b5d18fff5..f20e04da63 100644 --- a/tutorial/sample-problems/Misc/RandomPerson.pg +++ b/tutorial/sample-problems/Misc/RandomPerson.pg @@ -48,7 +48,7 @@ $c = random(4, 8, 2); #: capitalized pronouns. #: #: Alternatively, a more-natural set of methods, `they, them, their` and `theirs` -#: will generate the correct subject, object, possession, possesive forms of the +#: will generate the correct subject, object, possession, possessive forms of the #: pronoun. #: #: In addition, there is a `verb` method to conjugate most diff --git a/tutorial/sample-problems/Parametric/SpaceCurveGraph.pg b/tutorial/sample-problems/Parametric/SpaceCurveGraph.pg index c302547295..2b669f9d0f 100644 --- a/tutorial/sample-problems/Parametric/SpaceCurveGraph.pg +++ b/tutorial/sample-problems/Parametric/SpaceCurveGraph.pg @@ -24,7 +24,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'plotly3D.pl', 'PGcourse.pl'); #:% section = setup #: A `plotly3D` graph is created with the `Graph3D` function. There are many -#: options as decribed in PODLINK('plotly3D macro documentation','plotly3D.pl'). +#: options as described in PODLINK('plotly3D macro documentation','plotly3D.pl'). #: In this example, only the `height`, `width`, and `title` are provided. #: #: A parametric curve is added to the graph with the `addCurve` method, which diff --git a/tutorial/sample-problems/Parametric/SurfaceGraph.pg b/tutorial/sample-problems/Parametric/SurfaceGraph.pg index ee29e38b38..6da4bec911 100644 --- a/tutorial/sample-problems/Parametric/SurfaceGraph.pg +++ b/tutorial/sample-problems/Parametric/SurfaceGraph.pg @@ -24,7 +24,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'plotly3D.pl', 'PGcourse.pl'); #:% section = setup #: A `plotly3D` graph is created with the `Graph3D` function. There are many -#: options as decribed in PODLINK('plotly3D macro documentation','plotly3D.pl'). +#: options as described in PODLINK('plotly3D macro documentation','plotly3D.pl'). #: In this example, only the `height`, `width`, and `title` are provided. #: #: A parametric surface is added to the graph with the `addSurface` method, diff --git a/tutorial/sample-problems/ProblemTechniques/DigitsTolType.pg b/tutorial/sample-problems/ProblemTechniques/DigitsTolType.pg index 4ee1bf2542..343d5c7756 100644 --- a/tutorial/sample-problems/ProblemTechniques/DigitsTolType.pg +++ b/tutorial/sample-problems/ProblemTechniques/DigitsTolType.pg @@ -23,7 +23,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); #:% section = setup #: Several context flags are set for this problem. The meaning of these flags -#: are decribed in the following list. +#: are described in the following list. #: #: * The flag `tolType => 'digits'` switches from the default `'relative'` #: tolerance type to the `'digits'` tolerance type. diff --git a/tutorial/sample-problems/ProblemTechniques/DisableFunctions.pg b/tutorial/sample-problems/ProblemTechniques/DisableFunctions.pg index 483d402212..54ef7f4be0 100644 --- a/tutorial/sample-problems/ProblemTechniques/DisableFunctions.pg +++ b/tutorial/sample-problems/ProblemTechniques/DisableFunctions.pg @@ -59,7 +59,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); #: `sgn` #: * `Vector`: disables `norm`, `unit` #: * `Complex`: disables `arg`, `mod`, `Re`, `Im`, `conj` -#: * `All`: diables all predefined functions +#: * `All`: disables all predefined functions #: #: Alternatively, the following syntax can be used. #: diff --git a/tutorial/sample-problems/ProblemTechniques/Images.pg b/tutorial/sample-problems/ProblemTechniques/Images.pg index 7f2ae9344f..b8675fad1a 100644 --- a/tutorial/sample-problems/ProblemTechniques/Images.pg +++ b/tutorial/sample-problems/ProblemTechniques/Images.pg @@ -78,7 +78,7 @@ $gt = GraphTool('{circle, solid, (1, 1), (2, 2)}'); #: graphs. It is intended to be used to show an interactive graphing tool in #: which student's are expected to graph requested objects, in this case the #: circle that has center `(1, 1)` and passes through the point `(2, 2)`. -#: However, static images can be diplayed as is shown in this example. The +#: However, static images can be displayed as is shown in this example. The #: intent for this usage is to show the correct graph in a solution. BEGIN_PGML * A static image: [!Graph of an exponential!]{'image.png'}{120} diff --git a/tutorial/sample-problems/README.md b/tutorial/sample-problems/README.md index 64deacc459..3d2cbd2ed7 100644 --- a/tutorial/sample-problems/README.md +++ b/tutorial/sample-problems/README.md @@ -40,7 +40,7 @@ Here's an example: #:% categories = [graph] ``` -## Structure of the code documenation +## Structure of the code documentation The comments that are associated with each file in on the WeBWorK wiki have been embedded as formatted @@ -73,16 +73,16 @@ All lines following the documentation lines are considered code until the next ` ## Generate the documentation -The documentation is generated with the `parse-prob-doc.pl` script in the `bin` +The documentation is generated with the `parse-problem-doc.pl` script in the `bin` directory of pg. There are the following options (and many are required): -- `problem_dir` or `d`: The directory where the sample problems are. This defaults to +- `problem-dir` or `d`: The directory where the sample problems are. This defaults to `PG_ROOT/tutorial/sample-problems` if not passed in. -- `out_dir` or `o`: The directory where the resulting documentation files (HTML) +- `out-dir` or `o`: The directory where the resulting documentation files (HTML) will be located. -- `pod_root` or `p`: The URL where the POD is located. This is needed to +- `pod-base-url` or `p`: The URL where the POD is located. This is needed to correctly link POD from the sample problems. -- `pg_doc_home` or `h`: The URL of the directory for `out_dir`. This is needed +- `sample-problem-base-url` or `s`: The URL of the directory for `out-dir`. This is needed for correct linking. - `verbose` or `v`: verbose mode. @@ -94,7 +94,7 @@ produce four different ways of categorizing the problems. - an html file with the documented PG file - a pg file with the documentation removed. There is a link to this in the html file. -The script `parse-prob-doc.pl` parses each pg file and uses the `problem-template.mt` +The script `parse-problem-doc.pl` parses each pg file and uses the `problem-template.mt` template file to generate the html. This template is processed using the `Mojo::Template` Perl module. See the [Mojo::Template documentation](https://docs.mojolicious.org/Mojo/Template) for more information. diff --git a/tutorial/sample-problems/Statistics/ScatterPlot.pg b/tutorial/sample-problems/Statistics/ScatterPlot.pg index 9885ed7ffb..737a9b2ae0 100644 --- a/tutorial/sample-problems/Statistics/ScatterPlot.pg +++ b/tutorial/sample-problems/Statistics/ScatterPlot.pg @@ -34,7 +34,7 @@ loadMacros( #: components. #: #: For the plot, first set up the `Plot` with a plotting window that will -#: caputure the data. The `add_dataset` method adds the data to the plot +#: capture the data. The `add_dataset` method adds the data to the plot #: with circles and color blue. #: #: The line is added to the dataset with the `add_function` method. The 3rd diff --git a/tutorial/sample-problems/VectorCalc/VectorLineSegment1.pg b/tutorial/sample-problems/VectorCalc/VectorLineSegment1.pg index 0210fc1ff3..06478277b7 100644 --- a/tutorial/sample-problems/VectorCalc/VectorLineSegment1.pg +++ b/tutorial/sample-problems/VectorCalc/VectorLineSegment1.pg @@ -1,5 +1,5 @@ ## DESCRIPTION -## A Vector-value parametric line segment with a general paramterization +## A Vector-value parametric line segment with a general parameterization ## ENDDESCRIPTION ## DBsubject(WeBWorK) diff --git a/tutorial/sample-problems/VectorCalc/Vectors.pg b/tutorial/sample-problems/VectorCalc/Vectors.pg index b1f25d9d4f..498022c208 100644 --- a/tutorial/sample-problems/VectorCalc/Vectors.pg +++ b/tutorial/sample-problems/VectorCalc/Vectors.pg @@ -54,7 +54,7 @@ loadMacros('PGstandard.pl', 'PGML.pl', 'PGcourse.pl'); #: example, to obtain the first component of `$v3` use `$v3->extract(1)`. Note #: that the index of `extract` starts at 1, not 0. #: -#: See the [vector](https://webwork.maa.org/wiki/Vector_(MathObject_Class)) +#: See the [vector](https://wiki.openwebwork.org/wiki/Vector_(MathObject_Class)) #: wiki page for more information about MathObject vectors. Context('Vector'); # Uncomment to display vectors in ijk format. diff --git a/tutorial/templates/general-layout.mt b/tutorial/templates/general-layout.mt index 3f50146abd..b7d9fac40d 100644 --- a/tutorial/templates/general-layout.mt +++ b/tutorial/templates/general-layout.mt @@ -5,7 +5,7 @@ PG Sample Problems - + - + diff --git a/tutorial/templates/index.html b/tutorial/templates/index.html new file mode 100644 index 0000000000..aa271047f4 --- /dev/null +++ b/tutorial/templates/index.html @@ -0,0 +1,139 @@ + + + + PG Documentation + + + + + + + + +
    +
    +

    PG Documentation

    +
    +
    +
    +
    + +
    + + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +

    + This is the documentation for PG, the problem authoring language for WeBWorK. The links below + include sample problems demonstrating problem authoring techniques and POD (Plain Old + Documentation), explaining how to use PG macros and related modules. +

    +
    +
    + +
    + + diff --git a/tutorial/templates/problem-template.mt b/tutorial/templates/problem-template.mt index dfd9d3b798..f31700139c 100644 --- a/tutorial/templates/problem-template.mt +++ b/tutorial/templates/problem-template.mt @@ -5,12 +5,12 @@ <%= $filename %> - - + + + - % # Default explanations @@ -30,7 +30,7 @@

    <%= $description %>

    @@ -44,7 +44,9 @@