commit 54fdc17bba5f9b0dba1ea0ca96377a36589260c7 Author: TriMill Date: Thu Jun 23 01:58:54 2022 -0400 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c92b71a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +venv/ +*/__pycache__/ \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. 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 +them 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 prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. 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. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey 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; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + 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. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +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. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + 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 +state 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 3 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, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program 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, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU 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 Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..071ccf0 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# trimill.xyz + +Source code for the Flask server running [trimill.xyz](https://trimill.xyz). Licensed under [GNU GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html). \ No newline at end of file diff --git a/src/blog/2022-02-25-0.md b/src/blog/2022-02-25-0.md new file mode 100644 index 0000000..7801f24 --- /dev/null +++ b/src/blog/2022-02-25-0.md @@ -0,0 +1,13 @@ +--- +title: Test post? +desc: first!!!1! +timestamp: 2022-02-25T00:00:00-05:00 +--- + +Oh dear, what is this? The first post in a "blog", perhaps? + +Blog is a rather interesting word. It comes from a shortening of "weblog", web + log, where "web" refers to the interconnected structure of the Internet and "log" is itself a shortening of "logbook", a book used to record readings from a "chip log", a piece of wood used to measure the speed of a ship. In this way, "blog" is really a shortening of "weblogbook". + +I am currently writing this post in Markdown, which will be converted into HTML so it can be viewed in browsers. I wonder how formatting looks? **bold**. *italic*. ~~strikethrough~~. `code`. Interesting. + +This concludes the test post. diff --git a/src/blog/2022-06-01-0.md b/src/blog/2022-06-01-0.md new file mode 100644 index 0000000..46e796c --- /dev/null +++ b/src/blog/2022-06-01-0.md @@ -0,0 +1,19 @@ +--- +title: 28 Days of Tau - Day 1 +desc: The first of 28 posts about the beautiful constant tau. +timestamp: 2022-06-01T00:00:00-04:00 +--- + +Much like any ordinary child, I spent much of my childhood watching math videos on YouTube. During this time I discovered many channels that you might already be familiary with: [3Blue1Brown](https://www.youtube.com/c/3blue1brown), [Numberphile](https://www.youtube.com/user/numberphile), [Stand-up Maths](https://www.youtube.com/user/standupmaths), and many, many others. I credit these videos with inspiring me to continue my fascination with mathematics. However, one of these channels rises above all others in terms of how it shaped me: [Vihart](https://www.youtube.com/user/Vihart). + +Vi's videos, although lacking in pixels, more than make up for it with their humor, art, music, and beautiful introductions into various mathematics concepts that one would be lucky to even mention in a school curriculum. These videos introduced me to [$$ \tau $$](https://www.youtube.com/watch?v=FtxmFlMLYRI) (tau) - the superior circle constant, equal to the ratio between a circle's circumference and its radius (two times $$ \pi $$). Each day this June, leading up to June 28 ([tau day](https://tauday.org)), I will be posting a creative demonstration of the beauty of this constant, circles, and mathematics as a whole. + +All code used to generate these visualizations will be available [on GitHub](https://github.com/TriMill/28-days-of-tau/) the same day the blog post is released. + +## Labyrinth + +![Day 1 - Labyrinth](/static/i/blog/tau-day1.png) + +This image shows the digits of tau (as represented in a base-10 positional system) by assigning each one an angle, where 0 is directly right and each subsequent digit is $$ \frac{\tau}{10} $$ radians counterclockwise. The first digit is right next to the center, and following digits are further out and connected by arcs. + +[GitHub](https://github.com/TriMill/28-days-of-tau/tree/main/day1) \ No newline at end of file diff --git a/src/blog/2022-06-02-0.md b/src/blog/2022-06-02-0.md new file mode 100644 index 0000000..d355f68 --- /dev/null +++ b/src/blog/2022-06-02-0.md @@ -0,0 +1,13 @@ +--- +title: 28 Days of Tau - Day 2 +desc: The second day of Tau +timestamp: 2022-06-02T00:00:00-04:00 +--- + +## Skyline + +![Day 2 - Skyline](/static/i/blog/tau-day2.png) + +This image illustrates the digits of the fractional part of tau (0.283184...) in binary. Each rectangle's height represents an approximation of the fractional part of tau, with each having half the width of the previous. Circles are added on top of the rectangles for decoration. + +[GitHub](https://github.com/TriMill/28-days-of-tau/tree/main/day2) \ No newline at end of file diff --git a/src/blog/2022-06-03-0.md b/src/blog/2022-06-03-0.md new file mode 100644 index 0000000..c527671 --- /dev/null +++ b/src/blog/2022-06-03-0.md @@ -0,0 +1,13 @@ +--- +title: 28 Days of Tau - Day 3 +desc: The third day - polygon inscription +timestamp: 2022-06-03T00:00:00-04:00 +--- + +## Polygons + +![Day 3 - Polygons](/static/i/blog/tau-day3.png) + +One of the oldest methods known to calculate circle constants is inscribing polygons of increasing side counts within circles. It is easy to find the area of these polygons, and as the number of sides increases the area approaches $$ \frac{\tau}{2} $$ (assuming a circle with radius 1). + +[GitHub](https://github.com/TriMill/28-days-of-tau/tree/main/day3) \ No newline at end of file diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..292bc8d --- /dev/null +++ b/src/main.py @@ -0,0 +1,150 @@ +from flask import Flask, render_template, send_from_directory, abort, request, make_response +import markdown2 +import datetime +import os +import ast + +data = { + "feet": [ + "šŸ", + "Best viewed using Internet Explorer 6 or earlier", + "The HORSE is a noble animal", + "šŸ¦€", + "segmentation fault (core dumped)", + "Bees land on thyme", + "ā˜ƒ", + "# cat /dev/urandom > /dev/sda", + ":(){ :|: & };:", + "Formal complaints will recieve responses within 5-7 business days", + "++++[->++++<]>+[->++++++>+++++++>++<<<]>.>--------..+++++.<-.>--.>--.<++.<.>++++.----.", + "Copywrong Ā© 3034. All rights unreserved.", + "[citation needed]", + "Best viewed with eyes", + "Your browser does not support 7D graphics. Please update for the best user experience.", + "Press SPACE to jump", + "šŸ€", + "If problems persist, please return to the nearest Blockbuster VideoĀ® establishment", + "Oversalt to taste", + "curl -s -L http://bit.ly/10hA8iC | bash", + "Submit footer text via carrier pigeon to [REDACTED]", + "GEORGE is inevitable." + ], + "next_theme": { + "system": "dark", + "dark": "light", + "light": "contrast", + "contrast": "system" + } +} + +markdown_extras = ["fenced-code-blocks", "footnotes", "strike", "tables", "metadata"] + +app = Flask(__name__) + + +projects = [] +proj_dir = os.path.join(app.root_path, app.template_folder, "projects") +for root, dirs, files in os.walk(proj_dir): + for file in files: + try: + with open(os.path.join(root, file)) as f: + metaline = f.read().splitlines()[0] + if metaline.startswith("{% set meta="): + meta = metaline[12:-2].strip() + meta = ast.literal_eval(meta) + meta["path"] = os.path.relpath(root, start=proj_dir) + meta["file"] = file + projects.append(meta) + except Exception as e: + print(e) +data["projects"] = sorted(projects, key=lambda p: [p.get("star") != True, p.get("title")]) + +blogposts = [] +blog_dir = os.path.join(app.root_path, "blog") +for file in os.listdir(blog_dir): + with open(os.path.join(blog_dir, file)) as f: + contents = f.read() + html = markdown2.markdown(contents, extras=markdown_extras) + meta = html.metadata + meta["file"] = file + date_parts = file.removesuffix(".md").split("-") + date = datetime.date(int(date_parts[0]), int(date_parts[1]), int(date_parts[2])) + meta["timestamp"] = datetime.datetime.fromisoformat(meta["timestamp"]) + meta["date"] = date + meta["n"] = int(date_parts[3]) + meta["url"] = f"/blog/{date.isoformat().replace('-','/')}/{meta['n']}" + blogposts.append(meta) +data["blogposts"] = sorted(blogposts, key=lambda post:[post["date"], post["n"]], reverse=True) + + +@app.route("/favicon.ico") +def favicon(): + path = os.path.join(app.root_path, "static") + return send_from_directory(path, "favicon.ico", mimetype="image/vnd.microsoft.icon") + +@app.errorhandler(404) +def four_oh_four(e): + theme = request.cookies.get("theme") or "dark" + return render_template("404.html", data=data, theme=theme) + +def load_page(url): + if url.endswith(".html"): + path = os.path.join(app.root_path, app.template_folder, url) + if os.path.exists(path): + theme = request.cookies.get("theme") or "dark" + return render_template(url, data=data, theme=theme) + else: + return abort(404) + else: + return send_from_directory("templates", url) + +@app.route("/") +@app.route("/index.html") +def home(): + return load_page("index.html") + +@app.route("/projects/") +@app.route("/projects/index.html") +def projects_index(): + return load_page("projects.html") + +@app.route("/projects//") +@app.route("/projects//") +def project(page, file="index.html"): + return load_page(f"projects/{page}/{file}") + +@app.route("/blog/") +def blog_list(): + theme = request.cookies.get("theme") or "dark" + return render_template("blog.html", data=data, theme=theme) + + +@app.route("/blog////") +@app.route("/blog////") +def blog_page(y, m, d, n=0): + date = datetime.date(y, m, d) + print(date.isoformat()) + path = os.path.join(app.root_path, "blog", f"{date.isoformat()}-{n}.md") + if os.path.exists(path): + with open(path) as f: + contents = f.read() + content = markdown2.markdown(contents, extras=markdown_extras) + meta = content.metadata + theme = request.cookies.get("theme") or "dark" + return render_template("_blog.html", data=data, theme=theme, content=content, date=date, meta=meta) + else: + return abort(404) + +@app.route("/blog/rss.xml") +def blog_rss(): + xml = render_template("rss.xml", data=data) + response = make_response(xml) + response.headers["Content-Type"] = "application/rss+xml; charset=utf-8" + return response + +@app.route("/blog/atom.xml") +def blog_atom(): + xml = render_template("atom.xml", data=data) + response = make_response(xml) + response.headers["Content-Type"] = "application/atom+xml; charset=utf-8" + return response \ No newline at end of file diff --git a/src/static/favicon.ico b/src/static/favicon.ico new file mode 100644 index 0000000..82c6439 Binary files /dev/null and b/src/static/favicon.ico differ diff --git a/src/static/i/apioform1.png b/src/static/i/apioform1.png new file mode 100644 index 0000000..a657d2a Binary files /dev/null and b/src/static/i/apioform1.png differ diff --git a/src/static/i/blog/tau-day1.png b/src/static/i/blog/tau-day1.png new file mode 100644 index 0000000..070b2ad Binary files /dev/null and b/src/static/i/blog/tau-day1.png differ diff --git a/src/static/i/blog/tau-day2.png b/src/static/i/blog/tau-day2.png new file mode 100644 index 0000000..292ac72 Binary files /dev/null and b/src/static/i/blog/tau-day2.png differ diff --git a/src/static/i/blog/tau-day3.png b/src/static/i/blog/tau-day3.png new file mode 100644 index 0000000..36dc9a0 Binary files /dev/null and b/src/static/i/blog/tau-day3.png differ diff --git a/src/static/i/flappy/background1.png b/src/static/i/flappy/background1.png new file mode 100644 index 0000000..4b4b946 Binary files /dev/null and b/src/static/i/flappy/background1.png differ diff --git a/src/static/i/flappy/background2.png b/src/static/i/flappy/background2.png new file mode 100644 index 0000000..d2d9228 Binary files /dev/null and b/src/static/i/flappy/background2.png differ diff --git a/src/static/i/flappy/bird.png b/src/static/i/flappy/bird.png new file mode 100644 index 0000000..b5d88f1 Binary files /dev/null and b/src/static/i/flappy/bird.png differ diff --git a/src/static/logo16.png b/src/static/logo16.png new file mode 100644 index 0000000..c1753c7 Binary files /dev/null and b/src/static/logo16.png differ diff --git a/src/static/logo32.png b/src/static/logo32.png new file mode 100644 index 0000000..ec39d18 Binary files /dev/null and b/src/static/logo32.png differ diff --git a/src/static/logo320.png b/src/static/logo320.png new file mode 100644 index 0000000..c59d560 Binary files /dev/null and b/src/static/logo320.png differ diff --git a/src/static/logo64.png b/src/static/logo64.png new file mode 100644 index 0000000..748f3e7 Binary files /dev/null and b/src/static/logo64.png differ diff --git a/src/static/logo640.png b/src/static/logo640.png new file mode 100644 index 0000000..4982cbe Binary files /dev/null and b/src/static/logo640.png differ diff --git a/src/static/style.css b/src/static/style.css new file mode 100644 index 0000000..a7635c5 --- /dev/null +++ b/src/static/style.css @@ -0,0 +1,232 @@ +@import url('https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@500&family=Source+Sans+Pro:wght@400&family=Fira+Mono&display=swap'); + +@media (prefers-color-scheme: dark) { + :root[theme="system"] { + --bg: #242328; + --bg-intense: #141316; + --bg-faded: #373338; + --fg: #e0dedc; + --fg-faded: #9b9497; + --accent-1: #96bf59; + --accent-1-dark: #759438; + --accent-2: #789ebf; + --accent-2-dark: #52729d; + --error: #ee6d7d; + } + .only-theme-light { + display: none; + } +} + +@media (prefers-color-scheme: light) { + :root[theme="system"] { + --bg: #f6f5f2; + --bg-intense: #ffffff; + --bg-faded: #cbc4c7; + --fg: #28262b; + --fg-faded: #989097; + --accent-1: #0642c3; + --accent-1-dark: #07136d; + --accent-2: #0f7904; + --accent-2-dark: #094a05; + --error: #a50518; + } + .only-theme-dark { + display: none; + } +} + +:root[theme="dark"] { + --bg: #242328; + --bg-intense: #141316; + --bg-faded: #373338; + --fg: #e0dedc; + --fg-faded: #9b9497; + --accent-1: #96bf59; + --accent-1-dark: #759438; + --accent-2: #789ebf; + --accent-2-dark: #52729d; + --error: #ee6d7d; +} + +:root[theme="light"] { + --bg: #f6f5f2; + --bg-intense: #ffffff; + --bg-faded: #cbc4c7; + --fg: #28262b; + --fg-faded: #989097; + --accent-1: #0642c3; + --accent-1-dark: #07136d; + --accent-2: #0d8101; + --accent-2-dark: #045901; + --error: #a50518; +} + +:root[theme="contrast"] { + --bg: white; + --bg-intense: white; + --bg-faded: #c0c0c0; + --fg: black; + --fg-faded: #444444; + --accent-1: blue; + --accent-1-dark: darkblue; + --accent-2: #0088ff; + --accent-2-dark: #004488; + --error: #880000; +} + +:root[theme="special"] { + --bg: #ffff00; + --bg-intense: #ffffff; + --bg-faded: #00ff00; + --fg: #ff00ff; + --fg-faded: #ff0000; + --accent-1: #00ffff; + --accent-1-dark: #0000ff; + --accent-2: #000000; + --accent-2-dark: #00ffff; + --error: #00ff88; + font-family: "Comic Sans MS", cursive; +} + +:root { + color: var(--fg); + font-family: "Source Sans Pro", sans-serif; + font-size: 20px; + font-weight: 400; + line-height: 1.5; + --link: var(--accent-1); + --link-hover: var(--accent-1-dark); + --link-active: var(--accent-1-dark); + --button: var(--bg-intense); + --button-hover: var(--bg-faded); + --button-active: var(--accent-2-dark); + overflow-wrap: break-word; +} + +h1, h2, h3, nav { + font-family: "Roboto Slab", serif; + font-weight: 500; + margin-left: 20px; + margin-top: 16px; + margin-bottom: 16px; +} + +h1 { + width: fit-content; + margin-left: auto; + margin-right: auto; +} + +body { + background: var(--bg); + width: min(750px, 90vw); + margin: auto; + min-height: 100vh; + display: flex; + flex-direction: column; +} + +main { + flex-grow: 1; +} + +footer { + text-align: center; + color: var(--fg-faded); + margin-top: auto; + flex-shrink: 0; + min-height: 50px; + font-size: 12px; +} + +footer code { + font-size: 12px; +} + +nav { + padding-top: 10px; + width: fit-content; + margin: auto; + font-size: 20px; +} + +nav > svg { + position: absolute; + top: 16px; + height: 20px; + width: auto; + padding-left: 15px; + padding-right: 10px; + fill: var(--fg); +} + +nav > a { + color: var(--fg); + text-decoration: none; + padding-left: 10px; + padding-right: 10px; +} + +nav > a:hover { color: var(--fg-faded); } + +a { + color: var(--link); + text-decoration: none; +} + +:root[theme="contrast"] a { + text-decoration: underline; +} + +a:hover { color: var(--link-hover); } + +a:active { color: var(--link-active); } + +.faded { + color: var(--fg-faded); +} + +p { + margin-top: 8px; + margin-bottom: 8px; +} + +code, pre { + background-color: var(--bg-intense); + padding-left: 3px; + padding-right: 3px; + padding-top: 2px; + padding-bottom: 2px; + border-radius: 3px; + font-family: "Fira Mono", monospace; + font-size: 18px; +} + +pre { + padding-top: 3px; + padding-bottom: 3px; + padding-left: 12px; + border-left: 5px solid var(--accent-2-dark); +} + +button, input, select { + color: var(--fg); + background-color: var(--bg-faded); + border: 2px solid var(--fg-faded); + border-radius: 3px; + font-family: "Source Sans Pro", sans-serif; + font-size: 16px; +} + +button:active, input[type=button]:active { + background-color: var(--bg); +} + +button:active, input[type=button]:active { + background-color: var(--bg); +} + +.error { + color: var(--error); +} \ No newline at end of file diff --git a/src/templates/404.html b/src/templates/404.html new file mode 100644 index 0000000..aeaf127 --- /dev/null +++ b/src/templates/404.html @@ -0,0 +1,17 @@ +{% set meta={"title": "404", "desc": "Page not found"} %} +{% extends "/_base.html" %} +{% block content %} +

The page you have tried to access does not exist. How did you get here?

+{% if request.args.get("source") == "here" %} +

> From another page on this site

+

Oops! Please create an issue so I can fix this.

+

Return home

+{% elif request.args.get("source") == "other"%} +

> From somewhere else

+

Unfortunately I can only fix my own mistakes. Please inform that website's owner that their link no longer works.

+

Return home

+{% else %} +

> From another page on this site

+

> From somewhere else

+{% endif %} +{% endblock %} \ No newline at end of file diff --git a/src/templates/_base.html b/src/templates/_base.html new file mode 100644 index 0000000..20e442e --- /dev/null +++ b/src/templates/_base.html @@ -0,0 +1,92 @@ + + + + + + {{ meta.title }} - TriMill + + + + + + + + + + + + + + + + + + + + + {% block head %} {% endblock %} + + + {% if meta.fullscreen != True %} + +
+ {% block title %} +

{{ meta.title }}

+ {% endblock %} + {% endif %} +
+ {% block content %}{% endblock %} +
+ {% if meta.fullscreen != True %} +
+ {{ data.feet | random | safe }} +
+ {% endif %} + + \ No newline at end of file diff --git a/src/templates/_blog.html b/src/templates/_blog.html new file mode 100644 index 0000000..9da7586 --- /dev/null +++ b/src/templates/_blog.html @@ -0,0 +1,35 @@ +{% extends "/_base.html" %} +{% block head %} + + + + + + + +{% endblock %} + +{% block content %} +

{{ title }}

+

{{ date.isoformat() }}

+
+{{ content | safe }} +
+{% endblock %} \ No newline at end of file diff --git a/src/templates/atom.xml b/src/templates/atom.xml new file mode 100644 index 0000000..f9b9f11 --- /dev/null +++ b/src/templates/atom.xml @@ -0,0 +1,21 @@ + + + Blogā„¢ + + + {{ data.blogposts[0].timestamp.isoformat() }} + + TriMill + + https://trimill.xyz/blog/ + + {% for post in data.blogposts %} + + {{ post.title }} + + {{ post.desc }} + {{ post.timestamp.isoformat() }} + https://trimill.xyz/{{ post.url }} + + {% endfor %} + \ No newline at end of file diff --git a/src/templates/blog.html b/src/templates/blog.html new file mode 100644 index 0000000..b608ad3 --- /dev/null +++ b/src/templates/blog.html @@ -0,0 +1,14 @@ +{% set meta={"title": "Blogā„¢", "desc": "Blogā„¢"} %} +{% extends "/_base.html" %} +{% block content %} +

Welcome to Blogā„¢. I may on occasion add pages to here, in which case the list below will update. You can also +get updates delivered directly to you using the RSS or Atom feeds.

+{% for post in data.blogposts %} +
+

+ {{ post.title }} +

+

{{ post.date.isoformat() }}

+

{{ post.desc }}

+{% endfor %} +{% endblock %} \ No newline at end of file diff --git a/src/templates/index.html b/src/templates/index.html new file mode 100644 index 0000000..5e0599e --- /dev/null +++ b/src/templates/index.html @@ -0,0 +1,51 @@ +{% set meta = {"title": "Home", "desc": "Home page"} %} +{% extends "/_base.html" %} +{% block content %} + +

+Welcome! On this website you may view a multitude of content, including numerous +projects, a Blogā„¢, and various other information contained on +this page. Enjoy. +

+ +

About

+

+I am a person (proof of this fact is left to the reader) who has been granted an internet connection and uses +it extensively for assorted purposes. Much of my time not spent sleeping is spent doing mathematics, +programming (preferably in Rust), Minecraft, conlangs, twisty puzzles, and chess (although I am quite bad at it). +

+

GEORGE

+

+GEORGE is a webring of which I am a member. Use the standard-issue GEORGEbox +below to view the GEORGE homepage or access the previous and next members in the ring. GEORGE is inevitable. +

+{% if theme == "light" %} + +{% elif theme == "contrast" %} + +{% elif theme == "special" %} + +{% elif theme == "system" %} + + +{% else %} + +{% endif %} + +

Other places I exist

+

+Email: trimill012 ("at" sign) gmail (full stop) com
+Discord: TriMill#6898
+GitHub: TriMill
+Youtube: TriMill
+

+ +

Source code & attribution

+

+The source code for this website is available on GitHub and is +released under the GNU GPLv3 free and open-source license. +It is built using the Flask web framework and incorporates +icons from Open Iconic. +

+ +{% endblock %} \ No newline at end of file diff --git a/src/templates/projects.html b/src/templates/projects.html new file mode 100644 index 0000000..16ed51a --- /dev/null +++ b/src/templates/projects.html @@ -0,0 +1,20 @@ +{% set meta={"title": "Projects", "desc": "List of projects I have made"} %} +{% extends "/_base.html" %} +{% block content %} + +

Here are some projects I have made over the past few years.

+ +
    +{% for site in data.projects %} +{% if site.hidden != True %} +
  • +{% if site.star == True %} +ā˜… +{% endif %} +{{ site.title }} Ā· {{ site.desc | safe }} +
  • +{% endif %} +{% endfor %} +
