From 82dff02ba39af9e280e099590f0884b5a56053a3 Mon Sep 17 00:00:00 2001
From: gavinspytek <118202559+gavinspytek@users.noreply.github.com>
Date: Mon, 13 Feb 2023 14:30:09 -0600
Subject: [PATCH 1/5] Add files via upload
---
gradbot-main/LICENSE | 674 ++++++++
gradbot-main/Parts-Sketch.fig | 33 +
gradbot-main/README.md | 15 +
gradbot-main/codeflask.min.js | 1 +
gradbot-main/commands.html | 33 +
gradbot-main/dark-mode.css | 4 +
gradbot-main/gradbot.js | 3067 +++++++++++++++++++++++++++++++++
gradbot-main/index.css | 191 ++
gradbot-main/index.html | 217 +++
gradbot-main/light-mode.css | 4 +
gradbot-main/userbot.js | 257 +++
11 files changed, 4496 insertions(+)
create mode 100644 gradbot-main/LICENSE
create mode 100644 gradbot-main/Parts-Sketch.fig
create mode 100644 gradbot-main/README.md
create mode 100644 gradbot-main/codeflask.min.js
create mode 100644 gradbot-main/commands.html
create mode 100644 gradbot-main/dark-mode.css
create mode 100644 gradbot-main/gradbot.js
create mode 100644 gradbot-main/index.css
create mode 100644 gradbot-main/index.html
create mode 100644 gradbot-main/light-mode.css
create mode 100644 gradbot-main/userbot.js
diff --git a/gradbot-main/LICENSE b/gradbot-main/LICENSE
new file mode 100644
index 0000000..b27ad83
--- /dev/null
+++ b/gradbot-main/LICENSE
@@ -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/gradbot-main/Parts-Sketch.fig b/gradbot-main/Parts-Sketch.fig
new file mode 100644
index 0000000..55a6d40
--- /dev/null
+++ b/gradbot-main/Parts-Sketch.fig
@@ -0,0 +1,33 @@
+#FIG 3.2 Produced by xfig version 3.2.7a
+Landscape
+Center
+Metric
+A4
+100.00
+Single
+-2
+1200 2
+6 4275 4815 4365 4905
+2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2
+ 4320 4815 4320 4905
+2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2
+ 4275 4860 4365 4860
+-6
+6 4905 5445 4995 5535
+2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2
+ 4950 5445 4950 5535
+2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2
+ 4905 5490 4995 5490
+-6
+6 4275 6075 4365 6165
+2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2
+ 4320 6075 4320 6165
+2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2
+ 4275 6120 4365 6120
+-6
+2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5
+ 4050 4950 5850 4950 5850 6030 4050 6030 4050 4950
+2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5
+ 4050 6030 4590 6030 4590 6210 4050 6210 4050 6030
+2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5
+ 4050 4950 4590 4950 4590 4770 4050 4770 4050 4950
diff --git a/gradbot-main/README.md b/gradbot-main/README.md
new file mode 100644
index 0000000..738e983
--- /dev/null
+++ b/gradbot-main/README.md
@@ -0,0 +1,15 @@
+# Gradbot #
+---
+## Table of Contents
+1. Run Gradbot
+2. Gradbot Features
+3. Extra Information
+---
+### Run Gradbot
+---
+### Gradbot Features
+The Gradbot software is a way for students to learn beginning programming techniques in a fun and stable enviornment. Gradbot makes this easy with some of the following features.
+
+---
+### Extra Information
+---
diff --git a/gradbot-main/codeflask.min.js b/gradbot-main/codeflask.min.js
new file mode 100644
index 0000000..d186f85
--- /dev/null
+++ b/gradbot-main/codeflask.min.js
@@ -0,0 +1 @@
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.CodeFlask=t()}(this,function(){"use strict";var e,t,n,a='"SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace',s="\n .codeflask {\n position: absolute;\n width: 100%;\n height: 100%;\n overflow: hidden;\n }\n\n .codeflask, .codeflask * {\n box-sizing: border-box;\n }\n\n .codeflask__pre {\n pointer-events: none;\n z-index: 3;\n overflow: hidden;\n }\n\n .codeflask__textarea {\n background: none;\n border: none;\n color: "+(e="caret-color",t="#000",("undefined"!=typeof CSS?CSS.supports(e,t):"undefined"!=typeof document&&(n=(n=e).split("-").filter(function(e){return!!e}).map(function(e){return e[0].toUpperCase()+e.substr(1)}).join(""))[0].toLowerCase()+n.substr(1)in document.body.style)?"#fff":"#ccc")+";\n z-index: 1;\n resize: none;\n font-family: "+a+";\n -webkit-appearance: pre;\n caret-color: #111;\n z-index: 2;\n width: 100%;\n height: 100%;\n }\n\n .codeflask--has-line-numbers .codeflask__textarea {\n width: calc(100% - 40px);\n }\n\n .codeflask__code {\n display: block;\n font-family: "+a+";\n overflow: hidden;\n }\n\n .codeflask__flatten {\n padding: 10px;\n font-size: 13px;\n line-height: 20px;\n white-space: pre;\n position: absolute;\n top: 0;\n left: 0;\n overflow: auto;\n margin: 0 !important;\n outline: none;\n text-align: left;\n }\n\n .codeflask--has-line-numbers .codeflask__flatten {\n width: calc(100% - 40px);\n left: 40px;\n }\n\n .codeflask__line-highlight {\n position: absolute;\n top: 10px;\n left: 0;\n width: 100%;\n height: 20px;\n background: rgba(0,0,0,0.1);\n z-index: 1;\n }\n\n .codeflask__lines {\n padding: 10px 4px;\n font-size: 12px;\n line-height: 20px;\n font-family: 'Cousine', monospace;\n position: absolute;\n left: 0;\n top: 0;\n width: 40px;\n height: 100%;\n text-align: right;\n color: #999;\n z-index: 2;\n }\n\n .codeflask__lines__line {\n display: block;\n }\n\n .codeflask.codeflask--has-line-numbers {\n padding-left: 40px;\n }\n\n .codeflask.codeflask--has-line-numbers:before {\n content: '';\n position: absolute;\n left: 0;\n top: 0;\n width: 40px;\n height: 100%;\n background: #eee;\n z-index: 1;\n }\n";function i(e,t,n){var a=t||"codeflask-style",s=n||document.head;if(!e)return!1;if(document.getElementById(a))return!0;var i=document.createElement("style");return i.innerHTML=e,i.id=a,s.appendChild(i),!0}var r={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="};function o(e){return String(e).replace(/[&<>"'`=/]/g,function(e){return r[e]})}var l="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};var c,u=(function(e){var t=function(e){var t=/\blang(?:uage)?-([\w-]+)\b/i,n=0,a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function(e){return e instanceof s?new s(e.type,a.util.encode(e.content),e.alias):Array.isArray(e)?e.map(a.util.encode):e.replace(/&/g,"&").replace(/e.length)return;if(!(v instanceof s)){if(f&&k!=t.length-1){if(p.lastIndex=x,!(S=p.exec(e)))break;for(var w=S.index+(g?S[1].length:0),F=S.index+S[0].length,A=k,C=x,T=t.length;A=(C+=t[A].length)&&(++k,x=C);if(t[k]instanceof s)continue;_=A-k,v=e.slice(x,C),S.index-=x}else{p.lastIndex=0;var S=p.exec(v),_=1}if(S){g&&(b=S[1]?S[1].length:0);F=(w=S.index+b)+(S=S[0].slice(b)).length;var L=v.slice(0,w),E=v.slice(F),N=[k,_];L&&(++k,x+=L.length,N.push(L));var j=new s(c,h?a.tokenize(S,h):S,m,S,f);if(N.push(j),E&&N.push(E),Array.prototype.splice.apply(t,N),1!=_&&a.matchGrammar(e,t,n,k,x,!0,c),o)break}else if(o)break}}}}},tokenize:function(e,t){var n=[e],s=t.rest;if(s){for(var i in s)t[i]=s[i];delete t.rest}return a.matchGrammar(e,n,t,0,0,!1),n},hooks:{all:{},add:function(e,t){var n=a.hooks.all;n[e]=n[e]||[],n[e].push(t)},run:function(e,t){var n=a.hooks.all[e];if(n&&n.length)for(var s,i=0;s=n[i++];)s(t)}},Token:s};function s(e,t,n,a,s){this.type=e,this.content=t,this.alias=n,this.length=0|(a||"").length,this.greedy=!!s}if(e.Prism=a,s.stringify=function(e,t,n){if("string"==typeof e)return e;if(Array.isArray(e))return e.map(function(n){return s.stringify(n,t,e)}).join("");var i={type:e.type,content:s.stringify(e.content,t,n),tag:"span",classes:["token",e.type],attributes:{},language:t,parent:n};if(e.alias){var r=Array.isArray(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,r)}a.hooks.run("wrap",i);var o=Object.keys(i.attributes).map(function(e){return e+'="'+(i.attributes[e]||"").replace(/"/g,""")+'"'}).join(" ");return"<"+i.tag+' class="'+i.classes.join(" ")+'"'+(o?" "+o:"")+">"+i.content+""+i.tag+">"},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",function(t){var n=JSON.parse(t.data),s=n.language,i=n.code,r=n.immediateClose;e.postMessage(a.highlight(i,a.languages[s],s)),r&&e.close()},!1),a):a;var i=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return i&&(a.filename=i.src,a.manual||i.hasAttribute("data-manual")||("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(a.highlightAll):window.setTimeout(a.highlightAll,16):document.addEventListener("DOMContentLoaded",a.highlightAll))),a}("undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{});e.exports&&(e.exports=t),void 0!==l&&(l.Prism=t),t.languages.markup={comment://,prolog:/<\?[\s\S]+?\?>/,doctype://i,cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/i,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/i,inside:{punctuation:[/^=/,{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/?[\da-z]{1,8};/i},t.languages.markup.tag.inside["attr-value"].inside.entity=t.languages.markup.entity,t.hooks.add("wrap",function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))}),Object.defineProperty(t.languages.markup.tag,"addInlined",{value:function(e,n){var a={};a["language-"+n]={pattern:/(^$)/i,lookbehind:!0,inside:t.languages[n]},a.cdata=/^$/i;var s={"included-cdata":{pattern://i,inside:a}};s["language-"+n]={pattern:/[\s\S]+/,inside:t.languages[n]};var i={};i[e]={pattern:RegExp(/(<__[\s\S]*?>)(?:\s*|[\s\S])*?(?=<\/__>)/.source.replace(/__/g,e),"i"),lookbehind:!0,greedy:!0,inside:s},t.languages.insertBefore("markup","cdata",i)}}),t.languages.xml=t.languages.extend("markup",{}),t.languages.html=t.languages.markup,t.languages.mathml=t.languages.markup,t.languages.svg=t.languages.markup,function(e){var t=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-]+?[\s\S]*?(?:;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:RegExp("url\\((?:"+t.source+"|.*?)\\)","i"),selector:RegExp("[^{}\\s](?:[^{};\"']|"+t.source+")*?(?=\\s*\\{)"),string:{pattern:t,greedy:!0},property:/[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i,important:/!important\b/i,function:/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css;var n=e.languages.markup;n&&(n.tag.addInlined("style","css"),e.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:n.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:e.languages.css}},alias:"language-css"}},n.tag))}(t),t.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/--?|\+\+?|!=?=?|<=?|>=?|==?=?|&&?|\|\|?|\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/},t.languages.javascript=t.languages.extend("clike",{"class-name":[t.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])[_$A-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\.(?:prototype|constructor))/,lookbehind:!0}],keyword:[{pattern:/((?:^|})\s*)(?:catch|finally)\b/,lookbehind:!0},{pattern:/(^|[^.])\b(?:as|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],number:/\b(?:(?:0[xX][\dA-Fa-f]+|0[bB][01]+|0[oO][0-7]+)n?|\d+n|NaN|Infinity)\b|(?:\b\d+\.?\d*|\B\.\d+)(?:[Ee][+-]?\d+)?/,function:/[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,operator:/-[-=]?|\+[+=]?|!=?=?|<=?|>>?>?=?|=(?:==?|>)?|&[&=]?|\|[|=]?|\*\*?=?|\/=?|~|\^=?|%=?|\?|\.{3}/}),t.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,t.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s])\s*)\/(\[(?:[^\]\\\r\n]|\\.)*]|\\.|[^/\\\[\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})\]]))/,lookbehind:!0,greedy:!0},"function-variable":{pattern:/[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)?\s*\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\))/,lookbehind:!0,inside:t.languages.javascript},{pattern:/[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=>)/i,inside:t.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*=>)/,lookbehind:!0,inside:t.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*\s*)\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*\{)/,lookbehind:!0,inside:t.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),t.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\[\s\S]|\${[^}]+}|[^\\`])*`/,greedy:!0,inside:{interpolation:{pattern:/\${[^}]+}/,inside:{"interpolation-punctuation":{pattern:/^\${|}$/,alias:"punctuation"},rest:t.languages.javascript}},string:/[\s\S]+/}}}),t.languages.markup&&t.languages.markup.tag.addInlined("script","javascript"),t.languages.js=t.languages.javascript,"undefined"!=typeof self&&self.Prism&&self.document&&document.querySelector&&(self.Prism.fileHighlight=function(e){e=e||document;var n={js:"javascript",py:"python",rb:"ruby",ps1:"powershell",psm1:"powershell",sh:"bash",bat:"batch",h:"c",tex:"latex"};Array.prototype.slice.call(e.querySelectorAll("pre[data-src]")).forEach(function(e){if(!e.hasAttribute("data-src-loaded")){for(var a,s=e.getAttribute("data-src"),i=e,r=/\blang(?:uage)?-([\w-]+)\b/i;i&&!r.test(i.className);)i=i.parentNode;if(i&&(a=(e.className.match(r)||[,""])[1]),!a){var o=(s.match(/\.(\w+)$/)||[,""])[1];a=n[o]||o}var l=document.createElement("code");l.className="language-"+a,e.textContent="",l.textContent="Loading…",e.appendChild(l);var c=new XMLHttpRequest;c.open("GET",s,!0),c.onreadystatechange=function(){4==c.readyState&&(c.status<400&&c.responseText?(l.textContent=c.responseText,t.highlightElement(l),e.setAttribute("data-src-loaded","")):c.status>=400?l.textContent="✖ Error "+c.status+" while fetching file: "+c.statusText:l.textContent="✖ Error: File does not exist or is empty")},c.send(null)}}),t.plugins.toolbar&&t.plugins.toolbar.registerButton("download-file",function(e){var t=e.element.parentNode;if(t&&/pre/i.test(t.nodeName)&&t.hasAttribute("data-src")&&t.hasAttribute("data-download-link")){var n=t.getAttribute("data-src"),a=document.createElement("a");return a.textContent=t.getAttribute("data-download-link-label")||"Download",a.setAttribute("download",""),a.href=n,a}})},document.addEventListener("DOMContentLoaded",function(){self.Prism.fileHighlight()}))}(c={exports:{}},c.exports),c.exports),d=function(e,t){if(!e)throw Error("CodeFlask expects a parameter which is Element or a String selector");if(!t)throw Error("CodeFlask expects an object containing options as second parameter");if(e.nodeType)this.editorRoot=e;else{var n=document.querySelector(e);n&&(this.editorRoot=n)}this.opts=t,this.startEditor()};return d.prototype.startEditor=function(){if(!i(s,null,this.opts.styleParent))throw Error("Failed to inject CodeFlask CSS.");this.createWrapper(),this.createTextarea(),this.createPre(),this.createCode(),this.runOptions(),this.listenTextarea(),this.populateDefault(),this.updateCode(this.code)},d.prototype.createWrapper=function(){this.code=this.editorRoot.innerHTML,this.editorRoot.innerHTML="",this.elWrapper=this.createElement("div",this.editorRoot),this.elWrapper.classList.add("codeflask")},d.prototype.createTextarea=function(){this.elTextarea=this.createElement("textarea",this.elWrapper),this.elTextarea.classList.add("codeflask__textarea","codeflask__flatten")},d.prototype.createPre=function(){this.elPre=this.createElement("pre",this.elWrapper),this.elPre.classList.add("codeflask__pre","codeflask__flatten")},d.prototype.createCode=function(){this.elCode=this.createElement("code",this.elPre),this.elCode.classList.add("codeflask__code","language-"+(this.opts.language||"html"))},d.prototype.createLineNumbers=function(){this.elLineNumbers=this.createElement("div",this.elWrapper),this.elLineNumbers.classList.add("codeflask__lines"),this.setLineNumber()},d.prototype.createElement=function(e,t){var n=document.createElement(e);return t.appendChild(n),n},d.prototype.runOptions=function(){this.opts.rtl=this.opts.rtl||!1,this.opts.tabSize=this.opts.tabSize||2,this.opts.enableAutocorrect=this.opts.enableAutocorrect||!1,this.opts.lineNumbers=this.opts.lineNumbers||!1,this.opts.defaultTheme=!1!==this.opts.defaultTheme,this.opts.areaId=this.opts.areaId||null,this.opts.ariaLabelledby=this.opts.ariaLabelledby||null,this.opts.readonly=this.opts.readonly||null,"boolean"!=typeof this.opts.handleTabs&&(this.opts.handleTabs=!0),"boolean"!=typeof this.opts.handleSelfClosingCharacters&&(this.opts.handleSelfClosingCharacters=!0),"boolean"!=typeof this.opts.handleNewLineIndentation&&(this.opts.handleNewLineIndentation=!0),!0===this.opts.rtl&&(this.elTextarea.setAttribute("dir","rtl"),this.elPre.setAttribute("dir","rtl")),!1===this.opts.enableAutocorrect&&(this.elTextarea.setAttribute("spellcheck","false"),this.elTextarea.setAttribute("autocapitalize","off"),this.elTextarea.setAttribute("autocomplete","off"),this.elTextarea.setAttribute("autocorrect","off")),this.opts.lineNumbers&&(this.elWrapper.classList.add("codeflask--has-line-numbers"),this.createLineNumbers()),this.opts.defaultTheme&&i("\n.codeflask {\n background: #fff;\n color: #4f559c;\n}\n\n.codeflask .token.punctuation {\n color: #4a4a4a;\n}\n\n.codeflask .token.keyword {\n color: #8500ff;\n}\n\n.codeflask .token.operator {\n color: #ff5598;\n}\n\n.codeflask .token.string {\n color: #41ad8f;\n}\n\n.codeflask .token.comment {\n color: #9badb7;\n}\n\n.codeflask .token.function {\n color: #8500ff;\n}\n\n.codeflask .token.boolean {\n color: #8500ff;\n}\n\n.codeflask .token.number {\n color: #8500ff;\n}\n\n.codeflask .token.selector {\n color: #8500ff;\n}\n\n.codeflask .token.property {\n color: #8500ff;\n}\n\n.codeflask .token.tag {\n color: #8500ff;\n}\n\n.codeflask .token.attr-value {\n color: #8500ff;\n}\n","theme-default",this.opts.styleParent),this.opts.areaId&&this.elTextarea.setAttribute("id",this.opts.areaId),this.opts.ariaLabelledby&&this.elTextarea.setAttribute("aria-labelledby",this.opts.ariaLabelledby),this.opts.readonly&&this.enableReadonlyMode()},d.prototype.updateLineNumbersCount=function(){for(var e="",t=1;t<=this.lineNumber;t++)e=e+''+t+"";this.elLineNumbers.innerHTML=e},d.prototype.listenTextarea=function(){var e=this;this.elTextarea.addEventListener("input",function(t){e.code=t.target.value,e.elCode.innerHTML=o(t.target.value),e.highlight(),setTimeout(function(){e.runUpdate(),e.setLineNumber()},1)}),this.elTextarea.addEventListener("keydown",function(t){e.handleTabs(t),e.handleSelfClosingCharacters(t),e.handleNewLineIndentation(t)}),this.elTextarea.addEventListener("scroll",function(t){e.elPre.style.transform="translate3d(-"+t.target.scrollLeft+"px, -"+t.target.scrollTop+"px, 0)",e.elLineNumbers&&(e.elLineNumbers.style.transform="translate3d(0, -"+t.target.scrollTop+"px, 0)")})},d.prototype.handleTabs=function(e){if(this.opts.handleTabs){if(9!==e.keyCode)return;e.preventDefault();var t=this.elTextarea,n=t.selectionDirection,a=t.selectionStart,s=t.selectionEnd,i=t.value,r=i.substr(0,a),o=i.substring(a,s),l=i.substring(s),c=" ".repeat(this.opts.tabSize);if(a!==s&&o.length>=c.length){var u=a-r.split("\n").pop().length,d=c.length,p=c.length;if(e.shiftKey)i.substr(u,c.length)===c?(d=-d,u>a?(o=o.substring(0,u)+o.substring(u+c.length),p=0):u===a?(d=0,p=0,o=o.substring(c.length)):(p=-p,r=r.substring(0,u)+r.substring(u+c.length))):(d=0,p=0),o=o.replace(new RegExp("\n"+c.split("").join("\\"),"g"),"\n");else r=r.substr(0,u)+c+r.substring(u,a),o=o.replace(/\n/g,"\n"+c);t.value=r+o+l,t.selectionStart=a+d,t.selectionEnd=a+o.length+p,t.selectionDirection=n}else t.value=r+c+l,t.selectionStart=a+c.length,t.selectionEnd=a+c.length;var h=t.value;this.updateCode(h),this.elTextarea.selectionEnd=s+this.opts.tabSize}},d.prototype.handleSelfClosingCharacters=function(e){if(this.opts.handleSelfClosingCharacters){var t=e.key;if(["(","[","{","<","'",'"'].includes(t)||[")","]","}",">","'",'"'].includes(t))switch(t){case"(":case")":this.closeCharacter(t);break;case"[":case"]":this.closeCharacter(t);break;case"{":case"}":this.closeCharacter(t);break;case"<":case">":case"'":case'"':this.closeCharacter(t)}}},d.prototype.setLineNumber=function(){this.lineNumber=this.code.split("\n").length,this.opts.lineNumbers&&this.updateLineNumbersCount()},d.prototype.handleNewLineIndentation=function(e){if(this.opts.handleNewLineIndentation&&13===e.keyCode){e.preventDefault();var t=this.elTextarea,n=t.selectionStart,a=t.selectionEnd,s=t.value,i=s.substr(0,n),r=s.substring(a),o=s.lastIndexOf("\n",n-1),l=o+s.slice(o+1).search(/[^ ]|$/),c=l>o?l-o:0,u=i+"\n"+" ".repeat(c)+r;t.value=u,t.selectionStart=n+c+1,t.selectionEnd=n+c+1,this.updateCode(t.value)}},d.prototype.closeCharacter=function(e){var t=this.elTextarea.selectionStart,n=this.elTextarea.selectionEnd;if(this.skipCloseChar(e)){var a=this.code.substr(n,1)===e,s=a?n+1:n,i=!a&&["'",'"'].includes(e)?e:"",r=""+this.code.substring(0,t)+i+this.code.substring(s);this.updateCode(r),this.elTextarea.selectionEnd=++this.elTextarea.selectionStart}else{var o=e;switch(e){case"(":o=String.fromCharCode(e.charCodeAt()+1);break;case"<":case"{":case"[":o=String.fromCharCode(e.charCodeAt()+2)}var l=this.code.substring(t,n),c=""+this.code.substring(0,t)+l+o+this.code.substring(n);this.updateCode(c)}this.elTextarea.selectionEnd=t},d.prototype.skipCloseChar=function(e){var t=this.elTextarea.selectionStart,n=this.elTextarea.selectionEnd,a=Math.abs(n-t)>0;return[")","}","]",">"].includes(e)||["'",'"'].includes(e)&&!a},d.prototype.updateCode=function(e){this.code=e,this.elTextarea.value=e,this.elCode.innerHTML=o(e),this.highlight(),this.setLineNumber(),setTimeout(this.runUpdate.bind(this),1)},d.prototype.updateLanguage=function(e){var t=this.opts.language;this.elCode.classList.remove("language-"+t),this.elCode.classList.add("language-"+e),this.opts.language=e,this.highlight()},d.prototype.addLanguage=function(e,t){u.languages[e]=t},d.prototype.populateDefault=function(){this.updateCode(this.code)},d.prototype.highlight=function(){u.highlightElement(this.elCode,!1)},d.prototype.onUpdate=function(e){if(e&&"[object Function]"!=={}.toString.call(e))throw Error("CodeFlask expects callback of type Function");this.updateCallBack=e},d.prototype.getCode=function(){return this.code},d.prototype.runUpdate=function(){this.updateCallBack&&this.updateCallBack(this.code)},d.prototype.enableReadonlyMode=function(){this.elTextarea.setAttribute("readonly",!0)},d.prototype.disableReadonlyMode=function(){this.elTextarea.removeAttribute("readonly")},d});
diff --git a/gradbot-main/commands.html b/gradbot-main/commands.html
new file mode 100644
index 0000000..c193ef6
--- /dev/null
+++ b/gradbot-main/commands.html
@@ -0,0 +1,33 @@
+
+
+
+ Coding Your Robot
+
Coding Your Robot
+
+
+
+
+
+
+ 'side.setPower(level);'
+ Sets the power level of
+ the motor on the specified
+ side of the robot.
+ Side can be left or right.
+ Level can range from
+ 0 to 100.
+
+ 'await delay(number);'
+ Waits to move on to the
+ next command. Number is
+ the number of milliseconds
+ you would like the robot
+ to wait.
+
+ 'laser.fire;'
+ Fires the specified laser
+ one time. Use part name to
+ specify which laser to fire from.
+
+
+
\ No newline at end of file
diff --git a/gradbot-main/dark-mode.css b/gradbot-main/dark-mode.css
new file mode 100644
index 0000000..a9300be
--- /dev/null
+++ b/gradbot-main/dark-mode.css
@@ -0,0 +1,4 @@
+/* body {
+ color: #eee;
+ background: #121212;
+ } */
\ No newline at end of file
diff --git a/gradbot-main/gradbot.js b/gradbot-main/gradbot.js
new file mode 100644
index 0000000..3c57e2a
--- /dev/null
+++ b/gradbot-main/gradbot.js
@@ -0,0 +1,3067 @@
+/*
+ This program is a simulator for building robots.
+ Copyright (C) 2021 Robert Lowe
+
+ 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 .
+ */
+/**
+ * @file gradbot.js
+ * @copyright Robert Lowe 2021
+ * @license GPL 3.0
+ */
+
+
+/******************************************
+ * Utility Functions and Objects
+ ******************************************/
+
+
+/**
+ * This function takes an angle in radians and reduces it so its range
+ * is always within [0, 2*PI]
+ * @param {number} a - The angle to reduce
+ * @returns The reduced angle.
+ */
+function reduceAngle(a) {
+ var tau = 2 * Math.PI;
+
+ a %= tau;
+ if(a<0) {
+ a+= tau;
+ }
+
+ return a;
+}
+
+
+/**
+ * Compute the minimum distance from point e to the line segment
+ * ab. Each point is expected to be an object with fields x, y
+ * for the coordinates. This function also computes the angle to
+ * that closest point.
+ * @param {object} a - Endpoint a
+ * @param {object} b - Endpoint b
+ * @param {object} e - Endpoint e
+ * @returns {object} - an object {distance:, angle:}
+ */
+function minLineDist(a, b, e) {
+ //vectors
+ var ab = {x: b.x - a.x, y: b.y - a.y};
+ var be = {x: e.x - b.x, y: e.y - b.y};
+ var ae = {x: e.x - a.x, y: e.y - a.y};
+
+ //dot products
+ ab_be = ab.x * be.x + ab.y * be.y;
+ ab_ae = ab.x * ae.x + ab.y * ae.y;
+
+ //Minimum distance from e to the line segment
+ var result = {distance: Infinity, angle: 0};
+
+ if(ab_be > 0) {
+ // Case 1 - Point b is the closest
+ var y = b.y - e.y;
+ var x = b.x - e.x;
+ result.distance = Math.sqrt(x * x + y * y);
+ result.angle = Math.atan2(y, x);
+ } else if(ab_ae < 0) {
+ // Case 2 - Point a is the closest
+ var y = b.y - e.y;
+ var x = b.x - e.x;
+ result.distance = Math.sqrt(x * x + y * y);
+ result.angle = Math.atan2(y, x);
+ } else {
+ // Case 3 - Perpendicular distance
+ var x1 = ab.x;
+ var y1 = ab.y;
+ var x2 = ae.x;
+ var y2 = ae.y;
+ var mod = Math.sqrt(x1 * x1 + y1 * y1);
+ result.distance = Math.abs(x1 * y2 - y1 * x2) / mod;
+ result.angle = Math.atan2(ab.y, ab.x) + Math.PI/2;
+ }
+
+ return result;
+}
+
+
+
+/**
+ * Find the minimum distance between point p and polygon poly.
+ * It also computes the angle to the nearest point.
+ * @param {object} p - The point as an x, y object.
+ * @param {object} poly - A list of points in the polygon
+ * @returns {object} - an object {distance:, angle:}
+ */
+function minPolyDist(p, poly) {
+ var result = {distance: Infinity, angle: 0};
+ var dist;
+
+ //handle single point polygons
+ if(poly.length == 1) {
+ var dx = poly[0].x - p.x;
+ var dy = poly[0].y - p.y;
+ result.distance = Math.sqrt(dx*dx + dy*dy);
+ result.angle = Math.atan2(dy, dx);
+ return result;
+ }
+
+ //close the path
+ poly = poly.concat([poly[0]]);
+
+ // check all the line segments
+ for(var i=0; i= view2.minx &&
+ view1.miny <= view2.maxy &&
+ view1.maxy >= view2.miny;
+}
+
+
+
+/**
+ * This is a prototype for a positionable object. It handles all the
+ * intricacies of moving an object around.
+ * @param {number} x - x coordinate
+ * @param {number} y - y coordinate
+ * @param {number} heading - the direction in which the object is facing
+ */
+function Positionable(x, y, heading) {
+ this.x = x != undefined ? x : 0;
+ this.y = y != undefined ? y : 0;
+ this.heading = heading != undefined ? heading : 0;
+ this.heading = reduceAngle(this.heading);
+
+ /**
+ * Rotate the positionable.
+ * @param {number} a - The angle to rotate by (in radians)
+ */
+ this.rotate = function(a) {
+ this.heading = reduceAngle(this.heading + a);
+ };
+
+
+ /**
+ * Turn the positionable to face along the given heading.
+ * @param {number} a - The new heading to face along
+ */
+ this.face = function(a) {
+ this.heading = reduceAngle(a);
+ }
+
+
+ /**
+ * Move the positionable to (x,y)
+ * @param {*} x
+ * @param {*} y
+ */
+ this.moveTo = function(x, y) {
+ this.x = x;
+ this.y = y;
+ }
+}
+
+
+
+/******************************************
+ * Simulation Objects
+ ******************************************/
+
+/**
+ * A document for a part.
+ * Functions are an array of dictionaries:
+ * { name: name of the function
+ * doc: explanation of the function
+ * params: Array( {
+ * name : name of parameter
+ * doc : document of parameter
+ * }
+ * }
+ * Vars are an array of dictionaries:
+ * { name: name of var, doc: document of var }
+ * @param {*} name - name of the part
+ */
+function PartDoc() {
+ this.functions = Array();
+ this.vars = Array();
+ this.showName = true;
+
+ this.display = function(parent, name) {
+ var ul = document.createElement("ul");
+ parent.appendChild(ul);
+
+ // process the functions
+ for(var i=0; i" + p.name + " - " + p.doc;
+ parameterList.appendChild(pli);
+
+ }
+
+ code.innerHTML += ")";
+ li.appendChild(code);
+ li.appendChild(document.createElement("br"));
+ li.appendChild(funDoc);
+ li.appendChild(parameterList);
+ ul.appendChild(li);
+ }
+
+ // process the vars
+ for(var i=0; i 100) {
+ //power = 100;
+ //} else if(power < -100) {
+ //power = -100;
+ //}
+ this.power = power;
+ };
+
+
+ /**
+ * Update the part's state.
+ */
+ this.update = function() { };
+
+
+ /**
+ * Serialize
+ */
+ this.toJSON = function () {
+ result = {};
+ for (var attr in this) {
+ //skip parents and functions
+ if (attr == "parent" || typeof this[attr] == "function" || attr == "doc") {
+ continue;
+ }
+ result[attr] = this[attr];
+ }
+ return result;
+ };
+
+
+ /**
+ * Return a sendable (postable to a worker) version of this object.
+ */
+ this.sendable = function() {
+ //by default, just use the toJSON function
+ return this.toJSON();
+ };
+
+
+ /**
+ * Receive a message from the user thread
+ * @param {*} message
+ */
+ this.receiveUser = function(message) {
+ //by default, just ignore the message!
+ }
+}
+
+
+
+
+function Motor(parent, x, y, heading, name)
+{
+ //construct the part
+ Part.call(this, parent, x, y, heading, name);
+ this.type = "Motor";
+
+ //document the motor
+ this.doc.functions = Array(
+ { name: 'setPower',
+ doc: 'Set the power of the motor from -100 to 100',
+ params: Array( { name: 'p', doc: 'power setting'})
+ }
+ );
+ this.doc.vars = Array(
+ {name: 'power', doc: 'The current power setting of the motor'}
+ );
+
+
+ // handle speed of the motor
+ this.speed = 0; // motor speed in radians per second
+ this.update = function() {
+ //we are basing this on the sparkfun hobby motors which spin at 65 RPM (max)
+ //This maximum speed is roughly 6.81 radians per second
+ this.speed = 6.81 * this.power / 100;
+ }
+
+
+ /**
+ * Receive a message from the user thread
+ * @param {*} message
+ */
+ this.receiveUser = function(message) {
+ //copy the power setting from the user model
+ this.setPower(message.power);
+ }
+
+
+}
+
+
+function Marker(parent, x, y, name) {
+ //construct the part
+ Part.call(this, parent, x, y, 0, name);
+ this.type = "Marker";
+
+ // document the part
+ this.doc.functions = Array(
+ { name: 'penDown', doc: 'Begin drawing.', params: Array()},
+ { name: 'penUp', doc: 'Stop drawing.', params: Array()},
+ { name: 'setColor', doc: 'Change drawing color.', params: Array(
+ {name: 'c', doc: 'color'}
+ )}
+ );
+ this.doc.vars = Array(
+ { name: 'color', doc: 'The current color of the marker' },
+ { name: 'penDrawing', doc: 'True if the pen is drawing.'}
+ );
+
+ //by default we are coloring black
+ this.color = "black";
+
+ //by default the pen is up
+ this.penDrawing = false;
+
+ //set the marker color
+ this.setColor = function(color) {
+ this.color = color;
+ }
+
+ //lower the pen
+ this.penDown = function() {
+ this.penDrawing = true;
+ }
+
+ //raise the pen
+ this.penUp = function() {
+ this.penDrawing = false;
+ }
+
+
+ /**
+ * Receive a message from the user thread
+ * @param {*} message
+ */
+ this.receiveUser = function(message) {
+ this.color = message.color;
+ this.penDrawing = message.penDrawing;
+ }
+}
+
+
+function Chassis(x, y, heading, name)
+{
+ Part.call(this, null, x, y, heading, name);
+ this.type = "Chassis";
+
+ //no thread at first
+ this.thread = null;
+
+ //handle the subparts of the chassis
+ this.parts = Array();
+ this.addPart = function(p) {
+ this.parts.push(p);
+ };
+
+ // create the left and right motors
+ this.left = new Motor(this, -7, -7, 0, "left");
+ this.right = new Motor(this, -7, 7, Math.PI, "right");
+
+
+ //we start unexploded and healthy!
+ this.hp = 3;
+ this.blowedUp = false;
+ this.explosionVelocities = [];
+
+ // create the robot code
+ this.code = "";
+
+ // set up the laser battery
+ this.laserBattery = 50;
+
+ // deal with explosions
+ this.explode = function() {
+ //terminate the robot thread
+ if(this.thread){
+ this.thread.terminate();
+ }
+ this.blowedUp = true;
+ this.explosionVelocities = [];
+
+ //put all the parts in the world with new velocities
+ this.parts = this.parts.concat([this.left, this.right, this]);
+ var speed = 10;
+ for(var i=0; i= wxmax) {
+ this.x -= wxmax;
+ }
+ if(this.y >= wymax) {
+ this.y -= wymax;
+ }
+ };
+
+
+ /**
+ * Return a sendable (postable to a worker) version of this object.
+ */
+ this.sendable = function() {
+ var result = {};
+ result.name = this.name;
+ result.type = this.type;
+ result.code = this.code;
+ result.parts = [];
+
+ //push the motors onto the parts list
+ result.parts.push(this.left.sendable());
+ result.parts.push(this.right.sendable());
+
+ //push all the parts
+ for(var i=0; i 0.52 && angle < 5.76) {
+ continue;
+ }
+
+ //check for being the closest
+ if(dist.distance < closest) {
+ closest = dist.distance;
+ }
+ }
+
+ //calculate the distance
+ this.distance = closest / 60;
+ if(isNaN(this.distance) || this.distance > 5) {
+ this.distance = Infinity;
+ }
+
+
+ //pass the update into the web worker
+ if(this.parent.thread) {
+ this.parent.thread.postMessage({type: "update", update: {name: this.name, distance: this.distance}});
+ }
+ };
+
+
+ this.update = function() {
+ //populate the last update (if needed)
+ if(this.lastUpdate == undefined) {
+ this.lastUpdate = Date.now();
+ }
+
+ //compute elapsed time
+ var cur = Date.now();
+ var elapsed = (cur - this.lastUpdate) / 1000;
+
+ // trigger the sensor
+ if(elapsed >= 1 / this.freq) {
+ this.updateSensor();
+ this.lastUpdate = cur;
+ }
+ };
+}
+
+
+
+/**
+ * A sensor which determines the intensity of a light source.
+ * It can also be filtered, which makes it only see one color of light.
+ * @param {*} parent
+ * @param {*} x
+ * @param {*} y
+ */
+function LightSensor(parent, x, y) {
+ Part.call(this, parent, x, y);
+ this.type = "LightSensor"
+ this.worldx = 0;
+ this.worldy = 0;
+ this.intensity = 0;
+ this.freq = 10; //frequency in hertz
+
+ this.doc.vars = Array(
+ {name: 'intensity', doc: 'intensity of the sensed light'}
+ );
+
+ this.getRobotLights = function(r) {
+ var lights = [];
+
+ for(var i=0; i 0.52 && angle < 5.76) {
+ continue;
+ }
+
+ //calculate the square distance
+ var dist = dx*dx + dy*dy;
+ if(dist < closest) {
+ closest = dist;
+ }
+ }
+
+ //calculate the intensity
+ closest = closest / 400; // 20px per meter => 400px per meter^2
+
+ // 100% intensity at 1 m so...
+ this.intensity = 100 / closest;
+ if(isNaN(this.intensity)) {
+ this.intensity = 0;
+ }
+ if(this.intensity > 100) {
+ this.intensity=100;
+ }
+
+ //pass the update into the web worker
+ if(this.parent.thread) {
+ this.parent.thread.postMessage({type: "update", update: {name: this.name, intensity: this.intensity}});
+ }
+ };
+
+
+ this.update = function() {
+ //populate the last update (if needed)
+ if(this.lastUpdate == undefined) {
+ this.lastUpdate = Date.now();
+ }
+
+ //compute elapsed time
+ var cur = Date.now();
+ var elapsed = (cur - this.lastUpdate) / 1000;
+
+ // trigger the sensor
+ if(elapsed >= 1 / this.freq) {
+ this.updateSensor();
+ this.lastUpdate = cur;
+ }
+ };
+
+}
+
+
+
+/**
+ * A blast, from a laser. What else would it be?
+ */
+function LaserBlast(x, y, heading, firedBy) {
+ Part.call(this, null, x, y, heading);
+ this.type = "LaserBlast";
+
+ this.outline="red";
+ this.fill="red";
+
+ var speed = 30;
+
+ this.dx = speed * Math.cos(heading);
+ this.dy = speed * Math.sin(heading);
+ this.firedBy = firedBy;
+
+ this.vanish = function() {
+ //take ourselves out of the world objects
+ var toRemove = -1;
+ for(var i = 0; i< simState.worldObjects.length; i++) {
+ if(simState.worldObjects[i].part === this) {
+ toRemove = i;
+ break;
+ }
+ }
+ if(i >= 0) {
+ simState.worldObjects.splice(toRemove, 1);
+ }
+ }
+
+
+ this.update = function() {
+ this.x += this.dx;
+ this.y += this.dy;
+
+ //TODO, dont' hardcode the sizes
+ if(this.x < 0 || this.x > 800 || this.y < 0 || this.y > 600) {
+ this.vanish();
+ }
+ }
+}
+
+
+/**
+ * A frickin' laser beam.
+ */
+function Laser(parent, x, y, heading) {
+ Part.call(this, parent, x, y, heading);
+ this.type = "Laser";
+ this.charged = true;
+ this.lastUpdate = undefined;
+ this.chargeTime = 500;
+
+ this.doc.functions = Array(
+ {name: 'fire', doc: 'Fire the laser beam.', params: Array()}
+ );
+
+ //fire the laser beam
+ this.fire = function() {
+ // no charge, no pew
+ if(!this.charged) { return; }
+
+ // check that we have ample battery power
+ if(this.parent.laserBattery <= 0) {
+ return;
+ }
+
+ //no more power.
+ this.charged = false;
+ this.lastUpdate = Date.now();
+
+ //fire!
+ this.parent.depleteLaserBattery(1);
+ var lb = new LaserBlast(this.worldx, this.worldy, this.parent.heading, this.parent);
+ simState.worldObjects.push(constructView(lb));
+ }
+
+ //update the laser (charge it)
+ this.update = function() {
+ //already charged?
+ if(this.charged) {
+ return;
+ }
+
+ var elapsed = Date.now() - this.lastUpdate;
+ if(elapsed > this.chargeTime) {
+ this.charged = true;
+ this.lastUpdate = undefined;
+ }
+ }
+
+
+ //fire the laser when we receive a message
+ this.receiveUser = function(obj) {
+ this.fire();
+ }
+}
+
+
+/**
+ * Document the system functions.
+ */
+function SystemFunctions() {
+ this.doc = new PartDoc();
+ this.doc.showName = false;
+ this.name = 'System Functions';
+ this.type = '';
+
+ this.doc.functions = Array(
+ { name: 'await delay',
+ doc: 'Wait for a period of time to pass',
+ params: Array({name: 'ms', doc: 'delay in milliseconds'})}
+ );
+}
+
+
+
+/******************************************
+ * Utility Functions and Objects
+ ******************************************/
+
+
+function VectorView(x, y, heading, scale, points) {
+ // set up the fields
+ Positionable.call(this, x, y, heading);
+ this.scale = scale != undefined ? scale : 1;
+ this.points = points != undefined ? points : {};
+ this.outline = undefined;
+ this.fill = undefined;
+ this.polygon = [];
+
+
+ /**
+ * Reset the minx, miny, maxx, and maxy to Infinity and -Infinity
+ */
+ this.resetExtents = function() {
+ //extents of the vector view
+ this.minx = Infinity;
+ this.miny = Infinity;
+ this.maxx = -Infinity;
+ this.maxy = -Infinity;
+ };
+ this.resetExtents();
+
+
+ // draw the shape
+ this.draw = function(canvas, context) {
+ var x;
+ var y;
+ var started = false;
+ this.polygon = [];
+
+ //reset the extents
+ this.resetExtents();
+
+ // skip the blank shapes
+ if(this.points.length == 0) {
+ return;
+ }
+
+ //compute rotation coeffecients
+ var sin_th = Math.sin(this.heading);
+ var cos_th = Math.cos(this.heading);
+
+ context.beginPath();
+ for(var i in points) {
+ var p = points[i];
+
+ // get the raw point and scale
+ x = p.x * this.scale;
+ y = p.y * this.scale;
+
+ // rotate
+ var rx, ry;
+ rx = x * cos_th - y * sin_th;
+ ry = x * sin_th + y * cos_th;
+ x = rx;
+ y = ry;
+
+ // translate
+ x += this.x;
+ y += this.y;
+
+ // add to the polygon
+ this.polygon.push({x: x, y: y});
+
+ //add the line or start the shape
+ if(started) {
+ context.lineTo(x, y);
+ } else {
+ context.moveTo(x, y);
+ started = true;
+ }
+
+ //track extents
+ if(x < this.minx) { this.minx = x; }
+ if(x > this.maxx) { this.maxx = x; }
+ if(y < this.miny) { this.miny = y; }
+ if(y > this.maxy) { this.maxy = y; }
+ }
+ context.closePath();
+
+ // set the colors, if needed
+ if(this.outline) {
+ context.strokeStyle = this.outline;
+ }
+ if(this.fill) {
+ context.fillStyle = this.fill;
+ }
+
+ //draw the path
+ context.fill();
+ context.stroke();
+ };
+
+
+ /**
+ * Determines if the point (x,y) is within the extents of the view.
+ * @param {*} x - x coordinate
+ * @param {*} y - y coordinate
+ * @returns true if (x,y) is inside this view, false otherwise.
+ */
+ this.encloses = function(x, y) {
+ return x >= this.minx && x <= this.maxx &&
+ y >= this.miny && y <= this.maxy;
+ }
+}
+
+
+/**
+ * Construct a view for the given part. It works by calling the correct
+ * constructor for the part.
+ * @param {*} part
+ * @returns The newly constructed part. Returns undefined for parts with no known view.
+ */
+function constructView(part) {
+ if(part.type == "Chassis") {
+ return new ChassisView(part);
+ } else if(part.type == "Motor") {
+ return new MotorView(part);
+ } else if(part.type == "Marker") {
+ return new MarkerView(part);
+ } else if(part.type == "Light") {
+ return new LightView(part);
+ } else if(part.type == "LightSensor") {
+ return new LightSensorView(part);
+ } else if(part.type == "RangeSensor") {
+ return new RangeSensorView(part);
+ } else if(part.type == "Wall") {
+ return new WallView(part);
+ } else if(part.type == "LaserBlast") {
+ return new LaserBlastView(part);
+ } else if(part.type == "Laser") {
+ return new LaserView(part);
+ }
+ // we don't know how to show this part.
+ return undefined;
+}
+
+
+function PartView(part) {
+ // Construct the positionable parts of ourselves
+ Positionable.call(this, part.x, part.y, part.heading);
+
+ // Every PartView has a vector view
+ this.view = null;
+
+ // Scale the view
+ this.scale = 1;
+
+ // We also need a list of sub views
+ this.subviews = [];
+ this.addSubview = function(view) {
+ view.reOrigin();
+ this.subviews.push(view);
+ }
+
+ // remember the part we are viewing!
+ this.part = part;
+
+ /**
+ * Draw the part along with all of its subparts.
+ * @param {*} canvas - The canvas to draw on.
+ * @param {*} context - The context to draw on.
+ */
+ this.draw = function(canvas, context) {
+ // unparented parts are in the world (but not necessarily of
+ // it!)
+ if(!this.part.parent) {
+ this.x = this.part.x;
+ this.y = this.part.y;
+ this.heading = this.part.heading;
+ }
+
+ // set the color
+ this.view.outline = this.part.outline;
+ this.view.fill = this.part.fill;
+
+ // draw the base view (if it exists)
+ if(this.view) {
+ this.view.x = this.x;
+ this.view.y = this.y;
+ this.view.heading = this.heading;
+ this.view.scale = this.scale;
+ this.view.draw(canvas, context);
+ part.worldx = (this.view.minx + this.view.maxx)/2;
+ part.worldy = (this.view.miny + this.view.maxy)/2;
+ }
+
+ // draw each subviews offset to this view's pose
+ for(var i = 0; i < this.subviews.length; i++) {
+ var v = this.subviews[i];
+ v.x = this.x;
+ v.y = this.y;
+ v.face(this.heading);
+ v.scale = this.scale;
+ v.draw(canvas, context);
+ }
+ };
+
+
+ /**
+ * Shift the part's origin to its x,y and set its position to 0,0.
+ */
+ this.reOrigin = function() {
+ //compute rotation coeffecients
+ var sin_th = Math.sin(this.heading);
+ var cos_th = Math.cos(this.heading);
+
+ for(var i=0; i < this.view.points.length; i++) {
+ var p = this.view.points[i];
+ var rx, ry;
+ rx = p.x * cos_th - p.y * sin_th;
+ ry = p.x * sin_th + p.y * cos_th;
+ p.x = rx + this.x;
+ p.y = ry + this.y;
+ }
+
+ this.heading = 0;
+ this.view.heading = 0;
+ this.view.x = 0;
+ this.view.y = 0;
+ this.x = 0;
+ this.y = 0;
+ };
+
+
+ /**
+ * Move the part to x, y (in part coordinates)
+ * @param {*} x
+ * @param {*} y
+ */
+ this.moveTo = function(x, y) {
+ var dx = x - this.part.x;
+ var dy = y - this.part.y;
+
+ // Move all the points in the view
+ for(var i=0; i < this.view.points.length; i++) {
+ var p = this.view.points[i];
+ p.x += dx;
+ p.y += dy;
+ }
+
+ // move the part position
+ this.part.x = x;
+ this.part.y = y;
+ }
+
+
+ /**
+ * Convert a global coordinate to a part view coordinate.
+ * @param {*} x
+ * @param {*} y
+ * @returns Location object {x:, y:}
+ */
+ this.globalToPartLoc = function(x, y) {
+ var sin_th = Math.sin(-this.heading);
+ var cos_th = Math.cos(-this.heading);
+ var result = {};
+
+ x /= this.scale;
+ y /= this.scale;
+ x -= this.x / this.scale;
+ y -= this.y / this.scale;
+
+ result.x = x * cos_th - y * sin_th;
+ result.y = x * sin_th + y * cos_th;
+
+ return result;
+ };
+}
+
+
+/**
+ * Constructor for the chassis view object. This visualizes an entire
+ * robot.
+ * @param {*} part - The chassis part
+ */
+function ChassisView(part) {
+ // initialize the partview
+ PartView.call(this, part);
+
+ // add the motors to the subview list.
+ this.addSubview(constructView(part.left));
+ this.addSubview(constructView(part.right));
+
+ //add the other parts to the view
+ for(var i=0; i" + part.type + "";
+ li.onclick = function() {displayPartDoc(part); };
+ partList.appendChild(li);
+}
+
+
+/**
+ * Display part documentation.
+ * @param {*} part
+ */
+function displayPartDoc(part) {
+ var doc = document.getElementById('codePartDoc');
+ doc.innerHTML = '';
+ var e = document.createElement('div');
+ e.classList.add('toolboxHead');
+ e.innerHTML = part.name;
+ doc.appendChild(e);
+ part.doc.display(doc, part.name);
+}
+
+
+/**
+ * Draw the simulator canvas.
+ */
+function drawSim() {
+ var canvas = document.getElementById('simfg');
+ var context = canvas.getContext("2d");
+
+ //scale the view
+ simView.scale=2;
+
+ // clear the frame
+ context.clearRect(0, 0, canvas.width, canvas.height);
+
+ //draw the world objects
+ for(i in simState.worldObjects) {
+ simState.worldObjects[i].scale = 2;
+ simState.worldObjects[i].draw(canvas, context);
+ }
+
+ //draw the robot
+ simView.draw(canvas, context);
+
+ //draw the opponent (if there is one)
+ if(opponent) {
+ opponentView.scale=2;
+ opponentView.draw(canvas, context);
+ }
+}
+
+
+/**
+ * Draws the build canvas.
+ */
+function drawBuild() {
+ var canvas = document.getElementById("buildCanvas");
+ var context = canvas.getContext("2d");
+
+ //draw the build window
+ graphPaperFill("buildCanvas");
+ buildView.draw(canvas, context);
+}
+
+
+/**
+ * Fill the canvas with the given ID with a graph paper like pattern.
+ * @param {*} - id
+ */
+function graphPaperFill(id) {
+ var back = document.getElementById(id);
+ var context = back.getContext("2d");
+ context.fillStyle = "white";
+ context.fillRect(0, 0, back.width, back.height);
+
+ context.beginPath();
+ for (var lx = 0; lx < back.width; lx += 20) {
+ context.moveTo(lx, 0);
+ context.lineTo(lx, back.height);
+ }
+ for (var ly = 0; ly < back.height; ly += 20) {
+ context.moveTo(0, ly);
+ context.lineTo(back.width, ly);
+ }
+ context.strokeStyle = "lightblue";
+ context.stroke();
+
+ context.strokeRect(0, 0, back.width, back.height);
+}
+
+
+/*
+ * Simulation Events
+ */
+
+/**
+ * Handler for mouse down events on the sim canvas.
+ */
+function simMouseDown(event) {
+
+ // get the target of the click
+ simState.dragTarget = null;
+ if(simView.view.encloses(event.offsetX, event.offsetY)) {
+ simState.dragTarget = simView;
+ } else {
+ for(i in simState.worldObjects) {
+ var obj = simState.worldObjects[i];
+ if(obj.view.encloses(event.offsetX, event.offsetY)) {
+ simState.dragTarget = obj;
+ break;
+ }
+ }
+ }
+
+ if(!simState.dragTarget) {
+ return false;
+ }
+
+ //record this position
+ simState.lastX = event.offsetX;
+ simState.lastY = event.offsetY;
+
+ //get the mode
+ if(document.getElementById('dragRotate').checked) {
+ simState.dragMode = DRAG_ROTATE;
+ } else if(document.getElementById('dragMove').checked) {
+ simState.dragMode = DRAG_MOVE;
+ simState.dragTarget.part.moveTo(event.offsetX, event.offsetY);
+ } else if(document.getElementById('dragResize').checked) {
+ simState.dragMode = DRAG_RESIZE;
+ } else {
+ simState.dragMode = DRAG_NONE;
+ }
+
+ //refresh the canvas
+ drawSim();
+
+ return true;
+}
+
+
+/**
+ * Handler for mouse up events on the sim canvas.
+ */
+function simMouseUp(event) {
+ // one last move (if that is what we are up to!)
+ if(simState.dragMode == DRAG_MOVE) {
+ simState.dragTarget.part.moveTo(event.offsetX, event.offsetY);
+ } else if (simState.dragMode == DRAG_RESIZE && simState.dragTarget.part.type == 'Wall'){
+ if (simState.dragTarget.part.resizeFactor < 5){
+ simState.dragTarget.part.resizeFactor = simState.dragTarget.part.resizeFactor + 1;
+ }else{
+ simState.dragTarget.part.resizeFactor = 1;
+ }
+ for (var i=0; simState.worldObjects.length; i++){
+ if (simState.worldObjects[i].part.name == simState.dragTarget.part.name){
+ simState.worldObjects.splice(i, 1);
+ break;
+ };
+ }
+ simState.worldObjects.push(new WallView(simState.dragTarget.part));
+ }
+
+ // end the drag mode
+ simState.dragMode = DRAG_NONE;
+
+ //if this is not the robot, select it
+ if(simState.dragTarget && simState.dragTarget.part.type != "Chassis") {
+ selectPart(simState.dragTarget, simState);
+ }
+
+ //refresh the canvas
+ drawSim();
+ return true;
+}
+
+
+/**
+ * Handler for mouse move events on the sim canvas.
+ */
+function simMouseMove(event) {
+ // if we have no drag mode, do nothing
+ if(simState.dragMode == DRAG_NONE) {
+ return false;
+ }
+
+ // process movement
+ if(simState.dragMode == DRAG_MOVE) {
+ simState.dragTarget.part.moveTo(event.offsetX, event.offsetY);
+ }
+
+ //process rotation
+ if(simState.dragMode == DRAG_ROTATE) {
+ simState.dragTarget.part.rotate((event.offsetY-simState.lastY) * 0.1);
+ }
+
+ //record this position
+ simState.lastX = event.offsetX;
+ simState.lastY = event.offsetY;
+
+ // refresh the canvas
+ drawSim();
+
+ return true;
+}
+
+
+/**
+ * Add a light to the simulator.
+ * @param {*} event
+ */
+function simAddLight(event) {
+ var canvas = document.getElementById("simfg");
+ var light = new Light(null, canvas.width/2, canvas.height/2);
+
+ simState.worldObjects.push(constructView(light));
+ drawSim();
+}
+
+
+/**
+ * Add a wall to the simulator.
+ * @param {*} event
+ */
+function simAddWall(event) {
+ var canvas = document.getElementById("simfg");
+ var wall = new Wall(null, canvas.width/2, canvas.height/2);
+
+ simState.worldObjects.push(constructView(wall));
+ drawSim();
+}
+
+
+
+/*
+ * Build Events
+ */
+
+/**
+ * Show the editor for the part specified by "view".
+ * @param {*} view
+ */
+function showPartEditor(view, state) {
+ //populate the type and name
+ document.getElementById(state.prefix +"PartType").innerHTML = view.part.type;
+ document.getElementById(state.prefix +"PartName").value = view.part.name;
+
+ //get the colors populated
+ document.getElementById(state.prefix + "PartOutlineColor").value = view.part.outline;
+ document.getElementById(state.prefix + "PartFillColor").value = view.part.fill;
+
+
+ //show the editor pane
+ document.getElementById(state.prefix + "PartEditor").style.display="block";
+}
+
+
+/**
+ * Hide the part editor.
+ */
+function hidePartEditor(state) {
+ //hide the editor pane
+ document.getElementById(state.prefix + "PartEditor").style.display="none";
+}
+
+
+/**
+ * Select the partview in the editor.
+ * @param {*} view
+ */
+function selectPart(view, state) {
+ // deselect (if needed)
+ if(state.editTarget != null) {
+ deselectPart(state);
+ }
+
+ // grab the original color
+ state.editOriginalOutline = view.part.outline;
+
+ // set up the target
+ state.editTarget = view;
+
+ //show the editor
+ showPartEditor(view, state);
+
+ // color it coral
+ view.part.outline = "coral";
+
+
+ // redraw the canvas
+ if(state.prefix == "build") {
+ drawBuild();
+ } else if(state.prefix == "sim") {
+ drawSim();
+ }
+}
+
+
+/**
+ * Deselect the selected partview (if there is one)
+ */
+function deselectPart(state) {
+ // do nothing if there is no selected part
+ if(state.editTarget == null) {
+ return;
+ }
+
+ // retore the original color
+ state.editTarget.part.outline = state.editOriginalOutline;
+
+ // remove the selection
+ state.editTarget = null;
+ hidePartEditor(state);
+
+ // redraw the canvas
+ if(state.prefix == "build") {
+ drawBuild();
+ } else if(state.prefix == "sim") {
+ drawSim();
+ }
+}
+
+
+/**
+ * Apply the editor to the given state.
+ */
+function applyEditor(state) {
+ //get the color from the editor
+ var fill = document.getElementById(state.prefix + "PartFillColor").value;
+ var outline = document.getElementById(state.prefix + "PartOutlineColor").value;
+
+ //get the part name
+ var name = document.getElementById(state.prefix + "PartName").value;
+
+ //get the part we are editing
+ var part = state.editTarget.part;
+
+ //deselect the part
+ deselectPart(state);
+
+ //set the fields
+ part.fill = fill;
+ part.outline = outline;
+ part.name = name;
+
+ // redraw the canvas
+ if(state.prefix == "build") {
+ drawBuild();
+ } else if(state.prefix == "sim") {
+ drawSim();
+ }
+}
+
+
+/**
+ * Handle build canvas mouse down
+ * @param {*} event
+ */
+function buildMouseDown(event) {
+ var x = event.offsetX;
+ var y = event.offsetY;
+
+ // assume we have found nothing
+ buildState.dragTarget = null;
+ buildState.dragMode = DRAG_NONE;
+
+ // check for clicking on a robot subpart
+ for(var i=0; i < buildView.subviews.length; i++) {
+ var partView = buildView.subviews[i];
+ if(partView.view.encloses(x, y)) {
+ buildState.dragTarget = partView;
+ if(buildState.dragTarget.part.type != "Motor") {
+ buildState.dragMode = DRAG_MOVE;
+ }
+ break;
+ }
+ }
+
+ // if no subpart is selected, see if we have selected the chassis body.
+ if(buildState.dragTarget == null && buildView.view.encloses(x,y)) {
+ buildState.dragTarget = buildView;
+ }
+
+ // if we still lack a subpart, return
+ if(!buildState.dragTarget) {
+ return;
+ }
+
+ //record last x and last y
+ buildState.lastX = x;
+ buildState.lastY = y;
+}
+
+
+/**
+ * Handle build canvas mouse move
+ * @param {*} event
+ */
+function buildMouseMove(event) {
+ //if there is no return target, stop!
+ if(!buildState.dragTarget) { return; }
+
+ var x = event.offsetX;
+ var y = event.offsetY;
+
+ //move the part (if that is our mode)
+ if(buildState.dragMode == DRAG_MOVE) {
+ if(buildView.view.encloses(x, y)) {
+ var p = buildState.dragTarget.globalToPartLoc(x, y);
+ buildState.dragTarget.moveTo(p.x, p.y);
+ drawBuild();
+ }
+ }
+
+ //record last x and last y
+ buildState.lastX = x;
+ buildState.lastY = y;
+}
+
+
+/**
+ * Handle build canvas mouse up
+ * @param {*} event
+ */
+function buildMouseUp(event) {
+ // if there was a drag target, it is now the selected object
+ if(buildState.dragTarget) {
+ selectPart(buildState.dragTarget, buildState);
+ }
+
+ // clear the drag target
+ buildState.dragTarget = null;
+ buildState.dragMode = DRAG_NONE;
+}
+
+
+/**
+ * Handle the apply button for the build part editor.
+ * @param {*} event
+ */
+function buildApply(event) {
+ applyEditor(buildState);
+}
+
+
+/**
+ * Handle the apply button for the sim part editor.
+ * @param {*} event
+ */
+function simApply(event) {
+ applyEditor(simState);
+}
+
+
+/**
+ * Handle the build cancel button
+ * @param {*} event
+ */
+function buildCancel(event) {
+ deselectPart(buildState);
+}
+
+
+/**
+ * Handle the sim cancel button
+ * @param {*} event
+ */
+function simCancel(event) {
+ deselectPart(simState);
+}
+
+
+
+/**
+ * Handle the build delete button.
+ * @param {*} event
+ */
+function buildDeletePart(event) {
+ var part = buildState.editTarget.part;
+
+ if(part.type == "Motor" || part.type == "Chassis") {
+ alert("You cannot remove this part.");
+ return;
+ }
+
+ if(!confirm("Are you sure you want to remove " + part.name + "?")) {
+ return;
+ }
+
+ deselectPart(buildState);
+
+ // remove it from the robot parts list
+ robot.parts.splice(robot.parts.indexOf(part), 1);
+
+ // rebuild the robot
+ buildView = new ChassisBuildView(robot);
+ drawBuild();
+}
+
+
+/**
+ * Handle the sim delete button.
+ * @param {*} event
+ */
+function simDeletePart(event) {
+ var part = simState.editTarget.part;
+
+ if(part.type == "Motor" || part.type == "Chassis") {
+ alert("You cannot remove this part.");
+ return;
+ }
+
+ if(!confirm("Are you sure you want to remove " + part.name + "?")) {
+ return;
+ }
+
+ deselectPart(simState);
+
+ var partArray = [];
+ for(var i=0; i
+
+
+ Gradbot Simulator
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Drag Mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Simulation Controls
+
+
+
+
+
+
+
+ Object Editor
+
+
+ Type:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Add Object
+
+
+
+
+
+
+ Manage Oponent
+
+
+
+
+
+ *Note: refresh page after running simulation with opponent*
+
+
+