+ +{% endblock %} \ No newline at end of file diff --git a/src/templates/projects/complex_grapher/grammar.js b/src/templates/projects/complex_grapher/grammar.js new file mode 100644 index 0000000..2f99cef --- /dev/null +++ b/src/templates/projects/complex_grapher/grammar.js @@ -0,0 +1,1602 @@ +// Generated by Peggy 1.2.0. +// +// https://peggyjs.org/ + +"use strict"; + + + function toFixed(x) { + if (Math.abs(x) < 1.0) { + var e = parseInt(x.toString().split('e-')[1]); + if (e) { + x *= Math.pow(10,e-1); + x = '0.' + (new Array(e)).join('0') + x.toString().substring(2); + } + } else { + var e = parseInt(x.toString().split('+')[1]); + if (e > 20) { + e -= 20; + x /= Math.pow(10,e); + x += (new Array(e+1)).join('0'); + } + } + x = x.toString(); + if(!x.includes(".")) { + x += "."; + } + return x; + } + + class OpAdd { + constructor(left, right) { + this.left = left; + this.right = right; + } + gen(ctx) { + return "(" + this.left.gen(ctx) + "+" + this.right.gen(ctx) + ")"; + } + reduce() { + const left = this.left.reduce(); + const right = this.right.reduce(); + if(left !== null && right !== null) { + return new Vec2(left.re + right.re, left.im + right.im); + } else { + if(left !== null) { + this.left = left; + } else if(right !== null) { + this.right = right; + } + return null + } + } + } + class OpSub { + constructor(left, right) { + this.left = left; + this.right = right; + } + gen(ctx) { + return "(" + this.left.gen(ctx) + "-" + this.right.gen(ctx) + ")"; + } + reduce() { + const left = this.left.reduce(); + const right = this.right.reduce(); + if(left !== null && right !== null) { + return new Vec2(left.re - right.re, left.im - right.im); + } else { + if(left !== null) { + this.left = left; + } else if(right !== null) { + this.right = right; + } + return null + } + } + } + class OpNeg { + constructor(arg) { + this.arg = arg; + } + gen(ctx) { + return "(-(" + this.arg.gen(ctx) + "))"; + } + reduce() { + const arg = this.arg.reduce(); + if(arg !== null) { + return new Vec2(-arg.re, -arg.im); + } else { + return null + } + } + } + class Fn { + constructor(name, args, argoff) { + this.name = name; + this.args = args; + this.argoff = argoff; + } + gen(ctx) { + let name = this.name + "$" + this.args.length; + if(ctx[name] === undefined) { + throw "Error: the function " + this.name + " does not exist or has the wrong number of arguments"; + } + if(this.argoff != null) { + name += "_o"; + } + if(ctx[name] === undefined) { + throw "Error: the function " + this.name + " cannot take an argument offset"; + } + let s = ctx[name] + "("; + if(this.argoff != null) { + s += toFixed(this.argoff) + ","; + } + for(const a of this.args) { + s += a.gen(ctx) + ","; + } + s = s.slice(0,-1) + ")"; + return s; + } + reduce() { + for(const i in this.args) { + const a = this.args[i]; + const r = a.reduce(); + if(r !== null) { + this.args[i] = r; + } + } + return null; + } + } + class Variable { + constructor(name) { + this.name = name; + } + gen(ctx) { + if(ctx[this.name] === undefined) { + throw "Error: " + this.name + " is not a variable"; + } + return ctx[this.name]; + } + reduce() { + return null; + } + } + class Vec2 { + constructor(re, im) { + this.re = re; + this.im = im; + } + gen(ctx) { + return "vec2(" + toFixed(this.re) + "," + toFixed(this.im) + ")"; + } + reduce() { + return this; + } + } + class Conditional { + constructor(cond) { + this.cond = cond; + } + gen(ctx) { + let res = ""; + for(const part of this.cond) { + if(part.length == 2) { + res += "(" + part[0].gen(ctx) + ").x>0.0 ? (" + part[1].gen(ctx) + ") : "; + } else { + res += part[0].gen(ctx); + } + } + return "(" + res + ")"; + } + reduce() { + return null; + } + } + + +function peg$subclass(child, parent) { + function C() { this.constructor = child; } + C.prototype = parent.prototype; + child.prototype = new C(); +} + +function peg$SyntaxError(message, expected, found, location) { + var self = Error.call(this, message); + if (Object.setPrototypeOf) { + Object.setPrototypeOf(self, peg$SyntaxError.prototype); + } + self.expected = expected; + self.found = found; + self.location = location; + self.name = "SyntaxError"; + return self; +} + +peg$subclass(peg$SyntaxError, Error); + +function peg$padEnd(str, targetLength, padString) { + padString = padString || " "; + if (str.length > targetLength) { return str; } + targetLength -= str.length; + padString += padString.repeat(targetLength); + return str + padString.slice(0, targetLength); +} + +peg$SyntaxError.prototype.format = function(sources) { + var str = "Error: " + this.message; + if (this.location) { + var src = null; + var k; + for (k = 0; k < sources.length; k++) { + if (sources[k].source === this.location.source) { + src = sources[k].text.split(/\r\n|\n|\r/g); + break; + } + } + var s = this.location.start; + var loc = this.location.source + ":" + s.line + ":" + s.column; + if (src) { + var e = this.location.end; + var filler = peg$padEnd("", s.line.toString().length); + var line = src[s.line - 1]; + var last = s.line === e.line ? e.column : line.length + 1; + str += "\n --> " + loc + "\n" + + filler + " |\n" + + s.line + " | " + line + "\n" + + filler + " | " + peg$padEnd("", s.column - 1) + + peg$padEnd("", last - s.column, "^"); + } else { + str += "\n at " + loc; + } + } + return str; +}; + +peg$SyntaxError.buildMessage = function(expected, found) { + var DESCRIBE_EXPECTATION_FNS = { + literal: function(expectation) { + return "\"" + literalEscape(expectation.text) + "\""; + }, + + class: function(expectation) { + var escapedParts = expectation.parts.map(function(part) { + return Array.isArray(part) + ? classEscape(part[0]) + "-" + classEscape(part[1]) + : classEscape(part); + }); + + return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]"; + }, + + any: function() { + return "any character"; + }, + + end: function() { + return "end of input"; + }, + + other: function(expectation) { + return expectation.description; + } + }; + + function hex(ch) { + return ch.charCodeAt(0).toString(16).toUpperCase(); + } + + function literalEscape(s) { + return s + .replace(/\\/g, "\\\\") + .replace(/"/g, "\\\"") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); }); + } + + function classEscape(s) { + return s + .replace(/\\/g, "\\\\") + .replace(/\]/g, "\\]") + .replace(/\^/g, "\\^") + .replace(/-/g, "\\-") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); }); + } + + function describeExpectation(expectation) { + return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation); + } + + function describeExpected(expected) { + var descriptions = expected.map(describeExpectation); + var i, j; + + descriptions.sort(); + + if (descriptions.length > 0) { + for (i = 1, j = 1; i < descriptions.length; i++) { + if (descriptions[i - 1] !== descriptions[i]) { + descriptions[j] = descriptions[i]; + j++; + } + } + descriptions.length = j; + } + + switch (descriptions.length) { + case 1: + return descriptions[0]; + + case 2: + return descriptions[0] + " or " + descriptions[1]; + + default: + return descriptions.slice(0, -1).join(", ") + + ", or " + + descriptions[descriptions.length - 1]; + } + } + + function describeFound(found) { + return found ? "\"" + literalEscape(found) + "\"" : "end of input"; + } + + return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; +}; + +function peg$parse(input, options) { + options = options !== undefined ? options : {}; + + var peg$FAILED = {}; + var peg$source = options.grammarSource; + + var peg$startRuleFunctions = { Level0: peg$parseLevel0 }; + var peg$startRuleFunction = peg$parseLevel0; + + var peg$c0 = "+"; + var peg$c1 = "-"; + var peg$c2 = "*"; + var peg$c3 = "/"; + var peg$c4 = "^"; + var peg$c5 = "("; + var peg$c6 = ")"; + var peg$c7 = "{"; + var peg$c8 = "}"; + var peg$c9 = "["; + var peg$c10 = "]"; + var peg$c11 = "tau"; + var peg$c12 = "\u03C4"; + var peg$c13 = "t"; + var peg$c14 = "pi"; + var peg$c15 = "\u03C0"; + var peg$c16 = "p"; + var peg$c17 = ","; + var peg$c18 = ":"; + var peg$c19 = "i"; + var peg$c20 = "."; + + var peg$r0 = /^[a-zA-Z_\u221A\u0393\u03C0\u03C4\u03B3]/; + var peg$r1 = /^[+\-]/; + var peg$r2 = /^[0-9]/; + var peg$r3 = /^[ \t\n\r]/; + + var peg$e0 = peg$literalExpectation("+", false); + var peg$e1 = peg$literalExpectation("-", false); + var peg$e2 = peg$literalExpectation("*", false); + var peg$e3 = peg$literalExpectation("/", false); + var peg$e4 = peg$literalExpectation("^", false); + var peg$e5 = peg$literalExpectation("(", false); + var peg$e6 = peg$literalExpectation(")", false); + var peg$e7 = peg$literalExpectation("{", false); + var peg$e8 = peg$literalExpectation("}", false); + var peg$e9 = peg$literalExpectation("[", false); + var peg$e10 = peg$literalExpectation("]", false); + var peg$e11 = peg$literalExpectation("tau", false); + var peg$e12 = peg$literalExpectation("\u03C4", false); + var peg$e13 = peg$literalExpectation("t", false); + var peg$e14 = peg$literalExpectation("pi", false); + var peg$e15 = peg$literalExpectation("\u03C0", false); + var peg$e16 = peg$literalExpectation("p", false); + var peg$e17 = peg$literalExpectation(",", false); + var peg$e18 = peg$literalExpectation(":", false); + var peg$e19 = peg$otherExpectation("varname"); + var peg$e20 = peg$classExpectation([["a", "z"], ["A", "Z"], "_", "\u221A", "\u0393", "\u03C0", "\u03C4", "\u03B3"], false, false); + var peg$e21 = peg$otherExpectation("real"); + var peg$e22 = peg$otherExpectation("imag"); + var peg$e23 = peg$literalExpectation("i", false); + var peg$e24 = peg$otherExpectation("number"); + var peg$e25 = peg$classExpectation(["+", "-"], false, false); + var peg$e26 = peg$literalExpectation(".", false); + var peg$e27 = peg$otherExpectation("digits"); + var peg$e28 = peg$classExpectation([["0", "9"]], false, false); + var peg$e29 = peg$otherExpectation("whitespace"); + var peg$e30 = peg$classExpectation([" ", "\t", "\n", "\r"], false, false); + + var peg$f0 = function(head, tail) { + return tail.reduce(function(result, element) { + if (element[1] === "+") { return new OpAdd(result, element[3]); } + if (element[1] === "-") { return new OpSub(result, element[3]); } + }, head); + }; + var peg$f1 = function(head, tail) { + return tail.reduce(function(result, element) { + if (element[1] === "*") { return new Fn("*", [result, element[3]]); } + if (element[1] === "/") { return new Fn("/", [result, element[3]]); } + }, head); + }; + var peg$f2 = function(head, tail) { + return head.reverse().reduce(function(result, element) { + return new Fn("^", [element[0], result]); + }, tail); + }; + var peg$f3 = function(expr) { return expr; }; + var peg$f4 = function(cond) { return new Conditional(cond); }; + var peg$f5 = function(name, argoff, args) { + if(typeof(name) == "string") { + return new Fn(name, args.reverse(), argoff == null ? null : argoff[2]); + } else { + throw "Error: i is not a function"; + } + }; + var peg$f6 = function(name) { + if(typeof(name) == "string") { + return new Variable(name); + } else { + return name; + } + }; + var peg$f7 = function(e) { return new OpNeg(e); }; + var peg$f8 = function(n) { + console.log("AAA", n.re); + return n.re * 6.283185307179586; + }; + var peg$f9 = function(n) { + return n.re * 3.141592653589793; + }; + var peg$f10 = function(n) { + return n.re; + }; + var peg$f11 = function(arg, lst) { + lst.push(arg); + return lst; + }; + var peg$f12 = function(arg) { return [arg]; }; + var peg$f13 = function(cond, expr, rest) { return [].concat([[cond, expr]],rest); }; + var peg$f14 = function(expr) { return [[expr]]; }; + var peg$f15 = function() { + if(text() === "i") { + return new Vec2(0,1); + } else { + return text(); + } + }; + var peg$f16 = function() { + return new Vec2(parseFloat(text()),0); + }; + var peg$f17 = function() { + return new Vec2(0,parseFloat(text())); + }; + + var peg$currPos = 0; + var peg$savedPos = 0; + var peg$posDetailsCache = [{ line: 1, column: 1 }]; + var peg$maxFailPos = 0; + var peg$maxFailExpected = []; + var peg$silentFails = 0; + + var peg$result; + + if ("startRule" in options) { + if (!(options.startRule in peg$startRuleFunctions)) { + throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); + } + + peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; + } + + function text() { + return input.substring(peg$savedPos, peg$currPos); + } + + function offset() { + return peg$savedPos; + } + + function range() { + return { + source: peg$source, + start: peg$savedPos, + end: peg$currPos + }; + } + + function location() { + return peg$computeLocation(peg$savedPos, peg$currPos); + } + + function expected(description, location) { + location = location !== undefined + ? location + : peg$computeLocation(peg$savedPos, peg$currPos); + + throw peg$buildStructuredError( + [peg$otherExpectation(description)], + input.substring(peg$savedPos, peg$currPos), + location + ); + } + + function error(message, location) { + location = location !== undefined + ? location + : peg$computeLocation(peg$savedPos, peg$currPos); + + throw peg$buildSimpleError(message, location); + } + + function peg$literalExpectation(text, ignoreCase) { + return { type: "literal", text: text, ignoreCase: ignoreCase }; + } + + function peg$classExpectation(parts, inverted, ignoreCase) { + return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase }; + } + + function peg$anyExpectation() { + return { type: "any" }; + } + + function peg$endExpectation() { + return { type: "end" }; + } + + function peg$otherExpectation(description) { + return { type: "other", description: description }; + } + + function peg$computePosDetails(pos) { + var details = peg$posDetailsCache[pos]; + var p; + + if (details) { + return details; + } else { + p = pos - 1; + while (!peg$posDetailsCache[p]) { + p--; + } + + details = peg$posDetailsCache[p]; + details = { + line: details.line, + column: details.column + }; + + while (p < pos) { + if (input.charCodeAt(p) === 10) { + details.line++; + details.column = 1; + } else { + details.column++; + } + + p++; + } + + peg$posDetailsCache[pos] = details; + + return details; + } + } + + function peg$computeLocation(startPos, endPos) { + var startPosDetails = peg$computePosDetails(startPos); + var endPosDetails = peg$computePosDetails(endPos); + + return { + source: peg$source, + start: { + offset: startPos, + line: startPosDetails.line, + column: startPosDetails.column + }, + end: { + offset: endPos, + line: endPosDetails.line, + column: endPosDetails.column + } + }; + } + + function peg$fail(expected) { + if (peg$currPos < peg$maxFailPos) { return; } + + if (peg$currPos > peg$maxFailPos) { + peg$maxFailPos = peg$currPos; + peg$maxFailExpected = []; + } + + peg$maxFailExpected.push(expected); + } + + function peg$buildSimpleError(message, location) { + return new peg$SyntaxError(message, null, null, location); + } + + function peg$buildStructuredError(expected, found, location) { + return new peg$SyntaxError( + peg$SyntaxError.buildMessage(expected, found), + expected, + found, + location + ); + } + + function peg$parseLevel0() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + s0 = peg$currPos; + s1 = peg$parseLevel1(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$currPos; + s4 = peg$parse_(); + if (input.charCodeAt(peg$currPos) === 43) { + s5 = peg$c0; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e0); } + } + if (s5 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 45) { + s5 = peg$c1; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + } + if (s5 !== peg$FAILED) { + s6 = peg$parse_(); + s7 = peg$parseLevel1(); + if (s7 !== peg$FAILED) { + s4 = [s4, s5, s6, s7]; + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$currPos; + s4 = peg$parse_(); + if (input.charCodeAt(peg$currPos) === 43) { + s5 = peg$c0; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e0); } + } + if (s5 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 45) { + s5 = peg$c1; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + } + if (s5 !== peg$FAILED) { + s6 = peg$parse_(); + s7 = peg$parseLevel1(); + if (s7 !== peg$FAILED) { + s4 = [s4, s5, s6, s7]; + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } + peg$savedPos = s0; + s0 = peg$f0(s1, s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseLevel1() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + s0 = peg$currPos; + s1 = peg$parseLevel2(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$currPos; + s4 = peg$parse_(); + if (input.charCodeAt(peg$currPos) === 42) { + s5 = peg$c2; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e2); } + } + if (s5 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 47) { + s5 = peg$c3; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e3); } + } + } + if (s5 !== peg$FAILED) { + s6 = peg$parse_(); + s7 = peg$parseLevel2(); + if (s7 !== peg$FAILED) { + s4 = [s4, s5, s6, s7]; + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$currPos; + s4 = peg$parse_(); + if (input.charCodeAt(peg$currPos) === 42) { + s5 = peg$c2; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e2); } + } + if (s5 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 47) { + s5 = peg$c3; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e3); } + } + } + if (s5 !== peg$FAILED) { + s6 = peg$parse_(); + s7 = peg$parseLevel2(); + if (s7 !== peg$FAILED) { + s4 = [s4, s5, s6, s7]; + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } + peg$savedPos = s0; + s0 = peg$f1(s1, s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseLevel2() { + var s0, s1, s2, s3, s4, s5, s6; + + s0 = peg$currPos; + s1 = []; + s2 = peg$currPos; + s3 = peg$parseLevel3(); + if (s3 !== peg$FAILED) { + s4 = peg$parse_(); + if (input.charCodeAt(peg$currPos) === 94) { + s5 = peg$c4; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e4); } + } + if (s5 !== peg$FAILED) { + s6 = peg$parse_(); + s3 = [s3, s4, s5, s6]; + s2 = s3; + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$currPos; + s3 = peg$parseLevel3(); + if (s3 !== peg$FAILED) { + s4 = peg$parse_(); + if (input.charCodeAt(peg$currPos) === 94) { + s5 = peg$c4; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e4); } + } + if (s5 !== peg$FAILED) { + s6 = peg$parse_(); + s3 = [s3, s4, s5, s6]; + s2 = s3; + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + } + s2 = peg$parseLevel3(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f2(s1, s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseLevel3() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10; + + s0 = peg$currPos; + s1 = peg$parse_(); + if (input.charCodeAt(peg$currPos) === 40) { + s2 = peg$c5; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e5); } + } + if (s2 !== peg$FAILED) { + s3 = peg$parse_(); + s4 = peg$parseLevel0(); + if (s4 !== peg$FAILED) { + s5 = peg$parse_(); + if (input.charCodeAt(peg$currPos) === 41) { + s6 = peg$c6; + peg$currPos++; + } else { + s6 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e6); } + } + if (s6 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f3(s4); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parse_(); + if (input.charCodeAt(peg$currPos) === 123) { + s2 = peg$c7; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e7); } + } + if (s2 !== peg$FAILED) { + s3 = peg$parse_(); + s4 = peg$parseConditionalInner(); + if (s4 !== peg$FAILED) { + s5 = peg$parse_(); + if (input.charCodeAt(peg$currPos) === 125) { + s6 = peg$c8; + peg$currPos++; + } else { + s6 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e8); } + } + if (s6 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f4(s4); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parse_(); + s2 = peg$parseName(); + if (s2 !== peg$FAILED) { + s3 = peg$parse_(); + s4 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 91) { + s5 = peg$c9; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e9); } + } + if (s5 !== peg$FAILED) { + s6 = peg$parse_(); + s7 = peg$parseArgOffset(); + if (s7 !== peg$FAILED) { + s8 = peg$parse_(); + if (input.charCodeAt(peg$currPos) === 93) { + s9 = peg$c10; + peg$currPos++; + } else { + s9 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e10); } + } + if (s9 !== peg$FAILED) { + s10 = peg$parse_(); + s5 = [s5, s6, s7, s8, s9, s10]; + s4 = s5; + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + if (s4 === peg$FAILED) { + s4 = null; + } + if (input.charCodeAt(peg$currPos) === 40) { + s5 = peg$c5; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e5); } + } + if (s5 !== peg$FAILED) { + s6 = peg$parseParameterList(); + if (s6 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 41) { + s7 = peg$c6; + peg$currPos++; + } else { + s7 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e6); } + } + if (s7 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f5(s2, s4, s6); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parse_(); + s2 = peg$parseName(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f6(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$parseImag(); + if (s0 === peg$FAILED) { + s0 = peg$parseReal(); + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parse_(); + if (input.charCodeAt(peg$currPos) === 45) { + s2 = peg$c1; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s2 !== peg$FAILED) { + s3 = peg$parseLevel1(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f7(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + } + } + } + } + } + + return s0; + } + + function peg$parseArgOffset() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parseReal(); + if (s1 !== peg$FAILED) { + s2 = peg$parse_(); + if (input.substr(peg$currPos, 3) === peg$c11) { + s3 = peg$c11; + peg$currPos += 3; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e11); } + } + if (s3 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 964) { + s3 = peg$c12; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e12); } + } + if (s3 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 116) { + s3 = peg$c13; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e13); } + } + } + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f8(s1); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseReal(); + if (s1 !== peg$FAILED) { + s2 = peg$parse_(); + if (input.substr(peg$currPos, 2) === peg$c14) { + s3 = peg$c14; + peg$currPos += 2; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e14); } + } + if (s3 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 960) { + s3 = peg$c15; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e15); } + } + if (s3 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 112) { + s3 = peg$c16; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e16); } + } + } + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f9(s1); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseReal(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f10(s1); + } + s0 = s1; + } + } + + return s0; + } + + function peg$parseParameterList() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parse_(); + s2 = peg$parseLevel0(); + if (s2 !== peg$FAILED) { + s3 = peg$parse_(); + if (input.charCodeAt(peg$currPos) === 44) { + s4 = peg$c17; + peg$currPos++; + } else { + s4 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e17); } + } + if (s4 !== peg$FAILED) { + s5 = peg$parseParameterList(); + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f11(s2, s5); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parse_(); + s2 = peg$parseLevel0(); + if (s2 !== peg$FAILED) { + s3 = peg$parse_(); + peg$savedPos = s0; + s0 = peg$f12(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + + return s0; + } + + function peg$parseConditionalInner() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11; + + s0 = peg$currPos; + s1 = peg$parse_(); + s2 = peg$parseLevel0(); + if (s2 !== peg$FAILED) { + s3 = peg$parse_(); + if (input.charCodeAt(peg$currPos) === 58) { + s4 = peg$c18; + peg$currPos++; + } else { + s4 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e18); } + } + if (s4 !== peg$FAILED) { + s5 = peg$parse_(); + s6 = peg$parseLevel0(); + if (s6 !== peg$FAILED) { + s7 = peg$parse_(); + if (input.charCodeAt(peg$currPos) === 44) { + s8 = peg$c17; + peg$currPos++; + } else { + s8 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e17); } + } + if (s8 !== peg$FAILED) { + s9 = peg$parse_(); + s10 = peg$parseConditionalInner(); + if (s10 !== peg$FAILED) { + s11 = peg$parse_(); + peg$savedPos = s0; + s0 = peg$f13(s2, s6, s10); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parse_(); + s2 = peg$parseLevel0(); + if (s2 !== peg$FAILED) { + s3 = peg$parse_(); + peg$savedPos = s0; + s0 = peg$f14(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + + return s0; + } + + function peg$parseName() { + var s0, s1, s2; + + peg$silentFails++; + s0 = peg$currPos; + s1 = []; + if (peg$r0.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e20); } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + if (peg$r0.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e20); } + } + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f15(); + } + s0 = s1; + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e19); } + } + + return s0; + } + + function peg$parseReal() { + var s0, s1, s2; + + peg$silentFails++; + s0 = peg$currPos; + s1 = peg$parse_(); + s2 = peg$parseNumber(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f16(); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e21); } + } + + return s0; + } + + function peg$parseImag() { + var s0, s1, s2, s3; + + peg$silentFails++; + s0 = peg$currPos; + s1 = peg$parse_(); + s2 = peg$parseNumber(); + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 105) { + s3 = peg$c19; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e23); } + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f17(); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e22); } + } + + return s0; + } + + function peg$parseNumber() { + var s0, s1, s2, s3, s4; + + peg$silentFails++; + s0 = peg$currPos; + if (peg$r1.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e25); } + } + if (s1 === peg$FAILED) { + s1 = null; + } + s2 = peg$parseDigits(); + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 46) { + s3 = peg$c20; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e26); } + } + if (s3 !== peg$FAILED) { + s4 = peg$parseDigits(); + if (s4 !== peg$FAILED) { + s1 = [s1, s2, s3, s4]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (peg$r1.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e25); } + } + if (s1 === peg$FAILED) { + s1 = null; + } + s2 = peg$parseDigits(); + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 46) { + s3 = peg$c20; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e26); } + } + if (s3 !== peg$FAILED) { + s1 = [s1, s2, s3]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (peg$r1.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e25); } + } + if (s1 === peg$FAILED) { + s1 = null; + } + if (input.charCodeAt(peg$currPos) === 46) { + s2 = peg$c20; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e26); } + } + if (s2 !== peg$FAILED) { + s3 = peg$parseDigits(); + if (s3 !== peg$FAILED) { + s1 = [s1, s2, s3]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (peg$r1.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e25); } + } + if (s1 === peg$FAILED) { + s1 = null; + } + s2 = peg$parseDigits(); + if (s2 !== peg$FAILED) { + s1 = [s1, s2]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + } + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e24); } + } + + return s0; + } + + function peg$parseDigits() { + var s0, s1; + + peg$silentFails++; + s0 = []; + if (peg$r2.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e28); } + } + if (s1 !== peg$FAILED) { + while (s1 !== peg$FAILED) { + s0.push(s1); + if (peg$r2.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e28); } + } + } + } else { + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e27); } + } + + return s0; + } + + function peg$parse_() { + var s0, s1; + + peg$silentFails++; + s0 = []; + if (peg$r3.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e30); } + } + while (s1 !== peg$FAILED) { + s0.push(s1); + if (peg$r3.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e30); } + } + } + peg$silentFails--; + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e29); } + + return s0; + } + + peg$result = peg$startRuleFunction(); + + if (peg$result !== peg$FAILED && peg$currPos === input.length) { + return peg$result; + } else { + if (peg$result !== peg$FAILED && peg$currPos < input.length) { + peg$fail(peg$endExpectation()); + } + + throw peg$buildStructuredError( + peg$maxFailExpected, + peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, + peg$maxFailPos < input.length + ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) + : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) + ); + } +} + +module.exports = { + SyntaxError: peg$SyntaxError, + parse: peg$parse +}; diff --git a/src/templates/projects/complex_grapher/grammar.pegjs b/src/templates/projects/complex_grapher/grammar.pegjs new file mode 100644 index 0000000..6261711 --- /dev/null +++ b/src/templates/projects/complex_grapher/grammar.pegjs @@ -0,0 +1,266 @@ +{{ + function toFixed(x) { + if (Math.abs(x) < 1.0) { + var e = parseInt(x.toString().split('e-')[1]); + if (e) { + x *= Math.pow(10,e-1); + x = '0.' + (new Array(e)).join('0') + x.toString().substring(2); + } + } else { + var e = parseInt(x.toString().split('+')[1]); + if (e > 20) { + e -= 20; + x /= Math.pow(10,e); + x += (new Array(e+1)).join('0'); + } + } + x = x.toString(); + if(!x.includes(".")) { + x += "."; + } + return x; + } + + class OpAdd { + constructor(left, right) { + this.left = left; + this.right = right; + } + gen(ctx) { + return "(" + this.left.gen(ctx) + "+" + this.right.gen(ctx) + ")"; + } + reduce() { + const left = this.left.reduce(); + const right = this.right.reduce(); + if(left !== null && right !== null) { + return new Vec2(left.re + right.re, left.im + right.im); + } else { + if(left !== null) { + this.left = left; + } else if(right !== null) { + this.right = right; + } + return null + } + } + } + class OpSub { + constructor(left, right) { + this.left = left; + this.right = right; + } + gen(ctx) { + return "(" + this.left.gen(ctx) + "-" + this.right.gen(ctx) + ")"; + } + reduce() { + const left = this.left.reduce(); + const right = this.right.reduce(); + if(left !== null && right !== null) { + return new Vec2(left.re - right.re, left.im - right.im); + } else { + if(left !== null) { + this.left = left; + } else if(right !== null) { + this.right = right; + } + return null + } + } + } + class OpNeg { + constructor(arg) { + this.arg = arg; + } + gen(ctx) { + return "(-(" + this.arg.gen(ctx) + "))"; + } + reduce() { + const arg = this.arg.reduce(); + if(arg !== null) { + return new Vec2(-arg.re, -arg.im); + } else { + return null + } + } + } + class Fn { + constructor(name, args, argoff) { + this.name = name; + this.args = args; + this.argoff = argoff; + } + gen(ctx) { + let name = this.name + "$" + this.args.length; + if(ctx[name] === undefined) { + throw "Error: the function " + this.name + " does not exist or has the wrong number of arguments"; + } + if(this.argoff != null) { + name += "_o"; + } + if(ctx[name] === undefined) { + throw "Error: the function " + this.name + " cannot take an argument offset"; + } + let s = ctx[name] + "("; + if(this.argoff != null) { + s += toFixed(this.argoff) + ","; + } + for(const a of this.args) { + s += a.gen(ctx) + ","; + } + s = s.slice(0,-1) + ")"; + return s; + } + reduce() { + for(const i in this.args) { + const a = this.args[i]; + const r = a.reduce(); + if(r !== null) { + this.args[i] = r; + } + } + return null; + } + } + class Variable { + constructor(name) { + this.name = name; + } + gen(ctx) { + if(ctx[this.name] === undefined) { + throw "Error: " + this.name + " is not a variable"; + } + return ctx[this.name]; + } + reduce() { + return null; + } + } + class Vec2 { + constructor(re, im) { + this.re = re; + this.im = im; + } + gen(ctx) { + return "vec2(" + toFixed(this.re) + "," + toFixed(this.im) + ")"; + } + reduce() { + return this; + } + } + class Conditional { + constructor(cond) { + this.cond = cond; + } + gen(ctx) { + let res = ""; + for(const part of this.cond) { + if(part.length == 2) { + res += "(" + part[0].gen(ctx) + ").x>0.0 ? (" + part[1].gen(ctx) + ") : "; + } else { + res += part[0].gen(ctx); + } + } + return "(" + res + ")"; + } + reduce() { + return null; + } + } +}} + +Level0 + = head:Level1 tail:(_ ("+" / "-") _ Level1)* { + return tail.reduce(function(result, element) { + if (element[1] === "+") { return new OpAdd(result, element[3]); } + if (element[1] === "-") { return new OpSub(result, element[3]); } + }, head); + } + +Level1 + = head:Level2 tail:(_ ("*" / "/") _ Level2)* { + return tail.reduce(function(result, element) { + if (element[1] === "*") { return new Fn("*", [result, element[3]]); } + if (element[1] === "/") { return new Fn("/", [result, element[3]]); } + }, head); + } + +Level2 + = head:(Level3 _ "^" _)* tail:Level3 { + return head.reverse().reduce(function(result, element) { + return new Fn("^", [element[0], result]); + }, tail); + } + +Level3 + = _ "(" _ expr:Level0 _ ")" { return expr; } + / _ "{" _ cond:ConditionalInner _ "}" { return new Conditional(cond); } + / _ name:Name _ argoff:("[" _ ArgOffset _ "]" _)? "(" args:ParameterList ")" { + if(typeof(name) == "string") { + return new Fn(name, args.reverse(), argoff == null ? null : argoff[2]); + } else { + throw "Error: i is not a function"; + } + } + / _ name:Name { + if(typeof(name) == "string") { + return new Variable(name); + } else { + return name; + } + } + / Imag / Real + / _ "-" e:Level1 { return new OpNeg(e); } + +ArgOffset + = n:Real _ ("tau" / "Ļ„" / "t") { + console.log("AAA", n.re); + return n.re * 6.283185307179586; + } + / n:Real _ ("pi" / "Ļ€" / "p") { + return n.re * 3.141592653589793; + } + / n:Real { + return n.re; + } + +ParameterList + = _ arg:Level0 _ "," lst:ParameterList { + lst.push(arg); + return lst; +} + / _ arg:Level0 _ { return [arg]; } + +ConditionalInner + = _ cond:Level0 _ ":" _ expr:Level0 _ "," _ rest:ConditionalInner _ { return [].concat([[cond, expr]],rest); } + / _ expr:Level0 _ { return [[expr]]; } + +Name "varname" + = [a-zA-Z_āˆšĪ“Ļ€Ļ„Ī³]+ { + if(text() === "i") { + return new Vec2(0,1); + } else { + return text(); + } + } + +Real "real" + = _ Number { + return new Vec2(parseFloat(text()),0); + } + +Imag "imag" + = _ Number "i" { + return new Vec2(0,parseFloat(text())); + } + +Number "number" + = [+-]? Digits "." Digits + / [+-]? Digits "." + / [+-]? "." Digits + / [+-]? Digits + +Digits "digits" + = [0-9]+ + +_ "whitespace" + = [ \t\n\r]* diff --git a/src/templates/projects/complex_grapher/help.html b/src/templates/projects/complex_grapher/help.html new file mode 100644 index 0000000..0aa6285 --- /dev/null +++ b/src/templates/projects/complex_grapher/help.html @@ -0,0 +1,139 @@ +{% set meta={"title": "Complex Grapher Documentation", "desc": "Documentation for Complex Grapher", "hidden": True} %} +{% extends "/_base.html" %} +{% block content %} +

+Complex Grapher is an online tool to visualize complex functions using domain coloring. This help guide assumes basic knowledge about how complex numbers and domain coloring work, Wikipedia links will be provided for further explanation. +

+

Complex Grapher requires a browser that supports WebGL 3.0 or greater.

+ +

The user interface

+ +

+

    +
  • Equation - Enter the function you want to graph here, in terms of z. See below for information about the syntax of expressions.
  • +
  • Axis scale - Set the bounds of the real and imaginary axes. The first number in each pair is the left/bottom side of the canvas, the second number is the top/right side.
  • +
  • Shading amount - Control how much black/white shading is applied to numbers close to 0 or āˆž. Furthest left is no shading (however, values equal to 0 or āˆž will still be black and white, respectively).
  • +
  • Swap black and white - Check this to instead use black for āˆž and white for 0.
  • +
  • Draw contours - Draw rings at integer distances from the origin. The first ring (about the unit circle) is white, the rest are black.
  • +
  • Smooth coloring - Use a smoother coloring method.
  • +
  • Iteration + This section allows you to iterate the function you put in above a set number of times (that is, call the function on its own output repeatedly). If iteration is enabled, you may also use the variables c and n in your function. c will remain constant throughout the iterations, and n represents the current iteration number (starting with 0). +
      +
    • Iterations - How many times to iterate the function.
    • +
    • zā‚€(z) - The initial value for z
    • +
    • c(z) - The initial value for c
    • +
    +
  • +
  • Arg offset - use different branches of functions by changing where the branch cut of arg(z) is.
  • +
  • DPI - Control the resolution of the canvas. "single" uses the browser's default, "double" uses twice that on both axes (resulting in 4 times as many pixels), "quadruple" uses four times that (16 times as many pixels). These links will save the graph as a URL parameter and reload the page.
  • +
  • Graph - Self-explanatory.
  • +
  • Generate link - Generate a link containing all of the equations and settings currently applied.
  • +
+

+ +

Syntax

+ +
+level0 := level1 (("+" | "-") level1)*
+level1 := level2 (("*" | "/") level2)*
+level2 := (level3 "^")* level3
+level3 := "(" level0 ")"
+    | "{" condition "}"
+    | name "(" (level0 ",")* level0 ")"
+    | name
+    | imag
+    | real
+    | "-" level1
+condition := level0 ":" level0 "," condition
+    | level0
+name := /[a-zA-Z_āˆšĪ“Ļ€Ļ„Ī³]+/
+real := number
+imag := number "i"
+number := ("+" | "-")? digits "." digits
+    | ("+" | "-")? digits "."
+    | ("+" | "-")? "." digits
+    | ("+" | "-")? digits
+digits := /[0-9]+/
+whitespace = /[ \t\n\r]*/
+
+ +

Numbers

+

+Real numbers can be written in standard base-10 form (ex. 37, -1045, 33.26258). Imaginary numbers can be written similarly, but followed by an i (ex. 3i, -25i, .37i). i can also be used alone. Complex numbers can be expressed simply as the sum of a real and an imaginary number (ex. 3-5i, .7+52i). +

+

Operators

+

+The five basic operators are +, -, *, /, and ^ (exponentiation). These follow the normal order of operations, with ^ being right associative (so 2^3^4 = 2^(3^4), not (2^3)^4). Parentheses ( ) can be used for grouping. +

+

Variables and constants

+

+In f(z), the variables z, c, and n are available. z is the argument of the function, and c and n are used for iteration as described above. In zā‚€(z) and c(z), the only variable available is z. +

+

+The constants e, tau or Ļ„, pi or Ļ€ (= Ļ„/2), and emg or Ī³ (the Eulerā€“Mascheroni constant) are available +

+

Functions

+

+Functions are written as the funciton name followed by the argument in parentheses. All functions only take one argument. Examples: sin(z), gamma(3+2i), conj(1/z). +

+

+The following functions are available: +

    +
  • re and im - the real or imaginary part of a complex number, respectively
  • +
  • abs - absolute value (Euclidean distance from the origin)
  • +
  • arg - the argument (angle about the origin in the range (-Ļ€,Ļ€])
  • +
  • norm - the norm (equal to abs(z)^2 and re(z)^2 + im(z)^2)
  • +
  • conj - complex conjugate
  • +
  • sqrt or āˆš - square root (equal to z^0.5)
  • +
  • root - nth root (first argument is radicand, second is index)
  • +
  • exp - the exponential function (equal to e^z)
  • +
  • ln - the natural logarithm
  • +
  • log - logarith with base, where the base is the second argument (defaults to e)
  • +
  • recip - reciprocal, equivalent to 1/z
  • +
  • sin cos tan - sine, cosine and tangent
  • +
  • asin acos atan - inverse sine, cosine and tangent
  • +
  • sinh cosh tanh - hyperbolic sine, cosine and tangent
  • +
  • asinh acosh atanh - inverse hyperbolic sine, cosine and tangent
  • +
  • gamma or Ī“ - the gamma function
  • +
  • unitcircle - set abs(z) to 1, normalizing the complex number to the unit circle
  • +
  • signre - the sign of the real part of z (1 if re(z)>0, -1 if re(z)<0, 0 if re(z)=0
  • +
  • signim - the sign of the imaginary part of z (1 if im(z)>0, -1 if im(z)<0, 0 if im(z)=0
  • +
  • lambertw - the Lambert W function
  • +
+

+

+The following functions are currently experimental and may be changed or removed later: +

+ +

Conditionals

+

+Conditionals (using curly braces { }) can be used like if-statements to pick an expression from a list. They take the form {condition1: expression1, condition2: expression2, expression3}. The conditions are evaluated from left to right, once the first true one is reached the corresponding expression is evaluated. The final expression (with no condition) acts as a fallback if none of the previous conditions were true. A condition is true if the real part of the result is strictly positive. +

+

+Example: {z-3: 2*z, z-2: z^2, 1/z} will evaluate to 2*z whenever re(z)>3 (re(z-3)>0), otherwise to z^2 whenever re(z)>2, otherwise to 1/z. +

+

+Conditionals are currently experimental and may be changed later. +

+

A word of caution

+

+WebGL is very prone to crashing, especially when using iteration. Some operations, like addition and multiplication, are very fast and should not cause too many problems. Others, like exp(z) and sinh(z) are fairly fast since they are implemented directly in the hardware. But some functions, like lambertw(z) and gamma(z) are a lot slower due to their slow implementations. For the best performance, avoid mixing slow functunctions, high iteration counts, and high DPI settings together. +

+

Example graphs

+
    +
  • Rational equation
  • +
  • Essential singularity
  • +
  • Power Tower
  • +
  • Infinite power tower
  • +
  • Newton's fractal
  • +
  • The Mandelbrot set
  • +
  • The Euler function as an infinte product
  • +
      +{% endblock %} \ No newline at end of file diff --git a/src/templates/projects/complex_grapher/index.html b/src/templates/projects/complex_grapher/index.html new file mode 100644 index 0000000..b11171c --- /dev/null +++ b/src/templates/projects/complex_grapher/index.html @@ -0,0 +1,93 @@ +{% set meta={"title": "Complex Grapher", "desc": "Graph complex functions", "pagewidth": "1400px"} %} +{% extends "/_base.html" %} +{% block head %} + + + + + +{% endblock %} +{% block content %} +
      + ā€”ā€”ā€” Equation ā€”ā€”ā€” + f(z) = + + ā€”ā€”ā€” Axis scale ā€”ā€”ā€” + Real axis:      + + to + + Imaginary axis: + + to + + + ā€”ā€”ā€” Shading options ā€”ā€”ā€” + Shading amount: + Swap black and white + Draw contours + Smooth coloring + + ā€”ā€”ā€” Iteration ā€”ā€”ā€” + + + + + + + + + ā€”ā€”ā€” DPI ā€”ā€”ā€” + + single + double + quadruple + +   + + + Generate link + + +   + +
      +
      + +
      +{% endblock %} diff --git a/src/templates/projects/complex_grapher/main.js b/src/templates/projects/complex_grapher/main.js new file mode 100644 index 0000000..310524e --- /dev/null +++ b/src/templates/projects/complex_grapher/main.js @@ -0,0 +1,234 @@ +var shaderBase; +var dpi = 1; + +const BASE_CTX = { + "*$2": "cmul", + "/$2": "cdiv", + "^$2": "cpow", + "root$2": "croot", + "root$2_o": "croot_o", + "recip$1": "crecip", + "re$1": "cre", + "im$1": "cim", + "abs$1": "cabs", + "arg$1": "carg", + "arg$1_i": "carg_o", + "norm$1": "cnorm", + "conj$1": "cconj", + "sqrt$1": "csqrt", + "sqrt$1_o": "csqrt_o", + "āˆš$1": "csqrt", + "āˆš$1_o": "csqrt_o", + "sinh$1": "csinh", + "cosh$1": "ccosh", + "tanh$1": "ctanh", + "asinh$1": "casinh", + "asinh$1_o": "casinh_o", + "acosh$1_o": "cacosh_o", + "sin$1": "csin", + "cos$1": "ccos", + "tan$1": "ctan", + "asin$1": "casin", + "acos$1": "cacos", + "atan$1": "catan", + "asin$1_o": "casin_o", + "acos$1_o": "cacos_o", + "exp$1": "cexp", + "log$1": "clog", + "log$2": "clogbase", + "log$1_o": "clog_o", + "log$2_o": "clogbase_o", + "ln$1": "clog", + "ln$1_o": "clog_o", + "gamma$1": "cgamma", + "Ī“$1": "cgamma", + "unitcircle$1": "cunitcircle", + "signre$1": "csignre", + "signim$1": "csignim", + "lambertw$1": "clambertw", + // CONSTANTS + "e": "CONST_E", + "pi": "CONST_PI", + "Ļ€": "CONST_PI", + "tau": "CONST_TAU", + "Ļ„": "CONST_TAU", + "emg": "CONST_EMGAMMA", + "Ī³": "CONST_EMGAMMA", + // NOT OFFICIALLY SUPPORTED YET + "digamma$1": "cdigamma", + "loggamma$1": "cloggamma", + "erf$1": "cerf", + "eulerfn$1": "ceulerfn", + "ti$1": "cti", + "weierp$3": "cweierp", +}; + +const FZ_CTX = { + ...BASE_CTX, + "z": "z", + "c": "c", + "n": "n", +} + +const INIT_CTX = { + ...BASE_CTX, + "z": "z", +} + +function expr2source(expr, ctx) { + let ast = peg$parse(expr); + console.log(ast); + const reduced = ast.reduce(); + if(reduced !== null) { + ast = reduced; + } + return ast.gen(ctx); +} + +function graph() { + const shadingRaw = document.getElementById("shading").value + try { + const options = { + rmin: parseFloat(document.getElementById("rmin").value), + rmax: parseFloat(document.getElementById("rmax").value), + imin: parseFloat(document.getElementById("imin").value), + imax: parseFloat(document.getElementById("imax").value), + drawcontours: document.getElementById("drawcontours").checked, + smoothcolor: document.getElementById("smoothcolor").checked, + shading: 15/(4-4*shadingRaw**4) - 15/4, + swapbw: document.getElementById("swapbw").checked, + iterations: parseInt(document.getElementById("iterations").value), + } + + console.log(options); + + console.log("=== PRINTING GENERATED GLSL ==="); + const fz = document.getElementById("fz").value; + const fzSource = expr2source(fz, FZ_CTX); + console.log("fz: " + fzSource); + const initZ = document.getElementById("init_z").value; + const initZSource = expr2source(initZ, INIT_CTX); + console.log("z_init: " + initZSource); + const initC = document.getElementById("init_c").value; + const initCSource = expr2source(initC, INIT_CTX); + console.log("c_init: " + initCSource); + + const fsSource = shaderBase + + "vec2 f(vec2 z, vec2 c, vec2 n) { return " + + fzSource + + ";}" + + "vec2 z_init(vec2 z) { return " + + initZSource + + ";}" + + "vec2 c_init(vec2 z) { return " + + initCSource + + ";}"; + runShader(fsSource, options); + } catch(e) { + document.getElementById("errors").textContent = e.toString(); + console.log(e); + return; + } + document.getElementById("errors").textContent = ""; +} + +function decodeURLParams() { + const canvas = document.getElementById("canvas"); + var params = new URLSearchParams(window.location.search); + if(!params.has("dpi") || params.get("dpi") == "1") { + dpi = 1; + canvas.width = 640; + canvas.height = 640; + } else if(params.get("dpi") == "2") { + dpi = 2; + canvas.width = 640*2; + canvas.height = 640*2; + } else if(params.get("dpi") == "4") { + dpi = 4; + canvas.width = 640*4; + canvas.height = 640*4; + } + if(params.has("src")) { + const src = JSON.parse(atob(params.get("src"))); + document.getElementById("fz").value = src.expr; + document.getElementById("rmin").value = src.axes[0]; + document.getElementById("rmax").value = src.axes[1]; + document.getElementById("imin").value = src.axes[2]; + document.getElementById("imax").value = src.axes[3]; + document.getElementById("shading").value = src.shading; + document.getElementById("swapbw").checked = src.swapbw; + document.getElementById("drawcontours").checked = src.contours; + if(src.itermode !== false) { + document.getElementById("enable_iters").checked = true; + document.getElementById("iterations").value = src.itermode.iters; + document.getElementById("init_z").value = src.itermode.zinit; + document.getElementById("init_c").value = src.itermode.cinit; + } else { + document.getElementById("enable_iters").checked = false; + } + } +} + +function encodeURLParams() { + const itermode = document.getElementById("enable_iters").checked; + let params = { + expr: document.getElementById("fz").value, + axes: [ + document.getElementById("rmin").value, + document.getElementById("rmax").value, + document.getElementById("imin").value, + document.getElementById("imax").value, + ], + shading: document.getElementById("shading").value, + swapbw: document.getElementById("swapbw").checked, + contours: document.getElementById("drawcontours").checked, + }; + if(itermode) { + params.itermode = { + iters: document.getElementById("iterations").value, + zinit: document.getElementById("init_z").value, + cinit: document.getElementById("init_c").value, + } + } else { + params.itermode = false; + } + + const src = btoa(JSON.stringify(params)); + const url = window.location.origin + window.location.pathname + "?src=" + src + "&dpi=" + dpi; + document.location.href = url; +} + +function changeIterationMode() { + const checked = document.getElementById('enable_iters').checked; + document.getElementById('iteration_mode').hidden = !checked; + if(!checked) { + document.getElementById('iterations').value = 1; + document.getElementById('init_z').value = "z"; + document.getElementById('init_c').value = "0"; + } +} + +//const constantHtml = ` +// +// = +// + +// i +//` +// +//function addConstant() { +// const parse = Range.prototype.createContextualFragment.bind(document.createRange()); +// const constWrapper = document.getElementById("constants-wrapper"); +// constWrapper.appendChild(parse(constantHtml)); +//} + +window.onload = () => { + decodeURLParams(); + fetch('shaderbase.glsl') + .then(response => response.text()) + .then((data) => { + shaderBase = data; + graph(); + }); + changeIterationMode(); +}; + diff --git a/src/templates/projects/complex_grapher/peggy.sh b/src/templates/projects/complex_grapher/peggy.sh new file mode 100755 index 0000000..1e24bec --- /dev/null +++ b/src/templates/projects/complex_grapher/peggy.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +peggy -o pretty.js grammar.pegjs +#uglifyjs pretty.js > grammar.js +#rm pretty.js +mv pretty.js grammar.js diff --git a/src/templates/projects/complex_grapher/shaderbase.glsl b/src/templates/projects/complex_grapher/shaderbase.glsl new file mode 100644 index 0000000..f832478 --- /dev/null +++ b/src/templates/projects/complex_grapher/shaderbase.glsl @@ -0,0 +1,494 @@ +#version 300 es +precision highp float; +#define MAX_ITERS 1000 +#define TAU 6.283185307179586 + +#define TAU 6.283185307179586 +#define CONST_E vec2(2.718281828459045,0.) +#define CONST_PI vec2(TAU*.5,0.) +#define CONST_TAU vec2(TAU,0.) +#define CONST_EMGAMMA vec2(0.5772156649015329,0.) + +uniform highp vec2 uResolution; +uniform highp vec4 uRangeAxes; +uniform int uDrawContours; +uniform int uIterations; +uniform float uShadingIntensity; +uniform int uSwapBlackWhite; +uniform int uSmoothColor; + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +vec2 map(vec2 value, vec2 inMin, vec2 inMax, vec2 outMin, vec2 outMax) { + return outMin + (outMax - outMin) * (value - inMin) / (inMax - inMin); +} + +float valuemap(float r) { + return r*inversesqrt(r*r+0.0625*uShadingIntensity)*.975+.025; +} + +float saturationmap(float r) { + float rr = 1./r; + return rr*inversesqrt(rr*rr+0.0625*uShadingIntensity)*.975+.025; +} + +//////////////////////////////// +// Complex function library // +//////////////////////////////// + +vec2 cre(vec2 z) { + return vec2(z.x,0.); +} +vec2 cim(vec2 z) { + return vec2(z.y,0.); +} +vec2 carg(vec2 z) { + float angle = TAU*.5 - mod(TAU*.5 - atan(z.y,z.x), TAU); + return vec2(angle, 0.); +} +vec2 carg_o(float off, vec2 z) { + float angle = TAU*.5 - mod(TAU*.5 - atan(z.y,z.x) - off, TAU) - off; + return vec2(angle, 0.); +} +vec2 cabs(vec2 z) { + return vec2(sqrt(z.x*z.x + z.y*z.y),0.); +} +vec2 cnorm(vec2 z) { + return vec2(z.x*z.x + z.y*z.y,0.); +} +vec2 cconj(vec2 z) { + return vec2(z.x, -z.y); +} +vec2 cmul(vec2 z1, vec2 z2) { + return vec2(z1.x*z2.x - z1.y*z2.y, z1.x*z2.y + z1.y*z2.x); +} +vec2 cdiv(vec2 z1, vec2 z2) { + return vec2(z1.x*z2.x + z1.y*z2.y, -z1.x*z2.y + z1.y*z2.x)/(z2.x*z2.x + z2.y*z2.y); +} +vec2 crecip(vec2 z) { + return vec2(z.x, -z.y)/(z.x*z.x + z.y*z.y); +} +vec2 csquare(vec2 z) { + return vec2(z.x*z.x - z.y*z.y, 2.*z.x*z.y); +} +vec2 cexp(vec2 z) { + return exp(z.x)*vec2(cos(z.y), sin(z.y)); +} +vec2 clog(vec2 z) { + return vec2(log(cabs(z).x), carg(z).x); +} +vec2 clog_o(float off, vec2 z) { + return vec2(log(cabs(z).x), carg_o(off, z).x); +} +vec2 clogbase(vec2 z, vec2 base) { + return cdiv(clog(z),clog(base)); +} +vec2 clogbase_o(float off, vec2 z, vec2 base) { + return cdiv(clog_o(off, z),clog_o(off, base)); +} +vec2 cpow(vec2 z1, vec2 z2) { + return cexp(cmul(z2, clog(z1))); +} +vec2 croot(vec2 z1, vec2 z2) { + return cexp(cmul(crecip(z2), clog(z1))); +} +vec2 csqrt(vec2 z) { + return cexp(0.5*clog(z)); +} +vec2 csqrt_o(float off, vec2 z) { + return cexp(0.5*clog_o(off, z)); +} +vec2 csin(vec2 z) { + vec2 e1 = cexp(vec2( z.y,-z.x)); + vec2 e2 = cexp(vec2(-z.y, z.x)); + vec2 w = .5*(e1-e2); + return vec2(-w.y,w.x); +} +vec2 ccos(vec2 z) { + vec2 e1 = cexp(vec2( z.y,-z.x)); + vec2 e2 = cexp(vec2(-z.y, z.x)); + return .5*(e1 + e2); +} +vec2 ctan(vec2 z) { + vec2 e1 = cexp(vec2( z.y,-z.x)); + vec2 e2 = cexp(vec2(-z.y, z.x)); + vec2 w = cdiv(e1 - e2, e1 + e2); + return vec2(-w.y,w.x); +} +vec2 csinh(vec2 z) { + vec2 e1 = cexp(z); + vec2 e2 = cexp(-z); + return .5*(e1 - e2); +} +vec2 ccosh(vec2 z) { + vec2 e1 = cexp(z); + vec2 e2 = cexp(-z); + return .5*(e1 + e2); +} +vec2 ctanh(vec2 z) { + vec2 e1 = cexp(z); + vec2 e2 = cexp(-z); + return cdiv(e1 - e2, e1 + e2); +} +vec2 casin(vec2 z) { + vec2 v = clog( csqrt(vec2(1.,0.)-csquare(z)) + vec2(-z.y,z.x) ); + return vec2(v.y,-v.x); +} +vec2 cacos(vec2 z) { + vec2 v = clog( csqrt(vec2(1.,0.)-csquare(z)) + vec2(-z.y,z.x) ); + return CONST_TAU*.25 + vec2(-v.y,v.x); +} +vec2 catan(vec2 z) { + vec2 v = clog(cdiv(vec2(1.,0.) - vec2(-z.y,z.x), vec2(1.,0.) + vec2(-z.y,z.x))); + return .5*vec2(-v.y,v.x); +} +vec2 casin_o(float off, vec2 z) { + vec2 v = clog(csqrt_o(off, vec2(1.,0.)-csquare(z)) + vec2(-z.y,z.x) ); + return vec2(v.y,-v.x); +} +vec2 cacos_o(float off, vec2 z) { + vec2 v = clog(csqrt_o(off, vec2(1.,0.)-csquare(z)) + vec2(-z.y,z.x) ); + return CONST_TAU*.25 + vec2(-v.y,v.x); +} +vec2 casinh(vec2 z) { + return clog(csqrt(vec2(1.,0.)+csquare(z)) + z); +} +vec2 cacosh(vec2 z) { + return clog(csqrt(csquare(z)-vec2(1.,0.)) + z); +} +vec2 catanh(vec2 z) { + return 0.5*clog(cdiv(vec2(1.,0.)+z, vec2(1.,0.)-z)); +} +vec2 casinh_o(float off, vec2 z) { + return clog(csqrt_o(off, vec2(1.,0.)+csquare(z)) + z); +} +vec2 cacosh_o(float off, vec2 z) { + return clog(csqrt_o(off, csquare(z)-vec2(1.,0.)) + z); +} + +// values for gamma function +const float gammaP0 = 676.5203681218851; +const float gammaP1 = -1259.1392167224028; +const float gammaP2 = 771.32342877765313; +const float gammaP3 = -176.61502916214059; +const float gammaP4 = 12.507343278686905; +const float gammaP5 = -0.13857109526572012; +const float gammaP6 = 9.9843695780195716e-6; +const float gammaP7 = 1.5056327351493116e-7; + +vec2 gamma_inner(vec2 z) { + z -= vec2(1.,0.); + vec2 x = vec2(0.99999999999980993,0.); + x += cdiv(vec2(gammaP0,0.), z + vec2(1.,0.)); + x += cdiv(vec2(gammaP1,0.), z + vec2(2.,0.)); + x += cdiv(vec2(gammaP2,0.), z + vec2(3.,0.)); + x += cdiv(vec2(gammaP3,0.), z + vec2(4.,0.)); + x += cdiv(vec2(gammaP4,0.), z + vec2(5.,0.)); + x += cdiv(vec2(gammaP5,0.), z + vec2(6.,0.)); + x += cdiv(vec2(gammaP6,0.), z + vec2(7.,0.)); + x += cdiv(vec2(gammaP7,0.), z + vec2(8.,0.)); + vec2 t = z + vec2(7.5,0.); + return cmul( + sqrt(TAU) * cpow(t, z + vec2(0.5,0.)), + cmul(cexp(-t), x) + ); +} + +vec2 cgamma(vec2 z) { + if(z.x < 0.5) { + return cdiv(CONST_TAU*.5, cmul(csin(cmul(CONST_TAU*.5, z)), gamma_inner(vec2(1.,0.) - z))); + } else { + return gamma_inner(z); + } +} + + +vec2 cdigamma_pos(vec2 z) { + z -= vec2(.5,0); + float denom = dot(z, z); + vec2 iz = vec2(1.,-1.) * z / denom; + vec2 iz3 = vec2(z.x*z.x*z.x - 3.*z.x*z.y*z.y, z.y*z.y*z.y - 3.*z.y*z.x*z.x) / (denom*denom*denom); + return clog(z + 0.041666666666666664*iz + 0.006423611111111111*iz3); +} + +vec2 cdigamma(vec2 z) { + if(z.x > 10. || abs(z.y) > 10.) { + return cdigamma_pos(z); + } else if(z.x > -20.) { + vec2 res = cdigamma_pos(z + vec2(1000.,0.)); + for(int i = 0; i < 1001; i++) { + vec2 denom = vec2(i,0) + z; + res -= vec2(1,-1) * denom / dot(denom, denom); + } + return res; + } else { + return cdigamma_pos(vec2(1.,0.) - z) - cdiv(CONST_TAU*.5, ctan(TAU*.5 * z)); + } +} + +#define ERF_P 0.3275911 +#define ERF_A1 0.254829592 +#define ERF_A2 -0.284496736 +#define ERF_A3 1.421413741 +#define ERF_A4 -1.453152027 +#define ERF_A5 1.061405429 + +float erf_gt0(float x) { + float denom = 1. + ERF_P*x; + float denom2 = denom*denom; + return 1. - (ERF_A1/denom + ERF_A2/denom2 + ERF_A3/(denom*denom2) + ERF_A4/(denom2*denom2) + ERF_A5/(denom2*denom2*denom))*exp(-x*x); +} + +float erf(float x) { + if(x >= 0.) { + return erf_gt0(x); + } else { + return -erf_gt0(-x); + } +} + +#define CERF_A 0.5 +#define CERF_ITERS 20 +vec2 cerf_near_re(vec2 z) { + float x = z.x; + float y = z.y; + float x2 = x*x; + float exp_neg_x2 = exp(-x2); + vec2 summation = vec2(0); + for(int i = 1; i < CERF_ITERS; i++) { + float n = float(i); + float a2n2 = CERF_A*CERF_A*n*n; + float mult = exp(-a2n2)/(4.*a2n2 + 4.*x2); + float cosh_2any = cosh(2.*CERF_A*n*y); + float sinh_2any = sinh(2.*CERF_A*n*y); + float cos_2xy = cos(2.*x*y); + float sin_2xy = sin(2.*x*y); + summation += mult*vec2( + x - x*cosh_2any*cos_2xy + CERF_A*n*sinh_2any*sin_2xy, + x*cosh_2any*sin_2xy + CERF_A*n*sinh_2any*cos_2xy + ); + if(abs(y) > 8. && i > 7) { + break; + } else if(abs(y) > 4. && i > 9) { + break; + } + } + summation *= 16.*CERF_A*exp_neg_x2/TAU; + float coeff = CERF_A*exp_neg_x2/(TAU*.5*x); + summation += vec2(erf(x) + coeff*(1. - cos(2.*x*y)), coeff*sin(2.*x*y)); + return summation; +} + +#define CERF_TERMS 250 +#define RECIP_SQRT_PI 0.5641895835477563 +vec2 cerf_near_im(vec2 z) { + vec2 z2 = csquare(z); + vec2 coeff = 2. * RECIP_SQRT_PI * cmul(z, cexp(-z2)); + vec2 term = vec2(float(CERF_TERMS*2 + 1), 0); + for(int i = 0; i < CERF_TERMS; i++) { + int j = CERF_TERMS - i; + if(j < 1) { + break; + } + if((j / 2) * 2 == j) { + term = vec2(float(2*j - 1), 0.) + cdiv(float(2*j)*z2, term); + } else { + term = vec2(float(2*j - 1), 0.) - cdiv(float(2*j)*z2, term); + } + } + return cdiv(coeff, term); + +} + +vec2 cerf(vec2 z) { + if(z.y == 0.) { + return vec2(erf(z.x), 0); + } else if(abs(z.x) < abs(z.y)) { + return cerf_near_im(z); + } else { + return cerf_near_re(z); + } +} + +vec2 ceulerfn(vec2 z) { + if(cabs(z).x > 1.) { + return z*0./0.; + } + vec2 res = vec2(1,0); + vec2 w = z; + for(int i = 0; i < 600; i++) { + res = cmul(res, vec2(1,0) - w); + w = cmul(w, z); + } + return res; +} + +vec2 csignre(vec2 z) { + return vec2(sign(z.x), 0); +} + +vec2 csignim(vec2 z) { + return vec2(sign(z.y), 0); +} + +vec2 cunitcircle(vec2 z) { + return z/cabs(z).x; +} + +#define LAMBERT_W_ITERS 30 + +vec2 clambertw(vec2 z) { + vec2 res; + if(z.x < -0.3678795) { + if(z.y == 0.) { + res = vec2(z.x, 1.); + } else { + res = vec2(z.x, z.y + 0.4*sign(z.y)); + } + } else { + res = z; + } + res = clog(res + vec2(1,0)); + for(int i = 0; i < LAMBERT_W_ITERS; i++) { + res = cdiv(csquare(res) + cmul(z, cexp(-res)), res + vec2(1,0)); + } + return res; +} + +// https://www.desmos.com/calculator/eh9vx5dil4 +const float ti0 = 0.927295218002; +const float ti1 = -0.254590436003; +const float ti2 = -0.130819127994; +const float ti3 = 0.176304922654; +const float ti4 = -0.0454098453075; +const float ti5 = -0.0648283093849; +const float ti6 = 0.0695819521032; +const float ti7 = -0.00902813277778; +const float ti8 = -0.036994; +const float ti9 = 0.0321614; +const float ti10 = 0.00102442; +const float ti11 = -0.0221968; +const float ti12 = 0.0156038; +const float ti13 = 0.00369106; +const float ti14 = -0.0135653; + +const float tiOffset = -0.487222405586; + +vec2 cti_inner(vec2 z) { + z -= vec2(0.5,0); + vec2 z2 = cmul(z,z); + vec2 z3 = cmul(z2,z); + vec2 z4 = cmul(z2,z2); + vec2 z5 = cmul(z4,z); + vec2 z6 = cmul(z4,z2); + vec2 z7 = cmul(z4,z3); + vec2 z8 = cmul(z4,z4); + + return ti0*z + + ti1/2.*z2 + + ti2/3.*z3 + + ti3/4.*z4 + + ti4/5.*z5 + + ti5/6.*z6 + + ti6/7.*z7 + + ti7/8.*z8 + + ti8/9.*cmul(z8,z) + + ti9/10.*cmul(z8,z2) + + ti10/11.*cmul(z8,z3) + + ti11/12.*cmul(z8,z4) + + ti12/13.*cmul(z8,z5) + + ti13/14.*cmul(z8,z6) + + ti14/15.*cmul(z8,z7) + - vec2(tiOffset,0); +} + +vec2 cti(vec2 z) { + float m = 1.; + if(z.x < 0.) { + m = -1.; + } + if(cnorm(z).x <= 1.) { + return m*cti_inner(m*z); + } else { + return m*(TAU*.25*clog(vec2(abs(z.x), z.y*m)) + cti_inner(m*crecip(z))); + } +} + +// Weierstrass P function (ā„˜) +vec2 cweierp(vec2 z, vec2 w1, vec2 w2) { + vec2 sum = cpow(z, vec2(-2,0)); + for(int i = -25; i <= 25; i++) { + for(int j = -25; j <= 25; j++) { + if(i == 0 && j == 0) { continue; } + + vec2 lambda = float(i)*w1 + float(j)*w2; + sum += cpow(z - lambda, vec2(-2,0)) - cpow(lambda, vec2(-2,0)); + } + } + + return sum/2.; +} + +//////////////////// +// Drawing code // +//////////////////// + +// will be implemented by the input expression +vec2 f(vec2 z, vec2 c, vec2 n); +vec2 z_init(vec2 z); +vec2 c_init(vec2 z); + +out vec4 fragColor; +void main() { + float axis_size = 1./uResolution.x; + vec2 pt = map( + gl_FragCoord.xy, + vec2(0.), uResolution.xy, + uRangeAxes.xz, uRangeAxes.yw + ); + vec2 z = z_init(pt); + vec2 c = c_init(pt); + for(int i = 0; i < MAX_ITERS; i++) { + if(i >= uIterations) { + break; + } + z = f(z, c, vec2(float(i),0.)); + } + float rad = cabs(z).x; + if(rad == 0.) { + fragColor = vec4(vec3(uSwapBlackWhite),1.); + return; + } else if(isinf(rad)) { + fragColor = vec4(vec3(1-uSwapBlackWhite),1.); + return; + } else if(isnan(rad)) { + fragColor = vec4(.5,.5,.5,1.); + return; + } + float arg = carg(z).x; + float s = uSwapBlackWhite == 0 ? saturationmap(rad) : valuemap(rad); + float v = uSwapBlackWhite == 0 ? valuemap(rad) : saturationmap(rad); + if(uDrawContours == 1) { + if(abs(rad-1.) < 0.1) { + s *= 0.5; + v = clamp(v*1.5, 0., 1.); + } else if(rad > 0.1 && abs(fract(rad+0.5)-0.5) < 0.1) { + v *= 0.7; + } + } + vec3 col; + if(uSmoothColor == 1) { + float r = cos(arg)*.5 + .5; + float g = cos(arg - TAU/3.)*.5 + .5; + float b = cos(arg - 2.*TAU/3.)*.5 + .5; + vec3 hue = vec3(r,g,b); + col = hue*(1. - (1.-s) - (1.-v)) + (1.-s); + } else { + col = hsv2rgb(vec3(arg/TAU, s, v)); + } + fragColor = vec4(col,1.); +} diff --git a/src/templates/projects/complex_grapher/webgl_base.js b/src/templates/projects/complex_grapher/webgl_base.js new file mode 100644 index 0000000..7a76764 --- /dev/null +++ b/src/templates/projects/complex_grapher/webgl_base.js @@ -0,0 +1,111 @@ +const vsSource = `#version 300 es + in vec4 aVertexPosition; + void main() { + gl_Position = aVertexPosition; + } +`; + +function runShader(fsSource, options) { + const canvas = document.querySelector('#canvas'); + const gl = canvas.getContext('webgl2', {preserveDrawingBuffer:true}); + if (!gl) { + alert('could not initialize webgl.'); + return; + } + const shaderProgram = initShaderProgram(gl, vsSource, fsSource); + + gl.useProgram(shaderProgram); + + const vertexPosition = gl.getAttribLocation(shaderProgram, 'aVertexPosition'); + + uResolution = gl.getUniformLocation(shaderProgram, "uResolution"); + uRangeAxes = gl.getUniformLocation(shaderProgram, "uRangeAxes"); + uDrawContours = gl.getUniformLocation(shaderProgram, "uDrawContours"); + uShadingIntensity = gl.getUniformLocation(shaderProgram, "uShadingIntensity"); + uSwapBlackWhite = gl.getUniformLocation(shaderProgram, "uSwapBlackWhite"); + uIterations = gl.getUniformLocation(shaderProgram, "uIterations"); + uSmoothColor = gl.getUniformLocation(shaderProgram, "uSmoothColor"); + + gl.uniform2f(uResolution, gl.canvas.width, gl.canvas.height); + gl.uniform4f(uRangeAxes, options.rmin, options.rmax, options.imin, options.imax); + gl.uniform1i(uDrawContours, options.drawcontours ? 1 : 0); + gl.uniform1f(uShadingIntensity, options.shading); + gl.uniform1i(uSwapBlackWhite, options.swapbw) ? 1 : 0; + gl.uniform1i(uIterations, options.iterations); + gl.uniform1i(uSmoothColor, options.smoothcolor); + + const buffers = initBuffers(gl); + drawScene(gl, vertexPosition, buffers); +} + +function initBuffers(gl) { + const positionBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); + const positions = [ + 1.0, 1.0, + -1.0, 1.0, + 1.0, -1.0, + -1.0, -1.0, + ]; + gl.bufferData(gl.ARRAY_BUFFER, + new Float32Array(positions), + gl.STATIC_DRAW); + return { + position: positionBuffer, + }; +} + +function drawScene(gl, vertexPosition, buffers) { + gl.clearColor(0.0, 0.0, 0.0, 1.0); + gl.clearDepth(1.0); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + const projectionMatrix = mat4.create(); + const modelViewMatrix = mat4.create(); + + const numComponents = 2; + const type = gl.FLOAT; + const normalize = false; + const stride = 0; + const offset = 0; + + gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position); + gl.vertexAttribPointer( + vertexPosition, + numComponents, + type, + normalize, + stride, + offset); + gl.enableVertexAttribArray(vertexPosition); + + const vertexOffset = 0; + const vertexCount = 4; + gl.drawArrays(gl.TRIANGLE_STRIP, vertexOffset, vertexCount); +} + +function initShaderProgram(gl, vsSource, fsSource) { + const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource); + const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource); + const shaderProgram = gl.createProgram(); + gl.attachShader(shaderProgram, vertexShader); + gl.attachShader(shaderProgram, fragmentShader); + gl.linkProgram(shaderProgram); + if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { + throw Error('could not initialize shader program: ' + gl.getProgramInfoLog(shaderProgram)); + } + gl.deleteShader(vertexShader); + gl.deleteShader(fragmentShader); + return shaderProgram; +} + +function loadShader(gl, type, source) { + const shader = gl.createShader(type); + gl.shaderSource(shader, source); + gl.compileShader(shader); + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + const text = 'could not compile shaders: ' + gl.getShaderInfoLog(shader); + gl.deleteShader(shader); + throw Error(text); + } + return shader; +} diff --git a/src/templates/projects/converter/index.html b/src/templates/projects/converter/index.html new file mode 100644 index 0000000..19660c8 --- /dev/null +++ b/src/templates/projects/converter/index.html @@ -0,0 +1,34 @@ +{% set meta={"title": "Unit Converter", "desc": "Convert between units, both ordinary and esoteric"} %} +{% extends "/_base.html" %} +{% block head %} + + + +{% endblock %} +{% block content %} +

      Convert between many different units and scales of length, temparture, mass, and area

      +

      + + + = + + +
      +

      Results may be rounded or have noise in the last decimal digits. +If a result ends with a sequence of '9's, you should round up. +If it ends with '0's, you should ignore them. When converting from very +small units to very large ones, the result may be zero.

      + + +{% endblock %} \ No newline at end of file diff --git a/src/templates/projects/converter/main.js b/src/templates/projects/converter/main.js new file mode 100644 index 0000000..df0c8ee --- /dev/null +++ b/src/templates/projects/converter/main.js @@ -0,0 +1,115 @@ +Number.prototype.decimals = function() { + if(Math.floor(this.valueOf()) === this.valueOf()) return 0; + return (this.toString().split(".")[1] || '').length; +} + +Math.trueRound = function(number, precision) { + var shift = function (number, precision, reverseShift) { + if (reverseShift) { + precision = -precision; + } + var numArray = ("" + number).split("e"); + return +(numArray[0] + "e" + (numArray[1] ? (+numArray[1] + precision) : precision)); + }; + return shift(Math.round(shift(number, precision, false)), precision, true); +} + +window.onload = function() { + measureUpdate(); +} + +function measureUpdate() { + let units = length.units; + let select = length.select; + switch($('#measure').val()) { + case 'length': units = length.units; select = length.select; break; + case 'temp': units = temp.units; select = temp.select; break; + case 'mass': units = mass.units; select = mass.select; break; + case 'area': units = area.units; select = area.select; break; + case 'time': units = time.units; select = time.select; break; + case 'volume': units = volume.units; select = volume.select; break; + case 'energy': units = energy.units; select = energy.select; break; + case 'computing': units = computing.units; select = computing.select; break; + } + updateUnits(units, select); + recalc(); +} + +function updateUnits(units, select) { + $('#unit1').empty(); + $('#unit2').empty(); + s1 = units.indexOf(select[0]); + s2 = units.indexOf(select[1]); + for(let i = 0; i < units.length; i++) { + let elem = $('').val(units[i]).text(units[i]); + if(i == s1) elem = elem.attr('selected', true); + $('#unit1').append(elem); + } + for(let i = 0; i < units.length; i++) { + let elem = $('').val(units[i]).text(units[i]); + if(i == s2) elem = elem.attr('selected', true); + $('#unit2').append(elem); + } +} + +function recalc() { + let m = length; + switch($('#measure').val()) { + case 'length': m = length; break; + case 'temp': m = temp; break; + case 'mass': m = mass; break; + case 'area': m = area; break; + case 'time': m = time; break; + case 'volume': m = volume; break; + case 'energy': m = energy; break; + case 'computing': m = computing; break; + } + + let value = $('#val1').val(); + let result = 0; + + if(m.ctype == 'mul') { + + // Simple multiplication + let u1 = $('#unit1').val(), u2 = $('#unit2').val(); + let done = false; + + // Check custom conversions + for(let i = 0; i < m.custom.length; i++) { + let c = m.custom[i]; + + if(c[0] == u1 && c[1] == u2) { + result = Math.trueRound(value*c[2], 12); + done = true; + } else if(c[0] == u2 && c[1] == u1) { + result = Math.trueRound(value/c[2], 12); + done = true; + } + } + if(!done) { + + // Convert + let indexFrom = m.units.indexOf(u1), indexTo = m.units.indexOf(u2); + let fc = m.conversions[indexFrom], tc = m.conversions[indexTo]; + newv = value*fc/tc; + result = Math.trueRound(newv, 12); + } + } else if(m.ctype == 'funct') { + + // Function (for temp) + newv = m.conversions[':'+$('#unit2').val()](m.conversions[$('#unit1').val()+':'](value)) + result = Math.trueRound(newv, 8); + } + $('#val2').val(result); +} + +function swap() { + let u1 = $('#unit1').val(); + let u2 = $('#unit2').val(); + let v1 = $('#val1').val(); + let v2 = $('#val2').val(); + $('#unit1').val(u2); + $('#unit2').val(u1); + $('#val1').val(v2); + $('#val2').val(v1); +} diff --git a/src/templates/projects/converter/measures.js b/src/templates/projects/converter/measures.js new file mode 100644 index 0000000..ebd76f6 --- /dev/null +++ b/src/templates/projects/converter/measures.js @@ -0,0 +1,105 @@ +let length = { + units: ['Picometer', 'Nanometer', 'Micrometer', 'Millimeter', 'Centimeter', + 'Decimeter', 'Meter', 'Decameter', 'Hectometer', 'Kilometer', 'Megameter', + 'Gigameter', 'Barleycorn', 'Thou', 'Digit', 'Inch', 'Palm', 'Stick', 'Hand', + 'Link', 'Foot', 'Yard', 'Rod', 'Fathom', 'Chain', 'Shackle', 'Cable', 'Furlong', + 'Mile', 'Nautical Mile', 'League', 'AU', 'Light-Year', 'Smoot'], + common: 'Meter', + ctype: 'mul', + select: ['Meter', 'Foot'], + conversions: [1e-12, 1e-9, 1e-6, 0.001, 0.01, + 0.1, 1, 10, 100, 1000, 1e+6, + 1e+9, 127/15000, 0.0000254, 0.01905, 0.0254, 0.0508, 0.0762, 0.1016, + 0.201168, 0.3048, 0.9144, 5.0292, 1.852, 20.1168, 27.432, 185.2, 201.168, + 1609.344, 1852, 4828.032, 149597870700, 9460730472580800, 1.7], + custom: [['Foot', 'Inch', 12], ['Yard', 'Foot', 3], ['Mile', 'Foot', 5280], + ['Inch', 'Centimeter', 2.54], ['Mile', 'Kilometer', 1.609344]] +}; + +let temp = { + units: ['Celcius', 'Fahrenheit', 'Kelvin', 'Rankine', 'Delisle'], + common: 'Celcius', + ctype: 'funct', + select: ['Celcius', 'Fahrenheit'], + conversions: { + 'Celcius:': (n)=>{return n;}, + ':Celcius': (n)=>{return n;}, + 'Fahrenheit:': (n)=>{return (n-32)*(5/9);}, + ':Fahrenheit': (n)=>{return (n*9/5) + 32;}, + 'Kelvin:': (n)=>{return n - 273.15;}, + ':Kelvin': (n)=>{return n + 273.15;}, + 'Rankine:': (n)=>{return (n-491.67)*(5/9);}, + ':Rankine': (n)=>{return (n*9/5)+491.67;}, + 'Delisle:': (n)=>{return 100-(n*2/3);}, + ':Delisle': (n)=>{return (100-n)*3/2;}, + }, + custom: [] +}; + +let mass = { + units: ['Picogram', 'Nanogram', 'Microgram', 'Milligram', 'Centigram', 'Decigram', + 'Gram', 'Decagram', 'Hectogram', 'Kilogram', 'Metric tonne', 'Ounce', 'Pound', 'Slug', 'Stone', 'US ton', 'Solar mass', 'Earth mass'], + common: 'Kilogram', + ctype: 'mul', + select: ['Kilogram', 'Pound'], + conversions: [1e-15, 1e-12, 1e-9, 1e-6, 1e-5, 1e-4, + 0.001, 0.01, 0.1, 1, 1000, 0.028349523125, 0.45359237, + 14.593903, 6.35029318, 907.18474, 1.98855e+30, 5.9722e+24], + custom: [['US Ton', 'Pound', 2000], ['Pound', 'Ounce', 16]] +}; + +let area = { + units: ['Square millimeter', 'Square centimeter', 'Square meter', 'Hectare', + 'Square kilometer', 'Square inch', 'Square foot', 'Square yard', 'Square mile', + 'Acre'], + common: 'Square meter', + ctype: 'mul', + select: ['Square kilometer', 'Square mile'], + conversions: [1e-12, 1e-10, 1e-6, 0.01, 1, 6.4516e-10, 9.290304e-8, 8.3612736e-7, + 2.589988110336, 0.0040468564224], + custom: [] +}; + +let time = { + units: ['Microsecond', 'Millisecond', 'Second', 'Minute', 'Hour', 'Day', 'Week', + 'Fortnight', 'Siderial month', 'Lunar month', '30 Days', 'Month', 'Quarter', + 'Common year', 'Julian year', 'Year', 'Sidereal year', 'Leap Year', 'Decade', + 'Average human life', 'Century', 'Millenium'], + common: 'Day', + ctype: 'mul', + select: ['Year', 'Second'], + conversions: [1/86400000000, 1/86400000, 1/86400, 1/1440, 1/24, 1, 7, + 14, 27.321661, 29.530588, 30, 30.4166666667, 91.25, + 365, 365.25, 365.2425, 365.256363004, 366, 3652.425, + 25749.59625, 36524.25, 365242.5], + custom: [] +}; + +let volume = { + units: ['Microliter', 'Milliliter', 'Centiliter', 'Deciliter', 'Liter', + 'Decaliter', 'Hectoliter', 'Kiloliter', 'Cubic inch', 'Cubic foot', 'Cubic yard', 'Minim', 'Fluid dram', 'Teaspoon', 'Tablespoon', 'Fluid ounce', 'Shot', 'Cup', 'Pint', 'Quart', 'Gallon', 'Barrel', 'Hogshead', 'Cord'], + common: 'Milliliter', + ctype: 'mul', + select: ['Liter', 'Cup'], + conversions: [0.001, 1, 10, 100, 1000, 10000, 100000, 1000000, 16.387064, 28316.8, + 764555, 0.061611519921875, 3.6966911953125, 4.92892159375, 14.78676478125, 29.5735295625, 30, 44.36029434375, 236.5882365, 473.176473, 946.352946, 3785.411784,119240.471196, 238480.942392, 3624556.3648], + custom: [['Cubic foot', 'Cubic inch', 1728], ['Cubic yard', 'Cubic foot', 27], ['Tablespoon', 'Teaspoon', 3], ['Fluid ounce', 'Cubic inch', 1.8046875], ['Cup', 'Tablespoon', 16], ['Cup', 'Teaspoon', 48]] +}; + +let energy = { + units: ['Microjoule', 'Millijoule', 'Joule', 'Kilojoule', 'Megajoule', 'Gigajoule', 'Terajoule', 'Exajoule', 'Microwatt-hour', 'Milliwatt-hour', 'Watt-hour', 'Kilowatt-hour', 'Megawatt-hour', 'Gigawatt-hour', 'Terawatt-hour', 'Calorie', 'Kilocalorie', 'Foot-pound'], + common: 'Joule', + ctype: 'mul', + select: ['Kilowatt-hour', 'Joule'], + conversions: [1e-6, 0.001, 1, 1000, 1e6, 1e9, 1e12, 1e15, 0.0036, 3.6, 3600, 3.6e6, 3.6e9, 3.6e12, 3.6e15, 4.184, 4184, 1.35582], + custom: [] +} + +let computing = { + units: ['Bit', 'Kibibit', 'Kilobit', 'Mebibit', 'Megabit', 'Gibibit', 'Gigabit', 'Tebibit', 'Terabit', 'Pebibit', 'Petabit', 'Ebibit', 'Exabit', 'Zebibit', 'Zettabit', 'Yobibit', 'Yottabit', 'Nibble', 'Byte', 'Kibibyte', 'Kilobyte', 'Mebibyte', 'Megabyte', 'Gibibyte', 'Gigabyte', 'Tebibyte', 'Terabyte', 'Pebibyte', 'Petabyte', 'Ebibyte', 'Exabyte', 'Zebibyte', 'Zettabyte','Yobibyte', 'Yottabyte'], + common: 'Byte', + ctype: 'mul', + select: ['Gigabyte', 'Megabyte'], + conversions: [1/8, 2**7, 10**3/8, 2**17, 10**6/8, 2**27, 10**9/8, 2**37, 10**12/8, 2**47, 10**15/8, 2**57, 10**18/8, 2**67, 10**21/8, 2**77, 10**24/8, 1/2, 1, 2**10, 10**3, 2**20, 10**6, 2**30, 10**9, 2**40, 10**12, 2**50, 10**15, 2**60, 10**18, 2**70, 10*21, 2**80, 10**24], + custom: [] +} diff --git a/src/templates/projects/elementsgame/cookies.js b/src/templates/projects/elementsgame/cookies.js new file mode 100644 index 0000000..e8f8aec --- /dev/null +++ b/src/templates/projects/elementsgame/cookies.js @@ -0,0 +1,30 @@ +function loadCookie() { + let cookie = Cookies.getJSON('savedata'); + // If no cookie exists, don't try to load one + if(cookie != undefined) { + // Get found elements from cookie + let elems = cookie['list']; + if(elems != undefined) { + console.log('Loading'); + // Set found elements to cookie + found = elems; + } + numHints = cookie['hints']; + if(numHints == undefined) numHints = 1; + foundUpdated(true); + savedTime = cookie['time']; + if(savedTime == undefined) savedTime = 0; + if(savedTime > 0) nextHint = savedTime + 60000; + foundUpdated(true); + } +} + +function saveCookie() { + console.log('Saving'); + // Set the cookie + Cookies.set('savedata', { + 'list':found, + 'hints':numHints, + 'time':realMillis() + }, {expires: 30, path: ''}); +} diff --git a/src/templates/projects/elementsgame/data.js b/src/templates/projects/elementsgame/data.js new file mode 100644 index 0000000..6aa68fc --- /dev/null +++ b/src/templates/projects/elementsgame/data.js @@ -0,0 +1,317 @@ +// Classes and their color +let classes = [ + ['null', 0, 0, 0 ], + ['water', 5, 30, 140], + ['earth', 145, 90, 30 ], + ['fire', 160, 20, 5 ], + ['air', 60, 130, 200], + ['stone', 70, 70, 70 ], + ['energy', 240, 200, 20 ], + ['life', 80, 165, 40 ], + ['animal', 195, 65, 45 ], + ['human', 240, 100, 10 ], + ['death', 60, 90, 15 ], + ['space', 20, 20, 30 ], + ['wood', 70, 40, 0 ], + ['food', 110, 50, 130], + ['tools', 210, 200, 200], + ['computer',160, 160, 170] +] + +// Elements and their class +let elements = [ + ['Water', 'water'], + ['Earth', 'earth'], + ['Fire', 'fire'], + ['Air', 'air'], + ['Mud', 'earth'], + ['Lava', 'fire'], + ['Stone', 'stone'], + ['Cloud', 'air'], + ['Smoke', 'air'], + ['Steam', 'air'], + ['Sand', 'stone'], + ['Rain', 'water'], + ['Ocean', 'water'], + ['Salt', 'stone'], + ['Energy', 'energy'], + ['Earthquake', 'earth'], + ['Swamp', 'earth'], + ['Life', 'life'], + ['Algae', 'life'], + ['Bird', 'animal'], + ['Plant', 'life'], + ['Mammal', 'animal'], + ['Tree', 'life'], + ['Glass', 'stone'], + ['Brick', 'stone'], + ['Monkey', 'animal'], + ['Egg', 'life'], + ['Phoenix', 'fire'], + ['Human', 'human'], + ['Love', 'human'], + ['Clay', 'earth'], + ['Wall', 'stone'], + ['Family', 'human'], + ['House', 'human'], + ['Town', 'human'], + ['City', 'human'], + ['Metal', 'stone'], + ['Electricity', 'energy'], + ['Dust', 'earth'], + ['Lightning', 'energy'], + ['Atmosphere', 'air'], + ['Planet', 'space'], + ['Star', 'space'], + ['Ash', 'stone'], + ['Mountain', 'earth'], + ['Volcano', 'fire'], + ['Stick', 'wood'], + ['Axe', 'tools'], + ['Wood', 'wood'], + ['Campfire', 'fire'], + ['Wind', 'air'], + ['Cold', 'air'], + ['Seeds', 'life'], + ['Grass', 'life'], + ['Penguin', 'animal'], + ['Ostrich', 'animal'], + ['Chicken', 'animal'], + ['Roast Chicken', 'food'], + ['Pig', 'animal'], + ['Bacon', 'food'], + ['Ham', 'food'], + ['Cow', 'animal'], + ['Steak', 'food'], + ['Wheat', 'life'], + ['Flour', 'food'], + ['Dough', 'food'], + ['Bread', 'food'], + ['Toast', 'food'], + ['Blade', 'tools'], + ['Knife', 'tools'], + ['Shavings', 'wood'], + ['Wood Pulp', 'wood'], + ['Paper', 'tools'], + ['Ice', 'water'], + ['Snow', 'water'], + ['Fish', 'animal'], + ['Whale', 'animal'], + ['Sheep', 'animal'], + ['Worker', 'human'], + ['Blacksmith', 'human'], + ['Shepherd', 'human'], + ['Farmer', 'human'], + ['Wire', 'computer'], + ['Robot', 'computer'], + ['Computer', 'computer'], + ['Network', 'computer'], + ['Internet', 'computer'], + ['Pet', 'animal'], + ['Cat', 'animal'], + ['Dog', 'animal'], + ['Meme', 'computer'], + ['Corpse', 'death'], + ['Bone', 'death'], + ['Zombie', 'death'], + ['Alien', 'death'], + ['Rainbow', 'air'], + ['Unicorn', 'animal'], + ['Bonfire', 'fire'], + ['Storm', 'air'], + ['Hurricane', 'air'], + ['Tornado', 'air'], + ['Potato', 'food'], + ['Fruit', 'life'], + ['Apple', 'food'], + ['Watermelon', 'food'], + ['Eggplant', 'food'], + ['Crab', 'animal'], + ['Milk', 'food'], + ['Butter', 'food'], + ['Forest', 'life'], + ['Jungle', 'life'], + ['Banana', 'food'], + ['Skyscraper', 'human'], + ['Goldfish', 'animal'], + ['Wheel', 'wood'], + ['Car', 'computer'], + ['Book', 'tools'], + ['Children', 'human'], + ['School', 'human'] +] + +// Recipes: two inputs and some outputs +let recipes = [ + ['Water', 'Earth', ['Mud']], + ['Fire', 'Earth', ['Lava']], + ['Lava', 'Water', ['Stone']], + ['Air', 'Water', ['Cloud']], + ['Air', 'Fire', ['Smoke']], + ['Stone', 'Water', ['Sand']], + ['Water', 'Cloud', ['Rain']], + ['Water', 'Water', ['Ocean']], + ['Ocean', 'Fire', ['Salt']], + ['Water', 'Fire', ['Steam', 'Energy']], + ['Energy', 'Earth', ['Earthquake']], + ['Mud', 'Water', ['Swamp']], + ['Swamp', 'Energy', ['Life']], + ['Life', 'Water', ['Algae']], + ['Life', 'Air', ['Bird']], + ['Algae', 'Earth', ['Plant']], + ['Life', 'Earth', ['Mammal']], + ['Plant', 'Air', ['Tree']], + ['Sand', 'Fire', ['Glass']], + ['Clay', 'Fire', ['Brick']], + ['Tree', 'Mammal', ['Monkey']], + ['Monkey', 'Fire', ['Human']], + ['Bird', 'Stone', ['Egg']], + ['Bird', 'Fire', ['Phoenix']], + ['Monkey', 'Fire', ['Human']], + ['Human', 'Human', ['Love']], + ['Mud', 'Swamp', ['Clay']], + ['Brick', 'Brick', ['Wall']], + ['Human', 'Love', ['Family']], + ['Wall', 'Family', ['House']], + ['House', 'House', ['Town']], + ['Town', 'Town', ['City']], + ['Fire', 'Stone', ['Metal']], + ['Metal', 'Energy', ['Electricity']], + ['Air', 'Earth', ['Dust']], + ['Fire', 'Air', ['Smoke']], + ['Energy', 'Air', ['Lightning']], + ['Air', 'Air', ['Atmosphere']], + ['Atmosphere', 'Earth', ['Planet']], + ['Planet', 'Fire', ['Star']], + ['Tree', 'Fire', ['Ash']], + ['Plant', 'Fire', ['Ash']], + ['Earth', 'Earth', ['Mountain']], + ['Lava', 'Mountain', ['Volcano']], + ['Tree', 'Stone', ['Stick']], + ['Stick', 'Stone', ['Axe']], + ['Tree', 'Axe', ['Wood']], + ['Wood', 'Fire', ['Campfire', 'Smoke']], + ['Air', 'Atmosphere', ['Wind']], + ['Wind', 'Water', ['Cold']], + ['Life', 'Sand', ['Seeds']], + ['Seeds', 'Earth', ['Grass']], + ['Bird', 'Cold', ['Penguin']], + ['Bird', 'Sand', ['Ostrich']], + ['Earth', 'Bird', ['Chicken']], + ['Fire', 'Chicken',['Roast Chicken']], + ['Mammal', 'Mud', ['Pig']], + ['Pig', 'Fire', ['Ham']], + ['Pig', 'Smoke', ['Bacon']], + ['Mammal', 'Grass', ['Cow']], + ['Cow', 'Fire', ['Steak']], + ['Grass', 'Air', ['Wheat']], + ['Wheat', 'Stone', ['Flour']], + ['Flour', 'Water', ['Dough']], + ['Dough', 'Fire', ['Bread']], + ['Bread', 'Fire', ['Toast']], + ['Metal', 'Stone', ['Blade']], + ['Blade', 'Stick', ['Knife']], + ['Knife', 'Wood', ['Shavings']], + ['Water', 'Shavings', ['Wood Pulp']], + ['Fire', 'Wood Pulp', ['Paper']], + ['Cold', 'Water', ['Ice']], + ['Cold', 'Rain', ['Snow']], + ['Ocean', 'Life', ['Fish']], + ['Ocean', 'Mammal', ['Whale']], + ['Mammal', 'Cloud', ['Sheep']], + ['Human', 'Axe', ['Worker']], + ['Worker', 'Metal', ['Blacksmith']], + ['Worker', 'Sheep', ['Shepherd']], + ['Worker', 'Wheat', ['Farmer']], + ['Metal', 'Electricity', ['Wire']], + ['Wire', 'Wire', ['Computer']], + ['Human', 'Computer', ['Robot']], + ['Computer', 'Computer', ['Network']], + ['Network','Network',['Internet']], + ['Human', 'Mammal', ['Pet']], + ['Pet', 'Milk', ['Cat']], + ['Pet', 'Bone', ['Dog']], + ['Cat', 'Internet', ['Meme']], + ['Human', 'Fire', ['Corpse']], + ['Corpse', 'Fire', ['Bone', 'Ash']], + ['Corpse', 'Life', ['Zombie']], + ['Planet', 'Life', ['Alien']], + ['Star', 'Rain', ['Rainbow']], + ['Rainbow','Mammal', ['Unicorn']], + ['Fire', 'Fire', ['Bonfire']], + ['Rain', 'Rain', ['Storm']], + ['Storm', 'Wind', ['Hurricane']], + ['Hurricane','Wind', ['Tornado']], + ['Plant', 'Earth', ['Potato']], + ['Plant', 'Plant', ['Fruit']], + ['Fruit', 'Tree', ['Apple']], + ['Fruit', 'Water', ['Watermelon']], + ['Fruit', 'Egg', ['Eggplant']], + ['Fish', 'Earth', ['Crab']], + ['Cow', 'Water', ['Milk']], + ['Milk', 'Energy', ['Butter']], + ['Tree', 'Tree', ['Forest']], + ['Forest', 'Monkey', ['Jungle']], + ['Fruit', 'Monkey', ['Banana']], + ['Metal', 'City', ['Skyscraper']], + ['Fish', 'Pet', ['Goldfish']], + ['Energy', 'Wood', ['Wheel']] + ['Wheel', 'Metal', ['Car']], + ['Paper', 'Paper', ['Book']], + ['Family', 'Love', ['Children']], + ['House', 'Children', ['School']] +] + +// Get the outputs when two elements are combined +function getOutputs(e1, e2) { + for(let r = 0; r < recipes.length; r++) { + if((recipes[r][0] == e1 && recipes[r][1] == e2) || (recipes[r][0] == e2 && recipes[r][1] == e1)) { + return recipes[r][2]; + } + } + return null; +} + +// Get the color of a class +function getColor(clazz) { + for(let c = 0; c < classes.length; c++) { + if(classes[c][0] == clazz) { + return [classes[c][1], classes[c][2], classes[c][3]]; + } + } +} + +// Get the class of an element +function getClass(element) { + let clazz = classes[0][0]; + for(let e = 0; e < elements.length; e++) { + if(elements[e][0] == element) { + clazz = elements[e][1] + } + } + return clazz; +} + +// The help text shown when 'q' is pressed +let helpText = +'Click on an element to select it and then click' + +' on another one to combine them. Try to get all ' + +elements.length + '!' + +'\n \n' + +'A hint will select an element that you can combine' + +' with another element you have to create something' + +' you do not have.' + +'\n \n' + +'Sorting modes will sort your elements either by' + +' order added, alphabetically, by class, or randomly.' + +'\n \n' + +'Some elements can be combined with themselves. Some' + +' elements cannot be used in any combinations.' + +'\n' + +'\n Q - View this help message' + +'\n M - Change sorting mode' + +'\n T - Show/hide time' + +'\n H - Use a hint' + +'\n Enter - Recalculate' + +'\n Mouse Wheel - Scrolling' + +'\n X - Clear savedata'; diff --git a/src/templates/projects/elementsgame/index.html b/src/templates/projects/elementsgame/index.html new file mode 100644 index 0000000..239aa6c --- /dev/null +++ b/src/templates/projects/elementsgame/index.html @@ -0,0 +1,11 @@ +{% set meta={"title": "Elements Game", "desc": "Combine elements to create more elements (now with 20% more bugs!)"} %} +{% extends "/_base.html" %} +{% block head %} + + + + + + + +{% endblock %} diff --git a/src/templates/projects/elementsgame/inputs.js b/src/templates/projects/elementsgame/inputs.js new file mode 100644 index 0000000..5ccb4ea --- /dev/null +++ b/src/templates/projects/elementsgame/inputs.js @@ -0,0 +1,54 @@ +// When mouse is clicked +function mouseClicked() { + // Get index of element clicked + let i = getIndexOfCoords(mouseX, mouseY); + if(curIndex == -1 && i < found.length && i >= 0) { + // If no element is selected and an element was clicked on, select the element that was just clicked + curIndex = i; + } else if(i < found.length && i >= 0) { + // If an element is selected and an element was clicked on, combine them + combine(sortedFound[curIndex], sortedFound[i]); + curIndex = -1; + } else { + // If the empty backgound was clicked, clear the selected element + curIndex = -1; + } +} + +// Scroll the elements +function mouseWheel(event) { + if(event.delta > 0) + offset++; + if(event.delta < 0 && offset > 0) + offset--; + return false; +} + +function keyTyped() { + if(key == 'q') { + // Show help text + window.alert(helpText); + } else if(key == 'm') { + // Change mode + nextSortMode(); + foundUpdated(); + } else if(key == 't') { + // Show/hide stopwatch + showTime = !showTime; + } else if(key == 'h') { + // Try to give a hint + if(numHints > 0) { + getHint(); + numHints--; + saveCookie(); + } + } else if(key == 'x') { + Cookies.remove('savedata', {path: ''}); + clearText = true; + } else if(keyCode == ENTER) { + // Recalculate + foundUpdated(); + clearText = false; + } + return false; +} diff --git a/src/templates/projects/elementsgame/sketch.js b/src/templates/projects/elementsgame/sketch.js new file mode 100644 index 0000000..33db365 --- /dev/null +++ b/src/templates/projects/elementsgame/sketch.js @@ -0,0 +1,177 @@ +// Initial elements the user starts with +let found = ['Water', 'Earth', 'Fire', 'Air']; +// Sorted list of elements +let sortedFound = found; +// Number of hints available +let numHints = 1; +// Next number of milliseconds after start to give new hint at +let nextHint = 60000; +// Saved time for cookies +let savedTime = 0; + +// Index of selected element +let curIndex = -1; +// Scroll offset +let offset = 0; +// Whether or not to show the stopwatch +let showTime = true; +// +let clearText = false; + +function setup() { + createCanvas(600, 600); + loadCookie(); +} + +function draw() { + // Black background + background(0); + if(clearText) { + noStroke(); + fill(255, 0, 0) + textSize(22); + textFont('Calibri'); + textAlign(CENTER, CENTER); + text('Savedata cleared. Please reload the page.\nIf this was a mistake, press ENTER', width/2, height/2); + return; + } + if(frameCount == 0) { + foundUpdated(); + } + + noStroke(); + manageHints(); + drawFound(); + showText(); +} + +function manageHints() { + if(numHints < 5) { + if(realMillis() >= nextHint) { + numHints++; + nextHint = realMillis() + 60000; + saveCookie(); + } + } else { + nextHint = realMillis() + 60000; + } +} + +function showText() { + textSize(18); + textAlign(CENTER, CENTER); + // Show number of elements found + text('Found: ' + found.length + ' / ' + elements.length, 300, 15); + textAlign(LEFT, CENTER); + // Show current sorting mode + text('Sort Mode: ' + getSortModeName(), 3, 585); + textAlign(RIGHT, CENTER); + // Show help message + text('Press Q for Help', 597, 585); + // Show stopwatch if supposed to + if(showTime) { + textAlign(RIGHT, CENTER); + let min = ('0' + floor(realMillis()/60000)).slice(-2); + let sec = ('0' + floor((realMillis()/1000)%60)).slice(-2); + text('Time: ' + min + ':' + sec, 597, 15); + } + textAlign(CENTER, CENTER); + // Show number of hints and time until next hint + let timeText = (numHints >= 5) ? '' : ' (' + ceil((nextHint - realMillis())/1000) + ' sec)'; + text('Hints: ' + numHints + timeText, 300, 585); + if(clearText) { + background(0) + textAlign(CENTER, CENTER); + text('Savedata cleared. Please reload the page '); + } +} + +function drawFound() { + textSize(14); + textFont('Calibri'); + textAlign(CENTER, CENTER); + for(let i = 0; i < found.length; i++) { + // Calculate grid-y coordinate + let gridy = floor(i / 9) - offset + // Draw the element at the grid-x and grid-y positions if it's onscreen + if(gridy >= 0 && gridy < 9) + drawElement(i % 9, gridy, sortedFound[i]); + } +} + +// Size of elements +let es = 50; +function drawElement(x, y, elem) { + // Calculate real x and y coordinates from grid-x and grid-y + let realx = x*es*1.25 + es; + let realy = y*es*1.25 + es; + // Draw a white circle around the selected element + if(sortedFound[curIndex] == elem) { + fill(240); + ellipse(realx, realy, es*1.1); + } + // Draw the element as a colored circle + fill(getColor(getClass(elem))); + ellipse(realx, realy, es); + // Show the name of the element + fill(255); + text(elem, realx, realy); +} + +// Combine two elements and add the outcome to the found list +function combine(e1, e2) { + let e3 = getOutputs(e1, e2); + if(e3 != null) { + for(let i = 0; i < e3.length; i++) { + if(found.indexOf(e3[i]) < 0) { + found.push(e3[i]); + } + } + } + foundUpdated(); +} + +// Convert real-x and real-y to an index. Returns -1 if invalid +function getIndexOfCoords(x, y) { + let ex = round((x - es)/(es*1.25)); + let ey = round((y - es)/(es*1.25)); + if(ex < 0 || ey < 0 || ex > 8 || ey > 8) { + return -1; + } + return ex + (ey + offset)*9; +} + +// Selects an element that can be used right now +function getHint() { + // If all elements have been found + if(found.length >= elements.length) return false; + // Select random elements until one works + let e1 = random(found); + while(true) { + // Go through each of the elements + for(let i = 0; i < found.length; i++) { + let outputs = getOutputs(e1, found[i]); + let hasOutput = false; + for(let i = 0; outputs != null && i < outputs.length; i++) { + if(found.indexOf(outputs[i]) < 0) hasOutput = true; + } + // If the random element can be combined with another element to produce new + // results, select it and return + if(outputs != null && hasOutput) { + curIndex = found.indexOf(e1); + return true; + } + } + e1 = random(found); + } +} + +function realMillis() { + return millis() + savedTime; +} + +// When adding, removing, or changing found elements always call this immediately after +function foundUpdated() { + sortedFound = aSort(found); + saveCookie(); +} diff --git a/src/templates/projects/elementsgame/sorting.js b/src/templates/projects/elementsgame/sorting.js new file mode 100644 index 0000000..bbf911b --- /dev/null +++ b/src/templates/projects/elementsgame/sorting.js @@ -0,0 +1,83 @@ +// Current sort mode +let sortMode = 0; + +// Go to next sort mode +function nextSortMode() { + sortMode = (sortMode + 1) % 7; + +} + +// Sort array based on current sort mode +function aSort(arr) { + newArr = arr.slice(0); + switch(sortMode) { + case 0: + return creationSort(newArr); + case 1: + return creationSort(newArr).reverse(); + case 2: + return nameSort(newArr); + case 3: + return nameSort(newArr).reverse(); + case 4: + return classSort(newArr); + case 5: + return classSort(newArr).reverse(); + case 6: + return randomSort(newArr); + } +} + +// Get the name of the current sort mode +function getSortModeName() { + switch(sortMode) { + case 0: + return 'Creation Time'; + case 1: + return 'Creation Time (Reverse)'; + case 2: + return 'Name'; + case 3: + return 'Name (Reverse)'; + case 4: + return 'Class'; + case 5: + return 'Class (Reverse)'; + case 6: + return 'Random'; + } + return 'Mode ' + mode; +} + +// Sort in order of the list +function creationSort(arr) { + return arr; +} + +// Sort alphabetically +function nameSort(arr) { + arr.sort() + return arr; +} + +// Sort by class +function classSort(arr) { + arr.sort(); + arr.sort(function(a, b) { + let ac = getClass(a), bc = getClass(b); + if(ac > bc) { + return 1; + } else if(ac == bc) { + return 0; + } else return -1; + }) + return arr; +} + +// Sort randomly +function randomSort(arr) { + arr.sort(function(a, b) { + return random() - 0.5; + }) + return arr; +} diff --git a/src/templates/projects/flappy/assets.js b/src/templates/projects/flappy/assets.js new file mode 100644 index 0000000..2f1356b --- /dev/null +++ b/src/templates/projects/flappy/assets.js @@ -0,0 +1,23 @@ +function loadAssets() { + loadImage('/static/i/flappy/background1.png', function(img) { + bgImage1 = img; + console.log('Loaded image assets/background1.png') + }, function(evt) { + console.error('Failed to load image assets/background1.jpg: ', evt); + bgImage1 = createImage(160, 32); + }); + loadImage('/static/i/flappy/background2.png', function(img) { + bgImage2 = img; + console.log('Loaded image assets/background2.png') + }, function(evt) { + console.error('Failed to load image assets/background2.jpg: ', evt); + bgImage2 = createImage(160, 32); + }); + loadImage('/static/i/flappy/bird.png', function(img) { + birdImage = img; + console.log('Loaded image assets/bird.png') + }, function(evt) { + console.error('Failed to load image assets/bird.jpg: ', evt); + birdImage = createImage(32, 32); + }); +} diff --git a/src/templates/projects/flappy/bird.js b/src/templates/projects/flappy/bird.js new file mode 100644 index 0000000..adb2387 --- /dev/null +++ b/src/templates/projects/flappy/bird.js @@ -0,0 +1,30 @@ +function Bird() { + this.width = birdHitboxSize; + this.height = birdHitboxSize; + this.iconwidth = birdDrawSize; + this.iconheight = birdDrawSize; + this.pos = height/2-this.height/2; + this.vel = 0; + this.acc = initialAcceleration; + this.xpos = birdXPosition; + + this.show = function() { + image(birdImage, this.xpos, this.pos, this.iconwidth, this.iconheight); + } + + this.update = function() { + this.vel += this.acc; + this.vel = constrain(this.vel, -termVel, termVel); + this.pos += this.vel; + this.acc = 0; + } + + this.applyForce = function(force, type) { + if(type === 'flap' && cancelVelocity == 1) { + this.vel = 0; + this.acc = force; + } else { + this.acc += force; + } + } +} diff --git a/src/templates/projects/flappy/block.js b/src/templates/projects/flappy/block.js new file mode 100644 index 0000000..f36750c --- /dev/null +++ b/src/templates/projects/flappy/block.js @@ -0,0 +1,25 @@ + +function Block() { + this.x = width+capWidth; + this.y = height-(random(height-blockSize)+blockSize); + this.passed = false; + + this.show = function() { + stroke(0); + strokeWeight(3); + fill(100, 200, 100); + rect(this.x, this.y, blockSize, blockSize); + if(this.x + blockSize < 0) return true; + return false; + } + + this.intersects = function(b) { + if(b.xpos+b.width > this.x && b.xpos < this.x+blockSize) { + if(!this.passed) { + this.passed = true; + score++; + } + return (b.pos+b.width > this.y && b.pos < this.y+blockSize); + } + } +} diff --git a/src/templates/projects/flappy/data.js b/src/templates/projects/flappy/data.js new file mode 100644 index 0000000..13b9024 --- /dev/null +++ b/src/templates/projects/flappy/data.js @@ -0,0 +1,233 @@ +let gravity = 1.2; +let termVel = 25; +let flapForce = -22; +let birdDrawSize = 60; +let birdHitboxSize = 60; + +let pipeGap = 240; +let pipeCap = 120; +let pipeWidth = 130; +let capWidth = 200; +let blockSize = 360; + +let framesBetweenObstacle = 240; +let scrollSpeed = 1; +let blockChance = 0.33333; +let birdXPosition = 300 +let initialAcceleration = -3; + +let cancelVelOptions = ['No', 'Yes'] +let cancelVelocity = 0; + +let guiElems = []; +let activeElement = undefined; + +function toggleVelOptions(element, mouseButton) { + cancelVelocity = 1 - cancelVelocity + element.text = cancelVelOptions[cancelVelocity]; +} + +function flipGravity(element, mouseButton) { + guiElems[2].text = ''+-1*guiElems[2].text; + guiElems[6].text = ''+-1*guiElems[6].text; + guiElems[30].text = ''+-1*guiElems[30].text; +} + +function saveAndExit(element, mouseButton) { + gravity = 1*guiElems[2].text; + termVel = 1*guiElems[4].text; + flapForce = 1*guiElems[6].text; + birdDrawSize = 1*guiElems[8].text; + birdHitboxSize = 1*guiElems[10].text; + pipeGap = 1*guiElems[12].text; + pipeCap = 1*guiElems[14].text; + pipeWidth = 1*guiElems[16].text; + capWidth = 1*guiElems[18].text; + blockSize = 1*guiElems[20].text; + framesBetweenObstacle = 1*guiElems[22].text; + scrollSpeed = 1*guiElems[24].text; + blockChance = 1*guiElems[26].text; + birdXPosition = 1*guiElems[28].text; + initialAcceleration = 1*guiElems[30].text; + gamestate = 'start'; +} + +function initMenu() { + guiElems[0] = new Button(50, height-90, width-100, 40, 'Save & Exit', saveAndExit); + guiElems[1] = new Label (50, 50, 140, 40, 'Gravity: '); + guiElems[2] = new Textbox (250, 50, 200, 40, gravity); + guiElems[3] = new Label (50, 100, 140, 40, 'Terminal velocity: '); + guiElems[4] = new Textbox (250, 100, 200, 40, termVel); + guiElems[5] = new Label (50, 150, 140, 40, 'Flap force: '); + guiElems[6] = new Textbox (250, 150, 200, 40, flapForce); + guiElems[7] = new Label (50, 200, 140, 40, 'Bird icon size: '); + guiElems[8] = new Textbox (250, 200, 200, 40, birdDrawSize); + guiElems[9] = new Label (50, 250, 140, 40, 'Bird hitbox size: '); + guiElems[10] = new Textbox(250, 250, 200, 40, birdHitboxSize); + + guiElems[11] = new Label (50, 300, 140, 40, 'Pipe gap: '); + guiElems[12] = new Textbox(250, 300, 200, 40, pipeGap); + guiElems[13] = new Label (50, 350, 140, 40, 'Pipe cap size: '); + guiElems[14] = new Textbox(250, 350, 200, 40, pipeCap); + guiElems[15] = new Label (50, 400, 140, 40, 'Pipe width: '); + guiElems[16] = new Textbox(250, 400, 200, 40, pipeWidth); + guiElems[17] = new Label (50, 450, 140, 40, 'Cap width: '); + guiElems[18] = new Textbox(250, 450, 200, 40, capWidth); + guiElems[19] = new Label (50, 500, 140, 40, 'Block size: '); + guiElems[20] = new Textbox(250, 500, 200, 40, blockSize); + + guiElems[21] = new Label (550, 50, 140, 40, 'Frames between obstacle: '); + guiElems[22] = new Textbox(750, 50, 200, 40, framesBetweenObstacle); + guiElems[23] = new Label (550, 100, 140, 40, 'Scroll speed: '); + guiElems[24] = new Textbox(750, 100, 200, 40, scrollSpeed); + guiElems[25] = new Label (550, 150, 140, 40, 'Block chance: '); + guiElems[26] = new Textbox(750, 150, 200, 40, blockChance); + guiElems[27] = new Label (550, 200, 140, 40, 'Bird X position: '); + guiElems[28] = new Textbox(750, 200, 200, 40, birdXPosition); + guiElems[29] = new Label (550, 250, 140, 40, 'Initial acceleration: '); + guiElems[30] = new Textbox(750, 250, 200, 40, initialAcceleration); + + guiElems[31] = new Label (575, 300, 140, 40, 'Cancel velocity when flapping: '); + guiElems[32] = new Button(850, 300, 100, 40, cancelVelOptions[cancelVelocity], toggleVelOptions); + guiElems[33] = new Button(525, 450, 405, 40, 'Flip gravity', flipGravity); +} + +function drawMenu(width, height) { + background(25); + for(const elem of guiElems) { + elem.show(); + } +} + +function menuKey(key, keyCode) { + if(activeElement) { + activeElement.onKeyType(key, keyCode); + } +} + +function menuPress(mouseX, mouseY, button) { + for(const elem of guiElems) { + if(mouseX >= elem.x && mouseY >= elem.y + && mouseX < (elem.x + elem.w) && mouseY < (elem.y + elem.h)) { + elem.onPress(mouseX, mouseY, mouseX - elem.x, mouseY - elem.y, button); + return; + } + } +} +function menuRelease(mouseX, mouseY, button) { + for(const elem of guiElems) { + if(mouseX >= elem.x && mouseY >= elem.y + && mouseX < (elem.x + elem.w) && mouseY < (elem.y + elem.h)) { + elem.onRelease(mouseX, mouseY, mouseX - elem.x, mouseY - elem.y, button); + return; + } + } +} +function menuClick(mouseX, mouseY, button) { + for(const elem of guiElems) { + if(mouseX >= elem.x && mouseY >= elem.y + && mouseX < (elem.x + elem.w) && mouseY < (elem.y + elem.h)) { + elem.onClick(mouseX, mouseY, mouseX - elem.x, mouseY - elem.y, button); + return; + } + } + activeElement = undefined; +} + +class GUIElement { + constructor(x, y, w, h) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + this.enabled = true; + this.pressed = false; + } + onPress(absX, absY, relX, relY, mouseButton) { this.pressed = true; } + onRelease(absX, absY, relX, relY, mouseButton) { this.pressed = false; } + onClick(absX, absY, relX, relY, mouseButton) { activeElement = this; } + onKeyType(key, keyCode) { } + isActive() { return false; } + setEnabled(enabled) { this.enabled = enabled; } + show() { } +} + +class Button extends GUIElement{ + constructor(x, y, w, h, text, onClickFunc) { + super(x, y, w, h); + this.text = text; + this.onClickFunc = onClickFunc; + } + onClick(absX, absY, relX, relY, mouseButton) { + activeElement = this; + this.onClickFunc(this, mouseButton) + } + show() { + stroke(255); + strokeWeight(3); + fill(this.pressed ? 25 : 120); + rect(this.x, this.y, this.w, this.h); + textSize(18); + textAlign(CENTER, CENTER); + noStroke(); + fill(255); + text(this.text, this.x+this.w/2, this.y+this.h/2) + } +} + +class Label extends GUIElement{ + constructor(x, y, w, h, text,) { + super(x, y, w, h); + this.text = text; + } + show() { + textSize(18); + textAlign(CENTER, CENTER); + noStroke(); + fill(255); + text(this.text, this.x+this.w/2, this.y+this.h/2) + } +} + +class Textbox extends GUIElement{ + constructor(x, y, w, h, text) { + super(x, y, w, h); + this.text = '' + text + ''; + this.cursor = 0; + } + onKeyType(key, keyCode) { + if(keyCode == 8) { + if(cursor != 0) { + this.text = this.text.slice(0, this.cursor-1) + this.text.slice(this.cursor) + this.cursor = Math.min(this.text.length, this.cursor = Math.max(0, this.cursor)) + } + } else if(keyCode == 37) { + this.cursor = Math.max(0, this.cursor-1) + } else if(keyCode == 39) { + this.cursor = Math.min(this.text.length, this.cursor+1) + } else if(key.length == 1) { + this.text = this.text.slice(0, this.cursor) + key + this.text.slice(this.cursor) + this.cursor += 1; + } + } + onClick(absX, absY, relX, relY, mouseButton) { + activeElement = this; + } + show() { + stroke(255); + strokeWeight(3); + fill(25); + rect(this.x, this.y, this.w, this.h); + textSize(18); + textAlign(CENTER, CENTER); + noStroke(); + fill(255); + let displayText = this.text; + if(activeElement === this) { + displayText = this.text.slice(0, this.cursor) + + (millis()%1000 > 500 ? 'ā”‚' : ' ') + + this.text.slice(this.cursor); + } + text(displayText, this.x+this.w/2, this.y+this.h/2) + } +} \ No newline at end of file diff --git a/src/templates/projects/flappy/index.html b/src/templates/projects/flappy/index.html new file mode 100644 index 0000000..6ef7767 --- /dev/null +++ b/src/templates/projects/flappy/index.html @@ -0,0 +1,13 @@ +{% set meta={"title": "Flappy", "desc": "Flappy", "fullscreen": True} %} +{% extends "/_base.html" %} +{% block head %} + + + + + + + + + +{% endblock %} \ No newline at end of file diff --git a/src/templates/projects/flappy/input.js b/src/templates/projects/flappy/input.js new file mode 100644 index 0000000..e075468 --- /dev/null +++ b/src/templates/projects/flappy/input.js @@ -0,0 +1,38 @@ +// Prevent backspace key from going back in page history +$(document).unbind('keydown').bind('keydown', function (event) { + if (event.keyCode === 8) { + var doPrevent = true; + var types = ["text", "password", "file", "search", "email", "number", "date", "color", "datetime", "datetime-local", "month", "range", "search", "tel", "time", "url", "week"]; + var d = $(event.srcElement || event.target); + var disabled = d.prop("readonly") || d.prop("disabled"); + if (!disabled) { + if (d[0].isContentEditable) { + doPrevent = false; + } else if (d.is("input")) { + var type = d.attr("type"); + if (type) { + type = type.toLowerCase(); + } + if (types.indexOf(type) > -1) { + doPrevent = false; + } + } else if (d.is("textarea")) { + doPrevent = false; + } + } + return !doPrevent; + } +}); + +document.addEventListener('keydown', keyDown); + +function keyDown(e) { + if(gamestate == 'menu') { + menuKey(e.key, e.keyCode); + } + else if(e.key == ' ' && gamestate != 'dead' && gamestate != 'start') + bird.applyForce(flapForce, 'flap'); + else if(e.keyCode == 13 && (gamestate == 'dead' || gamestate == 'start')) { + newGame() + } +} \ No newline at end of file diff --git a/src/templates/projects/flappy/main.js b/src/templates/projects/flappy/main.js new file mode 100644 index 0000000..b8818a2 --- /dev/null +++ b/src/templates/projects/flappy/main.js @@ -0,0 +1,167 @@ +var bgImage1; +var bgImage2; +var birdImage; +var bird; +var pipes = []; +var blocks = []; + +var gamestate = 'ld'; +var bscroll1 = 0; +var bscroll2 = 0; +var score = 0; +var frame = 0; + +function setup() { + var canvas = createCanvas(windowWidth, windowHeight).elt; + var context = canvas.getContext('2d'); + context.mozImageSmoothingEnabled = false; + context.webkitImageSmoothingEnabled = false; + context.msImageSmoothingEnabled = false; + context.imageSmoothingEnabled = false; + textFont('Courier New'); + frameRate(30); + initMenu(); + loadAssets(); +} + +function draw() { + if(gamestate == 'ld') { + drawLoading(); + } else if(gamestate == 'game') { + background(25); + var imageWidth = bgImage1.width*(height/bgImage1.height); + drawBackground(imageWidth); + doBird(); + pipesAndBlocks(); + showScore(); + bscroll1 += 2*scrollSpeed; + bscroll1 %= imageWidth; + bscroll2 += 1*scrollSpeed; + bscroll2 %= imageWidth; + frame++; + } else if(gamestate == 'dead' || gamestate == 'start') { + background(25); + textAlign(CENTER, CENTER); + noStroke(); + fill(255); + textSize(100); + if(gamestate == 'dead') { + text('You died!', width/2, height/2-150); + textSize(50); + text('Score: ' + score, width/2, height/2); + text('Play', width/2-width/7, height/2+150); + text('Options', width/2+width/7, height/2+150); + } else if(gamestate == 'start') { + text('Flappy T', width/2, height/2-150); + textSize(50); + text('Play', width/2-width/7, height/2+150); + text('Options', width/2+width/7, height/2+150); + } + } else if(gamestate == 'menu') { + drawMenu(); + } +} + +function drawBackground(imageWidth) { + image(bgImage2, -bscroll2, 0, imageWidth, height); + image(bgImage2, -bscroll2+imageWidth, 0, imageWidth, height); + image(bgImage1, -bscroll1, 0, imageWidth, height); + image(bgImage1, -bscroll1+imageWidth, 0, imageWidth, height); +} + +function doBird() { + bird.applyForce(gravity, 'grav'); + bird.update(); + bird.show(); + if(bird.pos > height) gamestate = 'dead'; + if(bird.pos < -bird.height) gamestate = 'dead'; +} + +function pipesAndBlocks() { + if(frame % framesBetweenObstacle == 0) { + if(random(1) < blockChance) + blocks.push(new Block()); + else + pipes.push(new Pipe()); + } + for(var i = 0; i < pipes.length; i++) { + pipes[i].x -= 3*scrollSpeed; + var p = pipes[i].show(); + if(pipes[i].intersects(bird)) gamestate = 'dead'; + if(p) { + pipes.splice(i, 1); + i--; + } + } + for(var i = 0; i < blocks.length; i++) { + blocks[i].x -= 3*scrollSpeed; + var p = blocks[i].show(); + if(blocks[i].intersects(bird)) gamestate = 'dead'; + if(p) { + blocks.splice(i, 1); + i--; + } + } +} + +function showScore() { + textAlign(CENTER, CENTER); + stroke(0); + strokeWeight(3); + fill(255); + textSize(120); + text(score, width/2, 100); +} + +function drawLoading() { + background(25); + var total = 3; + var count = 0; + if(bgImage1 != undefined) count++; + if(bgImage2 != undefined) count++; + if(birdImage != undefined) count++; + if(count >= total) gamestate = 'start'; + var w = (count/total)*(width-50); + noStroke(); + fill(0, 255, 0); + rect(25, 25, w, 75); +} + +function windowResized() { + resizeCanvas(windowWidth, windowHeight); +} + +function newGame() { + bird = new Bird(); + blocks = []; + pipes = []; + frame = 0; + score = 0; + gamestate = 'game'; +} + +function mousePressed() { + if(gamestate == 'menu') { + menuPress(mouseX, mouseY, mouseButton); + } +} + +function mouseReleased() { + if(gamestate == 'menu') { + menuRelease(mouseX, mouseY, mouseButton); + } +} + +function mouseClicked() { + if(gamestate == 'menu') { + menuClick(mouseX, mouseY, mouseButton); + } else if(gamestate == 'game' ) { + bird.applyForce(flapForce, 'flap'); + } else if((gamestate == 'start' || gamestate == 'dead') && mouseY > height/2 + 75) { + if(mouseX < width/2) { + newGame() + } else { + gamestate = 'menu' + } + } +} diff --git a/src/templates/projects/flappy/pipe.js b/src/templates/projects/flappy/pipe.js new file mode 100644 index 0000000..bfa7df1 --- /dev/null +++ b/src/templates/projects/flappy/pipe.js @@ -0,0 +1,30 @@ +function Pipe() { + this.x = width+capWidth; + this.top = height-(random(height - pipeCap*2 - pipeGap)+pipeCap+pipeGap); + this.bottom = this.top+pipeGap; + this.passed = false; + + this.show = function() { + stroke(0); + strokeWeight(3); + fill(100, 200, 100); + rect(this.x, -10, pipeWidth, this.top); + rect(this.x, this.bottom, pipeWidth, height+10); + + rect(this.x-capWidth/2+pipeWidth/2, this.top-pipeCap, capWidth, pipeCap); + rect(this.x-capWidth/2+pipeWidth/2, this.bottom, capWidth, pipeCap); + + if(this.x + capWidth < 0) return true; + return false; + } + + this.intersects = function(b) { + if(b.xpos+b.width > this.x && b.xpos < this.x+pipeWidth) { + if(!this.passed) { + this.passed = true; + score++; + } + return (b.pos < this.top || b.pos+b.height > this.bottom); + } + } +} diff --git a/src/templates/projects/fracbase/index.html b/src/templates/projects/fracbase/index.html new file mode 100644 index 0000000..32191c1 --- /dev/null +++ b/src/templates/projects/fracbase/index.html @@ -0,0 +1,95 @@ +{% set meta={"title": "Fraction to base n", "desc": "Convert fractions to digit expansions in different bases"} %} +{% extends "/_base.html" %} +{% block head %} + +{% endblock%} +{% block content%} + +
      Numerator:
      +
      Denominator:
      +
      Base:
      +
      Digits:
      +
      Show block form
      +
      +
      Result: 
      +
      + +{% endblock %} diff --git a/src/templates/projects/griddraw/canvas.js b/src/templates/projects/griddraw/canvas.js new file mode 100644 index 0000000..bf9e6cc --- /dev/null +++ b/src/templates/projects/griddraw/canvas.js @@ -0,0 +1,167 @@ +function createCanvas(selector, width, height, spacing, mouseOver) { + const result = { + xsize: Math.ceil(width/spacing), + ysize: Math.ceil(height/spacing), + spacing: spacing, + undoStack: [], + redoStack: [], + undoFrame: null, + }; + + result.width = result.xsize*spacing; + result.height = result.ysize*spacing; + + const svg = d3.select(selector).append("svg") + .attr("id", "canvas") + .attr("width", result.width) + .attr("height", result.height) + .style("background", "white"); + const cells = svg.append("g").attr("id", "canvas-cells"); + const lines = svg.append("g").attr("id", "canvas-lines"); + for(let i = 0; i < result.ysize; i++) { + lines.append("line") + .style("stroke", "#888") + .attr("class", "line") + .attr("x1", 0) + .attr("x2", result.width) + .attr("y1", i*result.spacing) + .attr("y2", i*result.spacing); + } + for(let i = 0; i < result.xsize; i++) { + lines.append("line") + .style("stroke", "#888") + .attr("class", "line") + .attr("y1", 0) + .attr("y2", result.height) + .attr("x1", i*result.spacing) + .attr("x2", i*result.spacing); + } + svg.on("scroll", ()=>{}); + svg.on("contextmenu", (event, d)=>{event.preventDefault();}); + svg.on("mousedown", (event, d)=>{ + window.mouseButton = event.button; + result.beginUndoFrame("draw"); + mouseOver(d3.pointer(event)); + }); + svg.on("mousemove", (event, d)=>{ + if(window.mouseButton !== null) { + mouseOver(d3.pointer(event)); + } + }); + svg.on("mouseup", (event, d)=>{ + window.mouseButton = null; + result.endUndoFrame(); + }); + + result.svg = svg; + + result.beginUndoFrame = function(type) { + result.undoFrame = []; + } + + result.endUndoFrame = function() { + if(result.undoFrame.length > 0) { + result.undoStack.push(result.undoFrame); + } + result.undoFrame = null; + } + + result.undo = function() { + if(result.undoStack.length > 0) { + const frame = result.undoStack.pop(); + for(const change of frame.reverse()) { + result.setCell(change.x, change.y, change.before); + } + result.redoStack.push(frame); + } + } + + result.redo = function() { + if(result.redoStack.length > 0) { + const frame = result.redoStack.pop(); + for(const change of frame) { + result.setCell(change.x, change.y, change.after); + } + result.undoStack.push(frame); + } + } + + result.clear = function() { + result.beginUndoFrame(); + for(let x = 0; x < result.xsize; x++) { + for(let y = 0; y < result.xsize; y++) { + result.setCell(x, y, null); + } + } + result.endUndoFrame(); + } + + result.setCell = function(x, y, color) { + const cell = d3.select("#cell-"+x+"-"+y); + if(color === null) { + if(cell.size() != 0) { + if(result.undoFrame){ + result.undoFrame.push({ + x: x, + y: y, + before: cell.style("fill"), + after: null + }); + } + cell.remove(); + } + } else { + if(cell.size() == 0) { + if(result.undoFrame){ + result.undoFrame.push({ + x: x, + y: y, + before: null, + after: color + }); + } + d3.select("#canvas-cells").append("rect") + .style("stroke-width", 0) + .style("fill", color) + .attr("id", "cell-"+x+"-"+y) + .attr("x", x*result.spacing) + .attr("y", y*result.spacing) + .attr("width", result.spacing) + .attr("height", result.spacing); + } else { + if(cell.style("fill") == color) { + return; + } + if(result.undoFrame){ + result.undoFrame.push({ + x: x, + y: y, + before: cell.style("fill"), + after: color + }); + } + cell.style("fill", color); + } + } + } + + result.setbg = function(color) { + svg.style("background", color); + } + + result.setlinecol = function(color) { + const lines = d3.selectAll(".line"); + if(color !== null) { + lines.style("stroke", color); + } else { + const cur = lines.attr("visibility"); + if(cur == "hidden") { + lines.attr("visibility", null); + } else { + lines.attr("visibility", "hidden"); + } + } + } + + return result; +} diff --git a/src/templates/projects/griddraw/colorpicker.js b/src/templates/projects/griddraw/colorpicker.js new file mode 100644 index 0000000..20eb716 --- /dev/null +++ b/src/templates/projects/griddraw/colorpicker.js @@ -0,0 +1,91 @@ +function HSVtoRGB(c){var r,g,b,i,f,p,q,t,h=c.h,s=c.s,v=c.v;i=Math.floor(h*6);f=h*6-i;p=v*(1-s);q=v*(1-f*s);t=v*(1-(1-f)*s);switch(i%6){case 0:r=v,g=t,b=p;break;case 1:r=q,g=v,b=p;break;case 2:r=p,g=v,b=t;break;case 3:r=p,g=q,b=v;break;case 4:r=t,g=p,b=v;break;case 5:r=v,g=p,b=q;break}return "rgb("+Math.round(r*255)+", "+Math.round(g*255)+", "+Math.round(b*255)+")"} + +function RGBtoHEX(c){return "#"+c.split("(")[1].split(")")[0].split(",").map((x)=>{x=parseInt(x).toString(16);return(x.length==1)?"0"+x:x}).join("")} + +function HEXtoHSV(c){var r=parseInt(c[1]+c[2],16)/255,g=parseInt(c[3]+c[4],16)/255,b=parseInt(c[5]+c[6],16)/255,cmax,cmin,diff,h,s;cmax=Math.max(r,g,b);cmin=Math.min(r,g,b);diff=cmax-cmin;if(cmax==cmin){h=0}else if(cmax==r){h=(((g-b)/diff)/6+1)%1}else if(cmax==g){h=(((b-r)/diff)/6+1/3)%1}else if(cmax==b){h=(((r-g)/diff)/6+2/3)%1}if(cmax==0){s=0}else{s=diff/cmax}return{h:h,s:s,v:cmax}} + +function createColorpicker(selPickingArea, selPicker, selHuePickingArea, selHuePicker, selHexcode, selDisplay) { + const result = { + color: {h:0.6111,s:0.89,v:0.72} + }; + + const pickingArea = d3.select(selPickingArea); + const picker = d3.select(selPicker); + const huePickingArea = d3.select(selHuePickingArea); + const huePicker = d3.select(selHuePicker); + const hexcode = d3.select(selHexcode); + const display = d3.select(selDisplay); + + pickingArea.on("contextmenu", (event, d)=>{event.preventDefault();}); + pickingArea.on("mousedown", (event, d)=>{ + event.preventDefault(); + window.mouseButton = event.button; + colorChange(d3.pointer(event)); + }); + pickingArea.on("mousemove", (event, d)=>{ + event.preventDefault(); + if(window.mouseButton !== null) { + colorChange(d3.pointer(event)); + } + }); + pickingArea.on("mouseup", (event, d)=>{window.mouseButton = null;}); + + + huePickingArea.on("contextmenu", (event, d)=>{event.preventDefault();}); + huePickingArea.on("mousedown", (event, d)=>{ + event.preventDefault(); + window.mouseButton = event.button; + hueChange(d3.pointer(event)); + }); + huePickingArea.on("mousemove", (event, d)=>{ + event.preventDefault(); + if(window.mouseButton !== null) { + hueChange(d3.pointer(event)); + } + }); + huePickingArea.on("mouseup", (event, d)=>{window.mouseButton = null;}); + + + hexcode.on("input", hexChange); + hexcode.on("change", hexChange); + hexcode.on("keypress", hexChange); + hexcode.on("paste", hexChange); + + function hexChange(event, d) { + const val = event.target.value; + if(val.match(/#[0-9a-fA-F]{6}/)) { + result.color = HEXtoHSV(event.target.value); + result.updateColorOutput(); + } + } + + function colorChange(mouse) { + const xp = Math.min(Math.max(mouse[0],0),200); + const yp = Math.min(Math.max(mouse[1],0),200); + result.color.s = xp/200; + result.color.v = 1-yp/200; + result.updateColorOutput(); + } + + function hueChange(mouse) { + result.color.h = mouse[1]/200; + result.updateColorOutput(); + } + + result.updateColorOutput = function() { + const hsv = result.color; + const rgb = HSVtoRGB(hsv); + display.style("background", rgb); + hexcode.node().value = RGBtoHEX(rgb); + huePicker.style("top", hsv.h*200+"px"); + picker.style("top", ((1-hsv.v)*200-4)+"px").style("left", (hsv.s*200-4)+"px"); + pickingArea.style("background", + "-webkit-linear-gradient(top, #00000000, #000 100%),"+ + "-webkit-linear-gradient(left, #fff, #ffffff00 100%),"+ + HSVtoRGB({h:hsv.h,s:1,v:1}) + ); + } + + return result; + +} diff --git a/src/templates/projects/griddraw/index.html b/src/templates/projects/griddraw/index.html new file mode 100644 index 0000000..539d28f --- /dev/null +++ b/src/templates/projects/griddraw/index.html @@ -0,0 +1,48 @@ +{% set meta={"title": "Griddraw", "desc": "Draw on a grid", "pagewidth": "90%"}%} +{% extends "/_base.html"%} +{% block head %} + + + + + +{% endblock %} +{% block title %}{% endblock %} +{% block content %} +
      +
      +

      Griddraw

      +
      +
      +
      +
      +
      +
      +
      +
      +
      +
      + +
      +
      +
      Left click - paint
      +
      Right click - erase
      +
      Middle click - pick color
      +
      + + + +
      +
      + +
      +
      + + +
      +
      +
      +
      +
      +
      +{% endblock %} diff --git a/src/templates/projects/griddraw/index.js b/src/templates/projects/griddraw/index.js new file mode 100644 index 0000000..574f767 --- /dev/null +++ b/src/templates/projects/griddraw/index.js @@ -0,0 +1,39 @@ +window.onload = () => { + window.mouseButton = null; + window.canvas = createCanvas("#canvas-wrapper", window.innerWidth, window.innerHeight, 30, mouseOver); + + window.colorpicker = createColorpicker( + ".picking-area", ".picker", + ".hue-picking-area", ".hue-picker", + "#hexcode", "#colordisplay" + ); + + d3.select("#undo").on("click", window.canvas.undo); + d3.select("#redo").on("click", window.canvas.redo); + d3.select("#clear").on("click", window.canvas.clear); + d3.select("#setbg").on("click", ()=>window.canvas.setbg(HSVtoRGB(window.colorpicker.color))); + d3.select("#setlines").on("click", ()=>window.canvas.setlinecol(HSVtoRGB(window.colorpicker.color))); + d3.select("#togglelines").on("click", ()=>window.canvas.setlinecol(null)); + + window.colorpicker.updateColorOutput(); +}; + +function mouseOver(mouse) { + const x = Math.floor(mouse[0]/window.canvas.spacing); + const y = Math.floor(mouse[1]/window.canvas.spacing); + if(window.mouseButton == 0) { + window.canvas.setCell(x, y, HSVtoRGB(window.colorpicker.color)); + } else if(window.mouseButton == 1) { + const cell = d3.select("#cell-"+x+"-"+y); + if(cell.size() > 0) { + const color = cell.style("fill"); + const hsvcol = HEXtoHSV(RGBtoHEX(color)); + window.colorpicker.color = hsvcol; + window.colorpicker.updateColorOutput(); + } + } else if(window.mouseButton == 2) { + window.canvas.setCell(x, y, null); + } +} + +window.addEventListener("scroll", ()=>{window.scrollTo(0, 0)}); diff --git a/src/templates/projects/griddraw/style.css b/src/templates/projects/griddraw/style.css new file mode 100644 index 0000000..3661499 --- /dev/null +++ b/src/templates/projects/griddraw/style.css @@ -0,0 +1,82 @@ +main { + max-width: 100%; + padding: 20px; + margin: 0; +} +main > * { + margin: 0; +} +.leftcol > * { + margin-bottom: 0px; + margin-top: 7px; +} +.scrollable { + overflow: scroll; +} +.flexcontainer { + display: flex; + flex-direction: row; +} +.leftcol { + flex: 400px; +} +.rightcol { + flex: 100%; + height: calc(100vh - 40px); +} + +#colorpicker { + display: flex; + flex-direction: row; +} + +.picking-area { + position: relative; + width: 200px; + height: 200px; + background: -webkit-linear-gradient(top, #00000000, #000 100%), + -webkit-linear-gradient(left, #fff, #ffffff00 100%), rgb(0, 85, 255); + margin-right: 20px; + border: 1px solid #888; +} + +.picker { + position: absolute; + width: 8px; + height: 8px; + border: 1px solid #fff; + border-radius: 50%; + top: 52px; + left: 174px; +} +.picker::before { + width: 6px; + height: 6px; + position: absolute; + border: 1px solid #000; +} + +.hue-picking-area { + position: relative; + width: 30px; + height: 200px; + background: -webkit-linear-gradient(top, #f00 0%, #ff0 16.67%, #0f0 33.33%, #0ff 50%, #00f 66.67%, #f0f 83.33%, #f00 100%); + border: 1px solid #888; +} + +.hue-picker { + position: absolute; + width: 30px; + height: 2px; + background: #fff; + border: 1px solid #888; + top: 122px; +} + +#colordisplay { + width: 100px; + margin-left: 40px; + height: 23px; + border: 1px solid #888; + background: rgb(20, 75, 184); +} diff --git a/src/templates/projects/markov/index.html b/src/templates/projects/markov/index.html new file mode 100644 index 0000000..aa0a399 --- /dev/null +++ b/src/templates/projects/markov/index.html @@ -0,0 +1,50 @@ +{% set meta={"title": "Markov Word Gen", "desc": "Generate semi-random semi-pronouncable words using Markov chains"} %} +{% extends "/_base.html"%} +{% block head %} + + + + +{% endblock %} +{% block content %} +

      Generates semi-random semi-pronouncable words using Markov chains. Press +"Run Markov Chain" to generate a word. Press "Analyze frequencies" to run the +chain many times and make a list of the output probabilities. Enter text in +the box to create a new Markov chain from the text +you entered.

      + +
      +Presets: + +
      +
      +
      + +

      + + + + +

      Results:

      +
      +{% endblock %} \ No newline at end of file diff --git a/src/templates/projects/markov/main.js b/src/templates/projects/markov/main.js new file mode 100644 index 0000000..fc26186 --- /dev/null +++ b/src/templates/projects/markov/main.js @@ -0,0 +1,111 @@ +const SAMPLES = 500000; +const TRIES = 50000; +const FAILTEXT = 'Input conditions may be impossible: tried ' + TRIES + ' times and failed.'; + +var minLength = 3; +var maxLength = 10; +var order = 1; + +window.onload = function() { + repopulate(); +} + +function repeatRun(times) { + document.getElementById('results').innerHTML = ''; + for(var i = 0; i < times; i++) { + var word = getGoodChainOutput(); + if(word == null) { + document.getElementById('results').innerHTML = FAILTEXT; + return; + } + var p = document.createElement("P"); + p.className = "nospace"; + p.innerHTML = word; + document.getElementById('results').appendChild(p); + } +} + +function repeatRunReturn(times) { + var ret = []; + for(var i = 0; i < times; i++) { + var ch = getGoodChainOutput(); + if(ch == null) return null; + ret.push(ch); + } + return ret; +} + +function getGoodChainOutput() { + var word = runChain(); + var i = 0; + while(word.length < minLength || word.length > maxLength) { + word = runChain(); + i++ + if(i > 5000) return null; + } + return word; +} + +function analyze() { + document.getElementById('results').innerHTML = 'Analyzing...'; + setTimeout(doAnalyze); +} + +function doAnalyze() { + var run = repeatRunReturn(SAMPLES); + if(run == null) { + document.getElementById('results').innerHTML = FAILTEXT; + } + var results = {}; + for(var i = 0; i < run.length; i++) { + var w = run[i]; + if(results[w] != undefined) results[w] += 1; + else results[w] = 1; + } + var sorted = Object.keys(results).sort(function(a, b) {return results[b]-results[a]}); + sorted.splice(50); + var final = ''; + for(var i = 0; i < sorted.length; i++) { + final += sorted[i]; + var ns = Math.ceil((maxLength+1)/2) - sorted[i].length + 1; + for(var i = 0; i < ns; i++) { final += ' '; } + final += '(' + format(Math.round(10000*results[sorted[i]]/SAMPLES)/100, 1, 2) + '%)'; + final += '
      '; + } + document.getElementById('results').innerHTML = final; +} + +function format(number, wholeDigits, decimalDigits) { + var nstr = String(number); + var parts = nstr.split('.'); + var whole = parts[0], decimal = parts[1]; + while(whole != null && whole.length < wholeDigits) { + whole = '0' + whole; + } + while(decimal != null && decimal.length < decimalDigits) { + decimal += '0'; + } + if(whole == undefined) whole = 0; + if(decimal == undefined) decimal = 0; + return whole + '.' + decimal; +} + +function repopulate() { + var corpus = document.getElementById('corpus').value; + populateChain(corpus); +} + +function minmax() { + minLength = document.getElementById('min_length').value; + maxLength = document.getElementById('max_length').value; +} + +function setOrder() { + order = document.getElementById('markov_order').value; + repopulate(); +} + +function preset() { + document.getElementById('corpus').value = presets[document.getElementById('preset').value]; + repopulate(); +} diff --git a/src/templates/projects/markov/markov.js b/src/templates/projects/markov/markov.js new file mode 100644 index 0000000..42abee7 --- /dev/null +++ b/src/templates/projects/markov/markov.js @@ -0,0 +1,40 @@ +var chain = { + '!':[] +} + +function runChain() { + var index = '!'; + var word = ''; + while(index != '') { + var exits = chain[index]; + var next = exits[Math.floor(Math.random() * exits.length)]; + if(next == '') break; + if(order == 1) + index = next; + else + index = index.slice(-order+1) + next; + word += next; + } + return word; +} + +function populateChain(corpus) { + chain = {'!':[]}; + corpus = corpus.replace(/[^A-Za-z\s]|_/g, '').replace(/\s+/g, ' ').trim().toLowerCase(); + corpus = ' ' + corpus + ' '; + for(var i = 1; i < corpus.length; i++) { + var cur = corpus[i]; + var prev = ''; + for(var j = 0; j < order; j++) { + if(corpus[i-1-j] == ' ') { + prev = '!' + prev; + break; + } + prev = corpus[i-1-j] + prev; + } + if(cur == ' ') cur = ''; + if(chain[prev] == undefined) chain[prev] = []; + chain[prev].push(cur); + } + console.log(chain); +} diff --git a/src/templates/projects/markov/presets.js b/src/templates/projects/markov/presets.js new file mode 100644 index 0000000..e3913e3 --- /dev/null +++ b/src/templates/projects/markov/presets.js @@ -0,0 +1,11 @@ +var presets = { + "lipsum": " Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque eu tempor risus. Sed sodales arcu ut pellentesque vehicula. In sit amet eros fermentum, tincidunt enim a, posuere lorem. Nulla malesuada ipsum sit amet nisi commodo faucibus. Nullam porttitor commodo ultrices. In in cursus ex. Fusce eu posuere purus, ut convallis libero. Mauris tincidunt quis mauris at varius. Quisque sed suscipit lorem. Cras varius purus eget lorem hendrerit, eleifend tempor ligula varius. In eu tempus neque. Vestibulum aliquam neque sem, sit amet pellentesque nisl mollis vitae. Maecenas placerat neque metus, a consectetur nulla bibendum at. Donec nec nulla neque. Aliquam maximus dictum nulla, sed rutrum dolor iaculis non. Etiam in bibendum elit. Maecenas rutrum arcu eget finibus efficitur. Donec volutpat dictum nisl vitae sollicitudin. Aenean dictum eros in scelerisque finibus. Donec blandit accumsan nunc, in viverra risus tincidunt ut. Nulla felis odio, tempus non lectus nec, vestibulum euismod elit. Nulla id diam tempor, iaculis libero sed, elementum massa. Aliquam feugiat velit neque, blandit tempus ipsum varius non. Suspendisse potenti. Integer varius ipsum ac lacinia volutpat. Maecenas ultricies vestibulum tristique. Mauris sit amet est commodo, dictum arcu eu, lacinia lacus. Suspendisse feugiat sagittis eleifend. Aliquam tempor elementum massa. Curabitur elit enim, varius et maximus tincidunt, semper sit amet magna. Vestibulum facilisis dictum sapien nec commodo. Pellentesque pulvinar consectetur hendrerit. Nam blandit felis ut ante egestas dignissim. In urna purus, dapibus sodales sollicitudin quis, lobortis ac quam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nunc quis diam suscipit, dictum erat et, vehicula mi. Nulla quis augue eu sapien placerat maximus. Nulla blandit id erat eu suscipit. Duis sagittis pulvinar diam quis placerat. Pellentesque eros ligula, commodo et facilisis nec, faucibus ac mi. Morbi tempus justo tortor, gravida tincidunt tellus rhoncus at. Mauris pellentesque mi nec ex suscipit, vitae iaculis nibh venenatis. Cras diam massa, tincidunt vel dolor consequat, mattis posuere massa. Sed cursus ipsum mi, nec dapibus lorem aliquam et. Donec eleifend, augue sit amet sagittis elementum, magna mauris lacinia urna, at finibus justo ipsum mattis metus. Donec pellentesque vel libero pretium ultrices. Etiam massa odio, pharetra in quam id, lacinia efficitur justo. Fusce pretium urna eu ipsum vulputate, non ornare ligula porttitor. Integer ac ante vitae lectus commodo tincidunt vel vitae urna. Aenean rutrum dignissim nisl pharetra egestas. Donec ac commodo mi. Suspendisse ac tempor eros. Etiam cursus turpis non tortor egestas venenatis. In ut ultrices nibh.", + + "wikipedia": "A Markov chain is a stochastic model describing a sequence of possible events in which the probability of each event depends only on the state attained in the previous event.[1][2] In probability theory and related fields, a Markov process, named after the Russian mathematician Andrey Markov, is a stochastic process that satisfies the Markov property[1][3][4] (sometimes characterized as \"memorylessness\"). Roughly speaking, a process satisfies the Markov property if one can make predictions for the future of the process based solely on its present state just as well as one could knowing the process's full history, hence independently from such history; i.e., conditional on the present state of the system, its future and past states are independent. A Markov chain is a type of Markov process that has either a discrete state space or a discrete index set (often representing time), but the precise definition of a Markov chain varies.[5] For example, it is common to define a Markov chain as a Markov process in either discrete or continuous time with a countable state space (thus regardless of the nature of time),[6][7][8][9] but it is also common to define a Markov chain as having discrete time in either countable or continuous state space (thus regardless of the state space).[5] Markov studied Markov processes in the early 20th century, publishing his first paper on the topic in 1906.[10][11][12][13] Random walks based on integers and the gambler's ruin problem are examples of Markov processes.[14][15] Some variations of these processes were studied hundreds of years earlier in the context of independent variables.[16][17][18] Two important examples of Markov processes are the Wiener process, also known as the Brownian motion process, and the Poisson process,[19] which are considered the most important and central stochastic processes in the theory of stochastic processes,[20][21][22] and were discovered repeatedly and independently, both before and after 1906, in various settings.[23][24] These two processes are Markov processes in continuous time, while random walks on the integers and the gambler's ruin problem are examples of Markov processes in discrete time.[14][15] Markov chains have many applications as statistical models of real-world processes,[1][25][26][27] such as studying cruise control systems in motor vehicles, queues or lines of customers arriving at an airport, exchange rates of currencies, storage systems such as dams, and population growths of certain animal species.[28] The algorithm known as PageRank, which was originally proposed for the internet search engine Google, is based on a Markov process.[1][29][30] Markov processes are the basis for general stochastic simulation methods known as Gibbs sampling and Markov chain Monte Carlo. Furthermore, they are used for simulating random objects with specific probability distributions, and have found extensive application in Bayesian statistics.[28][31][32] The adjective Markovian is used to describe something that is related to a Markov process.[1][33]", + + "names": "Emma Olivia Ava Isabella Sophia Mia Charlotte Amelia Evelyn Abigail Harper Emily Elizabeth Avery Sofia Ella Madison Scarlett Victoria Aria Grace Chloe Camila Penelope Riley Layla Lillian Nora Zoey Mila Aubrey Hannah Lily Addison Eleanor Natalie Luna Savannah Brooklyn Leah Zoe Stella Hazel Ellie Paisley Audrey Skylar Violet Claire Bella Aurora Lucy Anna Samantha Caroline Genesis Aaliyah Kennedy Kinsley Allison Maya Sarah Madelyn Adeline Alexa Ariana Elena Gabriella Naomi Alice Sadie Hailey Eva Emilia Autumn Quinn Nevaeh Piper Ruby Serenity Willow Everly Cora Kaylee Lydia Aubree Arianna Eliana Peyton Melanie Gianna Isabelle Julia Valentina Nova Clara Vivian Reagan Mackenzie Madeline Brielle Delilah Isla Rylee Katherine Sophie Josephine Ivy Liliana Jade Maria Taylor Hadley Kylie Emery Adalynn Natalia Annabelle Faith Alexandra Ximena Ashley Brianna Raelynn Bailey Mary Athena Andrea Leilani Jasmine Lyla Margaret Alyssa Adalyn Arya Norah Khloe Kayla Eden Eliza Rose Ariel Melody Alexis Isabel Sydney Juliana Lauren Iris Emerson London Morgan Lilly Charlie Aliyah Valeria Arabella Sara Finley Trinity Ryleigh Jordyn Jocelyn Kimberly Esther Molly Valerie Cecilia Anastasia Daisy Reese Laila Mya Amy Teagan Amaya Elise Harmony Paige Adaline Fiona Alaina Nicole Genevieve Lucia Alina Mckenzie Callie Payton Eloise Brooke Londyn Mariah Julianna Rachel Daniela Gracie Catherine Angelina Presley Josie Harley Adelyn Vanessa Makayla Parker Juliette Amara Marley Lila Ana Rowan Alana Michelle Malia Rebecca Brooklynn Brynlee Summer Sloane Leila Sienna Adriana Sawyer Kendall Juliet Destiny Alayna Elliana Diana Hayden Ayla Dakota Angela Noelle Rosalie Joanna Jayla Alivia Lola Emersyn Georgia Selena June Daleyza Tessa Maggie Jessica Remi Delaney Camille Vivienne Hope Mckenna Gemma Olive Alexandria Blakely Izabella Catalina Raegan Journee Gabrielle Lucille Ruth Amiyah Evangeline Blake Thea Amina Giselle Lilah Melissa River Kate Adelaide Charlee Vera Leia Gabriela Zara Jane Journey Elaina Miriam Briella Stephanie Cali Ember Lilliana Aniyah Logan Kamila Brynn Ariella Makenzie Annie Mariana Kali Haven Elsie Nyla Paris Lena Freya Adelynn Lyric Camilla Sage Jennifer Paislee Talia Alessandra Juniper Fatima Raelyn Amira Arielle Phoebe Kinley Ada Nina Ariah Samara Myla Brinley Cassidy Maci Aspen Allie Keira Kaia Makenna Amanda Heaven Joy Lia Madilyn Gracelyn Laura Evelynn Lexi Haley Miranda Kaitlyn Daniella Felicity Jacqueline Evie Angel Danielle Ainsley Dylan Kiara Millie Jordan Maddison Rylie Alicia Maeve Margot Kylee Phoenix Heidi Zuri Alondra Lana Madeleine Gracelynn Kenzie Miracle Shelby Elle Adrianna Bianca Addilyn Kira Veronica Gwendolyn Esmeralda Chelsea Alison Skyler Magnolia Daphne Jenna Everleigh Kyla Braelynn Harlow Annalise Mikayla Dahlia Maliyah Averie Scarlet Kayleigh Luciana Kelsey Nadia Liam Noah William James Logan Benjamin Mason Elijah Oliver Jacob Lucas Michael Alexander Ethan Daniel Matthew Aiden Henry Joseph Jackson Samuel Sebastian David Carter Wyatt Jayden John Owen Dylan Luke Gabriel Anthony Isaac Grayson Jack Julian Levi Christopher Joshua Andrew Lincoln Mateo Ryan Jaxon Nathan Aaron Isaiah Thomas Charles Caleb Josiah Christian Hunter Eli Jonathan Connor Landon Adrian Asher Cameron Leo Theodore Jeremiah Hudson Robert Easton Nolan Nicholas Ezra Colton Angel Brayden Jordan Dominic Austin Ian Adam Elias Jaxson Greyson Jose Ezekiel Carson Evan Maverick Bryson Jace Cooper Xavier Parker Roman Jason Santiago Chase Sawyer Gavin Leonardo Kayden Ayden Jameson Kevin Bentley Zachary Everett Axel Tyler Micah Vincent Weston Miles Wesley Nathaniel Harrison Brandon Cole Declan Luis Braxton Damian Silas Tristan Ryder Bennett George Emmett Justin Kai Max Diego Luca Ryker Carlos Maxwell Kingston Ivan Maddox Juan Ashton Jayce Rowan Kaiden Giovanni Eric Jesus Calvin Abel King Camden Amir Blake Alex Brody Malachi Emmanuel Jonah Beau Jude Antonio Alan Elliott Elliot Waylon Xander Timothy Victor Bryce Finn Brantley Edward Abraham Patrick Grant Karter Hayden Richard Miguel Joel Gael Tucker Rhett Avery Steven Graham Kaleb Jasper Jesse Matteo Dean Zayden Preston August Oscar Jeremy Alejandro Marcus Dawson Lorenzo Messiah Zion Maximus River Zane Mark Brooks Nicolas Paxton Judah Emiliano Kaden Bryan Kyle Myles Peter Charlie Kyrie Thiago Brian Kenneth Andres Lukas Aidan Jax Caden Milo Paul Beckett Brady Colin Omar Bradley Javier Knox Jaden Barrett Israel Matias Jorge Zander Derek Josue Cayden Holden Griffin Arthur Leon Felix Remington Jake Killian Clayton Sean Adriel Riley Archer Legend Erick Enzo Corbin Francisco Dallas Emilio Gunner Simon Andre Walter Damien Chance Phoenix Colt Tanner Stephen Kameron Tobias Manuel Amari Emerson Louis Cody Finley Iker Martin Rafael Nash Beckham Cash Karson Rylan Reid Theo Ace Eduardo Spencer Raymond Maximiliano Anderson Ronan Lane Cristian Titus Travis Jett Ricardo Bodhi Gideon Jaiden Fernando Mario Conor Keegan Ali Cesar Ellis Jayceon Walker Cohen Arlo Hector Dante Kyler Garrett Donovan Seth Jeffrey Tyson Jase Desmond Caiden Gage Atlas Major Devin Edwin Angelo Orion Conner Julius Marco Jensen Daxton Peyton Zayn Collin Jaylen Dakota Prince Johnny Kayson Cruz Hendrix Atticus Troy Kane Edgar Sergio Kash Marshall Johnathan Romeo Shane Warren Joaquin Wade Leonel Trevor Dominick Muhammad Erik Odin Quinn Jaxton Dalton Nehemiah Frank Grady Gregory Andy Solomon Malik Rory Clark Reed Harvey Zayne Jay Jared Noel Shawn Fabian Ibrahim Adonis Ismael Pedro Leland Malakai Malcolm Alexis Kason Porter Sullivan Raiden", + + "beemovie": "according to all known laws of aviation there is no way a bee should be able to fly its wings are too small to get its fat little body off the ground the bee of course flies anyway because bees dont care what humans think is impossible yellow black yellow black yellow black yellow black ooh black and yellow lets shake it up a little barry breakfast is ready ooming hang on a second hello barry adam oan you believe this is happening i cant ill pick you up looking sharp use the stairs your father paid good money for those sorry im excited heres the graduate were very proud of you son a perfect report card all bs very proud ma i got a thing going here you got lint on your fuzz ow thats me wave to us well be in row 118 000 bye barry i told you stop flying in the house hey adam hey barry is that fuzz gel a little special day graduation never thought id make it three days grade school three days high school those were awkward three days college im glad i took a day and hitchhiked around the hive you did come back different hi barry artie growing a mustache looks good hear about frankie yeah you going to the funeral no im not going everybody knows sting someone you die dont waste it on a squirrel such a hothead i guess he could have just gotten out of the way i love this incorporating an amusement park into our day thats why we dont need vacations boy quite a bit of pomp under the circumstances well adam today we are men we are beemen amen hallelujah students faculty distinguished bees please welcome dean buzzwell welcome new hive oity graduating class of 915 that concludes our ceremonies and begins your career at honex industries will we pick ourjob today i heard its just orientation heads up here we go keep your hands and antennas inside the tram at all times wonder what itll be like a little scary welcome to honex a division of honesco and a part of the hexagon group this is it wow wow we know that you as a bee have worked your whole life to get to the point where you can work for your whole life honey begins when our valiant pollen jocks bring the nectar to the hive our topsecret formula is automatically colorcorrected scentadjusted and bubblecontoured into this soothing sweet syrup with its distinctive golden glow you know as honey that girl was hot shes my cousin she is yes were all cousins right youre right at honex we constantly strive to improve every aspect of bee existence these bees are stresstesting a new helmet technology what do you think he makes not enough here we have our latest advancement the krelman what does that do oatches that little strand of honey that hangs after you pour it saves us millions oan anyone work on the krelman of course most bee jobs are small ones but bees know that every small job if its done well means a lot but choose carefully because youll stay in the job you pick for the rest of your life the same job the rest of your life i didnt know that whats the difference youll be happy to know that bees as a species havent had one day off in 27 million years so youll just work us to death well sure try wow that blew my mind whats the difference how can you say that one job forever thats an insane choice to have to make im relieved now we only have to make one decision in life but adam how could they never have told us that why would you question anything were bees were the most perfectly functioning society on earth you ever think maybe things work a little too well here like what give me one example i dont know but you know what im talking about please clear the gate royal nectar force on approach wait a second oheck it out hey those are pollen jocks wow ive never seen them this close they know what its like outside the hive yeah but some dont come back hey jocks hi jocks you guys did great youre monsters youre sky freaks i love it i love it i wonder where they were i dont know their days not planned outside the hive flying who knows where doing who knows what you cantjust decide to be a pollen jock you have to be bred for that right look thats more pollen than you and i will see in a lifetime its just a status symbol bees make too much of it perhaps unless youre wearing it and the ladies see you wearing it those ladies arent they our cousins too distant distant look at these two oouple of hive harrys lets have fun with them it must be dangerous being a pollen jock yeah once a bear pinned me against a mushroom he had a paw on my throat and with the other he was slapping me oh my i never thought id knock him out what were you doing during this trying to alert the authorities i can autograph that a little gusty out there today wasnt it comrades yeah gusty were hitting a sunflower patch six miles from here tomorrow six miles huh barry a puddle jump for us but maybe youre not up for it maybe i am you are not were going 0900 at jgate what do you think buzzyboy are you bee enough i might be it all depends on what 0900 means hey honex dad you surprised me you decide what youre interested in well theres a lot of choices but you only get one do you ever get bored doing the same job every day son let me tell you about stirring you grab that stick and you just move it around and you stir it around you get yourself into a rhythm its a beautiful thing you know dad the more i think about it maybe the honey field just isnt right for me you were thinking of what making balloon animals thats a bad job for a guy with a stinger janet your sons not sure he wants to go into honey barry you are so funny sometimes im not trying to be funny youre not funny youre going into honey our son the stirrer youre gonna be a stirrer no ones listening to me wait till you see the sticks i have i could say anything right now im gonna get an ant tattoo lets open some honey and celebrate maybe ill pierce my thorax shave my antennae shack up with a grasshopper get a gold tooth and call everybody dawg im so proud were starting work today todays the day oome on all the good jobs will be gone yeah right pollen counting stunt bee pouring stirrer front desk hair removal is it still available hang on two left one of thems yours oongratulations step to the side whatd you get picking crud out stellar wow oouple of newbies yes sir our first day we are ready make your choice you want to go first no you go oh my whats available restroom attendants open not for the reason you think any chance of getting the krelman sure youre on im sorry the krelman just closed out wax monkeys always open the krelman opened up again what happened a bee died makes an opening see hes dead another dead one deady deadified two more dead dead from the neck up dead from the neck down thats life oh this is so hard heating cooling stunt bee pourer stirrer humming inspector number seven lint coordinator stripe supervisor mite wrangler barry what do you think i should barry barry all right weve got the sunflower patch in quadrant nine what happened to you where are you im going out out out where out there oh no i have to before i go to work for the rest of my life youre gonna die youre crazy hello another call coming in if anyones feeling brave theres a korean deli on 83rd that gets their roses today hey guys look at that isnt that the kid we saw yesterday hold it son flight decks restricted its ok lou were gonna take him up really feeling lucky are you sign here here just initial that thank you ok you got a rain advisory today and as you all know bees cannot fly in rain so be careful as always watch your brooms hockey sticks dogs birds bears and bats also i got a couple of reports of root beer being poured on us murphys in a home because of it babbling like a cicada thats awful and a reminder for you rookies bee law number one absolutely no talking to humans all right launch positions buzz buzz buzz buzz buzz buzz buzz buzz buzz buzz buzz buzz black and yellow hello you ready for this hot shot yeah yeah bring it on wind check antennae check nectar pack check wings check stinger check scared out of my shorts check ok ladies lets move it out pound those petunias you striped stemsuckers all of you drain those flowers wow im out i cant believe im out so blue i feel so fast and free box kite wow flowers this is blue leader we have roses visual bring it around 30 degrees and hold roses 30 degrees roger bringing it around stand to the side kid its got a bit of a kick that is one nectar collector ever see pollination up close no sir i pick up some pollen here sprinkle it over here maybe a dash over there a pinch on that one see that its a little bit of magic thats amazing why do we do that thats pollen power more pollen more flowers more nectar more honey for us oool im picking up a lot of bright yellow oould be daisies dont we need those oopy that visual wait one of these flowers seems to be on the move say again youre reporting a moving flower affirmative that was on the line this is the coolest what is it i dont know but im loving this color it smells good not like a flower but i like it yeah fuzzy ohemicaly oareful guys its a little grabby my sweet lord of bees oandybrain get off there problem guys this could be bad affirmative very close gonna hurt mamas little boy you are way out of position rookie ooming in at you like a missile help me i dont think these are flowers should we tell him i think he knows what is this match point you can start packing up honey because youre about to eat it yowser gross theres a bee in the car do something im driving hi bee hes back here hes going to sting me nobody move if you dont move he wont sting you freeze he blinked spray him granny what are you doing wow the tension level out here is unbelievable i gotta get home oant fly in rain oant fly in rain oant fly in rain mayday mayday bee going down ken could you close the window please ken could you close the window please oheck out my new resume i made it into a foldout brochure you see folds out oh no more humans i dont need this what was that maybe this time this time this time this time this time this drapes that is diabolical its fantastic its got all my special skills even my topten favorite movies whats number one star wars nah i dont go for that kind of stuff no wonder we shouldnt talk to them theyre out of their minds when i leave a job interview theyre flabbergasted cant believe what i say theres the sun maybe thats a way out i dont remember the sun having a big 75 on it i predicted global warming i could feel it getting hotter at first i thought it was just me wait stop bee stand back these are winter boots wait dont kill him you know im allergic to them this thing could kill me why does his life have less value than yours why does his life have any less value than mine is that your statement im just saying all life has value you dont know what hes capable of feeling my brochure there you go little guy im not scared of him its an allergic thing put that on your resume brochure my whole face could puff up make it one of your special skills knocking someone out is also a special skill right bye vanessa thanks vanessa next week yogurt night sure ken you know whatever you could put carob chips on there bye supposed to be less calories bye i gotta say something she saved my life i gotta say something all right here it goes nah what would i say i could really get in trouble its a bee law youre not supposed to talk to a human i cant believe im doing this ive got to oh i cant do it oome on no yes no do it i cant how should i start it you like jazz no thats no good here she comes speak you fool hi im sorry youre talking yes i know youre talking im so sorry no its ok its fine i know im dreaming but i dont recall going to bed well im sure this is very disconcerting this is a bit of a surprise to me i mean youre a bee i am and im not supposed to be doing this but they were all trying to kill me and if it wasnt for you i had to thank you its just how i was raised that was a little weird im talking with a bee yeah im talking to a bee and the bee is talking to me i just want to say im grateful ill leave now wait how did you learn to do that what the talking thing same way you did i guess mama dada honey you pick it up thats very funny yeah bees are funny if we didnt laugh wed cry with what we have to deal with anyway oan i get you something like what i dont know i mean i dont know ooffee i dont want to put you out its no trouble it takes two minutes its just coffee i hate to impose dont be ridiculous actually i would love a cup hey you want rum cake i shouldnt have some no i cant oome on im trying to lose a couple micrograms where these stripes dont help you look great i dont know if you know anything about fashion are you all right no hes making the tie in the cab as theyre flying up madison he finally gets there he runs up the steps into the church the wedding is on and he says watermelon i thought you said guatemalan why would i marry a watermelon is that a bee joke thats the kind of stuff we do yeah different so what are you gonna do barry about work i dont know i want to do my part for the hive but i cant do it the way they want i know how you feel you do sure my parents wanted me to be a lawyer or a doctor but i wanted to be a florist really my only interest is flowers our new queen was just elected with that same campaign slogan anyway if you look theres my hive right there see it youre in sheep meadow yes im right off the turtle pond no way i know that area i lost a toe ring there once why do girls put rings on their toes why not its like putting a hat on your knee maybe ill try that you all right maam oh yeah fine just having two cups of coffee anyway this has been great thanks for the coffee yeah its no trouble sorry i couldnt finish it if i did id be up the rest of my life are you oan i take a piece of this with me sure here have a crumb thanks yeah all right well then i guess ill see you around or not ok barry and thank you so much again for before oh that that was nothing well not nothing but anyway this cant possibly work hes all set to go we may as well try it ok dave pull the chute sounds amazing it was amazing it was the scariest happiest moment of my life humans i cant believe you were with humans giant scary humans what were they like huge and crazy they talk crazy they eat crazy giant things they drive crazy do they try and kill you like on tv some of them but some of them dont howd you get back poodle you did it and im glad you saw whatever you wanted to see you had your experience now you can pick out yourjob and be normal well well well i met someone you did was she beeish a wasp your parents will kill you no no no not a wasp spider im not attracted to spiders i know its the hottest thing with the eight legs and all i cant get by that face so who is she shes human no no thats a bee law you wouldnt break a bee law her names vanessa oh boy shes so nice and shes a florist oh no youre dating a human florist were not dating youre flying outside the hive talking to humans that attack our homes with power washers and m80s oneeighth a stick of dynamite she saved my life and she understands me this is over eat this this is not over what was that they call it a crumb it was so stingin stripey and thats not what they eat thats what falls off what they eat you know what a oinnabon is no its bread and cinnamon and frosting they heat it up sit down really hot listen to me we are not them were us theres us and theres them yes but who can deny the heart that is yearning theres no yearning stop yearning listen to me you have got to start thinking bee my friend thinking bee thinking bee thinking bee thinking bee thinking bee thinking bee thinking bee there he is hes in the pool you know what your problem is barry i gotta start thinking bee how much longer will this go on its been three days why arent you working ive got a lot of big life decisions to think about what life you have no life you have no job youre barely a bee would it kill you to make a little honey barry come out your fathers talking to you martin would you talk to him barry im talking to you you coming got everything all set go ahead ill catch up dont be too long watch this vanessa were still here i told you not to yell at him he doesnt respond to yelling then why yell at me because you dont listen im not listening to this sorry ive gotta go where are you going im meeting a friend a girl is this why you cant decide bye i just hope shes beeish they have a huge parade of flowers every year in pasadena to be in the tournament of roses thats every florists dream up on a float surrounded by flowers crowds cheering a tournament do the roses compete in athletic events no all right ive got one how come you dont fly everywhere its exhausting why dont you run everywhere its faster yeah ok i see i see all right your turn tivo you can just freeze live tv thats insane you dont have that we have hivo but its a disease its a horrible horrible disease oh my dumb bees you must want to sting all those jerks we try not to sting its usually fatal for us so you have to watch your temper very carefully you kick a wall take a walk write an angry letter and throw it out work through it like any emotion anger jealousy lust oh my goodness are you ok yeah what is wrong with you its a bug hes not bothering anybody get out of here you creep what was that a pic n save circular yeah it was how did you know it felt like about 10 pages seventyfive is pretty much our limit youve really got that down to a science i lost a cousin to italian vogue ill bet what in the name of mighty hercules is this how did this get here oute bee golden blossom ray liotta private select is he that actor i never heard of him why is this here for people we eat it you dont have enough food of your own well yes how do you get it bees make it i know who makes it and its hard to make it theres heating cooling stirring you need a whole krelman thing its organic its ourganic its just honey barry just what bees dont know about this this is stealing a lot of stealing youve taken our homes schools hospitals this is all we have and its on sale im getting to the bottom of this im getting to the bottom of all of this hey hector you almost done almost he is here i sense it well i guess ill go home now and just leave this nice honey out with no one around youre busted box boy i knew i heard something so you can talk i can talk and now youll start talking where you getting the sweet stuff whos your supplier i dont understand i thought we were friends the last thing we want to do is upset bees youre too late its ours now you sir have crossed the wrong sword you sir will be lunch for my iguana ignacio where is the honey coming from tell me where honey farms it comes from honey farms orazy person what horrible thing has happened here these faces they never knew what hit them and now theyre on the road to nowhere just keep still what youre not dead do i look dead they will wipe anything that moves where you headed to honey farms i am onto something huge here im going to alaska moose blood crazy stuff blows your head off im going to tacoma and you he really is dead all right uhoh what is that oh no a wiper triple blade triple blade jump on its your only chance bee why does everything have to be so doggone clean how much do you people need to see open your eyes stick your head out the window from npr news in washington im oarl kasell but dont kill no more bugs bee moose blood guy you hear something like what like tiny screaming turn off the radio whassup bee boy hey blood just a row of honey jars as far as the eye could see wow i assume wherever this truck goes is where theyre getting it i mean that honeys ours bees hang tight were all jammed in its a close community not us man we on our own every mosquito on his own what if you get in trouble you a mosquito you in trouble nobody likes us they just smack see a mosquito smack smack at least youre out in the world you must meet girls mosquito girls try to trade up get with a moth dragonfly mosquito girl dont want no mosquito you got to be kidding me moosebloods about to leave the building so long bee hey guys mooseblood i knew id catch yall down here did you bring your crazy straw we throw it in jars slap a label on it and its pretty much pure profit what is this place a bees got a brain the size of a pinhead they are pinheads pinhead oheck out the new smoker oh sweet thats the one you want the thomas 3000 smoker ninety puffs a minute semiautomatic twice the nicotine all the tar a couple breaths of this knocks them right out they make the honey and we make the money they make the honey and we make the money oh my whats going on are you ok yeah it doesnt last too long do you know youre in a fake hive with fake walls our queen was moved here we had no choice this is your queen thats a man in womens clothes thats a drag queen what is this oh no theres hundreds of them bee honey our honey is being brazenly stolen on a massive scale this is worse than anything bears have done i intend to do something oh barry stop who told you humans are taking our honey thats a rumor do these look like rumors thats a conspiracy theory these are obviously doctored photos how did you get mixed up in this hes been talking to humans what talking to humans he has a human girlfriend and they make out make out barry we do not you wish you could whose side are you on the bees i dated a cricket once in san antonio those crazy legs kept me up all night barry this is what you want to do with your life i want to do it for all our lives nobody works harder than bees dad i remember you coming home so overworked your hands were still stirring you couldnt stop i remember that what right do they have to our honey we live on two cups a year they put it in lip balm for no reason whatsoever even if its true what can one bee do sting them where it really hurts in the face the eye that would hurt no up the nose thats a killer theres only one place you can sting the humans one place where it matters hive at five the hives only fullhour action news source no more bee beards with bob bumble at the anchor desk weather with storm stinger sports with buzz larvi and jeanette ohung good evening im bob bumble and im jeanette ohung a tricounty bee barry benson intends to sue the human race for stealing our honey packaging it and profiting from it illegally tomorrow night on bee larry king well have three former queens here in our studio discussing their new book olassy ladies out this week on hexagon tonight were talking to barry benson did you ever think im a kid from the hive i cant do this bees have never been afraid to change the world what about bee oolumbus bee gandhi bejesus where im from wed never sue humans we were thinking of stickball or candy stores how old are you the bee community is supporting you in this case which will be the trial of the bee century you know they have a larry king in the human world too its a common name next week he looks like you and has a show and suspenders and colored dots next week glasses quotes on the bottom from the guest even though you just heard em bear week next week theyre scary hairy and here live always leans forward pointy shoulders squinty eyes very jewish in tennis you attack at the point of weakness it was my grandmother ken shes 81 honey her backhands a joke im not gonna take advantage of that quiet please actual work going on here is that that same bee yes it is im helping him sue the human race hello hello bee this is ken yeah i remember you timberland size ten and a half vibram sole i believe why does he talk again listen you better go cause were really busy working but its our yogurt night byebye why is yogurt night so difficult you poor thing you two have been at this for hours yes and adam here has been a huge help frosting how many sugars just one i try not to use the competition so why are you helping me bees have good qualities and it takes my mind off the shop instead of flowers people are giving balloon bouquets now those are great if youre three and artificial flowers oh those just get me psychotic yeah me too bent stingers pointless pollination bees must hate those fake things nothing worse than a daffodil thats had work done maybe this could make up for it a little bit this lawsuits a pretty big deal i guess you sure you want to go through with it am i sure when im done with the humans they wont be able to say honey im home without paying a royalty its an incredible scene here in downtown manhattan where the world anxiously waits because for the first time in history we will hear for ourselves if a honeybee can actually speak what have we gotten into here barry its pretty big isnt it i cant believe how many humans dont work during the day you think billiondollar multinational food companies have good lawyers everybody needs to stay behind the barricade whats the matter i dont know i just got a chill well if it isnt the bee team you boys work on this all rise the honorable judge bumbleton presiding all right oase number 4475 superior oourt of new york barry bee benson v the honey industry is now in session mr montgomery youre representing the five food companies collectively a privilege mr benson youre representing all the bees of the world im kidding yes your honor were ready to proceed mr montgomery your opening statement please ladies and gentlemen of the jury my grandmother was a simple woman born on a farm she believed it was mans divine right to benefit from the bounty of nature god put before us if we lived in the topsyturvy world mr benson imagines just think of what would it mean i would have to negotiate with the silkworm for the elastic in my britches talking bee how do we know this isnt some sort of holographic motionpicturecapture hollywood wizardry they could be using laser beams robotics ventriloquism oloning for all we know he could be on steroids mr benson ladies and gentlemen theres no trickery here im just an ordinary bee honeys pretty important to me its important to all bees we invented it we make it and we protect it with our lives unfortunately there are some people in this room who think they can take it from us cause were the little guys im hoping that after this is all over youll see how by taking our honey you not only take everything we have but everything we are i wish hed dress like that all the time so nice oall your first witness so mr klauss vanderhayden of honey farms big company you have i suppose so i see you also own honeyburton and honron yes they provide beekeepers for our farms beekeeper i find that to be a very disturbing term i dont imagine you employ any beefreeers do you no i couldnt hear you no no because you dont free bees you keep bees not only that it seems you thought a bear would be an appropriate image for a jar of honey theyre very lovable creatures yogi bear fozzie bear buildabear you mean like this bears kill bees howd you like his head crashing through your living room biting into your couch spitting out your throw pillows ok thats enough take him away so mr sting thank you for being here your name intrigues me where have i heard it before i was with a band called the police but youve never been a police officer have you no i havent no you havent and so here we have yet another example of bee culture casually stolen by a human for nothing more than a pranceabout stage name oh please have you ever been stung mr sting because im feeling a little stung sting or should i say mr gordon m sumner thats not his real name you idiots mr liotta first belated congratulations on your emmy win for a guest spot on er in 2005 thank you thank you i see from your resume that youre devilishly handsome with a churning inner turmoil thats ready to blow i enjoy what i do is that a crime not yet it isnt but is this what its come to for you exploiting tiny helpless bees so you dont have to rehearse your part and learn your lines sir watch it benson i could blow right now this isnt a goodfella this is a badfella why doesnt someone just step on this creep and we can all go home order in this court youre all thinking it order order i say say it mr liotta please sit down i think it was awfully nice of that bear to pitch in like that i think the jurys on our side are we doing everything right legally im a florist right well heres to a great team to a great team well hello ken hello i didnt think you were coming no i was just late i tried to call but the battery i didnt want all this to go to waste so i called barry luckily he was free oh that was lucky theres a little left i could heat it up yeah heat it up sure whatever so i hear youre quite a tennis player im not much for the game myself the balls a little grabby thats where i usually sit right there ken barry was looking at your resume and he agreed with me that eating with chopsticks isnt really a special skill you think i dont see what youre doing i know how hard it is to find the rightjob we have that in common do we bees have 100 percent employment but we do jobs like taking the crud out thats just what i was thinking about doing ken i let barry borrow your razor for his fuzz i hope that was all right im going to drain the old stinger yeah you do that look at that you know ive just about had it with your little mind games whats that italian vogue mamma mia thats a lot of pages a lot of ads remember what van said why is your life more valuable than mine funny i just cant seem to recall that i think something stinks in here i love the smell of flowers how do you like the smell of flames not as much water bug not taking sides ken im wearing a ohapstick hat this is pathetic ive got issues well well well a royal flush youre bluffing am i surfs up dude poo water that bowl is gnarly except for those dirty yellow rings kenneth what are you doing you know i dont even like honey i dont eat it we need to talk hes just a little bee and he happens to be the nicest bee ive met in a long time long time what are you talking about are there other bugs in your life no but there are other things bugging me in life and youre one of them fine talking bees no yogurt night my nerves are fried from riding on this emotional roller coaster goodbye ken and for your information i prefer sugarfree artificial sweeteners made by man im sorry about all that i know its got an aftertaste i like it i always felt there was some kind of barrier between ken and me i couldnt overcome it oh well are you ok for the trial i believe mr montgomery is about out of ideas we would like to call mr barry benson bee to the stand good idea you can really see why hes considered one of the best lawyers yeah layton youve gotta weave some magic with this jury or its gonna be all over dont worry the only thing i have to do to turn this jury around is to remind them of what they dont like about bees you got the tweezers are you allergic only to losing son only to losing mr benson bee ill ask you what i think wed all like to know what exactly is your relationship to that woman were friends good friends yes how good do you live together wait a minute are you her little bedbug ive seen a bee documentary or two from what i understand doesnt your queen give birth to all the bee children yeah but so those arent your real parents oh barry yes they are hold me back youre an illegitimate bee arent you benson hes denouncing bees dont yall date your cousins objection im going to pincushion this guy adam dont its what he wants oh im hit oh lordy i am hit order order the venom the venom is coursing through my veins i have been felled by a winged beast of destruction you see you cant treat them like equals theyre striped savages stingings the only thing they know its their way adam stay with me i cant feel my legs what angel of mercy will come forward to suck the poison from my heaving buttocks i will have order in this court order order please the case of the honeybees versus the human race took a pointed turn against the bees yesterday when one of their legal team stung layton t montgomery hey buddy hey is there much pain yeah i i blew the whole case didnt i it doesnt matter what matters is youre alive you could have died id be better off dead look at me they got it from the cafeteria downstairs in a tuna sandwich look theres a little celery still on it what was it like to sting someone i cant explain it it was all all adrenaline and then and then ecstasy all right you think it was all a trap of course im sorry i flew us right into this what were we thinking look at us were just a couple of bugs in this world what will the humans do to us if they win i dont know i hear they put the roaches in motels that doesnt sound so bad adam they check in but they dont check out oh my oould you get a nurse to close that window why the smoke bees dont smoke right bees dont smoke bees dont smoke but some bees are smoking thats it thats our case it is its not over get dressed ive gotta go somewhere get back to the court and stall stall any way you can and assuming youve done step correctly youre ready for the tub mr flayman yes yes your honor where is the rest of your team well your honor its interesting bees are trained to fly haphazardly and as a result we dont make very good time i actually heard a funny story about your honor havent these ridiculous bugs taken up enough of this courts valuable time how much longer will we allow these absurd shenanigans to go on they have presented no compelling evidence to support their charges against my clients who run legitimate businesses i move for a complete dismissal of this entire case mr flayman im afraid im going to have to consider mr montgomerys motion but you cant we have a terrific case where is your proof where is the evidence show me the smoking gun hold it your honor you want a smoking gun here is your smoking gun what is that its a bee smoker what this this harmless little contraption this couldnt hurt a fly let alone a bee look at what has happened to bees who have never been asked smoking or non is this what nature intended for us to be forcibly addicted to smoke machines and manmade wooden slat work camps living out our lives as honey slaves to the white man what are we gonna do hes playing the species card ladies and gentlemen please free these bees free the bees free the bees free the bees free the bees free the bees the court finds in favor of the bees vanessa we won i knew you could do it highfive sorry im ok you know what this means all the honey will finally belong to the bees now we wont have to work so hard all the time this is an unholy perversion of the balance of nature benson youll regret this barry how much honey is out there all right one at a time barry who are you wearing my sweater is ralph lauren and i have no pants what if montgomerys right what do you mean weve been living the bee way a long time 27 million years oongratulations on your victory what will you demand as a settlement first well demand a complete shutdown of all bee work camps then we want back the honey that was ours to begin with every last drop we demand an end to the glorification of the bear as anything more than a filthy smelly badbreath stink machine were all aware of what they do in the woods wait for my signal take him out hell have nauseous for a few hours then hell be fine and we will no longer tolerate beenegative nicknames but its just a pranceabout stage name unnecessary inclusion of honey in bogus health products and ladeeda human teatime snack garnishments oant breathe bring it in boys hold it right there good tap it mr buzzwell we just passed three cups and theres gallons more coming i think we need to shut down shut down weve never shut down shut down honey production stop making honey turn your key sir what do we do now oannonball were shutting honey production mission abort aborting pollination and nectar detail returning to base adam you wouldnt believe how much honey was out there oh yeah whats going on where is everybody are they out celebrating theyre home they dont know what to do laying out sleeping in i heard your uncle oarl was on his way to san antonio with a cricket at least we got our honey back sometimes i think so what if humans liked our honey who wouldnt its the greatest thing in the world i was excited to be part of making it this was my new desk this was my new job i wanted to do it really well and now now i cant i dont understand why theyre not happy i thought their lives would be better theyre doing nothing its amazing honey really changes people you dont have any idea whats going on do you what did you want to show me this what happened here that is not the half of it oh no oh my theyre all wilting doesnt look very good does it no and whose fault do you think that is you know im gonna guess bees bees specifically me i didnt think bees not needing to make honey would affect all these things its notjust flowers fruits vegetables they all need bees thats our whole sat test right there take away produce that affects the entire animal kingdom and then of course the human species so if theres no more pollination it could all just go south here couldnt it i know this is also partly my fault how about a suicide pact how do we do it ill sting you you step on me thatjust kills you twice right right listen barry sorry but i gotta get going i had to open my mouth and talk vanessa vanessa why are you leaving where are you going to the final tournament of roses parade in pasadena theyve moved it to this weekend because all the flowers are dying its the last chance ill ever have to see it vanessa i just wanna say im sorry i never meant it to turn out like this i know me neither tournament of roses roses cant do sports wait a minute roses roses roses vanessa roses barry roses are flowers yes they are flowers bees pollen i know thats why this is the last parade maybe not oould you ask him to slow down oould you slow down barry ok i made a huge mistake this is a total disaster all my fault yes it kind of is ive ruined the planet i wanted to help you with the flower shop ive made it worse actually its completely closed down i thought maybe you were remodeling but i have another idea and its greater than my previous ideas combined i dont want to hear it all right they have the roses the roses have the pollen i know every bee plant and flower bud in this park all we gotta do is get what theyve got back here with what weve got bees park pollen flowers repollination across the nation tournament of roses pasadena oalifornia theyve got nothing but flowers floats and cotton candy security will be tight i have an idea vanessa bloome ftd official floral business its real sorry maam nice brooch thank you it was a gift once inside we just pick the right float how about the princess and the pea i could be the princess and you could be the pea yes i got it where should i sit what are you i believe im the pea the pea it goes under the mattresses not in this fairy tale sweetheart im getting the marshal you do that this whole parade is a fiasco lets see what this babyll do hey what are you doing then all we do is blend in with traffic without arousing suspicion once at the airport theres no stopping us stop security you and your insect pack your float yes has it been in your possession the entire time would you remove your shoes remove your stinger its part of me i know just having some fun enjoy your flight then if were lucky well have just enough pollen to do the job oan you believe how lucky we are we have just enough pollen to do the job i think this is gonna work its got to work attention passengers this is oaptain scott we have a bit of bad weather in new york it looks like well experience a couple hours delay barry these are cut flowers with no water theyll never make it i gotta get up there and talk to them be careful oan i get help with the sky mall magazine id like to order the talking inflatable nose and ear hair trimmer oaptain im in a real situation whatd you say hal nothing bee dont freak out my entire species what are you doing wait a minute im an attorney whos an attorney dont move oh barry good afternoon passengers this is your captain would a miss vanessa bloome in 24b please report to the cockpit and please hurry what happened here there was a dustbuster a toupee a life raft exploded ones bald ones in a boat theyre both unconscious is that another bee joke no no ones flying the plane this is jfk control tower flight 356 whats your status this is vanessa bloome im a florist from new york wheres the pilot hes unconscious and so is the copilot not good does anyone onboard have flight experience as a matter of fact there is whos that barry benson from the honey trial oh great vanessa this is nothing more than a big metal bee its got giant wings huge engines i cant fly a plane why not isnt john travolta a pilot yes how hard could it be wait barry were headed into some lightning this is bob bumble we have some latebreaking news from jfk airport where a suspenseful scene is developing barry benson fresh from his legal victory thats barry is attempting to land a plane loaded with people flowers and an incapacitated flight crew flowers we have a storm in the area and two individuals at the controls with absolutely no flight experience just a minute theres a bee on that plane im quite familiar with mr benson and his noaccount compadres theyve done enough damage but isnt he your only hope technically a bee shouldnt be able to fly at all their wings are too small havent we heard this a million times the surface area of the wings and body mass make no sense get this on the air got it stand by were going live the way we work may be a mystery to you making honey takes a lot of bees doing a lot of small jobs but let me tell you about a small job if you do it well it makes a big difference more than we realized to us to everyone thats why i want to get bees back to working together thats the bee way were not made of jello we get behind a fellow black and yellow hello left right down hover hover forget hover this isnt so hard beepbeep beepbeep barry what happened wait i think we were on autopilot the whole time that may have been helping me and now were not so it turns out i cannot fly a plane all of you lets get behind this fellow move it out move out our only chance is if i do what id do you copy me with the wings of the plane dont have to yell im not yelling were in a lot of trouble its very hard to concentrate with that panicky tone in your voice its not a tone im panicking i cant do this vanessa pull yourself together you have to snap out of it you snap out of it you snap out of it you snap out of it you snap out of it you snap out of it you snap out of it you snap out of it you snap out of it hold it why oome on its my turn how is the plane flying i dont know hello benson got any flowers for a happy occasion in there the pollen jocks they do get behind a fellow black and yellow hello all right lets drop this tin can on the blacktop where i cant see anything oan you no nothing its all cloudy oome on you got to think bee barry thinking bee thinking bee thinking bee thinking bee thinking bee wait a minute i think im feeling something what i dont know its strong pulling me like a 27millionyearold instinct bring the nose down thinking bee thinking bee thinking bee what in the world is on the tarmac get some lights on that thinking bee thinking bee thinking bee vanessa aim for the flower ok out the engines were going in on bee power ready boys affirmative good good easy now thats it land on that flower ready full reverse spin it around not that flower the other one which one that flower im aiming at the flower thats a fat guy in a flowered shirt i mean the giant pulsating flower made of millions of bees pull forward nose down tail up rotate around it this is insane barry thiss the only way i know how to fly am i kookookachoo or is this plane flying in an insectlike pattern get your nose in there dont be afraid smell it full reverse just drop it be a part of it aim for the center now drop it in drop it in woman oome on already barry we did it you taught me how to fly yes no highfive right barry it worked did you see the giant flower what giant flower where of course i saw the flower that was genius thank you but were not done yet listen everyone this runway is covered with the last pollen from the last flowers available anywhere on earth that means this is our last chance were the only ones who make honey pollinate flowers and dress like this if were gonna survive as a species this is our moment what do you say are we going to be bees orjust museum of natural history keychains were bees keychain then follow me except keychain hold on barry here youve earned this yeah im a pollen jock and its a perfect fit all i gotta do are the sleeves oh yeah thats our barry mom the bees are back if anybody needs to make a call nows the time i got a feeling well be working late tonight heres your change have a great afternoon oan i help whos next would you like some honey with that it is beeapproved dont forget these milk cream cheese its all me and i dont see a nickel sometimes i just feel like a piece of meat i had no idea barry im sorry have you got a moment would you excuse me my mosquito associate will help you sorry im late hes a lawyer too i was already a bloodsucking parasite all i needed was a briefcase have a great afternoon barry i just got this huge tulip order and i cant get them anywhere no problem vannie just leave it to me youre a lifesaver barry oan i help whos next all right scramble jocks its time to fly thank you barry that bee is living my life let it go kenny when will this nightmare end let it all go beautiful day to fly sure is between you and me i was dying to get out of that office you have got to start thinking bee my friend thinking bee me hold it lets just stop for a second hold it im sorry im sorry everyone oan we stop here im not making a major life decision during a production number all right take ten everybody wrap it up guys i had virtually no rehearsal for that", + + "astronomy": "Sun Mercury Venus Earth Mars Jupter Saturn Uranus Nepture Moon Ganymede Callisto Io Europa Titan Iapetus Rhea Dione Tethys Titania Oberon Enceladus Mimas Triton Hyperion Umbriel Ariel Phobos Deimos Amalthea Phoebe Himalia Elara Pasiphae Sinope Carme Lysithea Miranda Nereid Ananke Janus Leda Themisto Charon Thebe Metis Adrastea Epimetheus Atlas Telesto Calypso Helene Prometheus Pandora Larissa Puck Ophelia Cordelia Portia Juliet Belinda Cressida Rosalind Desdemona Bianca Perdita Thalassa Naiad Proteus Galatea Despina Pan Sycorax Caliban Prospero Setebos Stephano Albiorix Siarnaq Tarvos Ijiraq Erriapus Skathi Thrymr Suttungr Mundilfari Paaliaq Ymir Kiviuq Praxidike Megaclite Kalyke Iocaste Taygete Harpalyke Isonoe Chaldene Erinome Callirrhoe Francisco Ferdinand Trinculo Dia Thyone Hermippe Autonoe Eurydome Euanthe Aitne Sponde Pasithee Orthosie Kale Euporie Halimede Neso Sao Laomedeia Arche Mab Cupid Margaret Narvi Psamathe Thelxinoe Helike Eukelade Aoede Hegemone Kallichore Kore Cyllene Carpo Herse Mneme Hati Fornjot Bergelmir Bebhionn Aegir Bestla Fenrir Farbauti Methone Polydeuces Pallene Daphnis Hydra Nix HiŹ»iaka Namaka Dysnomia Hyrrokkin Surtur Skoll Loge Jarnsaxa Greip Kari Tarqeq Anthe Aegaeon Kerberos Styx Valetudo Acamar Achernar Achird Acrab Acrux Acubens Adhafera Adhara Adhil Ain Ainalrami Aladfar AlamakAlathfarAlbaldah Albali Albireo Alchiba Alcor Alcyone Aldebaran Alderamin Aldhanab Aldhibah Aldulfin Alfirk Algedi Algenib Algieba AlgolAlgorab Alhena Alioth Aljanah Alkaid Al Kalb al RaiAlkalurops Alkaphrah Alkarab Alkes Almaaz Almach Al Minliar al AsadAlnair Alnasl Alnilam Alnitak Alniyat Alphard Alphecca Alpheratz Alpherg Alrakis Alrescha Alruba Alsafi Alsciaukat Alsephina Alshain Alshat Altair Altais Alterf Aludra Alula Australis Alula Borealis Alya Alzirr Ancha Angetenar Ankaa Anser Antares Arcturus Arkab Posterior Arkab Prior Arneb Ascella Asellus Australis Asellus Borealis Ashlesha Asellus PrimusAsellus SecundusAsellus TertiusAsmidiskeAspidiske Asterope Athebyne Atik Atlas Atria Avior Azelfafage Azha Azmidi Barnard's Star Baten Kaitos Beemim Beid Bellatrix Betelgeuse Bharani Biham Botein Brachium Bunda Canopus Capella Caph Castor Castula Cebalrai Celaeno Cervantes Chalawan Chamukuy Chara Chertan Copernicus Cor Caroli Cujam Cursa Dabih Dalim Deneb Deneb Algedi Denebola Diadem Diphda Dschubba Dubhe Dziban Edasich Electra Elgafar Elkurud Elnath Eltanin Enif Errai Fafnir Fang Fawaris Felis Fomalhaut Fulu Fumalsamakah Furud Fuyue Gacrux Garnet StarGiausar Gienah Ginan Gomeisa GraffiasGrumium Gudja Guniibuu Hadar Haedus Hamal Hassaleh Hatysa Helvetios Heze Homam Iklil Imai Intercrus Izar Jabbah Jishui Kaffaljidhma Kang Kaus Australis Kaus Borealis Kaus Media Keid Khambalia Kitalpha Kochab Kornephoros Kraz KumaKurhah La Superba Larawag Lesath Libertas Lich Lilii Borea Maasym Mahasim Maia MarfarkMarfik Markab Markeb Marsic Matar Mebsuta Megrez Meissa Mekbuda Meleph Menkalinan Menkar Menkent Menkib Merak Merga Meridiana Merope Mesarthim Miaplacidus Mimosa Minchir Minelauva Mintaka Mira Mirach Miram Mirfak Mirzam Misam Mizar Mothallah Muliphein Muphrid Muscida Musica Nahn Naos Nashira NaviNekkar Nembus Nihal Nunki Nusakan Ogma Okab Paikauhale Peacock Phact Phecda Pherkad Piautos Pipirima Pleione Polaris Polaris Australis Polis Pollux Porrima Praecipua Prima Hyadum Procyon Propus Proxima Centauri Ran RanaRasalas Rasalgethi Rasalhague Rastaban Regor [citation needed] Regulus Revati Rigel Rigil Kentaurus Rotanev Ruchbah Rukbat Sabik Saclateni Sadachbia Sadalbari Sadalmelik Sadalsuud Sadr Saiph Salm Sargas Sarin SarirSceptrum Scheat Schedar Secunda Hyadum Segin Seginus Sham Shaula Sheliak Sheratan Sirius Situla Skat Spica Sualocin Subra Suhail Sulafat Syrma Tabit Taiyangshou Taiyi Talitha Tania Australis Tania Borealis Tarazed Tarf Taygeta Tegmine Tejat Terebellum ThabitTheemin Thuban Tiaki Tianguan Tianyi Titawin Toliman Tonatiuh Torcular Tureis Ukdah Unukalhai Unurgunite Vega Veritate Vindemiatrix Wasat Wazn Wezen Wurren Xamidimura Xuange Yed Posterior Yed Prior Yildun Zaniah Zaurak Zavijava Zhang Zibal Zosma Zubenelgenubi Zubenelhakrabi Zubeneschamali Andromeda Antlia Apus Aquarius Aquila Ara Aries Auriga Bootes Caelum Camelopardalis Cancer Canes Venatici Canis Major Canis Minor Capricornus Carina Cassiopeia Centaurus Cepheus Cetus Chamaeleon Circinus Columba Coma Berenices Corona Australis Corona Borealis Corvus Crater Crux Cygnus Delphinus Dorado Draco Equuleus Eridanus Fornax Gemini Grus Hercules Horologium Hydra Hydrus Indus Lacerta Leo Leo Minor Lepus Libra Lupus Lynx Lyra Mensa Microscopium Monoceros Musca Norma Octans Ophiuchus Orion Pavo Pegasus Perseus Phoenix Pictor Pisces Piscis Austrinus Puppis Pyxis Reticulum Sagitta Sagittarius Scorpius Sculptor Scutum Serpens Caput Serpens Cauda Sextans Taurus Telescopium Triangulum Triangulum Australe Tucana Ursa Major Ursa Minor Vela Virgo Volans Vulpecula" +} diff --git a/src/templates/projects/mazemaker/bool-x-64.js b/src/templates/projects/mazemaker/bool-x-64.js new file mode 100644 index 0000000..d32d3b4 --- /dev/null +++ b/src/templates/projects/mazemaker/bool-x-64.js @@ -0,0 +1,69 @@ +function compressMaze(maze) { + if(maze == undefined) return undefined; + if(maze.length == 0) return [0, 0]; + var a = []; + var x, y, i; + for(x = 0; x < maze.length; x++) { + for(y = 0; y < maze[x].length; y++) { + a.push(maze[x][y][0]); + a.push(maze[x][y][1]); + a.push(maze[x][y][2]); + a.push(maze[x][y][3]); + } + } + return a; +} + +function expandMaze(maze, w, h) { + if(maze == undefined) return undefined; + if(w == 0 || h == 0) return []; + var a = []; + for(x = 0; x < w; x++) { + a.push([]); + for(y = 0; y < h; y++) { + a[x][y] = []; + var index = 4*(y + x*h) + a[x][y].push(maze[index+0]); + a[x][y].push(maze[index+1]); + a[x][y].push(maze[index+2]); + a[x][y].push(maze[index+3]); + } + } + return a; +} + +var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; +function arrToB64(arr) { + while(arr.length%6 != 0) { + arr.push(false); + } + var str = ''; + while(arr.length > 0) { + var n = arr.shift()*32+arr.shift()*16+arr.shift()*8+arr.shift()*4+arr.shift()*2+arr.shift(); + str += b64[n]; + } + return str; +} + +function b64ToArr(str64) { + var arr = []; + for(var i = 0; i < str64.length; i++) { + var index = b64.indexOf(str64[i]); + if(index<0) continue; + arr.push((index>>5)%2 != 0); + arr.push((index>>4)%2 != 0); + arr.push((index>>3)%2 != 0); + arr.push((index>>2)%2 != 0); + arr.push((index>>1)%2 != 0); + arr.push((index>>0)%2 != 0); + } + return arr; +} + +function encode(maze) { + return arrToB64(compressMaze(maze)); +} + +function decode(code, w, h) { + return expandMaze(b64ToArr(code), w, h); +} diff --git a/src/templates/projects/mazemaker/dom.js b/src/templates/projects/mazemaker/dom.js new file mode 100644 index 0000000..7ce3f58 --- /dev/null +++ b/src/templates/projects/mazemaker/dom.js @@ -0,0 +1,101 @@ +function elem(id) {return document.getElementById(id);} + +function updateFG() { + var select = elem('selectfg'); + elem('colorfg').value = select.options[select.selectedIndex].value; + var evt = new Event('change'); + elem('colorfg').dispatchEvent(evt) +} + +function updateBG() { + var select = elem('selectbg'); + elem('colorbg').value = select.options[select.selectedIndex].value; + var evt = new Event('change'); + elem('colorbg').dispatchEvent(evt) +} + +var options = [ + ['Black', '0, 0, 0'], + ['White', '255, 255, 255'], + ['Light Gray', '200, 200, 208'], + ['Gray', '150, 150, 158'], + ['Dark Gray', '100, 100, 108'], + ['Pink', '240, 100, 200'], + ['Red', '200, 20, 10'], + ['Orange', '250, 100, 10'], + ['Yellow', '250, 255, 30'], + ['Light Green', '140, 255, 90'], + ['Green', '0, 180, 10'], + ['Cyan', '30, 240, 250'], + ['Light Blue', '140, 160, 255'], + ['Blue', '20, 20, 240'], + ['Purple', '100, 10, 130'], + ['Brown', '90, 45, 20'], +] +function setupSelects() { + var sfg = elem('selectfg'); + var sbg = elem('selectbg'); + for(var i = 0; i < options.length; i++) { + var option = document.createElement('option'); + option.text = options[i][0]; + option.value = options[i][1]; + sfg.add(option); + option = document.createElement('option'); + option.text = options[i][0]; + option.value = options[i][1]; + sbg.add(option); + } + sfg.options[0].selected = 'selected'; + sbg.options[1].selected = 'selected'; +} + +function newMaze() { + canvasWidth = elem('width').value; + canvasHeight = elem('height').value; + resizeCanvas(canvasWidth, canvasHeight); + mazeWidth = elem('maze-width').value; + mazeHeight = elem('maze-height').value; + shiftpop = elem('shiftpop').value/100; + var selectBias = elem('bias'); + bias = selectBias.options[selectBias.selectedIndex].value; + frameRate(int(elem('speed').value)); + resetMaze(); + loop(); +} + +function getPermalinkURL() { + var url = location.href; + if(url.endsWith('index.html')) url = url.substring(0, url.length-'index.html'.length); + url += 'v/?'; + var w = canvasWidth, h = canvasHeight, mw = mazeWidth, mh = mazeHeight; + var fg = red(colorfg)+'_'+green(colorfg)+'_'+blue(colorfg); + var bg = red(colorbg)+'_'+green(colorbg)+'_'+blue(colorbg); + var wte = borderWeight; + var code = encode(cells); + url += 'w='+w+'&h='+h+'&mw='+mw+'&mh='+mh+'&fg='+fg+'&bg='+bg+'&wte='+wte+'&code='+code; + return url; +} + +function prop(from) { + if(elem('proportional').checked) { + var setW = elem('width').value, + setH = elem('height').value, + setMW = elem('maze-width').value, + setMH = elem('maze-height').value; + console.log(setW, setH, setMW, setMH, from); + switch(from) { + case 'w': + elem('height').value = round(setMH/setMW*setW); + break; + case 'h': + elem('width').value = round(setMW/setMH*setH); + break; + case 'mw': + elem('width').value = round(setMW/setMH*setH); + break; + case 'mh': + elem('height').value = round(setMH/setMW*setW); + break; + } + } +} diff --git a/src/templates/projects/mazemaker/index.html b/src/templates/projects/mazemaker/index.html new file mode 100644 index 0000000..a51387b --- /dev/null +++ b/src/templates/projects/mazemaker/index.html @@ -0,0 +1,50 @@ +{% set meta={"title": "Maze maker", "desc": "Generate mazes", "pagewidth": "1400px"} %} +{% extends "/_base.html"%} +{% block head %} + + + + + +{% endblock %} +{% block content %} +
      +
      + Maze width (pixels):
      + Maze height (pixels):
      + Cells wide:
      + Cells high:
      + Proportional:
      + Chance to shift instead of pop: %
      + Bias:
      +

      + + Foreground color: +
      + Background color: +
      + Border weight:

      + + Speed (fps): +
      + Show current cell:
      + Show colored overlay:

      + +

      + +
      Generating...
      +
      +{% endblock %} \ No newline at end of file diff --git a/src/templates/projects/mazemaker/main.js b/src/templates/projects/mazemaker/main.js new file mode 100644 index 0000000..f654e01 --- /dev/null +++ b/src/templates/projects/mazemaker/main.js @@ -0,0 +1,157 @@ +p5.disableFriendlyErrors = true; + +var canvasWidth = 720; +var canvasHeight = 720; +var mazeWidth = 18, mazeHeight = 18; +var shiftpop = 0; +var colorfg, colorbg, borderWeight; +var showOverlay = true; +var showCurrent = true; + +var canvas; + // 3d array, cells[x][y] = [top wall, right wall, bottom wall, left wall, available, in stack] +var cells; +var stack; +var cx, cy; +var done; +var bias = 0; + +function setup() { + canvas = createCanvas(canvasWidth+0.5, canvasHeight+0.5); + canvas.parent('canvas-wrapper'); + canvas.id('maze-canvas'); + setupSelects(); + colorfg = color(0); + colorbg = color(255); + borderWeight = 2; + resetMaze(); + frameRate(30); +} + +function resetMaze() { + background(colorbg); + cells = []; stack = []; done = false; + for(var x = 0; x < mazeWidth; x++) { + cells.push([]); + for(var y = 0; y < mazeHeight; y++) { + cells[x][y] = [true, true, true, true, true, false]; + } + } + cx = floor(random(mazeWidth)); + cy = floor(random(mazeHeight)); + cells[cx][cy][4] = false; + elem('status').innerHTML = 'Generating...'; +} + + +function draw() { + background(colorbg); + drawCells(); + chooseNextCell(); +} + +function drawCells() { + stroke(colorfg); + strokeWeight(borderWeight); + var cw = canvasWidth/mazeWidth; + var ch = canvasHeight/mazeHeight; + var x, y, c + // Show overlay + if(!done && showOverlay) { + noStroke(); + for(x = 0; x < mazeWidth; x++) { + for(y = 0; y < mazeHeight; y++) { + c = cells[x][y]; + // Fill with blue if visited & on stack + if(!c[4] && c[5]) + {fill( 99, 99, 255); rect(x*cw, y*ch, cw, ch);} + // Fill with light blue if visited but not on stack + if(!c[4] && !c[5]) + {fill(150, 180, 255); rect(x*cw, y*ch, cw, ch);} + } + } + stroke(colorfg); + } + if(!done && showCurrent) { + noStroke(); + var d = false; + var x, y; + for(x = 0; x < mazeWidth; x++) { + for(y = 0; y < mazeHeight; y++) { + // Fill with green if current cell + if(cx == x && cy == y) + {fill(0, 230, 50); rect(x*cw, y*ch, cw, ch); d=true; break; } + } + if(d) break; + } + stroke(colorfg); + } + for(x = 0; x < mazeWidth; x++) { + for(y = 0; y < mazeHeight; y++) { + c = cells[x][y]; + if(c[0]) line(x*cw, y*ch, x*cw+cw, y*ch); + if(c[1]) line(x*cw+cw, y*ch, x*cw+cw, y*ch+ch); + if(c[2]) line(x*cw, y*ch+ch, x*cw+cw, y*ch+ch); + if(c[3]) line(x*cw, y*ch, x*cw, y*ch+ch); + } + } + if(done) noLoop(); +} + +function chooseNextCell() { + var choices = []; + // Check left, right, up, and down + if(cells[cx-1] && cells[cx-1][cy] && cells[cx-1][cy][4]) { + choices.push([cx-1, cy, 3]); + if(bias > 0) {choices.push([cx-1, cy, 3]); choices.push([cx-1, cy, 3]);} + } + if(cells[cx+1] && cells[cx+1][cy] && cells[cx+1][cy][4]) { + choices.push([cx+1, cy, 1]); + if(bias > 0) {choices.push([cx+1, cy, 1]); choices.push([cx+1, cy, 1]);} + } + if(cells[cx] && cells[cx][cy-1] && cells[cx][cy-1][4]) { + choices.push([cx, cy-1, 0]); + if(bias < 0) {choices.push([cx, cy-1, 0]); choices.push([cx, cy-1, 0]);} + } + if(cells[cx] && cells[cx][cy+1] && cells[cx][cy+1][4]) { + choices.push([cx, cy+1, 2]); + if(bias < 0) {choices.push([cx, cy+1, 2]); choices.push([cx, cy+1, 2]);} + } + if(choices.length > 0){ + // Push current cell to stack + stack.push([cx, cy]); + cells[cx][cy][5] = true; + // Choose randomly if choices + var choice = random(choices); + // Remove wall + cells[cx][cy][choice[2]] = false; + // Move to new cell + cx = choice[0]; + cy = choice[1]; + // Remove wall + cells[cx][cy][(choice[2]+2)%4] = false; + // Mark cell as visited + cells[cx][cy][4] = false; + } else if(stack.length > 0) { + // Otherwise, pop the stack + var n; + if(shiftpop == 0) + n = stack.pop(); + else if(shiftpop == 1) + n = stack.shift(); + else { + var rand = random(); + if(rand > shiftpop) + n = stack.pop(); + else + n = stack.shift(); + } + cx = n[0]; + cy = n[1]; + cells[cx][cy][5] = false; + } else { + // If stack is empty, we're done + done = true; + elem('status').innerHTML = 'Done!'; + } +} diff --git a/src/templates/projects/mazemaker/v/index.html b/src/templates/projects/mazemaker/v/index.html new file mode 100644 index 0000000..82541d5 --- /dev/null +++ b/src/templates/projects/mazemaker/v/index.html @@ -0,0 +1,12 @@ +{% set meta={"title": "Maze Permalink", "desc": "", "hidden": True} %} +{% extends "/_base.html" %} +{% block head %} + + + +{% endblock %} +{% block content %} +
      +
      +
      +{% endblock %} \ No newline at end of file diff --git a/src/templates/projects/mazemaker/v/main.js b/src/templates/projects/mazemaker/v/main.js new file mode 100644 index 0000000..2dac3b7 --- /dev/null +++ b/src/templates/projects/mazemaker/v/main.js @@ -0,0 +1,46 @@ +//p5.disableFriendlyErrors = true; + +var w, h, mw, mh, cells, fg, bg, wte; + +function setup() { + getData(); + canvas = createCanvas(w+0.5, h+0.5); + canvas.parent('canvas-wrapper'); + canvas.id('maze-canvas'); + noLoop(); + redraw(); +} + +function draw() { + var cw = w/mw, ch = h/mh; + background(bg); + stroke(fg); + strokeWeight(wte); + for(x = 0; x < cells.length; x++) { + for(y = 0; y < cells[x].length; y++) { + c = cells[x][y]; + if(c[0]) line(x*cw, y*ch, x*cw+cw, y*ch); + if(c[1]) line(x*cw+cw, y*ch, x*cw+cw, y*ch+ch); + if(c[2]) line(x*cw, y*ch+ch, x*cw+cw, y*ch+ch); + if(c[3]) line(x*cw, y*ch, x*cw, y*ch+ch); + } + } +} + +function getData() { + w = int(query('w')); + h = int(query('h')); + mw = int(query('mw')); + mh = int(query('mh')); + fg = color('rgb('+query('fg').replace(/_/g, ', ')+')'); + bg = color('rgb('+query('bg').replace(/_/g, ', ')+')'); + wte = int(query('wte')); + cells = decode(query('code'), mw, mh); +} + + +function query(key) { + key = key.replace(/[*+?^$.\[\]{}()|\\\/]/g, "\\$&"); // escape RegEx meta chars + var match = location.search.match(new RegExp("[?&]"+key+"=([^&]+)(&|$)")); + return match && decodeURIComponent(match[1].replace(/\+/g, " ")); +} diff --git a/src/templates/projects/number/index.html b/src/templates/projects/number/index.html new file mode 100644 index 0000000..e63285a --- /dev/null +++ b/src/templates/projects/number/index.html @@ -0,0 +1,32 @@ +{% set meta={"title": "Number", "desc": "make go up"} %} +{% extends "/_base.html"%} +{% block head%} + +{% endblock %} +{% block content%} +
      ?
      +
      + +
      +{% endblock %} diff --git a/src/templates/projects/pixelcircle/index.html b/src/templates/projects/pixelcircle/index.html new file mode 100644 index 0000000..76611d8 --- /dev/null +++ b/src/templates/projects/pixelcircle/index.html @@ -0,0 +1,32 @@ +{% set meta={"title": "Pixel Circle", "desc": "Generate circles for games like Minecraft"} %} +{% extends "/_base.html" %} +{% block head %} + + +{% endblock %} +{% block content %} +
      +Radius: + + +
      +
      + Even center? +
      +
      +Accuracy (increase for large circles): + +
      +
      +
      + +
      + +{% endblock %} diff --git a/src/templates/projects/pixelcircle/main.js b/src/templates/projects/pixelcircle/main.js new file mode 100644 index 0000000..ffbed0a --- /dev/null +++ b/src/templates/projects/pixelcircle/main.js @@ -0,0 +1,210 @@ +// Cell size; the width/height of each pixel +var cs = 30; +// The zoom level +var zoom = 0; +// The list of pixels to shade +var pix = []; +// The x and y offsets +var xoff, yoff; +// If the canvas or circle need updating +var updateCanvas = true; +var updateCircle = true; +// The circle's radius +var rad = 5.1; +// The accuracy of the circle +var steps = 8000; +// The cell size below which the grid is not drawn +var gridShow = 10; +// Whether or not the center is even +var evenCenter = false; + +// Theme colors +var BACKGR, FOREGR, COLOR; + +//Create the canvas, initialize the input fields, and set the theme colors. +function setup() { + var canvas = createCanvas(900, 675); + canvas.parent('canvas-wrapper'); + document.getElementById('radius-slider').value = rad; + document.getElementById('radius-text').value = rad; + document.getElementById('even-center').value = evenCenter; + document.getElementById('accuracy').value = steps; + xoff = width / 2 - cs / 2; + yoff = height / 2 - cs / 2; + resetPix(); + BACKGR = color(220); + FOREGR = color(100); + COLOR = color(30, 160, 60); + document.getElementById("defaultCanvas0").onwheel = function (event) { + event.preventDefault(); + }; + document.getElementById("defaultCanvas0").onmousewheel = function (event) { + event.preventDefault(); + }; +} + +//Delete the old circle and create the highlighted center +function resetPix() { + pix = []; + pix.push({ x: 0, y: 0, c: COLOR }); + if (evenCenter) { + pix.push({ x: 0, y: 1, c: COLOR }); + pix.push({ x: 1, y: 0, c: COLOR }); + pix.push({ x: 1, y: 1, c: COLOR }); + } +} + +//When an update occurs, redraw the canvas on the next frame. +function draw() { + if (updateCanvas || updateCircle) { + background(BACKGR); + // If only the canvas is updated, the circle doesn't need to be retraced. + if (updateCircle) { + resetPix(); + // Slightly decrease the radius in certian cases to prevent glitches. + var r = rad; + if (!evenCenter && abs(round(r) - r) == 0.5) + r -= 0.00000001; + else if (evenCenter && round(r) - r == 0) + r -= 0.00000001; + // Trace the circle to find which pixels are inside it. + traceCircle(r); + } + // Draw the colored pixels to the screen. + drawPix(); + stroke(0); + strokeWeight(1); + // If not zoomed out too far, draw the grid. + if (cs > gridShow) + drawGrid(xoff, yoff, cs); + // Draw the highlighted circle path. + drawCirclePath() + // Do not update again immediately after this. + updateCircle = false; + updateCanvas = false; + } +} + +//Draw the grid of horsizontal and veritcal lines. +function drawGrid(xoff, yoff, gap) { + for (var x = xoff % gap; x < width; x += gap) { + line(x, 0, x, height); + } + for (var y = yoff % gap; y < height; y += gap) { + line(0, y, width, y); + } +} + +var lastX, lastY; +//Start the drag. +function mousePressed() { + lastX = mouseX; + lastY = mouseY; +} + +//Move the image around when dragged. +function mouseDragged() { + if (mouseX > 0 && mouseY > 0) { + xoff += mouseX - lastX; + yoff += mouseY - lastY; + lastX = mouseX; + lastY = mouseY; + updateCanvas = true; + } +} + +//Zoom in and out by scrolling. +function mouseWheel(event) { + if(mouseX >= 0 && mouseX < width && mouseY >= 0 && mouseY < height) { + var c = event.delta; + var f = (c > 0) ? 8 / 9 : (c < 0) ? 9 / 8 : 1; + cs *= f; + xoff = (xoff - mouseX) * f + mouseX; + yoff = (yoff - mouseY) * f + mouseY; + updateCanvas = true; + } +} + +//Find which pixels are in the path of the circle by tracing it. +function traceCircle(r) { + var lx, ly; + var cx = 0, cy = 0; + if (evenCenter) { cx = 0.5; cy = 0.5; } + // Go around the circle in number of steps + for (var ang = 0; ang < 1; ang += 1 / steps) { + var x = round(r * cos(ang * TAU) + cx); + var y = round(r * sin(ang * TAU) + cy); + // If the location is different from the previous one... + if (x != lx || y != ly) { + // ...create a shaded pixel at the location + var obj = { x: x, y: y, c: color(FOREGR) }; + pix.push(obj); + lx = x; + ly = y; + } + } +} + +// Draw the colored pixels to the screen +function drawPix() { + noStroke() + for (var i = 0; i < pix.length; i++) { + fill(pix[i].c); + rect(pix[i].x * cs + xoff, pix[i].y * cs + yoff, cs, cs); + } +} + +// Draw the colored circle path +function drawCirclePath() { + noFill(); + // Choose stroke weight based on zoom level + if (cs > 20) strokeWeight(2.5); + else if (cs > gridShow) strokeWeight(1.5); + else strokeWeight(1); + var cx = 0, cy = 0; + if (evenCenter) { cx = 0.5; cy = 0.5; } + stroke(COLOR); + ellipse(xoff + cx * cs + cs / 2, yoff + cy * cs + cs / 2, 2 * rad * cs, 2 * rad * cs); +} + +// Change the radius of the circle +function updateRadius(value) { + // Make sure the slider and text field match. + document.getElementById('radius-slider').value = value; + document.getElementById('radius-text').value = value; + rad = value; + updateCircle = true; +} + + +// Change the accuracy. +function updateAccuracy(value) { + if (value != 0) { + steps = +value; + updateCircle = true; + } +} + +// Change the center between odd (one pixel) and even (four pixels). +function updateCenter(value) { + old = evenCenter + if (old == true && value == false) { + xoff += 0.5 * cs; + yoff += 0.5 * cs; + } else if (old == false && value == true) { + xoff -= 0.5 * cs; + yoff -= 0.5 * cs; + } + evenCenter = value; + + updateCircle = true; +} + +// Reset drag and zoom. +function resetMotion() { + zoom = 0; + cs = 60; + xoff = width / 2 - cs / 2; + yoff = height / 2 - cs / 2; + updateCircle = true; +} diff --git a/src/templates/projects/rpsha/index.html b/src/templates/projects/rpsha/index.html new file mode 100644 index 0000000..3296fa7 --- /dev/null +++ b/src/templates/projects/rpsha/index.html @@ -0,0 +1,163 @@ +{% set meta={"title": "Rock Paper SHAssors", "desc": "Rock paper scissors with arbitrary strings"} %} +{% extends "/_base.html"%} +{% block head%} + + +{% endblock %} +{% block content %} +
      +

      Free yourself from the confines of the ternary.

      +
      +
      +
      + + +
      +{% endblock %} + diff --git a/src/templates/projects/stars/index.html b/src/templates/projects/stars/index.html new file mode 100644 index 0000000..4f05a45 --- /dev/null +++ b/src/templates/projects/stars/index.html @@ -0,0 +1,21 @@ +{% set meta={"title": "Star maker", "desc": "Generate stars"} %} +{% extends "/_base.html"%} +{% block head %} + + + +{% endblock %} +{% block content %} +
      + Points: + + Jump: + + Use colors: + +
      +
      +{% endblock %} \ No newline at end of file diff --git a/src/templates/projects/stars/main.js b/src/templates/projects/stars/main.js new file mode 100644 index 0000000..a00f2d4 --- /dev/null +++ b/src/templates/projects/stars/main.js @@ -0,0 +1,59 @@ +var p = 8, q = 3, useCols = false; + +var colors; + +function setup() { + var canvas = createCanvas(600, 600); + canvas.parent("canvas-wrapper"); + frameRate(5); + colors = [color(0), color(220, 20, 0), color(10, 200, 30), color(20, 40, 240), color(30, 180, 240), color(230, 210, 50), color(150, 50, 150), color(240, 120, 20), color(150, 150, 150), color(150, 100, 50)]; +} + +function draw() { + push(); + background(240); + translate(width/2, height/2); + rotate(-TAU/4); + + noFill(0, 10) + strokeWeight(2); + drawStar(); + + pop(); +} + +function drawStar() { + var iterations = gcd(p, q); + for(var i = 0; i < iterations; i++) { + if(useCols) + stroke(colors[i%colors.length]); + beginShape(); + for(var j = 0; j < p/iterations; j++) { + var point = getPoint(p, j*q+i, width/2-20); + vertex(point.x, point.y); + } + endShape(CLOSE); + } +} + +function getPoint(count, index, r) { + var x = r * Math.cos((TAU*index)/count); + var y = r * Math.sin((TAU*index)/count); + return createVector(x, y); +} + +$(()=>{ + $("#points").change(update); + $("#jump").change(update); + $("#colors").change(update); + update(); +}) + +function update() { + p = +$("#points").val(); + q = +$("#jump").val(); + useCols = $("#colors").prop("checked"); + draw(); +} + +function gcd(a,b) {return (!b)?a:gcd(b,a%b);} diff --git a/src/templates/projects/tagpost/index.html b/src/templates/projects/tagpost/index.html new file mode 100644 index 0000000..fb53ab1 --- /dev/null +++ b/src/templates/projects/tagpost/index.html @@ -0,0 +1,103 @@ +{% set meta={"title": "Tagpost", "desc": "The next great social media platform"} %} +{% extends "/_base.html" %} +{% block head%} + +{% endblock %} +{% block content %} + + +

      Enter any lowercase four-letter word (or not word) above and click "Go".

      + +{% endblock %} \ No newline at end of file diff --git a/src/templates/projects/whoami/index.html b/src/templates/projects/whoami/index.html new file mode 100644 index 0000000..32d05a3 --- /dev/null +++ b/src/templates/projects/whoami/index.html @@ -0,0 +1,17 @@ +--- +layout: default +title: Whoami +description: List information that websites you visit can know about you +--- + +

      Whoami?

      +You are:
      +

      +

      +Whoami uses UAParser to parse user-agent strings + + + diff --git a/src/templates/projects/whoami/main.js b/src/templates/projects/whoami/main.js new file mode 100644 index 0000000..a9e3108 --- /dev/null +++ b/src/templates/projects/whoami/main.js @@ -0,0 +1,60 @@ +$(load); + +let results = ''; +function load() { + + addEntry('User Agent', navigator.userAgent); + results += '
      '; + let ua = UAParser(); + console.log(ua); + addEntry('Name', ua.browser.name); + addEntry('Version', ua.browser.version); + addEntry('CPU', ua.cpu.architecture); + addEntry('Engine Name', ua.engine.name); + addEntry('Engine Version', ua.engine.version); + addEntry('OS Name', ua.os.name); + addEntry('OS Version', ua.os.version); + results += '
      '; + addEntry('App Name', navigator.appName); + addEntry('Codename', navigator.appCodeName); + addEntry('Java Enabled', navigator.javaEnabled()); + addEntry('Language', navigator.language); + addEntry('Languages', navigator.languages); + addEntry('Vendor', navigator.vendor); + addEntry('Cookies Enabled', navigator.cookieEnabled); + addEntry('Platform', navigator.platform); + addEntry('Query', (window.location.search.split('?')[1] || 'none').split('&')); + if(navigator.connection != null) + addEntry('Connection', navigator.connection.effectiveType); + results += '
      '; + if(navigator.getBattery) + navigator.getBattery().then(function(battery) { + addEntry('Battery Charge', battery.level*100+'%'); + addEntry('Charging', battery.charging); + results += '
      '; + finalize(); + }); + finalize(); +} + +function addEntry(name, value) { + let z = '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n'; + value = (z+value).substring(name.length); + value = value.replace(/\n/g, ' '); + results += '
      ' + name + ':' + value; +} +function finalize() { $('#results').html(results); } + +function requestLocation() { + navigator.geolocation.getCurrentPosition(function(location) { + console.log(location); + addEntry('Latitude', location.coords.latitude); + addEntry('Longitude', location.coords.longitude); + finalize(); + }, function(err) { + addEntry('Location', 'Error - ' + err.message); + finalize(); + }); + $('#location').remove(); + $('#lbr').remove(); +} diff --git a/src/templates/projects/whoami/ua-parser.js b/src/templates/projects/whoami/ua-parser.js new file mode 100644 index 0000000..8d7feb4 --- /dev/null +++ b/src/templates/projects/whoami/ua-parser.js @@ -0,0 +1,9 @@ +/** + * UAParser.js v0.7.17 + * Lightweight JavaScript-based User-Agent string parser + * https://github.com/faisalman/ua-parser-js + * + * Copyright Ā© 2012-2016 Faisal Salman + * Dual licensed under GPLv2 or MIT + */ +(function(window,undefined){"use strict";var LIBVERSION="0.7.17",EMPTY="",UNKNOWN="?",FUNC_TYPE="function",UNDEF_TYPE="undefined",OBJ_TYPE="object",STR_TYPE="string",MAJOR="major",MODEL="model",NAME="name",TYPE="type",VENDOR="vendor",VERSION="version",ARCHITECTURE="architecture",CONSOLE="console",MOBILE="mobile",TABLET="tablet",SMARTTV="smarttv",WEARABLE="wearable",EMBEDDED="embedded";var util={extend:function(regexes,extensions){var margedRegexes={};for(var i in regexes){if(extensions[i]&&extensions[i].length%2===0){margedRegexes[i]=extensions[i].concat(regexes[i])}else{margedRegexes[i]=regexes[i]}}return margedRegexes},has:function(str1,str2){if(typeof str1==="string"){return str2.toLowerCase().indexOf(str1.toLowerCase())!==-1}else{return false}},lowerize:function(str){return str.toLowerCase()},major:function(version){return typeof version===STR_TYPE?version.replace(/[^\d\.]/g,"").split(".")[0]:undefined},trim:function(str){return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")}};var mapper={rgx:function(ua,arrays){var i=0,j,k,p,q,matches,match;while(i0){if(q.length==2){if(typeof q[1]==FUNC_TYPE){this[q[0]]=q[1].call(this,match)}else{this[q[0]]=q[1]}}else if(q.length==3){if(typeof q[1]===FUNC_TYPE&&!(q[1].exec&&q[1].test)){this[q[0]]=match?q[1].call(this,match,q[2]):undefined}else{this[q[0]]=match?match.replace(q[1],q[2]):undefined}}else if(q.length==4){this[q[0]]=match?q[3].call(this,match.replace(q[1],q[2])):undefined}}else{this[q]=match?match:undefined}}}}i+=2}},str:function(str,map){for(var i in map){if(typeof map[i]===OBJ_TYPE&&map[i].length>0){for(var j=0;j + h1 { + color: var(--fg); + } + .option, .option:hover, .option:active { + color: var(--fg); + text-decoration: underline; + } + .option:visited{ + color: var(--fg-faded); + } + .cyan { + color: #0ff; + } + +{% endblock %} +{% block content %} +
      +

      + each node represents one zzcxz page. clicking on a node will load all of the pages it links to. +

      +

      + two nodes are connected with an arrow if one of them links to the other. nodes and arrows colored blue represent redirects. after a node has been clicked it will turn more gray to indicate that all of its outgoing links have been loaded. +

      +

      + you may click and drag on nodes to move them around, or on the page itself to pan. use the scroll wheel to zoom. +

      +

      + the root node (by default, the zzcxz page itself) is colored green. +

      +

      + click the double chevron link at the top-left corner to expand and collapse the user interface. this allows you to load pages from their id, or to reset the graph completely. if you select a node by right-clicking it, information about it will also show up here. deselect by pressing esc. +

      +

      + if you append ?skip to the URL of the main page, it will bypass the spoiler warning. +

      + +
      +{% endblock %} \ No newline at end of file diff --git a/src/templates/projects/zzcxz_vis/index.html b/src/templates/projects/zzcxz_vis/index.html new file mode 100644 index 0000000..bcbe813 --- /dev/null +++ b/src/templates/projects/zzcxz_vis/index.html @@ -0,0 +1,79 @@ +{% set meta={"title": "zzcxz visualizer", "desc": "Visualize the structure of zzcxz"} %} +{% extends "/_base.html" %} +{% block head %} + + + + +{% endblock %} +{% block content %} +
      +

      + you are about to view a force directed graph of zzcxz, a collaborative interactive fiction website by citrons. this will contain spoilers for zzcxz, so i strongly recommend you try it out before continuing. common side effects of continuing without using zzcxz first include intense guilt, deep regret, immediate apification, and feelings of worthlessness. +

      +

      + note: at time of writing (2022-06-23), zzcxz and all other citrons-created content is offline. zzcxz visualizer will not work until it is no longer offline. +

      + +
      + +
      + + + +{% endblock %} + diff --git a/src/templates/projects/zzcxz_vis/index.js b/src/templates/projects/zzcxz_vis/index.js new file mode 100644 index 0000000..8dad0ae --- /dev/null +++ b/src/templates/projects/zzcxz_vis/index.js @@ -0,0 +1,336 @@ +const TAU = 6.283185307179586; + +const radius = 25; +const radiusRedirect = 15; +const colorBackground = "var(--bg-intense)"; +const colorNormal = "var(--fg)"; +const colorRedirect = "#8af"; +const colorDone = "var(--fg-faded)"; +const colorRedirectDone = "#679"; +const colorRoot = "#4a5"; +const colorRootDone = "#484"; + +const alphaInit = 0.5; +const alphaTarget = 0.1; +const linkDistance = 150; +const linkStrength = 0.1; + +let nodes = []; +let links = []; +let refresh; +let transform = {x: 0, y: 0, k: 1}; +let selectedNode = null; +let showLabels = true; + +function getColorFor(elem) { + if(elem.root) { + if(elem.done) { return colorRootDone; } + return colorRoot; + } else if(elem.type === "normal") { + if(elem.done) { return colorDone; } + return colorNormal; + } else if(elem.type === "redirect") { + if(elem.done) { return colorRedirectDone; } + return colorRedirect; + } +} + +function getRadiusFor(elem) { + if(elem.type === "normal") { + return radius; + } else if(elem.type === "redirect") { + return radiusRedirect; + } +} + +// Fetch and parse a zzcxz page +async function loadPage(id) { + const url = "https://zzcxz.citrons.xyz/g/" + id + "/raw"; + console.log("fetching id " + id); + return fetch(url).then(x => x.text()).then(text => { + let pageType = "normal"; + const directive = text.match(/^\t#([A-Za-z]+)\s*(.*)\n?$/m); + const title = text.match(/^.+\n/m)[0]; + const links = (text.match(/^[a-z]{5}:.*\n/gm) || []) + .map(line => { + return {"to": line.trim().split(":")[0], "type": "normal"}; + }); + if(directive) { + const kind = directive[1]; + const arg = directive[2]; + if(kind.toLowerCase() === "redirect") { + pageType= "redirect"; + links.push({"to":arg,"type":"redirect"}); + } else { + throw "Unsupported directive: #" + kind + } + } + return { + "id": id, + "type": pageType, + "title": title, + "links": links, + }; + }); +} + +// Add a page to the network (nodes and links lists (not linked lists (at least i think not (i do not know how js lists are implemented)))) +function addPage(id, x, y, root) { + loadPage(id).then(page => { + if(nodes.map(node => node.id).includes(page.id)) { + return; + } + let newLinks = []; + for(node of nodes) { + for(link of node.links) { + if(link.to === page.id) { + newLinks.push({ + "source": node.id, + "target": page.id, + "type": link.type, + }); + } + } + for(link of page.links) { + if(link.to === node.id) { + newLinks.push({ + "source": page.id, + "target": node.id, + "type": link.type, + }); + } + } + } + nodes.push({ + "id": page.id, + "x": x, + "y": y, + "title": page.title, + "links": page.links, + "type": page.type, + "done": false, + "root": root, + }); + for(link of newLinks) { + links.push(link); + } + refresh(); + }); +} + +function addAll(node, x, y) { + const doneIds = nodes.map(n => n.id); + for(link of node.links) { + if(!doneIds.includes(link.to)) { + const theta = Math.random() * TAU; + const dx = 2 * radius * Math.cos(theta); + const dy = 2 * radius * Math.sin(theta); + addPage(link.to, x + dx, y + dy, false); + } + } + node.done = true; + d3.select(".node-" + node.id) + .style("fill", getColorFor); +} + +function selectNode(node) { + if(selectedNode !== null) { + d3.select(".node-" + selectedNode.id) + .style("stroke", "#0000"); + } + selectedNode = node; + if(selectedNode !== null) { + d3.select(".node-" + selectedNode.id) + .style("stroke", "#fff"); + d3.select("#selected-node").attr("hidden", null); + d3.select("#selected-node-title").html(selectedNode.title); + d3.select("#selected-node-id").html(selectedNode.id); + d3.select("#selected-node-link").attr("href", "https://zzcxz.citrons.xyz/g/" + selectedNode.id); + } else { + d3.select("#selected-node").attr("hidden", true); + } +} + +function visualize() { + const zoom = d3.zoom(); + zoom.on("zoom", event => { + const elems = d3.select("#elems") + .attr("transform", event.transform); + transform = event.transform; + }); + + const svg = d3.select("#graph") + .append("svg") + .attr("id", "graph-svg") + .attr("width", "100%") + .attr("height", "100%") + .style("background-color", colorBackground) + .call(zoom); + + const clientWidth = document.querySelector("#graph").clientWidth; + const clientHeight = document.querySelector("#graph").clientHeight; + + d3.select("#spoiler").remove(); + d3.select("#ui").attr("hidden", null); + + d3.select("#expand-link").on("click", () => { + d3.select("#uiexpand").attr("hidden", true); + d3.select("#ui").attr("hidden", null); + }); + + d3.select("#contract-link").on("click", () => { + d3.select("#uiexpand").attr("hidden", null); + d3.select("#ui").attr("hidden", true); + }); + + d3.select("#load-page").on("keypress", (event) => { + if(event.keyCode == 13) { + addPage(event.target.value, (clientWidth/2-transform.x)/transform.k, (clientHeight/2-transform.y)/transform.k, true); + event.target.value = ""; + } + }); + + d3.select("#clear-nodes").on("click", (event) => { + selectNode(null); + links.length = 0; + nodes.length = 0; + refresh(); + }); + + d3.select("#toggle-labels").on("click", (event) => { + showLabels = !showLabels; + if(showLabels) { + d3.selectAll(".label").attr("visibility", "visible"); + } else { + d3.selectAll(".label").attr("visibility", "hidden"); + } + }); + + d3.select("body") + .on("keydown", event => { + if(event.keyCode == 27) { + selectNode(null); + } + }) + .on("click", event => selectNode(null)); + + const marker = svg.append("svg:defs") + .selectAll(".triangle-marker") + .data([["normal", colorNormal], ["redirect", colorRedirect]]) + .enter() + .append("svg:marker") + .attr("id", d => `triangle-${d[0]}`) + .attr("refX", radius+10) + .attr("refY", 5) + .attr("orient", "auto") + .attr("markerUnits", "strokeWidth") + .attr("markerWidth", 15) + .attr("markerHeight", 10) + .append("svg:polygon") + .attr("points", "0 0, 15 5, 0 10") + .attr("fill", d => d[1]); + + const simulation = d3.forceSimulation().nodes(nodes); + const forceLinks = d3.forceLink(links) + .distance(linkDistance) + .strength(linkStrength) + .id((d, i) => d.id); + + simulation.force("link", forceLinks) + .force("charge", d3.forceManyBody().distanceMax(256).distanceMin(1).strength(-50)) + .force("collide", d3.forceCollide().radius(radius*1.4)); + + const elems = svg.append("g").attr("id", "elems"); + const gLinks = elems.append("g").attr("id", "links"); + const gNodes = elems.append("g").attr("id", "nodes"); + const gLabels = elems.append("g").attr("id", "labels"); + + let node, link, label; + + const dragDrop = d3.drag().on("start", (event, node) => { + node.fx = node.x; + node.fy = node.y; + }).on("drag", (event, node) => { + simulation.alpha(alphaInit).alphaTarget(alphaTarget).restart(); + node.fx = event.x; + node.fy = event.y; + }).on("end", (event, node) => { + if (!event.active) { + simulation.alphaTarget(alphaTarget); + } + node.fx = null; + node.fy = null; + }) + + function tick() { + gNodes.selectAll(".node") + .attr("cx", d => d.x) + .attr("cy", d => d.y); + gLabels.selectAll(".label") + .attr("x", d => d.x) + .attr("y", d => d.y); + gLinks.selectAll(".link") + .attr("x1", d => d.source.x) + .attr("y1", d => d.source.y) + .attr("x2", d => d.target.x) + .attr("y2", d => d.target.y); + } + + refresh = () => { + node = gNodes.selectAll(".node").data(nodes, d => d.id); + + node.exit().remove(); + + node.enter() + .append("circle") + .attr("class", d => `node node-${d.id}`) + .attr("r", getRadiusFor) + .style("fill", getColorFor) + .style("stroke", "#0000") + .style("stroke-width", 2.5) + .call(dragDrop) + .on("click", (event, target) => addAll(target, target.x, target.y)) + .on("contextmenu", (event, target) => { event.preventDefault(); selectNode(target); }); + + label = gLabels.selectAll(".label"); + + label = gLabels.selectAll(".label").data(nodes, d => d.id); + + label.exit().remove(); + + label.enter() + .append("text") + .attr("class", "label") + .attr("text-anchor", "middle") + .attr("dominant-baseline", "middle") + .style("fill", colorBackground) + .style("stroke", colorNormal) + .style("stroke-width", 2.5) + .style("paint-order", "stroke") + .style("font-size", "15px") + .text(d => d.title) + .attr("pointer-events", "none") + .attr("visibility", showLabels ? "visible" : "hidden"); + + link = gLinks.selectAll(".link").data(links); + + link.exit().remove(); + + link.enter() + .append("line") + .attr("class", "link") + .style("stroke", getColorFor) + .attr("marker-end", d => `url(#triangle-${d.type})`); + + simulation.nodes(nodes).on("tick", tick); + simulation.force("link").initialize(links); + simulation.alpha(alphaInit).alphaTarget(alphaTarget).restart(); + }; + addPage("zzcxz", clientWidth/2, clientHeight/2, true); +} + +window.onload = () => { + if(new URLSearchParams(window.location.search).has("skip")) { + visualize(); + } +} diff --git a/src/templates/rss.xml b/src/templates/rss.xml new file mode 100644 index 0000000..6aae616 --- /dev/null +++ b/src/templates/rss.xml @@ -0,0 +1,20 @@ + + + + Blogā„¢ + https://trimill.xyz/blog/ + Blogā„¢ + en + {{ data.blogposts[0].timestamp.strftime("%a, %d %b %Y %H:%M:%S %z") }} + + {% for post in data.blogposts %} + + {{ post.title }} + https://trimill.xyz/{{ post.url }} + {{ post.desc }} + {{ post.timestamp.strftime("%a, %d %b %Y %H:%M:%S %z") }} + https://trimill.xyz/{{ post.url }} + + {% endfor %} + + \ No newline at end of file diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..8b6cf66 --- /dev/null +++ b/test.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +. venv/bin/activate +export FLASK_APP=src/main +export FLASK_ENV=development +flask run \ No newline at end of file