[
  {
    "path": "LICENSE.md",
    "content": "### GNU GENERAL PUBLIC LICENSE\n\nVersion 3, 29 June 2007\n\nCopyright (C) 2007 Free Software Foundation, Inc.\n<https://fsf.org/>\n\nEveryone is permitted to copy and distribute verbatim copies of this\nlicense document, but changing it is not allowed.\n\n### Preamble\n\nThe GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\nThe licenses for most software and other practical works are designed\nto take away your freedom to share and change the works. By contrast,\nthe GNU General Public License is intended to guarantee your freedom\nto share and change all versions of a program--to make sure it remains\nfree software for all its users. We, the Free Software Foundation, use\nthe GNU General Public License for most of our software; it applies\nalso to any other work released this way by its authors. You can apply\nit to your programs, too.\n\nWhen we speak of free software, we are referring to freedom, not\nprice. Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\nTo protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights. Therefore, you\nhave certain responsibilities if you distribute copies of the\nsoftware, or if you modify it: responsibilities to respect the freedom\nof others.\n\nFor example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received. You must make sure that they, too, receive\nor can get the source code. And you must show them these terms so they\nknow their rights.\n\nDevelopers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\nFor the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software. For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\nSome devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the\nmanufacturer can do so. This is fundamentally incompatible with the\naim of protecting users' freedom to change the software. The\nsystematic pattern of such abuse occurs in the area of products for\nindividuals to use, which is precisely where it is most unacceptable.\nTherefore, we have designed this version of the GPL to prohibit the\npractice for those products. If such problems arise substantially in\nother domains, we stand ready to extend this provision to those\ndomains in future versions of the GPL, as needed to protect the\nfreedom of users.\n\nFinally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish\nto avoid the special danger that patents applied to a free program\ncould make it effectively proprietary. To prevent this, the GPL\nassures that patents cannot be used to render the program non-free.\n\nThe precise terms and conditions for copying, distribution and\nmodification follow.\n\n### TERMS AND CONDITIONS\n\n#### 0. Definitions.\n\n\"This License\" refers to version 3 of the GNU General Public License.\n\n\"Copyright\" also means copyright-like laws that apply to other kinds\nof works, such as semiconductor masks.\n\n\"The Program\" refers to any copyrightable work licensed under this\nLicense. Each licensee is addressed as \"you\". \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\nTo \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of\nan exact copy. The resulting work is called a \"modified version\" of\nthe earlier work or a work \"based on\" the earlier work.\n\nA \"covered work\" means either the unmodified Program or a work based\non the Program.\n\nTo \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy. Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\nTo \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies. Mere interaction with a user\nthrough a computer network, with no transfer of a copy, is not\nconveying.\n\nAn interactive user interface displays \"Appropriate Legal Notices\" to\nthe extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License. If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n#### 1. Source Code.\n\nThe \"source code\" for a work means the preferred form of the work for\nmaking modifications to it. \"Object code\" means any non-source form of\na work.\n\nA \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\nThe \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form. A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\nThe \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities. However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work. For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\nThe Corresponding Source need not include anything that users can\nregenerate automatically from other parts of the Corresponding Source.\n\nThe Corresponding Source for a work in source code form is that same\nwork.\n\n#### 2. Basic Permissions.\n\nAll rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met. This License explicitly affirms your unlimited\npermission to run the unmodified Program. The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work. This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\nYou may make, run and propagate covered works that you do not convey,\nwithout conditions so long as your license otherwise remains in force.\nYou may convey covered works to others for the sole purpose of having\nthem make modifications exclusively for you, or provide you with\nfacilities for running those works, provided that you comply with the\nterms of this License in conveying all material for which you do not\ncontrol copyright. Those thus making or running the covered works for\nyou must do so exclusively on your behalf, under your direction and\ncontrol, on terms that prohibit them from making any copies of your\ncopyrighted material outside their relationship with you.\n\nConveying under any other circumstances is permitted solely under the\nconditions stated below. Sublicensing is not allowed; section 10 makes\nit unnecessary.\n\n#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\nNo covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\nWhen you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such\ncircumvention is effected by exercising rights under this License with\nrespect to the covered work, and you disclaim any intention to limit\noperation or modification of the work as a means of enforcing, against\nthe work's users, your or third parties' legal rights to forbid\ncircumvention of technological measures.\n\n#### 4. Conveying Verbatim Copies.\n\nYou may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\nYou may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n#### 5. Conveying Modified Source Versions.\n\nYou may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these\nconditions:\n\n-   a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n-   b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under\n    section 7. This requirement modifies the requirement in section 4\n    to \"keep intact all notices\".\n-   c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy. This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged. This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n-   d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\nA compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit. Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n#### 6. Conveying Non-Source Forms.\n\nYou may convey a covered work in object code form under the terms of\nsections 4 and 5, provided that you also convey the machine-readable\nCorresponding Source under the terms of this License, in one of these\nways:\n\n-   a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n-   b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the Corresponding\n    Source from a network server at no charge.\n-   c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source. This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n-   d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge. You need not require recipients to copy the\n    Corresponding Source along with the object code. If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source. Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n-   e) Convey the object code using peer-to-peer transmission,\n    provided you inform other peers where the object code and\n    Corresponding Source of the work are being offered to the general\n    public at no charge under subsection 6d.\n\nA separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\nA \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal,\nfamily, or household purposes, or (2) anything designed or sold for\nincorporation into a dwelling. In determining whether a product is a\nconsumer product, doubtful cases shall be resolved in favor of\ncoverage. For a particular product received by a particular user,\n\"normally used\" refers to a typical or common use of that class of\nproduct, regardless of the status of the particular user or of the way\nin which the particular user actually uses, or expects or is expected\nto use, the product. A product is a consumer product regardless of\nwhether the product has substantial commercial, industrial or\nnon-consumer uses, unless such uses represent the only significant\nmode of use of the product.\n\n\"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to\ninstall and execute modified versions of a covered work in that User\nProduct from a modified version of its Corresponding Source. The\ninformation must suffice to ensure that the continued functioning of\nthe modified object code is in no case prevented or interfered with\nsolely because modification has been made.\n\nIf you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information. But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\nThe requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or\nupdates for a work that has been modified or installed by the\nrecipient, or for the User Product in which it has been modified or\ninstalled. Access to a network may be denied when the modification\nitself materially and adversely affects the operation of the network\nor violates the rules and protocols for communication across the\nnetwork.\n\nCorresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n#### 7. Additional Terms.\n\n\"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law. If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\nWhen you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit. (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.) You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\nNotwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders\nof that material) supplement the terms of this License with terms:\n\n-   a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n-   b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n-   c) Prohibiting misrepresentation of the origin of that material,\n    or requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n-   d) Limiting the use for publicity purposes of names of licensors\n    or authors of the material; or\n-   e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n-   f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions\n    of it) with contractual assumptions of liability to the recipient,\n    for any liability that these contractual assumptions directly\n    impose on those licensors and authors.\n\nAll other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10. If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term. If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\nIf you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\nAdditional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions; the\nabove requirements apply either way.\n\n#### 8. Termination.\n\nYou may not propagate or modify a covered work except as expressly\nprovided under this License. Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\nHowever, if you cease all violation of this License, then your license\nfrom a particular copyright holder is reinstated (a) provisionally,\nunless and until the copyright holder explicitly and finally\nterminates your license, and (b) permanently, if the copyright holder\nfails to notify you of the violation by some reasonable means prior to\n60 days after the cessation.\n\nMoreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\nTermination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License. If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n#### 9. Acceptance Not Required for Having Copies.\n\nYou are not required to accept this License in order to receive or run\na copy of the Program. Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance. However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work. These actions infringe copyright if you do\nnot accept this License. Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n#### 10. Automatic Licensing of Downstream Recipients.\n\nEach time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License. You are not responsible\nfor enforcing compliance by third parties with this License.\n\nAn \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations. If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\nYou may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License. For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n#### 11. Patents.\n\nA \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based. The\nwork thus licensed is called the contributor's \"contributor version\".\n\nA contributor's \"essential patent claims\" are all patent claims owned\nor controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version. For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\nEach contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\nIn the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement). To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\nIf you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients. \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\nIf, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\nA patent license is \"discriminatory\" if it does not include within the\nscope of its coverage, prohibits the exercise of, or is conditioned on\nthe non-exercise of one or more of the rights that are specifically\ngranted under this License. You may not convey a covered work if you\nare a party to an arrangement with a third party that is in the\nbusiness of distributing software, under which you make payment to the\nthird party based on the extent of your activity of conveying the\nwork, and under which the third party grants, to any of the parties\nwho would receive the covered work from you, a discriminatory patent\nlicense (a) in connection with copies of the covered work conveyed by\nyou (or copies made from those copies), or (b) primarily for and in\nconnection with specific products or compilations that contain the\ncovered work, unless you entered into that arrangement, or that patent\nlicense was granted, prior to 28 March 2007.\n\nNothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n#### 12. No Surrender of Others' Freedom.\n\nIf conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License. If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under\nthis License and any other pertinent obligations, then as a\nconsequence you may not convey it at all. For example, if you agree to\nterms that obligate you to collect a royalty for further conveying\nfrom those to whom you convey the Program, the only way you could\nsatisfy both those terms and this License would be to refrain entirely\nfrom conveying the Program.\n\n#### 13. Use with the GNU Affero General Public License.\n\nNotwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work. The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n#### 14. Revised Versions of this License.\n\nThe Free Software Foundation may publish revised and/or new versions\nof the GNU General Public License from time to time. Such new versions\nwill be similar in spirit to the present version, but may differ in\ndetail to address new problems or concerns.\n\nEach version is given a distinguishing version number. If the Program\nspecifies that a certain numbered version of the GNU General Public\nLicense \"or any later version\" applies to it, you have the option of\nfollowing the terms and conditions either of that numbered version or\nof any later version published by the Free Software Foundation. If the\nProgram does not specify a version number of the GNU General Public\nLicense, you may choose any version ever published by the Free\nSoftware Foundation.\n\nIf the Program specifies that a proxy can decide which future versions\nof the GNU General Public License can be used, that proxy's public\nstatement of acceptance of a version permanently authorizes you to\nchoose that version for the Program.\n\nLater license versions may give you additional or different\npermissions. However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n#### 15. Disclaimer of Warranty.\n\nTHERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT\nWARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND\nPERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE\nDEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR\nCORRECTION.\n\n#### 16. Limitation of Liability.\n\nIN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR\nCONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES\nARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT\nNOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR\nLOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM\nTO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER\nPARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.\n\n#### 17. Interpretation of Sections 15 and 16.\n\nIf the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\nEND OF TERMS AND CONDITIONS\n\n### How to Apply These Terms to Your New Programs\n\nIf you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these\nterms.\n\nTo do so, attach the following notices to the program. It is safest to\nattach them to the start of each source file to most effectively state\nthe exclusion of warranty; and each file should have at least the\n\"copyright\" line and a pointer to where the full notice is found.\n\n        <one line to give the program's name and a brief idea of what it does.>\n        Copyright (C) <year>  <name of author>\n\n        This program is free software: you can redistribute it and/or modify\n        it under the terms of the GNU General Public License as published by\n        the Free Software Foundation, either version 3 of the License, or\n        (at your option) any later version.\n\n        This program is distributed in the hope that it will be useful,\n        but WITHOUT ANY WARRANTY; without even the implied warranty of\n        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n        GNU General Public License for more details.\n\n        You should have received a copy of the GNU General Public License\n        along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper\nmail.\n\nIf the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n        <program>  Copyright (C) <year>  <name of author>\n        This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n        This is free software, and you are welcome to redistribute it\n        under certain conditions; type `show c' for details.\n\nThe hypothetical commands \\`show w' and \\`show c' should show the\nappropriate parts of the General Public License. Of course, your\nprogram's commands might be different; for a GUI interface, you would\nuse an \"about box\".\n\nYou should also get your employer (if you work as a programmer) or\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary. For more information on this, and how to apply and follow\nthe GNU GPL, see <https://www.gnu.org/licenses/>.\n\nThe GNU General Public License does not permit incorporating your\nprogram into proprietary programs. If your program is a subroutine\nlibrary, you may consider it more useful to permit linking proprietary\napplications with the library. If this is what you want to do, use the\nGNU Lesser General Public License instead of this License. But first,\nplease read <https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "# Nibbler\n\nNibbler is a real-time analysis GUI for [Leela Chess Zero](http://lczero.org/play/quickstart/) (Lc0), which runs Leela in the background and constantly displays opinions about the current position. You can also compel the engine to evaluate one or more specific moves. Nibbler is loosely inspired by [Lizzie](https://github.com/featurecat/lizzie) and [Sabaki](https://github.com/SabakiHQ/Sabaki).\n\nThese days, Nibbler more-or-less works with traditional engines like [Stockfish](https://stockfishchess.org/), too. (Ensure `MultiPV` is `1`, `Threads` (CPU) is set, and `Hash` is set (more is better), for maximum strength.)\n\nFor prebuilt binary releases, see the [Releases](https://github.com/rooklift/nibbler/releases) section. For help, the [Discord](https://discordapp.com/invite/pKujYxD) may be your best bet, or open an issue here.\n\n![Screenshot](https://user-images.githubusercontent.com/16438795/270297798-a432ea17-3601-4143-bddb-97420a0d6e6c.png)\n\n## Features\n\n* Display Leela's top choices graphically.\n* Winrate graph.\n* Optionally shows Leela statistics like N, P, Q, S, U, V, and WDL for each move.\n* UCI `searchmoves` functionality.\n* Automatic full-game analysis.\n* Play against Leela from any position.\n* Leela self-play from any position.\n* PGN loading via menu, clipboard, or drag-and-drop.\n* Supports PGN variations of arbitrary depth.\n* FEN loading.\n* Chess 960.\n\n## Installation - Windows / Linux\n\nSome Windows and Linux standalone releases are uploaded to the [Releases](https://github.com/rooklift/nibbler/releases) section from time to time.\n\n*Alternatively*, it is possible to run Nibbler from source. This requires Electron, but has no other dependencies. If you have Electron installed (e.g. `npm install -g electron`) you can likely enter the `/src` directory, then do `electron .` to run it. Nibbler should be compatible with at least version 5 and above.\n\nYou could also build a standalone app. See comments inside the Python script `builder.py` for info.\n\n## Linux install script\n\nLinux users can make use of the following *one-liner* to install the latest version of Nibbler:\n\n```bash\ncurl -L https://raw.githubusercontent.com/rooklift/nibbler/master/files/scripts/install.sh | bash\n```\n\n## Installation - Mac\n\nMac builds have been made by [twoplan](https://github.com/twoplan/Nibbler-for-macOS) and [Jac-Zac](https://github.com/Jac-Zac/Nibbler_MacOS) and [Zamana](https://github.com/Zamana/nibbler) - the last of which is probably the most up-to-date.\n\n## Mac install script\n\nAlternatively, MacOS users can run the following *one-liner* to assemble Nibbler locally. This (hopefully) removes any codesigning issues (Gatekeeper refusing to open unauthorized apps) by building Nibbler on-the-fly, though I can't test it myself:\n\n```bash\ncurl -L https://raw.githubusercontent.com/rooklift/nibbler/master/files/scripts/install_mac.sh | bash\n```\n\n## Advanced engine options\n\nMost people won't need them, but all of Leela's engine options can be set in two ways:\n\n* Leela automatically loads options from a file called `lc0.config` at startup - see [here](https://lczero.org/play/configuration/flags/#config-file).\n* Nibbler will send UCI options specified in Nibbler's own `engines.json` file (which you can find via the Dev menu).\n\n## Hints and tips\n\nAn option to enable the UCI `searchmoves` feature is available in the Analysis menu. Once enabled, one or more moves can be specified as moves to focus on; Leela will ignore other moves. This is useful when you think Leela isn't giving a certain move enough attention.\n\nLeela forgets much of the evaluation if the position changes. To mitigate this, an option in the Analysis menu allows you to hover over a PV (on the right) and see it play out on the board, without changing the position we're actually analysing. You might prefer to halt Leela while doing this, so that the PVs don't change while you're looking at them.\n\nLeela running out of RAM can be a problem if searches go on too long. You might like to set a reasonable node limit (in the Engine menu), perhaps 10 million or so.\n\n## Thanks\n\nThanks to everyone in Discord and GitHub who's offered advice and suggestions; and thanks to all Lc0 devs and GPU-hours contributors!\n\nThe pieces are from [Lichess](https://lichess.org/).\n\nIcon design by [ciriousjoker](https://github.com/ciriousjoker) based on [this](https://www.svgrepo.com/svg/155301/chess).\n"
  },
  {
    "path": "files/.editorconfig",
    "content": "[*]\nindent_style = tab\nindent_size = 4\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": "files/.gitignore",
    "content": ".DS_Store\nscripts/dist\nscripts/electron_zipped\nscripts/update_my_installation.py\n"
  },
  {
    "path": "files/misc/chess960.txt",
    "content": "https://www.mark-weeks.com/cfaa/chess960/c960strt.htm\nI've verified Nibbler produces exactly the same results given a Chess960 number.\n\n000\tBBQNNRKR\tbbqnnrkr\t959\tRKRNNQBB\n001\tBQNBNRKR\tbqnbnrkr\t955\tRKRNBNQB\n002\tBQNNRBKR\tbqnnrbkr\t951\tRKBRNNQB\n003\tBQNNRKRB\tbqnnrkrb\t947\tBRKRNNQB\n004\tQBBNNRKR\tqbbnnrkr\t958\tRKRNNBBQ\n005\tQNBBNRKR\tqnbbnrkr\t954\tRKRNBBNQ\n006\tQNBNRBKR\tqnbnrbkr\t950\tRKBRNBNQ\n007\tQNBNRKRB\tqnbnrkrb\t946\tBRKRNBNQ\n008\tQBNNBRKR\tqbnnbrkr\t957\tRKRBNNBQ\n009\tQNNBBRKR\tqnnbbrkr\t953\tRKRBBNNQ\n010\tQNNRBBKR\tqnnrbbkr\t949\tRKBBRNNQ\n011\tQNNRBKRB\tqnnrbkrb\t945\tBRKBRNNQ\n012\tQBNNRKBR\tqbnnrkbr\t956\tRBKRNNBQ\n013\tQNNBRKBR\tqnnbrkbr\t952\tRBKRBNNQ\n014\tQNNRKBBR\tqnnrkbbr\t948\tRBBKRNNQ\n015\tQNNRKRBB\tqnnrkrbb\t944\tBBRKRNNQ\n016\tBBNQNRKR\tbbnqnrkr\t943\tRKRNQNBB\n017\tBNQBNRKR\tbnqbnrkr\t939\tRKRNBQNB\n018\tBNQNRBKR\tbnqnrbkr\t935\tRKBRNQNB\n019\tBNQNRKRB\tbnqnrkrb\t931\tBRKRNQNB\n020\tNBBQNRKR\tnbbqnrkr\t942\tRKRNQBBN\n021\tNQBBNRKR\tnqbbnrkr\t938\tRKRNBBQN\n022\tNQBNRBKR\tnqbnrbkr\t934\tRKBRNBQN\n023\tNQBNRKRB\tnqbnrkrb\t930\tBRKRNBQN\n024\tNBQNBRKR\tnbqnbrkr\t941\tRKRBNQBN\n025\tNQNBBRKR\tnqnbbrkr\t937\tRKRBBNQN\n026\tNQNRBBKR\tnqnrbbkr\t933\tRKBBRNQN\n027\tNQNRBKRB\tnqnrbkrb\t929\tBRKBRNQN\n028\tNBQNRKBR\tnbqnrkbr\t940\tRBKRNQBN\n029\tNQNBRKBR\tnqnbrkbr\t936\tRBKRBNQN\n030\tNQNRKBBR\tnqnrkbbr\t932\tRBBKRNQN\n031\tNQNRKRBB\tnqnrkrbb\t928\tBBRKRNQN\n032\tBBNNQRKR\tbbnnqrkr\t927\tRKRQNNBB\n033\tBNNBQRKR\tbnnbqrkr\t923\tRKRQBNNB\n034\tBNNQRBKR\tbnnqrbkr\t919\tRKBRQNNB\n035\tBNNQRKRB\tbnnqrkrb\t915\tBRKRQNNB\n036\tNBBNQRKR\tnbbnqrkr\t926\tRKRQNBBN\n037\tNNBBQRKR\tnnbbqrkr\t922\tRKRQBBNN\n038\tNNBQRBKR\tnnbqrbkr\t918\tRKBRQBNN\n039\tNNBQRKRB\tnnbqrkrb\t914\tBRKRQBNN\n040\tNBNQBRKR\tnbnqbrkr\t925\tRKRBQNBN\n041\tNNQBBRKR\tnnqbbrkr\t921\tRKRBBQNN\n042\tNNQRBBKR\tnnqrbbkr\t917\tRKBBRQNN\n043\tNNQRBKRB\tnnqrbkrb\t913\tBRKBRQNN\n044\tNBNQRKBR\tnbnqrkbr\t924\tRBKRQNBN\n045\tNNQBRKBR\tnnqbrkbr\t920\tRBKRBQNN\n046\tNNQRKBBR\tnnqrkbbr\t916\tRBBKRQNN\n047\tNNQRKRBB\tnnqrkrbb\t912\tBBRKRQNN\n048\tBBNNRQKR\tbbnnrqkr\t911\tRKQRNNBB\n049\tBNNBRQKR\tbnnbrqkr\t907\tRKQRBNNB\n050\tBNNRQBKR\tbnnrqbkr\t903\tRKBQRNNB\n051\tBNNRQKRB\tbnnrqkrb\t899\tBRKQRNNB\n052\tNBBNRQKR\tnbbnrqkr\t910\tRKQRNBBN\n053\tNNBBRQKR\tnnbbrqkr\t906\tRKQRBBNN\n054\tNNBRQBKR\tnnbrqbkr\t902\tRKBQRBNN\n055\tNNBRQKRB\tnnbrqkrb\t898\tBRKQRBNN\n056\tNBNRBQKR\tnbnrbqkr\t909\tRKQBRNBN\n057\tNNRBBQKR\tnnrbbqkr\t905\tRKQBBRNN\n058\tNNRQBBKR\tnnrqbbkr\t901\tRKBBQRNN\n059\tNNRQBKRB\tnnrqbkrb\t897\tBRKBQRNN\n060\tNBNRQKBR\tnbnrqkbr\t908\tRBKQRNBN\n061\tNNRBQKBR\tnnrbqkbr\t904\tRBKQBRNN\n062\tNNRQKBBR\tnnrqkbbr\t900\tRBBKQRNN\n063\tNNRQKRBB\tnnrqkrbb\t896\tBBRKQRNN\n064\tBBNNRKQR\tbbnnrkqr\t895\tRQKRNNBB\n065\tBNNBRKQR\tbnnbrkqr\t891\tRQKRBNNB\n066\tBNNRKBQR\tbnnrkbqr\t887\tRQBKRNNB\n067\tBNNRKQRB\tbnnrkqrb\t883\tBRQKRNNB\n068\tNBBNRKQR\tnbbnrkqr\t894\tRQKRNBBN\n069\tNNBBRKQR\tnnbbrkqr\t890\tRQKRBBNN\n070\tNNBRKBQR\tnnbrkbqr\t886\tRQBKRBNN\n071\tNNBRKQRB\tnnbrkqrb\t882\tBRQKRBNN\n072\tNBNRBKQR\tnbnrbkqr\t893\tRQKBRNBN\n073\tNNRBBKQR\tnnrbbkqr\t889\tRQKBBRNN\n074\tNNRKBBQR\tnnrkbbqr\t885\tRQBBKRNN\n075\tNNRKBQRB\tnnrkbqrb\t881\tBRQBKRNN\n076\tNBNRKQBR\tnbnrkqbr\t892\tRBQKRNBN\n077\tNNRBKQBR\tnnrbkqbr\t888\tRBQKBRNN\n078\tNNRKQBBR\tnnrkqbbr\t884\tRBBQKRNN\n079\tNNRKQRBB\tnnrkqrbb\t880\tBBRQKRNN\n080\tBBNNRKRQ\tbbnnrkrq\t879\tQRKRNNBB\n081\tBNNBRKRQ\tbnnbrkrq\t875\tQRKRBNNB\n082\tBNNRKBRQ\tbnnrkbrq\t871\tQRBKRNNB\n083\tBNNRKRQB\tbnnrkrqb\t867\tBQRKRNNB\n084\tNBBNRKRQ\tnbbnrkrq\t878\tQRKRNBBN\n085\tNNBBRKRQ\tnnbbrkrq\t874\tQRKRBBNN\n086\tNNBRKBRQ\tnnbrkbrq\t870\tQRBKRBNN\n087\tNNBRKRQB\tnnbrkrqb\t866\tBQRKRBNN\n088\tNBNRBKRQ\tnbnrbkrq\t877\tQRKBRNBN\n089\tNNRBBKRQ\tnnrbbkrq\t873\tQRKBBRNN\n090\tNNRKBBRQ\tnnrkbbrq\t869\tQRBBKRNN\n091\tNNRKBRQB\tnnrkbrqb\t865\tBQRBKRNN\n092\tNBNRKRBQ\tnbnrkrbq\t876\tQBRKRNBN\n093\tNNRBKRBQ\tnnrbkrbq\t872\tQBRKBRNN\n094\tNNRKRBBQ\tnnrkrbbq\t868\tQBBRKRNN\n095\tNNRKRQBB\tnnrkrqbb\t864\tBBQRKRNN\n096\tBBQNRNKR\tbbqnrnkr\t863\tRKNRNQBB\n097\tBQNBRNKR\tbqnbrnkr\t859\tRKNRBNQB\n098\tBQNRNBKR\tbqnrnbkr\t855\tRKBNRNQB\n099\tBQNRNKRB\tbqnrnkrb\t851\tBRKNRNQB\n100\tQBBNRNKR\tqbbnrnkr\t862\tRKNRNBBQ\n101\tQNBBRNKR\tqnbbrnkr\t858\tRKNRBBNQ\n102\tQNBRNBKR\tqnbrnbkr\t854\tRKBNRBNQ\n103\tQNBRNKRB\tqnbrnkrb\t850\tBRKNRBNQ\n104\tQBNRBNKR\tqbnrbnkr\t861\tRKNBRNBQ\n105\tQNRBBNKR\tqnrbbnkr\t857\tRKNBBRNQ\n106\tQNRNBBKR\tqnrnbbkr\t853\tRKBBNRNQ\n107\tQNRNBKRB\tqnrnbkrb\t849\tBRKBNRNQ\n108\tQBNRNKBR\tqbnrnkbr\t860\tRBKNRNBQ\n109\tQNRBNKBR\tqnrbnkbr\t856\tRBKNBRNQ\n110\tQNRNKBBR\tqnrnkbbr\t852\tRBBKNRNQ\n111\tQNRNKRBB\tqnrnkrbb\t848\tBBRKNRNQ\n112\tBBNQRNKR\tbbnqrnkr\t847\tRKNRQNBB\n113\tBNQBRNKR\tbnqbrnkr\t843\tRKNRBQNB\n114\tBNQRNBKR\tbnqrnbkr\t839\tRKBNRQNB\n115\tBNQRNKRB\tbnqrnkrb\t835\tBRKNRQNB\n116\tNBBQRNKR\tnbbqrnkr\t846\tRKNRQBBN\n117\tNQBBRNKR\tnqbbrnkr\t842\tRKNRBBQN\n118\tNQBRNBKR\tnqbrnbkr\t838\tRKBNRBQN\n119\tNQBRNKRB\tnqbrnkrb\t834\tBRKNRBQN\n120\tNBQRBNKR\tnbqrbnkr\t845\tRKNBRQBN\n121\tNQRBBNKR\tnqrbbnkr\t841\tRKNBBRQN\n122\tNQRNBBKR\tnqrnbbkr\t837\tRKBBNRQN\n123\tNQRNBKRB\tnqrnbkrb\t833\tBRKBNRQN\n124\tNBQRNKBR\tnbqrnkbr\t844\tRBKNRQBN\n125\tNQRBNKBR\tnqrbnkbr\t840\tRBKNBRQN\n126\tNQRNKBBR\tnqrnkbbr\t836\tRBBKNRQN\n127\tNQRNKRBB\tnqrnkrbb\t832\tBBRKNRQN\n128\tBBNRQNKR\tbbnrqnkr\t831\tRKNQRNBB\n129\tBNRBQNKR\tbnrbqnkr\t827\tRKNQBRNB\n130\tBNRQNBKR\tbnrqnbkr\t823\tRKBNQRNB\n131\tBNRQNKRB\tbnrqnkrb\t819\tBRKNQRNB\n132\tNBBRQNKR\tnbbrqnkr\t830\tRKNQRBBN\n133\tNRBBQNKR\tnrbbqnkr\t826\tRKNQBBRN\n134\tNRBQNBKR\tnrbqnbkr\t822\tRKBNQBRN\n135\tNRBQNKRB\tnrbqnkrb\t818\tBRKNQBRN\n136\tNBRQBNKR\tnbrqbnkr\t829\tRKNBQRBN\n137\tNRQBBNKR\tnrqbbnkr\t825\tRKNBBQRN\n138\tNRQNBBKR\tnrqnbbkr\t821\tRKBBNQRN\n139\tNRQNBKRB\tnrqnbkrb\t817\tBRKBNQRN\n140\tNBRQNKBR\tnbrqnkbr\t828\tRBKNQRBN\n141\tNRQBNKBR\tnrqbnkbr\t824\tRBKNBQRN\n142\tNRQNKBBR\tnrqnkbbr\t820\tRBBKNQRN\n143\tNRQNKRBB\tnrqnkrbb\t816\tBBRKNQRN\n144\tBBNRNQKR\tbbnrnqkr\t815\tRKQNRNBB\n145\tBNRBNQKR\tbnrbnqkr\t811\tRKQNBRNB\n146\tBNRNQBKR\tbnrnqbkr\t807\tRKBQNRNB\n147\tBNRNQKRB\tbnrnqkrb\t803\tBRKQNRNB\n148\tNBBRNQKR\tnbbrnqkr\t814\tRKQNRBBN\n149\tNRBBNQKR\tnrbbnqkr\t810\tRKQNBBRN\n150\tNRBNQBKR\tnrbnqbkr\t806\tRKBQNBRN\n151\tNRBNQKRB\tnrbnqkrb\t802\tBRKQNBRN\n152\tNBRNBQKR\tnbrnbqkr\t813\tRKQBNRBN\n153\tNRNBBQKR\tnrnbbqkr\t809\tRKQBBNRN\n154\tNRNQBBKR\tnrnqbbkr\t805\tRKBBQNRN\n155\tNRNQBKRB\tnrnqbkrb\t801\tBRKBQNRN\n156\tNBRNQKBR\tnbrnqkbr\t812\tRBKQNRBN\n157\tNRNBQKBR\tnrnbqkbr\t808\tRBKQBNRN\n158\tNRNQKBBR\tnrnqkbbr\t804\tRBBKQNRN\n159\tNRNQKRBB\tnrnqkrbb\t800\tBBRKQNRN\n160\tBBNRNKQR\tbbnrnkqr\t799\tRQKNRNBB\n161\tBNRBNKQR\tbnrbnkqr\t795\tRQKNBRNB\n162\tBNRNKBQR\tbnrnkbqr\t791\tRQBKNRNB\n163\tBNRNKQRB\tbnrnkqrb\t787\tBRQKNRNB\n164\tNBBRNKQR\tnbbrnkqr\t798\tRQKNRBBN\n165\tNRBBNKQR\tnrbbnkqr\t794\tRQKNBBRN\n166\tNRBNKBQR\tnrbnkbqr\t790\tRQBKNBRN\n167\tNRBNKQRB\tnrbnkqrb\t786\tBRQKNBRN\n168\tNBRNBKQR\tnbrnbkqr\t797\tRQKBNRBN\n169\tNRNBBKQR\tnrnbbkqr\t793\tRQKBBNRN\n170\tNRNKBBQR\tnrnkbbqr\t789\tRQBBKNRN\n171\tNRNKBQRB\tnrnkbqrb\t785\tBRQBKNRN\n172\tNBRNKQBR\tnbrnkqbr\t796\tRBQKNRBN\n173\tNRNBKQBR\tnrnbkqbr\t792\tRBQKBNRN\n174\tNRNKQBBR\tnrnkqbbr\t788\tRBBQKNRN\n175\tNRNKQRBB\tnrnkqrbb\t784\tBBRQKNRN\n176\tBBNRNKRQ\tbbnrnkrq\t783\tQRKNRNBB\n177\tBNRBNKRQ\tbnrbnkrq\t779\tQRKNBRNB\n178\tBNRNKBRQ\tbnrnkbrq\t775\tQRBKNRNB\n179\tBNRNKRQB\tbnrnkrqb\t771\tBQRKNRNB\n180\tNBBRNKRQ\tnbbrnkrq\t782\tQRKNRBBN\n181\tNRBBNKRQ\tnrbbnkrq\t778\tQRKNBBRN\n182\tNRBNKBRQ\tnrbnkbrq\t774\tQRBKNBRN\n183\tNRBNKRQB\tnrbnkrqb\t770\tBQRKNBRN\n184\tNBRNBKRQ\tnbrnbkrq\t781\tQRKBNRBN\n185\tNRNBBKRQ\tnrnbbkrq\t777\tQRKBBNRN\n186\tNRNKBBRQ\tnrnkbbrq\t773\tQRBBKNRN\n187\tNRNKBRQB\tnrnkbrqb\t769\tBQRBKNRN\n188\tNBRNKRBQ\tnbrnkrbq\t780\tQBRKNRBN\n189\tNRNBKRBQ\tnrnbkrbq\t776\tQBRKBNRN\n190\tNRNKRBBQ\tnrnkrbbq\t772\tQBBRKNRN\n191\tNRNKRQBB\tnrnkrqbb\t768\tBBQRKNRN\n192\tBBQNRKNR\tbbqnrknr\t671\tRNKRNQBB\n193\tBQNBRKNR\tbqnbrknr\t667\tRNKRBNQB\n194\tBQNRKBNR\tbqnrkbnr\t663\tRNBKRNQB\n195\tBQNRKNRB\tbqnrknrb\t659\tBRNKRNQB\n196\tQBBNRKNR\tqbbnrknr\t670\tRNKRNBBQ\n197\tQNBBRKNR\tqnbbrknr\t666\tRNKRBBNQ\n198\tQNBRKBNR\tqnbrkbnr\t662\tRNBKRBNQ\n199\tQNBRKNRB\tqnbrknrb\t658\tBRNKRBNQ\n200\tQBNRBKNR\tqbnrbknr\t669\tRNKBRNBQ\n201\tQNRBBKNR\tqnrbbknr\t665\tRNKBBRNQ\n202\tQNRKBBNR\tqnrkbbnr\t661\tRNBBKRNQ\n203\tQNRKBNRB\tqnrkbnrb\t657\tBRNBKRNQ\n204\tQBNRKNBR\tqbnrknbr\t668\tRBNKRNBQ\n205\tQNRBKNBR\tqnrbknbr\t664\tRBNKBRNQ\n206\tQNRKNBBR\tqnrknbbr\t660\tRBBNKRNQ\n207\tQNRKNRBB\tqnrknrbb\t656\tBBRNKRNQ\n208\tBBNQRKNR\tbbnqrknr\t655\tRNKRQNBB\n209\tBNQBRKNR\tbnqbrknr\t651\tRNKRBQNB\n210\tBNQRKBNR\tbnqrkbnr\t647\tRNBKRQNB\n211\tBNQRKNRB\tbnqrknrb\t643\tBRNKRQNB\n212\tNBBQRKNR\tnbbqrknr\t654\tRNKRQBBN\n213\tNQBBRKNR\tnqbbrknr\t650\tRNKRBBQN\n214\tNQBRKBNR\tnqbrkbnr\t646\tRNBKRBQN\n215\tNQBRKNRB\tnqbrknrb\t642\tBRNKRBQN\n216\tNBQRBKNR\tnbqrbknr\t653\tRNKBRQBN\n217\tNQRBBKNR\tnqrbbknr\t649\tRNKBBRQN\n218\tNQRKBBNR\tnqrkbbnr\t645\tRNBBKRQN\n219\tNQRKBNRB\tnqrkbnrb\t641\tBRNBKRQN\n220\tNBQRKNBR\tnbqrknbr\t652\tRBNKRQBN\n221\tNQRBKNBR\tnqrbknbr\t648\tRBNKBRQN\n222\tNQRKNBBR\tnqrknbbr\t644\tRBBNKRQN\n223\tNQRKNRBB\tnqrknrbb\t640\tBBRNKRQN\n224\tBBNRQKNR\tbbnrqknr\t639\tRNKQRNBB\n225\tBNRBQKNR\tbnrbqknr\t635\tRNKQBRNB\n226\tBNRQKBNR\tbnrqkbnr\t631\tRNBKQRNB\n227\tBNRQKNRB\tbnrqknrb\t627\tBRNKQRNB\n228\tNBBRQKNR\tnbbrqknr\t638\tRNKQRBBN\n229\tNRBBQKNR\tnrbbqknr\t634\tRNKQBBRN\n230\tNRBQKBNR\tnrbqkbnr\t630\tRNBKQBRN\n231\tNRBQKNRB\tnrbqknrb\t626\tBRNKQBRN\n232\tNBRQBKNR\tnbrqbknr\t637\tRNKBQRBN\n233\tNRQBBKNR\tnrqbbknr\t633\tRNKBBQRN\n234\tNRQKBBNR\tnrqkbbnr\t629\tRNBBKQRN\n235\tNRQKBNRB\tnrqkbnrb\t625\tBRNBKQRN\n236\tNBRQKNBR\tnbrqknbr\t636\tRBNKQRBN\n237\tNRQBKNBR\tnrqbknbr\t632\tRBNKBQRN\n238\tNRQKNBBR\tnrqknbbr\t628\tRBBNKQRN\n239\tNRQKNRBB\tnrqknrbb\t624\tBBRNKQRN\n240\tBBNRKQNR\tbbnrkqnr\t623\tRNQKRNBB\n241\tBNRBKQNR\tbnrbkqnr\t619\tRNQKBRNB\n242\tBNRKQBNR\tbnrkqbnr\t615\tRNBQKRNB\n243\tBNRKQNRB\tbnrkqnrb\t611\tBRNQKRNB\n244\tNBBRKQNR\tnbbrkqnr\t622\tRNQKRBBN\n245\tNRBBKQNR\tnrbbkqnr\t618\tRNQKBBRN\n246\tNRBKQBNR\tnrbkqbnr\t614\tRNBQKBRN\n247\tNRBKQNRB\tnrbkqnrb\t610\tBRNQKBRN\n248\tNBRKBQNR\tnbrkbqnr\t621\tRNQBKRBN\n249\tNRKBBQNR\tnrkbbqnr\t617\tRNQBBKRN\n250\tNRKQBBNR\tnrkqbbnr\t613\tRNBBQKRN\n251\tNRKQBNRB\tnrkqbnrb\t609\tBRNBQKRN\n252\tNBRKQNBR\tnbrkqnbr\t620\tRBNQKRBN\n253\tNRKBQNBR\tnrkbqnbr\t616\tRBNQBKRN\n254\tNRKQNBBR\tnrkqnbbr\t612\tRBBNQKRN\n255\tNRKQNRBB\tnrkqnrbb\t608\tBBRNQKRN\n256\tBBNRKNQR\tbbnrknqr\t607\tRQNKRNBB\n257\tBNRBKNQR\tbnrbknqr\t603\tRQNKBRNB\n258\tBNRKNBQR\tbnrknbqr\t599\tRQBNKRNB\n259\tBNRKNQRB\tbnrknqrb\t595\tBRQNKRNB\n260\tNBBRKNQR\tnbbrknqr\t606\tRQNKRBBN\n261\tNRBBKNQR\tnrbbknqr\t602\tRQNKBBRN\n262\tNRBKNBQR\tnrbknbqr\t598\tRQBNKBRN\n263\tNRBKNQRB\tnrbknqrb\t594\tBRQNKBRN\n264\tNBRKBNQR\tnbrkbnqr\t605\tRQNBKRBN\n265\tNRKBBNQR\tnrkbbnqr\t601\tRQNBBKRN\n266\tNRKNBBQR\tnrknbbqr\t597\tRQBBNKRN\n267\tNRKNBQRB\tnrknbqrb\t593\tBRQBNKRN\n268\tNBRKNQBR\tnbrknqbr\t604\tRBQNKRBN\n269\tNRKBNQBR\tnrkbnqbr\t600\tRBQNBKRN\n270\tNRKNQBBR\tnrknqbbr\t596\tRBBQNKRN\n271\tNRKNQRBB\tnrknqrbb\t592\tBBRQNKRN\n272\tBBNRKNRQ\tbbnrknrq\t591\tQRNKRNBB\n273\tBNRBKNRQ\tbnrbknrq\t587\tQRNKBRNB\n274\tBNRKNBRQ\tbnrknbrq\t583\tQRBNKRNB\n275\tBNRKNRQB\tbnrknrqb\t579\tBQRNKRNB\n276\tNBBRKNRQ\tnbbrknrq\t590\tQRNKRBBN\n277\tNRBBKNRQ\tnrbbknrq\t586\tQRNKBBRN\n278\tNRBKNBRQ\tnrbknbrq\t582\tQRBNKBRN\n279\tNRBKNRQB\tnrbknrqb\t578\tBQRNKBRN\n280\tNBRKBNRQ\tnbrkbnrq\t589\tQRNBKRBN\n281\tNRKBBNRQ\tnrkbbnrq\t585\tQRNBBKRN\n282\tNRKNBBRQ\tnrknbbrq\t581\tQRBBNKRN\n283\tNRKNBRQB\tnrknbrqb\t577\tBQRBNKRN\n284\tNBRKNRBQ\tnbrknrbq\t588\tQBRNKRBN\n285\tNRKBNRBQ\tnrkbnrbq\t584\tQBRNBKRN\n286\tNRKNRBBQ\tnrknrbbq\t580\tQBBRNKRN\n287\tNRKNRQBB\tnrknrqbb\t576\tBBQRNKRN\n288\tBBQNRKRN\tbbqnrkrn\t383\tNRKRNQBB\n289\tBQNBRKRN\tbqnbrkrn\t379\tNRKRBNQB\n290\tBQNRKBRN\tbqnrkbrn\t375\tNRBKRNQB\n291\tBQNRKRNB\tbqnrkrnb\t371\tBNRKRNQB\n292\tQBBNRKRN\tqbbnrkrn\t382\tNRKRNBBQ\n293\tQNBBRKRN\tqnbbrkrn\t378\tNRKRBBNQ\n294\tQNBRKBRN\tqnbrkbrn\t374\tNRBKRBNQ\n295\tQNBRKRNB\tqnbrkrnb\t370\tBNRKRBNQ\n296\tQBNRBKRN\tqbnrbkrn\t381\tNRKBRNBQ\n297\tQNRBBKRN\tqnrbbkrn\t377\tNRKBBRNQ\n298\tQNRKBBRN\tqnrkbbrn\t373\tNRBBKRNQ\n299\tQNRKBRNB\tqnrkbrnb\t369\tBNRBKRNQ\n300\tQBNRKRBN\tqbnrkrbn\t380\tNBRKRNBQ\n301\tQNRBKRBN\tqnrbkrbn\t376\tNBRKBRNQ\n302\tQNRKRBBN\tqnrkrbbn\t372\tNBBRKRNQ\n303\tQNRKRNBB\tqnrkrnbb\t368\tBBNRKRNQ\n304\tBBNQRKRN\tbbnqrkrn\t367\tNRKRQNBB\n305\tBNQBRKRN\tbnqbrkrn\t363\tNRKRBQNB\n306\tBNQRKBRN\tbnqrkbrn\t359\tNRBKRQNB\n307\tBNQRKRNB\tbnqrkrnb\t355\tBNRKRQNB\n308\tNBBQRKRN\tnbbqrkrn\t366\tNRKRQBBN\n309\tNQBBRKRN\tnqbbrkrn\t362\tNRKRBBQN\n310\tNQBRKBRN\tnqbrkbrn\t358\tNRBKRBQN\n311\tNQBRKRNB\tnqbrkrnb\t354\tBNRKRBQN\n312\tNBQRBKRN\tnbqrbkrn\t365\tNRKBRQBN\n313\tNQRBBKRN\tnqrbbkrn\t361\tNRKBBRQN\n314\tNQRKBBRN\tnqrkbbrn\t357\tNRBBKRQN\n315\tNQRKBRNB\tnqrkbrnb\t353\tBNRBKRQN\n316\tNBQRKRBN\tnbqrkrbn\t364\tNBRKRQBN\n317\tNQRBKRBN\tnqrbkrbn\t360\tNBRKBRQN\n318\tNQRKRBBN\tnqrkrbbn\t356\tNBBRKRQN\n319\tNQRKRNBB\tnqrkrnbb\t352\tBBNRKRQN\n320\tBBNRQKRN\tbbnrqkrn\t351\tNRKQRNBB\n321\tBNRBQKRN\tbnrbqkrn\t347\tNRKQBRNB\n322\tBNRQKBRN\tbnrqkbrn\t343\tNRBKQRNB\n323\tBNRQKRNB\tbnrqkrnb\t339\tBNRKQRNB\n324\tNBBRQKRN\tnbbrqkrn\t350\tNRKQRBBN\n325\tNRBBQKRN\tnrbbqkrn\t346\tNRKQBBRN\n326\tNRBQKBRN\tnrbqkbrn\t342\tNRBKQBRN\n327\tNRBQKRNB\tnrbqkrnb\t338\tBNRKQBRN\n328\tNBRQBKRN\tnbrqbkrn\t349\tNRKBQRBN\n329\tNRQBBKRN\tnrqbbkrn\t345\tNRKBBQRN\n330\tNRQKBBRN\tnrqkbbrn\t341\tNRBBKQRN\n331\tNRQKBRNB\tnrqkbrnb\t337\tBNRBKQRN\n332\tNBRQKRBN\tnbrqkrbn\t348\tNBRKQRBN\n333\tNRQBKRBN\tnrqbkrbn\t344\tNBRKBQRN\n334\tNRQKRBBN\tnrqkrbbn\t340\tNBBRKQRN\n335\tNRQKRNBB\tnrqkrnbb\t336\tBBNRKQRN\n336\tBBNRKQRN\tbbnrkqrn\t335\tNRQKRNBB\n337\tBNRBKQRN\tbnrbkqrn\t331\tNRQKBRNB\n338\tBNRKQBRN\tbnrkqbrn\t327\tNRBQKRNB\n339\tBNRKQRNB\tbnrkqrnb\t323\tBNRQKRNB\n340\tNBBRKQRN\tnbbrkqrn\t334\tNRQKRBBN\n341\tNRBBKQRN\tnrbbkqrn\t330\tNRQKBBRN\n342\tNRBKQBRN\tnrbkqbrn\t326\tNRBQKBRN\n343\tNRBKQRNB\tnrbkqrnb\t322\tBNRQKBRN\n344\tNBRKBQRN\tnbrkbqrn\t333\tNRQBKRBN\n345\tNRKBBQRN\tnrkbbqrn\t329\tNRQBBKRN\n346\tNRKQBBRN\tnrkqbbrn\t325\tNRBBQKRN\n347\tNRKQBRNB\tnrkqbrnb\t321\tBNRBQKRN\n348\tNBRKQRBN\tnbrkqrbn\t332\tNBRQKRBN\n349\tNRKBQRBN\tnrkbqrbn\t328\tNBRQBKRN\n350\tNRKQRBBN\tnrkqrbbn\t324\tNBBRQKRN\n351\tNRKQRNBB\tnrkqrnbb\t320\tBBNRQKRN\n352\tBBNRKRQN\tbbnrkrqn\t319\tNQRKRNBB\n353\tBNRBKRQN\tbnrbkrqn\t315\tNQRKBRNB\n354\tBNRKRBQN\tbnrkrbqn\t311\tNQBRKRNB\n355\tBNRKRQNB\tbnrkrqnb\t307\tBNQRKRNB\n356\tNBBRKRQN\tnbbrkrqn\t318\tNQRKRBBN\n357\tNRBBKRQN\tnrbbkrqn\t314\tNQRKBBRN\n358\tNRBKRBQN\tnrbkrbqn\t310\tNQBRKBRN\n359\tNRBKRQNB\tnrbkrqnb\t306\tBNQRKBRN\n360\tNBRKBRQN\tnbrkbrqn\t317\tNQRBKRBN\n361\tNRKBBRQN\tnrkbbrqn\t313\tNQRBBKRN\n362\tNRKRBBQN\tnrkrbbqn\t309\tNQBBRKRN\n363\tNRKRBQNB\tnrkrbqnb\t305\tBNQBRKRN\n364\tNBRKRQBN\tnbrkrqbn\t316\tNBQRKRBN\n365\tNRKBRQBN\tnrkbrqbn\t312\tNBQRBKRN\n366\tNRKRQBBN\tnrkrqbbn\t308\tNBBQRKRN\n367\tNRKRQNBB\tnrkrqnbb\t304\tBBNQRKRN\n368\tBBNRKRNQ\tbbnrkrnq\t303\tQNRKRNBB\n369\tBNRBKRNQ\tbnrbkrnq\t299\tQNRKBRNB\n370\tBNRKRBNQ\tbnrkrbnq\t295\tQNBRKRNB\n371\tBNRKRNQB\tbnrkrnqb\t291\tBQNRKRNB\n372\tNBBRKRNQ\tnbbrkrnq\t302\tQNRKRBBN\n373\tNRBBKRNQ\tnrbbkrnq\t298\tQNRKBBRN\n374\tNRBKRBNQ\tnrbkrbnq\t294\tQNBRKBRN\n375\tNRBKRNQB\tnrbkrnqb\t290\tBQNRKBRN\n376\tNBRKBRNQ\tnbrkbrnq\t301\tQNRBKRBN\n377\tNRKBBRNQ\tnrkbbrnq\t297\tQNRBBKRN\n378\tNRKRBBNQ\tnrkrbbnq\t293\tQNBBRKRN\n379\tNRKRBNQB\tnrkrbnqb\t289\tBQNBRKRN\n380\tNBRKRNBQ\tnbrkrnbq\t300\tQBNRKRBN\n381\tNRKBRNBQ\tnrkbrnbq\t296\tQBNRBKRN\n382\tNRKRNBBQ\tnrkrnbbq\t292\tQBBNRKRN\n383\tNRKRNQBB\tnrkrnqbb\t288\tBBQNRKRN\n384\tBBQRNNKR\tbbqrnnkr\t767\tRKNNRQBB\n385\tBQRBNNKR\tbqrbnnkr\t763\tRKNNBRQB\n386\tBQRNNBKR\tbqrnnbkr\t759\tRKBNNRQB\n387\tBQRNNKRB\tbqrnnkrb\t755\tBRKNNRQB\n388\tQBBRNNKR\tqbbrnnkr\t766\tRKNNRBBQ\n389\tQRBBNNKR\tqrbbnnkr\t762\tRKNNBBRQ\n390\tQRBNNBKR\tqrbnnbkr\t758\tRKBNNBRQ\n391\tQRBNNKRB\tqrbnnkrb\t754\tBRKNNBRQ\n392\tQBRNBNKR\tqbrnbnkr\t765\tRKNBNRBQ\n393\tQRNBBNKR\tqrnbbnkr\t761\tRKNBBNRQ\n394\tQRNNBBKR\tqrnnbbkr\t757\tRKBBNNRQ\n395\tQRNNBKRB\tqrnnbkrb\t753\tBRKBNNRQ\n396\tQBRNNKBR\tqbrnnkbr\t764\tRBKNNRBQ\n397\tQRNBNKBR\tqrnbnkbr\t760\tRBKNBNRQ\n398\tQRNNKBBR\tqrnnkbbr\t756\tRBBKNNRQ\n399\tQRNNKRBB\tqrnnkrbb\t752\tBBRKNNRQ\n400\tBBRQNNKR\tbbrqnnkr\t751\tRKNNQRBB\n401\tBRQBNNKR\tbrqbnnkr\t747\tRKNNBQRB\n402\tBRQNNBKR\tbrqnnbkr\t743\tRKBNNQRB\n403\tBRQNNKRB\tbrqnnkrb\t739\tBRKNNQRB\n404\tRBBQNNKR\trbbqnnkr\t750\tRKNNQBBR\n405\tRQBBNNKR\trqbbnnkr\t746\tRKNNBBQR\n406\tRQBNNBKR\trqbnnbkr\t742\tRKBNNBQR\n407\tRQBNNKRB\trqbnnkrb\t738\tBRKNNBQR\n408\tRBQNBNKR\trbqnbnkr\t749\tRKNBNQBR\n409\tRQNBBNKR\trqnbbnkr\t745\tRKNBBNQR\n410\tRQNNBBKR\trqnnbbkr\t741\tRKBBNNQR\n411\tRQNNBKRB\trqnnbkrb\t737\tBRKBNNQR\n412\tRBQNNKBR\trbqnnkbr\t748\tRBKNNQBR\n413\tRQNBNKBR\trqnbnkbr\t744\tRBKNBNQR\n414\tRQNNKBBR\trqnnkbbr\t740\tRBBKNNQR\n415\tRQNNKRBB\trqnnkrbb\t736\tBBRKNNQR\n416\tBBRNQNKR\tbbrnqnkr\t735\tRKNQNRBB\n417\tBRNBQNKR\tbrnbqnkr\t731\tRKNQBNRB\n418\tBRNQNBKR\tbrnqnbkr\t727\tRKBNQNRB\n419\tBRNQNKRB\tbrnqnkrb\t723\tBRKNQNRB\n420\tRBBNQNKR\trbbnqnkr\t734\tRKNQNBBR\n421\tRNBBQNKR\trnbbqnkr\t730\tRKNQBBNR\n422\tRNBQNBKR\trnbqnbkr\t726\tRKBNQBNR\n423\tRNBQNKRB\trnbqnkrb\t722\tBRKNQBNR\n424\tRBNQBNKR\trbnqbnkr\t733\tRKNBQNBR\n425\tRNQBBNKR\trnqbbnkr\t729\tRKNBBQNR\n426\tRNQNBBKR\trnqnbbkr\t725\tRKBBNQNR\n427\tRNQNBKRB\trnqnbkrb\t721\tBRKBNQNR\n428\tRBNQNKBR\trbnqnkbr\t732\tRBKNQNBR\n429\tRNQBNKBR\trnqbnkbr\t728\tRBKNBQNR\n430\tRNQNKBBR\trnqnkbbr\t724\tRBBKNQNR\n431\tRNQNKRBB\trnqnkrbb\t720\tBBRKNQNR\n432\tBBRNNQKR\tbbrnnqkr\t719\tRKQNNRBB\n433\tBRNBNQKR\tbrnbnqkr\t715\tRKQNBNRB\n434\tBRNNQBKR\tbrnnqbkr\t711\tRKBQNNRB\n435\tBRNNQKRB\tbrnnqkrb\t707\tBRKQNNRB\n436\tRBBNNQKR\trbbnnqkr\t718\tRKQNNBBR\n437\tRNBBNQKR\trnbbnqkr\t714\tRKQNBBNR\n438\tRNBNQBKR\trnbnqbkr\t710\tRKBQNBNR\n439\tRNBNQKRB\trnbnqkrb\t706\tBRKQNBNR\n440\tRBNNBQKR\trbnnbqkr\t717\tRKQBNNBR\n441\tRNNBBQKR\trnnbbqkr\t713\tRKQBBNNR\n442\tRNNQBBKR\trnnqbbkr\t709\tRKBBQNNR\n443\tRNNQBKRB\trnnqbkrb\t705\tBRKBQNNR\n444\tRBNNQKBR\trbnnqkbr\t716\tRBKQNNBR\n445\tRNNBQKBR\trnnbqkbr\t712\tRBKQBNNR\n446\tRNNQKBBR\trnnqkbbr\t708\tRBBKQNNR\n447\tRNNQKRBB\trnnqkrbb\t704\tBBRKQNNR\n448\tBBRNNKQR\tbbrnnkqr\t703\tRQKNNRBB\n449\tBRNBNKQR\tbrnbnkqr\t699\tRQKNBNRB\n450\tBRNNKBQR\tbrnnkbqr\t695\tRQBKNNRB\n451\tBRNNKQRB\tbrnnkqrb\t691\tBRQKNNRB\n452\tRBBNNKQR\trbbnnkqr\t702\tRQKNNBBR\n453\tRNBBNKQR\trnbbnkqr\t698\tRQKNBBNR\n454\tRNBNKBQR\trnbnkbqr\t694\tRQBKNBNR\n455\tRNBNKQRB\trnbnkqrb\t690\tBRQKNBNR\n456\tRBNNBKQR\trbnnbkqr\t701\tRQKBNNBR\n457\tRNNBBKQR\trnnbbkqr\t697\tRQKBBNNR\n458\tRNNKBBQR\trnnkbbqr\t693\tRQBBKNNR\n459\tRNNKBQRB\trnnkbqrb\t689\tBRQBKNNR\n460\tRBNNKQBR\trbnnkqbr\t700\tRBQKNNBR\n461\tRNNBKQBR\trnnbkqbr\t696\tRBQKBNNR\n462\tRNNKQBBR\trnnkqbbr\t692\tRBBQKNNR\n463\tRNNKQRBB\trnnkqrbb\t688\tBBRQKNNR\n464\tBBRNNKRQ\tbbrnnkrq\t687\tQRKNNRBB\n465\tBRNBNKRQ\tbrnbnkrq\t683\tQRKNBNRB\n466\tBRNNKBRQ\tbrnnkbrq\t679\tQRBKNNRB\n467\tBRNNKRQB\tbrnnkrqb\t675\tBQRKNNRB\n468\tRBBNNKRQ\trbbnnkrq\t686\tQRKNNBBR\n469\tRNBBNKRQ\trnbbnkrq\t682\tQRKNBBNR\n470\tRNBNKBRQ\trnbnkbrq\t678\tQRBKNBNR\n471\tRNBNKRQB\trnbnkrqb\t674\tBQRKNBNR\n472\tRBNNBKRQ\trbnnbkrq\t685\tQRKBNNBR\n473\tRNNBBKRQ\trnnbbkrq\t681\tQRKBBNNR\n474\tRNNKBBRQ\trnnkbbrq\t677\tQRBBKNNR\n475\tRNNKBRQB\trnnkbrqb\t673\tBQRBKNNR\n476\tRBNNKRBQ\trbnnkrbq\t684\tQBRKNNBR\n477\tRNNBKRBQ\trnnbkrbq\t680\tQBRKBNNR\n478\tRNNKRBBQ\trnnkrbbq\t676\tQBBRKNNR\n479\tRNNKRQBB\trnnkrqbb\t672\tBBQRKNNR\n480\tBBQRNKNR\tbbqrnknr\t575\tRNKNRQBB\n481\tBQRBNKNR\tbqrbnknr\t571\tRNKNBRQB\n482\tBQRNKBNR\tbqrnkbnr\t567\tRNBKNRQB\n483\tBQRNKNRB\tbqrnknrb\t563\tBRNKNRQB\n484\tQBBRNKNR\tqbbrnknr\t574\tRNKNRBBQ\n485\tQRBBNKNR\tqrbbnknr\t570\tRNKNBBRQ\n486\tQRBNKBNR\tqrbnkbnr\t566\tRNBKNBRQ\n487\tQRBNKNRB\tqrbnknrb\t562\tBRNKNBRQ\n488\tQBRNBKNR\tqbrnbknr\t573\tRNKBNRBQ\n489\tQRNBBKNR\tqrnbbknr\t569\tRNKBBNRQ\n490\tQRNKBBNR\tqrnkbbnr\t565\tRNBBKNRQ\n491\tQRNKBNRB\tqrnkbnrb\t561\tBRNBKNRQ\n492\tQBRNKNBR\tqbrnknbr\t572\tRBNKNRBQ\n493\tQRNBKNBR\tqrnbknbr\t568\tRBNKBNRQ\n494\tQRNKNBBR\tqrnknbbr\t564\tRBBNKNRQ\n495\tQRNKNRBB\tqrnknrbb\t560\tBBRNKNRQ\n496\tBBRQNKNR\tbbrqnknr\t559\tRNKNQRBB\n497\tBRQBNKNR\tbrqbnknr\t555\tRNKNBQRB\n498\tBRQNKBNR\tbrqnkbnr\t551\tRNBKNQRB\n499\tBRQNKNRB\tbrqnknrb\t547\tBRNKNQRB\n500\tRBBQNKNR\trbbqnknr\t558\tRNKNQBBR\n501\tRQBBNKNR\trqbbnknr\t554\tRNKNBBQR\n502\tRQBNKBNR\trqbnkbnr\t550\tRNBKNBQR\n503\tRQBNKNRB\trqbnknrb\t546\tBRNKNBQR\n504\tRBQNBKNR\trbqnbknr\t557\tRNKBNQBR\n505\tRQNBBKNR\trqnbbknr\t553\tRNKBBNQR\n506\tRQNKBBNR\trqnkbbnr\t549\tRNBBKNQR\n507\tRQNKBNRB\trqnkbnrb\t545\tBRNBKNQR\n508\tRBQNKNBR\trbqnknbr\t556\tRBNKNQBR\n509\tRQNBKNBR\trqnbknbr\t552\tRBNKBNQR\n510\tRQNKNBBR\trqnknbbr\t548\tRBBNKNQR\n511\tRQNKNRBB\trqnknrbb\t544\tBBRNKNQR\n512\tBBRNQKNR\tbbrnqknr\t543\tRNKQNRBB\n513\tBRNBQKNR\tbrnbqknr\t539\tRNKQBNRB\n514\tBRNQKBNR\tbrnqkbnr\t535\tRNBKQNRB\n515\tBRNQKNRB\tbrnqknrb\t531\tBRNKQNRB\n516\tRBBNQKNR\trbbnqknr\t542\tRNKQNBBR\n517\tRNBBQKNR\trnbbqknr\t538\tRNKQBBNR\n518\tRNBQKBNR\trnbqkbnr\t534\tRNBKQBNR\n519\tRNBQKNRB\trnbqknrb\t530\tBRNKQBNR\n520\tRBNQBKNR\trbnqbknr\t541\tRNKBQNBR\n521\tRNQBBKNR\trnqbbknr\t537\tRNKBBQNR\n522\tRNQKBBNR\trnqkbbnr\t533\tRNBBKQNR\n523\tRNQKBNRB\trnqkbnrb\t529\tBRNBKQNR\n524\tRBNQKNBR\trbnqknbr\t540\tRBNKQNBR\n525\tRNQBKNBR\trnqbknbr\t536\tRBNKBQNR\n526\tRNQKNBBR\trnqknbbr\t532\tRBBNKQNR\n527\tRNQKNRBB\trnqknrbb\t528\tBBRNKQNR\n528\tBBRNKQNR\tbbrnkqnr\t527\tRNQKNRBB\n529\tBRNBKQNR\tbrnbkqnr\t523\tRNQKBNRB\n530\tBRNKQBNR\tbrnkqbnr\t519\tRNBQKNRB\n531\tBRNKQNRB\tbrnkqnrb\t515\tBRNQKNRB\n532\tRBBNKQNR\trbbnkqnr\t526\tRNQKNBBR\n533\tRNBBKQNR\trnbbkqnr\t522\tRNQKBBNR\n534\tRNBKQBNR\trnbkqbnr\t518\tRNBQKBNR\n535\tRNBKQNRB\trnbkqnrb\t514\tBRNQKBNR\n536\tRBNKBQNR\trbnkbqnr\t525\tRNQBKNBR\n537\tRNKBBQNR\trnkbbqnr\t521\tRNQBBKNR\n538\tRNKQBBNR\trnkqbbnr\t517\tRNBBQKNR\n539\tRNKQBNRB\trnkqbnrb\t513\tBRNBQKNR\n540\tRBNKQNBR\trbnkqnbr\t524\tRBNQKNBR\n541\tRNKBQNBR\trnkbqnbr\t520\tRBNQBKNR\n542\tRNKQNBBR\trnkqnbbr\t516\tRBBNQKNR\n543\tRNKQNRBB\trnkqnrbb\t512\tBBRNQKNR\n544\tBBRNKNQR\tbbrnknqr\t511\tRQNKNRBB\n545\tBRNBKNQR\tbrnbknqr\t507\tRQNKBNRB\n546\tBRNKNBQR\tbrnknbqr\t503\tRQBNKNRB\n547\tBRNKNQRB\tbrnknqrb\t499\tBRQNKNRB\n548\tRBBNKNQR\trbbnknqr\t510\tRQNKNBBR\n549\tRNBBKNQR\trnbbknqr\t506\tRQNKBBNR\n550\tRNBKNBQR\trnbknbqr\t502\tRQBNKBNR\n551\tRNBKNQRB\trnbknqrb\t498\tBRQNKBNR\n552\tRBNKBNQR\trbnkbnqr\t509\tRQNBKNBR\n553\tRNKBBNQR\trnkbbnqr\t505\tRQNBBKNR\n554\tRNKNBBQR\trnknbbqr\t501\tRQBBNKNR\n555\tRNKNBQRB\trnknbqrb\t497\tBRQBNKNR\n556\tRBNKNQBR\trbnknqbr\t508\tRBQNKNBR\n557\tRNKBNQBR\trnkbnqbr\t504\tRBQNBKNR\n558\tRNKNQBBR\trnknqbbr\t500\tRBBQNKNR\n559\tRNKNQRBB\trnknqrbb\t496\tBBRQNKNR\n560\tBBRNKNRQ\tbbrnknrq\t495\tQRNKNRBB\n561\tBRNBKNRQ\tbrnbknrq\t491\tQRNKBNRB\n562\tBRNKNBRQ\tbrnknbrq\t487\tQRBNKNRB\n563\tBRNKNRQB\tbrnknrqb\t483\tBQRNKNRB\n564\tRBBNKNRQ\trbbnknrq\t494\tQRNKNBBR\n565\tRNBBKNRQ\trnbbknrq\t490\tQRNKBBNR\n566\tRNBKNBRQ\trnbknbrq\t486\tQRBNKBNR\n567\tRNBKNRQB\trnbknrqb\t482\tBQRNKBNR\n568\tRBNKBNRQ\trbnkbnrq\t493\tQRNBKNBR\n569\tRNKBBNRQ\trnkbbnrq\t489\tQRNBBKNR\n570\tRNKNBBRQ\trnknbbrq\t485\tQRBBNKNR\n571\tRNKNBRQB\trnknbrqb\t481\tBQRBNKNR\n572\tRBNKNRBQ\trbnknrbq\t492\tQBRNKNBR\n573\tRNKBNRBQ\trnkbnrbq\t488\tQBRNBKNR\n574\tRNKNRBBQ\trnknrbbq\t484\tQBBRNKNR\n575\tRNKNRQBB\trnknrqbb\t480\tBBQRNKNR\n576\tBBQRNKRN\tbbqrnkrn\t287\tNRKNRQBB\n577\tBQRBNKRN\tbqrbnkrn\t283\tNRKNBRQB\n578\tBQRNKBRN\tbqrnkbrn\t279\tNRBKNRQB\n579\tBQRNKRNB\tbqrnkrnb\t275\tBNRKNRQB\n580\tQBBRNKRN\tqbbrnkrn\t286\tNRKNRBBQ\n581\tQRBBNKRN\tqrbbnkrn\t282\tNRKNBBRQ\n582\tQRBNKBRN\tqrbnkbrn\t278\tNRBKNBRQ\n583\tQRBNKRNB\tqrbnkrnb\t274\tBNRKNBRQ\n584\tQBRNBKRN\tqbrnbkrn\t285\tNRKBNRBQ\n585\tQRNBBKRN\tqrnbbkrn\t281\tNRKBBNRQ\n586\tQRNKBBRN\tqrnkbbrn\t277\tNRBBKNRQ\n587\tQRNKBRNB\tqrnkbrnb\t273\tBNRBKNRQ\n588\tQBRNKRBN\tqbrnkrbn\t284\tNBRKNRBQ\n589\tQRNBKRBN\tqrnbkrbn\t280\tNBRKBNRQ\n590\tQRNKRBBN\tqrnkrbbn\t276\tNBBRKNRQ\n591\tQRNKRNBB\tqrnkrnbb\t272\tBBNRKNRQ\n592\tBBRQNKRN\tbbrqnkrn\t271\tNRKNQRBB\n593\tBRQBNKRN\tbrqbnkrn\t267\tNRKNBQRB\n594\tBRQNKBRN\tbrqnkbrn\t263\tNRBKNQRB\n595\tBRQNKRNB\tbrqnkrnb\t259\tBNRKNQRB\n596\tRBBQNKRN\trbbqnkrn\t270\tNRKNQBBR\n597\tRQBBNKRN\trqbbnkrn\t266\tNRKNBBQR\n598\tRQBNKBRN\trqbnkbrn\t262\tNRBKNBQR\n599\tRQBNKRNB\trqbnkrnb\t258\tBNRKNBQR\n600\tRBQNBKRN\trbqnbkrn\t269\tNRKBNQBR\n601\tRQNBBKRN\trqnbbkrn\t265\tNRKBBNQR\n602\tRQNKBBRN\trqnkbbrn\t261\tNRBBKNQR\n603\tRQNKBRNB\trqnkbrnb\t257\tBNRBKNQR\n604\tRBQNKRBN\trbqnkrbn\t268\tNBRKNQBR\n605\tRQNBKRBN\trqnbkrbn\t264\tNBRKBNQR\n606\tRQNKRBBN\trqnkrbbn\t260\tNBBRKNQR\n607\tRQNKRNBB\trqnkrnbb\t256\tBBNRKNQR\n608\tBBRNQKRN\tbbrnqkrn\t255\tNRKQNRBB\n609\tBRNBQKRN\tbrnbqkrn\t251\tNRKQBNRB\n610\tBRNQKBRN\tbrnqkbrn\t247\tNRBKQNRB\n611\tBRNQKRNB\tbrnqkrnb\t243\tBNRKQNRB\n612\tRBBNQKRN\trbbnqkrn\t254\tNRKQNBBR\n613\tRNBBQKRN\trnbbqkrn\t250\tNRKQBBNR\n614\tRNBQKBRN\trnbqkbrn\t246\tNRBKQBNR\n615\tRNBQKRNB\trnbqkrnb\t242\tBNRKQBNR\n616\tRBNQBKRN\trbnqbkrn\t253\tNRKBQNBR\n617\tRNQBBKRN\trnqbbkrn\t249\tNRKBBQNR\n618\tRNQKBBRN\trnqkbbrn\t245\tNRBBKQNR\n619\tRNQKBRNB\trnqkbrnb\t241\tBNRBKQNR\n620\tRBNQKRBN\trbnqkrbn\t252\tNBRKQNBR\n621\tRNQBKRBN\trnqbkrbn\t248\tNBRKBQNR\n622\tRNQKRBBN\trnqkrbbn\t244\tNBBRKQNR\n623\tRNQKRNBB\trnqkrnbb\t240\tBBNRKQNR\n624\tBBRNKQRN\tbbrnkqrn\t239\tNRQKNRBB\n625\tBRNBKQRN\tbrnbkqrn\t235\tNRQKBNRB\n626\tBRNKQBRN\tbrnkqbrn\t231\tNRBQKNRB\n627\tBRNKQRNB\tbrnkqrnb\t227\tBNRQKNRB\n628\tRBBNKQRN\trbbnkqrn\t238\tNRQKNBBR\n629\tRNBBKQRN\trnbbkqrn\t234\tNRQKBBNR\n630\tRNBKQBRN\trnbkqbrn\t230\tNRBQKBNR\n631\tRNBKQRNB\trnbkqrnb\t226\tBNRQKBNR\n632\tRBNKBQRN\trbnkbqrn\t237\tNRQBKNBR\n633\tRNKBBQRN\trnkbbqrn\t233\tNRQBBKNR\n634\tRNKQBBRN\trnkqbbrn\t229\tNRBBQKNR\n635\tRNKQBRNB\trnkqbrnb\t225\tBNRBQKNR\n636\tRBNKQRBN\trbnkqrbn\t236\tNBRQKNBR\n637\tRNKBQRBN\trnkbqrbn\t232\tNBRQBKNR\n638\tRNKQRBBN\trnkqrbbn\t228\tNBBRQKNR\n639\tRNKQRNBB\trnkqrnbb\t224\tBBNRQKNR\n640\tBBRNKRQN\tbbrnkrqn\t223\tNQRKNRBB\n641\tBRNBKRQN\tbrnbkrqn\t219\tNQRKBNRB\n642\tBRNKRBQN\tbrnkrbqn\t215\tNQBRKNRB\n643\tBRNKRQNB\tbrnkrqnb\t211\tBNQRKNRB\n644\tRBBNKRQN\trbbnkrqn\t222\tNQRKNBBR\n645\tRNBBKRQN\trnbbkrqn\t218\tNQRKBBNR\n646\tRNBKRBQN\trnbkrbqn\t214\tNQBRKBNR\n647\tRNBKRQNB\trnbkrqnb\t210\tBNQRKBNR\n648\tRBNKBRQN\trbnkbrqn\t221\tNQRBKNBR\n649\tRNKBBRQN\trnkbbrqn\t217\tNQRBBKNR\n650\tRNKRBBQN\trnkrbbqn\t213\tNQBBRKNR\n651\tRNKRBQNB\trnkrbqnb\t209\tBNQBRKNR\n652\tRBNKRQBN\trbnkrqbn\t220\tNBQRKNBR\n653\tRNKBRQBN\trnkbrqbn\t216\tNBQRBKNR\n654\tRNKRQBBN\trnkrqbbn\t212\tNBBQRKNR\n655\tRNKRQNBB\trnkrqnbb\t208\tBBNQRKNR\n656\tBBRNKRNQ\tbbrnkrnq\t207\tQNRKNRBB\n657\tBRNBKRNQ\tbrnbkrnq\t203\tQNRKBNRB\n658\tBRNKRBNQ\tbrnkrbnq\t199\tQNBRKNRB\n659\tBRNKRNQB\tbrnkrnqb\t195\tBQNRKNRB\n660\tRBBNKRNQ\trbbnkrnq\t206\tQNRKNBBR\n661\tRNBBKRNQ\trnbbkrnq\t202\tQNRKBBNR\n662\tRNBKRBNQ\trnbkrbnq\t198\tQNBRKBNR\n663\tRNBKRNQB\trnbkrnqb\t194\tBQNRKBNR\n664\tRBNKBRNQ\trbnkbrnq\t205\tQNRBKNBR\n665\tRNKBBRNQ\trnkbbrnq\t201\tQNRBBKNR\n666\tRNKRBBNQ\trnkrbbnq\t197\tQNBBRKNR\n667\tRNKRBNQB\trnkrbnqb\t193\tBQNBRKNR\n668\tRBNKRNBQ\trbnkrnbq\t204\tQBNRKNBR\n669\tRNKBRNBQ\trnkbrnbq\t200\tQBNRBKNR\n670\tRNKRNBBQ\trnkrnbbq\t196\tQBBNRKNR\n671\tRNKRNQBB\trnkrnqbb\t192\tBBQNRKNR\n672\tBBQRKNNR\tbbqrknnr\t479\tRNNKRQBB\n673\tBQRBKNNR\tbqrbknnr\t475\tRNNKBRQB\n674\tBQRKNBNR\tbqrknbnr\t471\tRNBNKRQB\n675\tBQRKNNRB\tbqrknnrb\t467\tBRNNKRQB\n676\tQBBRKNNR\tqbbrknnr\t478\tRNNKRBBQ\n677\tQRBBKNNR\tqrbbknnr\t474\tRNNKBBRQ\n678\tQRBKNBNR\tqrbknbnr\t470\tRNBNKBRQ\n679\tQRBKNNRB\tqrbknnrb\t466\tBRNNKBRQ\n680\tQBRKBNNR\tqbrkbnnr\t477\tRNNBKRBQ\n681\tQRKBBNNR\tqrkbbnnr\t473\tRNNBBKRQ\n682\tQRKNBBNR\tqrknbbnr\t469\tRNBBNKRQ\n683\tQRKNBNRB\tqrknbnrb\t465\tBRNBNKRQ\n684\tQBRKNNBR\tqbrknnbr\t476\tRBNNKRBQ\n685\tQRKBNNBR\tqrkbnnbr\t472\tRBNNBKRQ\n686\tQRKNNBBR\tqrknnbbr\t468\tRBBNNKRQ\n687\tQRKNNRBB\tqrknnrbb\t464\tBBRNNKRQ\n688\tBBRQKNNR\tbbrqknnr\t463\tRNNKQRBB\n689\tBRQBKNNR\tbrqbknnr\t459\tRNNKBQRB\n690\tBRQKNBNR\tbrqknbnr\t455\tRNBNKQRB\n691\tBRQKNNRB\tbrqknnrb\t451\tBRNNKQRB\n692\tRBBQKNNR\trbbqknnr\t462\tRNNKQBBR\n693\tRQBBKNNR\trqbbknnr\t458\tRNNKBBQR\n694\tRQBKNBNR\trqbknbnr\t454\tRNBNKBQR\n695\tRQBKNNRB\trqbknnrb\t450\tBRNNKBQR\n696\tRBQKBNNR\trbqkbnnr\t461\tRNNBKQBR\n697\tRQKBBNNR\trqkbbnnr\t457\tRNNBBKQR\n698\tRQKNBBNR\trqknbbnr\t453\tRNBBNKQR\n699\tRQKNBNRB\trqknbnrb\t449\tBRNBNKQR\n700\tRBQKNNBR\trbqknnbr\t460\tRBNNKQBR\n701\tRQKBNNBR\trqkbnnbr\t456\tRBNNBKQR\n702\tRQKNNBBR\trqknnbbr\t452\tRBBNNKQR\n703\tRQKNNRBB\trqknnrbb\t448\tBBRNNKQR\n704\tBBRKQNNR\tbbrkqnnr\t447\tRNNQKRBB\n705\tBRKBQNNR\tbrkbqnnr\t443\tRNNQBKRB\n706\tBRKQNBNR\tbrkqnbnr\t439\tRNBNQKRB\n707\tBRKQNNRB\tbrkqnnrb\t435\tBRNNQKRB\n708\tRBBKQNNR\trbbkqnnr\t446\tRNNQKBBR\n709\tRKBBQNNR\trkbbqnnr\t442\tRNNQBBKR\n710\tRKBQNBNR\trkbqnbnr\t438\tRNBNQBKR\n711\tRKBQNNRB\trkbqnnrb\t434\tBRNNQBKR\n712\tRBKQBNNR\trbkqbnnr\t445\tRNNBQKBR\n713\tRKQBBNNR\trkqbbnnr\t441\tRNNBBQKR\n714\tRKQNBBNR\trkqnbbnr\t437\tRNBBNQKR\n715\tRKQNBNRB\trkqnbnrb\t433\tBRNBNQKR\n716\tRBKQNNBR\trbkqnnbr\t444\tRBNNQKBR\n717\tRKQBNNBR\trkqbnnbr\t440\tRBNNBQKR\n718\tRKQNNBBR\trkqnnbbr\t436\tRBBNNQKR\n719\tRKQNNRBB\trkqnnrbb\t432\tBBRNNQKR\n720\tBBRKNQNR\tbbrknqnr\t431\tRNQNKRBB\n721\tBRKBNQNR\tbrkbnqnr\t427\tRNQNBKRB\n722\tBRKNQBNR\tbrknqbnr\t423\tRNBQNKRB\n723\tBRKNQNRB\tbrknqnrb\t419\tBRNQNKRB\n724\tRBBKNQNR\trbbknqnr\t430\tRNQNKBBR\n725\tRKBBNQNR\trkbbnqnr\t426\tRNQNBBKR\n726\tRKBNQBNR\trkbnqbnr\t422\tRNBQNBKR\n727\tRKBNQNRB\trkbnqnrb\t418\tBRNQNBKR\n728\tRBKNBQNR\trbknbqnr\t429\tRNQBNKBR\n729\tRKNBBQNR\trknbbqnr\t425\tRNQBBNKR\n730\tRKNQBBNR\trknqbbnr\t421\tRNBBQNKR\n731\tRKNQBNRB\trknqbnrb\t417\tBRNBQNKR\n732\tRBKNQNBR\trbknqnbr\t428\tRBNQNKBR\n733\tRKNBQNBR\trknbqnbr\t424\tRBNQBNKR\n734\tRKNQNBBR\trknqnbbr\t420\tRBBNQNKR\n735\tRKNQNRBB\trknqnrbb\t416\tBBRNQNKR\n736\tBBRKNNQR\tbbrknnqr\t415\tRQNNKRBB\n737\tBRKBNNQR\tbrkbnnqr\t411\tRQNNBKRB\n738\tBRKNNBQR\tbrknnbqr\t407\tRQBNNKRB\n739\tBRKNNQRB\tbrknnqrb\t403\tBRQNNKRB\n740\tRBBKNNQR\trbbknnqr\t414\tRQNNKBBR\n741\tRKBBNNQR\trkbbnnqr\t410\tRQNNBBKR\n742\tRKBNNBQR\trkbnnbqr\t406\tRQBNNBKR\n743\tRKBNNQRB\trkbnnqrb\t402\tBRQNNBKR\n744\tRBKNBNQR\trbknbnqr\t413\tRQNBNKBR\n745\tRKNBBNQR\trknbbnqr\t409\tRQNBBNKR\n746\tRKNNBBQR\trknnbbqr\t405\tRQBBNNKR\n747\tRKNNBQRB\trknnbqrb\t401\tBRQBNNKR\n748\tRBKNNQBR\trbknnqbr\t412\tRBQNNKBR\n749\tRKNBNQBR\trknbnqbr\t408\tRBQNBNKR\n750\tRKNNQBBR\trknnqbbr\t404\tRBBQNNKR\n751\tRKNNQRBB\trknnqrbb\t400\tBBRQNNKR\n752\tBBRKNNRQ\tbbrknnrq\t399\tQRNNKRBB\n753\tBRKBNNRQ\tbrkbnnrq\t395\tQRNNBKRB\n754\tBRKNNBRQ\tbrknnbrq\t391\tQRBNNKRB\n755\tBRKNNRQB\tbrknnrqb\t387\tBQRNNKRB\n756\tRBBKNNRQ\trbbknnrq\t398\tQRNNKBBR\n757\tRKBBNNRQ\trkbbnnrq\t394\tQRNNBBKR\n758\tRKBNNBRQ\trkbnnbrq\t390\tQRBNNBKR\n759\tRKBNNRQB\trkbnnrqb\t386\tBQRNNBKR\n760\tRBKNBNRQ\trbknbnrq\t397\tQRNBNKBR\n761\tRKNBBNRQ\trknbbnrq\t393\tQRNBBNKR\n762\tRKNNBBRQ\trknnbbrq\t389\tQRBBNNKR\n763\tRKNNBRQB\trknnbrqb\t385\tBQRBNNKR\n764\tRBKNNRBQ\trbknnrbq\t396\tQBRNNKBR\n765\tRKNBNRBQ\trknbnrbq\t392\tQBRNBNKR\n766\tRKNNRBBQ\trknnrbbq\t388\tQBBRNNKR\n767\tRKNNRQBB\trknnrqbb\t384\tBBQRNNKR\n768\tBBQRKNRN\tbbqrknrn\t191\tNRNKRQBB\n769\tBQRBKNRN\tbqrbknrn\t187\tNRNKBRQB\n770\tBQRKNBRN\tbqrknbrn\t183\tNRBNKRQB\n771\tBQRKNRNB\tbqrknrnb\t179\tBNRNKRQB\n772\tQBBRKNRN\tqbbrknrn\t190\tNRNKRBBQ\n773\tQRBBKNRN\tqrbbknrn\t186\tNRNKBBRQ\n774\tQRBKNBRN\tqrbknbrn\t182\tNRBNKBRQ\n775\tQRBKNRNB\tqrbknrnb\t178\tBNRNKBRQ\n776\tQBRKBNRN\tqbrkbnrn\t189\tNRNBKRBQ\n777\tQRKBBNRN\tqrkbbnrn\t185\tNRNBBKRQ\n778\tQRKNBBRN\tqrknbbrn\t181\tNRBBNKRQ\n779\tQRKNBRNB\tqrknbrnb\t177\tBNRBNKRQ\n780\tQBRKNRBN\tqbrknrbn\t188\tNBRNKRBQ\n781\tQRKBNRBN\tqrkbnrbn\t184\tNBRNBKRQ\n782\tQRKNRBBN\tqrknrbbn\t180\tNBBRNKRQ\n783\tQRKNRNBB\tqrknrnbb\t176\tBBNRNKRQ\n784\tBBRQKNRN\tbbrqknrn\t175\tNRNKQRBB\n785\tBRQBKNRN\tbrqbknrn\t171\tNRNKBQRB\n786\tBRQKNBRN\tbrqknbrn\t167\tNRBNKQRB\n787\tBRQKNRNB\tbrqknrnb\t163\tBNRNKQRB\n788\tRBBQKNRN\trbbqknrn\t174\tNRNKQBBR\n789\tRQBBKNRN\trqbbknrn\t170\tNRNKBBQR\n790\tRQBKNBRN\trqbknbrn\t166\tNRBNKBQR\n791\tRQBKNRNB\trqbknrnb\t162\tBNRNKBQR\n792\tRBQKBNRN\trbqkbnrn\t173\tNRNBKQBR\n793\tRQKBBNRN\trqkbbnrn\t169\tNRNBBKQR\n794\tRQKNBBRN\trqknbbrn\t165\tNRBBNKQR\n795\tRQKNBRNB\trqknbrnb\t161\tBNRBNKQR\n796\tRBQKNRBN\trbqknrbn\t172\tNBRNKQBR\n797\tRQKBNRBN\trqkbnrbn\t168\tNBRNBKQR\n798\tRQKNRBBN\trqknrbbn\t164\tNBBRNKQR\n799\tRQKNRNBB\trqknrnbb\t160\tBBNRNKQR\n800\tBBRKQNRN\tbbrkqnrn\t159\tNRNQKRBB\n801\tBRKBQNRN\tbrkbqnrn\t155\tNRNQBKRB\n802\tBRKQNBRN\tbrkqnbrn\t151\tNRBNQKRB\n803\tBRKQNRNB\tbrkqnrnb\t147\tBNRNQKRB\n804\tRBBKQNRN\trbbkqnrn\t158\tNRNQKBBR\n805\tRKBBQNRN\trkbbqnrn\t154\tNRNQBBKR\n806\tRKBQNBRN\trkbqnbrn\t150\tNRBNQBKR\n807\tRKBQNRNB\trkbqnrnb\t146\tBNRNQBKR\n808\tRBKQBNRN\trbkqbnrn\t157\tNRNBQKBR\n809\tRKQBBNRN\trkqbbnrn\t153\tNRNBBQKR\n810\tRKQNBBRN\trkqnbbrn\t149\tNRBBNQKR\n811\tRKQNBRNB\trkqnbrnb\t145\tBNRBNQKR\n812\tRBKQNRBN\trbkqnrbn\t156\tNBRNQKBR\n813\tRKQBNRBN\trkqbnrbn\t152\tNBRNBQKR\n814\tRKQNRBBN\trkqnrbbn\t148\tNBBRNQKR\n815\tRKQNRNBB\trkqnrnbb\t144\tBBNRNQKR\n816\tBBRKNQRN\tbbrknqrn\t143\tNRQNKRBB\n817\tBRKBNQRN\tbrkbnqrn\t139\tNRQNBKRB\n818\tBRKNQBRN\tbrknqbrn\t135\tNRBQNKRB\n819\tBRKNQRNB\tbrknqrnb\t131\tBNRQNKRB\n820\tRBBKNQRN\trbbknqrn\t142\tNRQNKBBR\n821\tRKBBNQRN\trkbbnqrn\t138\tNRQNBBKR\n822\tRKBNQBRN\trkbnqbrn\t134\tNRBQNBKR\n823\tRKBNQRNB\trkbnqrnb\t130\tBNRQNBKR\n824\tRBKNBQRN\trbknbqrn\t141\tNRQBNKBR\n825\tRKNBBQRN\trknbbqrn\t137\tNRQBBNKR\n826\tRKNQBBRN\trknqbbrn\t133\tNRBBQNKR\n827\tRKNQBRNB\trknqbrnb\t129\tBNRBQNKR\n828\tRBKNQRBN\trbknqrbn\t140\tNBRQNKBR\n829\tRKNBQRBN\trknbqrbn\t136\tNBRQBNKR\n830\tRKNQRBBN\trknqrbbn\t132\tNBBRQNKR\n831\tRKNQRNBB\trknqrnbb\t128\tBBNRQNKR\n832\tBBRKNRQN\tbbrknrqn\t127\tNQRNKRBB\n833\tBRKBNRQN\tbrkbnrqn\t123\tNQRNBKRB\n834\tBRKNRBQN\tbrknrbqn\t119\tNQBRNKRB\n835\tBRKNRQNB\tbrknrqnb\t115\tBNQRNKRB\n836\tRBBKNRQN\trbbknrqn\t126\tNQRNKBBR\n837\tRKBBNRQN\trkbbnrqn\t122\tNQRNBBKR\n838\tRKBNRBQN\trkbnrbqn\t118\tNQBRNBKR\n839\tRKBNRQNB\trkbnrqnb\t114\tBNQRNBKR\n840\tRBKNBRQN\trbknbrqn\t125\tNQRBNKBR\n841\tRKNBBRQN\trknbbrqn\t121\tNQRBBNKR\n842\tRKNRBBQN\trknrbbqn\t117\tNQBBRNKR\n843\tRKNRBQNB\trknrbqnb\t113\tBNQBRNKR\n844\tRBKNRQBN\trbknrqbn\t124\tNBQRNKBR\n845\tRKNBRQBN\trknbrqbn\t120\tNBQRBNKR\n846\tRKNRQBBN\trknrqbbn\t116\tNBBQRNKR\n847\tRKNRQNBB\trknrqnbb\t112\tBBNQRNKR\n848\tBBRKNRNQ\tbbrknrnq\t111\tQNRNKRBB\n849\tBRKBNRNQ\tbrkbnrnq\t107\tQNRNBKRB\n850\tBRKNRBNQ\tbrknrbnq\t103\tQNBRNKRB\n851\tBRKNRNQB\tbrknrnqb\t099\tBQNRNKRB\n852\tRBBKNRNQ\trbbknrnq\t110\tQNRNKBBR\n853\tRKBBNRNQ\trkbbnrnq\t106\tQNRNBBKR\n854\tRKBNRBNQ\trkbnrbnq\t102\tQNBRNBKR\n855\tRKBNRNQB\trkbnrnqb\t098\tBQNRNBKR\n856\tRBKNBRNQ\trbknbrnq\t109\tQNRBNKBR\n857\tRKNBBRNQ\trknbbrnq\t105\tQNRBBNKR\n858\tRKNRBBNQ\trknrbbnq\t101\tQNBBRNKR\n859\tRKNRBNQB\trknrbnqb\t097\tBQNBRNKR\n860\tRBKNRNBQ\trbknrnbq\t108\tQBNRNKBR\n861\tRKNBRNBQ\trknbrnbq\t104\tQBNRBNKR\n862\tRKNRNBBQ\trknrnbbq\t100\tQBBNRNKR\n863\tRKNRNQBB\trknrnqbb\t096\tBBQNRNKR\n864\tBBQRKRNN\tbbqrkrnn\t095\tNNRKRQBB\n865\tBQRBKRNN\tbqrbkrnn\t091\tNNRKBRQB\n866\tBQRKRBNN\tbqrkrbnn\t087\tNNBRKRQB\n867\tBQRKRNNB\tbqrkrnnb\t083\tBNNRKRQB\n868\tQBBRKRNN\tqbbrkrnn\t094\tNNRKRBBQ\n869\tQRBBKRNN\tqrbbkrnn\t090\tNNRKBBRQ\n870\tQRBKRBNN\tqrbkrbnn\t086\tNNBRKBRQ\n871\tQRBKRNNB\tqrbkrnnb\t082\tBNNRKBRQ\n872\tQBRKBRNN\tqbrkbrnn\t093\tNNRBKRBQ\n873\tQRKBBRNN\tqrkbbrnn\t089\tNNRBBKRQ\n874\tQRKRBBNN\tqrkrbbnn\t085\tNNBBRKRQ\n875\tQRKRBNNB\tqrkrbnnb\t081\tBNNBRKRQ\n876\tQBRKRNBN\tqbrkrnbn\t092\tNBNRKRBQ\n877\tQRKBRNBN\tqrkbrnbn\t088\tNBNRBKRQ\n878\tQRKRNBBN\tqrkrnbbn\t084\tNBBNRKRQ\n879\tQRKRNNBB\tqrkrnnbb\t080\tBBNNRKRQ\n880\tBBRQKRNN\tbbrqkrnn\t079\tNNRKQRBB\n881\tBRQBKRNN\tbrqbkrnn\t075\tNNRKBQRB\n882\tBRQKRBNN\tbrqkrbnn\t071\tNNBRKQRB\n883\tBRQKRNNB\tbrqkrnnb\t067\tBNNRKQRB\n884\tRBBQKRNN\trbbqkrnn\t078\tNNRKQBBR\n885\tRQBBKRNN\trqbbkrnn\t074\tNNRKBBQR\n886\tRQBKRBNN\trqbkrbnn\t070\tNNBRKBQR\n887\tRQBKRNNB\trqbkrnnb\t066\tBNNRKBQR\n888\tRBQKBRNN\trbqkbrnn\t077\tNNRBKQBR\n889\tRQKBBRNN\trqkbbrnn\t073\tNNRBBKQR\n890\tRQKRBBNN\trqkrbbnn\t069\tNNBBRKQR\n891\tRQKRBNNB\trqkrbnnb\t065\tBNNBRKQR\n892\tRBQKRNBN\trbqkrnbn\t076\tNBNRKQBR\n893\tRQKBRNBN\trqkbrnbn\t072\tNBNRBKQR\n894\tRQKRNBBN\trqkrnbbn\t068\tNBBNRKQR\n895\tRQKRNNBB\trqkrnnbb\t064\tBBNNRKQR\n896\tBBRKQRNN\tbbrkqrnn\t063\tNNRQKRBB\n897\tBRKBQRNN\tbrkbqrnn\t059\tNNRQBKRB\n898\tBRKQRBNN\tbrkqrbnn\t055\tNNBRQKRB\n899\tBRKQRNNB\tbrkqrnnb\t051\tBNNRQKRB\n900\tRBBKQRNN\trbbkqrnn\t062\tNNRQKBBR\n901\tRKBBQRNN\trkbbqrnn\t058\tNNRQBBKR\n902\tRKBQRBNN\trkbqrbnn\t054\tNNBRQBKR\n903\tRKBQRNNB\trkbqrnnb\t050\tBNNRQBKR\n904\tRBKQBRNN\trbkqbrnn\t061\tNNRBQKBR\n905\tRKQBBRNN\trkqbbrnn\t057\tNNRBBQKR\n906\tRKQRBBNN\trkqrbbnn\t053\tNNBBRQKR\n907\tRKQRBNNB\trkqrbnnb\t049\tBNNBRQKR\n908\tRBKQRNBN\trbkqrnbn\t060\tNBNRQKBR\n909\tRKQBRNBN\trkqbrnbn\t056\tNBNRBQKR\n910\tRKQRNBBN\trkqrnbbn\t052\tNBBNRQKR\n911\tRKQRNNBB\trkqrnnbb\t048\tBBNNRQKR\n912\tBBRKRQNN\tbbrkrqnn\t047\tNNQRKRBB\n913\tBRKBRQNN\tbrkbrqnn\t043\tNNQRBKRB\n914\tBRKRQBNN\tbrkrqbnn\t039\tNNBQRKRB\n915\tBRKRQNNB\tbrkrqnnb\t035\tBNNQRKRB\n916\tRBBKRQNN\trbbkrqnn\t046\tNNQRKBBR\n917\tRKBBRQNN\trkbbrqnn\t042\tNNQRBBKR\n918\tRKBRQBNN\trkbrqbnn\t038\tNNBQRBKR\n919\tRKBRQNNB\trkbrqnnb\t034\tBNNQRBKR\n920\tRBKRBQNN\trbkrbqnn\t045\tNNQBRKBR\n921\tRKRBBQNN\trkrbbqnn\t041\tNNQBBRKR\n922\tRKRQBBNN\trkrqbbnn\t037\tNNBBQRKR\n923\tRKRQBNNB\trkrqbnnb\t033\tBNNBQRKR\n924\tRBKRQNBN\trbkrqnbn\t044\tNBNQRKBR\n925\tRKRBQNBN\trkrbqnbn\t040\tNBNQBRKR\n926\tRKRQNBBN\trkrqnbbn\t036\tNBBNQRKR\n927\tRKRQNNBB\trkrqnnbb\t032\tBBNNQRKR\n928\tBBRKRNQN\tbbrkrnqn\t031\tNQNRKRBB\n929\tBRKBRNQN\tbrkbrnqn\t027\tNQNRBKRB\n930\tBRKRNBQN\tbrkrnbqn\t023\tNQBNRKRB\n931\tBRKRNQNB\tbrkrnqnb\t019\tBNQNRKRB\n932\tRBBKRNQN\trbbkrnqn\t030\tNQNRKBBR\n933\tRKBBRNQN\trkbbrnqn\t026\tNQNRBBKR\n934\tRKBRNBQN\trkbrnbqn\t022\tNQBNRBKR\n935\tRKBRNQNB\trkbrnqnb\t018\tBNQNRBKR\n936\tRBKRBNQN\trbkrbnqn\t029\tNQNBRKBR\n937\tRKRBBNQN\trkrbbnqn\t025\tNQNBBRKR\n938\tRKRNBBQN\trkrnbbqn\t021\tNQBBNRKR\n939\tRKRNBQNB\trkrnbqnb\t017\tBNQBNRKR\n940\tRBKRNQBN\trbkrnqbn\t028\tNBQNRKBR\n941\tRKRBNQBN\trkrbnqbn\t024\tNBQNBRKR\n942\tRKRNQBBN\trkrnqbbn\t020\tNBBQNRKR\n943\tRKRNQNBB\trkrnqnbb\t016\tBBNQNRKR\n944\tBBRKRNNQ\tbbrkrnnq\t015\tQNNRKRBB\n945\tBRKBRNNQ\tbrkbrnnq\t011\tQNNRBKRB\n946\tBRKRNBNQ\tbrkrnbnq\t007\tQNBNRKRB\n947\tBRKRNNQB\tbrkrnnqb\t003\tBQNNRKRB\n948\tRBBKRNNQ\trbbkrnnq\t014\tQNNRKBBR\n949\tRKBBRNNQ\trkbbrnnq\t010\tQNNRBBKR\n950\tRKBRNBNQ\trkbrnbnq\t006\tQNBNRBKR\n951\tRKBRNNQB\trkbrnnqb\t002\tBQNNRBKR\n952\tRBKRBNNQ\trbkrbnnq\t013\tQNNBRKBR\n953\tRKRBBNNQ\trkrbbnnq\t009\tQNNBBRKR\n954\tRKRNBBNQ\trkrnbbnq\t005\tQNBBNRKR\n955\tRKRNBNQB\trkrnbnqb\t001\tBQNBNRKR\n956\tRBKRNNBQ\trbkrnnbq\t012\tQBNNRKBR\n957\tRKRBNNBQ\trkrbnnbq\t008\tQBNNBRKR\n958\tRKRNNBBQ\trkrnnbbq\t004\tQBBNNRKR\n959\tRKRNNQBB\trkrnnqbb\t000\tBBQNNRKR\n"
  },
  {
    "path": "files/misc/escape_and_missing_quotes.pgn",
    "content": "[Event \"Test \\\"quotes\\\" \\\\ and escaping and missing quotes in the FEN tag\"]\n[Site \"local\"]\n[Date \"2021.04.01\"]\n[Round \"1\"]\n[White \"Jimmy \\\"The Pawn\\\" Smith\"]\n[Black \"Lc0 \\\\ Stockfish\"]\n[Result \"*\"]\n[FEN rnbqkb1r/ppp1pppp/5n2/3p4/3P1B2/5N2/PPP1PPPP/RN1QKB1R b KQkq - 3 3]\n[SetUp \"1\"]\n\n3... c5 {EV: 47.0%, N: 63.14% of 63.4k} 4. e3 {EV: 53.3%, N: 95.14% of 66.9k} e6\n{EV: 47.4%, N: 67.90% of 115k} 5. Nbd2 {EV: 53.1%, N: 67.58% of 96.2k} Qb6 {EV:\n47.8%, N: 77.32% of 198k} 6. Rb1 {EV: 52.7%, N: 90.85% of 166k} *\n"
  },
  {
    "path": "files/misc/nibbler_menu_translations_template.json",
    "content": "{\n\t\"LANGUAGE_NAME_FIXME\": {\t\t\t\t\t\t// The language name itself, in its own language.\n\t\t\"File\": \"TODO\",\t\t\t\t\t\t\t\t\t// As in: \"file menu\"\n\t\t\t\"About\": \"TODO\",\n\t\t\t\"New game\": \"TODO\",\n\t\t\t\"New 960 game\": \"TODO\",\t\t\t\t\t\t\t// New game of Chess960\n\t\t\t\"Open PGN...\": \"TODO\",\t\t\t\t\t\t\t// Portable game notation file\n\t\t\t\"Load FEN / PGN from clipboard\": \"TODO\",\t\t// Examines clipboard for valid FEN or PGN data\n\t\t\t\"Save this game...\": \"TODO\",\t\t\t\t\t// Saves as PGN\n\t\t\t\"Write PGN to clipboard\": \"TODO\",\n\t\t\t\"PGN saved statistics\": \"TODO\",\t\t\t\t\t// Has submenu of which statistics to save to PGN variations...\n\t\t\t\t\"EV\": \"TODO\",\t\t\t\t\t\t\t\t\t// ... expected value\n\t\t\t\t\"Centipawns\": \"TODO\",\t\t\t\t\t\t\t// ...\n\t\t\t\t\"N (%)\": \"TODO\",\t\t\t\t\t\t\t\t// ... N (node count, percent of total)\n\t\t\t\t\"N (absolute)\": \"TODO\",\t\t\t\t\t\t\t// ... N (absolute count)\n\t\t\t\t\"...out of total\": \"TODO\",\t\t\t\t\t\t// ... display total N\n\t\t\t\t\"Depth (A/B only)\": \"TODO\",\t\t\t\t\t\t// ... search depth, only displayed if alphabeta-style engine\n\t\t\t\"Cut\": \"TODO\",\n\t\t\t\"Copy\": \"TODO\",\n\t\t\t\"Paste\": \"TODO\",\n\t\t\t\"Quit\": \"TODO\",\n\t\t\"Tree\": \"TODO\",\t\t\t\t\t\t\t\t\t// Game tree manipulation functions\n\t\t\t\"Play engine choice\": \"TODO\",\t\t\t\t\t// Adds engine's choice of move to game tree / goes to it if already present\n\t\t\t\t\"1st\": \"TODO\",\t\t\t\t\t\t\t\t\t// ... i.e. play engine's top choice\n\t\t\t\t\"2nd\": \"TODO\",\t\t\t\t\t\t\t\t\t// ... i.e. play engine's 2nd choice\n\t\t\t\t\"3rd\": \"TODO\",\t\t\t\t\t\t\t\t\t// ... i.e. play engine's 3rd choice\n\t\t\t\t\"4th\": \"TODO\",\t\t\t\t\t\t\t\t\t// ... i.e. play engine's 3rd choice\n\t\t\t\"Root\": \"TODO\",\t\t\t\t\t\t\t\t\t// Move to root node in game tree\n\t\t\t\"End\": \"TODO\",\t\t\t\t\t\t\t\t\t// Move to final node in (this line of) game tree\n\t\t\t\"Backward\": \"TODO\",\t\t\t\t\t\t\t\t// Backward one move in game tree\n\t\t\t\"Forward\": \"TODO\",\t\t\t\t\t\t\t\t// Forward one move in game tree\n\t\t\t\"Previous sibling\": \"TODO\",\t\t\t\t\t\t// Go to previous sibling in game tree (if extant)\n\t\t\t\"Next sibling\": \"TODO\",\t\t\t\t\t\t\t// Go to next sibling in game tree (if extant)\n\t\t\t\"Return to main line\": \"TODO\",\n\t\t\t\"Promote line to main line\": \"TODO\",\n\t\t\t\"Promote line by 1 level\": \"TODO\",\n\t\t\t\"Delete node\": \"TODO\",\n\t\t\t\"Delete children\": \"TODO\",\t\t\t\t\t\t// i.e. child nodes in game tree\n\t\t\t\"Delete siblings\": \"TODO\",\n\t\t\t\"Delete ALL other lines\": \"TODO\",\n\t\t\t\"Show PGN games list\": \"TODO\",\t\t\t\t\t// For when a PGN file has multiple games\n\t\t\t\"Escape\": \"TODO\",\t\t\t\t\t\t\t\t// Escape key function - \"escape\" out of this screen to main screen\n\t\t\"Analysis\": \"TODO\",\n\t\t\t\"Go\": \"TODO\",\t\t\t\t\t\t\t\t\t// Starts the engine running\n\t\t\t\"Go and lock engine\": \"TODO\",\t\t\t\t\t// Starts the engine running on current position, and does NOT change the engine position even if GUI position changes\n\t\t\t\"Return to locked position\": \"TODO\",\t\t\t// Returns to the position the engine is analysing, IF the \"go and lock engine\" item was used\n\t\t\t\"Halt\": \"TODO\",\t\t\t\t\t\t\t\t\t// Stops the engine\n\t\t\t\"Auto-evaluate line\": \"TODO\",\t\t\t\t\t// Makes engine analyse a whole line of the game tree\n\t\t\t\"Auto-evaluate line, backwards\": \"TODO\",\t\t// Makes engine analyse a whole line of the game tree, backwards\n\t\t\t\"Show focus (searchmoves) buttons\": \"TODO\",\t\t// Shows buttons next to each legal move to allow the engine to focus analysis on selected moves\n\t\t\t\"Clear focus\": \"TODO\",\t\t\t\t\t\t\t// Deselects all such buttons\n\t\t\t\"Invert focus\": \"TODO\",\t\t\t\t\t\t\t// Inverts all such buttons\n\t\t\t\"Winrate POV\": \"TODO\",\t\t\t\t\t\t\t// Winrate (really EV) displayed from whose point of view?\n\t\t\t\t\"Current\": \"TODO\",\t\t\t\t\t\t\t\t// ... displayed from current player's\n\t\t\t\t\"White\": \"TODO\",\t\t\t\t\t\t\t\t// ... displayed from White's point of view\n\t\t\t\t\"Black\": \"TODO\",\t\t\t\t\t\t\t\t// ... displayed from Black's point of view\n\t\t\t\"Centipawn POV\": \"TODO\",\t\t\t\t\t\t// Centipawn scores displayed from whose point of view? (submenu not in this list)\n\t\t\t\"Win / draw / loss POV\": \"TODO\",\t\t\t\t// WDL statistic displayed from whose point of view? (submenu not in this list)\n\t\t\t\"PV clicks\": \"TODO\",\t\t\t\t\t\t\t// What to do when user clicks on a displayed principal variation (PV)\n\t\t\t\t\"Do nothing\": \"TODO\",\t\t\t\t\t\t\t// ... nothing\n\t\t\t\t\"Go there\": \"TODO\",\t\t\t\t\t\t\t\t// ... move to that position\n\t\t\t\t\"Add to tree\": \"TODO\",\t\t\t\t\t\t\t// ... add PV to game tree\n\t\t\t\"Write infobox to clipboard\": \"TODO\",\n\t\t\t\"Forget all analysis\": \"TODO\",\n\t\t\"Display\": \"TODO\",\t\t\t\t\t\t\t\t// Has large submenu of display options\n\t\t\t\"Flip board\": \"TODO\",\n\t\t\t\"Arrows\": \"TODO\",\n\t\t\t\"Piece-click spotlight\": \"TODO\",\t\t\t\t// Displays arrows showing legal moves, when a piece is clicked\n\t\t\t\"Always show actual move (if known)\": \"TODO\",\t// Displays arrow representing move actually played in the game record (if known)\n\t\t\t\"...with unique colour\": \"TODO\",\t\t\t\t// ... displays that arrow with a unique colour\n\t\t\t\"...with outline\": \"TODO\",\t\t\t\t\t\t// ... and an outline\n\t\t\t\"Arrowhead type\": \"TODO\",\t\t\t\t\t\t// Arrows display what statistic?\n\t\t\t\t\"Winrate\": \"TODO\",\t\t\t\t\t\t\t\t// ... (really expected value)\n\t\t\t\t\"Node %\": \"TODO\",\t\t\t\t\t\t\t\t// ... What % of examined nodes (examined by the engine) were this move?\n\t\t\t\t\"Policy\": \"TODO\",\t\t\t\t\t\t\t\t// Policy / prior\n\t\t\t\t\"MultiPV rank\": \"TODO\",\t\t\t\t\t\t\t// Ordinal rank in engine's preferred moves\n\t\t\t\t\"Moves Left Head\": \"TODO\",\t\t\t\t\t\t// Output of engine neural network head estimating \"moves left before game ends\"\n\t\t\t\"Arrow filter (Lc0)\": \"TODO\",\t\t\t\t\t// Filter criteria for displaying / not displaying arrows (when using Lc0 engine)\n\t\t\t\t\"All moves\": \"TODO\",\t\t\t\t\t\t\t// ... always display all arrows\n\t\t\t\t\"Top move\": \"TODO\",\t\t\t\t\t\t\t\t// ... display arrow for top move only\n\t\t\t\"Arrow filter (others)\": \"TODO\",\t\t\t\t// Filter criteria for displaying / not displaying arrows (when using Stockfish or similar)\n\t\t\t\t\"Diff < 15%\": \"TODO\",\t\t\t\t\t\t\t// ... EV was within 15% of best move\n\t\t\t\t\"Diff < 10%\": \"TODO\",\t\t\t\t\t\t\t// ... EV was within 10% of best move\n\t\t\t\t\"Diff < 5%\": \"TODO\",\t\t\t\t\t\t\t// ... EV was within 5% of best move\n\t\t\t\"Infobox stats\": \"TODO\",\t\t\t\t\t\t// Stats to display in info pane\n\t\t\t\t\"N - nodes (%)\": \"TODO\",\t\t\t\t\t\t// ...\n\t\t\t\t\"N - nodes (absolute)\": \"TODO\",\t\t\t\t\t// ...\n\t\t\t\t\"P - policy\": \"TODO\",\t\t\t\t\t\t\t// ...\n\t\t\t\t\"V - static evaluation\": \"TODO\",\t\t\t\t// ...\n\t\t\t\t\"Q - evaluation\": \"TODO\",\t\t\t\t\t\t// ...\n\t\t\t\t\"U - uncertainty\": \"TODO\",\t\t\t\t\t\t// ...\n\t\t\t\t\"S - search priority\": \"TODO\",\t\t\t\t\t// ...\n\t\t\t\t\"M - moves left\": \"TODO\",\t\t\t\t\t\t// ...\n\t\t\t\t\"WDL - win / draw / loss\": \"TODO\",\t\t\t\t// ...\n\t\t\t\t\"Linebreak before stats\": \"TODO\",\n\t\t\t\"PV move numbers\": \"TODO\",\t\t\t\t\t\t// Display move numbers when writing principal variation e.g. 40. Kc2 Qf1 41. Rd8 etc etc\n\t\t\t\"Online API\": \"TODO\",\t\t\t\t\t\t\t// Access an online API to display real-world game outcome stats\n\t\t\t\t\"None\": \"TODO\",\t\t\t\t\t\t\t\t\t// ... Don't access API\n\t\t\t\t\"ChessDB.cn evals\": \"TODO\",\t\t\t\t\t\t// ... Use ChessDB.cn\n\t\t\t\t\"Lichess results (masters)\": \"TODO\",\t\t\t// ... Use results from Lichess master player database\n\t\t\t\t\"Lichess results (plebs)\": \"TODO\",\t\t\t\t// ... Use results from Lichess user database\n\t\t\t\"Allow API after move 25\": \"TODO\",\n\t\t\t\"Draw PV on mouseover\": \"TODO\",\n\t\t\t\"Draw PV method\": \"TODO\",\t\t\t\t\t\t// How to draw a PV when mouse-over-ing part of it\n\t\t\t\t\"Animate\": \"TODO\",\t\t\t\t\t\t\t\t// Animate a series of moves\n\t\t\t\t\"Single move\": \"TODO\",\t\t\t\t\t\t\t// Show exactly the position after the move being hovered over\n\t\t\t\t\"Final position\": \"TODO\",\t\t\t\t\t\t// Show the final position in the sequence\n\t\t\t\"Pieces\": \"TODO\",\t\t\t\t\t\t\t\t// Icons / images to use for piece display\n\t\t\t\t\"Choose pieces folder...\": \"TODO\",\t\t\t\t// ... Select folder containing such images\n\t\t\t\t\"Default\": \"TODO\",\t\t\t\t\t\t\t\t// ... Just use default images\n\t\t\t\t\"About custom pieces\": \"TODO\",\t\t\t\t\t// Displays text explaining how to create custom images\n\t\t\t\"Background\": \"TODO\",\t\t\t\t\t\t\t// Background to use for board display\n\t\t\t\t\"Choose background image...\": \"TODO\",\t\t\t// ... Select image file for background\n\t\t\t\"Book frequency arrows\": \"TODO\",\t\t\t\t// Dispay arrows representing move frequency in opening book file\n\t\t\t\"Lichess frequency arrows\": \"TODO\",\t\t\t\t// Dispay arrows representing move frequency in Lichess database\n\t\t\"Sizes\": \"TODO\",\t\t\t\t\t\t\t\t// Adjust sizes of displayed things\n\t\t\t\"Infobox font\": \"TODO\",\t\t\t\t\t\t\t// Adjust SIZE of font in info pane\n\t\t\t\"Move history font\": \"TODO\",\t\t\t\t\t// Adjust SIZE of font in game tree pane\n\t\t\t\"Board\": \"TODO\",\t\t\t\t\t\t\t\t// Adjust SIZE of board\n\t\t\t\t\"Giant\": \"TODO\",\t\t\t\t\t\t\t\t// ...\n\t\t\t\t\"Large\": \"TODO\",\t\t\t\t\t\t\t\t// ...\n\t\t\t\t\"Medium\": \"TODO\",\t\t\t\t\t\t\t\t// ...\n\t\t\t\t\"Small\": \"TODO\",\t\t\t\t\t\t\t\t// ...\n\t\t\t\"Graph\": \"TODO\",\t\t\t\t\t\t\t\t// Adjust SIZE (height) of graph displaying winrate\n\t\t\t\"Graph lines\": \"TODO\",\t\t\t\t\t\t\t// Adjust SIZE of graph line\n\t\t\t\"I want other size options!\": \"TODO\",\t\t\t// Displays text explaining how to set custom size options\n\t\t\"Engine\": \"TODO\",\t\t\t\t\t\t\t\t// Top level menu item for engine settings\n\t\t\t\"Choose engine...\": \"TODO\",\t\t\t\t\t\t// Choose engine via file picker\n\t\t\t\"Choose known engine...\": \"TODO\",\t\t\t\t// Choose engine from a list of previously used engines\n\t\t\t\"Weights\": \"TODO\",\t\t\t\t\t\t\t\t// Choose neural net weight file\n\t\t\t\t\"Lc0 WeightsFile...\": \"TODO\",\t\t\t\t\t// ... For Lc0\n\t\t\t\t\"Stockfish EvalFile...\": \"TODO\",\t\t\t\t// ... For Stockfish\n\t\t\t\t\"Set to <auto>\": \"TODO\",\t\t\t\t\t\t// ... Just allow engine to use default\n\t\t\t\"Backend\": \"TODO\",\t\t\t\t\t\t\t\t// Backend choices for Lc0 engine (submenu not in this list)\n\t\t\t\"Choose Syzygy path...\": \"TODO\",\t\t\t\t// Find endgame tablebase folder\n\t\t\t\"Unset\": \"TODO\",\t\t\t\t\t\t\t\t// Clear endgame tablebase folder\n\t\t\t\"Limit - normal\": \"TODO\",\t\t\t\t\t\t// Node limit for engine in normal circumstances\n\t\t\t\t\"Unlimited\": \"TODO\",\t\t\t\t\t\t\t// ... no node limit (note: other options exist but are not in this list)\n\t\t\t\t\"Up slightly\": \"TODO\",\t\t\t\t\t\t\t// ... increase node limit slightly\n\t\t\t\t\"Down slightly\": \"TODO\",\t\t\t\t\t\t// ... decrease node limit slightly\n\t\t\t\"Limit - auto-eval / play\": \"TODO\",\t\t\t\t// Node limit for engine when playing or evaluating a game\n\t\t\t\"Limit by time instead of nodes\": \"TODO\",\t\t// Consider node limit setting as a time limit in milliseconds instead\n\t\t\t\"Threads\": \"TODO\",\t\t\t\t\t\t\t\t// How many threads engine should used\n\t\t\t\t\"Warning about threads\": \"TODO\",\t\t\t\t// ... Displays a text warning about when not to use too many threads\n\t\t\t\"Hash\": \"TODO\",\t\t\t\t\t\t\t\t\t// Hash size (submenu not in this list)\n\t\t\t\t\"I want other hash options!\": \"TODO\",\t\t\t// ... Displays text about how to have a custom hash setting\n\t\t\t\"MultiPV\": \"TODO\",\t\t\t\t\t\t\t\t// How many lines a Stockfish-like engine should consider at once (multipv setting)\n\t\t\t\"Contempt Mode\": \"TODO\",\n\t\t\t\t\"White analysis\": \"TODO\",\t\t\t\t\t\t// ... Contempt from White's point of view\n\t\t\t\t\"Black analysis\": \"TODO\",\t\t\t\t\t\t// ... Contempt from Black's point of view\n\t\t\t\"Contempt\": \"TODO\",\t\t\t\t\t\t\t\t// Numerical contempt value (submenu not in this list)\n\t\t\t\"WDL Calibration Elo\": \"TODO\",\t\t\t\t\t// Technical contempt setting\n\t\t\t\t\"Use default WDL\": \"TODO\",\t\t\t\t\t\t// ... Technical contempt setting\n\t\t\t\"WDL Eval Objectivity\": \"TODO\",\t\t\t\t\t// Technical contempt setting\n\t\t\t\t\"Yes\": \"TODO\",\t\t\t\t\t\t\t\t\t// ...\n\t\t\t\t\"No\": \"TODO\",\t\t\t\t\t\t\t\t\t// ...\n\t\t\t\"Score Type\": \"TODO\",\t\t\t\t\t\t\t// Technical contempt setting\n\t\t\t\"Custom scripts\": \"TODO\",\n\t\t\t\t\"How to add scripts\": \"TODO\",\t\t\t\t\t// ... Displays text explanation\n\t\t\t\t\"Show scripts folder\": \"TODO\",\t\t\t\t\t// ... Displays folder in filesystem\n\t\t\t\"Restart engine\": \"TODO\",\n\t\t\t\"Soft engine reset\": \"TODO\",\t\t\t\t\t// Tells engine to reset cache etc, without actually restarting\n\t\t\"Play\": \"TODO\",\t\t\t\t\t\t\t\t\t// Top level menu item for playing against the engine\n\t\t\t\"Play this colour\": \"TODO\",\t\t\t\t\t\t// Engine itself plays current colour from now on\n\t\t\t\"Start self-play\": \"TODO\",\t\t\t\t\t\t// Engine plays against itself\n\t\t\t\"Use Polyglot book...\": \"TODO\",\n\t\t\t\"Use PGN book...\": \"TODO\",\n\t\t\t\"Unload book / abort load\": \"TODO\",\n\t\t\t\"Book depth limit\": \"TODO\",\t\t\t\t\t\t// What depth to stop using the book\n\t\t\t\"Temperature\": \"TODO\",\t\t\t\t\t\t\t// Temperature for Lc0 neural network\n\t\t\t\"Temp Decay Moves\": \"TODO\",\t\t\t\t\t\t// Technical temperature-related setting (numerical options present, but not in this list)\n\t\t\t\t\"Infinite\": \"TODO\",\t\t\t\t\t\t\t\t// ...\n\t\t\t\"About play modes\": \"TODO\",\t\t\t\t\t\t// Displays text\n\t\t\"Dev\": \"TODO\",\t\t\t\t\t\t\t\t\t// Top level menu for Dev / technical settings\n\t\t\t\"Toggle Developer Tools\": \"TODO\",\n\t\t\t\"Toggle Debug CSS\": \"TODO\",\t\t\t\t\t\t// Shows red border around HTML elements\n\t\t\t\"Permanently enable save\": \"TODO\",\t\t\t\t// Allows save function to be used\n\t\t\t\"Show config.json\": \"TODO\",\t\t\t\t\t\t// Displays config file in filesystem\n\t\t\t\"Show engines.json\": \"TODO\",\t\t\t\t\t// Displays config file in filesystem\n\t\t\t\"Reload engines.json (and restart engine)\": \"TODO\",\n\t\t\t\"Random move\": \"TODO\",\t\t\t\t\t\t\t// Makes a random move from legal possibilities\n\t\t\t\"Disable hardware acceleration for GUI\": \"TODO\",\n\t\t\t\"Spin rate\": \"TODO\",\t\t\t\t\t\t\t// How frequently to update GUI\n\t\t\t\t\"Frenetic\": \"TODO\",\t\t\t\t\t\t\t\t// ...\n\t\t\t\t\"Fast\": \"TODO\",\t\t\t\t\t\t\t\t\t// ...\n\t\t\t\t\"Normal\": \"TODO\",\t\t\t\t\t\t\t\t// ...\n\t\t\t\t\"Relaxed\": \"TODO\",\t\t\t\t\t\t\t\t// ...\n\t\t\t\t\"Lazy\": \"TODO\",\t\t\t\t\t\t\t\t\t// ...\n\t\t\t\"Show engine state\": \"TODO\",\t\t\t\t\t// Shows debug info about engine\n\t\t\t\"List sent options\": \"TODO\",\t\t\t\t\t// Shows a list of all options ever sent to the engine\n\t\t\t\"Show error log\": \"TODO\",\t\t\t\t\t\t// Shows engine stderr\n\t\t\t\"Hacks and kludges\": \"TODO\",\t\t\t\t\t// Submenu with specific hacks\n\t\t\t\t\"Allow arbitrary scripts\": \"TODO\",\t\t\t\t// ...\n\t\t\t\t\"Accept any file size\": \"TODO\",\t\t\t\t\t// ...\n\t\t\t\t\"Allow stopped analysis\": \"TODO\",\t\t\t\t// ...\n\t\t\t\t\"Never hide focus buttons\": \"TODO\",\t\t\t\t// ...\n\t\t\t\t\"Never grayout move info\": \"TODO\",\t\t\t\t// ...\n\t\t\t\t\"Use lowerbound / upperbound info\": \"TODO\",\t\t// ...\n\t\t\t\t\"Suppress ucinewgame\": \"TODO\",\t\t\t\t\t// ... do not send ucinewgame token to engine\n\t\t\t\"Log RAM state to console\": \"TODO\",\n\t\t\t\"Fire GC\": \"TODO\",\t\t\t\t\t\t\t\t// Call JS garbage collector\n\t\t\t\"Logging\": \"TODO\",\n\t\t\t\t\"Use logfile...\": \"TODO\",\t\t\t\t\t\t// ...\n\t\t\t\t\"Disable logging\": \"TODO\",\t\t\t\t\t\t// ...\n\t\t\t\t\"Clear log when opening\": \"TODO\",\t\t\t\t// ...\n\t\t\t\t\"Use unique logfile each time\": \"TODO\",\t\t\t// ...\n\t\t\t\t\"Log illegal moves\": \"TODO\",\t\t\t\t\t// ...\n\t\t\t\t\"Log positions\": \"TODO\",\t\t\t\t\t\t// ...\n\t\t\t\t\"Log info lines\": \"TODO\",\t\t\t\t\t\t// ...\n\t\t\t\t\"...including useless lines\": \"TODO\",\t\t\t// ...\n\t\t\"Language\": \"TODO\",\n\n\t\t\"RESTART_REQUIRED\": \"TODO\"\t\t\t\t\t\t// Special item - translate from \"The GUI must now be restarted.\"\n\t}\n}\n"
  },
  {
    "path": "files/misc/pathological.pgn",
    "content": "[Event \"Jeopardy Match 12\"]\n[Site \"brinanSee\"]\n[Date \"2019.06.15\"]\n[Round \"4\"]\n[White \"lc0.net.42518\"]\n[Black \"Stockfish.dev 19061000\"]\n[Result \"1/2-1/2\"]\n[ECO \"D31\"]\n[Opening \"Semi-Slav\"]\n[Variation \"Marshall Gambit, 8.Ne2\"]\n[TimeControl \"5100+1\"]\n[Termination \"normal\"]\n[PlyCount \"304\"]\n\n1. d4 (d4 Nf6 c4 e6 g3 Bb4 Bd2 Be7 Bg2 d5 Nf3 O-O O-O c6 Qc2 Nbd7 Rd1 b6\nBf4 Ba6 Nbd2 Nh5 Be3 Rc8 Rac1 h6 Ne5 Nhf6 Qa4 Nb8 Qb3 Nbd7 h3 Bb7 Qa4 a6\ncxd5) {+0.27/13 89} d5 {0.00/1} 2. c4 (c4 e6 Nc3 Nf6 cxd5 exd5 Bg5 Be7 e3\nh6 Bh4 O-O Bd3 c6 Nge2 Ne4 Bxe7 Qxe7 Bxe4 dxe4 O-O Nd7 d5 Nf6 dxc6 bxc6 Qa4\nRb8) {+0.32/13 52} e6 {0.00/1 0} 3. Nc3 (Nc3 Nf6 cxd5 exd5 Bg5 Be7 e3 h6\nBh4 O-O Bd3 c6 Nge2 Ne4 Bxe7 Qxe7 Bxe4 dxe4 O-O Nd7 d5 f5 Nf4 Ne5 Qb3 Qf7\nRfd1) {+0.32/15 62} c6 {0.00/1 0} 4. e4 (e4 dxe4 Nxe4 Bb4 Bd2 Qxd4 Bxb4\nQxe4 Ne2 Ne7 Qd2 c5 Bxc5 Nbc6 Rd1 O-O Qf4 Qxf4 Nxf4 b6 Ba3 Re8 f3 e5 Nd5\nNxd5 cxd5 Nd4 Kf2 Rd8 f4 Bg4 Rd2 Rxd5 Bc4 Rd7 fxe5 Rc8 b3 b5) {+0.42/18 94}\ndxe4 {0.00/1 0} 5. Nxe4 (Nxe4 Bb4 Bd2 Qxd4 Bxb4 Qxe4 Ne2 Nd7 Qd6 Qe5 O-O-O\nQxd6 Bxd6 Nh6 Ng3 f5 Bd3 Kf7 Rhe1 Re8 Bc2 g6 h3 Kg7 Bb4 e5 f4 e4) {+0.41/20\n41} Bb4+ {0.00/1 0} 6. Bd2 (Bd2 Qxd4 Bxb4 Qxe4 Ne2 Nd7 Qd6 Qe5 O-O-O Qxd6\nBxd6 b6 g4 Bb7 Bg2 O-O-O Rhe1 Ngf6 g5 Ng4 Ng3 c5 Bxb7 Kxb7 Ne4 f5 gxf6\nNdxf6 Nxf6 gxf6 Rxe6 Nxf2 Rd2 Ng4) {+0.38/20 63} Qxd4 {0.00/1} 7. Bxb4\n(Bxb4 Qxe4 Ne2 Nd7 Qd6 Qe5 O-O-O Qxd6 Bxd6 b6 g4 Bb7 Bg2 O-O-O Rhe1 Ngf6 g5\nNg4 Ng3 c5 Bxb7 Kxb7 Ne4 f5 gxf6 Ndxf6 Nxf6 gxf6 Rxe6 Nxf2 Rd2 Ng4)\n{+0.38/15 3} Qxe4+ {0.00/1 0} 8. Ne2 (Ne2 Nd7 Qd6 Qe5 O-O-O Qxd6 Bxd6 Nh6\nNg3 f5 Bd3 g6 Rhe1 Kf7 Bc2 Re8 f4 Kg8 Bb4 Nf7 Bc3 Kf8 Nf1 e5 fxe5 Ndxe5 b3)\n{+0.32/18 117} Nd7 {0.00/1 0} 9. Qd6 (Qd6 Qe5 O-O-O Qxd6 Bxd6 Nh6 Ng3 f5\nBd3 g6 Rhe1 Kf7 Bc2 Re8 f4 b6 Ne2 Ba6 b3 c5 Ng1 Bb7 Nh3 Kg8 Ng5) {+0.28/18\n58} Qe5 {0.00/1 0} 10. O-O-O (O-O-O Qxd6 Bxd6 Nh6 Ng3 f5 Bd3 g6 Rhe1 Kf7\nNh1 Re8 f3 Kg8 Nf2 Nf7 Bb4 e5 Bc2 a5 Bc3 Nc5 b3 b6 h4 Ra7 h5 Rae7)\n{+0.23/16 154} Qxd6 {0.00/1 0} 11. Bxd6 (Bxd6 Nh6 Ng3 f5 Bd3 g6 Rhe1 Kf7\nNh1 Re8 f3 Kg8 Nf2 e5 Bc2 Nf7 Bb4 a5 Bc3 Nc5 b3 b6 h4 Ra7 h5 Rae7)\n{+0.22/15 19} Nh6 {0.00/1 0} 12. Ng3 (Ng3 f5 Bd3 g6 Rhe1 Kf7 Nh1 Re8 f3 Kg8\nBb4 e5 Nf2 Nf7 Bc2 a5 Bc3 Nc5 b3 b6 h4 Ra7 h5 Rae7 hxg6) {+0.19/14 175} f5\n{0.00/1 0} 13. Bd3 (Bd3 g6 Rhe1 Kf7 Nh1 Re8 f3 Kg8 Bb4 a5 Bc3 e5 Nf2 Nf7\nBc2 Nc5 b3 b6 h4 Ra7 h5 Rae7 hxg6 hxg6) {+0.17/14 89} g6 {0.00/1 0} 14.\nRhe1 (Rhe1 Kf7 Nh1 Re8 f3 Kg8 Bb4 a5 Bc3 e5 Nf2 Nf7 Bc2 Nc5 Bd4 Nd7 Bc3 Nc5\nBd4 Nd7 Bc3) {+0.14/14 146} Kf7 {0.00/1 0} 15. Nh1 (Nh1 Re8 f3 Kg8 Bb4 a5\nBc3 e5 Nf2 Nf7 Bc2 Nc5 b3 b6 h4 Ra7 h5 Rae7 hxg6 hxg6 g4 Kf8 Rg1) {+0.12/13\n271} a5 (a5 f3 Re8 Ba3 Kg7 h3 Nf7 g4 Nf6 b3 Ng5 Bb2 Kf7 f4 Nxh3 gxf5 gxf5\nRf1 Rg8 Rf3 Ng1 Rff1) {0.00/41 680} 16. Bf4 (Bf4 Ng4 f3 Ngf6 Nf2 e5 Bd2 Re8\nBc3 a4 Bc2 b5 g4 bxc4 gxf5 gxf5 Bxf5 Nc5 Bc2 a3 bxa3 Nd5 Bb4 Nxb4 axb4 Na6\na3) {+0.33/12 86} Ng8 (Ng8 f3 Ngf6 Nf2 Re8 Bg5 a4 g4 b5 cxb5 cxb5 gxf5 gxf5\nBxb5 Ra5 Bxf6 Rxb5 Bc3 e5 Rd6 Re6 Rxe6 Kxe6 Nd3 Bb7 Nxe5 Nxe5 f4 Rc5 Rxe5\nRxe5 Bxe5 Bd5 b3 axb3 axb3 Bxb3 Kb2 Bd5 Kc3 h6 Kb4) {+0.05/35 68} 17. f3\n(f3 Ngf6 Nf2 e5 Bd2 Re8 Bc3 a4 h3 h5 h4 e4 fxe4 Nc5 e5 Ng4 Nh3 Be6 Bb4 Nxd3\nRxd3 Bxc4 Rd7 Kg8 Bc3 b5 a3 c5 Rd6 b4 axb4) {+0.36/14 57} Ngf6 (Ngf6 Nf2\nRe8 Bd6 b6 g4 Ba6 Nh3 Kg8 Ng5 fxg4 fxg4 e5 h3 b5 Ne4 bxc4 Nxf6 Nxf6 Bc2 Bb7\nBxe5 Nd5 Bd4 Ba6 a3 Rad8 Bc5 Rxe1 Rxe1 Nf4 Bb6 Rd3 Be3 Ng2 Bxd3 cxd3 Bd2\nNxe1 Bxe1 Kf7 Bxa5 Ke6 Kd2) {+0.01/38 53} 18. Nf2 (Nf2 e5 Bd2 Re8 Bc3 a4 h3\nh5 h4 e4 fxe4 Nc5 e5 Ng4 Nh3 Be6 Bb4 Nxd3 Rxd3 Bxc4 Rd7 Kg8 Bc3 Bxa2 Nf4\nBe6 Rxb7 Nf2 Rc7 a3 bxa3) {+0.40/15 14} Re8 (Re8 Bd6 b6 g4 Ba6 Nh3 Kg8 Ng5\nfxg4 fxg4 e5 h3 b5 Ne4 bxc4 Nxf6 Nxf6 Bc2 Nd5 Bxe5 Bb7 Bd4 Rad8 Bc5 Rxe1\nRxe1 Kf7 Rf1 Kg8 Re1) {0.00/37 51} 19. Bd6 (Bd6 b5 cxb5 cxb5 Bxb5 Ba6 Ba4\nRec8 Kb1 Nb6 Bb3 Nfd5 Nh3 h6 Nf4 Bc4 Bxc4 Rxc4 Nd3 Nd7 Rc1 Rd4 Red1 Rd8 Bg3\nNe3 Rd2 Rd5 Bc7 Rc8 Bxa5 Nc4 Rdc2 Na3 bxa3 Rxc2 Kxc2) {+0.23/15 128} b6 (b6\ng4 Ba6 Nh3 Kg8 Ng5 fxg4 fxg4 Nxg4 Bf1 e5 Bc7 Bc8 Rd6 Ra7 Rxc6 Bb7 Rd6 Bc8)\n{0.00/41 0} 20. g4 (g4 Ba6 Bf1 Ra7 Nh3 e5 gxf5 gxf5 Bd3 h6 b3 e4 fxe4 Nxe4\nBxe4 Rxe4 Rxe4 fxe4 Rf1 Ke6 Bg3 b5 Nf4 Ke7 Re1 Nf6 Bh4 Kf7 Rf1) {+0.40/12\n109} Ba6 (Ba6 h4 h5 gxh5 Nxh5 Nh3 e5 Ng5 Kg7 Ba3 b5 cxb5 cxb5 b4 axb4 Bxb4\nRa7 Bc2 Bc8 Bb3 Nb6 Bd6 Kf6 Kb1 Nc4 Bxc4 bxc4 Rd5 e4 fxe4 Kg7 Ka1 Ra6 Re5\nRxe5 Bxe5 Kf8 Kb2 Rb6 Kc2 Ra6 Rd1 Ke7 Kc3 Rxa2) {-0.23/36 65} 21. b3 (b3\nBb7 g5 Nh5 Be2 a4 Bc7 Bc8 f4 e5 fxe5 axb3 axb3 Nf4 Bf3 Ra7 Bd6 Ra2 Nd3 Nxd3\nRxd3 Ra1 Kd2 Rxe1 Kxe1 Nxe5 Bxe5 Rxe5 Kf2 f4 Rd6 Rxg5 Rxc6 Be6 Rxb6)\n{+0.37/14 165} b5 (b5 Nh3 Kg8 Ng5 fxg4 fxg4 bxc4 bxc4 Nxg4 Bg3 Nc5 Bc2 e5\nh3 Nh6 Rd6 Nf7 Nxf7 Kxf7 Rxe5 Rxe5 Bxe5 Bxc4 Rf6 Ke7 Rxc6 Nd3 Bxd3 Bxd3 Bc3\nKd7 Rc5 Bf5 Rxa5 Rxa5 Bxa5 Bxh3 a4 Kc6 Be1) {+0.40/32 44} 22. g5 (g5 bxc4\nbxc4 Ng8 c5 Ne7 Bxa6 Rxa6 Nd3 Nd5 Kd2 Nb4 a3 Nxd3 Kxd3 Ra7 Rb1 e5 Kc4 e4\nfxe4 Ne5 Bxe5 Rxe5 exf5 Rxf5 Rf1 Re7 Rxf5 gxf5 Rb6 Re6 Kd3 Kg6) {+0.16/13\n98} Nh5 (Nh5 Bf1 bxc4 bxc4 Ra7 Be5 c5 Rd6 Re7 Bc3 Nf4 Red1 e5 Nd3 Nxd3\nR1xd3 Bxc4 Rxd7 Raxd7 Rxd7 Bxf1 Rxe7 Kxe7 Bxe5 Ke6 f4 Bc4 Kb2 Kd5 a4 Ke4\nKc3 Bd5 h4 Kf3 Bc7 Kg4 Bd6 Kg3 Bc7 Kxh4 Bxa5 Kg3) {+0.58/33 52} 23. c5 (c5\ne5 b4 Nf4 Bc2 Ng2 Re2 Nf4 Red2 Ne6 Bb3 axb4 Re1 Kg7 Nd3 Nxg5 Nxe5 Nh3 Rdd1\nNxe5 Bxe5 Kh6 Rd4 f4 Bf7 Red8 Rd6 Rxd6) {+0.57/16 89} e5 (e5 Bc2 b4 Nd3\nBxd3 Bxd3 Ra7 Bc4 Kg7 Rd4 h6 h4 Kh7 Kb2 Kg7) {0.00/43 188} 24. b4 (b4 Nf4\nBc2 Ng2 Re2 Nf4 Red2 axb4 Nd3 Ne6 Bb3 Kg7 Re1 Nd4 Bxe5 Nxe5 Nxe5 Nxb3 axb3\nBc8 Kb2 f4 Rde2 Bf5 Nxc6 Rxe2 Rxe2 Rc8 Nxb4 Rxc5 Re7 Kf8 Rxh7 Be6 h4 Bf7\nNd3 Rf5 Kc3 Kg8 Rh6 Kg7 b4 Bc4 Nc5 Bd5 Ne4 Bxe4 fxe4 Rf8 Kd2 Re8 Kd3 f3 h5)\n{+0.57/33 0} Nf4 (Nf4 Bc2 Ng2 Re2 Nf4) {0.00/42 151} 25. Bc2 (Bc2 Ng2 Re2\nNf4 Red2 axb4 Nd3 Ne6 Bb3 Kg7 Re1 Nd4 Bxe5 Nxe5 Nxe5 Nxb3 axb3 Bc8 Kb2 f4\nRde2 Bf5 Nxc6 Rxe2 Rxe2 Rc8 Nxb4 Rxc5 Re7 Kf8 Rxh7 Be6 h4 Bf7 Nd3 Rf5 Kc3\nKg8 Rh6 Kg7 b4 Bc4 Nc5 Bd5 Ne4 Bxe4 fxe4 Rf8 Kd2 f3 Ke1 Rf4 h5 gxh5 Rxh5)\n{+0.55/24 61} Ng2 (Ng2 Re2 Nf4 Ree1) {0.00/43 14} 26. Re2 (Re2 Nf4 Ree1 Ng2\nRe2 Nf4 Red2 axb4 Nd3 Ne6 Bb3 Kg7 Re1 Nd4 Bxe5 Nxe5 Nxe5 Nxb3 axb3 Bc8 Kb2\nf4 Rde2 Bf5 Nxc6 Rxe2 Rxe2 Rc8 Nxb4 Rxc5 Re7 Kf8 Rxh7 Be6 h4 Bf7 Nd3 Rf5\nKc3 Kg8 Rh6 Kg7 b4 Bc4 Nc5 Bd5 Ne4 Bxe4 fxe4 Rf8 Kd2 f3 Ke1 Rf4) {+0.55/22\n19} Nf4 (Nf4 Ree1 Ng2 Re2) {0.00/45 57} 27. Red2 (Red2 axb4 Nd3 Ne6 Bb3 Kg7\nRe1 Nd4 Nxe5 Nxb3 axb3 Bc8 Kb2 Nxe5 Bxe5 Kg8 Rd6 Be6 Rxc6 Bd5 Rc7 Bxf3 Rg7\nKf8 Rxh7 Be4 Bf6 f4 Rh4 Bf5 Rxe8 Rxe8 Rxf4 Re2 Ka1 Rc2 Rxb4 Rxc5 Kb2 Rc2\nKa3 Bd3 Rd4 Bf5 h4 Re2 Rd5) {+0.53/26 250} a4 (a4 Nd3 Nd5 Re1 Re6 a3 Rae8\nRde2 Bc8 h4 e4 fxe4 Rxe4 Rxe4 fxe4 Nf4 Nxf4 Bxf4 e3 Bb1 Ne5 Ba2 Nc4 Bxc4\nbxc4 Rxe3 Rxe3 Bxe3 Ke6 Kd2 Kd5 Kc3 Bf5 Bc1 Bd3 Bd2 Be2 Be3 Bf3 Bd4 Bg4 Bf6\nBe2 Kd2 Bf3 Ke3 Bg4 Kf4 Be6 Bc3 Bd7 Ke3 Bf5 Kf4) {0.00/42 311} 28. a3 (a3\nNd5 Bb1 Nc3 Ba2 Nxa2 Rxa2 Re6 Re2 Rae8 Nd3 Bc8 h4 Kg8 Rde1 e4 Nf4 Ne5 fxe4\nfxe4 Nxe6 Nd3 Kd2 Bxe6 Rf1 Bf5 Kc3 Kf7 Kd4 Kg7 h5) {+1.00/14 112} Bc8 (Bc8\nRe1 Ng2 Rg1 Nf4 Bb1 Nf8 Re1 Be6 Rxe5 Rac8 Nd3 Nxd3 Bxd3 Bb3 Rde2 Rxe5 Bxe5\nNe6 f4 Rd8 Re3 Bd5 Kc2 Rd7 Bd6 Rd8 Kc3 Re8 Be5 Rd8 Rh3 Kg8 Bd6 Re8 Rg3 Kf7\nBe5 Re7 Re3 Re8 Kc2 Bb3 Kd2 Bd5 Re2 Bb3 Kc3 Rd8 Rd2 Bd5 Be2 Be4 Rxd8 Nxd8\nh4) {-0.50/42 440} 29. Bb1 (Bb1 Nf8 Re1 Be6 Rxe5 Kg8 Kb2 Rad8 h4 Nd5 Rde2\nNf4 Re1 Ng2 R1e2 Nf4 Rd2 Nd5 Ba2 Nf4 Rxe6 N4xe6 Nd3 Kg7 Be5 Kg8 Bf6 Rd7 Kc3\nh6 Ne5 Rxd2 Kxd2 hxg5 hxg5 Nh7 Nxc6 Nxf6 gxf6 Kf7) {+1.08/20 82} Nf8 (Nf8\nRe1 Be6 Rxe5 Rac8 Nd3 Nxd3 Bxd3 Bb3 f4 Rcd8 Ree2 Ne6 Rf2 Rd7 h4 Nd8 Be5 Ne6\nBe2 Rxd2 Kxd2 Bd5 Rh2 Re7 Bd3 Bb3 Ke3 Nf8 Re2 Nd7 Kd4 Nxe5 Rxe5 Rd7 Kc3 Bd5\nh5 Rd8 h6 Rd7 Bc2 Re7 Kd4 Rd7 Ke3 Bc4 Kf3 Bd5 Kg3) {-0.54/41 150} 30. Re1\n(Re1 Be6 Rxe5 Kg8 Kb2 Rad8 h4 Nd5 Ba2 Nf4 Rxe6 N4xe6 Nd3 Kg7 Ne5 Rc8 f4 h6\nBxf8 Nxf8 Kc3 Rc7 Rd6 Ree7 Kd3 Re8 Nxc6 hxg5 hxg5) {+1.14/17 0} Be6 (Be6\nRxe5 Rac8 Nd3 Nxd3 Bxd3 Bb3 f4 Ne6 Re3 Ng7 Ree2 Rcd8 Kb2 Rd7 Rxe8 Nxe8 Be5\nKe6 Kc3 Nc7 Bxc7 Rxc7 Be2 Bd5 Rd3 h5 gxh6 Rh7 Rh3 Kf7 Bf1 Kg8 Bd3 Re7 h7\nRxh7 Rxh7 Kxh7 Be2 Kg7 Kd4 Be4 Ke5 Kf7 h4 Ke7 h5 gxh5 Bxh5 Kd8 Kf6 Kc7 Kg5)\n{-0.67/42 179} 31. Rxe5 (Rxe5 Kg8 Kb2 Rad8 h4 Nd5 Ba2 Nf4 Rxe6 N4xe6 Nd3\nKh8 Ne5 Rc8 Bxf8 Nxf8 Nf7 Kg7 Nd6 Rcd8 Nxe8 Rxe8 Rd6 Re2 Kb1 Re1 Kc2 Re2\nRd2 Re3 Kb2 Rxf3 Rd6 Rf2 Kb1 Rf1 Kc2 Rf2 Rd2 Rxd2 Kxd2 Nd7 Ke3 Ne5)\n{+1.19/19 0} Rac8 (Rac8 Nd3 Nxd3 Bxd3 Bb3 f4 Ne6 Re3 Ng7 Ree2 Rcd8 Kb2 Rd7\nRxe8 Nxe8 Be5 Ke6 Kc3 Nc7 Bxc7 Rxc7 Be2 Bd5 Rd3 h5 gxh6 Rh7 Rh3 Kf7 Bf1 Kg8\nKd4 Re7 h7 Kh8 Bd3 Be4 Kc3 Bxd3 Kxd3 Re4 Rh6 Rxf4 Rxg6 Rf3 Kd4 Rxa3 Rxc6\nRb3 Rc7 Rxb4 Kd5 Rg4 Rf7 Rc4) {-0.70/40 94} 32. Rd4 (Rd4 Nd5 Kd2 Nd7 Re1\nKg7 Nd3 Bf7 Be5 Kg8 Ba2 Nf8 h4 Re7 Bd6 Rxe1 Nxe1 Ne6 Rd3 Nef4 Bxf4 Nxf4\nBxf7 Kxf7 Rd7 Ke6 Rd6 Ke7 Nd3 Nxd3 Kxd3) {+1.27/16 209} Nd5 (Nd5 Kd2 Nd7\nRe1 Kg7 Nd3 Bf7 f4 Rcd8 Ne5 Nxe5 Bxe5 Kg8 Kc1 Re7 Rd3 Red7 Red1 Be6 Ba2 Kf8\nKb2 Bf7 R1d2 Kg8 Bb1 Be6 Re2 Bf7 Rc2 Be6 Rc1 Bf7 Ba2 Be6 Rcd1 Re8 R1d2 Bf7\nBb1 Rde7 Bc2 h6 gxh6 Kh7) {-0.89/36 88} 33. Kd2 (Kd2 Nd7 Re1 Kg7 Nd3 Bf7\nBe5 Kg8 Ba2 Nf8 h4 Rcd8 Bd6 Rxe1 Nxe1 Ne6 Rd3 Ng7 Be5 Nh5 Nc2 Re8 Bxd5 Bxd5\nRxd5 cxd5 f4 Kf7 Nd4 Nxf4 Bxf4 Re4) {+1.30/16 12} Kg7 (Kg7 Nd3 Nd7 Re1 Bf7\nf4 Rcd8 Ne5 Nxe5 Bxe5 Kg8 Kc1 Rd7 Rd3 Red8 Red1 Be6 Ba2 Kf7 R3d2 Kf8 h4 Kg8\nKb2 Bf7 Rd4 Kf8 R1d2 Kg8 Kc1 Re8 R4d3 Red8 Rd1 Kf8 Bb1 Ke8 Re1 Re7 Rd2 Kd7\nh5 Kc8 Rh1 Red7) {-0.99/40 80} 34. Bd3 (Bd3 Nd7 Re2 h6 h4 Bf7 Nd1 hxg5 hxg5\nRxe2 Bxe2 Re8 f4 Rh8 Bf3 Rh2 Ke1 Be6 Rd3 Kf7 Ne3 Rh3 Kf2 Nxe3) {+1.38/14\n348} Nd7 (Nd7 Re2 h6 h4 hxg5 hxg5 Bf7 Nd1 Nc7 Rxe8 Nxe8 Bg3 Nf8 Be5 Kg8 Nf2\nBb3 Rh4 Ng7 Be2 Rd8 Kc3 Re8 f4 Nd7 Bf3 Nxe5 fxe5 Rxe5 Nd3 Re3 Bxc6 Bc4 Rxc4\nbxc4 Kxc4 Ne6 Bxa4 Kf8 Bb3 Ke7 a4 Nxg5 a5) {-1.17/36 237} 35. Re2 (Re2 h6\nh4 Bf7 Nd1 Nf8 Rxe8 Rxe8 f4 hxg5 hxg5 Nd7 Be2 Rh8 Bf3 Rh2 Ke1 Be6 Rd3 Rh3\nKf2 Kf7 Kg2 Rh8 Nc3 Nxc3) {+1.52/15 0} h6 (h6 f4 hxg5 fxg5 Rcd8 Nd1 Kf7 Nc3\nN7b6 cxb6 Rxd6 Nxd5 Red8 Nc7 Rxd4 Nxe6 Rxd3 Kc2 R3d7 Nxd8 Rxd8 Rd2 Rb8 Rd7\nKe6 Rg7 Rxb6 Rxg6 Ke5 h4 Rb8 Rxc6 Rh8 Rh6 Rc8 Kd3 Rc4 h5 Rg4 g6 Rg3 Ke2 f4\nRh7 Re3 Kf2 Rxa3 Rb7 Rb3 Rxb5 Kf6 Ra5 Rxb4) {-1.13/36 124} 36. h4 (h4 hxg5\nhxg5 Rcd8 Nd1 Nf8 f4 Kf7 Rh2 Ne7 Nc3 Nc8 Be5 Rxd4 Bxd4 Ne7 Bf6 Nd5 Be5 Nxc3\nKxc3 Bd5 Rh6 Bf3 Bb1 Bd5 Rh8 Ne6) {+1.55/14 41} hxg5 (hxg5 hxg5 Kg8 Nh3 Bf7\nf4 Nf8 Ng1 Red8 Rh2 Rxd6 cxd6 Rd8 Rh4 Kg7 Be2 Ne6 Rxd5 cxd5 Bxb5 Rxd6 Ne2\nRd8 Bd3 Rd6 Rh1 Be8 Rb1 Kf7 b5 Rb6 Rb4 Rb7 Nc3 d4 Ne2 Ke7 b6 Bc6 Bc4 Kd6)\n{-1.23/34 52} 37. hxg5 (hxg5 Rcd8 Nd1 Kf7 Rh2 Nf8 f4 Ne7 Nc3 Nc8 Be5 Rxd4\nBxd4 Ne7 Be5 Rd8 Ke3 Nd5 Nxd5 Bxd5 Bd6 Re8 Kd2 Be4 Be2 Bd5 Rh8 Kg7)\n{+1.61/14 89} Kg8 (Kg8 Nh3 Bf7 f4 Nf8 Ng1 Red8 Rh2 Rxd6 cxd6 Rd8 Rh4 Kg7\nBe2 Ne6 Rxd5 cxd5 Bxb5 Rxd6 Ne2 Rd8 Rh3 Be8 Bd3 d4 Bc4 Nc7 Rd3 Bb5 Nxd4\nBxc4 Nxf5 gxf5 Rxd8 Bf7 Ke3 Nd5 Kf3) {-0.95/34} 38. Nd1 (Nd1 Kf7 Rh4 Rh8\nReh2 Rxh4 Rxh4 Kg7 Be2 Bg8 Rd4 Be6 Ne3 Nxe3 Kxe3 Re8 Kf2 Kf7 f4 Rh8 Bf3 Rc8\nRd1 Bb3 Rh1 Kg7 Re1 Kg8 Re7) {+1.68/14 163} Nf8 (Nf8 Rh2 Rcd8 Nc3 Nxc3 Kxc3\nBb3 Rdh4 Kg7 Kd2 Bg8 Bf1 Bd5 f4 Kf7 Bd3 Rxd6 cxd6 Rd8 Kc3 Rxd6 Rh6 Rd7 Rd2\nKg7 Rh4 Kf7 Rh3 Kg8 Bf1 Kf7 Rh6 Rd6 Bg2 Ne6 Rh7 Kg8 Rh4 Kf8 Bf3 Ke7 Rh7 Kd8\nBxd5 cxd5 Rf2) {-1.61/37 182} 39. f4 (f4 Ne7 Nc3 Kf7 Rh2 Rcd8 Rh3 Nc8 Be5\nRxd4 Bxd4 Ne7 Be5 Rd8 Ke1 Bb3 Bd6 Nc8 Bc7 Rd7 Be5 Ne7 Bf1 Nd5 Bg2 Rd8 Nxd5)\n{+1.70/13 254} Ne7 (Ne7 Nc3 Kf7 Rh2 Bb3 Bf1 Ne6 Rd3 Rh8 Rdh3 Rxh3 Rxh3 Nd5\nNxd5 Bxd5 Rh7 Kg8 Re7 Rd8 Be2 Ng7 Bc7 Re8 Rd7 Be4 Be5 Ne6 Ke3 Nxg5 Rg7 Kf8\nRxg6 Nf7 Bg7 Ke7 Bh5 Kd7 Rf6 Bd5 Kf2 Ke7 Bxf7 Bxf7 Rxc6 Bc4) {-1.21/38 231}\n40. Nc3 (Nc3 Kf7 Rh2 Rcd8 Rh3 Nc8 Be5 Rxd4 Bxd4 Ne7 Be5 Rd8 Ke1 Bb3 Bd6 Nd5\nNxd5 Bxd5 Kf2 Re8 Bxf8 Rxf8 Rh7 Ke6 Rg7 Rh8 Rxg6) {+1.54/14 0} Kf7 (Kf7 Rh2\nRcd8 Rh8 Bc8 Be2 Ne6 Rxe8 Rxe8 Rd3 Rh8 Bf3 Bd7 Ke1 Rh2 Ne2 Nd5 Kf1 Bc8 Kg1\nRh3 Kg2 Rh8 Be5 Re8 Bxd5 cxd5 Rh3 d4 Kf2 d3 Nc3 d2 Rh7 Kg8 Rh6 Kf7 c6 Rd8\nKe2) {-1.91/36 79} 41. Rh2 (Rh2 Rcd8 Rh3 Nc8 Be5 Rxd4 Bxd4 Ne7 Ne2 Rd8 Rh8\nRd7 Be5 Bc4 Nd4 Ne6 Rh7 Ke8 Rh8 Kf7 Rh7 Ke8 Bxc4 bxc4 Rh8 Kf7 Kc3 Nxd4 Bxd4\nNd5 Kxc4 Nxf4 Be5 Ne6 Rh7 Ke8 Rh6 Kf7 b5 Rd5 Rh7 Kg8) {+1.58/18 18} Rcd8\n(Rcd8 Rh8 Bc8 Be2 Ne6 Rxe8 Rxe8 Rd3 Rh8 Bf3 Bb7 Re3 Rd8 Ke1 Rd7 Be2 Nc7 Rh3\nNcd5 Nxd5 Nxd5 Kf2 Ke6 Rh6 Kf7 Be5 Ba6 Bf3 Bb7 Rh8 Ne7 Ke3 Nd5 Bxd5 cxd5\nKd4 Ke6 Bd6) {-1.85/35 16} 42. Rh6 (Rh6 Nc8 Be5 Rxd4 Bxd4 Ne7 Be5 Bb3 Bd6\nNd5 Bxf8 Rxf8 Rh7 Kg8 Nxd5 Bxd5 Rd7 Bg2 Bb1 Bd5 Rd6 Kf7 Bd3 Bg2 Rd7 Kg8 Ke3\nRe8 Kf2 Bh1 Be2 Re4 Rd1 Rxf4 Kg3) {+1.56/18 119} Nc8 (Nc8 Be5 Rxd4 Bxd4 Bb3\nBe5 Ne7 Rh8 Rd8 Bc7 Re8 Bd6 Nd5 Rh6 Rd8 Nxd5 Bxd5 Ke3 Kg7 Kd4 Ne6 Kc3 Nf8\nBe5 Kf7 Rh8 Re8 Rh2 Rd8 Rd2 Ne6 Bd6 Bf3 Rh2 Nf8 Bc7 Rd7 Be5 Bd5 Bd6 Rd8 Rd2\nNe6 Bf1 Be4 Rh2 Nf8 Rh8 Bd5 Bxf8 Be4 Rh6) {-0.74/36 24} 43. Be5 (Be5 Rxd4\nBxd4 Ne7 Be5 Bb3 Bd6 Nd5 Bxf8 Rxf8 Rh7 Kg8 Nxd5 Bxd5 Rd7 Bg2 Bb1 Bd5 Rd6\nKf7 Bd3 Bg2 Rd7 Kg8 Bb1 Bd5 Kc3 Re8 Rxd5 cxd5 Bd3 Kf7 Bxb5 Re3 Bd3 Rf3)\n{+1.52/17 19} Rxd4 (Rxd4 Bxd4 Bb3 Be5 Ne7 Rh8 Rd8 Bc7 Re8 Bd6 Nd5 Nxd5 Bxd5\nRh2 Be4 Be2 Rd8 Kc3 Bd5 Bd3 Re8 Rh6 Kg7 Be5 Kf7 Rh8 Rd8 Be2 Be4 Rh3 Re8 Rh2\nRd8 Rh8 Bd5 Rh3 Bg2 Rh6 Be4 Bd6 Bd5 Bd3 Re8 Bxf8 Rxf8 Rh7 Kg8) {-0.74/40\n17} 44. Bxd4 (Bxd4 Ne7 Be5 Bb3 Bd6 Nd5 Bxf8 Rxf8 Rh7 Kg8 Nxd5 Bxd5 Rd7 Bg2\nBb1 Bd5 Rd6 Kf7 Bd3 Bg2 Rd7 Kg8 Bb1 Bd5 Kc3 Re8 Rxd5 cxd5 Bd3 Kf7 Bxb5 Re3\nBd3 Rf3) {+1.47/16 25} Ne7 (Ne7 Ne2 Bb3 Be5 Ne6 Rh7 Kg8 Rh4 Rd8 Rh1 Kf7 Rh7\nKe8 Bd6 Nc8 Bb8 Ne7 Be5 Nd5 Bd6 Rd7 Rh4 Kf7 Be5 Ne7 Rh7 Kg8 Rh8 Kf7 Bd6 Nd5\nRa8 Rd8 Ra7 Kg8 Be5 Rc8 Rd7 Kf8 Rh7 Rd8 Bd6 Kg8 Rh4 Bc4 Bxc4 bxc4 Rh6 Ndxf4\nNxf4 Nxf4 Kc3) {-0.74/39 17} 45. Be5 (Be5 Bb3 Ne2 Ne6 Rh7 Kg8 Rh6 Kf7 Nd4\nNxd4 Bxd4 Rd8 Be5 Rd7 Rh7 Ke6 Rh6 Kf7 Rh7 Ke8 Bd6 Bg8 Rh8 Kf7 Rh1 Nc8 Be5\nNe7 Rh6 Ke8 Bd6 Kf7 Be2) {+1.41/16 272} Bb3 (Bb3 Ne2 Ne6 Ng1 Bd5 Rh4 Kf8\nNe2 Kf7 Rh7 Kg8 Rh6 Kf7 Nc3 Bb3 Rh7 Kg8 Rh2 Kf7 Bf1 Rd8 Ke3 Nd5 Nxd5 Bxd5\nRh7 Ke8 Bd6 Rd7 Rh8 Kf7 Bd3 Kg7 Rh3 Rd8 Be5 Kf7 Rh7 Ke8 Bd6 Rd7 Rh6 Kf7 Be2\nBe4 Rh7 Ke8 Rh3 Kf7 Bf3 Bxf3 Kxf3 Nf8 Rh6 Kg7 Bxf8 Kxf8 Rxg6 Rd3 Ke2 Rxa3)\n{-0.75/41 61} 46. Ne2 (Ne2 Ne6 Rh7 Kg8 Rh6 Kf7 Nd4 Nxd4 Bxd4 Rd8 Be5 Rd7\nRh7 Ke6 Rh6 Kf7 Rh7 Ke8 Bd6 Bg8 Rh8 Kf7 Rh1 Nc8 Be5 Ne7 Rh6 Ke8 Bd6 Kf7\nBe2) {+1.40/15 24} Ne6 (Ne6 Ng1 Bd5 Rh4 Kf8 Ne2 Kf7 Rh7 Kg8 Rh6 Kf7 Nc3 Bb3\nRh7 Kg8 Rh2 Kf7 Be2 Rd8 Ke3 Nd5 Nxd5 Bxd5 Rh7 Ke8 Bd6 Rd7 Rh8 Kf7 Bd3 Kg7\nRh3 Nd8 Kd4 Ne6 Kc3 Nd8 Be5 Kf7 Rh8 Ne6 Bd6 Kg7 Rh6 Nd8 Be5 Kf7 Kd2 Ne6 Rh7\nKe8 Rh8 Kf7 Bd6 Kg7 Rh2 Kf7 Ke3 Kg7 Be5 Kf7 Rh7 Ke8 Rh4 Kf7 Bd6 Nf8 Bxf8\nKxf8 Rh6) {-0.74/41 4} 47. Rh7+ (Rh7 Kg8 Rh6 Kf7 Nd4 Nxd4 Bxd4 Rd8 Rh7 Ke6\nBe5 Rd5 Kc3 Rd7 Rh6 Kf7 Rh8 Ke6 Bd6 Nd5 Kd2 Nxf4 Bxf4 Bc4 Bd6 Bxd3 Kxd3 Rf7\nBf4 Rd7 Ke3 Kd5) {+1.35/14 149} Kg8 (Kg8 Rh2 Kf7 Rh4 Bd5 Rh7 Kg8 Rh2 Kf7\nNc3 Bb3 Rh7 Kg8 Rh6 Kf7 Ke3 Nf8 Ne2 Ne6 Rh7 Kg8 Rh8 Kf7 Rh6 Nd5 Kd2 Ne7 Ng1\nBd5 Rh7 Kg8 Rh3 Kf7 Rh6 Rd8 Ne2 Bf3 Nc3 Nd5 Rh7 Kg8 Rh4 Kf7 Bd6 Kg7 Nxd5\nBxd5 Be5 Kg8 Rh6 Kf7 Bf6 Re8 Rh7 Kg8) {-0.74/41 31} 48. Rh6 (Rh6 Kf7 Rh2\nNd5 Rh7 Kf8 Ra7 Rc8 Ra6 Kf7 Ra7 Kg8 Rd7 Rd8 Rxd8 Nxd8 Nd4 Kf7 Be2 Ba2 Bf3\nBc4 Bd6 Ke8 Bg2) {+1.33/15 72} Kf7 (Kf7 Nc3 Nf8 Be2 Rd8 Ke3 Nd5 Nxd5 Bxd5\nBd3 Re8 Kf2 Rd8 Bd6 Re8 Bf1 Be4 Rh3 Kg8 Be5 Rd8 Rh8 Kf7 Bd6 Re8 Rh6 Kg7 Be5\nKf7 Ke3 Rd8 Bd6 Bd5 Bd3 Re8 Kd2 Rd8 Kc3 Kg7 Rh2 Kf7 Rd2 Ne6 Bf1 Be4 Rh2 Nf8\nBxf8 Rxf8 Rh7 Kg8) {-0.74/44 51} 49. Ng1 (Ng1 Nd5 Rh7 Kf8 Ne2 Kg8 Rd7 Rd8\nRa7 Rc8 Ra6 Kf7 Bd6 Ke8 Ra7 Rd8 Rh7 Rd7 Rh8 Kf7 Rc8 Ne7 Ra8 Nd5 Ra6 Ne7\nNc1) {+1.35/15 55} Bd5 (Bd5 Rh7 Kg8 Rh4 Kf7 Ke3 Bb3 Ne2 Nd5 Kd2 Ne7 Rh6 Nd5\nBd6 Kg7 Rh1 Kg8 Rh4 Ne7 Nc3 Nf8 Rh3 Nd5 Nxd5 Bxd5 Rh6 Kf7 Rh4 Be4 Be2 Bd5\nBe5 Be4 Ke3 Bd5 Rh3 Rd8 Rh6 Re8 Kf2 Rd8 Bd6 Re8 Bxf8 Rxf8 Rh7 Ke8 Bf3)\n{-0.74/45 39} 50. Rh7+ (Rh7 Kg8 Rh2 Kf7 Ne2 Bb3 Nd4 Nxd4 Bxd4 Rd8 Be5 Rd5\nKc3 Rd7 Rh8 Nd5 Kd2 Bc4 Bxc4 bxc4 Kc1 Ne3 Bd6 c3 b5 cxb5 c6) {+1.38/14 1}\nKg8 (Kg8 Rh4 Kf7 Ke3 Bb3 Ne2 Nd5 Kd2 Ne7 Rh6 Nd5 Bd6 Kg7 Rh1 Kg8 Rh4 Ne7\nNc3 Nf8 Rh3 Nd5 Nxd5 Bxd5 Rh6 Kf7 Rh4 Be4 Be2 Bd5 Be5 Be4 Ke3 Bd5 Rh3 Rd8\nRh6 Re8 Kf2 Rd8 Bd6 Re8 Bd3 Bc4 Bxc4 bxc4 Be5 Nd7 Rh7 Ke6) {-0.74/45 28}\n51. Rh2 (Rh2 Rd8 Ke3 Re8 Ne2 Bb3 Nc3 Nd5 Nxd5 Bxd5 Rh8 Kf7 Rh7 Kf8 Rd7 Rd8\nBd6 Kg8 Re7 Rf8 Bf1 Rd8 Be2 Rf8 Bf3 Bxf3 Rxe6 Bd5 Rxg6) {+1.49/14 38} Kf7\n(Kf7 Ne2 Bb3 Rh7 Kg8 Rh8 Kf7 Rh2 Nd5 Rh7 Kf8 Rh4 Kf7 Bd6 Kg8 Rh2 Kf7 Rh7\nKg8 Rh4 Rd8 Nc3 Nxc3 Kxc3 Nf8 Rh6 Kg7 Rh3 Kf7 Kd2 Bd5 Rh8 Kg7 Be5 Kf7 Kc3\nRe8 Rh6 Bb3 Be2 Bd5 Kd2 Rd8 Ke3 Be4 Rh3 Bd5 Bd6 Re8 Kf2 Be4 Rh6 Bd5 Bxf8\nRxf8 Rh7 Ke8) {-0.74/41 36} 52. Ne2 (Ne2 Bb3 Nd4 Nxd4 Bxd4 Rd8 Be5 Rd7 Bd6\nNc8 Bb8 Ne7 Rh7 Kg8 Rh6 Kf7 Rh8 Bc4 Bd6 Bxd3 Kxd3 Nd5 Rf8 Ke6 Rc8 Rh7 Rxc6\nRh3 Kd4 Rxa3 Be5 Ke7 Rxg6 Nxb4) {+1.34/14 65} Bb3 (Bb3) {-0.74/42} 53. Rh1\n(Rh1 Nd5 Rh7 Kg8 Rd7 Rd8 Ra7 Rc8 Ra5 Kf8 Ra6 Kf7 Ra7 Kg8 Rd7 Rd8 Rxd8 Nxd8\nNd4 Kf7 Be2 Ba2 Bf3 Bc4 Bd6 Ke8 Bg2) {+1.30/13 113} Nd5 (Nd5 Rh7 Kf8 Bd6\nKg8 Rh4 Ne7 Rh6 Kg7 Ng1 Bd5 Rh4 Kg8 Be5 Kf7 Rh7 Kg8 Rh2 Kf7 Ne2 Bb3 Rh7 Kg8\nRh8 Kf7 Rh2 Nd5 Rh1 Kf8 Rh7 Rd8 Rh4 Kf7 Bd6 Ba2 Rh7 Kg8 Ra7 Bb3 Be5 Rc8 Nd4\nNxd4 Bxd4 Nxf4) {-0.74/39 52} 54. Rh7+ (Rh7 Kg8 Rd7 Rd8 Ra7 Rc8 Ra5 Kf8 Ra6\nKf7 Ra7 Kg8 Rd7 Rd8 Rxd8 Nxd8 Nd4 Kf7 Be2 Ba2 Bf3 Ke8 Bg2 Kd7 Nf3)\n{+1.26/15 0} Kf8 (Kf8 Bd6 Kg8 Rh4 Ba2 Rh2 Kf7 Rh6 Rd8 Nc3 Nxc3 Kxc3 Rd7 Kd2\nBb3 Rh7 Ke8 Rh4 Kf7 Kc3 Bd5 Kb2 Nd4 Rh7 Ke6 Rh6 Kf7 Bb1 Ne6 Rh7 Ke8 Rh8 Kf7\nRh6 Bb3 Rh7 Ke8 Rh8 Kf7 Kc3 Kg7 Be5 Kf7 Bd3 Bd5 Rh6 Ba2 Bf1 Bd5 Bd6 Be4 Be2\nKg7 Bd1 Bd5 Be5 Kf7 Rh7 Ke8 Rh4 Kf7 Be2 Nf8 Rh6 Rd8 Bd6 Re8 Bd3 Bf3 Be5 Rd8\nRh8 Bd5 Bd6 Bf3 Kd2 Bd5 Ke3 Re8 Be5 Bg2 Rh2 Bd5 Rh6 Bb3 Be2 Bd5 Kd2 Rd8 Bd6\nBe4 Kc3 Kg7 Rh3 Kf7 Bd3 Bd5 Rh6 Bf3 Kd2 Re8 Bxf8 Rxf8 Rh7 Kg8) {-0.74/39\n46} 55. Ra7 (Ra7 Rc8 Rd7 Rd8 Bd6 Kg8 Ra7 Re8 Ra6 Rc8 Be5 Kf7 Ra7 Kg8 Rd7\nRd8 Rxd8 Nxd8 Nd4 Kf7 Be2 Ba2 Bf3 Bc4 Bd6 Ke8) {+1.28/13 65} Rc8 (Rc8 Rd7\nRd8 Rh7 Kg8 Rh2 Re8 Rh1 Kf7 Rh7 Kf8 Bd6 Kg8 Rh4 Ba2 Rh6 Kf7 Nc3 Nxc3 Kxc3\nBd5 Rh7 Kg8 Rb7 Be4 Bf1 Bf3 Rd7 Be4 Ra7 Bd5 Ra6 Rd8 Bd3 Rd7 Ra8 Kg7 Be5 Kf7\nRh8 Ke7 Rh7 Ke8 Rh4 Kf7 Bd6 Ke8 Bf1 Kf7 Rh7 Ke8 Rh8 Kf7 Bd3 Kg7 Be5 Kf7 Kd2\nRd8 Rh7 Kg8 Rh6 Kf7 Bd6 Kg7 Kc3 Nf8 Rh2 Kf7 Bxf8 Kxf8 Rh7) {-0.74/42 26}\n56. Rh7 (Rh7 Re8 Bd6 Kg8 Ra7 Rd8 Be5 Rc8 Rd7 Rd8 Rxd8 Nxd8 Nd4 Kf7 Be2 Ba2\nBf3 Ke8 Bg2 Kd7 Nf3) {+1.22/13 45} Rd8 (Rd8 Rh8 Kf7 Rh6 Re8 Rh7 Kf8 Bd6 Kg8\nRh4 Ba2 Rh6 Kf7 Nc3 Nxc3 Kxc3 Bd5 Rh7 Kg8 Rb7 Be4 Bf1 Bf3 Rd7 Nf8 Rc7 Ne6\nRb7 Bd5 Ra7 Nf8 Bd3 Ne6 Ra6 Kf7 Bc2 Be4 Ra7 Kg8 Rd7 Bd5 Be5 Nxg5 Rg7 Kf8\nRxg6 Ne4 Kb2 Re6) {-0.74/41 26} 57. Bd6+ (Bd6 Kg8 Ra7 Re8 Be5 Rc8 Rd7 Rd8\nRxd8 Nxd8 Nd4 Kf7 Be2 Ke8 Bf3 Kd7 Nxb3 axb3 Kc1 Ne3 Be2 Ne6) {+1.24/13 8}\nKg8 (Kg8 Rh4 Re8 Rh3 Rd8 Be5 Re8 Rh6 Kf7 Rh4 Ne7 Nc3 Nd5 Bd6 Kg7 Nxd5 Bxd5\nKc3 Nf8 Be5 Kg8 Rh8 Kf7 Rh6 Rd8 Bd6 Kg7 Bc7 Rd7 Be5 Kf7 Bd6 Ne6 Rh7 Ke8 Rh4\nKf7 Kd2 Kg8 Rh6 Kg7 Be5 Kf7 Rh7 Ke8 Rh8 Kf7 Kc3 Nf8 Bd6 Ne6 Kb2 Kg7 Rh3 Kf7\nRh7 Ng7 Kc3 Kg8 Rh6 Kf7 Kd4 Rd8 Rh7 Kg8 Rh3 Ne6 Ke3 Re8 Kf2 Nf8 Rh6 Kf7\nBxf8 Rxf8 Rh7 Kg8) {-0.74/43 23} 58. Ra7 (Ra7 Rc8 Rd7 Rd8 Ra7 Re8 Ra6 Rc8\nBe5 Kf7 Ra7 Kg8 Rd7 Rd8 Rxd8 Nxd8 Nd4 Kf7 Be2 Ba2 Bf3 Bc4 Bd6 Ke8 Bg2)\n{+1.23/13 24} Kh8 (Kh8 Ra6 Rc8 Be5 Kg8 Ra7 Rd8 Rb7 Ba2 Bb8 Bb3 Bd6 Kh8 Be5\nKg8 Ra7 Rc8 Rd7 Kf8 Rh7 Rd8 Rh8 Kf7 Rh6 Re8 Rh4 Ne7 Nc3 Nd5 Bd6 Kg7 Nxd5\nBxd5 Kc3 Nf8 Be5 Kg8 Rh6 Kf7 Rh3 Rd8 Rh2 Re8 Rd2 Rd8 Bc7 Rd7 Bd6 Ne6)\n{-0.74/41 51} 59. Ra6 (Ra6 Rc8 Ra5 Kg8 Ra6 Kf7 Be5 Ke7 Ra7 Kf8 Rd7 Rd8 Rxd8\nNxd8 Nd4 Ke8 Be2 Kd7 Bf3 Bc4 Bg2) {+1.25/13 42} Rc8 (Rc8 Be5 Kg8 Ra5 Re8\nRa7 Rd8 Rb7 Ba2 Bb8 Bb3 Bd6 Ra8 Rd7 Rc8 Be5 Kf8 Rh7 Rd8 Rh8 Kf7 Rh6 Re8 Rh4\nNe7 Nc3 Nd5 Bd6 Kg7 Nxd5 Bxd5 Kc3 Nf8 Be5 Kg8 Rh6 Kf7 Rh3 Rd8 Rh2 Re8 Rd2\nRd8 Bc7 Rd7 Bd6 Ne6) {-0.74/45} 60. Ra5 (Ra5 Kg8 Ra6 Kf7 Be5 Ke7 Ra7 Kf8\nRd7 Rd8 Bd6 Ke8 Rh7 Rd7 Rh8 Kf7 Rc8 Ne7 Ra8 Nd5 Ra6 Ne7 Nc1) {+1.24/12 15}\nRe8 (Re8 Be5 Kg8 Ra6 Rc8 Bd6 Kh8 Ra7 Rd8 Rb7 Kg8 Be5 Rc8 Rd7 Kf8 Rh7 Rd8\nRh8 Kf7 Rh4 Rd7 Rh7 Ke8 Rh8 Kf7 Bd6 Rd8 Rh6 Rd7 Rh7 Ke8 Rh4 Kf7 Nc3 Nd4 Rh3\nNe6 Rh7 Ke8 Rh8 Kf7 Nxd5 Bxd5 Ke3 Kg7 Rh6 Kf7 Rh7 Ke8 Rh3 Kf7 Rh6 Kg7 Be5\nKf7) {-0.76/40 66} 61. Ra7 (Ra7 Kg8 Be5 Rc8 Rd7 Rd8 Ra7 Rc8 Rd7 Kf8 Rd6 Kf7\nKe1 Ne3 Nd4 Nxd4 Bxd4 Ng2 Kd2 Nxf4 Rf6) {+1.24/13 28} Rd8 (Rd8 Be5 Kg8 Ra6\nRc8 Bd6 Kh8 Bxb5 cxb5 Be5 Kg8 Rxe6 Nxf4 Re7 Nxe2 Kxe2 Kf8 Bd6 Re8 Re3 Kf7\nc6 Bc4 Kd2 Rxe3 Kxe3 f4 Kd4 Ke8 Bxf4 Kd8 Kc5 Bd3 Kd6 Kc8 Kd5 Kd8 Kc5 Bc4\nBh2 Kc8 Kd4 Kd8 Ke5 Kc8 Kf6 Bd3 Bf4 Kd8 Bd6 Kc8 Ke6 Bc4 Ke5 Be2 Kf4 Bd3 Ke3\nBc4 Ke4 Kd8 c7 Kc8) {-0.52/39 20} 62. Ra6 (Ra6 Rc8 Be5 Kg8 Ra7 Kf8 Rd7 Rd8\nBd6 Kg8 Rxd8 Nxd8 Nd4 Kf7 Bf1 Ke8 Bg2 Kd7 Nf3 Nf7 Ne5 Nxe5 Bxe5) {+1.15/14\n14} Rc8 (Rc8 Bxb5 cxb5 Be5 Kg8 Rxe6 Nxf4 Re7 Nxe2 Kxe2 Kf8 Bd6 Re8 Re3 Kf7\nc6 Bc4 Kd2 Rxe3 Kxe3 f4 Kd4 Ke8 Bxf4 Kd8 Kc5 Bd3 Kd6 Kc8 Kd5 Kd8 Kc5 Bc4\nBh2 Kc8 Kd6 Bd3 Kd5 Bc4 Kc5 Bd3 Bd6 Bc4 Kb6 Be2 Be5 Bc4 Bd4 Bd3 c7 Bc4 Be5\nBd3 Kc5) {-0.52/42 21} 63. Ra5 (Ra5 Kg7 Ra7 Kg8 Be5 Kf8 Rd7 Rd8 Bd6 Kg8\nRxd8 Nxd8 Nd4 Kf7 Bf1 Ke8 Bg2 Kd7 Nf3 Nf7 Bb8 Kc8 Be5) {+1.13/13 5} Re8\n(Re8 Be5 Kg8 Ra7 Rd8 Ra6 Rc8 Bxb5 cxb5 Rxe6 Nxf4 Re7 Nxe2 Kxe2 Kf8 Bd6 Re8\nRe3 Kf7 c6 Bc4 Kd2 Rxe3 Kxe3 f4 Kd4 Ke8 Bxf4 Kd8 Kc5 Bd3 Kd6 Kc8 Kd5 Kd8\nKc5 Bc4 Bh2 Bd3 Kb6 Kc8 Bf4 Bc4 Be5 Bd3 Bd4 Bc4 Kc5 Kd8 Kd6 Bd3 Be5 Bc4 c7\nKc8 Kc6 Bd3 Bf4) {-0.52/43 25} 64. Be5+ (Be5 Kg8 Ra6 Rc8 Ra7 Kf8 Rd7 Rd8\nBd6 Kg8 Rxd8 Nxd8 Nd4 Kf7 Bf1 Ke8 Bg2 Kd7 Nf3 Nf7 Bb8 Kc8 Be5) {+1.16/13\n10} Kg8 (Kg8 Ra7 Rd8 Ra6 Rc8 Bxb5 cxb5 Rxe6 Nxf4 Re7 Nxe2 Kxe2 Kf8 Bd6 Re8\nRe3 Kf7 c6 Bc4 Kd2 Rxe3 Kxe3 f4 Kd4 Ke8 Bxf4 Kd8 Kc5 Bd3 Kd6 Kc8 Kd5 Kd8\nKc5 Bc4 Bh2 Bd3 Kb6 Kc8 Bf4 Bc4 Be5 Bd3 Bh2 Be2 Bg3 Bd3 Kc5 Bc4 Kd6 Kd8 Be5\nKc8 Ke7 Bd3 c7 Be2 Bg3) {-0.52/45 11} 65. Ra6 (Ra6 Rc8 Ra5 Kf7 Ra6 Ke7 Ra7\nKf8 Rd7 Rd8 Bd6 Ke8 Rh7 Rd7 Rh8 Kf7 Rc8 Ne7 Ra8 Nd5 Rh8 Ne7 Ng1 Bd5)\n{+1.14/13 21} Rc8 (Rc8 Bxb5 cxb5 Rxe6 Nxf4 Re7 Nxe2 Kxe2 Kf8 Bd6 Re8 Re3\nKf7 c6 Bc4 Kd2 f4 Bxf4 Rxe3 Kxe3 Ke7 Kd4 Kd8 Ke5 Kc8 Kf6 Bd3 Bd6 Be4 Kg7\nBd3 Kf7 Kd8 Kf6 Be4 Bf4 Bd3 c7 Kd7 Ke5 Bf5 Kd5 Be6 Kc5 Bc4 Be5 Bd3 Bg3 Bf1\nBd6 Kc8 Kd5 Be2 Bf4 Bd3 Kc6 Bc4) {-0.52/45 23} 66. Ra5 (Ra5 Kf7 Ra6 Ke7 Ra7\nKf8 Rd7 Rd8 Bd6 Kg8 Rxd8 Nxd8 Nd4 Kf7 Bf1 Ke8 Bg2 Kd7 Nf3 Nf7 Ne5 Nxe5 Bxe5\nBc4) {+1.13/15 0} Re8 (Re8 Ra7 Rd8 Rb7 Kf8 Rh7 Kg8 Rh8 Kf7 Rh6 Ne7 Rh7 Ke8\nBd6 Rd7 Nc3 Nf8 Rh8 Kf7 Ke3 Rd8 Rh6 Nd5 Nxd5 Bxd5 Kd4 Bb3 Kc3 Kg7 Bc7 Re8\nBe5 Kf7 Rh8 Bd5 Rh3 Rd8 Rh6 Re8 Rh8 Ne6 Rh7 Kg8 Ra7 Nxg5 Rd7 Ne4 Kd4 Bf7\nRc7 Nf2 Rxc6 Nxd3) {-1.04/40 91} 67. Ra7 (Ra7 Rc8 Rd7 Kf8 Bd6 Kg8 Ra7 Rd8\nRa6 Rc8 Be5 Kf7 Ra7 Kg8 Rd7 Rd8 Rxd8 Nxd8 Nd4 Kf7 Be2 Ba2 Bf3 Bc4)\n{+1.14/12 10} Rd8 (Rd8 Rb7 Ba2 Bb8 Bb3 Bd6 Rc8 Ra7 Re8 Ra6 Rc8 Be5 Kf7 Ra7\nKg8 Rd7 Kf8 Rh7 Rd8 Rh8 Ke7 Rh2 Ke8 Rh6 Kf7 Rh4 Kf8 Bd6 Kg8 Rh6 Kf7 Be5 Rd7\nRh7 Ke8 Rh4 Kf8 Bd6 Kg7 Ng1 Bc4 Bxc4 bxc4 Nf3 Rd8 Kc2 Re8 Be5 Kf7 Rh7 Kf8\nRh8 Kf7 Rh4 Rd8 Rh7 Kg8 Rh6 Kf7) {-1.04/42 28} 68. Rb7 (Rb7 Kf8 Rh7 Re8 Bd6\nKg8 Rd7 Rc8 Ra7 Rd8 Ra6 Rc8 Be5 Ba2 Nc3 Nxc3 Kxc3 Bd5 Ra7 Re8 Rd7 Rd8 Re7)\n{+1.10/13 9} Kf8 (Kf8 Rh7 Kg8 Ra7 Rc8 Rd7 Kf8 Rh7 Rd8 Rh4 Kf7 Nc3 Re8 Bd6\nNd4 Rh7 Kg8 Rh3 Kf7 Ne2 Ne6 Rh7 Kg8 Rh4 Rd8 Nc3 Nxc3 Kxc3 Nf8 Rh6 Kg7 Be5\nKf7 Rh4 Re8 Rh1 Bd5 Rh3 Rd8 Rh8 Bb3 Be2 Bd5 Bd6 Kg7 Rh6 Nd7) {-0.84/37 5}\n69. Rh7 (Rh7 Re8 Ra7 Rc8 Rd7 Rd8 Bd6 Kg8 Rxd8 Nxd8 Nd4 Kf7 Bf1 Ke8 Bg2 Kd7\nNf3 Nf7 Bb8 Kc8 Be5 Kd7 Bh1) {+1.08/12 0} Kg8 (Kg8 Ra7 Rc8 Rd7 Kf8 Rh7 Re8\nBd6 Kg8 Rh4 Rd8 Nc3 Nxc3 Kxc3 Nf8 Rh6 Kg7 Be5 Kf7 Be2 Bd5 Bd6 Kg7 Rh3 Re8\nBe5 Kf7 Bf1 Rd8 Bd3 Re8 Rh8 Bb3 Rh2 Rd8 Bd6 Re8 Rh6 Kg7 Be5 Kf7 Rh8 Rd8 Be2\nBd5 Rh6 Re8 Bf1 Rd8 Bd6 Kg7 Rh2 Be4 Rh3 Kf7 Bd3 Bxd3 Kxd3 Re8) {-0.81/40\n18} 70. Ra7 (Ra7 Rc8 Rd7 Kf8 Ra7 Kg8 Rd7 Kf8 Kc1 Ke8 Rh7 Rd8 Bd6 Rd7 Rh8\nKf7 Bb1 Bc4 Ng1 Ndxf4) {+1.13/11 7} Rc8 (Rc8 Rd7 Kf8 Rh7 Rd8 Rh8 Kf7 Rh2\nKe8 Bd6 Kf7 Rh4 Ba2 Rh3 Re8 Rh7 Kg8 Rh4 Bb3 Rh6 Kg7 Be5 Kf7 Rh7 Kf8 Bd6 Kg8\nRh4 Ne7 Be5 Kf7 Rh7 Kg8 Rh3 Kf7 Nc3 Nd5 Rh7 Kg8 Rh6 Nexf4 Nxd5 Nxd5 Rxg6\nKf7 Rg7 Kf8) {-0.81/39 18} 71. Rd7 (Rd7 Kf8 Bd6 Kg8 Ra7 Rd8 Be5 Rc8 Rd7 Kf8\nKe1 Rd8 Rxd8 Nxd8 Nd4 Ne3 Kd2 Nc4 Bxc4 Bxc4 Bf6 Ne6) {+1.06/12 4} Kf8 (Kf8\nRh7 Re8 Bd6 Kg8 Rh4 Ne7 Rh6 Kg7 Ng1 Bd5 Be5 Kf7 Rh7 Kg8 Rh2 Kf7 Ne2 Bb3 Nc3\nNd5 Rh7 Kg8 Rh4 Kf7 Bd6 Kg8 Nxd5 Bxd5 Rh6 Kg7 Kc3 Nf8 Be5 Kf7 Rh3 Bb3 Rh4\nBa2 Rh1 Bd5 Rh6 Bb3 Bd6 Kg7 Bxf8 Rxf8) {-0.81/37 13} 72. Bd6+ (Bd6 Kg8 Ra7\nRd8 Ra6 Rc8 Be5 Kf7 Ra7 Kg8 Rd7 Kf8 Kc1 Ke8 Rh7 Rd8 Bd6 Rd7 Rh8 Kf7 Kd2 Rd8\nRh7) {+1.03/13 0} Kg8 (Kg8 Be5 Kf8 Rh7 Re8 Bd6 Kg8 Rh4 Ne7 Rh3 Nd5 Be5 Kf7\nRh4 Ne7 Ng1 Bd5 Rh7 Kg8 Rh3 Kf7 Ne2 Bb3 Nc3 Nd5 Rh7 Kg8 Rh4 Kf7 Bd6 Kg8\nNxd5 Bxd5 Rh6 Kg7 Kc3 Nf8 Be5 Kf7 Rh3 Bb3 Bf1 Bd5 Rh8 Rd8 Bd3 Re8 Rh6 Bb3)\n{-0.81/42 26} 73. Ra7 (Ra7 Rd8 Ra6 Rc8 Be5 Kf7 Ra7 Kg8 Rd7 Kf8 Rd6 Ke7 Ke1\nNe3 Kd2 Nd5 Kc1 Kf7 Kd2 Ke7) {+0.99/14 0} Re8 (Re8 Rd7 Rd8 Rb7 Rc8 Be5 Rd8\nRa7 Rc8 Rd7 Kf8 Rh7 Re8 Bd6 Kg8 Rh4 Ne7 Be5 Nd5 Nc3 Kf7 Bd6 Nd4 Rh7 Kg8 Rh3\nKf7 Ne2 Ne6 Rh6 Rd8 Rh4 Kg8 Ng1 Bc4 Bxc4 bxc4 Ne2 Ndc7 Rh6 Kg7 Kc2 Nb5 Be5\nKf7 Rh7 Ke8 Nc3 Ned4 Bxd4 Nxd4 Kb2 Ne6 Rh8 Ke7 Rxd8 Kxd8 Nxa4 Nxf4 Nb6 Ne6)\n{-0.81/43 15} 74. Ra6 (Ra6 Rc8 Be5 Kf7 Ra7 Kg8 Rd7 Kf8 Kc1 Rd8 Rxd8 Nxd8\nNd4 Ke8 Nxb3 axb3 Be2 Nf7 Bb8 Kd7 Kb2 Kc8 Bf3) {+0.96/12 16} Rc8 (Rc8 Be5\nKf7 Ra7 Kf8 Rd7 Rd8 Rh7 Re8 Bd6 Kg8 Rh4 Ne7 Nc3 Nd5 Be5 Kf7 Ne2 Ne7 Ng1 Bd5\nRh2 Rd8 Ke3 Re8 Ne2 Bb3 Nc3 Nf8 Rh8 Ne6 Rh6 Nf8 Kd2 Nd5 Nxd5 Bxd5 Kc3 Bb3\nBd6 Bd5 Bxf8 Rxf8 Rh7 Kg8 Rd7 Bg2 Bb1 Re8) {-0.81/41 20} 75. Be5 (Be5 Kf7\nRa7 Kg8 Rd7 Kf8 Kc1 Rd8 Bd6 Ke8 Rh7 Rd7 Rh8 Kf7 Kd2 Rd8 Rxd8 Nxd8 Nd4 Ke8\nBe5 Kd7 Nxf5 gxf5 Bxf5) {+0.93/13 0} Kf7 (Kf7 Ra7 Kg8 Rd7 Kf8 Rh7 Re8 Bd6\nKg8 Rh4 Ne7 Nc3 Nd5 Be5 Kf7 Ne2 Ne7 Rh2 Rd8 Rh7 Ke8 Rh6 Kf7 Nc3 Nf8 Ke3 Re8\nRh2 Rd8 Be2 Nd5 Nxd5 Bxd5 Rh8 Re8 Kd2 Rd8 Kc3 Re8 Bd3 Bb3 Bd6 Kg7 Rxf8)\n{-0.80/39 12} 76. Ra7+ (Ra7 Kg8 Rd7 Kf8 Kc1 Rd8 Bd6 Ke8 Rh7 Rd7 Rh8 Kf7 Kd2\nRd8 Rxd8 Nxd8 Nd4 Ke8 Bf1 Kd7 Bg2 Ne6 Nxe6 Kxe6 Bf3 Bc4 Be5) {+0.90/13 7}\nKg8 (Kg8 Rd7 Kf8 Rh7 Re8 Bd6 Kg8 Rh4 Ne7 Rh1 Nd5 Be5 Kf7 Rh4 Ne7 Rh7 Kg8\nRh3 Kf7 Ng1 Bd5 Rh6 Rd8 Ne2 Bb3 Rh1 Re8 Nc3 Nf8 Rh8 Nd5 Nxd5 Bxd5 Kc3 Bb3\nBf1 Bd5 Rh3 Rd8 Rh6 Re8 Bd3 Bb3 Rh8 Bd5 Bd6 Kg7) {-0.80/41 11} 77. Rd7 (Rd7\nKf8 Kc1 Rd8 Bd6 Ke8 Rh7 Rd7 Rh8 Kf7 Rc8 Ne7 Ra8 Nd5 Kd2 Ne7 Ra6 Bd5 Nc3 Bg2\nKe3 Nc8) {+0.90/13 0} Kf8 (Kf8 Rh7 Re8 Bd6 Kg8 Rh4 Ne7 Rh6 Kg7 Ng1 Bd5 Be5\nKf7 Rh7 Kg8 Rh2 Kf7 Ne2 Bb3 Nc3 Nf8 Rh8 Rd8 Bc7 Re8 Bd6 Kg7 Rh2 Nd5 Nxd5\nBxd5 Be5 Kf7 Kc3 Bb3 Rh8 Bd5 Rh6 Bb3 Bd6 Kg7 Rh3) {-0.80/38 14} 78. Rd6\n(Rd6 Ke7 Ke1 Ne3 Bf6 Kf7 Kd2 Nd5 Be5 Ke7 Ke1 Ne3 Nd4 Nxd4 Bxd4 Ng2 Kd2 Nxf4\nBf1 Bd5 Bf6 Kf7 Rd7 Kf8) {+0.87/13 7} Ke7 (Ke7 Nd4 Nxd4 Bxd4 Nxf4 Bf1 Kf7\nKe3 Nd5 Kf2 Bc4 Bg2 Re8 Bxd5 Bxd5 Rf6 Ke7 Rxg6 Kf8 Rf6 Kg8 Be3 Re4 Rxf5 Rc4\nKe2 Be4 Rf4 Bd5 Rxc4 Bxc4 Kf3 Kf7 Bc1 Ke6 Kf4 Kf7 Kf5 Bd3 Kg4 Kg6)\n{-0.31/29 13} 79. Ke1 (Ke1 Ne3 Kd2 Nd5 Ng1 Ndxf4 Nf3 Bd5 Bf6 Ke8 Ne5 Nxd3\nKxd3 Rc7 Nxg6 Rh7 Ne5 Rh3 Kd2 Rh2 Ke1 Rh1 Kf2 Rh2 Kg3 Rg2 Kh3 Nf4)\n{+0.81/14 0} Rd8 (Rd8 Rxc6 Nxb4 axb4 Rxd3 Ra6 Bc4 Bf6 Kf7 Ra7 Ke8 Nc3 Nd4\nRa8 Kf7 Bxd4 Rxd4 c6 Be6 Ra7 Kf8 c7 Rxb4 Ke2 Rxf4 Ra8 Kf7 Nxb5 Ke7 c8=Q\nBxc8 Rxc8 Rg4 Ra8 Rxg5 Nd4 Rg4 Rxa4 Re4 Kd3 Kf6 Ra6 Ke5 Ne2 g5 Ra3 Kf6 Ra8\nRe5 Rf8 Ke7) {-0.24/34 22} 80. Rxc6 (Rxc6 Nxb4 axb4 Rxd3 Ra6 Bc4 Nc1 Rd5 c6\nRxe5 fxe5 Kd8 Ne2 Kc7 Kf2 f4 Nc3 Nxg5 Kg2 Ne6 Kf3 g5 Kg4 Nd4 Ra7 Kxc6)\n{+0.89/14 1} Nxb4 (Nxb4 axb4 Rxd3 Ra6 Bc4 Bf6 Ke8 Nc3 Nd4 Ra8 Kf7 Bxd4 Rxd4\nc6 Be6 Ra7 Kf8 c7 Rxb4 Ke2 Rxf4 Ra8 Kf7 Nxb5 Ke7 c8=N Bxc8 Rxc8 Rg4 Ra8\nRxg5 Nd4 Rg2 Kf3 Rg4 Ke3 Re4 Kd3 Kf6 Ra6 Ke5 Rxa4 Kf4 Ne6 Ke5 Ra6 Rg4)\n{-0.22/36 10} 81. axb4 (axb4 Rxd3 Ra6 Bc4 Nc1 Rd5 c6 Rxe5 fxe5 Kd8 Ra7 Nxg5\nNd3 Ne6 Nb2 Bb3 Nd3 Bc4 Kd2 g5 Nc5 Nxc5 bxc5 f4 Rd7 Kc8 Rd6) {+0.89/16 0}\nRxd3 (Rxd3 Ra6 Bc4 Bf6 Ke8 Nc3 Nd4 Ra8 Kf7 Bxd4 Rxd4 c6 Be6 Ra7 Kf8 c7 Rxb4\nKe2 Rxf4 Ra8 Kf7 Nxb5 Ke7 c8=N Bxc8 Rxc8 Rg4 Ra8 Rxg5 Nd4 Rg3 Kf2 Rd3 Rxa4\nf4 Ke2 Re3 Kd2 Kf6 Ra6 Ke5 Nc6 Ke6 Ra5 g5 Nd4 Kf6 Nf5 Re8) {-0.13/36 14}\n82. Ra6 (Ra6 Bc4 Nc1 Rd5 Ne2 Rd3 Nc1 Rd5 c6 Rxe5 fxe5 Kd8 Ra7 Nxg5 Nd3 Ne6\nNb2 Bb3 Nd3 Bc4 Kd2 g5 Nc5 Nxc5 bxc5 f4 Rd7 Kc8 Rd6) {+0.76/16 0} Bc4 (Bc4\nNc1 Rd5 c6 Rxe5 fxe5 Kd8 Ne2 Kc7 Kf2 f4 Nc3 Nxg5 Ra7 Kxc6 Ra6 Kc7 Rxg6 Nf7\nRg7 Kc6 Rg6) {0.00/37 37} 83. Nc1 (Nc1 Rd5 Ne2 Rd3 Nc1 Rd5 c6 Rxe5 fxe5 Kd8\nRa7 Nxg5 Nd3 Ne6 Kd2 g5 Nb2 Bb3 Nd1 Kc8 Ne3 Nd4 Kc3 Nxc6 Rg7 f4 Nf5 f3 Nd6\nKd8 Rxg5) {+0.60/18 0} Rd5 (Rd5 c6 Rxe5 fxe5 Kd8 Ra7 Nxg5 Nd3 Ne6 Kf2 f4\nNb2 Bb3 Nd1 Kc8 Nc3 Nd4 Rg7 g5 Ne4 Nxc6 Nd6 Kd8 Nxb5 Nxe5 Rxg5 Nc6 Kf3 Nxb4\nKxf4 a3 Nd4 Bd5 Rg3 Kc7 Rxa3) {0.00/35 8} 84. Ne2 (Ne2 Rd3 Nc1 Rd5 c6 Rxe5\nfxe5 Kd8 Ra7 Nxg5 Nd3 Ne6 Nc5 Nxc5 bxc5 g5 c7 Kc8 Ra6 Kxc7 e6 Kd8 c6 a3 Ra8\nKe7 c7 Bxe6 Rxa3 Kd7 Ra6 Bc4 Rc6 Kc8 Rc5 Be6 Rxb5 Kxc7) {+0.56/19 1} Rd3\n(Rd3 Ng1 a3 Bf6 Ke8 c6 Nxf4 Ra8 Kf7 Ra7 Kf8 Ra8) {0.00/37 22} 85. Bf6+ (Bf6\nKe8 Nc3 Nxf4 c6 Rd8 Bxd8 Kxd8 Kd2 Kc7 Nb1 Ne6 Na3 Bf1 Ke3 Nxg5 Kf4 Ne6 Ke5\nNd8 Kd5 f4 Kc5 Ne6 Kd5 Nd8 Kc5 Ne6 Kd5 Nd8) {+0.55/20 3} Ke8 (Ke8 Nc3 Nxf4\nc6 Rd8 Bxd8 Kxd8 Kd2 Kc7 Nb1 Bd5 Ra7 Kxc6 Ra6 Kb7 Rd6 Kc7 Rf6 Be6 Kc2 Bd5)\n{0.00/36 20} 86. Nc3 (Nc3 Nxf4 c6 Rd8 Bxd8 Kxd8 Kd2 Kc7 Nb1 Nd5 Ra7 Kxc6\nRg7 Nf4 Na3 Kd5 Rd7 Kc6 Rd8 Bf1 Rc8 Kd5 Rc5 Ke4 Nxb5 Bxb5 Rxb5 Nd5 Rb8 a3)\n{+0.53/20 0} Nxf4 (Nxf4 c6 Rd8 Bxd8 Kxd8 Kd2 Kc7 Nb1 Bd5 Ra7 Kxc6 Ra6 Kc7\nRf6 Be6 Kc2 Bd5) {0.00/37 9} 87. c6 (c6 Rd8 Bxd8 Kxd8 Kd2 Kc7 Nb1 Nd5 Ra7\nKxc6 Rg7 Nf4 Na3 Kd5 Rd7 Kc6 Rd8 Bf1 Rc8 Kd5 Rc5 Ke4 Nxb5 Bxb5 Rxb5 Nd5 Rb8\na3) {+0.50/21 0} Rd8 (Rd8 Bxd8 Kxd8 Ra7 Nd3 Kd2 Nxb4 c7 Kd7 Nxa4 bxa4 Rxa4\nNa6 c8=Q Kxc8 Rxc4 Kd7 Rh4 Nc5 Ke3 Ne6 Rh7 Kd6 Rh6 Nxg5) {0.00/39 9} 88.\nBxd8 (Bxd8 Kxd8 Kd2 Kc7 Nb1 Nd5 Ra7 Kxc6 Rg7 Nf4 Na3 Kd5 Rd7 Kc6 Rd8 Bf1\nRc8 Kd5 Rc5 Ke4 Nxb5 Bxb5 Rxb5 Nd5 Rb8 a3) {+0.48/18 0} Kxd8 (Kxd8 Ra7 Nd3\nKd2 Nxb4 c7 Kd7 Nxa4 bxa4 Rxa4 Na6 c8=Q Kxc8 Rxc4 Kd7 Rh4 Nc5 Ke3 Ne6 Rh7\nKd6 Rh6 Nxg5) {0.00/42 11} 89. Kd2 (Kd2 Kc7 Nb1 Nd5 Ra7 Kxc6 Rg7 Nxb4 Rxg6\nKc5 Na3 Bd5 Rf6 Nc6 Nc2 b4 Rxf5 b3 Na3 Nd4 Rf4 b2 g6 Nb5 Kc2 Nxa3 Kxb2 Nc4\nKa1 Ne3 g7 Nc2) {+0.46/17 0} Kc7 (Kc7 Nb1 Nd5 Na3 Nxb4 Ra7 Kxc6 Nxc4 bxc4\nKc3 Nd5 Kxc4 Nc7 Rxa4 Kd7 Kb4 Ne6 Ra7 Kd6 Ra6 Ke5 Kc4 Nxg5) {0.00/41 9} 90.\nNb1 (Nb1 Nd5 Ra7 Kxc6 Rg7 Nxb4 Rxg6 Kc5 Na3 Bd5 Rf6 Nc6 Nc2 b4 Rxf5 b3 Ne3\nNe7 Re5 a3 Kc1 Kd6 Rxe7 Kxe7 Nxd5 Kf7 Nf4 Kg7) {+0.47/18 0} Nd5 (Nd5 Na3\nNxb4 Ra7 Kxc6 Nxc4 bxc4 Kc3 Nd5 Kxc4 Nc7 Rxa4 Kd7 Kb4 Ne6 Ra7 Kd6 Ra6 Ke5\nKc4 Nxg5) {0.00/41 8} 91. Ra7+ (Ra7 Kxc6 Rg7 Nxb4 Rxg6 Kc5 Na3 Bd5 Rf6 Nc6\nNc2 b4 Ne3 Be4 g6 a3 Nc2 a2 Na1 Ne7 g7 Bd5 Kc2 Kd4 Kb2 Ke5 Rf8 f4 Nc2 b3\nNb4 Bg8 Nd3) {+0.48/18 8} Kxc6 (Kxc6 Ra6 Nb6 Na3 Bf1 Ra7 Nc4 Nxc4 Bxc4 Ra6\nKd5 Rxg6 f4 Ra6 Ke4 g6 f3 g7 f2 Rf6 a3 Rxf2 a2 Re2 Bxe2 Kxe2) {0.00/40 8}\n92. Rg7 (Rg7 Nxb4 Rxg6 Kc5 Na3 Bd5 Rf6 Nc6 Nc2 b4 Ne3 Be4 g6 a3 Nc2 a2 Na1\nNe7 Re6 Ng8 g7 Bd5 Re5 f4 Rf5 f3 Kc2 Kd4 Kb2 Ke4 Rf8 b3) {+0.46/18 0} Nxb4\n(Nxb4 Rxg6 Kc5 Rf6 Nd5 Rxf5 b4 Kc2 Kd4 Rf3 Ne3 Kd2 a3 Nxa3 bxa3 Rxe3)\n{0.00/40 12} 93. Na3 (Na3 Bd5 Rxg6 Kc5 Rf6 Nc6 Nc2 b4 Ne3 Be4 g6 a3 Nc2 a2\nNa1 Ne7 Re6 Ng8 Re8 Bd5 Rf8 Ne7 g7 b3 Kc3 Kd6 Nxb3 Bxb3 Kxb3 a1=Q g8=Q)\n{+0.41/17 3} Bd5 (Bd5 Rxg6 Kc5 Rf6 Nc6 Nxb5 Kxb5 Rxf5 Kc5 Rxd5) {0.00/42\n10} 94. Rxg6+ (Rxg6 Kc5 Rf6 Nc6 Nc2 b4 Ne3 Be4 g6 a3 Nc2 a2 Na1 Ne7 g7 Bd5\nKd3 Bg8 Kc2 Kd5 Kb2 Ke5 Rf8 f4 Nc2 b3 Ne1 Ke4 Ng2) {+0.28/18 0} Kc5 (Kc5\nRf6 Nc6 Nc2 b4 g6 b3 Rxf5 bxc2 Rxd5 Kxd5) {0.00/39 9} 95. Rf6 (Rf6 Nc6 Nc2\nb4 Ne3 Be4 g6 a3 Nc2 a2 Na1 Ne7 g7 Bd5 Kd3 Bc4 Kc2 Kd4 Kb2 Ke5 Rf8 f4 Nb3\nBd5 Nc1 f3 Nxa2 Nf5) {+0.27/16 0} Nc6 (Nc6 Nc2 b4 Rxf5 b3 g6 bxc2 Rxd5\nKxd5) {0.00/41 7} 96. Nc2 (Nc2 b4 Ne3 Be4 g6 a3 Nc2 a2 Na1 Ne7 g7 Bd5 Kd3\nBg8 Kc2 Kd5 Kb2 Ke5 Rf8 f4 Nc2 b3 Ne1 Ke4 Re8 Kf5) {+0.27/16 0} b4 (b4 g6\nb3 Rxf5 bxc2 Rxd5 Kxd5) {0.00/41 16} 97. Ne3 (Ne3 Be4 g6 a3 Nc2 a2 Na1 Ne7\ng7 Bd5 Kd3 Bg8 Kc2 Kd5 Kb2 Ke5 Rf8 b3 Nxb3 Bxb3 Re8 Kf6 Rxe7 Kxe7 Kxb3)\n{+0.22/17 0} Be4 (Be4 g6 Ne7 Re6 Nxg6 Rxg6 a3 Rg1 b3 Kc3 b2 Kb3 b1=R Rxb1\nBxb1) {0.00/37 8} 98. g6 (g6 a3 Nc2 a2 Na1 Ne7 Kc1 Bd5 Kb2 Kd4 Nc2 Ke5 Rb6\nf4 Nxb4 Bg8 Nd3 Kf5 g7 f3 Ra6 Kg5 Nf2 Nf5) {+0.19/16 0} a3 (a3 Nc2 Ne7 Re6\nNxg6 Rxg6 b3 Nxa3 Kb4 Ra6 b2 Ra7 Kb3 Ke2 b1=Q Nxb1) {0.00/38 6} 99. Nc2\n(Nc2 a2 Na1 Ne7 g7 Bd5 Kd3 Bg8 Kc2 Kd4 Kb2 Ke5 Rf8 b3 Ka3 f4 Kb2 Bd5 Nxb3\nf3 Nd2 f2) {+0.18/15 0} Ne7 (Ne7 Re6 Nxg6 Rxg6 b3 Nxa3 Kb4 Ra6 b2 Ra7 Kb3\nKe2 b1=Q Nxb1) {0.00/40 5} 100. g7 (g7 a2 Na1 Bd5 Kd3 Bc4 Kc2 Kd4 Kb2 Ke5\nRf8 b3 Kc3 Be6 Nxb3 Bxb3 Kxb3) {+0.22/10 4} a2 (a2 Na1 Bd5 Kc2 Kd4 Ra6 Ke5\nNb3 f4 Rxa2 Bxb3 Kxb3 Kf6 Rg2 Ng8 Kb2 f3 Rg3 f2 Rf3 Kxg7) {0.00/39 8} 101.\nNa1 (Na1 Bd5 Kd3 Bg8 Kc2 Kd4 Kb2 Ke5 Rf8 b3 Rb8 Kf6 Nxb3 Kxg7 Kxa2 f4 Kb2\nf3 Nd2 Bd5) {+0.07/12 0} Bd5 (Bd5 Kc2 Kd4 Ra6 f4 Nb3 Ke5 Rxa2 Nf5 Ra5 Nxg7\nKd3 Nf5 Nd2 Kd6 Rb5 f3 Rxb4 f2 Rf4 f1=Q Nxf1) {0.00/41 4} 102. Kc2 (Kc2 Kd4\nKb2 Ke5 Rb6 f4 Nc2 f3 Ne3 Be6 Rxb4 f2 Ra4 Kf6 Rf4 Kxg7 Rxf2 Kg6 Ng2 Kg5)\n{+0.04/11 1} b3+ (b3 Kb2 Kd4 Ra6 Ke5 Nxb3 Bxb3 Kxb3 a1=Q Rxa1) {0.00/42 5}\n103. Kc3 (Kc3 Bc4 Kb2 Kd4 Rb6 Ke5 Nxb3 f4 Nd2 Bd5 Ra6 f3 Nxf3 Bxf3 Ra7 Ng8)\n{+0.31/8 1} Bc4 (Bc4 Rf8 Bd5 Kb2 Kd4 Re8 Ng8 Rb8 Ke5 Nxb3 Bxb3 Rxb3 a1=Q\nKxa1) {0.00/43 6} 104. Kb2 (Kb2 Kd4 Rb6 Ke5 Nxb3 f4 Nd2 Bd5 Rb5 Ke6 Rb4 Kf7\nRxf4 Kxg7 Ne4 a1=Q Kxa1) {+0.31/10 0} Kd4 (Kd4 Rf8 Ke5 g8=Q Nxg8 Re8 Kf6\nRe1 f4 Nxb3 f3 Nd2 f2 Ne4 Kg6 Nxf2 a1=Q Rxa1) {0.00/46 5} 105. Rb6 (Rb6 Ke5\nNxb3 Bxb3 Rxb3 Kf6 Rb7 Ng8 Kxa2 Kg6 Kb3 f4 Kc4 f3 Kd3 f2 Ke2 Nh6) {+0.27/11\n0} Ke5 (Ke5 Nxb3 Bxb3 Rxb3 a1=Q Kxa1) {0.00/48 5} 106. Nxb3 (Nxb3 Bxb3 Rxb3\nKf6 Rb7 Ng8 Kxa2 Kg6 Kb3 f4 Kc4 f3 Kd3 Nh6 Ke3 Nf5 Kxf3) {+0.23/11 0} Bxb3\n(Bxb3 Rxb3 a1=Q Kxa1) {0.00/53 7} 107. Rxb3 (Rxb3 Kf6 Rb7 Ng8 Kxa2 Kg6 Kb3\nf4 Kc4 f3 Kd3 Nh6 Ke3 Nf5 Kxf3 Nxg7) {+0.17/10 0} a1=Q+ (a1=Q Kxa1)\n{0.00/105 4} 108. Kxa1 (Kxa1 Kf6 Rb7 Ng8 Kb2 Kg6 Kc3 f4 Kd4 f3 Ke3 Nh6 Kxf3\nKh7 Kf4) {+0.25/8 1} Kf6 (Kf6 Rb7 Ng8 Kb2 Ke6 Kb3 Kf6 Kb4 Nh6 Kc3 Ng8 Ra7\nKg6 Kd4 Kf6 Kd3 Kg6 Ke2 Nh6 Rb7 Kf6 Kf3 Kg6 Kf2 Kf6 Rd7 Kg6 Kg2 Ng8 Kf3 Nh6\nRb7 Ng8 Kf4 Kf6 Ra7 Nh6 Rd7 Ng8 Kf3 Kg6 Kg2 Nf6 Rb7 Ng8 Kg1 Nf6 Kf2 Ne4 Kf3\nNf6 Ra7 Ng8 Kf4 Nh6 Ke5 Nf7 Ke6 Nh6 Rb7 f4 Kd5 f3) {0.00/55 16} 109. Rb7\n(Rb7 Ng8 Kb2 Kg6 Kc3 Nh6 Kd4 Kf6 Kd5 Ng8 Ra7 Nh6 Rd7 Ng8 Kd6 Nh6) {+0.26/11\n0} Ng8 (Ng8 Kb2 Ke6 Kb3 Kf6 Kb4 Nh6 Kc3 Ng8 Ra7 Nh6 Kd3 Kg6 Kd4 Kf6 Rc7 Kg6\nKe5 Nf7 Kd5 Nh6 Rb7 Kf6 Rd7 Kg6 Ke5 Ng4 Kd4 Nh6 Ke3 Ng8 Rb7 Nh6 Kf4 Kf6 Rd7\nKg6 Rc7 Kf6 Kg3 Ng8 Ra7 Kg6 Kh4 Nf6 Kh3 Kh7 Kg3 Kg6 Kf3 Ng8 Rb7 Nf6 Ke3 Ng8\nRd7 Nh6 Ke2 Ng8 Kd1 Nf6 Kc2 f4 Ra7 Kh7) {0.00/58 8} 110. Kb2 (Kb2 Kg6 Kc3\nNh6 Kd4 Kf6 Kd5 Ng8 Rc7 Nh6 Rb7 Kg6 Ke6 Ng8 Rf7 Nh6) {+0.31/10 0} Nh6 (Nh6\nKc3 Ng8 Ra7 Kg6 Kd4 Kf6 Kd3 Kg6 Ke3 Nh6 Kd4 Kf6 Rc7 Kg6 Ke5 Nf7 Kd5 Nh6 Rb7\nKf6 Rd7 Ng8 Kc4 Nh6 Kd3 Ng8 Rc7 Kg6 Ke3 Nf6 Kd4 Ng8 Ke5 Nh6 Rd7 Nf7 Kf4 Nh6\nKf3 Ng8 Rc7 Nh6 Rb7 Ng8 Kg3 Nf6 Kf2 Ng8 Rc7 Nf6 Ra7 Ng8 Kg2 Nh6 Kg3 Ng8 Kh3\nNf6 Kg2 Kh7 Kg3 Nh5 Kh4 Nxg7 Kg5 Kg8) {0.00/56 3} 111. Kc3 (Kc3 Kg6 Kd4 Kf6\nRc7 Kg6 Ke5 Ng8 Ra7 Nh6 Ke6 Ng8 Ra1 Kxg7) {+0.34/9 0} Ng8 (Ng8 Ra7 Kg6 Kd4\nNh6 Kd3 Ng8 Rd7 Kh7 Kd4 Nh6 Kd5 Kg6 Rc7 Kf6 Kd4 Ng8 Kc4 Nh6 Kd5 Kg6 Rd7 Kf6\nKd4 Kg6 Ra7 Ng8 Ke5 Nh6 Kf4 Kf6 Rc7 Ng8 Rd7 Nh6 Ke3 Kg6 Kf3 Kf6 Rb7 Kg6 Ra7\nKf6 Kf2 Ng8 Kg2 Kg6 Kh3 Nh6 Rc7 Ng8 Rb7 Kf6 Rd7 Kg6 Rc7 Kh7 Rb7 Kg6 Kh2 Nh6\nRd7 Ng4 Kh3 Nf6 Kh4 f4 Ra7 f3) {0.00/50 3} 112. Kd4 (Kd4 Nh6 Ke3 Kg6 Kf4\nNg8 Ke5 Nh6 Rd7 Ng8 Ke6 Nh6 Rc7 Ng8) {+0.40/8 3} Kg6 (Kg6 Ke5 Nh6 Rd7 Nf7\nKe6 Nh6 Kd5 Kf6 Ra7 Kg6 Rc7 Kf6 Rd7 Kg6 Kd4 Kf6 Ke3 Kg6 Kf3 Ng8 Kf2 Nf6 Rb7\nNg8 Kg2 Nh6 Kg3 Ng8 Kh3 Nf6 Kg2 Kh7 Kg3 Ng8 Kf4 Kg6 Ra7 Nh6 Kf3 Ng8 Ke3 Nf6\nKd4 Ng8 Rb7 Nf6 Rc7 Ng8 Kc3 Nf6 Ra7 Ng8 Rd7 Nh6 Rb7 f4 Kd3 f3) {0.00/53 53}\n113. Ke5 (Ke5 Nh6 Ra7 Ng8 Ke6 Nh6 Ra1 Kxg7 Rg1 Kh7 Kf6 Ng8 Kf7 Nh6 Ke6)\n{+0.42/9 1} Nh6 (Nh6 Rd7 Nf7 Ke6 Nh6 Kd5 Kf6 Ra7 Kg6 Rc7 Kf6 Rd7 Kg6 Kd4\nKf6 Kd3 Kg6 Ke2 Kh7 Kf3 Ng8 Kg3 Nf6 Rf7 Ng8 Rb7 Kg6 Kf4 Nf6 Ke5 Ng4 Kd4 Nf6\nKc4 Ng8 Kc3 Kf6 Ra7 Nh6 Kd3 Kg6 Rd7 Ng8 Kd4 Nh6 Rc7 Ng8 Ke5 Nh6 Kf4 Ng8 Ra7\nNf6 Kf3 Ng8 Kg2 Nh6 Kf2 Ng8 Rd7 Nf6 Rb7 Ng8 Kg3 Nf6 Kh4 f4 Ra7 f3) {0.00/55\n2} 114. Rc7 (Rc7 Ng8 Ra7 Nh6 Ke6 f4 Ra1 Kxg7 Rg1 Kf8 Kf6 Ng8 Ke6 Nh6 Rg6)\n{+0.39/9 2} Ng4+ (Ng4 Kd4 Nh6 Rd7 Ng8 Ke5 Nh6 Kf4 Ng8 Rb7 Nf6 Ke5 Ng4 Kd4\nNf6 Re7 Ng8 Rd7 Nf6 Rb7 Ng8 Ke5 Nh6 Rd7 Ng4 Kd5 Nh6 Ra7 Kf6 Kd4 Kg6 Kd3 Ng8\nKc3 Nh6 Kd2 Ng8 Ke1 Nf6 Kf2 Ng4 Kg3 Nf6 Kh3 Ng8 Kg2 Nh6 Kg3 Kf6 Rd7 Kg6 Rc7\nKh7 Ra7 Ng8 Rd7 Nh6 Kh3 f4 Kg2 Nf5) {0.00/51 4} 115. Ke6 (Ke6 Nh6 Rf7 Ng8\nRd7 Nh6 Ra7 f4 Ke5 f3 Ke6 f2 Ra1 Kxg7 Rf1) {+0.38/8 0} Nh6 (Nh6 Kd5 Kf6 Rb7\nKg6 Kd4 Ng8 Ke5 Nh6 Rd7 Nf7 Kd5 Nh6 Kd4 Ng8 Rb7 Nf6 Rc7 Ng8 Kc3 Kf6 Kd2 Kg6\nRb7 Kh7 Ke3 Kg6 Ra7 Nf6 Kd3 Ng8 Kc2 Kf6 Kb3 Kg6 Kb2 Nh6 Kb1 f4 Kc2 f3 Kd1\nKh7) {0.00/38 31} 116. Rf7 (Rf7 Ng8 Rc7 Nh6 Ra7 f4 Ke5 f3 Ke4 f2 Ra1 Kxg7\nKe3 f1=B Ra7 Kf6) {+0.32/10 0} Ng8 (Ng8 Rxf5 Kxg7 Rf7 Kg6 Rf1 Kg7 Rg1 Kf8\nRg6 Ne7 Rf6 Ke8 Rf3 Ng8 Rf1 Nh6 Kf6 Ng8 Ke5 Nh6 Rb1 Kf7 Rb7 Kg6 Ke6 Ng8 Rf7\nNh6 Ra7 Ng8 Ra8 Kg7 Re8 Nh6 Re7 Kg6 Kd5 Kg5 Ra7 Kf4 Ra1 Kg5 Kd4 Ng4 Ra7 Kf5\nRf7 Kg5 Kc3 Nh6 Kb2 Nxf7 Kb1 Kh4) {0.00/40 2} 117. Rc7 (Rc7 Nh6 Rb7 f4 Rf7\nNg8 Rxf4 Kxg7 Rg4 Kf8 Rg6 Ne7 Rf6 Ke8 Rh6 Ng8) {+0.28/9 1} f4 (f4 Rf7 f3\nRxf3 Kxg7 Kf5 Nh6 Ke5 Kg6 Rg3 Kf7 Kd5 Ng8 Rf3 Ke7 Re3 Kf8 Ke5 Kg7 Rg3 Kf8\nKe6 Nh6 Kf6 Ng8 Kg5 Kf7 Rg2 Ne7 Rd2 Ke6 Re2 Kf7 Kf4 Kf6 Ke4 Ng8 Rg2 Kf7 Kd4\nNe7 Rg1 Kf6 Rf1 Ke6 Kc5 Ng6 Re1 Kf5 Kd4 Kf6 Ke4 Ne7 Rf1 Kg7 Rg1 Kf7 Ke5 Ng8\nRf1 Kg7 Kf5 Ne7 Ke4 Ng8 Ke5 Nh6 Rg1 Kf7 Kf4 Kf6 Ke4 Kf7 Ke3 Nf5 Kf4 Nd4 Ke4\nNe6 Ke5 Nd8 Kd5 Ke7 Rg7 Nf7 Rg3 Kf6 Rf3 Kg6 Ke6 Ng5 Ke7 Nxf3 Kd8 Kf6)\n{0.00/46 3} 118. Rf7 (Rf7 f3 Rxf3 Kxg7 Rg3 Kf8 Rg6 Ne7 Rf6 Ke8 Rh6 Ng8 Rh8\nKf8 Rh7 Ke8) {+0.19/8 1} f3 (f3 Rf8 Kxg7 Rf7 Kh6 Rxf3 Kg7 Rg3 Kf8 Rg1 Nh6\nKf6 Ng8 Kf5 Kf7 Re1 Nh6 Kf4 Ng8 Kg5 Kf8 Re6 Kf7 Rb6 Kg7 Rb7 Kf8 Rd7 Ne7 Kf4\nNg8 Kf5 Ne7 Kg5 Ng8 Rd8 Kf7 Rb8 Kg7 Ra8 Kf7 Kf4 Kg7 Ra7 Kf6 Ra6 Kf7 Ke5 Ne7\nRa3 Ng6 Kf5 Ne7 Kg5 Ng8 Rf3 Ke6 Rf1 Ne7 Rg1 Nc6 Re1 Kd6 Kf4 Kd5 Re8 Kd6 Ke4\nNe7 Rd8 Ke6 Rb8 Nd5 Kd4 Ne7 Rb6 Kf7 Ke3 Nd5 Kd2 Nxb6 Ke1 Ke6) {0.00/49 0}\n119. Rxf3 (Rxf3 Kxg7 Rg3 Kf8 Rg6 Ne7 Rf6 Ke8 Rh6 Ng8 Rh8 Kf8 Rh7 Ke8 Ra7)\n{+0.13/8 0} Kxg7 (Kxg7 Ke5 Kg6 Rg3 Kf7 Rd3 Nh6 Rf3 Ke7 Rg3 Nf7 Ke4 Nd6 Kd5\nNe8 Ke5 Kd7 Rg6 Nc7 Rg5 Ne6 Rh5 Nc7 Rh7 Kc6 Kf5 Nd5 Ke6 Nc7 Ke5 Nb5 Rh6 Kc5\nRh1 Kc6 Rf1 Nc7 Rc1 Kd7 Rd1 Kc6 Rd6 Kc5 Rd8 Kc6 Rc8 Kb7 Rg8 Kc6 Rg4 Nb5 Rc4\nKd7 Kf6 Kd6 Rf4 Na3 Rf3 Nb5 Rd3 Kc6 Rd8 Nc7 Kg6 Ne6 Kf5 Nxd8 Kf6 Kd7)\n{0.00/51 3} 120. Rg3+ (Rg3 Kf8 Rg6 Ne7 Rf6 Ke8 Rh6 Ng8 Rh8 Kf8 Rh7 Ke8 Ra7\nKf8 Rf7) {+0.13/7 0} Kf8 (Kf8 Rf3 Kg7 Ke5 Kg6 Rg3 Kf7 Rd3 Nh6 Rf3 Ke7 Rg3\nNf7 Ke4 Nd6 Kd5 Ne8 Ke5 Kd7 Rg6 Nc7 Rg5 Ne6 Rh5 Nc7 Rh7 Kc6 Kf5 Nd5 Ke6 Nc7\nKe5 Nb5 Rh6 Kc5 Rh1 Kc6 Rf1 Nc7 Rc1 Kd7 Rd1 Kc6 Ke4 Nb5 Rg1 Nc7 Rg6 Kd7 Kd3\nNe8 Kd4 Nc7 Rb6 Ne8 Kd5 Nc7 Kc5 Ne6 Kc4 Nd8 Kd3 Ke7 Kd4 Ne6 Kd5 Nc7 Kc6 Ne8\nKc5 Kd7 Rb7 Ke6 Kd4 Nf6 Rb6 Kf5 Rb5 Ke6 Ra5 Ng4 Ra8 Kf5 Re8 Kg6 Kd5 Nf6 Ke6\nNxe8 Ke5 Kg5) {0.00/54 2} 121. Rg6 (Rg6 Ne7 Rf6 Ke8 Rh6 Ng8 Rh8 Kf8 Rh7 Ke8\nRa7 Kf8 Rf7 Ke8) {+0.18/7 1} Ne7 (Ne7 Rg3 Ng8 Rf3 Kg7 Ke5 Kg6 Rg3 Kf7 Rd3\nNh6 Rf3 Ke7 Rg3 Nf7 Ke4 Nd6 Kd5 Ne8 Ke5 Kd7 Rg6 Nc7 Rg5 Ne6 Rh5 Nc7 Rh7 Kc6\nKf5 Nd5 Ke6 Nc7 Ke5 Nb5 Rh6 Kc5 Rh1 Kc6 Rf1 Nc7 Rc1 Kd7 Kd4 Kd6 Ke4 Nb5 Kf5\nKd5 Rd1 Kc5 Ke6 Na3 Rd7 Nb5 Rg7 Kc6 Rg6 Nd4 Ke5 Kc5 Kf4 Nc6 Ke4 Nb4 Rg1 Nc6\nRd1 Na7 Rd3 Nb5 Rd8 Nd6 Ke5 Nc4 Ke6 Ne3 Rd7 Kc6 Kf6 Kxd7 Kg5 Ke6) {0.00/56\n2} 122. Rf6+ (Rf6 Ke8 Rh6 Ng8 Rg6 Kf8 Kd6 Kf7 Rg2 Nh6 Ke5 Ng8) {+0.15/7 1}\nKe8 (Ke8 Kd6 Nc8 Kc5 Ke7 Ra6 Kf7 Ra8 Ne7 Kd4 Ke6 Ra6 Kf5 Rb6 Ng6 Kd5 Nf4\nKd6 Ke4 Ra6 Nd3 Ra8 Kf5 Ra5 Kf6 Kd5 Nf4 Ke4 Ne6 Rf5 Ke7 Ke5 Nf8 Rf1 Nd7 Kd4\nNf6 Ra1 Kf8 Ke5 Ng8 Ra7 Ne7 Rd7 Ng8 Kf5 Ne7 Kg5 Ng8 Rd8 Kg7 Kf5 Ne7 Ke6 Ng8\nRc8 Nh6 Ke5 Ng4 Kf4 Nh6 Rc7 Kf6 Rc6 Kg7 Kg5 Ng8 Re6 Kf8 Rb6 Kf7 Rb7 Ke6 Rb1\nKf7 Rg1 Ke7 Rf1 Kd6 Re1 Kd7 Rd1 Ke7 Kf5 Kf7 Kf4 Kg7 Rf1 Nh6 Ke5 Ng4 Kf5 Ne3\nKe4 Nxf1 Ke5 Ng3) {0.00/50 2} 123. Rh6 (Rh6 Ng8 Rg6 Kf8 Kd6 Kf7 Ke5 Kxg6)\n{+0.14/7 1} Ng8 (Ng8 Rh8 Kf8 Rh7 Ke8 Ra7 Kf8 Ra8 Kg7 Rc8 Nh6 Rc7 Kg6 Ke5\nNg4 Kf4 Nh6 Ke4 Kf6 Kd4 Kf5 Rb7 Kf4 Kd3 Nf5 Rb5 Ng7 Kc4 Ke4 Rb1 Ne6 Ra1 Ke5\nRe1 Kf5 Kd5 Nf4 Kd6 Kf6 Ra1 Kf5 Ra4 Nd3 Ra5 Kf6 Kd5 Nf4 Ke4 Ne6 Rf5 Ke7 Ke5\nNf8 Rf1 Nd7 Kd4 Ke6 Ra1 Ke7 Ra7 Ke6 Ke4 Nc5 Ke3 Kd5 Kf4 Ne6 Kg4 Ke5 Ra5 Kd4\nKf5 Nc5 Ra7 Kd5 Rh7 Ne4 Re7 Nd6 Kf4 Kd4 Rc7 Nc4 Rc6 Ne3 Re6 Nd5 Kf5 Ne3\nRxe3 Kxe3 Ke5 Kf3) {0.00/51 2} 124. Rh7 (Rh7 Kf8 Rf7 Ke8 Ra7 Kf8 Rf7 Ke8\nRf3 Nh6 Ra3 Kf8) {+0.11/7 1} Kf8 (Kf8 Ra7 Nh6 Ra3 Kg7 Rg3 Kf8 Rf3 Kg7 Ke5\nNg8 Ke4 Nf6 Kf5 Ng8 Rg3 Kf8 Ke4 Nf6 Kd3 Kf7 Kd4 Ng8 Rg1 Nf6 Re1 Ng8 Ra1 Nh6\nRa7 Kg6 Ra6 Kg5 Kc5 Nf5 Kd5 Nh6 Ra7 Kf5 Kc5 Ng4 Rf7 Ke4 Re7 Kf5 Kd6 Kg5 Rg7\nKf4 Rf7 Ke4 Rf8 Ne3 Re8 Kd4 Ke6 Nc4 Rd8 Ke3 Kd5 Nd2 Rd7 Nf3 Rf7 Nh2 Re7 Kf4\nRe4 Kf5 Re6 Ng4 Ra6 Nf6 Kc4 Kg6 Kd3 Kg7 Ra8 Kh6 Rf8 Nd7 Ke3 Nxf8 Kf2 Ne6)\n{0.00/52 2} 125. Rf7+ (Rf7 Ke8 Rg7 Kf8 Rg6 Ne7 Rf6 Ke8 Rh6 Ng8 Rg6 Kf8 Rg4)\n{+0.11/8 0} Ke8 (Ke8) {0.00/1 0} 126. Rf2 (Rf2 Nh6 Ra2 Kf8 Ra7 Ng8 Rh7 Ke8\nRa7 Kf8 Rf7 Ke8 Rf4) {+0.15/7 1} Nh6 (Nh6 Kd5 Ke7 Rf1 Nf7 Rf3 Nh6 Ke5 Ng8\nRe3 Kf7 Rg3 Kf8 Kf4 Ne7 Rg1 Ng8 Rd1 Kf7 Rf1 Kf6 Kg3 Ke7 Ra1 Ke6 Kf4 Kf6 Ra6\nKf7 Kg3 Ne7 Ra7 Ke6 Ra8 Ke5 Kf3 Ke6 Ra6 Ke5 Ra5 Nd5 Ra1 Kd4 Ra4 Ke5 Re4 Kf5\nRe8 Nf6 Rf8 Ke5 Ra8 Ke6 Kf4 Nd5 Ke4 Nf6 Kf3 Kd6 Kf4 Nd5 Kf5 Ne7 Ke4 Nc6 Rg8\nNe7 Rg1 Ke6 Rf1 Nc6 Rd1 Nb4 Rc1 Kd6 Rg1 Nc6 Rg6 Kd7 Ke3 Ne5 Kf4 Nxg6 Ke4\nNe7) {0.00/53 6} 127. Ra2 (Ra2 Kf8 Ra7 Ng8 Kf5 Ne7 Ke6 Ng8 Rf7 Ke8 Rf4 Nh6\nRf6 Ng8) {+0.16/8 1} Kf8 (Kf8 Rg2 Ng8 Kd6 Nh6 Kc5 Ng8 Rb2 Kf7 Rb1 Ke6 Kd4\nNe7 Ke4 Nd5 Ra1 Nf6 Kd4 Nd7 Ra6 Ke7 Rg6 Nf6 Ke5 Nd7 Kf4 Kf7 Ra6 Ke7 Ra7 Kd6\nRb7 Ke6 Rb1 Kd6 Rd1 Ke6 Rg1 Nc5 Re1 Kd5 Rd1 Ke6 Rd8 Ke7 Rg8 Ke6 Ke3 Ke5 Rg5\nKd6 Kd4 Ne6 Kd3 Nxg5 Kc4 Ke5) {0.00/50 3} 128. Rg2 (Rg2 Ng8 Rg6 Ne7 Rg5 Ng8\nRg1 Nh6 Kf6 Ng8 Kg5 Kf7 Rf1) {+0.14/8 0} Ng8 (Ng8 Kd6 Nh6 Kc5 Ng8 Rb2 Kf7\nRb1 Ke6 Kd4 Ne7 Ke4 Nd5 Ra1 Nf6 Kd4 Nd7 Ra6 Ke7 Rg6 Nf6 Kd3 Kf7 Rg5 Nd7 Ke4\nKe6 Rg6 Ke7 Rc6 Nf6 Kf5 Nd7 Re6 Kf7 Rd6 Nf8 Rd1 Ng6 Rd7 Ne7 Ke5 Kf8 Rd1 Kg7\nRg1 Kf8 Rf1 Kg7 Rf3 Ng8 Ke4 Nf6 Kf5 Ng8 Rg3 Kf8 Kg5 Ke7 Re3 Kd6 Re1 Ne7 Kf6\nNd5 Kg6 Ne7 Kf7 Nf5 Re4 Kd5 Kf6 Kxe4 Kg5 Nd4) {0.00/51 1} 129. Rg6 (Rg6 Ne7\nRf6 Ke8 Rh6 Ng8 Rh8 Kf8 Rh5 Kg7 Rg5 Kf8) {+0.16/7 0} Ne7 (Ne7 Rg3 Ng8 Rf3\nKg7 Ke5 Kg6 Rg3 Kf7 Kf4 Nh6 Rg2 Kf6 Kf3 Nf5 Ke4 Nh6 Rg3 Kf7 Rf3 Ke6 Kf4 Nf7\nRe3 Kf6 Re1 Nd6 Rf1 Ke6 Rg1 Kf6 Rg5 Nc4 Rg8 Nd6 Rb8 Ke6 Rb6 Kd5 Ra6 Nc4 Rg6\nKd4 Kf5 Kd5 Kf6 Nd6 Rg5 Kc6 Ke6 Nb5 Rg3 Nc7 Kf5 Kc5 Rd3 Kc6 Kf6 Nb5 Ke7 Na7\nRd2 Nb5 Rd7 Nc7 Rd6 Kc5 Rd1 Nd5 Ke6 Nc7 Kf5 Nb5 Rg1 Nd4 Ke5 Nf3 Kf4 Nxg1\nKe3 Kb4) {0.00/52 2} 130. Rf6+ (Rf6 Ke8 Rh6 Ng8 Rh5 Kf8 Rh7 Ke8 Ra7 Kf8 Rf7\nKe8 Rf4) {+0.11/7 1} Ke8 (Ke8 Kd6 Nc8 Kc5 Ke7 Ra6 Kf7 Ra8 Ne7 Kd4 Nf5 Ke5\nNh6 Kd5 Kf6 Ra6 Kg7 Ra7 Kf6 Rc7 Ng8 Rc6 Kg7 Rb6 Nf6 Kc4 Kf7 Rb7 Ke6 Ra7 Ne4\nKd4 Nf6 Ra6 Kf5 Rb6 Ne4 Rb5 Kf4 Rb7 Nf6 Rb6 Ne4 Re6 Ng3 Rf6 Nf5 Kd5 Kg5 Ra6\nNh6 Rb6 Ng4 Kd4 Kf4 Rb1 Ne3 Rb8 Nf5 Kd5 Ne3 Kc5 Ke5 Re8 Kf4 Kd4 Nf5 Kd3 Kg5\nKe2 Kf6 Kf3 Kg7 Kf4 Kf7 Kxf5 Kxe8 Ke6 Kf8) {0.00/53 3} 131. Rh6 (Rh6 Ng8\nRg6 Kf8 Kd7 Kf7 Re6 Kg7 Rd6 Kf7) {+0.13/7 0} Ng8 (Ng8 Rh8 Kf8 Rh7 Ke8 Ra7\nKf8 Rf7 Ke8 Rf1 Nh6 Rd1 Ng8 Rb1 Kf8 Kf5 Ne7 Kf4 Kf7 Ke4 Ke6 Rb6 Kf7 Rb7 Kf6\nRc7 Ng6 Rc6 Kf7 Ra6 Ne7 Kd4 Kg7 Rb6 Nf5 Ke5 Nh6 Rb7 Kg6 Kf4 Kf6 Ke4 Nf7 Kf3\nNd6 Rc7 Ke5 Ra7 Nc4 Ra1 Kd5 Rd1 Ke5 Re1 Kd5 Kf4 Kd6 Ke4 Nd2 Kf5 Nc4 Rd1 Kc5\nKe4 Nd6 Kf4 Kc6 Kg5 Nc4 Kf6 Kc5 Kf5 Ne3 Ke5 Nxd1 Kf6 Nc3) {0.00/52 2} 132.\nRg6 (Rg6 Kf8 Kd7 Kf7 Rg2 Nf6 Kd6 Ne8 Ke5 Ng7) {+0.11/7 1} Kf8 (Kf8 Kd6 Kf7\nRg4 Kf8 Rg1 Nf6 Ke5 Ng8 Rf1 Kg7 Kd4 Kg6 Ke3 Nf6 Kf4 Ng8 Rg1 Kf7 Rg2 Nf6 Rb2\nNd5 Ke5 Ne7 Rb7 Kf8 Rb8 Kf7 Ra8 Kg7 Kd4 Nf5 Kc4 Ne3 Kb5 Nf5 Ka4 Kf6 Ra5 Ke6\nKb4 Nd6 Kb3 Ne4 Kc2 Nd6 Kd2 Ne8 Ra6 Kd5 Kd3 Nd6 Rb6 Ke5 Rxd6 Kxd6 Ke2 Kc5)\n{0.00/47 2} 133. Rg1 (Rg1 Nh6 Rg2 Ng8 Rd2 Kg7 Rd7 Kg6 Rf7 Nh6) {+0.13/7 1}\nNh6 (Nh6 Re1 Kg7 Kd6 Nf5 Kd7 Kf6 Re6 Kg7 Rb6 Nh6 Rb7 Kg6 Kc6 Nf7 Kd5 Kf6\nRb6 Kg7 Ke6 Nh6 Rb7 Kg6 Kd5 Ng8 Rb8 Nf6 Kd6 Ng4 Rg8 Kf5 Rf8 Ke4 Re8 Kf5 Re2\nKf4 Kc5 Ne3 Kd4 Nf5 Kd3 Kg4 Rg2 Kf4 Rf2 Ke5 Re2 Kd5 Rg2 Nd6 Rg5 Ke6 Ra5 Nf5\nRc5 Ne7 Kd4 Nf5 Kc3 Ng3 Rc8 Kf7 Kd4 Nf5 Kd5 Ne7 Ke4 Nxc8 Kf4 Nd6) {0.00/48\n2} 134. Rg2 (Rg2 Ng8 Rg3 Nh6 Kf6 Ng8 Kf5 Kf7 Rf3) {+0.11/7 1} Ng8 (Ng8 Kd6\nNf6 Rf2 Kg7 Rf1 Ng8 Ke5 Ne7 Rf3 Ng8 Rg3 Kf8 Kf4 Ne7 Rg1 Ng8 Rg6 Kf7 Rg2 Nf6\nRb2 Nd5 Ke5 Ne7 Rb7 Kf8 Rb8 Kf7 Ra8 Kg7 Kd4 Nf5 Kc4 Ne3 Kb5 Nf5 Ka4 Kf6 Ra5\nKe6 Kb4 Nd6 Kb3 Ne4 Kc4 Nd6 Kd4 Nf5 Ke4 Ng3 Kf3 Nf5 Ra8 Ke5 Ke2 Ke4 Kd2 Nd4\nRe8 Kd5 Re1 Nf3 Kd3 Nxe1 Kc3 Nf3) {0.00/53 2} 135. Rg3 (Rg3 Nh6 Rg1 Ng8 Rf1\nKg7 Rf7 Kg6 Rf8 Kg7 Rf7 Kg6) {+0.11/7 0} Nh6 (Nh6 Rg1 Ng8 Rc1 Nh6 Rf1 Kg7\nRg1 Kf8 Rc1 Kg7 Rc7 Kg6 Ra7 Ng4 Rb7 Nh6 Kd6 Nf5 Kd5 Nh6 Rb6 Kg5 Ke4 Ng4 Rb5\nKf6 Rf5 Kg6 Kf4 Nh6 Rf8 Kg7 Ra8 Kg6 Ra6 Kg7 Kg5 Nf7 Kh5 Ne5 Rb6 Nc4 Rc6 Ne5\nRa6 Nf7 Rg6 Kf8 Rg1 Nd6 Re1 Kf7 Kg5 Ne8 Rf1 Ke7 Ra1 Kd6 Ra6 Ke7 Kg6 Nc7 Kf5\nNxa6 Ke4 Nb4) {0.00/53 2} 136. Rg6 (Rg6 Ng8 Kd6 Kf7 Rg7 Kxg7) {+0.10/6 1}\nNg8 (Ng8 Kd6 Kf7 Rg4 Kf8 Rg1 Nf6 Ke5 Ng8 Ke4 Ne7 Rf1 Kg7 Ke3 Ng6 Kd4 Ne7\nKe4 Ng8 Ke5 Ne7 Rf3 Ng8 Rg3 Kf7 Ke4 Ne7 Kf3 Kf6 Rg2 Ke5 Re2 Kf6 Re1 Nd5 Ke4\nNe7 Kd3 Ng6 Kd2 Nf4 Rb1 Nd5 Kd3 Nf4 Ke4 Ne6 Rf1 Ke7 Ke5 Nc7 Rf6 Ne8 Re6 Kd7\nRg6 Nc7 Rg7 Kc6 Ke4 Nb5 Rf7 Nd6 Kd3 Nxf7 Ke3 Kc7) {0.00/50 2} 137. Rg2 (Rg2\nNh6 Rg3 Ng8 Rh3 Kg7 Rg3 Kf8) {+0.12/6 0} Nh6 (Nh6 Kf6 Ng8 Kg6 Ne7 Kg5 Ng8\nRf2 Ke7 Re2 Kd6 Re1 Ne7 Kf6 Nd5 Kf5 Ne7 Ke4 Nc6 Rd1 Ke6 Rg1 Ne5 Rg5 Nd7 Rg6\nNf6 Kd4 Kf5 Rg1 Nd7 Rf1 Ke6 Re1 Kd6 Ke3 Nc5 Kf3 Kd5 Rd1 Ke6 Ra1 Kd5 Kg4 Ne4\nKf4 Nc5 Rd1 Ke6 Kf3 Ke5 Kg4 Ne4 Rf1 Kd4 Kh5 Ng3 Kg5 Nxf1 Kh6 Kc5) {0.00/51\n2} 138. Rg3 (Rg3 Ng8 Rg5 Nh6 Rg1 Ng8 Rg2 Nh6 Rg6 Ng8) {+0.10/6 1} Ng8 (Ng8\nRf3 Kg7 Rb3 Kf8 Kf5 Ne7 Kf4 Ng8 Rf3 Kg7 Re3 Kf7 Re5 Kf6 Rf5 Ke6 Rg5 Ne7 Re5\nKd6 Ke4 Nc6 Rd5 Ke6 Rd1 Ne5 Kf4 Nc6 Rg1 Ne7 Re1 Kd6 Ke4 Nc6 Rd1 Ke6 Rg1 Ne5\nRg8 Nd7 Re8 Kd6 Ra8 Nf6 Kf5 Nd7 Rg8 Kd5 Rg6 Nc5 Rg1 Nd3 Ra1 Nc5 Ra7 Ne4 Rd7\nNd6 Kf4 Kc6 Ke5 Kxd7 Kf4 Ke6 Kg4) {0.00/50 1} 139. Rg5 (Rg5 Nh6 Rg1 Ng8 Rg2\nNh6 Kf6 Ng8 Ke6) {+0.11/6 1} Nh6 (Nh6 Kd5 Kf7 Re5 Kg7 Kc4 Kf7 Kd3 Kf6 Ke4\nNg4 Rf5 Ke6 Rg5 Nf6 Kd3 Kd6 Kd4 Nd7 Rg6 Ke7 Kd5 Nf6 Ke5 Nd7 Kf4 Nf6 Kg5 Ne8\nRc6 Nd6 Rc1 Nf7 Kf4 Kd6 Rg1 Ke6 Rg6 Kd5 Rb6 Nd6 Rb8 Ne4 Rb1 Nc3 Kf5 Nxb1\nKf4 Nc3) {0.00/44 2} 140. Kf6 (Kf6 Ng8 Kg6 Ne7 Kf6 Ng8 Ke6 Nh6 Rg1 Ng8 Rg2\nNh6 Rg6 Ng8) {+0.10/6 0} Ng8+ (Ng8 Ke6 Nh6 Kd5 Nf7 Rg2 Nh6 Rb2 Kf7 Rb7 Kg6\nRb6 Kg5 Ke5 Ng4 Kd4 Kf4 Rb1 Nf6 Rb7 Kf5 Rb5 Ke6 Rb8 Nd7 Ra8 Kd6 Ra6 Ke7 Rg6\nNf6 Ke5 Nd7 Kf4 Nf6 Kg5 Ne8 Rc6 Nd6 Rc1 Nf7 Kf4 Kd6 Ke4 Ke7 Rc7 Ke6 Rc6 Ke7\nKf4 Nd8 Rc8 Kd7 Ke4 Kxc8 Kd5 Kb7) {0.00/45 2} 141. Ke6 (Ke6 Nh6 Rg1 Ng8 Rc1\nNh6 Rc7 Ng8 Rf7 Ke8 Rf4 Nh6) {+0.09/7 0} Nh6 (Nh6 Kd5 Nf7 Rg2 Nh6 Rb2 Kf7\nRb7 Kg6 Rb6 Kg5 Ke5 Ng4 Kd4 Kf4 Rb1 Nf6 Rb7 Kf5 Rb5 Ke6 Rb8 Nd7 Ra8 Kd6 Ra6\nKe7 Rg6 Nf6 Ke5 Nd7 Kf4 Nf6 Kg5 Ne8 Rc6 Nd6 Rc1 Nf7 Kf4 Kd6 Ke4 Ke7 Rc7 Ke6\nRc6 Ke7 Kf4 Nd8 Rc8 Kd7 Ke4 Kxc8 Kd5 Kb7) {0.00/51 0} 142. Rg1 (Rg1 Ng8 Rg2\nNh6 Rd2 Ng8 Rd7 Nh6 Rh7 Ng8) {+0.10/5 0} Ng8 (Ng8 Rc1 Nh6 Rf1 Kg7 Rg1 Kf8\nRc1 Kg7 Rc7 Kg6 Ra7 Ng4 Rb7 Nh6 Kd6 Nf5 Kd5 Nh6 Rb6 Kg5 Ke5 Ng4 Kd4 Kf4 Rb1\nNf6 Rb7 Kf5 Rb5 Ke6 Rb8 Nd7 Ra8 Kd6 Ra6 Ke7 Rg6 Nf6 Ke5 Nd7 Kf4 Nf6 Kf5 Nd5\nRc6 Ne3 Ke4 Ng4 Kf4 Nf6 Rxf6 Kxf6 Ke3 Kg5) {0.00/54 1} 143. Rg3 (Rg3 Nh6\nRg1 Ng8 Rh1 Kg7 Rg1 Kf8) {+0.12/6 1} Nh6 (Nh6 Rg1 Ng8 Rc1 Nh6 Rf1 Kg7 Rg1\nKf8 Rc1 Kg7 Rc7 Kg6 Ra7 Ng4 Rb7 Nh6 Kd6 Nf5 Kd5 Nh6 Rb6 Kg5 Ke5 Ng4 Kd4 Kf4\nRb1 Nf6 Rb7 Kf5 Rb5 Ke6 Re5 Kd6 Re1 Nd5 Ke4 Ne7 Ra1 Kd7 Ra6 Ke8 Ke5 Kf8 Rb6\nNg8 Kf5 Ke8 Rb8 Kf7 Rxg8 Kxg8 Kg6 Kf8 Kh5 Ke7) {0.00/54 3} 144. Rg1 (Rg1\nNg8 Rg2 Nh6 Rg6 Ng8) {+0.09/6 1} Ng8 (Ng8 Rc1 Nh6 Rf1 Kg7 Rg1 Kf8 Rc1 Kg7\nRc7 Kg6 Ra7 Ng4 Rb7 Nh6 Kd6 Nf5 Kd5 Nh6 Rb6 Kg5 Ke5 Ng4 Ke6 Kf4 Rb4 Kf3 Rb1\nKf4 Ra1 Nh6 Ra4 Kg5 Ke5 Nf7 Ke4 Nd6 Kd5 Nf7 Ra7 Kg6 Ke4 Nh6 Kf4 Kf6 Ra6 Kg7\nRxh6 Kxh6 Ke5 Kg6 Ke6 Kg7) {0.00/53 0} 145. Rg2 (Rg2 Nh6 Rg3 Ng8) {+0.10/6\n0} Nh6 (Nh6 Rb2 Ng8 Ke5 Kf7 Kd6 Kg7 Rb7 Kf6 Rb6 Ne7 Kd7 Kf7 Rb1 Ng8 Kd6 Kg6\nRg1 Kf7 Rg4 Kf8 Rf4 Kg7 Rf3 Kg6 Ke5 Kg7 Kf5 Nh6 Kg5 Ng8 Rh3 Kf8 Kf5 Kf7 Rh7\nKf8 Ra7 Ne7 Ke4 Nc6 Ra1 Ke7 Kd5 Kd7 Re1 Ne7 Rxe7 Kxe7 Ke5 Kd7 Kf6 Kd6)\n{0.00/50 2} 146. Rg5 (Rg5 Ng8 Rg1 Nh6 Kf6 Ng8 Ke5 Kf7 Rf1) {+0.08/6 1} Ng8\n(Ng8 Rc5 Kg7 Rc7 Kg6 Ra7 Nh6 Ra3 Ng4 Rg3 Kg5 Rg2 Kf4 Ra2 Ke4 Ra4 Kf3 Kf5\nNe3 Kf6 Ng4 Ke6 Nh6 Rb4 Ng4 Kd5 Ne3 Ke5 Ng4 Kd4 Nh6 Rb1 Kf4 Ra1 Nf5 Kd5 Kf3\nKe5 Ne3 Ra3 Ke2 Kf4 Nf1 Rf3 Ke1 Rxf1 Kxf1 Kg3 Kg1 Kg4 Kf2) {0.00/49 2} 147.\nRg1 (Rg1 Nh6 Rg3 Ng8) {+0.11/6 0} Nh6 (Nh6 Ke5 Nf7 Kf6 Nh6 Kg6 Ng8 Rg5 Ne7\nKh7 Kf7 Ra5 Kf8 Ra3 Ke8 Ra8 Kf7 Ra2 Nd5 Ra6 Nf6 Kh6 Ng8 Kh5 Nf6 Kh4 Nd5 Kg5\nNe7 Rb6 Ke8 Rb7 Ng8 Ra7 Kf8 Kh5 Nf6 Kh6 Ng8 Kg5 Ke8 Kg6 Ne7 Rxe7 Kxe7 Kg7\nKe6) {0.00/43 1} 148. Kf6 (Kf6 Ng8 Kg6 Ne7 Kf6 Ng8 Ke6) {+0.08/6 1} Ng8+\n(Ng8 Kg6 Ne7 Kg5 Ng8 Ra1 Kg7 Re1 Kf8 Kf4 Nf6 Rf1 Ng8 Rf3 Kg7 Re3 Kf7 Rd3\nNf6 Ke5 Ke7 Kf5 Ne8 Re3 Kd7 Kg6 Nc7 Re4 Kc6 Kf5 Nb5 Ke5 Kd7 Rf4 Nc7 Rg4 Ne6\nRe4 Nd8 Rd4 Ke7 Rxd8 Kxd8 Kd6 Ke8 Kc5 Kf7 Kb4 Ke7) {0.00/47 2} 149. Kg6\n(Kg6 Ne7 Kf6 Ng8 Kg6 Ne7 Kh7 Kf7 Rf1 Ke6 Re1 Kf6 Re2) {+0.08/7 0} Ne7+ (Ne7\nKg5 Ng8 Ra1 Kg7 Re1 Kf8 Kf4 Nf6 Rf1 Ng8 Rf3 Kg7 Re3 Kf7 Rd3 Nf6 Ke5 Ke7 Kf5\nNe8 Re3 Kd7 Rf3 Ng7 Kg6 Ne6 Rf6 Nc7 Rf1 Nd5 Kg5 Nc7 Re1 Ne8 Kg4 Nd6 Rd1 Ke6\nRxd6 Kxd6 Kh3 Ke5) {0.00/42 0} 150. Kf6 (Kf6 Ng8 Kg6 Ne7 Kh7 Kf7 Rf1 Ke6\nRe1 Kf6 Re2) {+0.09/7 0} Ng8+ (Ng8 Kg6 Ne7 Kg5 Nd5 Rd1 Ne7 Rd7 Kf7 Rb7 Kf8\nKf6 Ng8 Kf5 Ne7 Ke5 Ng8 Kd6 Nh6 Rb6 Nf7 Ke6 Nh6 Rb1 Kg7 Rf1 Ng8 Re1 Kf8 Rh1\nKg7 Rh5 Kg6 Rf5 Nh6 Rf6 Kg5 Rxh6 Kxh6 Kf6 Kh7 Kf7 Kh6 Ke8 Kg6) {0.00/39 15}\n151. Kg6 (Kg6 Ne7 Kf6 Ng8) {+0.06/7 1} Ne7+ (Ne7 Kg5 Nd5 Rd1 Ne7 Rd7 Kf7\nRb7 Kf8 Kf6 Ng8 Kf5 Ne7 Ke5 Ng8 Rd7 Nh6 Ra7 Ke8 Kf6 Ng8 Kf5 Kf8 Kg5 Ne7 Ra6\nKg7 Rf6 Ng8 Rf3 Nh6 Rg3 Ng8 Kh4 Kf7 Rxg8 Kxg8 Kh3 Kh7) {0.00/33 0} 152. Kf6\n(Kf6 Ng8) {+0.05/5 0} Ng8+ (Ng8) {0.00/52 1 3-fold repetition} 1/2-1/2\n\n"
  },
  {
    "path": "files/misc/pgn.txt",
    "content": "\n\nStandard: Portable Game Notation Specification and Implementation Guide\n\nRevised: 1994.03.12\n\nAuthors: Interested readers of the Internet newsgroup rec.games.chess\n\nCoordinator: Steven J. Edwards (send comments to sje@world.std.com)\n\n\n0: Preface\n\nFrom the Tower of Babel story:\n\n\"If now, while they are one people, all speaking the same language, they have\nstarted to do this, nothing will later stop them from doing whatever they\npropose to do.\"\n\nGenesis XI, v.6, _New American Bible_\n\n\n1: Introduction\n\nPGN is \"Portable Game Notation\", a standard designed for the representation of\nchess game data using ASCII text files.  PGN is structured for easy reading and\nwriting by human users and for easy parsing and generation by computer\nprograms.  The intent of the definition and propagation of PGN is to facilitate\nthe sharing of public domain chess game data among chessplayers (both organic\nand otherwise), publishers, and computer chess researchers throughout the\nworld.\n\nPGN is not intended to be a general purpose standard that is suitable for every\npossible use; no such standard could fill all conceivable requirements.\nInstead, PGN is proposed as a universal portable representation for data\ninterchange.  The idea is to allow the construction of a family of chess\napplications that can quickly and easily process chess game data using PGN for\nimport and export among themselves.\n\n\n2: Chess data representation\n\nComputer usage among chessplayers has become quite common in recent years and a\nvariety of different programs, both commercial and public domain, are used to\ngenerate, access, and propagate chess game data.  Some of these programs are\nrather impressive; most are now well behaved in that they correctly follow the\nLaws of Chess and handle users' data with reasonable care.  Unfortunately, many\nprograms have had serious problems with several aspects of the external\nrepresentation of chess game data.  Sometimes these problems become more\nvisible when a user attempts to move significant quantities of data from one\nprogram to another; if there has been no real effort to ensure portability of\ndata, then the chances for a successful transfer are small at best.\n\n\n2.1: Data interchange incompatibility\n\nThe reasons for format incompatibility are easy to understand.  In fact, most\nof them are correlated with the same problems that have already been seen with\ncommercial software offerings for other domains such as word processing,\nspreadsheets, fonts, and graphics.  Sometimes a manufacturer deliberately\ndesigns a data format using encryption or some other secret, proprietary\ntechnique to \"lock in\" a customer.  Sometimes a designer may produce a format\nthat can be deciphered without too much difficulty, but at the same time\npublicly discourage third party software by claiming trade secret protection.\nAnother software producer may develop a non-proprietary system, but it may work\nwell only within the scope of a single program or application because it is not\neasily expandable.  Finally, some other software may work very well for many\npurposes, but it uses symbols and language not easily understood by people or\ncomputers available to those outside the country of its development.\n\n\n2.2: Specification goals\n\nA specification for a portable game notation must observe the lessons of\nhistory and be able to handle probable needs of the future.  The design\ncriteria for PGN were selected to meet these needs.  These criteria include:\n\n1) The details of the system must be publicly available and free of unnecessary\ncomplexity.  Ideally, if the documentation is not available for some reason,\ntypical chess software developers and users should be able to understand most\nof the data without the need for third party assistance.\n\n2) The details of the system must be non-proprietary so that users and software\ndevelopers are unrestricted by concerns about infringing on intellectual\nproperty rights.  The idea is to let chess programmers compete in a free market\nwhere customers may choose software based on their real needs and not based on\nartificial requirements created by a secret data format.\n\n3) The system must work for a variety of programs.  The format should be such\nthat it can be used by chess database programs, chess publishing programs,\nchess server programs, and chessplaying programs without being unnecessarily\nspecific to any particular application class.\n\n4) The system must be easily expandable and scalable.  The expansion ability\nmust include handling data items that may not exist currently but could be\nexpected to emerge in the future.  (Examples: new opening classifications and\nnew country names.)  The system should be scalable in that it must not have any\narbitrary restrictions concerning the quantity of stored data.  Also, planned\nmodes of expansion should either preserve earlier databases or at least allow\nfor their automatic conversion.\n\n5) The system must be international.  Chess software users are found in many\ncountries and the system should be free of difficulties caused by conventions\nlocal to a given region.\n\n6) Finally, the system should handle the same kinds and amounts of data that\nare already handled by existing chess software and by print media.\n\n\n2.3: A sample PGN game\n\nAlthough its description may seem rather lengthy, PGN is actually fairly\nsimple.  A sample PGN game follows; it has most of the important features\ndescribed in later sections of this document.\n\n[Event \"F/S Return Match\"]\n[Site \"Belgrade, Serbia JUG\"]\n[Date \"1992.11.04\"]\n[Round \"29\"]\n[White \"Fischer, Robert J.\"]\n[Black \"Spassky, Boris V.\"]\n[Result \"1/2-1/2\"]\n\n1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 4. Ba4 Nf6 5. O-O Be7 6. Re1 b5 7. Bb3 d6 8. c3\nO-O 9. h3 Nb8 10. d4 Nbd7 11. c4 c6 12. cxb5 axb5 13. Nc3 Bb7 14. Bg5 b4 15.\nNb1 h6 16. Bh4 c5 17. dxe5 Nxe4 18. Bxe7 Qxe7 19. exd6 Qf6 20. Nbd2 Nxd6 21.\nNc4 Nxc4 22. Bxc4 Nb6 23. Ne5 Rae8 24. Bxf7+ Rxf7 25. Nxf7 Rxe1+ 26. Qxe1 Kxf7\n27. Qe3 Qg5 28. Qxg5 hxg5 29. b3 Ke6 30. a3 Kd6 31. axb4 cxb4 32. Ra5 Nd5 33.\nf3 Bc8 34. Kf2 Bf5 35. Ra7 g6 36. Ra6+ Kc5 37. Ke1 Nf4 38. g3 Nxh3 39. Kd2 Kb5\n40. Rd6 Kc5 41. Ra6 Nf2 42. g4 Bd3 43. Re6 1/2-1/2\n\n\n3: Formats: import and export\n\nThere are two formats in the PGN specification.  These are the \"import\" format\nand the \"export\" format.  These are the two different ways of formatting the\nsame PGN data according to its source.  The details of the two formats are\ndescribed throughout the following sections of this document.\n\nOther than formats, there is the additional topic of PGN presentation.  While\nboth PGN import and export formats are designed to be readable by humans, there\nis no recommendation that either of these be an ultimate mode of chess data\npresentation.  Rather, software developers are urged to consider all of the\nvarious techniques at their disposal to enhance the display of chess data at\nthe presentation level (i.e., highest level) of their programs.  This means\nthat the use of different fonts, character sizes, color, and other tools of\ncomputer aided interaction and publishing should be explored to provide a high\nquality presentation appropriate to the function of the particular program.\n\n\n3.1: Import format allows for manually prepared data\n\nThe import format is rather flexible and is used to describe data that may have\nbeen prepared by hand, much like a source file for a high level programming\nlanguage.  A program that can read PGN data should be able to handle the\nsomewhat lax import format.\n\n\n3.2: Export format used for program generated output\n\nThe export format is rather strict and is used to describe data that is usually\nprepared under program control, something like a pretty printed source program\nreformatted by a compiler.\n\n\n3.2.1: Byte equivalence\n\nFor a given PGN data file, export format representations generated by different\nPGN programs on the same computing system should be exactly equivalent, byte\nfor byte.\n\n\n3.2.2: Archival storage and the newline character\n\nExport format should also be used for archival storage.  Here, \"archival\"\nstorage is defined as storage that may be accessed by a variety of computing\nsystems.  The only extra requirement for archival storage is that the newline\ncharacter have a specific representation that is independent of its value for a\nparticular computing system's text file usage.  The archival representation of\na newline is the ASCII control character LF (line feed, decimal value 10,\nhexadecimal value 0x0a).\n\nSadly, there are some accidents of history that survive to this day that have\nbaroque representations for a newline: multicharacter sequences, end-of-line\nrecord markers, start-of-line byte counts, fixed length records, and so forth.\nIt is well beyond the scope of the PGN project to reconcile all of these to the\nunified world of ANSI C and the those enjoying the bliss of a single '\\n'\nconvention.  Some systems may just not be able to handle an archival PGN text\nfile with native text editors.  In these cases, an indulgence of sorts is\ngranted to use the local newline convention in non-archival PGN files for those\ntext editors.\n\n\n3.2.3: Speed of processing\n\nSeveral parts of the export format deal with exact descriptions of line and\nfield justification that are absent from the import format details.  The main\nreason for these restrictions on the export format are to allow the\nconstruction of simple data translation programs that can easily scan PGN data\nwithout having to have a full chess engine or other complex parsing routines.\nThe idea is to encourage chess software authors to always allow for at least a\nlimited PGN reading capability.  Even when a full chess engine parsing\ncapability is available, it is likely to be at least two orders of magnitude\nslower than a simple text scanner.\n\n\n3.2.4: Reduced export format\n\nA PGN game represented using export format is said to be in \"reduced export\nformat\" if all of the following hold: 1) it has no commentary, 2) it has only\nthe standard seven tag roster identification information (\"STR\", see below), 3)\nit has no recursive annotation variations (\"RAV\", see below), and 4) it has no\nnumeric annotation glyphs (\"NAG\", see below).  Reduced export format is used\nfor bulk storage of unannotated games.  It represents a minimum level of\nstandard conformance for a PGN exporting application.\n\n\n4: Lexicographical issues\n\nPGN data is composed of characters; non-overlapping contiguous sequences of\ncharacters form lexical tokens.\n\n\n4.1: Character codes\n\nPGN data is represented using a subset of the eight bit ISO 8859/1 (Latin 1)\ncharacter set.  (\"ISO\" is an acronym for the International Standards\nOrganization.)  This set is also known as ECMA-94 and is similar to other ISO\nLatin character sets.  ISO 8859/1 includes the standard seven bit ASCII\ncharacter set for the 32 control character code values from zero to 31.  The 95\nprinting character code values from 32 to 126 are also equivalent to seven bit\nASCII usage.  (Code value 127, the ASCII DEL control character, is a graphic\ncharacter in ISO 8859/1; it is not used for PGN data representation.)\n\nThe 32 ISO 8859/1 code values from 128 to 159 are non-printing control\ncharacters.  They are not used for PGN data representation.  The 32 code values\nfrom 160 to 191 are mostly non-alphabetic printing characters and their use for\nPGN data is discouraged as their graphic representation varies considerably\namong other ISO Latin sets.  Finally, the 64 code values from 192 to 255 are\nmostly alphabetic printing characters with various diacritical marks; their use\nis encouraged for those languages that require such characters.  The graphic\nrepresentations of this last set of 64 characters is fairly constant for the\nISO Latin family.\n\nPrinting character codes outside of the seven bit ASCII range may only appear\nin string data and in commentary.  They are not permitted for use in symbol\nconstruction.\n\nBecause some PGN users' environments may not support presentation of non-ASCII\ncharacters, PGN game authors should refrain from using such characters in\ncritical commentary or string values in game data that may be referenced in\nsuch environments.  PGN software authors should have their programs handle such\nenvironments by displaying a question mark (\"?\") for non-ASCII character codes.\nThis is an important point because there are many computing systems that can\ndisplay eight bit character data, but the display graphics may differ among\nmachines and operating systems from different manufacturers.\n\nOnly four of the ASCII control characters are permitted in PGN import format;\nthese are the horizontal and vertical tabs along with the linefeed and carriage\nreturn codes.\n\nThe external representation of the newline character may differ among\nplatforms; this is an acceptable variation as long as the details of the\nimplementation are hidden from software implementors and users.  When a choice\nis practical, the Unix \"newline is linefeed\" convention is preferred.\n\n\n4.2: Tab characters\n\nTab characters, both horizontal and vertical, are not permitted in the export\nformat.  This is because the treatment of tab characters is highly dependent\nupon the particular software in use on the host computing system.  Also, tab\ncharacters may not appear inside of string data.\n\n\n4.3: Line lengths\n\nPGN data are organized as simple text lines without any special bytes or\nmarkers for secondary record structure imposed by specific operating systems.\nImport format PGN text lines are limited to having a maximum of 255 characters\nper line including the newline character.  Lines with 80 or more printing\ncharacters are strongly discouraged because of the difficulties experienced by\ncommon text editors with long lines.\n\nIn some cases, very long tag values will require 80 or more columns, but these\nare relatively rare.  An example of this is the \"FEN\" tag pair; it may have a\nlong tag value, but this particular tag pair is only used to represent a game\nthat doesn't start from the usual initial position.\n\n\n5: Commentary\n\nComment text may appear in PGN data.  There are two kinds of comments.  The\nfirst kind is the \"rest of line\" comment; this comment type starts with a\nsemicolon character and continues to the end of the line.  The second kind\nstarts with a left brace character and continues to the next right brace\ncharacter.  Comments cannot appear inside any token.\n\nBrace comments do not nest; a left brace character appearing in a brace comment\nloses its special meaning and is ignored.  A semicolon appearing inside of a\nbrace comment loses its special meaning and is ignored.  Braces appearing\ninside of a semicolon comments lose their special meaning and are ignored.\n\n*** Export format representation of comments needs definition work.\n\n\n6: Escape mechanism\n\nThere is a special escape mechanism for PGN data.  This mechanism is triggered\nby a percent sign character (\"%\") appearing in the first column of a line; the\ndata on the rest of the line is ignored by publicly available PGN scanning\nsoftware.  This escape convention is intended for the private use of software\ndevelopers and researchers to embed non-PGN commands and data in PGN streams.\n\nA percent sign appearing in any other place other than the first position in a\nline does not trigger the escape mechanism.\n\n\n7: Tokens\n\nPGN character data is organized as tokens.  A token is a contiguous sequence of\ncharacters that represents a basic semantic unit.  Tokens may be separated from\nadjacent tokens by white space characters.  (White space characters include\nspace, newline, and tab characters.)  Some tokens are self delimiting and do\nnot require white space characters.\n\nA string token is a sequence of zero or more printing characters delimited by a\npair of quote characters (ASCII decimal value 34, hexadecimal value 0x22).  An\nempty string is represented by two adjacent quotes.  (Note: an apostrophe is\nnot a quote.)  A quote inside a string is represented by the backslash\nimmediately followed by a quote.  A backslash inside a string is represented by\ntwo adjacent backslashes.  Strings are commonly used as tag pair values (see\nbelow).  Non-printing characters like newline and tab are not permitted inside\nof strings.  A string token is terminated by its closing quote.  Currently, a\nstring is limited to a maximum of 255 characters of data.\n\nAn integer token is a sequence of one or more decimal digit characters.  It is\na special case of the more general \"symbol\" token class described below.\nInteger tokens are used to help represent move number indications (see below).\nAn integer token is terminated just prior to the first non-symbol character\nfollowing the integer digit sequence.\n\nA period character (\".\") is a token by itself.  It is used for move number\nindications (see below).  It is self terminating.\n\nAn asterisk character (\"*\") is a token by itself.  It is used as one of the\npossible game termination markers (see below); it indicates an incomplete game\nor a game with an unknown or otherwise unavailable result.  It is self\nterminating.\n\nThe left and right bracket characters (\"[\" and \"]\") are tokens.  They are used\nto delimit tag pairs (see below).  Both are self terminating.\n\nThe left and right parenthesis characters (\"(\" and \")\") are tokens.  They are\nused to delimit Recursive Annotation Variations (see below).  Both are self\nterminating.\n\nThe left and right angle bracket characters (\"<\" and \">\") are tokens.  They are\nreserved for future expansion.  Both are self terminating.\n\nA Numeric Annotation Glyph (\"NAG\", see below) is a token; it is composed of a\ndollar sign character (\"$\") immediately followed by one or more digit\ncharacters.  It is terminated just prior to the first non-digit character\nfollowing the digit sequence.\n\nA symbol token starts with a letter or digit character and is immediately\nfollowed by a sequence of zero or more symbol continuation characters.  These\ncontinuation characters are letter characters (\"A-Za-z\"), digit characters\n(\"0-9\"), the underscore (\"_\"), the plus sign (\"+\"), the octothorpe sign (\"#\"),\nthe equal sign (\"=\"), the colon (\":\"),  and the hyphen (\"-\").  Symbols are used\nfor a variety of purposes.  All characters in a symbol are significant.  A\nsymbol token is terminated just prior to the first non-symbol character\nfollowing the symbol character sequence.  Currently, a symbol is limited to a\nmaximum of 255 characters in length.\n\n\n8: Parsing games\n\nA PGN database file is a sequential collection of zero or more PGN games.  An\nempty file is a valid, although somewhat uninformative, PGN database.\n\nA PGN game is composed of two sections.  The first is the tag pair section and\nthe second is the movetext section.  The tag pair section provides information\nthat identifies the game by defining the values associated with a set of\nstandard parameters.  The movetext section gives the usually enumerated and\npossibly annotated moves of the game along with the concluding game termination\nmarker.  The chess moves themselves are represented using SAN (Standard\nAlgebraic Notation), also described later in this document.\n\n\n8.1: Tag pair section\n\nThe tag pair section is composed of a series of zero or more tag pairs.\n\nA tag pair is composed of four consecutive tokens: a left bracket token, a\nsymbol token, a string token, and a right bracket token.  The symbol token is\nthe tag name and the string token is the tag value associated with the tag\nname.  (There is a standard set of tag names and semantics described below.)\nThe same tag name should not appear more than once in a tag pair section.\n\nA further restriction on tag names is that they are composed exclusively of\nletters, digits, and the underscore character.  This is done to facilitate\nmapping of tag names into key and attribute names for use with general purpose\ndatabase programs.\n\nFor PGN import format, there may be zero or more white space characters between\nany adjacent pair of tokens in a tag pair.\n\nFor PGN export format, there are no white space characters between the left\nbracket and the tag name, there are no white space characters between the tag\nvalue and the right bracket, and there is a single space character between the\ntag name and the tag value.\n\nTag names, like all symbols, are case sensitive.  All tag names used for\narchival storage begin with an upper case letter.\n\nPGN import format may have multiple tag pairs on the same line and may even\nhave a tag pair spanning more than a single line.  Export format requires each\ntag pair to appear left justified on a line by itself; a single empty line\nfollows the last tag pair.\n\nSome tag values may be composed of a sequence of items.  For example, a\nconsultation game may have more than one player for a given side.  When this\noccurs, the single character \":\" (colon) appears between adjacent items.\nBecause of this use as an internal separator in strings, the colon should not\notherwise appear in a string.\n\nThe tag pair format is designed for expansion; initially only strings are\nallowed as tag pair values.  Tag value formats associated with the STR (Seven\nTag Roster, see below) will not change; they will always be string values.\nHowever, there are long term plans to allow general list structures as tag\nvalues for non-STR tag pairs.  Use of these expanded tag values will likely be\nrestricted to special research programs.  In all events, the top level\nstructure of a tag pair remains the same: left bracket, tag name, tag value,\nand right bracket.\n\n\n8.1.1: Seven Tag Roster\n\nThere is a set of tags defined for mandatory use for archival storage of PGN\ndata.  This is the STR (Seven Tag Roster).  The interpretation of these tags is\nfixed as is the order in which they appear.  Although the definition and use of\nadditional tag names and semantics is permitted and encouraged when needed, the\nSTR is the common ground that all programs should follow for public data\ninterchange.\n\nFor import format, the order of tag pairs is not important.  For export format,\nthe STR tag pairs appear before any other tag pairs.  (The STR tag pairs must\nalso appear in order; this order is described below).  Also for export format,\nany additional tag pairs appear in ASCII order by tag name.\n\nThe seven tag names of the STR are (in order):\n\n1) Event (the name of the tournament or match event)\n\n2) Site (the location of the event)\n\n3) Date (the starting date of the game)\n\n4) Round (the playing round ordinal of the game)\n\n5) White (the player of the white pieces)\n\n6) Black (the player of the black pieces)\n\n7) Result (the result of the game)\n\nA set of supplemental tag names is given later in this document.\n\nFor PGN export format, a single blank line appears after the last of the tag\npairs to conclude the tag pair section.  This helps simple scanning programs to\nquickly determine the end of the tag pair section and the beginning of the\nmovetext section.\n\n\n8.1.1.1: The Event tag\n\nThe Event tag value should be reasonably descriptive.  Abbreviations are to be\navoided unless absolutely necessary.  A consistent event naming should be used\nto help facilitate database scanning.  If the name of the event is unknown, a\nsingle question mark should appear as the tag value.\n\nExamples:\n\n[Event \"FIDE World Championship\"]\n\n[Event \"Moscow City Championship\"]\n\n[Event \"ACM North American Computer Championship\"]\n\n[Event \"Casual Game\"]\n\n\n8.1.1.2: The Site tag\n\nThe Site tag value should include city and region names along with a standard\nname for the country.  The use of the IOC (International Olympic Committee)\nthree letter names is suggested for those countries where such codes are\navailable.  If the site of the event is unknown, a single question mark should\nappear as the tag value.  A comma may be used to separate a city from a region.\nNo comma is needed to separate a city or region from the IOC country code.  A\nlater section of this document gives a list of three letter nation codes along\nwith a few additions for \"locations\" not covered by the IOC.\n\nExamples:\n\n[Site \"New York City, NY USA\"]\n\n[Site \"St. Petersburg RUS\"]\n\n[Site \"Riga LAT\"]\n\n\n8.1.1.3: The Date tag\n\nThe Date tag value gives the starting date for the game.  (Note: this is not\nnecessarily the same as the starting date for the event.)  The date is given\nwith respect to the local time of the site given in the Event tag.  The Date\ntag value field always uses a standard ten character format: \"YYYY.MM.DD\".  The\nfirst four characters are digits that give the year, the next character is a\nperiod, the next two characters are digits that give the month, the next\ncharacter is a period, and the final two characters are digits that give the\nday of the month.  If the any of the digit fields are not known, then question\nmarks are used in place of the digits.\n\nExamples:\n\n[Date \"1992.08.31\"]\n\n[Date \"1993.??.??\"]\n\n[Date \"2001.01.01\"]\n\n\n8.1.1.4: The Round tag\n\nThe Round tag value gives the playing round for the game.  In a match\ncompetition, this value is the number of the game played.  If the use of a\nround number is inappropriate, then the field should be a single hyphen\ncharacter.  If the round is unknown, a single question mark should appear as\nthe tag value.\n\nSome organizers employ unusual round designations and have multipart playing\nrounds and sometimes even have conditional rounds.  In these cases, a multipart\nround identifier can be made from a sequence of integer round numbers separated\nby periods.  The leftmost integer represents the most significant round and\nsucceeding integers represent round numbers in descending hierarchical order.\n\nExamples:\n\n[Round \"1\"]\n\n[Round \"3.1\"]\n\n[Round \"4.1.2\"]\n\n\n8.1.1.5: The White tag\n\nThe White tag value is the name of the player or players of the white pieces.\nThe names are given as they would appear in a telephone directory.  The family\nor last name appears first.  If a first name or first initial is available, it\nis separated from the family name by a comma and a space.  Finally, one or more\nmiddle initials may appear.  (Wherever a comma appears, the very next character\nshould be a space.  Wherever an initial appears, the very next character should\nbe a period.)  If the name is unknown, a single question mark should appear as\nthe tag value.\n\nThe intent is to allow meaningful ASCII sorting of the tag value that is\nindependent of regional name formation customs.  If more than one person is\nplaying the white pieces, the names are listed in alphabetical order and are\nseparated by the colon character between adjacent entries.  A player who is\nalso a computer program should have appropriate version information listed\nafter the name of the program.\n\nThe format used in the FIDE Rating Lists is appropriate for use for player name\ntags.\n\nExamples:\n\n[White \"Tal, Mikhail N.\"]\n\n[White \"van der Wiel, Johan\"]\n\n[White \"Acme Pawngrabber v.3.2\"]\n\n[White \"Fine, R.\"]\n\n\n8.1.1.6: The Black tag\n\nThe Black tag value is the name of the player or players of the black pieces.\nThe names are given here as they are for the White tag value.\n\nExamples:\n\n[Black \"Lasker, Emmanuel\"]\n\n[Black \"Smyslov, Vasily V.\"]\n\n[Black \"Smith, John Q.:Woodpusher 2000\"]\n\n[Black \"Morphy\"]\n\n\n8.1.1.7: The Result tag\n\nThe Result field value is the result of the game.  It is always exactly the\nsame as the game termination marker that concludes the associated movetext.  It\nis always one of four possible values: \"1-0\" (White wins), \"0-1\" (Black wins),\n\"1/2-1/2\" (drawn game), and \"*\" (game still in progress, game abandoned, or\nresult otherwise unknown).  Note that the digit zero is used in both of the\nfirst two cases; not the letter \"O\".\n\nAll possible examples:\n\n[Result \"0-1\"]\n\n[Result \"1-0\"]\n\n[Result \"1/2-1/2\"]\n\n[Result \"*\"]\n\n\n8.2: Movetext section\n\nThe movetext section is composed of chess moves, move number indications,\noptional annotations, and a single concluding game termination marker.\n\nBecause illegal moves are not real chess moves, they are not permitted in PGN\nmovetext.  They may appear in commentary, however.  One would hope that illegal\nmoves are relatively rare in games worthy of recording.\n\n\n8.2.1: Movetext line justification\n\nIn PGN import format, tokens in the movetext do not require any specific line\njustification.\n\nIn PGN export format, tokens in the movetext are placed left justified on\nsuccessive text lines each of which has less than 80 printing characters.  As\nmany tokens as possible are placed on a line with the remainder appearing on\nsuccessive lines.  A single space character appears between any two adjacent\nsymbol tokens on the same line in the movetext.  As with the tag pair section,\na single empty line follows the last line of data to conclude the movetext\nsection.\n\nNeither the first or the last character on an export format PGN line is a\nspace.  (This may change in the case of commentary; this area is currently\nunder development.)\n\n\n8.2.2: Movetext move number indications\n\nA move number indication is composed of one or more adjacent digits (an integer\ntoken) followed by zero or more periods.  The integer portion of the indication\ngives the move number of the immediately following white move (if present) and\nalso the immediately following black move (if present).\n\n\n8.2.2.1: Import format move number indications\n\nPGN import format does not require move number indications.  It does not\nprohibit superfluous move number indications anywhere in the movetext as long\nas the move numbers are correct.\n\nPGN import format move number indications may have zero or more period\ncharacters following the digit sequence that gives the move number; one or more\nwhite space characters may appear between the digit sequence and the period(s).\n\n\n8.2.2.2: Export format move number indications\n\nThere are two export format move number indication formats, one for use\nappearing immediately before a white move element and one for use appearing\nimmediately before a black move element.  A white move number indication is\nformed from the integer giving the fullmove number with a single period\ncharacter appended.  A black move number indication is formed from the integer\ngiving the fullmove number with three period characters appended.\n\nAll white move elements have a preceding move number indication.  A black move\nelement has a preceding move number indication only in two cases: first, if\nthere is intervening annotation or commentary between the black move and the\nprevious white move; and second, if there is no previous white move in the\nspecial case where a game starts from a position where Black is the active\nplayer.\n\nThere are no other cases where move number indications appear in PGN export\nformat.\n\n\n8.2.3: Movetext SAN (Standard Algebraic Notation)\n\nSAN (Standard Algebraic Notation) is a representation standard for chess moves\nusing the ASCII Latin alphabet.\n\nExamples of SAN recorded games are found throughout most modern chess\npublications.  SAN as presented in this document uses English language single\ncharacter abbreviations for chess pieces, although this is easily changed in\nthe source.  English is chosen over other languages because it appears to be\nthe most widely recognized.\n\nAn alternative to SAN is FAN (Figurine Algebraic Notation).  FAN uses miniature\npiece icons instead of single letter piece abbreviations.  The two notations\nare otherwise identical.\n\n\n8.2.3.1: Square identification\n\nSAN identifies each of the sixty four squares on the chessboard with a unique\ntwo character name.  The first character of a square identifier is the file of\nthe square; a file is a column of eight squares designated by a single lower\ncase letter from \"a\" (leftmost or queenside) up to and including \"h\" (rightmost\nor kingside).  The second character of a square identifier is the rank of the\nsquare; a rank is a row of eight squares designated by a single digit from \"1\"\n(bottom side [White's first rank]) up to and including \"8\" (top side [Black's\nfirst rank]).  The initial squares of some pieces are: white queen rook at a1,\nwhite king at e1, black queen knight pawn at b7, and black king rook at h8.\n\n\n8.2.3.2: Piece identification\n\nSAN identifies each piece by a single upper case letter.  The standard English\nvalues: pawn = \"P\", knight = \"N\", bishop = \"B\", rook = \"R\", queen = \"Q\", and\nking = \"K\".\n\nThe letter code for a pawn is not used for SAN moves in PGN export format\nmovetext.  However, some PGN import software disambiguation code may allow for\nthe appearance of pawn letter codes.  Also, pawn and other piece letter codes\nare needed for use in some tag pair and annotation constructs.\n\nIt is admittedly a bit chauvinistic to select English piece letters over those\nfrom other languages.  There is a slight justification in that English is a de\nfacto universal second language among most chessplayers and program users.  It\nis probably the best that can be done for now.  A later section of this\ndocument gives alternative piece letters, but these should be used only for\nlocal presentation software and not for archival storage or for dynamic\ninterchange among programs.\n\n\n8.2.3.3: Basic SAN move construction\n\nA basic SAN move is given by listing the moving piece letter (omitted for\npawns) followed by the destination square.  Capture moves are denoted by the\nlower case letter \"x\" immediately prior to the destination square; pawn\ncaptures include the file letter of the originating square of the capturing\npawn immediately prior to the \"x\" character.\n\nSAN kingside castling is indicated by the sequence \"O-O\"; queenside castling is\nindicated by the sequence \"O-O-O\".  Note that the upper case letter \"O\" is\nused, not the digit zero.  The use of a zero character is not only incompatible\nwith traditional text practices, but it can also confuse parsing algorithms\nwhich also have to understand about move numbers and game termination markers.\nAlso note that the use of the letter \"O\" is consistent with the practice of\nhaving all chess move symbols start with a letter; also, it follows the\nconvention that all non-pwn move symbols start with an upper case letter.\n\nEn passant captures do not have any special notation; they are formed as if the\ncaptured pawn were on the capturing pawn's destination square.  Pawn promotions\nare denoted by the equal sign \"=\" immediately following the destination square\nwith a promoted piece letter (indicating one of knight, bishop, rook, or queen)\nimmediately following the equal sign.  As above, the piece letter is in upper\ncase.\n\n\n8.2.3.4: Disambiguation\n\nIn the case of ambiguities (multiple pieces of the same type moving to the same\nsquare), the first appropriate disambiguating step of the three following steps\nis taken:\n\nFirst, if the moving pieces can be distinguished by their originating files,\nthe originating file letter of the moving piece is inserted immediately after\nthe moving piece letter.\n\nSecond (when the first step fails), if the moving pieces can be distinguished\nby their originating ranks, the originating rank digit of the moving piece is\ninserted immediately after the moving piece letter.\n\nThird (when both the first and the second steps fail), the two character square\ncoordinate of the originating square of the moving piece is inserted\nimmediately after the moving piece letter.\n\nNote that the above disambiguation is needed only to distinguish among moves of\nthe same piece type to the same square; it is not used to distinguish among\nattacks of the same piece type to the same square.  An example of this would be\na position with two white knights, one on square c3 and one on square g1 and a\nvacant square e2 with White to move.  Both knights attack square e2, and if\nboth could legally move there, then a file disambiguation is needed; the\n(nonchecking) knight moves would be \"Nce2\" and \"Nge2\".  However, if the white\nking were at square e1 and a black bishop were at square b4 with a vacant\nsquare d2 (thus an absolute pin of the white knight at square c3), then only\none white knight (the one at square g1) could move to square e2: \"Ne2\".\n\n\n8.2.3.5: Check and checkmate indication characters\n\nIf the move is a checking move, the plus sign \"+\" is appended as a suffix to\nthe basic SAN move notation; if the move is a checkmating move, the octothorpe\nsign \"#\" is appended instead.\n\nNeither the appearance nor the absence of either a check or checkmating\nindicator is used for disambiguation purposes.  This means that if two (or\nmore) pieces of the same type can move to the same square the differences in\nchecking status of the moves does not allieviate the need for the standard rank\nand file disabiguation described above.  (Note that a difference in checking\nstatus for the above may occur only in the case of a discovered check.)\n\nNeither the checking or checkmating indicators are considered annotation as\nthey do not communicate subjective information.  Therefore, they are\nqualitatively different from move suffix annotations like \"!\" and \"?\".\nSubjective move annotations are handled using Numeric Annotation Glyphs as\ndescribed in a later section of this document.\n\nThere are no special markings used for double checks or discovered checks.\n\nThere are no special markings used for drawing moves.\n\n\n8.2.3.6: SAN move length\n\nSAN moves can be as short as two characters (e.g., \"d4\"), or as long as seven\ncharacters (e.g., \"Qa6xb7#\", \"fxg1=Q+\").  The average SAN move length seen in\nrealistic games is probably just fractionally longer than three characters.  If\nthe SAN rules seem complicated, be assured that the earlier notation systems of\nLEN (Long English Notation) and EDN (English Descriptive Notation) are much\nmore complex, and that LAN (Long Algebraic Notation, the predecessor of SAN) is\nunnecessarily bulky.\n\n\n8.2.3.7: Import and export SAN\n\nPGN export format always uses the above canonical SAN to represent moves in the\nmovetext section of a PGN game.  Import format is somewhat more relaxed and it\nmakes allowances for moves that do not conform exactly to the canonical format.\nHowever, these allowances may differ among different PGN reader programs.  Only\ndata appearing in export format is in all cases guaranteed to be importable\ninto all PGN readers.\n\nThere are a number of suggested guidelines for use with implementing PGN reader\nsoftware for permitting non-canonical SAN move representation.  The idea is to\nhave a PGN reader apply various transformations to attempt to discover the move\nthat is represented by non-canonical input.  Some suggested transformations\ninclude: letter case remapping, capture indicator insertion, check indicator\ninsertion, and checkmate indicator insertion.\n\n\n8.2.3.8: SAN move suffix annotations\n\nImport format PGN allows for the use of traditional suffix annotations for\nmoves.  There are exactly six such annotations available: \"!\", \"?\", \"!!\", \"!?\",\n\"?!\", and \"??\".  At most one such suffix annotation may appear per move, and if\npresent, it is always the last part of the move symbol.\n\nWhen exported, a move suffix annotation is translated into the corresponding\nNumeric Annotation Glyph as described in a later section of this document.  For\nexample, if the single move symbol \"Qxa8?\" appears in an import format PGN\nmovetext, it would be replaced with the two adjacent symbols \"Qxa8 $2\".\n\n\n8.2.4: Movetext NAG (Numeric Annotation Glyph)\n\nAn NAG (Numeric Annotation Glyph) is a movetext element that is used to\nindicate a simple annotation in a language independent manner.  An NAG is\nformed from a dollar sign (\"$\") with a non-negative decimal integer suffix.\nThe non-negative integer must be from zero to 255 in value.\n\n\n8.2.5: Movetext RAV (Recursive Annotation Variation)\n\nAn RAV (Recursive Annotation Variation) is a sequence of movetext containing\none or more moves enclosed in parentheses.  An RAV is used to represent an\nalternative variation.  The alternate move sequence given by an RAV is one that\nmay be legally played by first unplaying the move that appears immediately\nprior to the RAV.  Because the RAV is a recursive construct, it may be nested.\n\n*** The specification for import/export representation of RAV elements needs\nfurther development.\n\n\n8.2.6: Game Termination Markers\n\nEach movetext section has exactly one game termination marker; the marker\nalways occurs as the last element in the movetext.  The game termination marker\nis a symbol that is one of the following four values: \"1-0\" (White wins), \"0-1\"\n(Black wins), \"1/2-1/2\" (drawn game), and \"*\" (game in progress, result\nunknown, or game abandoned).  Note that the digit zero is used in the above;\nnot the upper case letter \"O\".  The game termination marker appearing in the\nmovetext of a game must match the value of the game's Result tag pair.  (While\nthe marker appears as a string in the Result tag, it appears as a symbol\nwithout quotes in the movetext.)\n\n\n9: Supplemental tag names\n\nThe following tag names and their associated semantics are recommended for use\nfor information not contained in the Seven Tag Roster.\n\n\n9.1: Player related information\n\nNote that if there is more than one player field in an instance of a player\n(White or Black) tag, then there will be corresponding multiple fields in any\nof the following tags.  For example, if the White tag has the three field value\n\"Jones:Smith:Zacharias\" (a consultation game), then the WhiteTitle tag could\nhave a value of \"IM:-:GM\" if Jones was an International Master, Smith was\nuntitled, and Zacharias was a Grandmaster.\n\n\n9.1.1: Tags: WhiteTitle, BlackTitle\n\nThese use string values such as \"FM\", \"IM\", and \"GM\"; these tags are used only\nfor the standard abbreviations for FIDE titles.  A value of \"-\" is used for an\nuntitled player.\n\n\n9.1.2: Tags: WhiteElo, BlackElo\n\nThese tags use integer values; these are used for FIDE Elo ratings.  A value of\n\"-\" is used for an unrated player.\n\n\n9.1.3: Tags: WhiteUSCF, BlackUSCF\n\nThese tags use integer values; these are used for USCF (United States Chess\nFederation) ratings.  Similar tag names can be constructed for other rating\nagencies.\n\n\n9.1.4: Tags: WhiteNA, BlackNA\n\nThese tags use string values; these are the e-mail or network addresses of the\nplayers.  A value of \"-\" is used for a player without an electronic address.\n\n\n9.1.5: Tags: WhiteType, BlackType\n\nThese tags use string values; these describe the player types.  The value\n\"human\" should be used for a person while the value \"program\" should be used\nfor algorithmic (computer) players.\n\n\n9.2: Event related information\n\nThe following tags are used for providing additional information about the\nevent.\n\n\n9.2.1: Tag: EventDate\n\nThis uses a date value, similar to the Date tag field, that gives the starting\ndate of the Event.\n\n\n9.2.2: Tag: EventSponsor\n\nThis uses a string value giving the name of the sponsor of the event.\n\n\n9.2.3: Tag: Section\n\nThis uses a string; this is used for the playing section of a tournament (e.g.,\n\"Open\" or \"Reserve\").\n\n\n9.2.4: Tag: Stage\n\nThis uses a string; this is used for the stage of a multistage event (e.g.,\n\"Preliminary\" or \"Semifinal\").\n\n\n9.2.5: Tag: Board\n\nThis uses an integer; this identifies the board number in a team event and also\nin a simultaneous exhibition.\n\n\n9.3: Opening information (locale specific)\n\nThe following tag pairs are used for traditional opening names.  The associated\ntag values will vary according to the local language in use.\n\n\n9.3.1: Tag: Opening\n\nThis uses a string; this is used for the traditional opening name.  This will\nvary by locale.  This tag pair is associated with the use of the EPD opcode\n\"v0\" described in a later section of this document.\n\n\n9.3.2: Tag: Variation\n\nThis uses a string; this is used to further refine the Opening tag.  This will\nvary by locale.  This tag pair is associated with the use of the EPD opcode\n\"v1\" described in a later section of this document.\n\n\n9.3.3: Tag: SubVariation\n\nThis uses a string; this is used to further refine the Variation tag.  This\nwill vary by locale.  This tag pair is associated with the use of the EPD\nopcode \"v2\" described in a later section of this document.\n\n\n9.4: Opening information (third party vendors)\n\nThe following tag pairs are used for representing opening identification\naccording to various third party vendors and organizations.  References to\nthese organizations does not imply any endorsement of them or any endorsement\nby them.\n\n\n9.4.1: Tag: ECO\n\nThis uses a string of either the form \"XDD\" or the form \"XDD/DD\" where the \"X\"\nis a letter from \"A\" to \"E\" and the \"D\" positions are digits; this is used for\nan opening designation from the five volume _Encyclopedia of Chess Openings_.\nThis tag pair is associated with the use of the EPD opcode \"eco\" described in a\nlater section of this document.\n\n\n9.4.2: Tag: NIC\n\nThis uses a string; this is used for an opening designation from the _New in\nChess_ database.  This tag pair is associated with the use of the EPD opcode\n\"nic\" described in a later section of this document.\n\n\n9.5: Time and date related information\n\nThe following tags assist with further refinement of the time and data\ninformation associated with a game.\n\n\n9.5.1: Tag: Time\n\nThis uses a time-of-day value in the form \"HH:MM:SS\"; similar to the Date tag\nexcept that it denotes the local clock time (hours, minutes, and seconds) of\nthe start of the game.  Note that colons, not periods, are used for field\nseparators for the Time tag value.  The value is taken from the local time\ncorresponding to the location given in the Site tag pair.\n\n\n9.5.2: Tag: UTCTime\n\nThis tag is similar to the Time tag except that the time is given according to\nthe Universal Coordinated Time standard.\n\n\n9.5.3: Tag:; UTCDate\n\nThis tag is similar to the Date tag except that the date is given according to\nthe Universal Coordinated Time standard.\n\n\n9.6: Time control\n\nThe follwing tag is used to help describe the time control used with the game.\n\n\n9.6.1: Tag: TimeControl\n\nThis uses a list of one or more time control fields.  Each field contains a\ndescriptor for each time control period; if more than one descriptor is present\nthen they are separated by the colon character (\":\").  The descriptors appear\nin the order in which they are used in the game.  The last field appearing is\nconsidered to be implicitly repeated for further control periods as needed.\n\nThere are six kinds of TimeControl fields.\n\nThe first kind is a single question mark (\"?\") which means that the time\ncontrol mode is unknown.  When used, it is usually the only descriptor present.\n\nThe second kind is a single hyphen (\"-\") which means that there was no time\ncontrol mode in use.  When used, it is usually the only descriptor present.\n\nThe third Time control field kind is formed as two positive integers separated\nby a solidus (\"/\") character.  The first integer is the number of moves in the\nperiod and the second is the number of seconds in the period.  Thus, a time\ncontrol period of 40 moves in 2 1/2 hours would be represented as \"40/9000\".\n\nThe fourth TimeControl field kind is used for a \"sudden death\" control period.\nIt should only be used for the last descriptor in a TimeControl tag value.  It\nis sometimes the only descriptor present.  The format consists of a single\ninteger that gives the number of seconds in the period.  Thus, a blitz game\nwould be represented with a TimeControl tag value of \"300\".\n\nThe fifth TimeControl field kind is used for an \"incremental\" control period.\nIt should only be used for the last descriptor in a TimeControl tag value and\nis usually the only descriptor in the value.  The format consists of two\npositive integers separated by a plus sign (\"+\") character.  The first integer\ngives the minimum number of seconds allocated for the period and the second\ninteger gives the number of extra seconds added after each move is made.  So,\nan incremental time control of 90 minutes plus one extra minute per move would\nbe given by \"4500+60\" in the TimeControl tag value.\n\nThe sixth TimeControl field kind is used for a \"sandclock\" or \"hourglass\"\ncontrol period.  It should only be used for the last descriptor in a\nTimeControl tag value and is usually the only descriptor in the value.  The\nformat consists of an asterisk (\"*\") immediately followed by a positive\ninteger.  The integer gives the total number of seconds in the sandclock\nperiod.  The time control is implemented as if a sandclock were set at the\nstart of the period with an equal amount of sand in each of the two chambers\nand the players invert the sandclock after each move with a time forfeit\nindicated by an empty upper chamber.  Electronic implementation of a physical\nsandclock may be used.  An example sandclock specification for a common three\nminute egg timer sandclock would have a tag value of \"*180\".\n\nAdditional TimeControl field kinds will be defined as necessary.\n\n\n9.7: Alternative starting positions\n\nThere are two tags defined for assistance with describing games that did not\nstart from the usual initial array.\n\n\n9.7.1: Tag: SetUp\n\nThis tag takes an integer that denotes the \"set-up\" status of the game.  A\nvalue of \"0\" indicates that the game has started from the usual initial array.\nA value of \"1\" indicates that the game started from a set-up position; this\nposition is given in the \"FEN\" tag pair.  This tag must appear for a game\nstarting with a set-up position.  If it appears with a tag value of \"1\", a FEN\ntag pair must also appear.\n\n\n9.7.2: Tag: FEN\n\nThis tag uses a string that gives the Forsyth-Edwards Notation for the starting\nposition used in the game.  FEN is described in a later section of this\ndocument.  If a SetUp tag appears with a tag value of \"1\", the FEN tag pair is\nalso required.\n\n\n9.8: Game conclusion\n\nThere is a single tag that discusses the conclusion of the game.\n\n\n9.8.1: Tag: Termination\n\nThis takes a string that describes the reason for the conclusion of the game.\nWhile the Result tag gives the result of the game, it does not provide any\nextra information and so the Termination tag is defined for this purpose.\n\nStrings that may appear as Termination tag values:\n\n* \"abandoned\": abandoned game.\n\n* \"adjudication\": result due to third party adjudication process.\n\n* \"death\": losing player called to greater things, one hopes.\n\n* \"emergency\": game concluded due to unforeseen circumstances.\n\n* \"normal\": game terminated in a normal fashion.\n\n* \"rules infraction\": administrative forfeit due to losing player's failure to\nobserve either the Laws of Chess or the event regulations.\n\n* \"time forfeit\": loss due to losing player's failure to meet time control\nrequirements.\n\n* \"unterminated\": game not terminated.\n\n\n9.9: Miscellaneous\n\nThese are tags that can be briefly described and that doon't fit well inother\nsections.\n\n\n9.9.1: Tag: Annotator\n\nThis tag uses a name or names in the format of the player name tags; this\nidentifies the annotator or annotators of the game.\n\n\n9.9.2: Tag: Mode\n\nThis uses a string that gives the playing mode of the game.  Examples: \"OTB\"\n(over the board), \"PM\" (paper mail), \"EM\" (electronic mail), \"ICS\" (Internet\nChess Server), and \"TC\" (general telecommunication).\n\n\n9.9.3: Tag: PlyCount\n\nThis tag takes a single integer that gives the number of ply (moves) in the\ngame.\n\n\n10: Numeric Annotation Glyphs\n\nNAG zero is used for a null annotation; it is provided for the convenience of\nsoftware designers as a placeholder value and should probably not be used in\nexternal PGN data.\n\nNAGs with values from 1 to 9 annotate the move just played.\n\nNAGs with values from 10 to 135 modify the current position.\n\nNAGs with values from 136 to 139 describe time pressure.\n\nOther NAG values are reserved for future definition.\n\nNote: the number assignments listed below should be considered preliminary in\nnature; they are likely to be changed as a result of reviewer feedback.\n\nNAG    Interpretation\n---    --------------\n  0    null annotation\n  1    good move (traditional \"!\")\n  2    poor move (traditional \"?\")\n  3    very good move (traditional \"!!\")\n  4    very poor move (traditional \"??\")\n  5    speculative move (traditional \"!?\")\n  6    questionable move (traditional \"?!\")\n  7    forced move (all others lose quickly)\n  8    singular move (no reasonable alternatives)\n  9    worst move\n 10    drawish position\n 11    equal chances, quiet position\n 12    equal chances, active position\n 13    unclear position\n 14    White has a slight advantage\n 15    Black has a slight advantage\n 16    White has a moderate advantage\n 17    Black has a moderate advantage\n 18    White has a decisive advantage\n 19    Black has a decisive advantage\n 20    White has a crushing advantage (Black should resign)\n 21    Black has a crushing advantage (White should resign)\n 22    White is in zugzwang\n 23    Black is in zugzwang\n 24    White has a slight space advantage\n 25    Black has a slight space advantage\n 26    White has a moderate space advantage\n 27    Black has a moderate space advantage\n 28    White has a decisive space advantage\n 29    Black has a decisive space advantage\n 30    White has a slight time (development) advantage\n 31    Black has a slight time (development) advantage\n 32    White has a moderate time (development) advantage\n 33    Black has a moderate time (development) advantage\n 34    White has a decisive time (development) advantage\n 35    Black has a decisive time (development) advantage\n 36    White has the initiative\n 37    Black has the initiative\n 38    White has a lasting initiative\n 39    Black has a lasting initiative\n 40    White has the attack\n 41    Black has the attack\n 42    White has insufficient compensation for material deficit\n 43    Black has insufficient compensation for material deficit\n 44    White has sufficient compensation for material deficit\n 45    Black has sufficient compensation for material deficit\n 46    White has more than adequate compensation for material deficit\n 47    Black has more than adequate compensation for material deficit\n 48    White has a slight center control advantage\n 49    Black has a slight center control advantage\n 50    White has a moderate center control advantage\n 51    Black has a moderate center control advantage\n 52    White has a decisive center control advantage\n 53    Black has a decisive center control advantage\n 54    White has a slight kingside control advantage\n 55    Black has a slight kingside control advantage\n 56    White has a moderate kingside control advantage\n 57    Black has a moderate kingside control advantage\n 58    White has a decisive kingside control advantage\n 59    Black has a decisive kingside control advantage\n 60    White has a slight queenside control advantage\n 61    Black has a slight queenside control advantage\n 62    White has a moderate queenside control advantage\n 63    Black has a moderate queenside control advantage\n 64    White has a decisive queenside control advantage\n 65    Black has a decisive queenside control advantage\n 66    White has a vulnerable first rank\n 67    Black has a vulnerable first rank\n 68    White has a well protected first rank\n 69    Black has a well protected first rank\n 70    White has a poorly protected king\n 71    Black has a poorly protected king\n 72    White has a well protected king\n 73    Black has a well protected king\n 74    White has a poorly placed king\n 75    Black has a poorly placed king\n 76    White has a well placed king\n 77    Black has a well placed king\n 78    White has a very weak pawn structure\n 79    Black has a very weak pawn structure\n 80    White has a moderately weak pawn structure\n 81    Black has a moderately weak pawn structure\n 82    White has a moderately strong pawn structure\n 83    Black has a moderately strong pawn structure\n 84    White has a very strong pawn structure\n 85    Black has a very strong pawn structure\n 86    White has poor knight placement\n 87    Black has poor knight placement\n 88    White has good knight placement\n 89    Black has good knight placement\n 90    White has poor bishop placement\n 91    Black has poor bishop placement\n 92    White has good bishop placement\n 93    Black has good bishop placement\n 84    White has poor rook placement\n 85    Black has poor rook placement\n 86    White has good rook placement\n 87    Black has good rook placement\n 98    White has poor queen placement\n 99    Black has poor queen placement\n100    White has good queen placement\n101    Black has good queen placement\n102    White has poor piece coordination\n103    Black has poor piece coordination\n104    White has good piece coordination\n105    Black has good piece coordination\n106    White has played the opening very poorly\n107    Black has played the opening very poorly\n108    White has played the opening poorly\n109    Black has played the opening poorly\n110    White has played the opening well\n111    Black has played the opening well\n112    White has played the opening very well\n113    Black has played the opening very well\n114    White has played the middlegame very poorly\n115    Black has played the middlegame very poorly\n116    White has played the middlegame poorly\n117    Black has played the middlegame poorly\n118    White has played the middlegame well\n119    Black has played the middlegame well\n120    White has played the middlegame very well\n121    Black has played the middlegame very well\n122    White has played the ending very poorly\n123    Black has played the ending very poorly\n124    White has played the ending poorly\n125    Black has played the ending poorly\n126    White has played the ending well\n127    Black has played the ending well\n128    White has played the ending very well\n129    Black has played the ending very well\n130    White has slight counterplay\n131    Black has slight counterplay\n132    White has moderate counterplay\n133    Black has moderate counterplay\n134    White has decisive counterplay\n135    Black has decisive counterplay\n136    White has moderate time control pressure\n137    Black has moderate time control pressure\n138    White has severe time control pressure\n139    Black has severe time control pressure\n\n\n11: File names and directories\n\nFile names chosen for PGN data should be both informative and portable.  The\ndirectory names and arrangements should also be chosen for the same reasons and\nalso for ease of navigation.\n\nSome of suggested file and directory names may be difficult or impossible to\nrepresent on certain computing systems.  Use of appropriate conversion customs\nis encouraged.\n\n\n11.1: File name suffix for PGN data\n\nThe use of the file suffix \".pgn\" is encouraged for ASCII text files containing\nPGN data.\n\n\n11.2: File name formation for PGN data for a specific player\n\nPGN games for a specific player should have a file name consisting of the\nplayer's last name followed by the \".pgn\" suffix.\n\n\n11.3: File name formation for PGN data for a specific event\n\nPGN games for a specific event should have a file name consisting of the\nevent's name followed by the \".pgn\" suffix.\n\n\n11.4: File name formation for PGN data for chronologically ordered games\n\nPGN data files used for chronologically ordered (oldest first) archives use\ndate information as file name root strings.  A file containing all the PGN\ngames for a given year would have an eight character name in the format\n\"YYYY.pgn\".  A file containing PGN data for a given month would have a ten\ncharacter name in the format \"YYYYMM.pgn\".  Finally, a file for PGN games for a\nsingle day would have a twelve character name in the format \"YYYYMMDD.pgn\".\nLarge files are split into smaller files as needed.\n\nAs game files are commonly arranged by chronological order, games with missing\nor incomplete Date tag pair data are to be avoided.  Any question mark\ncharacters in a Date tag value will be treated as zero digits for collation\nwithin a file and also for file naming.\n\nLarge quantities of PGN data arranged by chronological order should be\norganized into hierarchical directories.  A directory containing all PGN data\nfor a given year would have a four character name in the format \"YYYY\";\ndirectories containing PGN files for a given month would have a six character\nname in the format \"YYYYMM\".\n\n\n11.5: Suggested directory tree organization\n\nA suggested directory arrangement for ftp sites and CD-ROM distributions:\n\n* PGN: master directory of the PGN subtree (pub/chess/Game-Databases/PGN)\n\n* PGN/Events: directory of PGN files, each for a specific event\n\n* PGN/Events/News: news and status of the event collection\n\n* PGN/Events/ReadMe: brief description of the local directory contents\n\n* PGN/MGR: directory of the Master Games Repository subtree\n\n* PGN/MGR/News: news and status of the entire PGN/MGR subtree\n\n* PGN/MGR/ReadMe: brief description of the local directory contents\n\n* PGN/MGR/YYYY: directory of games or subtrees for the year YYYY\n\n* PGN/MGR/YYYY/ReadMe: description of local directory for year YYYY\n\n* PGN/MGR/YYYY/News: news and status for year YYYY data\n\n* PGN/News: news and status of the entire PGN subtree\n\n* PGN/Players: directory of PGN files, each for a specific player\n\n* PGN/Players/News: news and status of the player collection\n\n* PGN/Players/ReadMe: brief description of the local directory contents\n\n* PGN/ReadMe: brief description of the local directory contents\n\n* PGN/Standard: the PGN standard (this document)\n\n* PGN/Tools: software utilities that access PGN data\n\n\n12: PGN collating sequence\n\nThere is a standard sorting order for PGN games within a file.  This collation\nis based on eight keys; these are the seven tag values of the STR and also the\nmovetext itself.\n\nThe first (most important, primary key) is the Date tag.  Earlier dated games\nappear prior to games played at a later date.  This field is sorted by\nascending numeric value first with the year, then the month, and finally the\nday of the month.  Query characters used for unknown date digit values will be\ntreated as zero digit characters for ordering comparison.\n\nThe second key is the Event tag.  This is sorted in ascending ASCII order.\n\nThe third key is the Site tag.  This is sorted in ascending ASCII order.\n\nThe fourth key is the Round tag.  This is sorted in ascending numeric order\nbased on the value of the integer used to denote the playing round.  A query or\nhyphen used for the round is ordered before any integer value.  A query\ncharacter is ordered before a hyphen character.\n\nThe fifth key is the White tag.  This is sorted in ascending ASCII order.\n\nThe sixth key is the Black tag.  This is sorted in ascending ASCII order.\n\nThe seventh key is the Result tag.  This is sorted in ascending ASCII order.\n\nThe eighth key is the movetext itself.  This is sorted in ascending ASCII order\nwith the entire text including spaces and newline characters.\n\n\n13: PGN software\n\nThis section describes some PGN software that is either currently available or\nexpected to be available in the near future.  The entries are presented in\nrough chronological order of their being made known to the PGN standard\ncoordinator.  Authors of PGN capable software are encouraged to contact the\ncoordinator (e-mail address listed near the start of this document) so that the\ninformation may be included here in this section.\n\nIn addition to the PGN standard, there are two more chess standards of interest\nto the chess software community.  These are the FEN standard (Forsyth-Edwards\nNotation) for position notation and the EPD standard (Extended Position\nDescription) for comprehensive position description for automated interprogram\nprocessing.  These are described in a later section of this document.\n\nSome PGN software is freeware and can be gotten from ftp sites and other\nsources.  Other PGN software is payware and appears as part of commercial\nchessplaying programs and chess database managers.  Those who are interested in\nthe propagation of the PGN standard are encouraged to support manufacturers of\nchess software that use the standard.  If a particular vendor does not offer\nPGN compatibility, it is likely that a few letters to them along with a copy of\nthis specification may help them decide to include PGN support in their next\nrelease.\n\nThe staff at the University of Oklahoma at Norman (USA) have graciously\nprovided an ftp site (chess.uoknor.edu) for the storage of chess related data\nand programs.  Because file names change over time, those accessing the site\nare encouraged to first retrieve the file \"pub/chess/ls-lR.gz\" for a current\nlisting.  A scan of this listing will also help locate versions of PGN programs\nfor machine types and operating systems other than those listed below.  Further\ninformation about this archive can be gotten from its administrator, Chris\nPetroff (chris@uoknor.edu).\n\nFor European users, the kind staff at the University of Hamburg (Germany) have\nprovided the ftp site ftp.math.uni-hamburg.de; this carries a daily mirror of\nthe pub/chess directory at the chess.uoknor.edu site.\n\n\n13.1: The SAN Kit\n\nThe \"SAN Kit\" is an ANSI C source chess programming toolkit available for free\nfrom the ftp site chess.uoknor.edu in the directory pub/chess/Unix as the file\n\"SAN.tar.gz\" (a gzip tar archive).  This kit contains code for PGN import and\nexport and can be used to \"regularize\" PGN data into reduced export format by\nuse of its \"tfgg\" command.  The SAN Kit also supports FEN I/O.  Code from this\nkit is freely redistributable for anyone as long as future distribution is\nunhindered for everyone.  The SAN Kit is undergoing continuous development,\nalthough dates of future deliveries are quite difficult to predict and releases\nsometimes appear months apart.  Suggestions and comments should be directed to\nits author, Steven J. Edwards (sje@world.std.com).\n\n\n13.2: pgnRead\n\nThe program \"pgnRead\" runs under MS Windows 3.1 and provides an interactive\ngraphical user interface for scanning PGN data files.  This program includes a\ncolorful figurine chessboard display and scrolling controls for game and game\ntext selection.  It is available from the chess.uoknor.edu ftp site in the\npub/chess/DOS directory; several versions are available with names of the form\n\"pgnrd**.exe\"; the latest at this writing is \"PGNRD130.EXE\".  Suggestions and\ncomments should be directed to its author, Keith Fuller (keithfx@aol.com).\n\n\n13.3: mail2pgn/GIICS\n\nThe program \"mail2pgn\" produces a PGN version of chess game data generated by\nthe ICS (Internet Chess Server).  It can be found at the chess.uoknor.edu ftp\nsite in the pub/chess/DOS directory as the file \"mail2pgn.zip\"  A C language\nversion is in the directory pub/chess/Unix as the file \"mail2pgn.c\".\nSuggestions and comments should be directed to its author, John Aronson\n(aronson@helios.ece.arizona.edu).  This code has been reportedly incorporated\ninto the GIICS (Graphical Interface for the ICS); suggestions and comments\nshould be directed to its author, Tony Acero (ace3@midway.uchicago.edu).\n\nThere is a report that mail2pgn has been superseded by the newer program\n\"MV2PGN\" described below.\n\n\n13.4: XBoard\n\n\"XBoard\" is a comprehensive chess utility running under the X Window System\nthat provides a graphical user interface in a portable manner.  A new version\nnow handles PGN data.  It is available from the chess.uoknor.edu ftp site in\nthe pub/chess/X directory as the file \"xboard-3.0.pl9.tar.gz\".  Suggestions and\ncomments should be directed to its author, Tim Mann (mann@src.dec.com).\n\n\n13.5: cupgn\n\nThe program \"cupgn\" converts game data stored in the ChessBase format into PGN.\nIt is available from the chess.uoknor.edu ftp site in the\npub/chess/Game-Databases/CBUFF directory as the file \"cupgn.tar.gz\".  Another\nversion is in the directory pub/chess/DOS as the file \"cupgn120.exe\".\nSuggestions and comments should be directed to its author, Anjo Anjewierden\n(anjo@swi.psy.uva.nl).\n\n\n13.6: Zarkov\n\nThe current version (3.0) of the commercial chessplaying program \"Zarkov\" can\nread and write games using PGN.  This program can also use the EPD standard for\ncommunication with other EPD capable programs.  Historically, Zarkov is the\nvery first program to use EPD.  Suggestions and comments should be directed to\nits author, John Stanback (jhs@icbdfcs1.fc.hp.com).\n\nA vendor for North America is:\n\n    International Chess Enterprises\n    P.O. Box 19457\n    Seattle, WA 98109\n    USA\n    (800) 262-4277\n\nA vendor for Europe is:\n\n    Gambit-Soft\n    Feckenhauser Strasse 27\n    D-78628 Rottweil\n    GERMANY\n    49-741-21573\n\n\n13.7: Chess Assistant\n\nThe upcoming version of the multifunction commercial database program \"Chess\nAssistant\" will be able to use the PGN standard as an import and export option.\nThere is a report of a freeware program, \"PGN2CA\", that will convert PGN\ndatabases into Chess Assistant format.  For more information, the contact is\nVictor Zakharov, one of the members of the Chess Assistant development team\n(VICTOR@ldis.cs.msu.su).\n\nA vendor for North America is:\n\n    International Chess Enterprises\n    P.O. Box 19457\n    Seattle, WA 98109\n    USA\n    (800) 262-4277\n\n\n13.8: BOOKUP\n\nThe MS-DOS edition of the multifunction commercial program BOOKUP, version 8.1,\nis able to use the EPD standard for communication with other EPD capable\nprograms.  It may also be PGN capable as well.\n\nThe BOOKUP 8.1.1 Addenda notes dated 1993.12.17 provide comprehensive\ninformation on how to use EPD in conjunction with \"analyst\" programs such as\nZarkov and HIARCS.  Specifically, the search and evaluation abilities of an\nanalyst program are combined with the information organization abilities of the\nBOOKUP database program to provide position scoring.  This is done by first\nhaving BOOKUP export a database in EPD format, then having an analyst program\nannotate each EPD record with a numeric score, and then having BOOKUP import\nthe changed EPD file.  BOOKUP can then apply minimaxing to the imported\ndatabase; this results in scores from terminal positions being propagated back\nto earlier positions and even back to moves from the starting array.\n\nFor some reason, BOOKUP calls this process \"backsolving\", but it's really just\nstandard minimaxing.  In any case, it's a good example of how different\nprograms from different authors performing different types of tasks can be\nintegrated by use of a common, non-proprietary standard.  This allows for a new\nset of powerful features that are beyond the capabilities of any one of the\nindividual component programs.\n\nBOOKUP allows for some customizing of EPD actions.  One such customization is\nto require the positional evaluations to follow the EPD standard; this means\nthat the score is always given from the viewpoint of the active player.  This\nis explained more fully in the section on the \"ce\" (centipawn evaluation)\nopcode in the EPD description in a later section of this document.  To ensure\nthat BOOKUP handles the centipawn evaluations in the \"right\" way, the EPD\nsetting \"Positive for White\" must be set to \"N\".  This makes BOOKUP work\ncorrectly with Zarkov and with all other programs that use the \"right\"\ncentipawn evaluation convention.  There is an apparent problem with HIARCS that\nrequires this option to be set to \"Y\"; but this really means that, if true,\nHIARCS needs to be adjusted to use the \"right\" centipawn evaluation convention.\n\nA vendor in North America is:\n\n    BOOKUP\n    2763 Kensington Place West\n    Columbus, OH 43202\n    USA\n    (800) 949-5445\n    (614) 263-7219\n\n\n13.9: HIARCS\n\nThe current version (2.1) of the commercial chessplaying program \"HIARCS\" is\nable to use the EPD standard for communication with other EPD capable programs.\nIt may also be PGN capable as well.  More details will appear here as they\nbecome available.\n\nA vendor in North America is:\n\n    HIARCS\n    c/o BOOKUP\n    2763 Kensington Place West\n    Columbus, OH 43202\n    USA\n    (800) 949-5445\n    (614) 263-7219\n\n\n13.10: Deja Vu\n\nThe chess database \"Deja Vu\" from ChessWorks is a PGN compatible collection of\nover 300,000 games.  It is available only on CD-ROM and is scheduled for\nrelease in 1994.05 with periodic revisions thereafter.  The introductory price\nis US$329.  For further information, the authors are John Crayton and Eric\nSchiller and they can be contacted via e-mail (chesswks@netcom.com).\n\n\n13.11: MV2PGN\n\nThe program \"MV2PGN\" can be used to convert game data generated by both current\nand older versions of the GIICS (Graphical Interface - Internet Chess Server).\nThe program is included in the self extracting archive available from\nchess.uoknor.edu in the directory pub/chess/DOS as the file \"ics2pgn.exe\".\nSource code is also included.  This program is reported to supersede the older\n\"mail2pgn\" and was needed due to a change in ICS recording format in late 1993.\nFor further information about MV2PGN, the contact person is Gary Bastin\n(gbastin@x102a.ess.harris.com).\n\n\n13.12: The Hansen utilities (cb2pgn, nic2pgn, pgn2cb, pgn2nic)\n\nThe Hansen utilities are used to convert among various chess data\nrepresentation formats.  The PGN related programs include: \"cb2pgn.exe\"\n(convert ChessBase to PGN), \"nic2pgn.exe\" (convert NIC to PGN), \"pgn2cb.exe\"\n(convert PGN to ChessBase), and \"pgn2nic.exe\" (convert PGN to NIC).\n\nThe ChessBase related utilities (cb2pgn/pgn2cb) are found at chess.uoknor.edu\nin the pub/chess/Game-Databases/ChessBase directory.\n\nThe NIC related utilities (nic2pgn/pgn2nic) are found at chess.uoknor.edu in\nthe pub/chess/Game-Databases/NIC directory.\n\nFor further information about the Hansen utilities, the contact person is the\nauthor, Carsten Hansen (ch0506@hdc.hha.dk).\n\n\n13.13: Slappy the Database\n\n\"Slappy the Database\" is a commercial chess database and translation program\nscheduled for release no sooner than late 1994.  It is a low cost utility with\na simple character interface intended for those who want a supported product\nbut who do not need (or cannot afford) a comprehensive, feature-laden program\nwith a graphical user interface.  Slappy's two most important features are its\nbatch processing ability and its full implementation of each and every standard\ndescribed in this document.  Versions of Slappy the Database will be provided\nfor various platforms inclu\nding: Intel 386/486 Unix, Apple Macintosh, and\nMS-DOS.\n\nSlappy may also be useful to those who have a full feature program who also\nneed to run time consuming chess database tasks on a spare computer.\n\nSuggestions and comments should be directed to its author, Steven J. Edwards\n(sje@world.std.com).  More details will appear here as they become available.\n\n\n13.14: CBASCII\n\n\"CBASCII\" is a general utility for converting chess data between ChessBase\nformat and ASCII representations.  It has PGN capability, and it is available\nfrom the chess.uoknor.edu ftp site in the pub/chess/DOS directory as the file\n\"cba1_2.zip\".  The contact person is the program's author, Andy Duplain\n(duplain@btcs.bt.co.uk).\n\n\n13.15: ZZZZZZ\n\n\"ZZZZZZ\" is a chessplaying program, complete with source, that also includes\nsome database functions.  A recent version is reported to have both PGN and EPD\ncapabilities.  It is available from the chess.uoknor.edu ftp site in the\npub/chess/Unix directory as the file \"zzzzzz-3.2b1.tar.gz\".  The contact person\nis its author, Gijsbert Wiesenecker (wiesenecker@sara.nl).\n\n\n13.16: icsconv\n\nThe program \"icsconv\" can be used to convert Internet Chess Server games, both\nold and new format, to PGN.  It is available from the chess.uoknor.edu site in\nthe pub/chess/Game-Databases/PGN/Tools directory as the file \"icsconv.exe\".\nThe contact person is the author, Kevin Nomura (chow@netcom.com).\n\n\n13.17: CHESSOP (CHESSOPN/CHESSOPG)\n\nCHESSOP is an openings database and viewing tool with support for reading PGN\ngames.  It runs under MS-DOS and displays positions rather than games.  For\neach position, both good and bad moves are listed with appropriate annotation.\nTranspositions are handled as well.  The distributed database contains over\n100,000 positions covering all the common openings.  Users can feed in their\nown PGN data as well.  CHESSOP takes 3 Mbyte of hard disk, costs US$39 and can\nbe obtained from:\n\n    CHESSX Software\n    12 Bluebell Close\n    Glenmore Park\n    AUSTRALIA 2745.\n\nThe ideas behind CHESSOP can be seen in CHESSOPN (alias CHESSOPG), a free\nversion on the ICS server which has a reduced openings database (25,000\npositions) and no PGN or transposition support but is otherwise the same as\nCHESSOP.  (These are the files \"chessopg.zip\" in the directory pub/chess/DOS at\nthe chess.uoknor.edu ftp site.)\n\n\n13.18: CAT2PGN\n\nThe program \"CAT2PGN\" is a utility that translates data from the format used by\nChess Assistant into PGN.  It is available from the chess.uoknor.edu ftp site.\nThe contact person for CAT2PGN is its author, David Myers\n(myers@frodo.biochem.duke.edu).\n\n\n13.19: pgn2opg\n\nThe utility \"pgn2opg\" can be used to convert PGN files into a text format used\nby the \"CHESSOPG\" program mentioned above.  Although it does not perform any\nsemantic analysis on PGN input, it has been demonstrated to handle known\ncorrect PGN input properly.  The file can be found in the pub/chess/PGN/Tools\ndirectory at the chess.uoknor.edu ftp site.  For more information, the author\nis David Barnes (djb@ukc.ac.uk).\n\n\n14: PGN data archives\n\nThe primary PGN data archive repository is located at the ftp site\nchess.uoknor.edu as the directory \"pub/chess/Game-Databases/PGN\".  It is\norganized according to the description given in section C.5 of this document.\nThe European site ftp.math.uni-hamburg.de is also reported to carry a regularly\nupdated copy of the repository.\n\n\n15: International Olympic Committee country codes\n\nInternational Olympic Committee country codes are employed for Site nation\ninformation because of their traditional use with the reporting of\ninternational sporting events.  Due to changes in geography and linguistic\ncustom, some of the following may be incorrect or outdated.  Corrections and\nextensions should be sent via e-mail to the PGN coordinator whose address\nlisted near the start of this document.\n\nAFG: Afghanistan\nAIR: Aboard aircraft\nALB: Albania\nALG: Algeria\nAND: Andorra\nANG: Angola\nANT: Antigua\nARG: Argentina\nARM: Armenia\nATA: Antarctica\nAUS: Australia\nAZB: Azerbaijan\nBAN: Bangladesh\nBAR: Bahrain\nBHM: Bahamas\nBEL: Belgium\nBER: Bermuda\nBIH: Bosnia and Herzegovina\nBLA: Belarus\nBLG: Bulgaria\nBLZ: Belize\nBOL: Bolivia\nBRB: Barbados\nBRS: Brazil\nBRU: Brunei\nBSW: Botswana\nCAN: Canada\nCHI: Chile\nCOL: Columbia\nCRA: Costa Rica\nCRO: Croatia\nCSR: Czechoslovakia\nCUB: Cuba\nCYP: Cyprus\nDEN: Denmark\nDOM: Dominican Republic\nECU: Ecuador\nEGY: Egypt\nENG: England\nESP: Spain\nEST: Estonia\nFAI: Faroe Islands\nFIJ: Fiji\nFIN: Finland\nFRA: France\nGAM: Gambia\nGCI: Guernsey-Jersey\nGEO: Georgia\nGER: Germany\nGHA: Ghana\nGRC: Greece\nGUA: Guatemala\nGUY: Guyana\nHAI: Haiti\nHKG: Hong Kong\nHON: Honduras\nHUN: Hungary\nIND: India\nIRL: Ireland\nIRN: Iran\nIRQ: Iraq\nISD: Iceland\nISR: Israel\nITA: Italy\nIVO: Ivory Coast\nJAM: Jamaica\nJAP: Japan\nJRD: Jordan\nJUG: Yugoslavia\nKAZ: Kazakhstan\nKEN: Kenya\nKIR: Kyrgyzstan\nKUW: Kuwait\nLAT: Latvia\nLEB: Lebanon\nLIB: Libya\nLIC: Liechtenstein\nLTU: Lithuania\nLUX: Luxembourg\nMAL: Malaysia\nMAU: Mauritania\nMEX: Mexico\nMLI: Mali\nMLT: Malta\nMNC: Monaco\nMOL: Moldova\nMON: Mongolia\nMOZ: Mozambique\nMRC: Morocco\nMRT: Mauritius\nMYN: Myanmar\nNCG: Nicaragua\nNET: The Internet\nNIG: Nigeria\nNLA: Netherlands Antilles\nNLD: Netherlands\nNOR: Norway\nNZD: New Zealand\nOST: Austria\nPAK: Pakistan\nPAL: Palestine\nPAN: Panama\nPAR: Paraguay\nPER: Peru\nPHI: Philippines\nPNG: Papua New Guinea\nPOL: Poland\nPOR: Portugal\nPRC: People's Republic of China\nPRO: Puerto Rico\nQTR: Qatar\nRIN: Indonesia\nROM: Romania\nRUS: Russia\nSAF: South Africa\nSAL: El Salvador\nSCO: Scotland\nSEA: At Sea\nSEN: Senegal\nSEY: Seychelles\nSIP: Singapore\nSLV: Slovenia\nSMA: San Marino\nSPC: Aboard spacecraft\nSRI: Sri Lanka\nSUD: Sudan\nSUR: Surinam\nSVE: Sweden\nSWZ: Switzerland\nSYR: Syria\nTAI: Thailand\nTMT: Turkmenistan\nTRK: Turkey\nTTO: Trinidad and Tobago\nTUN: Tunisia\nUAE: United Arab Emirates\nUGA: Uganda\nUKR: Ukraine\nUNK: Unknown\nURU: Uruguay\nUSA: United States of America\nUZB: Uzbekistan\nVEN: Venezuela\nVGB: British Virgin Islands\nVIE: Vietnam\nVUS: U.S. Virgin Islands\nWLS: Wales\nYEM: Yemen\nYUG: Yugoslavia\nZAM: Zambia\nZIM: Zimbabwe\nZRE: Zaire\n\n\n16: Additional chess data standards\n\nWhile PGN is used for game storage, there are other data representation\nstandards for other chess related purposes.  Two important standards are FEN\nand EPD, both described in this section.\n\n\n16.1: FEN\n\nFEN is \"Forsyth-Edwards Notation\"; it is a standard for describing chess\npositions using the ASCII character set.\n\nA single FEN record uses one text line of variable length composed of six data\nfields.  The first four fields of the FEN specification are the same as the\nfirst four fields of the EPD specification.\n\nA text file composed exclusively of FEN data records should have a file name\nwith the suffix \".fen\".\n\n\n16.1.1: History\n\nFEN is based on a 19th century standard for position recording designed by the\nScotsman David Forsyth, a newspaper journalist.  The original Forsyth standard\nhas been slightly extended for use with chess software by Steven Edwards with\nassistance from commentators on the Internet.  This new standard, FEN, was\nfirst implemented in Edwards' SAN Kit.\n\n\n16.1.2: Uses for a position notation\n\nHaving a standard position notation is particularly important for chess\nprogrammers as it allows them to share position databases.  For example, there\nexist standard position notation databases with many of the classical benchmark\ntests for chessplaying programs, and by using a common position notation format\nmany hours of tedious data entry can be saved.  Additionally, a position\nnotation can be useful for page layout programs and for confirming position\nstatus for e-mail competition.\n\nMany interesting chess problem sets represented using FEN can be found at the\nchess.uoknor.edu ftp site in the directory pub/chess/SAN_testsuites.\n\n\n16.1.3: Data fields\n\nFEN specifies the piece placement, the active color, the castling availability,\nthe en passant target square, the halfmove clock, and the fullmove number.\nThese can all fit on a single text line in an easily read format.  The length\nof a FEN position description varies somewhat according to the position. In\nsome cases, the description could be eighty or more characters in length and so\nmay not fit conveniently on some displays.  However, these positions aren't too\ncommon.\n\nA FEN description has six fields.  Each field is composed only of non-blank\nprinting ASCII characters.  Adjacent fields are separated by a single ASCII\nspace character.\n\n\n16.1.3.1: Piece placement data\n\nThe first field represents the placement of the pieces on the board.  The board\ncontents are specified starting with the eighth rank and ending with the first\nrank.  For each rank, the squares are specified from file a to file h.  White\npieces are identified by uppercase SAN piece letters (\"PNBRQK\") and black\npieces are identified by lowercase SAN piece letters (\"pnbrqk\").  Empty squares\nare represented by the digits one through eight; the digit used represents the\ncount of contiguous empty squares along a rank.  A solidus character \"/\" is\nused to separate data of adjacent ranks.\n\n\n16.1.3.2: Active color\n\nThe second field represents the active color.  A lower case \"w\" is used if\nWhite is to move; a lower case \"b\" is used if Black is the active player.\n\n\n16.1.3.3: Castling availability\n\nThe third field represents castling availability.  This indicates potential\nfuture castling that may of may not be possible at the moment due to blocking\npieces or enemy attacks.  If there is no castling availability for either side,\nthe single character symbol \"-\" is used.  Otherwise, a combination of from one\nto four characters are present.  If White has kingside castling availability,\nthe uppercase letter \"K\" appears.  If White has queenside castling\navailability, the uppercase letter \"Q\" appears.  If Black has kingside castling\navailability, the lowercase letter \"k\" appears.  If Black has queenside\ncastling availability, then the lowercase letter \"q\" appears.  Those letters\nwhich appear will be ordered first uppercase before lowercase and second\nkingside before queenside.  There is no white space between the letters.\n\n\n16.1.3.4: En passant target square\n\nThe fourth field is the en passant target square.  If there is no en passant\ntarget square then the single character symbol \"-\" appears.  If there is an en\npassant target square then is represented by a lowercase file character\nimmediately followed by a rank digit.  Obviously, the rank digit will be \"3\"\nfollowing a white pawn double advance (Black is the active color) or else be\nthe digit \"6\" after a black pawn double advance (White being the active color).\n\nAn en passant target square is given if and only if the last move was a pawn\nadvance of two squares.  Therefore, an en passant target square field may have\na square name even if there is no pawn of the opposing side that may\nimmediately execute the en passant capture.\n\n\n16.1.3.5: Halfmove clock\n\nThe fifth field is a nonnegative integer representing the halfmove clock.  This\nnumber is the count of halfmoves (or ply) since the last pawn advance or\ncapturing move.  This value is used for the fifty move draw rule.\n\n\n16.1.3.6: Fullmove number\n\nThe sixth and last field is a positive integer that gives the fullmove number.\nThis will have the value \"1\" for the first move of a game for both White and\nBlack.  It is incremented by one immediately after each move by Black.\n\n\n16.1.4: Examples\n\nHere's the FEN for the starting position:\n\nrnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\n\nAnd after the move 1. e4:\n\nrnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1\n\nAnd then after 1. ... c5:\n\nrnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq c6 0 2\n\nAnd then after 2. Nf3:\n\nrnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2\n\nFor two kings on their home squares and a white pawn on e2 (White to move) with\nthirty eight full moves played with five halfmoves since the last pawn move or\ncapture:\n\n4k3/8/8/8/8/8/4P3/4K3 w - - 5 39\n\n\n16.2: EPD\n\nEPD is \"Extended Position Description\"; it is a standard for describing chess\npositions along with an extended set of structured attribute values using the\nASCII character set.  It is intended for data and command interchange among\nchessplaying programs.  It is also intended for the representation of portable\nopening library repositories.\n\nA single EPD uses one text line of variable length composed of four data field\nfollowed by zero or more operations.  The four fields of the EPD specification\nare the same as the first four fields of the FEN specification.\n\nA text file composed exclusively of EPD data records should have a file name\nwith the suffix \".epd\".\n\n\n16.2.1: History\n\nEPD is based in part on the earlier FEN standard; it has added extensions for\nuse with opening library preparation and also for general data and command\ninterchange among advanced chess programs.  EPD was developed by John Stanback\nand Steven Edwards; its first implementation is in Stanback's master strength\nchessplaying program Zarkov.\n\n\n16.2.2: Uses for an extended position notation\n\nLike FEN, EPD can also be used for general position description.  However,\nunlike FEN, EPD is designed to be expandable by the addition of new operations\nthat provide new functionality as needs arise.\n\nMany interesting chess problem sets represented using EPD can be found at the\nchess.uoknor.edu ftp site in the directory pub/chess/SAN_testsuites.\n\n\n16.2.3: Data fields\n\nEPD specifies the piece placement, the active color, the castling availability,\nand the en passant target square of a position.  These can all fit on a single\ntext line in an easily read format.  The length of an EPD position description\nvaries somewhat according to the position and any associated operations. In\nsome cases, the description could be eighty or more characters in length and so\nmay not fit conveniently on some displays.  However, most EPD descriptions pass\namong programs only and these are not usually seen by program users.\n\n(Note: due to the likelihood of future expansion of EPD, implementors are\nencouraged to have their programs handle EPD text lines of up to 1024\ncharacters long.)\n\nEach EPD data field is composed only of non-blank printing ASCII characters.\nAdjacent data fields are separated by a single ASCII space character.\n\n\n16.2.3.1: Piece placement data\n\nThe first field represents the placement of the pieces on the board.  The board\ncontents are specified starting with the eighth rank and ending with the first\nrank.  For each rank, the squares are specified from file a to file h.  White\npieces are identified by uppercase SAN piece letters (\"PNBRQK\") and black\npieces are identified by lowercase SAN piece letters (\"pnbrqk\").  Empty squares\nare represented by the digits one through eight; the digit used represents the\ncount of contiguous empty squares along a rank.  A solidus character \"/\" is\nused to separate data of adjacent ranks.\n\n\n16.2.3.2: Active color\n\nThe second field represents the active color.  A lower case \"w\" is used if\nWhite is to move; a lower case \"b\" is used if Black is the active player.\n\n\n16.2.3.3: Castling availability\n\nThe third field represents castling availability.  This indicates potential\nfuture castling that may or may not be possible at the moment due to blocking\npieces or enemy attacks.  If there is no castling availability for either side,\nthe single character symbol \"-\" is used.  Otherwise, a combination of from one\nto four characters are present.  If White has kingside castling availability,\nthe uppercase letter \"K\" appears.  If White has queenside castling\navailability, the uppercase letter \"Q\" appears.  If Black has kingside castling\navailability, the lowercase letter \"k\" appears.  If Black has queenside\ncastling availability, then the lowercase letter \"q\" appears.  Those letters\nwhich appear will be ordered first uppercase before lowercase and second\nkingside before queenside.  There is no white space between the letters.\n\n\n16.2.3.4: En passant target square\n\nThe fourth field is the en passant target square.  If there is no en passant\ntarget square then the single character symbol \"-\" appears.  If there is an en\npassant target square then is represented by a lowercase file character\nimmediately followed by a rank digit.  Obviously, the rank digit will be \"3\"\nfollowing a white pawn double advance (Black is the active color) or else be\nthe digit \"6\" after a black pawn double advance (White being the active color).\n\nAn en passant target square is given if and only if the last move was a pawn\nadvance of two squares.  Therefore, an en passant target square field may have\na square name even if there is no pawn of the opposing side that may\nimmediately execute the en passant capture.\n\n\n16.2.4: Operations\n\nAn EPD operation is composed of an opcode followed by zero or more operands and\nis concluded by a semicolon.\n\nMultiple operations are separated by a single space character.  If there is at\nleast one operation present in an EPD line, it is separated from the last\n(fourth) data field by a single space character.\n\n\n16.2.4.1: General format\n\nAn opcode is an identifier that starts with a letter character and may be\nfollowed by up to fourteen more characters.  Each additional character may be a\nletter or a digit or the underscore character.\n\nAn operand is either a set of contiguous non-white space printing characters or\na string.  A string is a set of contiguous printing characters delimited by a\nquote character at each end.  A string value must have less than 256 bytes of\ndata.\n\nIf at least one operand is present in an operation, there is a single space\nbetween the opcode and the first operand.  If more than one operand is present\nin an operation, there is a single blank character between every two adjacent\noperands.  If there are no operands, a semicolon character is appended to the\nopcode to mark the end of the operation.  If any operands appear, the last\noperand has an appended semicolon that marks the end of the operation.\n\nAny given opcode appears at most once per EPD record.  Multiple operations in a\nsingle EPD record should appear in ASCII order of their opcode names\n(mnemonics).  However, a program reading EPD records may allow for operations\nnot in ASCII order by opcode mnemonics; the semantics are the same in either\ncase.\n\nSome opcodes that allow for more than one operand may have special ordering\nrequirements for the operands.  For example, the \"pv\" (predicted variation)\nopcode requires its operands (moves) to appear in the order in which they would\nbe played.  All other opcodes that allow for more than one operand should have\noperands appearing in ASCII order.  An example of the latter set is the \"bm\"\n(best move[s]) opcode; its operands are moves that are all immediately playable\nfrom the current position.\n\nSome opcodes require one or more operands that are chess moves.  These moves\nshould be represented using SAN.  If a different representation is used, there\nis no guarantee that the EPD will be read correctly during subsequent\nprocessing.\n\nSome opcodes require one or more operands that are integers.  Some opcodes may\nrequire that an integer operand must be within a given range; the details are\ndescribed in the opcode list given below.  A negative integer is formed with a\nhyphen (minus sign) preceding the integer digit sequence.  An optional plus\nsign may be used for indicating a non-negative value, but such use is not\nrequired and is indeed discouraged.\n\nSome opcodes require one or more operands that are floating point numbers.\nSome opcodes may require that a floating point operand must be within a given\nrange; the details are described in the opcode list given below.  A floating\npoint operand is constructed from an optional sign character (\"+\" or \"-\"), a\ndigit sequence (with at least one digit), a radix point (always \".\"), and a\nfinal digit sequence (with at least one digit).\n\n\n16.2.4.2: Opcode mnemonics\n\nAn opcode mnemonic used for archival storage and for interprogram communication\nstarts with a lower case letter and is composed of only lower case letters,\ndigits, and the underscore character (i.e., no upper case letters).  These\nmnemonics will also all be at least two characters in length.\n\nOpcode mnemonics used only by a single program or an experimental suite of\nprograms should start with an upper case letter.  This is so they may be easily\ndistinguished should they be inadvertently be encountered by other programs.\nWhen a such a \"private\" opcode be demonstrated to be widely useful, it should\nbe brought into the official list (appearing below) in a lower case form.\n\nIf a given program does not recognize a particular opcode, that operation is\nsimply ignored; it is not signaled as an error.\n\n\n16.2.5: Opcode list\n\nThe opcodes are listed here in ASCII order of their mnemonics.  Suggestions for\nnew opcodes should be sent to the PGN standard coordinator listed near the\nstart of this document.\n\n\n16.2.5.1: Opcode \"acn\": analysis count: nodes\n\nThe opcode \"acn\" takes a single non-negative integer operand.  It is used to\nrepresent the number of nodes examined in an analysis.  Note that the value may\nbe quite large for some extended searches and so use of (at least) a long (four\nbyte) representation is suggested.\n\n\n16.2.5.2: Opcode \"acs\": analysis count: seconds\n\nThe opcode \"acs\" takes a single non-negative integer operand.  It is used to\nrepresent the number of seconds used for an analysis.  Note that the value may\nbe quite large for some extended searches and so use of (at least) a long (four\nbyte) representation is suggested.\n\n\n16.2.5.3: Opcode \"am\": avoid move(s)\n\nThe opcode \"am\" indicates a set of zero or more moves, all immediately playable\nfrom the current position, that are to be avoided in the opinion of the EPD\nwriter.  Each operand is a SAN move; they appear in ASCII order.\n\n\n16.2.5.4: Opcode \"bm\": best move(s)\n\nThe opcode \"bm\" indicates a set of zero or more moves, all immediately playable\nfrom the current position, that are judged to the best available by the EPD\nwriter.  Each operand is a SAN move; they appear in ASCII order.\n\n\n16.2.5.5: Opcode \"c0\": comment (primary, also \"c1\" though \"c9\")\n\nThe opcode \"c0\" (lower case letter \"c\", digit character zero) indicates a top\nlevel comment that applies to the given position.  It is the first of ten\nranked comments, each of which has a mnemonic formed from the lower case letter\n\"c\" followed by a single decimal digit.  Each of these opcodes takes either a\nsingle string operand or no operand at all.\n\nThis ten member comment family of opcodes is intended for use as descriptive\ncommentary for a complete game or game fragment.  The usual processing of these\nopcodes are as follows:\n\n1) At the beginning of a game (or game fragment), a move sequence scanning\nprogram initializes each element of its set of ten comment string registers to\nbe null.\n\n2) As the EPD record for each position in the game is processed, the comment\noperations are interpreted from left to right.  (Actually, all operations in n\nEPD record are interpreted from left to right.)  Because operations appear in\nASCII order according to their opcode mnemonics, opcode \"c0\" (if present) will\nbe handled prior to all other opcodes, then opcode \"c1\" (if present), and so\nforth until opcode \"c9\" (if present).\n\n3) The processing of opcode \"cN\" (0 <= N <= 9) involves two steps.  First, all\ncomment string registers with an index equal to or greater than N are set to\nnull.  (This is the set \"cN\" though \"c9\".)  Second, and only if a string\noperand is present, the value of the corresponding comment string register is\nset equal to the string operand.\n\n\n16.2.5.6: Opcode \"ce\": centipawn evaluation\n\nThe opcode \"ce\" indicates the evaluation of the indicated position in centipawn\nunits.  It takes a single operand, an optionally signed integer that gives an\nevaluation of the position from the viewpoint of the active player; i.e., the\nplayer with the move.  Positive values indicate a position favorable to the\nmoving player while negative values indicate a position favorable to the\npassive player; i.e., the player without the move.  A centipawn evaluation\nvalue close to zero indicates a neutral positional evaluation.\n\nValues are restricted to integers that are equal to or greater than -32767 and\nare less than or equal to 32766.\n\nA value greater than 32000 indicates the availability of a forced mate to the\nactive player.  The number of plies until mate is given by subtracting the\nevaluation from the value 32767.  Thus, a winning mate in N fullmoves is a mate\nin ((2 * N) - 1) halfmoves (or ply) and has a corresponding centipawn\nevaluation of (32767 - ((2 * N) - 1)).  For example, a mate on the move (mate\nin one) has a centipawn evaluation of 32766 while a mate in five has a\ncentipawn evaluation of 32758.\n\nA value less than -32000 indicates the availability of a forced mate to the\npassive player.  The number of plies until mate is given by subtracting the\nevaluation from the value -32767 and then negating the result.  Thus, a losing\nmate in N fullmoves is a mate in (2 * N) halfmoves (or ply) and has a\ncorresponding centipawn evaluation of (-32767 + (2 * N)).  For example, a mate\nafter the move (losing mate in one) has a centipawn evaluation of -32765 while\na losing mate in five has a centipawn evaluation of -32757.\n\nA value of -32767 indicates an illegal position.  A stalemate position has a\ncentipawn evaluation of zero as does a position drawn due to insufficient\nmating material.  Any other position known to be a certain forced draw also has\na centipawn evaluation of zero.\n\n\n16.2.5.7: Opcode \"dm\": direct mate fullmove count\n\nThe \"dm\" opcode is used to indicate the number of fullmoves until checkmate is\nto be delivered by the active color for the indicated position.  It always\ntakes a single operand which is a positive integer giving the fullmove count.\nFor example, a position known to be a \"mate in three\" would have an operation\nof \"dm 3;\" to indicate this.\n\nThis opcode is intended for use with problem sets composed of positions\nrequiring direct mate answers as solutions.\n\n\n16.2.5.8: Opcode \"draw_accept\": accept a draw offer\n\nThe opcode \"draw_accept\" is used to indicate that a draw offer made after the\nmove that lead to the indicated position is accepted by the active player.\nThis opcode takes no operands.\n\n\n16.2.5.9: Opcode \"draw_claim\": claim a draw\n\nThe opcode \"draw_claim\" is used to indicate claim by the active player that a\ndraw exists.  The draw is claimed because of a third time repetition or because\nof the fifty move rule or because of insufficient mating material.  A supplied\nmove (see the opcode \"sm\") is also required to appear as part of the same EPD\nrecord.  The draw_claim opcode takes no operands.\n\n\n16.2.5.10: Opcode \"draw_offer\": offer a draw\n\nThe opcode \"draw_offer\" is used to indicate that a draw is offered by the\nactive player.  A supplied move (see the opcode \"sm\") is also required to\nappear as part of the same EPD record; this move is considered played from the\nindicated position.  The draw_offer opcode takes no operands.\n\n\n16.2.5.11: Opcode \"draw_reject\": reject a draw offer\n\nThe opcode \"draw_reject\" is used to indicate that a draw offer made after the\nmove that lead to the indicated position is rejected by the active player.\nThis opcode takes no operands.\n\n\n16.2.5.12: Opcode \"eco\": _Encyclopedia of Chess Openings_ opening code\n\nThe opcode \"eco\" is used to associate an opening designation from the\n_Encyclopedia of Chess Openings_ taxonomy with the indicated position.  The\nopcode takes either a single string operand (the ECO opening name) or no\noperand at all.  If an operand is present, its value is associated with an\n\"ECO\" string register of the scanning program.  If there is no operand, the ECO\nstring register of the scanning program is set to null.\n\nThe usage is similar to that of the \"ECO\" tag pair of the PGN standard.\n\n\n16.2.5.13: Opcode \"fmvn\": fullmove number\n\nThe opcode \"fmvn\" represents the fullmove n umber associated with the position.\nIt always takes a single operand that is the positive integer value of the move\nnumber.\n\nThis opcode is used to explicitly represent the fullmove number in EPD that is\npresent by default in FEN as the sixth field.  Fullmove number information is\nusually omitted from EPD because it does not affect move generation (commonly\nneeded for EPD-using tasks) but it does affect game notation (commonly needed\nfor FEN-using tasks).  Because of the desire for space optimization for large\nEPD files, fullmove numbers were dropped from EPD's parent FEN.  The halfmove\nclock information was similarly dropped.\n\n\n16.2.5.14: Opcode \"hmvc\": halfmove clock\n\nThe opcode \"hmvc\" represents the halfmove clock associated with the position.\nThe halfmove clock of a position is equal to the number of plies since the last\npawn move or capture.  This information is used to implement the fifty move\ndraw rule.  It always takes a single operand that is the non-negative integer\nvalue of the halfmove clock.\n\nThis opcode is used to explicitly represent the halfmove clock in EPD that is\npresent by default in FEN as the fifth field.  Halfmove clock information is\nusually omitted from EPD because it does not affect move generation (commonly\nneeded for EPD-using tasks) but it does affect game termination issues\n(commonly needed for FEN-using tasks).  Because of the desire for space\noptimization for large EPD files, halfmove clock values were dropped from EPD's\nparent FEN.  The fullmove number information was similarly dropped.\n\n\n16.2.5.15: Opcode \"id\": position identification\n\nThe opcode \"id\" is used to provide a simple identifying label for the indicated\nposition.  It takes a single string operand.\n\nThis opcode is intended for use with test suites used for measuring\nchessplaying program strength.  An example \"id\" operand for the seven hundred\nfifty seventh position of the one thousand one problems in Reinfeld's _1001\nWinning Chess Sacrifices and Combinations_ would be \"WCSAC.0757\" while the\nfifteenth position in the twenty four problem Bratko-Kopec test suite would\nhave an \"id\" operand of \"BK.15\".\n\n\n16.2.5.16: Opcode \"nic\": _New In Chess_ opening code\n\nThe opcode \"nic\" is used to associate an opening designation from the _New In\nChess_ taxonomy with the indicated position.  The opcode takes either a single\nstring operand (the NIC opening name) or no operand at all.  If an operand is\npresent, its value is associated with an \"NIC\" string register of the scanning\nprogram.  If there is no operand, the NIC string register of the scanning\nprogram is set to null.\n\nThe usage is similar to that of the \"NIC\" tag pair of the PGN standard.\n\n\n16.2.5.17: Opcode \"noop\": no operation\n\nThe \"noop\" opcode is used to indicate no operation.  It takes zero or more\noperands, each of which may be of any type.  The operation involves no\nprocessing.  It is intended for use by developers for program testing purposes.\n\n\n16.2.5.18: Opcode \"pm\": predicted move\n\nThe \"pm\" opcode is used to provide a single predicted move for the indicated\nposition.  It has exactly one operand, a move playable from the position.  This\nmove is judged by the EPD writer to represent the best move available to the\nactive player.\n\nIf a non-empty \"pv\" (predicted variation) line of play is also present in the\nsame EPD record, the first move of the predicted variation is the same as the\npredicted move.\n\nThe \"pm\" opcode is intended for use as a general \"display hint\" mechanism.\n\n\n16.2.5.19: Opcode \"pv\": predicted variation\n\nThe \"pv\" opcode is used to provide a predicted variation for the indicated\nposition.  It has zero or more operands which represent a sequence of moves\nplayable from the position.  This sequence is judged by the EPD writer to\nrepresent the best play available.\n\nIf a \"pm\" (predicted move) operation is also present in the same EPD record,\nthe predicted move is the same as the first move of the predicted variation.\n\n\n16.2.5.20: Opcode \"rc\": repetition count\n\nThe \"rc\" opcode is used to indicate the number of occurrences of the indicated\nposition.  It takes a single, positive integer operand.  Any position,\nincluding the initial starting position, is considered to have an \"rc\" value of\nat least one.  A value of three indicates a candidate for a draw claim by the\nposition repetition rule.\n\n\n16.2.5.21: Opcode \"resign\": game resignation\n\nThe opcode \"resign\" is used to indicate that the active player has resigned the\ngame.  This opcode takes no operands.\n\n\n16.2.5.22: Opcode \"sm\": supplied move\n\nThe \"sm\" opcode is used to provide a single supplied move for the indicated\nposition.  It has exactly one operand, a move playable from the position.  This\nmove is the move to be played from the position.\n\nThe \"sm\" opcode is intended for use to communicate the most recent played move\nin an active game.  It is used to communicate moves between programs in\nautomatic play via a network.  This includes correspondence play using e-mail\nand also programs acting as network front ends to human players.\n\n\n16.2.5.23: Opcode \"tcgs\": telecommunication: game selector\n\nThe \"tcgs\" opcode is one of the telecommunication family of opcodes used for\ngames conducted via e-mail and similar means.  This opcode takes a single\noperand that is a positive integer.  It is used to select among various games\nin progress between the same sender and receiver.\n\n\n16.2.5.24: Opcode \"tcri\": telecommunication: receiver identification\n\nThe \"tcri\" opcode is one of the telecommunication family of opcodes used for\ngames conducted via e-mail and similar means.  This opcode takes two order\ndependent string operands.  The first operand is the e-mail address of the\nreceiver of the EPD record.  The second operand is the name of the player\n(program or human) at the address who is the actual receiver of the EPD record.\n\n\n16.2.5.25: Opcode \"tcsi\": telecommunication: sender identification\n\nThe \"tcsi\" opcode is one of the telecommunication family of opcodes used for\ngames conducted via e-mail and similar means.  This opcode takes two order\ndependent string operands.  The first operand is the e-mail address of the\nsender of the EPD record.  The second operand is the name of the player\n(program or human) at the address who is the actual sender of the EPD record.\n\n\n16.2.5.26: Opcode \"v0\": variation name (primary, also \"v1\" though \"v9\")\n\nThe opcode \"v0\" (lower case letter \"v\", digit character zero) indicates a top\nlevel variation name that applies to the given position.  It is the first of\nten ranked variation names, each of which has a mnemonic formed from the lower\ncase letter \"v\" followed by a single decimal digit.  Each of these opcodes\ntakes either a single string operand or no operand at all.\n\nThis ten member variation name family of opcodes is intended for use as\ntraditional variation names for a complete game or game fragment.  The usual\nprocessing of these opcodes are as follows:\n\n1) At the beginning of a game (or game fragment), a move sequence scanning\nprogram initializes each element of its set of ten variation name string\nregisters to be null.\n\n2) As the EPD record for each position in the game is processed, the variation\nname operations are interpreted from left to right.  (Actually, all operations\nin n EPD record are interpreted from left to right.)  Because operations appear\nin ASCII order according to their opcode mnemonics, opcode \"v0\" (if present)\nwill be handled prior to all other opcodes, then opcode \"v1\" (if present), and\nso forth until opcode \"v9\" (if present).\n\n3) The processing of opcode \"vN\" (0 <= N <= 9) involves two steps.  First, all\nvariation name string registers with an index equal to or greater than N are\nset to null.  (This is the set \"vN\" though \"v9\".)  Second, and only if a string\noperand is present, the value of the corresponding variation name string\nregister is set equal to the string operand.\n\n\n17: Alternative chesspiece identifier letters\n\nEnglish language piece names are used to define the letter set for identifying\nchesspieces in PGN movetext.  However, authors of programs which are used only\nfor local presentation or scanning of chess move data may find it convenient to\nuse piece letter codes common in their locales.  This is not a problem as long\nas PGN data that resides in archival storage or that is exchanged among\nprograms still uses the SAN (English) piece letter codes: \"PNBRQK\".\n\nFor the above authors only, a list of alternative piece letter codes are\nprovided:\n\nLanguage     Piece letters (pawn knight bishop rook queen king)\n----------   --------------------------------------------------\nCzech        P J S V D K\nDanish       B S L T D K\nDutch        O P L T D K\nEnglish      P N B R Q K\nEstonian     P R O V L K\nFinnish      P R L T D K\nFrench       P C F T D R\nGerman       B S L T D K\nHungarian    G H F B V K\nIcelandic    P R B H D K\nItalian      P C A T D R\nNorwegian    B S L T D K\nPolish       P S G W H K\nPortuguese   P C B T D R\nRomanian     P C N T D R\nSpanish      P C A T D R\nSwedish      B S L T D K\n\n\n18: Formal syntax\n\n<PGN-database> ::= <PGN-game> <PGN-database>\n                   <empty>\n\n<PGN-game> ::= <tag-section> <movetext-section>\n\n<tag-section> ::= <tag-pair> <tag-section>\n                  <empty>\n\n<tag-pair> ::= [ <tag-name> <tag-value> ]\n\n<tag-name> ::= <identifier>\n\n<tag-value> ::= <string>\n\n<movetext-section> ::= <element-sequence> <game-termination>\n\n<element-sequence> ::= <element> <element-sequence>\n                       <recursive-variation> <element-sequence>\n                       <empty>\n\n<element> ::= <move-number-indication>\n              <SAN-move>\n              <numeric-annotation-glyph>\n\n<recursive-variation> ::= ( <element-sequence> )\n\n<game-termination> ::= 1-0\n                       0-1\n                       1/2-1/2\n                       *\n<empty> ::=\n\n\n19: Canonical chess position hash coding\n\n*** This section is under development.\n\n\n20: Binary representation (PGC)\n\n*** This section is under development.\n\nThe binary coded version of PGN is PGC (PGN Game Coding).  PGC is a binary\nrepresentation standard of PGN data designed for the dual goals of storage\nefficiency and program I/O.  A file containing PGC data should have a name with\na suffix of \".pgc\".\n\nUnlike PGN text files that may have locale dependent representations for\nnewlines, PGC files have data that does not vary due to local processing\nenvironment.  This means that PGC files may be transferred among systems using\ngeneral binary file methods.\n\nPGC files should be used only when the use of PGN is impractical due to time\nand space resource constraints.  As the general level of processing\ncapabilities increases, the need for PGC over PGN will decrease.  Therefore,\nimplementors are encouraged not to use PGC as the default representation\nbecause it is much more difficult (than PGN) to understand without proper\nsoftware.\n\nPGC data is composed of a sequence of PGC records.  Each record is composed of\na sequence of one or more bytes.  The first byte is the PGN record marker and\nit specifies the interpretation of the remaining portion of the record.  This\nremaining portion is composed of zero or more PGN record items.  Item types\ninclude move sequences, move sets, and character strings.\n\n\n20.1: Bytes, words, and doublewords\n\nAt the lowest level, PGC binary data is organized as bytes, words (two\ncontiguous bytes), and doublewords (four contiguous bytes).  All eight bits of\na byte are used.  Longwords (eight contiguous bytes) are not used.  Integer\nvalues are stored using two's complement representation.  Integers may be\nsigned or unsigned depending on context.  Multibyte integers are stored in\nlow-endian format with the least significant byte appearing first.\n\nA one byte integer item is called \"int-1\".  A two byte integer item is called\n\"int-2\".  A four byte integer item is called \"int-4\".\n\nCharacters are stored as bytes using the ISO 8859/1 Latin-1 (ECMA-94) code set.\nThere is no provision for other characters sets or representations.\n\n\n20.2: Move ordinals\n\nA chess move is represented using a move ordinal.  This is a single unsigned\nbyte quantity with values from zero to 255.  A move ordinal is interpreted as\nan index into the list of legal moves from the current position.  This list is\nconstructed by generating the legal moves from the current position, assigning\nSAN ASCII strings to each move, and then sorting these strings in ascending\norder.  Note that a seven bit ordinal, as used by some inferior representation\nsystems, is insufficient as there are some positions that have more than 128\nmoves available.\n\nExamples:  From the initial position, there are twenty moves.  Move ordinal 0\ncorresponds to the SAN move string \"Na3\"; move ordinal 1 corresponds to \"Nc3\",\nmove ordinal 4 corresponds to \"a3\", and move ordinal 19 corresponds to \"h4\".\n\nMoves can be organized into sequences and sets.  A move sequence is an ordered\nlist of moves that are played, one after another from first to last.  A move\nset is a list of moves that are all playable from the current position.\n\nMove sequence data is represented using a length header followed by move\nordinal data.  The length header is an unsigned integer that may be a byte or a\nword.  The integer gives the number, possibly zero, of following move ordinal\nbytes.  Most move sequences can be represented using just a byte header; these\nare called \"mvseq-1\" items.  Move sequence data using a word header are called\n\"mvseq-2\" items.\n\nMove set data is represented using a length header followed by move ordinal\ndata.  The length header is an unsigned integer that is a byte.  The integer\ngives the number, possibly zero, of following move ordinal bytes.  All move\nsets are be represented using just a byte header; these are called \"mvset-1\"\nitems.  (Note the implied restriction that a move set can only have a maximum\nof 255 of the possible 256 ordinals present at one time.)\n\n\n20.3: String data\n\nPGC string data is represented using a length header followed by bytes of\ncharacter data.  The length header is an unsigned integer that may be a byte, a\nword, or a doubleword.  The integer gives the number, possibly zero, of\nfollowing character bytes.  Most strings can be represented using just a byte\nheader; these are called \"string-1\" items.  String data using a word header are\ncalled \"string-2\" items and string data using a doubleword header are called\n\"string-4\" items.  No special ASCII NUL termination byte is required for PGC\nstorage of a string as the length is explicitly given in the item header.\n\n\n20.4: Marker codes\n\nPGC marker codes are given in hexadecimal format.  PGC marker code zero (marker\n0x00) is the \"noop\" marker and carries no meaning.  Each additional marker code\ndefined appears in its own subsection below.\n\n\n20.4.1: Marker 0x01: reduced export format single game\n\nMarker 0x01 is used to indicate a single complete game in reduced export\nformat.  This refers to a game that has only the Seven Tag Roster data, played\nmoves, and no annotations or comments.  This record type is used as an\nalternative to the general game data begin/end record pairs described below.\nThe general marker pair (0x05/0x06) is used to help represent game data that\ncan't be adequately represented in reduced export format.  There are eight\nitems that follow marker 0x01 to form the \"reduced export format single game\"\nrecord.  In order, these are:\n\n1) string-1 (Event tag value)\n\n2) string-1 (Site tag value)\n\n3) string-1 (Date tag value)\n\n4) string-1 (Round tag value)\n\n5) string-1 (White tag value)\n\n6) string-1 (Black tag value)\n\n7) string-1 (Result tag value)\n\n8) mvseq-2 (played moves)\n\n\n20.4.2: Marker 0x02: tag pair\n\nMarker 0x02 is used to indicate a single tag pair.  There are two items that\nfollow marker 0x02 to form the \"tag pair\" record; in order these are:\n\n1) string-1 (tag pair name)\n\n2) string-1 (tag pair value)\n\n\n20.4.3: Marker 0x03: short move sequence\n\nMarker 0x03 is used to indicate a short move sequence.  There is one item that\nfollows marker 0x03 to form the \"short move sequence\" record; this is:\n\n1) mvseq-1 (played moves)\n\n\n20.4.4: Marker 0x04: long move sequence\n\nMarker 0x04 is used to indicate a long move sequence.  There is one item that\nfollows marker 0x04 to form the \"long move sequence\" record; this is:\n\n1) mvseq-2 (played moves)\n\n\n20.4.5: Marker 0x05: general game data begin\n\nMarker 0x05 is used to indicate the beginning of data for a game.  It has no\nassociated items; it is a complete record by itself.  Instead, it marks the\nbeginning of PGC records used to describe a game.  All records up to the\ncorresponding \"general game data end\" record are considered to be part of the\nsame game.  (PGC record type 0x01, \"reduced export format single game\", is not\npermitted to appear within a general game begin/end record pair.  The general\ngame construct is to be used as an alternative to record type 0x01 in those\ncases where the latter is too restrictive to contain the data for a game.)\n\n\n20.4.6: Marker 0x06: general game data end\n\nMarker 0x06 is used to indicate the end of data for a game.  It has no\nassociated items; it is a complete record by itself.  Instead, it marks the end\nof PGC records used to describe a game.  All records after the corresponding\n(and earlier appearing) \"general game data begin\" record are considered to be\npart of the same game.\n\n\n20.4.7: Marker 0x07: simple-nag\n\nMarker 0x07 is used to indicate the presence of a simple NAG (Numeric\nAnnotation Glyph).  This is an annotation marker that has only a short type\nidentification and no operands.  There is one item that follows marker 0x07 to\nform the \"simple-nag\" record; this is:\n\n1) int-1 (unsigned NAG value, from 0 to 255)\n\n\n20.4.8: Marker 0x08: rav-begin\n\nMarker 0x08 is used to indicate the beginning of an RAV (Recursive Annotation\nVariation).  It has no associated items; it is a complete record by itself.\nInstead, it marks the beginning of PGC records used to describe a recursive\nannotation.  It is considered an opening bracket for a later rav-end record;\nthe recursive annotation is completely described between the bracket pair.  The\nrav-begin/data/rav-end structures can be nested.\n\n\n20.4.9: Marker 0x09: rav-end\n\nMarker 0x09 is used to indicate the end of an RAV (Recursive Annotation\nVariation).  It has no associated items; it is a complete record by itself.\nInstead, it marks the end of PGC records used to describe a recursive\nannotation.  It is considered a closing bracket for an earlier rav-begin\nrecord; the recursive annotation is completely described between the bracket\npair.  The rav-begin/data/rav-end structures can be nested.\n\n\n20.4.10: Marker 0x0a: escape-string\n\nMarker 0x0a is used to indicate the presence of an escape string.  This is a\nstring represented by the use of the percent sign (\"%\") escape mechanism in\nPGN.  The data that is escaped is the sequence of characters immediately\nfollwoing the percent sign up to but not including the terminating newline.  As\nis the case with the PGN percent sign escape, the use of a PGC escape-string\nrecord is limited to use for non-archival data.  There is one item that follows\nmarker 0x0a to form the \"escape-string\" record; this is the string data being\nescaped:\n\n1) string-2 (escaped string data)\n\n\n21: E-mail correspondence usage\n\n*** This section is under development.\n\nStandard: EOF\n\n"
  },
  {
    "path": "files/misc/scraps.js",
    "content": "\"use strict\";\n\nfunction NewPGNFileLoader(filename, callback) {\n\n\tlet loader = Object.create(null);\n\tloader.type = \"pgn\";\n\n\tloader.callback = callback;\n\tloader.msg = \"Loading PGN...\";\n\tloader.buf = null;\n\tloader.preparser = null;\n\n\tloader.shutdown = function() {\n\t\tthis.callback = null;\n\t\tthis.msg = \"\";\n\t\tthis.buf = null;\n\t\tif (this.preparser) {\n\t\t\tthis.preparser.shutdown();\n\t\t\tthis.preparser = null;\n\t\t}\n\t};\n\n\tloader.load = function(filename) {\n\t\tfs.readFile(filename, (err, data) => {\n\t\t\tif (err) {\n\t\t\t\tconsole.log(err);\n\t\t\t\tthis.shutdown();\n\t\t\t} else if (this.callback) {\t\t\t\t\t// We might already have aborted\n\t\t\t\tthis.buf = data;\n\t\t\t\tthis.continue();\n\t\t\t}\n\t\t});\n\t};\n\n\tloader.continue = function() {\n\n\t\tif (!this.callback) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.preparser) {\n\t\t\tthis.preparser = NewPGNPreParser(this.buf, (games) => {\n\t\t\t\tif (this.callback) {\n\t\t\t\t\tlet cb = this.callback; cb(games);\n\t\t\t\t}\n\t\t\t\tthis.shutdown();\n\t\t\t});\n\t\t}\n\n\t\tthis.msg = this.preparser.msg;\n\t\tsetTimeout(() => {this.continue();}, 20);\t\t// Just to update these messages.\n\t};\n\n\tsetTimeout(() => {loader.load(filename);}, 0);\n\treturn loader;\n}\n\n// ------------------------------------------------------------------------------------------------------------------------------\n\nfunction NewPGNPreParser(buf, callback) {\t\t// Cannot fail unless aborted.\n\n\tlet loader = Object.create(null);\n\tloader.type = \"pgn\";\n\n\tloader.callback = callback;\n\tloader.msg = \"Preparsing...\";\n\tloader.games = [new_pgn_record()];\n\tloader.lines = null;\n\tloader.buf = buf;\n\tloader.splitter = null;\n\n\tloader.n = 0;\n\n\tloader.shutdown = function() {\n\t\tthis.callback = null;\n\t\tthis.msg = \"\";\n\t\tthis.games = null;\n\t\tthis.lines = null;\n\t\tthis.buf = null;\n\t\tif (this.splitter) {\n\t\t\tthis.splitter.shutdown();\n\t\t\tthis.splitter = null;\n\t\t}\n\t};\n\n\tloader.continue = function() {\n\n\t\tif (!this.callback) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.splitter) {\n\t\t\tthis.splitter = NewLineSplitter(this.buf, (lines) => {\n\t\t\t\tthis.lines = lines;\n\t\t\t});\n\t\t}\n\n\t\tif (!this.lines) {\n\t\t\tthis.msg = this.splitter.msg;\n\t\t\tsetTimeout(() => {this.continue();}, 20);\n\t\t\treturn;\n\t\t}\n\n\t\tlet continuetime = performance.now();\n\n\t\twhile (true) {\n\n\t\t\tif (this.n >= this.lines.length) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tlet rawline = this.lines[this.n++];\n\n\t\t\tif (rawline.length === 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (rawline[0] === 37) {\t\t\t// Percent % sign is a special comment type.\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet tagline = \"\";\n\n\t\t\tif (rawline[0] === 91) {\n\t\t\t\tlet s = decoder.decode(rawline).trim();\n\t\t\t\tif (s.endsWith(`\"]`)) {\n\t\t\t\t\ttagline = s;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (tagline !== \"\") {\n\n\t\t\t\tif (this.games[this.games.length - 1].movebufs.length > 0) {\n\t\t\t\t\t// We have movetext already, so this must be a new game. Start it.\n\t\t\t\t\tthis.games.push(new_pgn_record());\n\t\t\t\t}\n\n\t\t\t\t// Parse the tag line...\n\n\t\t\t\ttagline = tagline.slice(1, -1);\t\t\t\t\t\t\t\t// So now it's like:\t\tFoo \"bar etc\"\n\n\t\t\t\tlet quote_i = tagline.indexOf(`\"`);\n\n\t\t\t\tif (quote_i === -1) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlet key = tagline.slice(0, quote_i).trim();\n\t\t\t\tlet value = tagline.slice(quote_i + 1).trim();\n\n\t\t\t\tif (value.endsWith(`\"`)) {\n\t\t\t\t\tvalue = value.slice(0, -1);\n\t\t\t\t}\n\n\t\t\t\tthis.games[this.games.length - 1].tags[key] = SafeStringHTML(value);\t\t// Escape evil characters. IMPORTANT!\n\n\t\t\t} else {\n\n\t\t\t\tthis.games[this.games.length - 1].movebufs.push(rawline);\n\n\t\t\t}\n\n\t\t\tif (this.n % 1000 === 0) {\n\t\t\t\tif (performance.now() - continuetime > 20) {\n\t\t\t\t\tthis.msg = `Preparsing... ${(100 * (this.n / this.lines.length)).toFixed(0)}%`;\n\t\t\t\t\tsetTimeout(() => {this.continue();}, 20);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Once, after the while loop is broken...\n\n\t\tlet cb = this.callback; cb(this.games);\n\t\tthis.shutdown();\n\t};\n\n\tsetTimeout(() => {loader.continue();}, 0);\t\t// setTimeout especially required here since there's no async load() function in this one.\n\treturn loader;\n}\n\n// ------------------------------------------------------------------------------------------------------------------------------\n\nfunction NewLineSplitter(buf, callback) {\n\n\t// The original sync version of this is in misc/scraps.js and is easier to read.\n\n\tlet loader = Object.create(null);\n\tloader.type = \"?\";\n\n\tloader.callback = callback;\n\tloader.msg = \"PGN: Splitting...\";\n\tloader.lines = [];\n\tloader.buf = buf;\n\n\tloader.a = 0;\n\tloader.b = 0;\n\n\tif (buf.length > 3 && buf[0] === 239 && buf[1] === 187 && buf[2] === 191) {\n\t\tloader.a = 3;\t\t// 1st slice will skip byte order mark (BOM).\n\t}\n\n\tloader.shutdown = function() {\n\t\tthis.callback = null;\n\t\tthis.msg = \"\";\n\t\tthis.lines = null;\n\t\tthis.buf = null;\n\t};\n\n\tloader.append = function(arr) {\n\t\tif (arr.length > 0 && arr[arr.length - 1] === 13) {\t\t// Discard \\r\n\t\t\tthis.lines.push(Buffer.from(arr.slice(0, -1)));\n\t\t} else {\n\t\t\tthis.lines.push(Buffer.from(arr));\n\t\t}\n\t};\n\n\tloader.continue = function() {\n\n\t\tif (!this.callback) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet continuetime = performance.now();\n\n\t\twhile (true) {\n\n\t\t\tif (this.b >= this.buf.length) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (this.buf[this.b] === 10) {\t\t\t\t\t// Split on \\n\n\t\t\t\tlet line = this.buf.slice(this.a, this.b);\n\t\t\t\tthis.append(line);\n\t\t\t\tthis.a = this.b + 1;\n\t\t\t}\n\n\t\t\tthis.b++;\n\n\t\t\tif (this.lines.length % 1000 === 0) {\n\t\t\t\tif (performance.now() - continuetime > 20) {\n\t\t\t\t\tthis.msg = `PGN: Splitting... ${(100 * (this.b / this.buf.length)).toFixed(0)}%`;\n\t\t\t\t\tsetTimeout(() => {this.continue();}, 20);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Once, after the while loop is broken...\n\n\t\tif (this.a !== this.b) {\t\t\t\t\t\t\t// We haven't added the last line before EOF.\n\t\t\tlet line = this.buf.slice(this.a, this.b);\n\t\t\tthis.append(line);\n\t\t}\n\n\t\tlet cb = this.callback; cb(this.lines);\n\t\tthis.shutdown();\n\t};\n\n\tsetTimeout(() => {loader.continue();}, 0);\t\t// setTimeout especially required here since there's no async load() function in this one.\n\treturn loader;\n}\n\n// ------------------------------------------------------------------------------------------------------------------------------\n\nfunction split_buffer_alternative(buf) {\n\n\t// Split a binary buffer into an array of binary buffers corresponding to lines.\n\n\tlet lines = [];\n\tlet search = Buffer.from(\"\\n\");\n\tlet off = 0;\n\n\tif (buf.length > 3 && buf[0] === 239 && buf[1] === 187 && buf[2] === 191) {\n\t\toff = 3;\t\t\t\t\t\t\t\t// Skip byte order mark (BOM).\n\t}\n\n\twhile (true) {\n\n\t\tlet hi = buf.indexOf(search, off);\n\n\t\tif (hi === -1) {\n\t\t\tif (off < buf.length) {\n\t\t\t\tlines.push(buf.slice(off));\n\t\t\t}\n\t\t\treturn lines;\n\t\t}\n\n\t\tif (buf[hi - 1] === 13) {\t\t\t\t// Discard \\r\n\t\t\tlines.push(buf.slice(off, hi - 1));\n\t\t} else {\n\t\t\tlines.push(buf.slice(off, hi));\n\t\t}\n\n\t\toff = hi + 1;\n\t}\n}\n"
  },
  {
    "path": "files/misc/state_logic.txt",
    "content": "State logic\nAs of 2020-05-26:\n                                                   /-----------> halt()\n                                                   |\n            1                                      |\n\"bestmove\" --> move() --> position_changed() --> behave() -----> go()\n                              ^       |            ^              ^\n                              |       |            |              |\nUser changes POSITION --------/       \\----> set_behaviour()      |\n                                     2             ^              |\n                                                   |              |\nUser changes BEHAVIOUR ----------------------------/              |\n                                            3                     |\n                                                                  |\nUser changes SEARCHMOVES -----> handle_searchmoves_change() ------/\n                                                                  |\n                                                                  |\nUser changes NODE LIMITS ------> handle_node_limit_change() ------/\n\n\nNotes:\n1. Only with relevant behaviour setting\n2. This path only sets \"halt\"\n3. Except that \"analysis_locked\" has its own function go_and_lock()\n"
  },
  {
    "path": "files/misc/uci.txt",
    "content": "Description of the universal chess interface (UCI)    April  2006\n=================================================================\n\n* The specification is independent of the operating system. For Windows,\n  the engine is a normal exe file, either a console or \"real\" windows application.\n\n* all communication is done via standard input and output with text commands,\n\n* The engine should boot and wait for input from the GUI,\n  the engine should wait for the \"isready\" or \"setoption\" command to set up its internal parameters\n  as the boot process should be as quick as possible.\n\n* the engine must always be able to process input from stdin, even while thinking.\n\n* all command strings the engine receives will end with '\\n',\n  also all commands the GUI receives should end with '\\n',\n  Note: '\\n' can be 0x0d or 0x0a0d or any combination depending on your OS.\n  If you use Engine and GUI in the same OS this should be no problem if you communicate in text mode,\n  but be aware of this when for example running a Linux engine in a Windows GUI.\n\n* arbitrary white space between tokens is allowed\n  Example: \"debug on\\n\" and  \"   debug     on  \\n\" and \"\\t  debug \\t  \\t\\ton\\t  \\n\"\n  all set the debug mode of the engine on.\n\n* The engine will always be in forced mode which means it should never start calculating\n  or pondering without receiving a \"go\" command first.\n\n* Before the engine is asked to search on a position, there will always be a position command\n  to tell the engine about the current position.\n\n* by default all the opening book handling is done by the GUI,\n  but there is an option for the engine to use its own book (\"OwnBook\" option, see below)\n\n* if the engine or the GUI receives an unknown command or token it should just ignore it and try to\n  parse the rest of the string in this line.\n  Examples: \"joho debug on\\n\" should switch the debug mode on given that joho is not defined,\n            \"debug joho on\\n\" will be undefined however.\n\n* if the engine receives a command which is not supposed to come, for example \"stop\" when the engine is\n  not calculating, it should also just ignore it.\n\n\nMove format:\n------------\n\nThe move format is in long algebraic notation.\nA nullmove from the Engine to the GUI should be sent as 0000.\nExamples:  e2e4, e7e5, e1g1 (white short castling), e7e8q (for promotion)\n\n\n\nGUI to engine:\n--------------\n\nThese are all the command the engine gets from the interface.\n\n* uci\n    tell engine to use the uci (universal chess interface),\n    this will be sent once as a first command after program boot\n    to tell the engine to switch to uci mode.\n    After receiving the uci command the engine must identify itself with the \"id\" command\n    and send the \"option\" commands to tell the GUI which engine settings the engine supports if any.\n    After that the engine should send \"uciok\" to acknowledge the uci mode.\n    If no uciok is sent within a certain time period, the engine task will be killed by the GUI.\n\n* debug [ on | off ]\n    switch the debug mode of the engine on and off.\n    In debug mode the engine should send additional infos to the GUI, e.g. with the \"info string\" command,\n    to help debugging, e.g. the commands that the engine has received etc.\n    This mode should be switched off by default and this command can be sent\n    any time, also when the engine is thinking.\n\n* isready\n    this is used to synchronize the engine with the GUI. When the GUI has sent a command or\n    multiple commands that can take some time to complete,\n    this command can be used to wait for the engine to be ready again or\n    to ping the engine to find out if it is still alive.\n    E.g. this should be sent after setting the path to the tablebases as this can take some time.\n    This command is also required once before the engine is asked to do any search\n    to wait for the engine to finish initializing.\n    This command must always be answered with \"readyok\" and can be sent also when the engine is calculating\n    in which case the engine should also immediately answer with \"readyok\" without stopping the search.\n\n* setoption name <id> [value <x>]\n    this is sent to the engine when the user wants to change the internal parameters\n    of the engine. For the \"button\" type no value is needed.\n    One string will be sent for each parameter and this will only be sent when the engine is waiting.\n    The name and value of the option in <id> should not be case sensitive and can inlude spaces.\n    The substrings \"value\" and \"name\" should be avoided in <id> and <x> to allow unambiguous parsing,\n    for example do not use <name> = \"draw value\".\n    Here are some strings for the example below:\n        \"setoption name Nullmove value true\\n\"\n        \"setoption name Selectivity value 3\\n\"\n        \"setoption name Style value Risky\\n\"\n        \"setoption name Clear Hash\\n\"\n        \"setoption name NalimovPath value c:\\chess\\tb\\4;c:\\chess\\tb\\5\\n\"\n\n* register\n    this is the command to try to register an engine or to tell the engine that registration\n    will be done later. This command should always be sent if the engine    has sent \"registration error\"\n    at program startup.\n    The following tokens are allowed:\n    * later\n        the user doesn't want to register the engine now.\n    * name <x>\n        the engine should be registered with the name <x>\n    * code <y>\n        the engine should be registered with the code <y>\n    Example:\n        \"register later\"\n        \"register name Stefan MK code 4359874324\"\n\n* ucinewgame\n    this is sent to the engine when the next search (started with \"position\" and \"go\") will be from\n    a different game. This can be a new game the engine should play or a new game it should analyse but\n    also the next position from a testsuite with positions only.\n    If the GUI hasn't sent a \"ucinewgame\" before the first \"position\" command, the engine shouldn't\n    expect any further ucinewgame commands as the GUI is probably not supporting the ucinewgame command.\n    So the engine should not rely on this command even though all new GUIs should support it.\n    As the engine's reaction to \"ucinewgame\" can take some time the GUI should always send \"isready\"\n    after \"ucinewgame\" to wait for the engine to finish its operation.\n\n* position [fen <fenstring> | startpos ]  moves <move1> .... <movei>\n    set up the position described in fenstring on the internal board and\n    play the moves on the internal chess board.\n    if the game was played  from the start position the string \"startpos\" will be sent\n    Note: no \"new\" command is needed. However, if this position is from a different game than\n    the last position sent to the engine, the GUI should have sent a \"ucinewgame\" inbetween.\n\n* go\n    start calculating on the current position set up with the \"position\" command.\n    There are a number of commands that can follow this command, all will be sent in the same string.\n    If one command is not sent its value should be interpreted as it would not influence the search.\n    * searchmoves <move1> .... <movei>\n        restrict search to this moves only\n        Example: After \"position startpos\" and \"go infinite searchmoves e2e4 d2d4\"\n        the engine should only search the two moves e2e4 and d2d4 in the initial position.\n    * ponder\n        start searching in pondering mode.\n        Do not exit the search in ponder mode, even if it's mate!\n        This means that the last move sent in in the position string is the ponder move.\n        The engine can do what it wants to do, but after a \"ponderhit\" command\n        it should execute the suggested move to ponder on. This means that the ponder move sent by\n        the GUI can be interpreted as a recommendation about which move to ponder. However, if the\n        engine decides to ponder on a different move, it should not display any mainlines as they are\n        likely to be misinterpreted by the GUI because the GUI expects the engine to ponder\n        on the suggested move.\n    * wtime <x>\n        white has x msec left on the clock\n    * btime <x>\n        black has x msec left on the clock\n    * winc <x>\n        white increment per move in mseconds if x > 0\n    * binc <x>\n        black increment per move in mseconds if x > 0\n    * movestogo <x>\n        there are x moves to the next time control,\n        this will only be sent if x > 0,\n        if you don't get this and get the wtime and btime it's sudden death\n    * depth <x>\n        search x plies only.\n    * nodes <x>\n        search x nodes only,\n    * mate <x>\n        search for a mate in x moves\n    * movetime <x>\n        search exactly x mseconds\n    * infinite\n        search until the \"stop\" command. Do not exit the search without being told so in this mode!\n\n* stop\n    stop calculating as soon as possible,\n    don't forget the \"bestmove\" and possibly the \"ponder\" token when finishing the search\n\n* ponderhit\n    the user has played the expected move. This will be sent if the engine was told to ponder on the same move\n    the user has played. The engine should continue searching but switch from pondering to normal search.\n\n* quit\n    quit the program as soon as possible\n\n\nEngine to GUI:\n--------------\n\n* id\n    * name <x>\n        this must be sent after receiving the \"uci\" command to identify the engine,\n        e.g. \"id name Shredder X.Y\\n\"\n    * author <x>\n        this must be sent after receiving the \"uci\" command to identify the engine,\n        e.g. \"id author Stefan MK\\n\"\n\n* uciok\n    Must be sent after the id and optional options to tell the GUI that the engine\n    has sent all infos and is ready in uci mode.\n\n* readyok\n    This must be sent when the engine has received an \"isready\" command and has\n    processed all input and is ready to accept new commands now.\n    It is usually sent after a command that can take some time to be able to wait for the engine,\n    but it can be used anytime, even when the engine is searching,\n    and must always be answered with \"isready\".\n\n* bestmove <move1> [ ponder <move2> ]\n    the engine has stopped searching and found the move <move> best in this position.\n    the engine can send the move it likes to ponder on. The engine must not start pondering automatically.\n    this command must always be sent if the engine stops searching, also in pondering mode if there is a\n    \"stop\" command, so for every \"go\" command a \"bestmove\" command is needed!\n    Directly before that the engine should send a final \"info\" command with the final search information,\n    the the GUI has the complete statistics about the last search.\n\n* copyprotection\n    this is needed for copyprotected engines. After the uciok command the engine can tell the GUI,\n    that it will check the copy protection now. This is done by \"copyprotection checking\".\n    If the check is ok the engine should send \"copyprotection ok\", otherwise \"copyprotection error\".\n    If there is an error the engine should not function properly but should not quit alone.\n    If the engine reports \"copyprotection error\" the GUI should not use this engine\n    and display an error message instead!\n    The code in the engine can look like this\n        TellGUI(\"copyprotection checking\\n\");\n        // ... check the copy protection here ...\n        if(ok)\n            TellGUI(\"copyprotection ok\\n\");\n        else\n            TellGUI(\"copyprotection error\\n\");\n\n* registration\n    this is needed for engines that need a username and/or a code to function with all features.\n    Analog to the \"copyprotection\" command the engine can send \"registration checking\"\n    after the uciok command followed by either \"registration ok\" or \"registration error\".\n    Also after every attempt to register the engine it should answer with \"registration checking\"\n    and then either \"registration ok\" or \"registration error\".\n    In contrast to the \"copyprotection\" command, the GUI can use the engine after the engine has\n    reported an error, but should inform the user that the engine is not properly registered\n    and might not use all its features.\n    In addition the GUI should offer to open a dialog to\n    enable registration of the engine. To try to register an engine the GUI can send\n    the \"register\" command.\n    The GUI has to always answer with the \"register\" command    if the engine sends \"registration error\"\n    at engine startup (this can also be done with \"register later\")\n    and tell the user somehow that the engine is not registered.\n    This way the engine knows that the GUI can deal with the registration procedure and the user\n    will be informed that the engine is not properly registered.\n\n* info\n    the engine wants to send information to the GUI. This should be done whenever one of the info has changed.\n    The engine can send only selected infos or multiple infos with one info command,\n    e.g. \"info currmove e2e4 currmovenumber 1\" or\n         \"info depth 12 nodes 123456 nps 100000\".\n    Also all infos belonging to the pv should be sent together\n    e.g. \"info depth 2 score cp 214 time 1242 nodes 2124 nps 34928 pv e2e4 e7e5 g1f3\"\n    I suggest to start sending \"currmove\", \"currmovenumber\", \"currline\" and \"refutation\" only after one second\n    to avoid too much traffic.\n    Additional info:\n    * depth <x>\n        search depth in plies\n    * seldepth <x>\n        selective search depth in plies,\n        if the engine sends seldepth there must also be a \"depth\" present in the same string.\n    * time <x>\n        the time searched in ms, this should be sent together with the pv.\n    * nodes <x>\n        x nodes searched, the engine should send this info regularly\n    * pv <move1> ... <movei>\n        the best line found\n    * multipv <num>\n        this for the multi pv mode.\n        for the best move/pv add \"multipv 1\" in the string when you send the pv.\n        in k-best mode always send all k variants in k strings together.\n    * score\n        * cp <x>\n            the score from the engine's point of view in centipawns.\n        * mate <y>\n            mate in y moves, not plies.\n            If the engine is getting mated use negative values for y.\n        * lowerbound\n            the score is just a lower bound.\n        * upperbound\n            the score is just an upper bound.\n    * currmove <move>\n        currently searching this move\n    * currmovenumber <x>\n        currently searching move number x, for the first move x should be 1 not 0.\n    * hashfull <x>\n        the hash is x permill full, the engine should send this info regularly\n    * nps <x>\n        x nodes per second searched, the engine should send this info regularly\n    * tbhits <x>\n        x positions where found in the endgame table bases\n    * sbhits <x>\n        x positions where found in the shredder endgame databases\n    * cpuload <x>\n        the cpu usage of the engine is x permill.\n    * string <str>\n        any string str which will be displayed be the engine,\n        if there is a string command the rest of the line will be interpreted as <str>.\n    * refutation <move1> <move2> ... <movei>\n        move <move1> is refuted by the line <move2> ... <movei>, i can be any number >= 1.\n        Example: after move d1h5 is searched, the engine can send\n        \"info refutation d1h5 g6h5\"\n        if g6h5 is the best answer after d1h5 or if g6h5 refutes the move d1h5.\n        if there is no refutation for d1h5 found, the engine should just send\n        \"info refutation d1h5\"\n        The engine should only send this if the option \"UCI_ShowRefutations\" is set to true.\n    * currline <cpunr> <move1> ... <movei>\n        this is the current line the engine is calculating. <cpunr> is the number of the cpu if\n        the engine is running on more than one cpu. <cpunr> = 1,2,3....\n        if the engine is just using one cpu, <cpunr> can be omitted.\n        If <cpunr> is greater than 1, always send all k lines in k strings together.\n        The engine should only send this if the option \"UCI_ShowCurrLine\" is set to true.\n\n\n* option\n    This command tells the GUI which parameters can be changed in the engine.\n    This should be sent once at engine startup after the \"uci\" and the \"id\" commands\n    if any parameter can be changed in the engine.\n    The GUI should parse this and build a dialog for the user to change the settings.\n    Note that not every option needs to appear in this dialog as some options like\n    \"Ponder\", \"UCI_AnalyseMode\", etc. are better handled elsewhere or are set automatically.\n    If the user wants to change some settings, the GUI will send a \"setoption\" command to the engine.\n    Note that the GUI need not send the setoption command when starting the engine for every option if\n    it doesn't want to change the default value.\n    For all allowed combinations see the examples below,\n    as some combinations of this tokens don't make sense.\n    One string will be sent for each parameter.\n    * name <id>\n        The option has the name id.\n        Certain options have a fixed value for <id>, which means that the semantics of this option is fixed.\n        Usually those options should not be displayed in the normal engine options window of the GUI but\n        get a special treatment. \"Pondering\" for example should be set automatically when pondering is\n        enabled or disabled in the GUI options. The same for \"UCI_AnalyseMode\" which should also be set\n        automatically by the GUI. All those certain options have the prefix \"UCI_\" except for the\n        first 6 options below. If the GUI gets an unknown Option with the prefix \"UCI_\", it should just\n        ignore it and not display it in the engine's options dialog.\n        * <id> = Hash, type is spin\n            the value in MB for memory for hash tables can be changed,\n            this should be answered with the first \"setoptions\" command at program boot\n            if the engine has sent the appropriate \"option name Hash\" command,\n            which should be supported by all engines!\n            So the engine should use a very small hash first as default.\n        * <id> = NalimovPath, type string\n            this is the path on the hard disk to the Nalimov compressed format.\n            Multiple directories can be concatenated with \";\"\n        * <id> = NalimovCache, type spin\n            this is the size in MB for the cache for the nalimov table bases\n            These last two options should also be present in the initial options exchange dialog\n            when the engine is booted if the engine supports it\n        * <id> = Ponder, type check\n            this means that the engine is able to ponder.\n            The GUI will send this whenever pondering is possible or not.\n            Note: The engine should not start pondering on its own if this is enabled, this option is only\n            needed because the engine might change its time management algorithm when pondering is allowed.\n        * <id> = OwnBook, type check\n            this means that the engine has its own book which is accessed by the engine itself.\n            if this is set, the engine takes care of the opening book and the GUI will never\n            execute a move out of its book for the engine. If this is set to false by the GUI,\n            the engine should not access its own book.\n        * <id> = MultiPV, type spin\n            the engine supports multi best line or k-best mode. the default value is 1\n        * <id> = UCI_ShowCurrLine, type check, should be false by default,\n            the engine can show the current line it is calculating. see \"info currline\" above.\n        * <id> = UCI_ShowRefutations, type check, should be false by default,\n            the engine can show a move and its refutation in a line. see \"info refutations\" above.\n        * <id> = UCI_LimitStrength, type check, should be false by default,\n            The engine is able to limit its strength to a specific Elo number,\n            This should always be implemented together with \"UCI_Elo\".\n        * <id> = UCI_Elo, type spin\n            The engine can limit its strength in Elo within this interval.\n            If UCI_LimitStrength is set to false, this value should be ignored.\n            If UCI_LimitStrength is set to true, the engine should play with this specific strength.\n            This should always be implemented together with \"UCI_LimitStrength\".\n        * <id> = UCI_AnalyseMode, type check\n            The engine wants to behave differently when analysing or playing a game.\n            For example when playing it can use some kind of learning.\n            This is set to false if the engine is playing a game, otherwise it is true.\n        * <id> = UCI_Opponent, type string\n            With this command the GUI can send the name, title, elo and if the engine is playing a human\n            or computer to the engine.\n            The format of the string has to be [GM|IM|FM|WGM|WIM|none] [<elo>|none] [computer|human] <name>\n            Examples:\n            \"setoption name UCI_Opponent value GM 2800 human Gary Kasparov\"\n            \"setoption name UCI_Opponent value none none computer Shredder\"\n        * <id> = UCI_EngineAbout, type string\n            With this command, the engine tells the GUI information about itself, for example a license text,\n            usually it doesn't make sense that the GUI changes this text with the setoption command.\n            Example:\n            \"option name UCI_EngineAbout type string default Shredder by Stefan Meyer-Kahlen, see www.shredderchess.com\"\n        * <id> = UCI_ShredderbasesPath, type string\n            this is either the path to the folder on the hard disk containing the Shredder endgame databases or\n            the path and filename of one Shredder endgame datbase.\n        * <id> = UCI_SetPositionValue, type string\n            the GUI can send this to the engine to tell the engine to use a certain value in centipawns from white's\n            point of view if evaluating this specifix position.\n            The string can have the formats:\n            <value> + <fen> | clear + <fen> | clearall\n\n    * type <t>\n        The option has type t.\n        There are 5 different types of options the engine can send\n        * check\n            a checkbox that can either be true or false\n        * spin\n            a spin wheel that can be an integer in a certain range\n        * combo\n            a combo box that can have different predefined strings as a value\n        * button\n            a button that can be pressed to send a command to the engine\n        * string\n            a text field that has a string as a value,\n            an empty string has the value \"<empty>\"\n    * default <x>\n        the default value of this parameter is x\n    * min <x>\n        the minimum value of this parameter is x\n    * max <x>\n        the maximum value of this parameter is x\n    * var <x>\n        a predefined value of this parameter is x\n    Examples:\n    Here are 5 strings for each of the 5 possible types of options\n        \"option name Nullmove type check default true\\n\"\n        \"option name Selectivity type spin default 2 min 0 max 4\\n\"\n        \"option name Style type combo default Normal var Solid var Normal var Risky\\n\"\n        \"option name NalimovPath type string default c:\\\\n\"\n        \"option name Clear Hash type button\\n\"\n\n\n\nExamples:\n---------\n\nThis is how the communication when the engine boots can look like:\n\nGUI         engine\n\n// tell the engine to switch to UCI mode\n\nuci\n\n// engine identify\n\n            id name Shredder\n            id author Stefan MK\n\n// engine sends the options it can change\n// the engine can change the hash size from 1 to 128 MB\n\n            option name Hash type spin default 1 min 1 max 128\n\n// the engine supports Nalimov endgame tablebases\n\n            option name NalimovPath type string default <empty>\n            option name NalimovCache type spin default 1 min 1 max 32\n\n// the engine can switch off Nullmove and set the playing style\n\n            option name Nullmove type check default true\n            option name Style type combo default Normal var Solid var Normal var Risky\n\n// the engine has sent all parameters and is ready\n\n            uciok\n\n// Note: here the GUI can already send a \"quit\" command if it just wants to find out\n// details about the engine, so the engine should not initialize its internal\n// parameters before here.\n\n// now the GUI sets some values in the engine\n// set hash to 32 MB\n\nsetoption name Hash value 32\n\n// init tbs\n\nsetoption name NalimovCache value 1\nsetoption name NalimovPath value d:\\tb;c\\tb\n\n// waiting for the engine to finish initializing\n// this command and the answer is required here!\n\nisready\n\n// engine has finished setting up the internal values\n\n            readyok\n\n// now we are ready to go\n\n// if the GUI is supporting it, tell the engine that is is\n// searching on a game that it hasn't searched on before\n\nucinewgame\n\n// if the engine supports the \"UCI_AnalyseMode\" option and the next search is supposed to\n// be an analysis, the GUI should set \"UCI_AnalyseMode\" to true if it is currently\n// set to false with this engine\n\nsetoption name UCI_AnalyseMode value true\n\n// tell the engine to search infinite from the start position after 1.e4 e5\n\nposition startpos moves e2e4 e7e5\ngo infinite\n\n// the engine starts sending infos about the search to the GUI\n// (only some examples are given)\n\n            info depth 1 seldepth 0\n            info score cp 13  depth 1 nodes 13 time 15 pv f1b5\n            info depth 2 seldepth 2\n            info nps 15937\n            info score cp 14  depth 2 nodes 255 time 15 pv f1c4 f8c5\n            info depth 2 seldepth 7 nodes 255\n            info depth 3 seldepth 7\n            info nps 26437\n            info score cp 20  depth 3 nodes 423 time 15 pv f1c4 g8f6 b1c3\n            info nps 41562\n            ....\n\n// here the user has seen enough and asks to stop the searching\n\nstop\n\n// the engine has finished searching and is sending the bestmove command\n// which is needed for every \"go\" command sent to tell the GUI\n// that the engine is ready again\n\n            bestmove g1f3 ponder d8f6\n\n\n\nChess960\n========\n\nUCI could easily be extended to support Chess960 (also known as Fischer Random Chess).\n\nThe engine has to tell the GUI that it is capable of playing Chess960 and the GUI has to tell\nthe engine that is should play according to the Chess960 rules.\nThis is done by the special engine option UCI_Chess960. If the engine knows about Chess960\nit should send the command 'option name UCI_Chess960 type check default false'\nto the GUI at program startup.\nWhenever a Chess960 game is played, the GUI should set this engine option to 'true'.\n\nCastling is different in Chess960 and the white king move when castling short is not always e1g1.\nA king move could both be the castling king move or just a normal king move.\nThis is why castling moves are sent in the form king \"takes\" his own rook.\nExample: e1h1 for the white short castle move in the normal chess start position.\n\nIn EPD and FEN position strings specifying the castle rights with w and q is not enough as\nthere could be more than one rook on the right or left side of the king.\nThis is why the castle rights are specified with the letter of the castle rook's line.\nUpper case letters for white's and lower case letters for black's castling rights.\nExample: The normal chess position would be:\n\nrnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w AHah -\n"
  },
  {
    "path": "files/res/linux/nibbler.desktop",
    "content": "[Desktop Entry]\nCategories=Game;BoardGame;\nComment=Nibbler is a real-time analysis GUI for Leela Chess Zero (Lc0).\nExec=nibbler\nGenericName=Chess Analysis GUI\nIcon=nibbler\nName=Nibbler\nTerminal=false\nType=Application\n"
  },
  {
    "path": "files/scripts/builder.py",
    "content": "import json, os, shutil, zipfile\n\nzips = {\n\t\"windows\": \"scripts/electron_zipped/electron-v9.4.4-win32-x64.zip\",\n\t\"linux\": \"scripts/electron_zipped/electron-v9.4.4-linux-x64.zip\",\n}\n\n# To build Nibbler: (for info see https://electronjs.org/docs/tutorial/application-distribution)\n#\n# Obtain the appropriate Electron asset named above, from https://github.com/electron/electron/releases\n# Create a folder at scripts/electron_zipped and place the Electron asset in it\n# Run ./builder.py\n#\n# Note: later Electron versions work also, but with a couple minor glitches:\n# https://github.com/rooklift/nibbler/issues/140\n\nos.chdir(os.path.dirname(os.path.realpath(__file__)))\t\t# Ensure we're in builder.py's directory.\nos.chdir(\"..\")\t\t\t\t\t\t\t\t\t\t\t\t# Then come up one level.\n\nwith open(\"src/package.json\") as f:\n\tversion = json.load(f)[\"version\"]\n\nfor key, value in zips.items():\n\n\t# check if electron archives exist\n\tif not os.path.exists(value):\n\t\tprint(\"Skipping {} build: {} not present.\".format(key, value))\n\t\tcontinue\n\n\t# make build directory\n\tbuild_dir = \"scripts/dist/nibbler-{}-{}\".format(version, key)\n\tos.makedirs(build_dir)\n\n\t# copy files\n\tbuild_res_dir = os.path.join(build_dir, \"resources\")\n\tshutil.copytree(\"res\", build_res_dir)\n\tshutil.copytree(\"src\", os.path.join(build_res_dir, \"app\"))\n\n\t# extract electron\n\tprint(\"Extracting for {}...\".format(key))\n\tz = zipfile.ZipFile(value, \"r\")\n\tz.extractall(build_dir)\n\tz.close()\n\n\t# rename executable\n\tif os.path.exists(os.path.join(build_dir, \"electron.exe\")):\n\t\tos.rename(os.path.join(build_dir, \"electron.exe\"), os.path.join(build_dir, \"nibbler.exe\"))\n\tif os.path.exists(os.path.join(build_dir, \"electron\")):\n\t\tos.rename(os.path.join(build_dir, \"electron\"), os.path.join(build_dir, \"nibbler\"))\n"
  },
  {
    "path": "files/scripts/install.sh",
    "content": "#!/usr/bin/env bash\nset -e\n\nBASE_URL=\"https://github.com/rooklift/nibbler\"\n\n# check curl\nif ! which curl 1>/dev/null 2>&1 ; then\n    echo \"Please install curl and make sure it's added to \\$PATH\"\n    echo \"Aborting\"\n    exit 1\nfi\n\n# start\necho \"You are installing Nibbler\"\n\n# get the latest release version\nVERSION=$(curl -fs -o /dev/null -w \"%{redirect_url}\" \"${BASE_URL}/releases/latest\" | xargs basename)\necho \"Latest release is ${VERSION}\"\nZIP_NAME=\"nibbler-${VERSION#v}-linux.zip\"\nZIP_URL=\"${BASE_URL}/releases/download/${VERSION}/${ZIP_NAME}\"\n\n# create and enter temp dir\nTEMP_DIR=$(mktemp -d)\ncd \"${TEMP_DIR}\"\n\n# download\necho \"Downloading release from ${ZIP_URL}\"\nif curl -fOL \"${ZIP_URL}\"; then\n    echo \"Successfully downloaded ${ZIP_NAME}\"\nelse\n    echo \"Failed to download ${ZIP_NAME}\"\n    echo \"Exiting\"\n    exit 1\nfi\n\n# extract\necho \"Extracting...\"\nunzip -q \"${ZIP_NAME}\"\necho \"Successfully extracted Nibbler\"\nUNZIPPED_NAME=\"${ZIP_NAME%.zip}\"\n\n# prepare\nchmod +x \"${UNZIPPED_NAME}/nibbler\"\nmv \"${UNZIPPED_NAME}/resources/\"{nibbler.png,nibbler.svg,linux} ./\n\n# check if already installed\nINSTALL_DIR=\"/opt/nibbler\"\nif [[ -d \"${INSTALL_DIR}\" ]]; then\n    echo \"${INSTALL_DIR} already exists!\"\n    echo \"It looks like there is an existing installation of Nibbler on your system\"\n    read -p \"Would you like to overwrite it? [y/n]\" -n 1 CONFIRM_INSTALL\n    echo\n    if ! [[ \"$CONFIRM_INSTALL\" =~ ^[Yy]$ ]]; then\n        echo \"Aborting\"\n        exit 1\n    fi\nfi\n\n# start install\nBIN_SYMLINK_PATH=\"/usr/local/bin/nibbler\"\nDESKTOP_ENTRY_PATH=\"/usr/local/share/applications/nibbler.desktop\"\nICON_PNG_PATH=\"/usr/local/share/icons/hicolor/512x512/apps/nibbler.png\"\nICON_SVG_PATH=\"/usr/local/share/icons/hicolor/scalable/apps/nibbler.svg\"\necho \"Installing Nibbler to ${INSTALL_DIR}\"\necho \"Creating binary symlink at ${BIN_SYMLINK_PATH}\"\necho \"Installing desktop entry to ${DESKTOP_ENTRY_PATH}\"\necho \"Installing icons to ${ICON_PNG_PATH} and ${ICON_SVG_PATH}\"\necho \"This will require sudo privilege.\"\n\n# remove old and make sure directories are created\nfor FILE in \"${INSTALL_DIR}\" \"${BIN_SYMLINK_PATH}\" \"${DESKTOP_ENTRY_PATH}\" \\\n    \"${ICON_PNG_PATH}\" \"${ICON_SVG_PATH}\"; do\n    sudo rm -rf \"$FILE\"\n    sudo mkdir -p $(dirname \"$FILE\")\ndone\n\n# install new\nsudo mv \"${UNZIPPED_NAME}\" \"${INSTALL_DIR}\"\nsudo ln -s \"${INSTALL_DIR}/nibbler\" \"${BIN_SYMLINK_PATH}\"\nsed -i \"s|^Exec=.*|Exec=nibbler --no-sandbox|\" \"linux/nibbler.desktop\"\nsudo mv \"linux/nibbler.desktop\" \"${DESKTOP_ENTRY_PATH}\"\nsudo mv \"nibbler.png\" \"${ICON_PNG_PATH}\"\nsudo mv \"nibbler.svg\" \"${ICON_SVG_PATH}\"\n\n# done\necho \"Successfully installed Nibbler ${VERSION}\"\necho \"You will be able to find Nibbler in your launcher shortly\"\n"
  },
  {
    "path": "files/scripts/install_mac.sh",
    "content": "#!/bin/bash\nset -euo pipefail\n\nmsg() {\n\tprintf \"=======================\\n\"\n\tprintf \"\\033[93m%s\\033[0m\\n\" \"$1\"\n}\n\nok() {\n\tprintf \"\\033[92m \\u2714 %s\\033[0m\\n\" \"$1\"\n}\n\nWORKDIR=\"/tmp/nibbler-install\"\nVERSION=\"2.5.8\"\nELECTRON_VERSION=\"41.0.3\"\n\nARCH=\"$(uname -m)\"\ncase \"$ARCH\" in\n\tarm64) ELECTRON_ARCH=\"arm64\" ;;\n\tx86_64) ELECTRON_ARCH=\"x64\" ;;\n\t*) msg \"Unsupported architecture: $ARCH\" >&2; exit 1 ;;\nesac\n\n# robustness fix... clean exit\ncleanup_on_failure() {\n\tlocal exit_code=\"$1\"\n\tif [[ \"$exit_code\" -ne 0 ]]; then\n\t\trm -rf \"$WORKDIR\"\n\tfi\n}\ntrap 'cleanup_on_failure \"$?\"' EXIT\n\nrm -rf \"$WORKDIR\"\nmkdir -p \"$WORKDIR\"\ncd \"$WORKDIR\"\n\nmsg \"Downloading nibbler v${VERSION}...\"\ncurl -# -fL -O \"https://github.com/rooklift/nibbler/archive/refs/tags/v${VERSION}.zip\"\nunzip -q \"v${VERSION}.zip\"\nNIBBLER=\"nibbler-${VERSION}\"\n[[ -d \"$NIBBLER\" ]] || { msg \"Failed to fetch nibbler...\"; exit 1; }\nok \"Fetched nibbler!\"\n\nmsg \"Downloading electron v${ELECTRON_VERSION}...\"\nELECTRON_ZIP=\"electron-v${ELECTRON_VERSION}-darwin-${ELECTRON_ARCH}.zip\"\ncurl -# -fL -O \"https://github.com/electron/electron/releases/download/v${ELECTRON_VERSION}/${ELECTRON_ZIP}\"\n\nmkdir -p electron\ncd electron\nunzip -q \"../${ELECTRON_ZIP}\"\n[[ -d \"$WORKDIR/electron/Electron.app\" ]] || { msg \"Failed to fetch electron...\"; exit 1; }\nok \"Fetched electron!\"\n\nmsg \"Assembling Nibbler.app...\"\nAPP=\"$WORKDIR/electron/Electron.app\"\nAPP_ROOT=\"$APP/Contents/Resources/app\"\n\nrm -f \"$APP/Contents/Resources/default_app.asar\"\nrm -rf \"$APP_ROOT\"\ncp -R \"$WORKDIR/nibbler-${VERSION}/files/src\" \"$APP_ROOT\"\n\nPLIST=\"$APP/Contents/Info.plist\"\n/usr/libexec/PlistBuddy -c \"Set :CFBundleDisplayName Nibbler\" \"$PLIST\"\n/usr/libexec/PlistBuddy -c \"Set :CFBundleName Nibbler\" \"$PLIST\"\n/usr/libexec/PlistBuddy -c \"Set :CFBundleIdentifier com.rooklift.nibbler\" \"$PLIST\"\n\nmv \"$APP\" \"$WORKDIR/electron/Nibbler.app\"\nok \"Built Nibbler.app!\"\n\nAPP_DIR=\"$WORKDIR/electron\"\nAPP=\"$APP_DIR/Nibbler.app\"\nAPPS_DIR=\"$HOME/Applications\"\nAPPS_APP=\"$APPS_DIR/Nibbler.app\"\n\nread -r -p \"Move Nibbler.app to ~/Applications and overwrite any existing copy? [Y/n] \" MOVE_APP\n\nmsg \"Nice, here you go!\"\nif [[ -z \"$MOVE_APP\" || \"$MOVE_APP\" =~ ^[Yy]$ ]]; then\n\tmsg \"Installing Nibbler.app to ~/Applications...\"\n\tmkdir -p \"$APPS_DIR\"\n\trm -rf \"$APPS_APP\"\n\tmv \"$APP\" \"$APPS_DIR/\"\n\tok \"Installed Nibbler.app to ${APPS_APP}!\"\n\topen -R \"$APPS_APP\"\nelse\n\topen \"$APP_DIR\"\n\tmsg \"IMPORTANT: Make sure to move app under Applications/ or ~/Applications!\"\nfi\n"
  },
  {
    "path": "files/src/main.js",
    "content": "\"use strict\";\n\nconst electron = require(\"electron\");\n\n// The docs are a bit vague but it seems there's a limited timeframe\n// in which command line flags can be passed, so do this ASAP...\n\nelectron.app.commandLine.appendSwitch(\"js-flags\", \"--expose_gc\");\n\n// Config...\n\nconst config_io = require(\"./modules/config_io\");\nlet config = config_io.load()[1];\t\t\t\t\t// Do this early, it's a needed global.\n\n// disableHardwareAcceleration() needs to be called before the app is ready...\n\nlet actually_disabled_hw_accel = false;\n\nif (config.disable_hw_accel) {\n\ttry {\n\t\telectron.app.disableHardwareAcceleration();\n\t\tactually_disabled_hw_accel = true;\n\t\tconsole.log(\"Hardware acceleration for Nibbler (GUI, not engine) disabled by config setting.\");\n\t} catch (err) {\n\t\tconsole.log(\"Failed to disable hardware acceleration.\");\n\t}\n}\n\n// Other requires...\n\nconst alert = require(\"./modules/alert_main\");\nconst custom_uci = require(\"./modules/custom_uci\");\nconst engineconfig_io = require(\"./modules/engineconfig_io\");\nconst messages = require(\"./modules/messages\");\nconst path = require(\"path\");\nconst running_as_electron = require(\"./modules/running_as_electron\");\nconst stringify = require(\"./modules/stringify\");\nconst translate = require(\"./modules/translate\");\nconst url = require(\"url\");\n\ntranslate.register_startup_language(config.language);\n\n// We want sync save and open dialogs. In Electron 5 we could get these by calling\n// showSaveDialog or showOpenDialog without a callback, but in Electron 6 this no\n// longer works and we must call new functions. So find out if they exist...\n\nconst save_dialog = electron.dialog.showSaveDialogSync || electron.dialog.showSaveDialog;\nconst open_dialog = electron.dialog.showOpenDialogSync || electron.dialog.showOpenDialog;\n\n// Note that as the user adjusts menu items, our copy of the config will become\n// out of date. The renderer is responsible for having an up-to-date copy.\n\nlet win;\nlet menu = menu_build();\nlet menu_is_set = false;\n\nlet have_sent_quit = false;\nlet have_received_terminate = false;\n\nlet loaded_engine = \"\";\nlet loaded_weights = \"\";\nlet loaded_evalfile = \"\";\n\nlet have_warned_hw_accel_setting = false;\n\n// Avoid a theoretical race by checking whether the ready event has already occurred,\n// otherwise set an event listener for it...\n\nif (electron.app.isReady()) {\n\tstartup();\n} else {\n\telectron.app.once(\"ready\", () => {\n\t\tstartup();\n\t});\n}\n\n// ----------------------------------------------------------------------------------\n\nfunction startup() {\n\n\tlet desired_zoomfactor = 1 / electron.screen.getPrimaryDisplay().scaleFactor;\n\n\twin = new electron.BrowserWindow({\n\t\twidth: config.width,\n\t\theight: config.height,\n\t\tbackgroundColor: \"#000000\",\n\t\tresizable: true,\n\t\tshow: false,\n\t\tuseContentSize: true,\n\t\twebPreferences: {\n\t\t\tbackgroundThrottling: false,\n\t\t\tcontextIsolation: false,\n\t\t\tnodeIntegration: true,\n\t\t\tspellcheck: false,\n\t\t\tzoomFactor: desired_zoomfactor\t\t// Unreliable, see https://github.com/electron/electron/issues/10572\n\t\t}\n\t});\n\n\twin.once(\"ready-to-show\", () => {\n\t\ttry {\n\t\t\twin.webContents.setZoomFactor(desired_zoomfactor);\t// This seems to work, note issue 10572 above.\n\t\t} catch (err) {\n\t\t\twin.webContents.zoomFactor = desired_zoomfactor;\t// The method above \"will be removed\" in future.\n\t\t}\n\t\twin.show();\n\t\twin.focus();\n\t});\n\n\twin.webContents.once(\"crashed\", () => {\n\t\talert(win, messages.renderer_crash);\n\t});\n\n\twin.webContents.once(\"unresponsive\", () => {\n\t\talert(win, messages.renderer_hang);\n\t});\n\n\twin.on(\"close\", (event) => {\t\t\t\t\t\t// We used to use .once() but I suppose there's a race condition if two events happen rapidly.\n\n\t\tif (!have_received_terminate) {\n\n\t\t\tevent.preventDefault();\t\t\t\t\t\t// Only a \"terminate\" message from the Renderer should close the app.\n\n\t\t\tif (!have_sent_quit) {\n\t\t\t\twin.webContents.send(\"call\", \"quit\");\t// Renderer's \"quit\" method runs. It then sends \"terminate\" back.\n\t\t\t\thave_sent_quit = true;\n\t\t\t}\n\n\t\t\t// Create a setTimeout that will make the app close without the renderer's help if it takes too long (due to a crash)...\n\n\t\t\tsetTimeout(() => {\n\t\t\t\tconsole.log(\"Renderer seems unresponsive, quitting anyway.\");\n\t\t\t\thave_received_terminate = true;\n\t\t\t\twin.close();\n\t\t\t}, 3000);\n\t\t}\n\t});\n\n\telectron.ipcMain.on(\"terminate\", () => {\n\t\thave_received_terminate = true;\t\t\t\t\t// Needed so the \"close\" handler (see above) knows to allow it.\n\t\twin.close();\n\t});\n\n\telectron.app.on(\"window-all-closed\", () => {\n\t\telectron.app.quit();\n\t});\n\n\telectron.ipcMain.once(\"renderer_ready\", () => {\n\n\t\tif (actually_disabled_hw_accel) {\n\t\t\twin.webContents.send(\"call\", {\n\t\t\t\tfn: \"console\",\n\t\t\t\targs: [\"Hardware acceleration is disabled.\"],\n\t\t\t});\n\t\t}\n\n\t\t// Open a file via command line. We must wait until the renderer has properly loaded before we do this.\n\t\t// While it might seem like we could do this after \"ready-to-show\" I'm not 100% sure that the renderer\n\t\t// will have fully loaded when that fires.\n\n\t\tlet filename = \"\";\n\n\t\tif (running_as_electron()) {\n\t\t\tif (process.argv.length > 2) {\n\t\t\t\tfilename = process.argv[process.argv.length - 1];\n\t\t\t}\n\t\t} else {\n\t\t\tif (process.argv.length > 1) {\n\t\t\t\tfilename = process.argv[process.argv.length - 1];\n\t\t\t}\n\t\t}\n\n\t\tif (filename !== \"\") {\n\t\t\twin.webContents.send(\"call\", {\n\t\t\t\tfn: \"open\",\n\t\t\t\targs: [filename]\n\t\t\t});\n\t\t}\n\t});\n\n\telectron.ipcMain.on(\"alert\", (event, msg) => {\n\t\talert(win, msg);\n\t});\n\n\telectron.ipcMain.on(\"set_title\", (event, msg) => {\n\t\twin.setTitle(msg);\n\t});\n\n\telectron.ipcMain.on(\"web_link\", (event, msg) => {\n\t\telectron.shell.openExternal(msg);\n\t});\n\n\telectron.ipcMain.on(\"ack_engine\", (event, msg) => {\n\t\tloaded_engine = msg;\n\t\tset_one_check(msg ? true : false, \"Engine\", \"Choose engine...\");\n\t});\n\n\telectron.ipcMain.on(\"ack_logfile\", (event, msg) => {\n\t\tset_one_check(msg ? true : false, \"Dev\", \"Logging\", \"Use logfile...\");\n\t});\n\n\telectron.ipcMain.on(\"ack_book\", (event, msg) => {\n\t\tset_one_check(msg === \"polyglot\", \"Play\", \"Use Polyglot book...\");\n\t\tset_one_check(msg === \"pgn\", \"Play\", \"Use PGN book...\");\n\t});\n\n\telectron.ipcMain.on(\"ack_node_limit\", (event, msg) => {\n\t\tset_checks(\"Engine\", \"Limit - normal\", msg);\n\t});\n\n\telectron.ipcMain.on(\"ack_special_node_limit\", (event, msg) => {\n\t\tset_checks(\"Engine\", \"Limit - auto-eval / play\", msg);\n\t});\n\n\telectron.ipcMain.on(\"ack_limit_by_time\", (event, msg) => {\n\t\tset_one_check(msg ? true : false, \"Engine\", \"Limit by time instead of nodes\");\n\t});\n\n\telectron.ipcMain.on(\"ack_setoption\", (event, msg) => {\n\n\t\t// These are received whenever the renderer actually sends a setoption UCI command.\n\t\t// But we also sometimes query some option and get a response indicating what the\n\t\t// last value we sent was, or \"\" if not applicable.\n\n\t\t// Expect msg.key to be a lowercase string\n\t\t// Expect msg.val to be a string, possibly \"\" (can use the fact that \"\" is false-ish)\n\n\t\t// REMEMBER TO UPDATE engine.js GUI_WANTS_TO_KNOW const WHEN THINGS ARE ADDED...\n\n\t\tswitch (msg.key) {\n\n\t\tcase \"weightsfile\":\n\t\t\tloaded_weights = msg.val;\n\t\t\tset_one_check(msg.val ? true : false, \"Engine\", \"Weights\", \"Lc0 WeightsFile...\");\n\t\t\tbreak;\n\n\t\tcase \"evalfile\":\n\t\t\tloaded_evalfile = msg.val;\n\t\t\tset_one_check(msg.val ? true : false, \"Engine\", \"Weights\", \"Stockfish EvalFile...\");\n\t\t\tbreak;\n\n\t\tcase \"syzygypath\":\n\t\t\tset_one_check(msg.val ? true : false, \"Engine\", \"Choose Syzygy path...\");\n\t\t\tbreak;\n\n\t\tcase \"backend\":\n\t\t\tset_checks(\"Engine\", \"Backend\", msg.val);\n\t\t\tbreak;\n\n\t\tcase \"threads\":\n\t\t\tset_checks(\"Engine\", \"Threads\", msg.val);\n\t\t\tbreak;\n\n\t\tcase \"hash\":\n\t\t\tlet mb = parseInt(msg.val, 10);\n\t\t\tif (Number.isNaN(mb) === false) {\n\t\t\t\tlet gb = Math.floor(mb / 1024);\n\t\t\t\tset_checks(\"Engine\", \"Hash\", `${gb} GB`);\n\t\t\t} else {\n\t\t\t\tset_checks(\"Engine\", \"Hash\", \"\");\t\t\t// i.e. clear all\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase \"multipv\":\n\t\t\tset_checks(\"Engine\", \"MultiPV\", msg.val);\t\t// If it's \"500\" it will clear all.\n\t\t\tbreak;\n\n\t\tcase \"temperature\":\t\t\t// Sketchy because there are equivalent representations.\n\t\t\tif (msg.val === \"0\" || msg.val === \"0.0\") {\n\t\t\t\tset_checks(\"Play\", \"Temperature\", \"0\");\n\t\t\t} else if (msg.val === \"1\" || msg.val === \"1.0\") {\n\t\t\t\tset_checks(\"Play\", \"Temperature\", \"1.0\");\n\t\t\t} else {\n\t\t\t\tset_checks(\"Play\", \"Temperature\", msg.val);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase \"tempdecaymoves\":\t\t// Not so sketchy because it should be a string of an integer.\n\t\t\tset_checks(\"Play\", \"Temp Decay Moves\", msg.val === \"0\" ? \"Infinite\" : msg.val);\n\t\t\tbreak;\n\n\t\tcase \"contemptmode\":\t\t// All the menu items are different from the UCI values...\n\t\t\tif (msg.val === \"white_side_analysis\") {\n\t\t\t\tset_checks(\"Engine\", \"Contempt Mode\", \"White analysis\");\n\t\t\t} else if (msg.val === \"black_side_analysis\") {\n\t\t\t\tset_checks(\"Engine\", \"Contempt Mode\", \"Black analysis\");\n\t\t\t} else {\n\t\t\t\tset_checks(\"Engine\", \"Contempt Mode\", msg.val);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase \"contempt\":\n\t\t\tset_checks(\"Engine\", \"Contempt\", msg.val);\n\t\t\tbreak;\n\n\t\tcase \"wdlcalibrationelo\":\n\t\t\tset_checks(\"Engine\", \"WDL Calibration Elo\", msg.val === \"0\" ? \"Use default WDL\" : msg.val);\n\t\t\tbreak;\n\n\t\tcase \"wdlevalobjectivity\":\n\t\t\tif (msg.val === \"1\") {\n\t\t\t\tset_checks(\"Engine\", \"WDL Eval Objectivity\", \"Yes\");\n\t\t\t} else if (msg.val === \"0\") {\n\t\t\t\tset_checks(\"Engine\", \"WDL Eval Objectivity\", \"No\");\n\t\t\t} else {\n\t\t\t\tset_checks(\"Engine\", \"WDL Eval Objectivity\", msg.val);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase \"scoretype\":\n\t\t\tset_checks(\"Engine\", \"Score Type\", msg.val);\n\t\t\tbreak;\n\n\t\t// REMEMBER TO UPDATE engine.js GUI_WANTS_TO_KNOW const WHEN THINGS ARE ADDED...\n\n\t\t}\n\n\t});\n\n\telectron.Menu.setApplicationMenu(menu);\n\tmenu_is_set = true;\n\n\t// Actually load the page last, I guess, so the event handlers above are already set up.\n\t// Send some needed info as a query.\n\n\tlet query = {};\n\tquery.user_data_path = electron.app.getPath(\"userData\");\n\tquery.zoomfactor = desired_zoomfactor;\n\n\twin.loadFile(\n\t\tpath.join(__dirname, \"nibbler.html\"),\n\t\t{query: query}\n\t);\n}\n\nfunction menu_build() {\n\n\tconst million = 1000 * 1000;\n\tconst billion = 1000 * million;\n\n\tlet scriptlist_in_menu = [];\n\n\tlet template = [\n\t\t{\n\t\t\tlabel: translate.t(\"File\"),\n\t\t\tsubmenu: [\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"About\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\tlet s = `Nibbler ${electron.app.getVersion()} in Electron ${process.versions.electron}\\n\\n`;\n\t\t\t\t\t\ts += `Engine: ${loaded_engine}\\nWeights: ${loaded_weights || loaded_evalfile || \"<auto>\"}`;\n\t\t\t\t\t\talert(win, s);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"New game\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+N\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"new_game\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"New 960 game\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+Shift+N\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"new_960\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Open PGN...\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+O\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\tlet files = open_dialog(win, {\n\t\t\t\t\t\t\tdefaultPath: config.pgn_dialog_folder,\n\t\t\t\t\t\t\tproperties: [\"openFile\"],\n\t\t\t\t\t\t\tfilters: [{name: \"PGN\", extensions: [\"pgn\"]}, {name: \"All files\", extensions: [\"*\"]}]\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (Array.isArray(files) && files.length > 0) {\n\t\t\t\t\t\t\tlet file = files[0];\n\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\tfn: \"open\",\n\t\t\t\t\t\t\t\targs: [file]\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t// Save the dir as the new default dir, in both processes.\n\t\t\t\t\t\t\tconfig.pgn_dialog_folder = path.dirname(file);\n\t\t\t\t\t\t\twin.webContents.send(\"set\", {pgn_dialog_folder: path.dirname(file)});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Load FEN / PGN from clipboard\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+Shift+V\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"load_fen_or_pgn_from_string\",\n\t\t\t\t\t\t\targs: [electron.clipboard.readText()]\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Save this game...\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+S\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\tif (config.save_enabled !== true) {\t\t// Note: exact test for true, not just any truthy value\n\t\t\t\t\t\t\talert(win, messages.save_not_enabled);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlet file = save_dialog(win, {\n\t\t\t\t\t\t\tdefaultPath: config.pgn_dialog_folder,\n\t\t\t\t\t\t\tfilters: [{name: \"PGN\", extensions: [\"pgn\"]}, {name: \"All files\", extensions: [\"*\"]}]\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (typeof file === \"string\" && file.length > 0) {\n\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\tfn: \"save\",\n\t\t\t\t\t\t\t\targs: [file]\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t// Save the dir as the new default dir, in both processes.\n\t\t\t\t\t\t\tconfig.pgn_dialog_folder = path.dirname(file);\n\t\t\t\t\t\t\twin.webContents.send(\"set\", {pgn_dialog_folder: path.dirname(file)});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Write PGN to clipboard\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+K\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"pgn_to_clipboard\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"PGN saved statistics\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"EV\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pgn_ev,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"pgn_ev\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Centipawns\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pgn_cp,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"pgn_cp\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"N (%)\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pgn_n,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"pgn_n\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"N (absolute)\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pgn_n_abs,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"pgn_n_abs\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"...out of total\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pgn_of_n,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"pgn_of_n\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Depth (A/B only)\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pgn_depth,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"pgn_depth\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"P\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pgn_p,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"pgn_p\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"V\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pgn_v,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"pgn_v\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Q\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pgn_q,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"pgn_q\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"U\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pgn_u,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"pgn_u\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"S\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pgn_s,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"pgn_s\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"M\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pgn_m,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"pgn_m\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"WDL\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pgn_wdl,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"pgn_wdl\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Cut\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+X\",\n\t\t\t\t\trole: \"cut\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Copy\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+C\",\n\t\t\t\t\trole: \"copy\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Paste\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+V\",\n\t\t\t\t\trole: \"paste\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Quit\"),\t\t\t\t// Presumably calls electron.app.quit(), which tries to\n\t\t\t\t\taccelerator: \"CommandOrControl+Q\",\t\t// close all windows, and quits iff it succeeds (which\n\t\t\t\t\trole: \"quit\"\t\t\t\t\t\t\t// it won't, because we prevent the initial close...)\n\t\t\t\t},\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\tlabel: translate.t(\"Tree\"),\n\t\t\tsubmenu: [\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Play engine choice\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"1st\"),\n\t\t\t\t\t\t\taccelerator: \"F1\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"play_info_index\",\n\t\t\t\t\t\t\t\t\targs: [0]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"2nd\"),\n\t\t\t\t\t\t\taccelerator: \"F2\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"play_info_index\",\n\t\t\t\t\t\t\t\t\targs: [1]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"3rd\"),\n\t\t\t\t\t\t\taccelerator: \"F3\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"play_info_index\",\n\t\t\t\t\t\t\t\t\targs: [2]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"4th\"),\n\t\t\t\t\t\t\taccelerator: \"F4\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"play_info_index\",\n\t\t\t\t\t\t\t\t\targs: [3]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Root\"),\n\t\t\t\t\taccelerator: \"Home\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"goto_root\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"End\"),\n\t\t\t\t\taccelerator: \"End\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"goto_end\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Backward\"),\n\t\t\t\t\taccelerator: \"Left\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"prev\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Forward\"),\n\t\t\t\t\taccelerator: \"Right\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"next\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Previous sibling\"),\n\t\t\t\t\taccelerator: \"Up\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"previous_sibling\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Next sibling\"),\n\t\t\t\t\taccelerator: \"Down\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"next_sibling\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Return to main line\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+R\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"return_to_main_line\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Promote line to main line\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+L\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"promote_to_main_line\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Promote line by 1 level\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+Up\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"promote\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Delete node\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+Backspace\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"delete_node\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Delete children\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"delete_children\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Delete siblings\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"delete_siblings\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Delete ALL other lines\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"delete_other_lines\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Show PGN games list\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+P\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"show_pgn_chooser\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Escape\"),\n\t\t\t\t\taccelerator: \"Escape\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"escape\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\tlabel: translate.t(\"Analysis\"),\n\t\t\tsubmenu: [\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Go\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+G\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"set_behaviour\",\n\t\t\t\t\t\t\targs: [\"analysis_free\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Go and lock engine\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+Shift+G\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"set_behaviour\",\n\t\t\t\t\t\t\targs: [\"analysis_locked\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Return to locked position\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"return_to_lock\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Halt\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+H\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"set_behaviour\",\n\t\t\t\t\t\t\targs: [\"halt\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Auto-evaluate line\"),\n\t\t\t\t\taccelerator: \"F12\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"set_behaviour\",\n\t\t\t\t\t\t\targs: [\"auto_analysis\"]\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Auto-evaluate line, backwards\"),\n\t\t\t\t\taccelerator: \"Shift+F12\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"set_behaviour\",\n\t\t\t\t\t\t\targs: [\"back_analysis\"]\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Show focus (searchmoves) buttons\"),\n\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\tchecked: config.searchmoves_buttons,\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\targs: [\"searchmoves_buttons\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Clear focus\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"clear_searchmoves\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Invert focus\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+I\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"invert_searchmoves\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Winrate POV\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Current\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.ev_pov !== \"w\" && config.ev_pov !== \"b\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Analysis\", \"Winrate POV\", \"Current\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {ev_pov: null});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"White\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.ev_pov === \"w\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Analysis\", \"Winrate POV\", \"White\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {ev_pov: \"w\"});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Black\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.ev_pov === \"b\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Analysis\", \"Winrate POV\", \"Black\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {ev_pov: \"b\"});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Centipawn POV\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Current\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.cp_pov !== \"w\" && config.cp_pov !== \"b\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Analysis\", \"Centipawn POV\", \"Current\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {cp_pov: null});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"White\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.cp_pov === \"w\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Analysis\", \"Centipawn POV\", \"White\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {cp_pov: \"w\"});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Black\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.cp_pov === \"b\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Analysis\", \"Centipawn POV\", \"Black\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {cp_pov: \"b\"});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Win / draw / loss POV\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Current\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.wdl_pov !== \"w\" && config.wdl_pov !== \"b\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Analysis\", \"Win / draw / loss POV\", \"Current\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {wdl_pov: null});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"White\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.wdl_pov === \"w\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Analysis\", \"Win / draw / loss POV\", \"White\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {wdl_pov: \"w\"});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Black\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.wdl_pov === \"b\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Analysis\", \"Win / draw / loss POV\", \"Black\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {wdl_pov: \"b\"});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"PV clicks\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Do nothing\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pv_click_event === 0,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Analysis\", \"PV clicks\", \"Do nothing\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {pv_click_event: 0});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Go there\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pv_click_event === 1,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Analysis\", \"PV clicks\", \"Go there\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {pv_click_event: 1});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Add to tree\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pv_click_event === 2,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Analysis\", \"PV clicks\", \"Add to tree\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {pv_click_event: 2});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Write infobox to clipboard\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"infobox_to_clipboard\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Forget all analysis\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+.\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"forget_analysis\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\tlabel: translate.t(\"Display\"),\n\t\t\tsubmenu: [\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Flip board\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+F\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\targs: [\"flip\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Arrows\"),\n\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\tchecked: config.arrows_enabled,\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\targs: [\"arrows_enabled\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Piece-click spotlight\"),\n\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\tchecked: config.click_spotlight,\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\targs: [\"click_spotlight\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Always show actual move (if known)\"),\n\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\tchecked: config.next_move_arrow,\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\targs: [\"next_move_arrow\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"...with unique colour\"),\n\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\tchecked: config.next_move_unique_colour,\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\targs: [\"next_move_unique_colour\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"...with outline\"),\n\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\tchecked: config.next_move_outline,\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\targs: [\"next_move_outline\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Arrowhead type\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Winrate\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.arrowhead_type === 0,\n\t\t\t\t\t\t\taccelerator: \"F5\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Arrowhead type\", \"Winrate\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {arrowhead_type: 0});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Node %\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.arrowhead_type === 1,\n\t\t\t\t\t\t\taccelerator: \"F6\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Arrowhead type\", \"Node %\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {arrowhead_type: 1});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Policy\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.arrowhead_type === 2,\n\t\t\t\t\t\t\taccelerator: \"F7\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Arrowhead type\", \"Policy\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {arrowhead_type: 2});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"MultiPV rank\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.arrowhead_type === 3,\n\t\t\t\t\t\t\taccelerator: \"F8\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Arrowhead type\", \"MultiPV rank\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {arrowhead_type: 3});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Moves Left Head\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.arrowhead_type === 4,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Arrowhead type\", \"Moves Left Head\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {arrowhead_type: 4});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Arrow filter (Lc0)\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"All moves\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.arrow_filter_type === \"all\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Arrow filter (Lc0)\", \"All moves\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_arrow_filter\",\n\t\t\t\t\t\t\t\t\targs: [\"all\", 0],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Top move\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.arrow_filter_type === \"top\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Arrow filter (Lc0)\", \"Top move\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_arrow_filter\",\n\t\t\t\t\t\t\t\t\targs: [\"top\", 0],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"N > 0.5%\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.arrow_filter_type === \"N\" && config.arrow_filter_value === 0.005,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Arrow filter (Lc0)\", \"N > 0.5%\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_arrow_filter\",\n\t\t\t\t\t\t\t\t\targs: [\"N\", 0.005],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"N > 1%\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.arrow_filter_type === \"N\" && config.arrow_filter_value === 0.01,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Arrow filter (Lc0)\", \"N > 1%\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_arrow_filter\",\n\t\t\t\t\t\t\t\t\targs: [\"N\", 0.01],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"N > 2%\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.arrow_filter_type === \"N\" && config.arrow_filter_value === 0.02,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Arrow filter (Lc0)\", \"N > 2%\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_arrow_filter\",\n\t\t\t\t\t\t\t\t\targs: [\"N\", 0.02],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"N > 3%\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.arrow_filter_type === \"N\" && config.arrow_filter_value === 0.03,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Arrow filter (Lc0)\", \"N > 3%\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_arrow_filter\",\n\t\t\t\t\t\t\t\t\targs: [\"N\", 0.03],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"N > 4%\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.arrow_filter_type === \"N\" && config.arrow_filter_value === 0.04,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Arrow filter (Lc0)\", \"N > 4%\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_arrow_filter\",\n\t\t\t\t\t\t\t\t\targs: [\"N\", 0.04],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"N > 5%\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.arrow_filter_type === \"N\" && config.arrow_filter_value === 0.05,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Arrow filter (Lc0)\", \"N > 5%\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_arrow_filter\",\n\t\t\t\t\t\t\t\t\targs: [\"N\", 0.05],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"N > 10%\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.arrow_filter_type === \"N\" && config.arrow_filter_value === 0.1,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Arrow filter (Lc0)\", \"N > 10%\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_arrow_filter\",\n\t\t\t\t\t\t\t\t\targs: [\"N\", 0.1],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Arrow filter (others)\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Diff < 15%\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.ab_filter_threshold === 0.15,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Arrow filter (others)\", \"Diff < 15%\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {ab_filter_threshold: 0.15});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Diff < 10%\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.ab_filter_threshold === 0.1,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Arrow filter (others)\", \"Diff < 10%\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {ab_filter_threshold: 0.1});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Diff < 5%\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.ab_filter_threshold === 0.05,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Arrow filter (others)\", \"Diff < 5%\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {ab_filter_threshold: 0.05});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Infobox stats\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Centipawns\"),\n\t\t\t\t\t\t\taccelerator: \"CommandOrControl+T\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.show_cp,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"show_cp\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"N - nodes (%)\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.show_n,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"show_n\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"N - nodes (absolute)\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.show_n_abs,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"show_n_abs\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Depth (A/B only)\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.show_depth,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"show_depth\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"P - policy\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.show_p,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"show_p\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"V - static evaluation\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.show_v,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"show_v\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Q - evaluation\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.show_q,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"show_q\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"U - uncertainty\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.show_u,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"show_u\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"S - search priority\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.show_s,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"show_s\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"M - moves left\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.show_m,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"show_m\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"WDL - win / draw / loss\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.show_wdl,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"show_wdl\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Linebreak before stats\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.infobox_stats_newline,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"infobox_stats_newline\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"PV move numbers\"),\n\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\tchecked: config.infobox_pv_move_numbers,\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\targs: [\"infobox_pv_move_numbers\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Online API\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"None\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: typeof config.looker_api !== \"string\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Online API\", \"None\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_looker_api\",\n\t\t\t\t\t\t\t\t\targs: [null]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"ChessDB.cn evals\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.looker_api === \"chessdbcn\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Online API\", \"ChessDB.cn evals\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_looker_api\",\n\t\t\t\t\t\t\t\t\targs: [\"chessdbcn\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Lichess results (masters)\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.looker_api === \"lichess_masters\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Online API\", \"Lichess results (masters)\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_looker_api\",\n\t\t\t\t\t\t\t\t\targs: [\"lichess_masters\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Lichess results (plebs)\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.looker_api === \"lichess_plebs\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Online API\", \"Lichess results (plebs)\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_looker_api\",\n\t\t\t\t\t\t\t\t\targs: [\"lichess_plebs\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Set Lichess API token\"),\n\t\t\t\t\t\t\tclick : () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"show_config_item_editor\",\n\t\t\t\t\t\t\t\t\targs: [\"lichess_token\", \"https://lichess.org/account/oauth/token/create\", \"Acquire a token here (no need for specific permissions)\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Allow API after move 25\"),\n\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\tchecked: config.look_past_25,\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\targs: [\"look_past_25\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Draw PV on mouseover\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+D\",\n\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\tchecked: config.hover_draw,\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\targs: [\"hover_draw\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Draw PV method\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Animate\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.hover_method === 0,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Draw PV method\", \"Animate\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {hover_method: 0});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Single move\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.hover_method === 1,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Draw PV method\", \"Single move\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {hover_method: 1});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Final position\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.hover_method === 2,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Display\", \"Draw PV method\", \"Final position\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {hover_method: 2});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Pieces\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Choose pieces folder...\"),\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tlet folders = open_dialog(win, {\n\t\t\t\t\t\t\t\t\tdefaultPath: config.pieces_dialog_folder,\n\t\t\t\t\t\t\t\t\tproperties: [\"openDirectory\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tif (Array.isArray(folders) && folders.length > 0) {\n\t\t\t\t\t\t\t\t\tlet folder = folders[0];\n\t\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\t\tfn: \"change_piece_set\",\n\t\t\t\t\t\t\t\t\t\targs: [folder]\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t// Save the dir as the new default dir, in both processes.\n\t\t\t\t\t\t\t\t\tconfig.pieces_dialog_folder = path.dirname(folder);\n\t\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {pieces_dialog_folder: path.dirname(folder)});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Default\"),\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"change_piece_set\",\n\t\t\t\t\t\t\t\t\targs: [null]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"About custom pieces\"),\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\talert(win, messages.about_custom_pieces);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Background\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Choose background image...\"),\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tlet files = open_dialog(win, {\n\t\t\t\t\t\t\t\t\tdefaultPath: config.background_dialog_folder,\n\t\t\t\t\t\t\t\t\tproperties: [\"openFile\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tif (Array.isArray(files) && files.length > 0) {\n\t\t\t\t\t\t\t\t\tlet file = files[0];\n\t\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\t\tfn: \"change_background\",\n\t\t\t\t\t\t\t\t\t\targs: [file]\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t// Save the dir as the new default dir, in both processes.\n\t\t\t\t\t\t\t\t\tconfig.background_dialog_folder = path.dirname(file);\n\t\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {background_dialog_folder: path.dirname(file)});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Default\"),\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"change_background\",\n\t\t\t\t\t\t\t\t\targs: [null]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Book frequency arrows\"),\n\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\tchecked: config.book_explorer,\t\t\t// But this is never saved in the config file.\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\targs: [\"book_explorer\"]\t\t\t// The hub will automatically turn off lichess weights mode.\n\t\t\t\t\t\t});\n\t\t\t\t\t\tset_one_check(false, \"Display\", \"Lichess frequency arrows\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Lichess frequency arrows\"),\n\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\taccelerator: \"CommandOrControl+E\",\n\t\t\t\t\tchecked: config.lichess_explorer,\t\t// But this is never saved in the config file.\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\targs: [\"lichess_explorer\"]\t\t// The hub will automatically turn off book weights mode.\n\t\t\t\t\t\t});\n\t\t\t\t\t\tset_one_check(false, \"Display\", \"Book frequency arrows\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\tlabel: translate.t(\"Sizes\"),\n\t\t\tsubmenu: [\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Infobox font\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"32\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.info_font_size === 32,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Infobox font\", \"32\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_info_font_size\",\n\t\t\t\t\t\t\t\t\targs: [32],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"28\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.info_font_size === 28,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Infobox font\", \"28\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_info_font_size\",\n\t\t\t\t\t\t\t\t\targs: [28],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"24\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.info_font_size === 24,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Infobox font\", \"24\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_info_font_size\",\n\t\t\t\t\t\t\t\t\targs: [24],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"20\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.info_font_size === 20,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Infobox font\", \"20\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_info_font_size\",\n\t\t\t\t\t\t\t\t\targs: [20],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"18\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.info_font_size === 18,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Infobox font\", \"18\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_info_font_size\",\n\t\t\t\t\t\t\t\t\targs: [18],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"16\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.info_font_size === 16,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Infobox font\", \"16\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_info_font_size\",\n\t\t\t\t\t\t\t\t\targs: [16],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Move history font\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"32\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pgn_font_size === 32,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Move history font\", \"32\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_pgn_font_size\",\n\t\t\t\t\t\t\t\t\targs: [32],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"28\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pgn_font_size === 28,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Move history font\", \"28\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_pgn_font_size\",\n\t\t\t\t\t\t\t\t\targs: [28],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"24\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pgn_font_size === 24,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Move history font\", \"24\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_pgn_font_size\",\n\t\t\t\t\t\t\t\t\targs: [24],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"20\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pgn_font_size === 20,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Move history font\", \"20\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_pgn_font_size\",\n\t\t\t\t\t\t\t\t\targs: [20],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"18\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pgn_font_size === 18,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Move history font\", \"18\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_pgn_font_size\",\n\t\t\t\t\t\t\t\t\targs: [18],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"16\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.pgn_font_size === 16,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Move history font\", \"16\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_pgn_font_size\",\n\t\t\t\t\t\t\t\t\targs: [16],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Board\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"1280\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.board_size === 1280,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Board\", \"1280\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_board_size\",\n\t\t\t\t\t\t\t\t\targs: [1280],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"1120\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.board_size === 1120,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Board\", \"1120\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_board_size\",\n\t\t\t\t\t\t\t\t\targs: [1120],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"960\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.board_size === 960,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Board\", \"960\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_board_size\",\n\t\t\t\t\t\t\t\t\targs: [960],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"800\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.board_size === 800,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Board\", \"800\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_board_size\",\n\t\t\t\t\t\t\t\t\targs: [800],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"640\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.board_size === 640,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Board\", \"640\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_board_size\",\n\t\t\t\t\t\t\t\t\targs: [640],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"576\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.board_size === 576,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Board\", \"576\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_board_size\",\n\t\t\t\t\t\t\t\t\targs: [576],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"512\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.board_size === 512,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Board\", \"512\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_board_size\",\n\t\t\t\t\t\t\t\t\targs: [512],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"480\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.board_size === 480,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Board\", \"480\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_board_size\",\n\t\t\t\t\t\t\t\t\targs: [480],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"448\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.board_size === 448,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Board\", \"448\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_board_size\",\n\t\t\t\t\t\t\t\t\targs: [448],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"416\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.board_size === 416,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Board\", \"416\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_board_size\",\n\t\t\t\t\t\t\t\t\targs: [416],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Arrows\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Giant\"),\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_arrow_size\",\n\t\t\t\t\t\t\t\t\targs: [24, 32, 40]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Large\"),\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_arrow_size\",\n\t\t\t\t\t\t\t\t\targs: [16, 24, 32]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Medium\"),\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_arrow_size\",\n\t\t\t\t\t\t\t\t\targs: [12, 18, 24]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Small\"),\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_arrow_size\",\n\t\t\t\t\t\t\t\t\targs: [8, 12, 18]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Graph\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"192\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.graph_height === 192,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Graph\", \"192\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_graph_height\",\n\t\t\t\t\t\t\t\t\targs: [192],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"160\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.graph_height === 160,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Graph\", \"160\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_graph_height\",\n\t\t\t\t\t\t\t\t\targs: [160],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"128\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.graph_height === 128,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Graph\", \"128\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_graph_height\",\n\t\t\t\t\t\t\t\t\targs: [128],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"96\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.graph_height === 96,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Graph\", \"96\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_graph_height\",\n\t\t\t\t\t\t\t\t\targs: [96],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"64\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.graph_height === 64,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Graph\", \"64\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_graph_height\",\n\t\t\t\t\t\t\t\t\targs: [64],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"48\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.graph_height === 48,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Graph\", \"48\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_graph_height\",\n\t\t\t\t\t\t\t\t\targs: [48],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"32\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.graph_height === 32,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Graph\", \"32\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_graph_height\",\n\t\t\t\t\t\t\t\t\targs: [32],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"0\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.graph_height === 0,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Graph\", \"0\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_graph_height\",\n\t\t\t\t\t\t\t\t\targs: [0],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Graph lines\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"8\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.graph_line_width === 8,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Graph lines\", \"8\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {graph_line_width: 8});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"7\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.graph_line_width === 7,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Graph lines\", \"7\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {graph_line_width: 7});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"6\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.graph_line_width === 6,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Graph lines\", \"6\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {graph_line_width: 6});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"5\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.graph_line_width === 5,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Graph lines\", \"5\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {graph_line_width: 5});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"4\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.graph_line_width === 4,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Graph lines\", \"4\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {graph_line_width: 4});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"3\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.graph_line_width === 3,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Graph lines\", \"3\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {graph_line_width: 3});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"2\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.graph_line_width === 2,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Sizes\", \"Graph lines\", \"2\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {graph_line_width: 2});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"I want other size options!\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\talert(win, messages.about_sizes);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\tlabel: translate.t(\"Engine\"),\n\t\t\tsubmenu: [\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Choose engine...\"),\n\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\tchecked: false,\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\tlet files = open_dialog(win, {\n\t\t\t\t\t\t\tdefaultPath: config.engine_dialog_folder,\n\t\t\t\t\t\t\tproperties: [\"openFile\"]\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (Array.isArray(files) && files.length > 0) {\n\t\t\t\t\t\t\tlet file = files[0];\n\t\t\t\t\t\t\tif (file === process.argv[0] || path.basename(file).includes(\"client\")) {\n\t\t\t\t\t\t\t\talert(win, messages.wrong_engine_exe);\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", \"send_ack_engine\");\t// Force an ack IPC to fix our menu check state.\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\tfn: \"switch_engine\",\n\t\t\t\t\t\t\t\targs: [file]\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t// Save the dir as the new default dir, in both processes.\n\t\t\t\t\t\t\tconfig.engine_dialog_folder = path.dirname(file);\n\t\t\t\t\t\t\twin.webContents.send(\"set\", {engine_dialog_folder: path.dirname(file)});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\twin.webContents.send(\"call\", \"send_ack_engine\");\t\t// Force an ack IPC to fix our menu check state.\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Choose known engine...\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"show_fast_engine_chooser\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Weights\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Lc0 WeightsFile...\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tlet files = open_dialog(win, {\n\t\t\t\t\t\t\t\t\tdefaultPath: config.weights_dialog_folder,\n\t\t\t\t\t\t\t\t\tproperties: [\"openFile\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tif (Array.isArray(files) && files.length > 0) {\n\t\t\t\t\t\t\t\t\tlet file = files[0];\n\t\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\t\targs: [\"WeightsFile\", file]\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t\t\t// Save the dir as the new default dir, in both processes.\n\t\t\t\t\t\t\t\t\tconfig.weights_dialog_folder = path.dirname(file);\n\t\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {weights_dialog_folder: path.dirname(file)});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\t\t\t\t\t\t// Force an ack IPC to fix our menu check state.\n\t\t\t\t\t\t\t\t\t\tfn: \"send_ack_setoption\",\n\t\t\t\t\t\t\t\t\t\targs: [\"WeightsFile\"],\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Stockfish EvalFile...\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tlet files = open_dialog(win, {\n\t\t\t\t\t\t\t\t\tdefaultPath: config.evalfile_dialog_folder,\n\t\t\t\t\t\t\t\t\tproperties: [\"openFile\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tif (Array.isArray(files) && files.length > 0) {\n\t\t\t\t\t\t\t\t\tlet file = files[0];\n\t\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\t\targs: [\"EvalFile\", file]\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t\t\t// Save the dir as the new default dir, in both processes.\n\t\t\t\t\t\t\t\t\tconfig.evalfile_dialog_folder = path.dirname(file);\n\t\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {evalfile_dialog_folder: path.dirname(file)});\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\t\t\t\t\t\t// Force an ack IPC to fix our menu check state.\n\t\t\t\t\t\t\t\t\t\tfn: \"send_ack_setoption\",\n\t\t\t\t\t\t\t\t\t\targs: [\"EvalFile\"],\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Set to <auto>\"),\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", \"auto_weights\");\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Backend\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"cuda-auto\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"cuda-auto\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"cuda\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"cuda\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"cuda-fp16\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"cuda-fp16\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"cudnn-auto\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"cudnn-auto\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"cudnn\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"cudnn\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"cudnn-fp16\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"cudnn-fp16\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"blas\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"blas\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"dx12\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"dx12\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"eigen\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"eigen\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"metal\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"metal\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"onednn\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"onednn\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"opencl\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"opencl\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"xla\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"xla\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"tensorflow-cc\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"tensorflow-cc\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"tensorflow-cc-cpu\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"tensorflow-cc-cpu\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"onnx-cpu\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"onnx-cpu\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"onnx-cuda\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"onnx-cuda\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"onnx-dml\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"onnx-dml\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"onnx-rocm\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"onnx-rocm\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"random\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"random\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"trivial\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"trivial\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"demux\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"demux\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"multiplexing\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"multiplexing\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"roundrobin\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Backend\", \"roundrobin\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Choose Syzygy path...\"),\n\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\tchecked: false,\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\tlet folders = open_dialog(win, {\n\t\t\t\t\t\t\tdefaultPath: config.syzygy_dialog_folder,\n\t\t\t\t\t\t\tproperties: [\"openDirectory\"]\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (Array.isArray(folders) && folders.length > 0) {\n\t\t\t\t\t\t\tlet folder = folders[0];\n\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\targs: [\"SyzygyPath\", folder]\t\t\t// FIXME: should send all folders, separated by system separator.\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t// Save the dir as the new default dir, in both processes.\n\t\t\t\t\t\t\tconfig.syzygy_dialog_folder = path.dirname(folder);\n\t\t\t\t\t\t\twin.webContents.send(\"set\", {syzygy_dialog_folder: path.dirname(folder)});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\tfn: \"send_ack_setoption\",\n\t\t\t\t\t\t\t\targs: [\"SyzygyPath\"]\t\t\t\t\t// Force an ack IPC to fix our menu check state.\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Unset\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"disable_syzygy\");\n\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Limit - normal\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Unlimited\"),\n\t\t\t\t\t\t\taccelerator: \"CommandOrControl+U\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit\",\n\t\t\t\t\t\t\t\t\targs: [null]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"1,000,000,000\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit\",\n\t\t\t\t\t\t\t\t\targs: [1 * billion]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"100,000,000\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit\",\n\t\t\t\t\t\t\t\t\targs: [100 * million]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"10,000,000\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit\",\n\t\t\t\t\t\t\t\t\targs: [10 * million]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"1,000,000\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit\",\n\t\t\t\t\t\t\t\t\targs: [1 * million]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"100,000\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit\",\n\t\t\t\t\t\t\t\t\targs: [100000]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"10,000\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit\",\n\t\t\t\t\t\t\t\t\targs: [10000]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"1,000\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit\",\n\t\t\t\t\t\t\t\t\targs: [1000]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"100\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit\",\n\t\t\t\t\t\t\t\t\targs: [100]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"10\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit\",\n\t\t\t\t\t\t\t\t\targs: [10]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"2\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit\",\n\t\t\t\t\t\t\t\t\targs: [2]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"1\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit\",\n\t\t\t\t\t\t\t\t\targs: [1]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Up slightly\"),\n\t\t\t\t\t\t\taccelerator: \"CommandOrControl+=\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"adjust_node_limit\",\n\t\t\t\t\t\t\t\t\targs: [1, false]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Down slightly\"),\n\t\t\t\t\t\t\taccelerator: \"CommandOrControl+-\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"adjust_node_limit\",\n\t\t\t\t\t\t\t\t\targs: [-1, false]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Limit - auto-eval / play\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"1,000,000,000\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit_special\",\n\t\t\t\t\t\t\t\t\targs: [1 * billion]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"100,000,000\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit_special\",\n\t\t\t\t\t\t\t\t\targs: [100 * million]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"10,000,000\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit_special\",\n\t\t\t\t\t\t\t\t\targs: [10 * million]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"1,000,000\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit_special\",\n\t\t\t\t\t\t\t\t\targs: [1 * million]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"100,000\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit_special\",\n\t\t\t\t\t\t\t\t\targs: [100000]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"10,000\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit_special\",\n\t\t\t\t\t\t\t\t\targs: [10000]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"1,000\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit_special\",\n\t\t\t\t\t\t\t\t\targs: [1000]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"100\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit_special\",\n\t\t\t\t\t\t\t\t\targs: [100]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"10\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit_special\",\n\t\t\t\t\t\t\t\t\targs: [10]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"2\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit_special\",\n\t\t\t\t\t\t\t\t\targs: [2]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"1\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_node_limit_special\",\n\t\t\t\t\t\t\t\t\targs: [1]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\",\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Up slightly\"),\n\t\t\t\t\t\t\taccelerator: \"CommandOrControl+]\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"adjust_node_limit\",\n\t\t\t\t\t\t\t\t\targs: [1, true]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Down slightly\"),\n\t\t\t\t\t\t\taccelerator: \"CommandOrControl+[\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"adjust_node_limit\",\n\t\t\t\t\t\t\t\t\targs: [-1, true]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Limit by time instead of nodes\"),\n\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\tchecked: false,\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"toggle_limit_by_time\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Threads\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"128\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Threads\", 128],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"96\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Threads\", 96],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"64\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Threads\", 64],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"48\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Threads\", 48],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"32\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Threads\", 32],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"24\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Threads\", 24],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"16\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Threads\", 16],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"14\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Threads\", 14],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"12\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Threads\", 12],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"10\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Threads\", 10],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"8\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Threads\", 8],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"7\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Threads\", 7],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"6\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Threads\", 6],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"5\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Threads\", 5],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"4\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Threads\", 4],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"3\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Threads\", 3],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"2\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Threads\", 2],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"1\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Threads\", 1],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Warning about threads\"),\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\talert(win, messages.thread_warning);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Hash\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"120 GB\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Hash\", 120 * 1024]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"56 GB\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Hash\", 56 * 1024]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"24 GB\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Hash\", 24 * 1024]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"12 GB\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Hash\", 12 * 1024]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"8 GB\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Hash\", 8 * 1024]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"6 GB\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Hash\", 6 * 1024]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"4 GB\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Hash\", 4 * 1024]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"2 GB\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Hash\", 2 * 1024]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"1 GB\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Hash\", 1 * 1024]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"0 GB\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Hash\", 1]\t\t\t\t\t// 1 MB is Stockfish actual minimum.\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"I want other hash options!\"),\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\talert(win, messages.about_hashes);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"MultiPV\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"5\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"MultiPV\", 5]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"4\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"MultiPV\", 4]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"3\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"MultiPV\", 3]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"2\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"MultiPV\", 2]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"1\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"MultiPV\", 1]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Contempt Mode\"),// Other valid options are \"play\" (which messes with normal analysis) and \"disable\"\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"White analysis\"),\t// Note string searched when ack'd.\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"ContemptMode\", \"white_side_analysis\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Black analysis\"),\t// Note string searched when ack'd.\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"ContemptMode\", \"black_side_analysis\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Contempt\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"250\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"Contempt\", 250]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"200\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"Contempt\", 200]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"150\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"Contempt\", 150]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"100\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"Contempt\", 100]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"50\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"Contempt\", 50]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"0\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"Contempt\", 0]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"-50\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"Contempt\", -50]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"-100\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"Contempt\", -100]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"-150\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"Contempt\", -150]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"-200\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"Contempt\", -200]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"-250\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"Contempt\", -250]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"WDL Calibration Elo\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"3600\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"WDLCalibrationElo\", 3600]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"3400\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"WDLCalibrationElo\", 3400]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"3200\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"WDLCalibrationElo\", 3200]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"3000\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"WDLCalibrationElo\", 3000]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"2800\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"WDLCalibrationElo\", 2800]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"2600\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"WDLCalibrationElo\", 2600]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"2400\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"WDLCalibrationElo\", 2400]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"2200\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"WDLCalibrationElo\", 2200]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"2000\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"WDLCalibrationElo\", 2000]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Use default WDL\"),\t// This string is searched for when receiving ack 0, don't edit this alone.\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent_and_cleartree\",\n\t\t\t\t\t\t\t\t\targs: [\"WDLCalibrationElo\", 0]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"WDL Eval Objectivity\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Yes\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"WDLEvalObjectivity\", 1]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"No\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"WDLEvalObjectivity\", 0]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Score Type\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"WDL_mu\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"ScoreType\", \"WDL_mu\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"centipawn\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"ScoreType\", \"centipawn\"]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Custom scripts\"),\n\t\t\t\t\tsubmenu: scriptlist_in_menu\t\t\t// Will be filled at the end, see below.\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Restart engine\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"restart_engine\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Soft engine reset\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"soft_engine_reset\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\tlabel: translate.t(\"Play\"),\n\t\t\tsubmenu: [\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Play this colour\"),\n\t\t\t\t\taccelerator: \"F9\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"play_this_colour\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Start self-play\"),\n\t\t\t\t\taccelerator: \"F11\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"set_behaviour\",\n\t\t\t\t\t\t\targs: [\"self_play\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Halt\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"set_behaviour\",\n\t\t\t\t\t\t\targs: [\"halt\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Use Polyglot book...\"),\n\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\tchecked: false,\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\tlet files = open_dialog(win, {\n\t\t\t\t\t\t\tdefaultPath: config.book_dialog_folder,\n\t\t\t\t\t\t\tproperties: [\"openFile\"],\n\t\t\t\t\t\t\tfilters: [{name: \"Polyglot\", extensions: [\"bin\"]}, {name: \"All files\", extensions: [\"*\"]}]\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (Array.isArray(files) && files.length > 0) {\n\t\t\t\t\t\t\tlet file = files[0];\n\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\tfn: \"load_polyglot_book\",\n\t\t\t\t\t\t\t\targs: [file]\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t// Save the dir as the new default dir, in both processes.\n\t\t\t\t\t\t\tconfig.book_dialog_folder = path.dirname(file);\n\t\t\t\t\t\t\twin.webContents.send(\"set\", {book_dialog_folder: path.dirname(file)});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\twin.webContents.send(\"call\", \"send_ack_book\");\t\t// Force an ack IPC to fix our menu check state.\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Use PGN book...\"),\n\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\tchecked: false,\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\tlet files = open_dialog(win, {\n\t\t\t\t\t\t\tdefaultPath: config.book_dialog_folder,\n\t\t\t\t\t\t\tproperties: [\"openFile\"],\n\t\t\t\t\t\t\tfilters: [{name: \"PGN\", extensions: [\"pgn\"]}, {name: \"All files\", extensions: [\"*\"]}]\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (Array.isArray(files) && files.length > 0) {\n\t\t\t\t\t\t\tlet file = files[0];\n\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\tfn: \"load_pgn_book\",\n\t\t\t\t\t\t\t\targs: [file]\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t// Save the dir as the new default dir, in both processes.\n\t\t\t\t\t\t\tconfig.book_dialog_folder = path.dirname(file);\n\t\t\t\t\t\t\twin.webContents.send(\"set\", {book_dialog_folder: path.dirname(file)});\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\twin.webContents.send(\"call\", \"send_ack_book\");\t\t// Force an ack IPC to fix our menu check state.\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Unload book / abort load\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"unload_book\");\n\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Book depth limit\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Unlimited\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: typeof config.book_depth !== \"number\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Play\", \"Book depth limit\", \"Unlimited\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {book_depth: null});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"20\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.book_depth === 20,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Play\", \"Book depth limit\", \"20\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {book_depth: 20});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"18\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.book_depth === 18,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Play\", \"Book depth limit\", \"18\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {book_depth: 18});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"16\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.book_depth === 16,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Play\", \"Book depth limit\", \"16\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {book_depth: 16});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"14\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.book_depth === 14,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Play\", \"Book depth limit\", \"14\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {book_depth: 14});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"12\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.book_depth === 12,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Play\", \"Book depth limit\", \"12\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {book_depth: 12});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"10\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.book_depth === 10,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Play\", \"Book depth limit\", \"10\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {book_depth: 10});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"8\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.book_depth === 8,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Play\", \"Book depth limit\", \"8\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {book_depth: 8});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"6\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.book_depth === 6,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Play\", \"Book depth limit\", \"6\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {book_depth: 6});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"4\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.book_depth === 4,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Play\", \"Book depth limit\", \"4\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {book_depth: 4});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"2\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.book_depth === 2,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Play\", \"Book depth limit\", \"2\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {book_depth: 2});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Temperature\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"1.0\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Temperature\", 1.0]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"0.9\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Temperature\", 0.9]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"0.8\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Temperature\", 0.8]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"0.7\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Temperature\", 0.7]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"0.6\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Temperature\", 0.6]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"0.5\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Temperature\", 0.5]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"0.4\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Temperature\", 0.4]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"0.3\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Temperature\", 0.3]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"0.2\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Temperature\", 0.2]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"0.1\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Temperature\", 0.1]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"0\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"Temperature\", 0]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Temp Decay Moves\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Infinite\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"TempDecayMoves\", 0]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"20\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"TempDecayMoves\", 20]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"18\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"TempDecayMoves\", 18]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"16\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"TempDecayMoves\", 16]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"14\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"TempDecayMoves\", 14]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"12\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"TempDecayMoves\", 12]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"10\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"TempDecayMoves\", 10]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"8\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"TempDecayMoves\", 8]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"6\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"TempDecayMoves\", 6]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"4\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"TempDecayMoves\", 4]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: \"2\",\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: false,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_uci_option_permanent\",\n\t\t\t\t\t\t\t\t\targs: [\"TempDecayMoves\", 2]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"About play modes\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\talert(win, messages.about_versus_mode);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\tlabel: translate.t(\"Dev\"),\n\t\t\tsubmenu: [\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Toggle Developer Tools\"),\n\t\t\t\t\trole: \"toggledevtools\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Toggle Debug CSS\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"toggle_debug_css\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Permanently enable save\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\tconfig.save_enabled = true;\t\t\t\t\t\t\t\t// The main process actually uses this variable...\n\t\t\t\t\t\twin.webContents.send(\"set\", {save_enabled: true});\t\t// But it's the renderer process that saves the config file.\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(`Show ${config_io.filename}`),\t\t\t// Ugh.\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\telectron.shell.showItemInFolder(config_io.filepath);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(`Show ${engineconfig_io.filename}`),\t\t// Ugh.\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\telectron.shell.showItemInFolder(engineconfig_io.filepath);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(`Reload ${engineconfig_io.filename} (and restart engine)`),\t\t// Ugh.\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"reload_engineconfig\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Random move\"),\n\t\t\t\t\taccelerator: \"CommandOrControl+/\",\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"random_move\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Disable hardware acceleration for GUI\"),\n\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\tchecked: config.disable_hw_accel,\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\targs: [\"disable_hw_accel\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (!have_warned_hw_accel_setting) {\n\t\t\t\t\t\t\talert(win, \"This will not take effect until you restart the GUI.\");\n\t\t\t\t\t\t\thave_warned_hw_accel_setting = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Spin rate\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Frenetic\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.update_delay === 25,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Dev\", \"Spin rate\", \"Frenetic\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {update_delay: 25});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Fast\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.update_delay === 60,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Dev\", \"Spin rate\", \"Fast\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {update_delay: 60});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Normal\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.update_delay === 125,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Dev\", \"Spin rate\", \"Normal\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {update_delay: 125});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Relaxed\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.update_delay === 170,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Dev\", \"Spin rate\", \"Relaxed\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {update_delay: 170});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Lazy\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.update_delay === 250,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tset_checks(\"Dev\", \"Spin rate\", \"Lazy\");\n\t\t\t\t\t\t\t\twin.webContents.send(\"set\", {update_delay: 250});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Show engine state\"),\n\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\tchecked: config.show_engine_state,\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\targs: [\"show_engine_state\"]\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"List sent options\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"show_sent_options\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Show error log\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"show_error_log\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Hacks and kludges\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Allow arbitrary scripts\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.allow_arbitrary_scripts,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"allow_arbitrary_scripts\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Accept any file size\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.ignore_filesize_limits,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"ignore_filesize_limits\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Allow stopped analysis\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.allow_stopped_analysis,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"allow_stopped_analysis\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Never hide focus buttons\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.never_suppress_searchmoves,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"never_suppress_searchmoves\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Never grayout move info\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.never_grayout_infolines,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"never_grayout_infolines\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Use lowerbound / upperbound info\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.accept_bounds,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"accept_bounds\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Suppress ucinewgame\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.suppress_ucinewgame,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"suppress_ucinewgame\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Log RAM state to console\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"log_ram\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Fire GC\"),\n\t\t\t\t\tclick: () => {\n\t\t\t\t\t\twin.webContents.send(\"call\", \"fire_gc\");\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\ttype: \"separator\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: translate.t(\"Logging\"),\n\t\t\t\t\tsubmenu: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Use logfile...\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: typeof config.logfile === \"string\" && config.logfile !== \"\",\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\tlet file = save_dialog(win, {});\n\t\t\t\t\t\t\t\tif (typeof file === \"string\" && file.length > 0) {\n\t\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\t\tfn: \"set_logfile\",\n\t\t\t\t\t\t\t\t\t\targs: [file]\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\twin.webContents.send(\"call\", \"send_ack_logfile\");\t\t// Force an ack IPC to fix our menu check state.\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Disable logging\"),\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"set_logfile\",\n\t\t\t\t\t\t\t\t\targs: [null]\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t// Will receive an ack IPC which sets menu checks.\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Clear log when opening\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.clear_log,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"clear_log\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Use unique logfile each time\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.logfile_timestamp,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"logfile_timestamp\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"separator\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Log illegal moves\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.log_illegal_moves,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"log_illegal_moves\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Log positions\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.log_positions,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"log_positions\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"Log info lines\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.log_info_lines,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"log_info_lines\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: translate.t(\"...including useless lines\"),\n\t\t\t\t\t\t\ttype: \"checkbox\",\n\t\t\t\t\t\t\tchecked: config.log_useless_info,\n\t\t\t\t\t\t\tclick: () => {\n\t\t\t\t\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\t\t\t\t\tfn: \"toggle\",\n\t\t\t\t\t\t\t\t\targs: [\"log_useless_info\"],\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\tlabel: translate.t(\"Language\"),\n\t\t\tsubmenu: language_choices_submenu()\n\t\t}\n\t];\n\n\t// Some special shennanigans to build the custom scripts menu...\n\n\tlet scriptlist = custom_uci.load();\n\n\tfor (let script of scriptlist) {\n\t\tscriptlist_in_menu.push({\n\t\t\tlabel: script.name,\n\t\t\tclick: () => {\n\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\tfn: \"run_script\",\n\t\t\t\t\targs: [script.path]\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t}\n\n\tif (scriptlist_in_menu.length > 0) {\n\t\tscriptlist_in_menu.push({type: \"separator\"});\n\t}\n\tscriptlist_in_menu.push({\n\t\tlabel: translate.t(\"How to add scripts\"),\n\t\tclick: () => {\n\t\t\talert(win, messages.adding_scripts);\n\t\t}\n\t});\n\tscriptlist_in_menu.push({\n\t\tlabel: translate.t(\"Show scripts folder\"),\n\t\tclick: () => {\n\t\t\telectron.shell.showItemInFolder(custom_uci.script_dir_path);\n\t\t}\n\t});\n\n\t// Actually build the menu...\n\n\treturn electron.Menu.buildFromTemplate(template);\n}\n\nfunction language_choices_submenu() {\n\n\tlet ret = [];\n\n\tfor (let language of translate.all_languages()) {\n\t\tret.push({\n\t\t\tlabel: language,\n\t\t\ttype: \"checkbox\",\n\t\t\tchecked: config.language === language,\n\t\t\tclick: () => {\n\t\t\t\tset_checks(\"Language\", language);\n\t\t\t\twin.webContents.send(\"call\", {\n\t\t\t\t\tfn: \"set_language\",\n\t\t\t\t\targs: [language]\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t}\n\n\treturn ret;\n}\n\nfunction get_submenu_items(menupath) {\n\n\t// If the path is to a submenu, this returns a list of all items in the submenu.\n\t// If the path is to a specific menu item, it just returns that item.\n\n\tlet o = menu.items;\n\tfor (let p of menupath) {\n\t\tp = translate.t(stringify(p));\n\t\tfor (let item of o) {\n\t\t\tif (item.label === p) {\n\t\t\t\tif (item.submenu) {\n\t\t\t\t\to = item.submenu.items;\n\t\t\t\t\tbreak;\n\t\t\t\t} else {\n\t\t\t\t\treturn item;\t\t// No submenu so this must be the end.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn o;\n}\n\nfunction set_checks(...menupath) {\n\n\tif (!menu_is_set) {\n\t\treturn;\n\t}\n\n\t// Since I don't know precisely how the menu works behind the scenes,\n\t// give a little time for the original click to go through first.\n\n\tsetTimeout(() => {\n\t\tlet items = get_submenu_items(menupath.slice(0, -1));\n\t\tfor (let n = 0; n < items.length; n++) {\n\t\t\tif (items[n].checked !== undefined) {\n\t\t\t\titems[n].checked = items[n].label === translate.t(stringify(menupath[menupath.length - 1]));\n\t\t\t}\n\t\t}\n\t}, 50);\n}\n\nfunction set_one_check(state, ...menupath) {\n\n\tstate = state ? true : false;\n\n\tif (!menu_is_set) {\n\t\treturn;\n\t}\n\n\tlet item = get_submenu_items(menupath);\n\tif (item.checked !== undefined) {\n\t\titem.checked = state;\n\t}\n}\n"
  },
  {
    "path": "files/src/modules/alert_main.js",
    "content": "\"use strict\";\n\n// Exports a function we use to draw alert messages.\n// To be used in the main process only (so when the renderer needs to make an alert, it sends the message to main via IPC).\n\nconst electron = require(\"electron\");\nconst stringify = require(\"./stringify\");\n\nlet major_version = (process && process.versions) ? parseInt(process.versions.electron, 10) : 0;\n\nif (Number.isNaN(major_version)) {\n\tmajor_version = 0;\n}\n\nlet alerts_open = 0;\n\nmodule.exports = function(...args) {\t\t\t\t\t\t\t// Can be called as  (msg)  or as  (win, msg)\n\n\tlet win = (args.length < 2) ? undefined : args[0];\n\tlet msg = (args.length < 2) ? args[0]   : args[1];\n\t\n\tif (alerts_open >= 3) {\n\t\tconsole.log(msg);\n\t\treturn;\n\t}\n\n\talerts_open++;\n\n\tif (major_version <= 5) {\n\n\t\t// Old API. Providing a callback makes the window not block the process...\n\t\t// This is all rather untested.\n\n\t\tif (win) {\n\t\t\telectron.dialog.showMessageBox(win, {message: stringify(msg), title: \"Alert\", buttons: [\"OK\"]}, () => {\n\t\t\t\talerts_open--;\n\t\t\t});\n\t\t} else {\n\t\t\telectron.dialog.showMessageBox({message: stringify(msg), title: \"Alert\", buttons: [\"OK\"]}, () => {\n\t\t\t\talerts_open--;\n\t\t\t});\n\t\t}\n\n\t} else {\n\n\t\t// New promise-based API. Shouldn't block the process...\n\n\t\tif (win) {\n\t\t\telectron.dialog.showMessageBox(win, {message: stringify(msg), title: \"Alert\", buttons: [\"OK\"]}).then(() => {\n\t\t\t\talerts_open--;\n\t\t\t});\n\t\t} else {\n\t\t\telectron.dialog.showMessageBox({message: stringify(msg), title: \"Alert\", buttons: [\"OK\"]}).then(() => {\n\t\t\t\talerts_open--;\n\t\t\t});\n\t\t}\n\t}\n};\n"
  },
  {
    "path": "files/src/modules/background.js",
    "content": "\"use strict\";\n\nfunction background(light, dark, square_size) {\n\n\tlet c = document.createElement(\"canvas\");\n\tc.width = square_size * 8;\n\tc.height = square_size * 8;\n\tlet ctx = c.getContext(\"2d\");\n\n\tfor (let x = 0; x < 8; x++) {\n\t\tfor (let y = 0; y < 8; y++) {\n\t\t\tctx.fillStyle = (x + y) % 2 === 0 ? light : dark;\n\t\t\tctx.fillRect(x * square_size, y * square_size, square_size, square_size);\n\t\t}\n\t}\n\n\t// I guess the canvas c gets garbage-collected? https://stackoverflow.com/questions/15320853\n\n\treturn `url(\"${c.toDataURL(\"image/png\")}\")`;\n}\n\nmodule.exports = background;\n"
  },
  {
    "path": "files/src/modules/config_io.js",
    "content": "\"use strict\";\n\nconst electron = require(\"electron\");\nconst fs = require(\"fs\");\nconst path = require(\"path\");\nconst querystring = require(\"querystring\");\n\nconst debork_json = require(\"./debork_json\");\n\nexports.filename = \"config.json\";\n\n// To avoid using \"remote\", we rely on the main process passing userData location in the query...\n\nexports.filepath = electron.app ?\n\t\tpath.join(electron.app.getPath(\"userData\"), exports.filename) :\t\t\t\t\t\t\t\t\t\t\t// in Main process\n\t\tpath.join(querystring.parse(global.location.search.slice(1))[\"user_data_path\"], exports.filename);\t\t// in Renderer process\n\nfunction Config() {}\t\t\t// This exists solely to make instanceof work.\nConfig.prototype = {};\n\nexports.defaults = {\n\t\"warning\": \"EDITING THIS FILE WHILE NIBBLER IS RUNNING WILL GENERALLY CAUSE YOUR EDITS TO BE LOST.\",\n\n\t\"language\": \"English\",\n\n\t\"path\": null,\t\t\t\t// Not undefined, all normal keys should have an actual value.\n\n\t\"args_unused\": null,\n\t\"options_unused\": null,\n\n\t\"disable_hw_accel\": false,\n\n\t\"width\": 1280,\n\t\"height\": 835,\n\t\"board_size\": 640,\n\t\"info_font_size\": 16,\n\t\"pgn_font_size\": 16,\n\t\"fen_font_size\": 16,\n\t\"arrow_width\": 8,\n\t\"arrowhead_radius\": 12,\n\t\"board_font\": \"18px Arial\",\n\n\t\"graph_height\": 96,\n\t\"graph_line_width\": 2,\n\t\"graph_minimum_length\": 41,\t\t\t\t\t// Desired depth + 1\n\n\t\"light_square\": \"#dadada\",\n\t\"dark_square\": \"#b4b4b4\",\n\t\"active_square\": \"#66aaaa\",\n\t\"move_squares_with_alpha\": \"#ffff0026\",\n\n\t\"best_colour\": \"#66aaaa\",\n\t\"good_colour\": \"#66aa66\",\n\t\"bad_colour\": \"#cccc66\",\n\t\"terrible_colour\": \"#cc6666\",\n\t\"actual_move_colour\": \"#cc9966\",\n\n\t\"searchmoves_buttons\": true,\n\t\"focus_on_text\": \"focused:\",\n\t\"focus_off_text\": \"focus?\",\n\n\t\"accept_bounds\": false,\n\t\"max_info_lines\": null,\t\t\t\t\t\t// Hidden option\n\n\t\"bad_move_threshold\": 0.02,\n\t\"terrible_move_threshold\": 0.04,\n\t\"ab_filter_threshold\": 0.1,\n\n\t\"arrow_filter_type\": \"N\",\n\t\"arrow_filter_value\": 0.01,\n\n\t\"arrows_enabled\": true,\n\t\"click_spotlight\": true,\n\t\"next_move_arrow\": false,\n\t\"next_move_outline\": false,\n\t\"next_move_unique_colour\": false,\n\t\"arrowhead_type\": 0,\n\n\t\"ev_pov\": null,\n\t\"cp_pov\": null,\n\t\"wdl_pov\": null,\n\n\t\"show_cp\": false,\n\t\"show_n\": true,\n\t\"show_n_abs\": true,\n\t\"show_depth\": true,\n\t\"show_p\": true,\n\t\"show_v\": false,\n\t\"show_q\": false,\n\t\"show_u\": false,\n\t\"show_s\": false,\n\t\"show_m\": false,\n\t\"show_wdl\": true,\n\t\"infobox_stats_newline\": false,\n\t\"infobox_pv_move_numbers\": false,\n\t\"hover_draw\": false,\n\t\"hover_method\": 2,\n\n\t\"looker_api\": null,\n\t\"look_past_25\": false,\n\n\t\"lichess_token\": \"\",\n\n\t\"pv_click_event\": 1,\t\t// 0: nothing, 1: goto, 2: tree\n\n\t\"pgn_ev\": true,\n\t\"pgn_cp\": false,\n\t\"pgn_n\": true,\n\t\"pgn_n_abs\": false,\n\t\"pgn_of_n\": true,\n\t\"pgn_depth\": false,\n\t\"pgn_p\": false,\n\t\"pgn_v\": false,\n\t\"pgn_q\": false,\n\t\"pgn_u\": false,\n\t\"pgn_s\": false,\n\t\"pgn_m\": false,\n\t\"pgn_wdl\": false,\n\n\t\"pgn_dialog_folder\": \"\",\n\t\"engine_dialog_folder\": \"\",\n\t\"weights_dialog_folder\": \"\",\n\t\"evalfile_dialog_folder\": \"\",\n\t\"syzygy_dialog_folder\": \"\",\n\t\"pieces_dialog_folder\": \"\",\n\t\"background_dialog_folder\": \"\",\n\t\"book_dialog_folder\": \"\",\n\n\t\"update_delay\": 170,\n\t\"animate_delay_multiplier\": 4,\n\n\t\"allow_arbitrary_scripts\": false,\n\t\"ignore_filesize_limits\": false,\n\t\"allow_stopped_analysis\": false,\n\t\"never_suppress_searchmoves\": true,\n\t\"never_grayout_infolines\": false,\n\t\"suppress_ucinewgame\": false,\n\n\t\"show_engine_state\": false,\n\n\t\"book_depth\": 10,\n\n\t\"save_enabled\": false,\n\t\"override_piece_directory\": null,\n\t\"override_board\": null,\n\n\t\"leelaish_names\": [\"Lc0\", \"Leela\", \"Ceres\"],\t\t// If this gets updated, will need to fix old config files.\n\n\t\"logfile\": null,\n\t\"clear_log\": true,\n\t\"logfile_timestamp\": false,\n\t\"log_info_lines\": false,\n\t\"log_useless_info\": false,\n\t\"log_illegal_moves\": true,\n\t\"log_positions\": true,\n};\n\nfunction fix(cfg) {\n\n\t// We want to create a few temporary things (not saved to file)...\n\n\tcfg.flip = false;\n\tcfg.behaviour = \"halt\";\n\tcfg.square_size = Math.floor(cfg.board_size / 8);\n\n\t// Make sure objectish things at least exist...\n\n\tif (Array.isArray(cfg.leelaish_names) === false) {\n\t\tcfg.leelaish_names = Array.from(exports.defaults.leelaish_names);\n\t}\n\n\t// Fix the board size...\n\n\tcfg.board_size = cfg.square_size * 8;\n\n\t// The uncertainty_cutoff key was removed. Filtering by U was also removed...\n\n\tif (cfg.uncertainty_cutoff !== undefined || cfg.arrow_filter_type === \"U\") {\n\t\tcfg.arrow_filter_type = \"N\";\n\t\tcfg.arrow_filter_value = exports.defaults.arrow_filter_value;\n\t}\n\n\t// This can't be 0 because we divide by it...\n\n\tcfg.animate_delay_multiplier = Math.floor(cfg.animate_delay_multiplier);\n\n\tif (cfg.animate_delay_multiplier <= 0) {\n\t\tcfg.animate_delay_multiplier = 1;\n\t}\n\n\t// We used to expect font sizes to be strings with \"px\"...\n\n\tfor (let key of [\"info_font_size\", \"pgn_font_size\", \"fen_font_size\"]) {\n\t\tif (typeof cfg[key] === \"string\") {\n\t\t\tcfg[key] = parseInt(cfg[key], 10);\t\t// Works even if string ends with \"px\"\n\t\t\tif (Number.isNaN(cfg[key])) {\n\t\t\t\tcfg[key] = exports.defaults[key];\n\t\t\t}\n\t\t}\n\t}\n\n\t// Convert any strings of \"false\", \"true\" and \"null\"...\n\n\tfor (let key of Object.keys(cfg)) {\n\t\tif (typeof cfg[key] === \"string\") {\n\t\t\tif (cfg[key].toLowerCase() === \"true\") {\n\t\t\t\tcfg[key] = true;\n\t\t\t} else if (cfg[key].toLowerCase() === \"false\") {\n\t\t\t\tcfg[key] = false;\n\t\t\t} else if (cfg[key].toLowerCase() === \"null\") {\n\t\t\t\tcfg[key] = null;\n\t\t\t}\n\t\t}\n\t}\n\n\t// These things need to be strings. They are used as defaultPath parameters\n\t// but versions of Electron >= 6 (I think) crash when they aren't strings.\n\t// Sadly we defaulted them to null in 1.2.1 so bad config files may exist.\n\n\tif (typeof cfg.pgn_dialog_folder !== \"string\") {\n\t\tcfg.pgn_dialog_folder = \"\";\n\t}\n\tif (typeof cfg.engine_dialog_folder !== \"string\") {\n\t\tcfg.engine_dialog_folder = \"\";\n\t}\n\tif (typeof cfg.weights_dialog_folder !== \"string\") {\n\t\tcfg.weights_dialog_folder = \"\";\n\t}\n\n\t// These three vars were replaced...\n\n\tif (cfg.ev_white_pov) {\n\t\tcfg.ev_pov = \"w\";\n\t}\n\tif (cfg.cp_white_pov) {\n\t\tcfg.cp_pov = \"w\";\n\t}\n\tif (cfg.wdl_white_pov) {\n\t\tcfg.wdl_pov = \"w\";\n\t}\n\n\t// Too many people are setting this...\n\n\tcfg.show_engine_state = exports.defaults.show_engine_state;\n}\n\nexports.load = () => {\n\n\tlet cfg = new Config();\n\n\tlet err_to_return = null;\n\n\ttry {\n\t\tif (fs.existsSync(exports.filepath)) {\n\t\t\tlet raw = fs.readFileSync(exports.filepath, \"utf8\");\n\t\t\ttry {\n\t\t\t\tObject.assign(cfg, JSON.parse(raw));\n\t\t\t} catch (err) {\n\t\t\t\tconsole.log(exports.filename, err.toString(), \"...trying to debork...\");\n\t\t\t\tObject.assign(cfg, JSON.parse(debork_json(raw)));\n\t\t\t}\n\t\t}\n\t} catch (err) {\n\t\tconsole.log(err.toString());\t\t\t\t\t\t\t// alert() might not be available.\n\t\terr_to_return = err.toString();\n\t}\n\n\t// Copy default values for any missing keys into the config...\n\t// We use a copy so that any objects that are assigned are not the default objects.\n\n\tlet defaults_copy = JSON.parse(JSON.stringify(exports.defaults));\n\n\tfor (let key of Object.keys(defaults_copy)) {\n\t\tif (cfg.hasOwnProperty(key) === false) {\n\t\t\tcfg[key] = defaults_copy[key];\n\t\t}\n\t}\n\n\tfix(cfg);\n\treturn [err_to_return, cfg];\n};\n\nexports.save = (cfg) => {\n\n\tif (cfg instanceof Config === false) {\n\t\tthrow \"Wrong type of object sent to config_io.save()\";\n\t}\n\n\t// Make a copy of the defaults. Doing it this way seems to\n\t// ensure the final JSON string has the same ordering...\n\n\tlet out = JSON.parse(JSON.stringify(exports.defaults));\n\n\t// Adjust that copy, but only for keys present in both.\n\n\tfor (let key of Object.keys(cfg)) {\n\t\tif (out.hasOwnProperty(key)) {\n\t\t\tout[key] = cfg[key];\n\t\t}\n\t}\n\n\ttry {\n\t\tfs.writeFileSync(exports.filepath, JSON.stringify(out, null, \"\\t\"));\n\t} catch (err) {\n\t\tconsole.log(err.toString());\t\t// alert() might not be available.\n\t}\n};\n\nexports.create_if_needed = (cfg) => {\n\n\t// Note that this must be called fairly late, when userData directory exists.\n\n\tif (cfg instanceof Config === false) {\n\t\tthrow \"Wrong type of object sent to config_io.create_if_needed()\";\n\t}\n\n\tif (fs.existsSync(exports.filepath)) {\n\t\treturn;\n\t}\n\n\texports.save(cfg);\n};\n"
  },
  {
    "path": "files/src/modules/custom_uci.js",
    "content": "\"use strict\";\n\nconst electron = require(\"electron\");\nconst fs = require(\"fs\");\nconst path = require(\"path\");\nconst querystring = require(\"querystring\");\n\nconst scripts_dir = \"scripts\";\nconst example_file = \"example.txt\";\n\nconst example =\n`setoption name Something value WhoKnows\nsetoption name Example value Whatever`;\n\n// To avoid using \"remote\", we rely on the main process passing userData location in the query...\n\nexports.script_dir_path = electron.app ?\n\t\tpath.join(electron.app.getPath(\"userData\"), scripts_dir) :\n\t\tpath.join(querystring.parse(global.location.search.slice(1))[\"user_data_path\"], scripts_dir);\n\nexports.load = () => {\n\n\ttry {\n\t\tlet files = fs.readdirSync(exports.script_dir_path);\n\n\t\tlet ret = [];\n\n\t\tfor (let file of files) {\n\t\t\tret.push({\n\t\t\t\tname: file,\n\t\t\t\tpath: path.join(exports.script_dir_path, file)\n\t\t\t});\n\t\t}\n\n\t\treturn ret;\n\n\t} catch (err) {\n\n\t\treturn [\n\t\t\t{\n\t\t\t\tname: example_file,\n\t\t\t\tpath: path.join(exports.script_dir_path, example_file)\n\t\t\t}\n\t\t];\n\n\t}\n};\n\nexports.create_if_needed = () => {\n\n\t// Note that this must be called fairly late, when userData directory exists.\n\n\ttry {\n\t\tif (!fs.existsSync(exports.script_dir_path)) {\n\t\t\tfs.mkdirSync(exports.script_dir_path);\n\t\t\tlet example_path = path.join(exports.script_dir_path, example_file);\n\t\t\tfs.writeFileSync(example_path, example);\n\t\t}\n\t} catch (err) {\n\t\tconsole.log(err.toString());\n\t}\n};\n"
  },
  {
    "path": "files/src/modules/debork_json.js",
    "content": "\"use strict\";\n\nfunction replace_all(s, search, replace) {\n\tif (!s.includes(search)) {\t\t\t\t\t// Seems to improve speed overall.\n\t\treturn s;\n\t}\n\treturn s.split(search).join(replace);\n}\n\nfunction debork_json(s) {\n\n\t// Convert totally blank files into {}\n\n\tif (s.length < 50 && s.trim() === \"\") {\n\t\ts = \"{}\";\n\t}\n\n\t// Replace fruity quote characters. Note that these could exist in legit JSON,\n\t// which is why we only call this function if the first parse fails...\n\n\ts = replace_all(s, '“', '\"');\n\ts = replace_all(s, '”', '\"');\n\n\t// Replace single \\ characters\n\n\ts = replace_all(s, \"\\\\\\\\\", \"correct_zbcyg278gfdakjadjk\");\n\ts = replace_all(s, \"\\\\\", \"\\\\\\\\\");\n\ts = replace_all(s, \"correct_zbcyg278gfdakjadjk\", \"\\\\\\\\\");\n\n\treturn s;\n}\n\nmodule.exports = debork_json;\n"
  },
  {
    "path": "files/src/modules/empty.js",
    "content": "\"use strict\";\n\n// The most perfect JS module ever written. Extensive testing\n// shows that this module contains less than 20 bugs.\n"
  },
  {
    "path": "files/src/modules/engineconfig_io.js",
    "content": "\"use strict\";\n\nconst electron = require(\"electron\");\nconst fs = require(\"fs\");\nconst path = require(\"path\");\nconst querystring = require(\"querystring\");\n\nconst debork_json = require(\"./debork_json\");\n\nexports.filename = \"engines.json\";\n\n// To avoid using \"remote\", we rely on the main process passing userData location in the query...\n\nexports.filepath = electron.app ?\n\t\tpath.join(electron.app.getPath(\"userData\"), exports.filename) :\t\t\t\t\t\t\t\t\t\t\t// in Main process\n\t\tpath.join(querystring.parse(global.location.search.slice(1))[\"user_data_path\"], exports.filename);\t\t// in Renderer process\n\nfunction EngineConfig() {}\t\t\t// This exists solely to make instanceof work.\nEngineConfig.prototype = {};\n\n// ---------------------------------------------------------------------------------------------------------------------------\n\nfunction fix(cfg) {\n\n\t// The nameless dummy that hub creates at startup needs an entry...\n\n\tcfg[\"\"] = exports.newentry();\n\n\t// Fix any saved entries present in the file...\n\n\tfor (let key of Object.keys(cfg)) {\n\t\tif (typeof cfg[key] !== \"object\" || cfg[key] === null) {\n\t\t\tcfg[key] = exports.newentry();\n\t\t}\n\t\tif (Array.isArray(cfg[key].args) === false) {\n\t\t\tcfg[key].args = [];\n\t\t}\n\t\tif (typeof cfg[key].options !== \"object\" || cfg[key].options === null) {\n\t\t\tcfg[key].options = {};\n\t\t}\n\t\tif (typeof cfg[key].limit_by_time !== \"boolean\") {\n\t\t\tcfg[key].limit_by_time = cfg[key].limit_by_time ? true : false;\n\t\t}\n\n\t\t// We don't really care about missing search_nodes and search_nodes_special properties. (?)\n\t}\n}\n\nexports.newentry = () => {\n\treturn {\n\t\t\"args\": [],\n\t\t\"options\": {},\n\t\t\"search_nodes\": null,\n\t\t\"search_nodes_special\": 10000,\n\t\t\"limit_by_time\": false,\n\t};\n};\n\nexports.load = () => {\n\n\tlet cfg = new EngineConfig();\n\n\tlet err_to_return = null;\n\n\ttry {\n\t\tif (fs.existsSync(exports.filepath)) {\n\t\t\tlet raw = fs.readFileSync(exports.filepath, \"utf8\");\n\t\t\ttry {\n\t\t\t\tObject.assign(cfg, JSON.parse(raw))\n\t\t\t} catch (err) {\n\t\t\t\tconsole.log(exports.filename, err.toString(), \"...trying to debork...\");\n\t\t\t\tObject.assign(cfg, JSON.parse(debork_json(raw)));\n\t\t\t}\n\t\t}\n\t} catch (err) {\n\t\tconsole.log(err.toString());\t\t\t\t\t\t\t// alert() might not be available.\n\t\terr_to_return = err.toString();\n\t}\n\n\tfix(cfg);\n\treturn [err_to_return, cfg];\n};\n\nexports.save = (cfg) => {\n\n\tif (cfg instanceof EngineConfig === false) {\n\t\tthrow \"Wrong type of object sent to engineconfig_io.save()\";\n\t}\n\n\tlet blank = cfg[\"\"];\n\tdelete cfg[\"\"];\n\n\ttry {\n\t\tfs.writeFileSync(exports.filepath, JSON.stringify(cfg, null, \"\\t\"));\n\t} catch (err) {\n\t\tconsole.log(err.toString());\t\t\t\t\t\t\t// alert() might not be available.\n\t}\n\n\tcfg[\"\"] = blank;\n};\n\nexports.create_if_needed = (cfg) => {\n\n\t// Note that this must be called fairly late, when userData directory exists.\n\n\tif (cfg instanceof EngineConfig === false) {\n\t\tthrow \"Wrong type of object sent to engineconfig_io.create_if_needed()\";\n\t}\n\n\tif (fs.existsSync(exports.filepath)) {\n\t\treturn;\n\t}\n\n\texports.save(cfg);\n};\n"
  },
  {
    "path": "files/src/modules/images.js",
    "content": "\"use strict\";\n\nconst fs = require(\"fs\");\nconst path = require(\"path\");\n\nlet sprites = {\n\n\tloads: 0,\n\n\tfully_loaded: function() {\n\t\treturn this.loads === 12;\n\t},\n\n\tvalidate_folder: function(directory) {\n\n\t\tif (typeof directory !== \"string\") {\n\t\t\treturn false;\n\t\t}\n\n\t\tfor (let c of \"KkQqRrBbNnPp\") {\n\n\t\t\tif (!fs.existsSync(path.join(directory, `${c.toUpperCase()}.svg`))) {\n\t\t\t\tif (!fs.existsSync(path.join(directory, `${c.toUpperCase()}.png`))) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!fs.existsSync(path.join(directory, `_${c.toUpperCase()}.svg`))) {\n\t\t\t\tif (!fs.existsSync(path.join(directory, `_${c.toUpperCase()}.png`))) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t},\n\n\tload_from: function(directory) {\n\n\t\tlet urlsafe_directory = directory.replace(/#/g, \"%23\");\t\t// Looks like replacing # with %23 is the only thing that's needed? Maybe some others??\n\n\t\tsprites.loads = 0;\n\n\t\tfor (let c of \"KkQqRrBbNnPp\") {\n\n\t\t\tsprites[c] = new Image();\n\t\t\tsprites[c].addEventListener(\"load\", () => {sprites.loads++;}, {once: true});\n\n\t\t\tif (c === c.toUpperCase()) {\n\n\t\t\t\tsprites[c].addEventListener(\"error\", () => {console.log(`Failed to load image ${c}.svg or ${c}.png`);}, {once: true});\n\n\t\t\t\tif (fs.existsSync(path.join(directory, `${c}.svg`))) {\n\t\t\t\t\tsprites[c].src = path.join(urlsafe_directory, `${c}.svg`);\n\t\t\t\t} else if (fs.existsSync(path.join(directory, `${c}.png`))) {\n\t\t\t\t\tsprites[c].src = path.join(urlsafe_directory, `${c}.png`);\n\t\t\t\t}\n\n\t\t\t} else {\n\n\t\t\t\tsprites[c].addEventListener(\"error\", () => {console.log(`Failed to load image _${c.toUpperCase()}.svg or _${c.toUpperCase()}.png`);}, {once: true});\n\n\t\t\t\tif (fs.existsSync(path.join(directory, `_${c.toUpperCase()}.svg`))) {\n\t\t\t\t\tsprites[c].src = path.join(urlsafe_directory, `_${c.toUpperCase()}.svg`);\n\t\t\t\t} else if (fs.existsSync(path.join(directory, `_${c.toUpperCase()}.png`))) {\n\t\t\t\t\tsprites[c].src = path.join(urlsafe_directory, `_${c.toUpperCase()}.png`);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Note that, after the src is set above, it is automatically changed by the JS engine to be something like\n\t\t\t// \"file:///C:/foo/bar/whatever.png\"\n\n\t\t\tsprites[c].string_for_bg_style = `url(\"${sprites[c].src}\")`;\t\t// Since the src path won't contain \" this should be safe.\n\t\t}\n\t},\n};\n\nmodule.exports = sprites;\n"
  },
  {
    "path": "files/src/modules/messages.js",
    "content": "\"use strict\";\n\nconst config_io = require(\"./config_io\");\nconst engineconfig_io = require(\"./engineconfig_io\");\n\n\nexports.about_versus_mode = `The \"play this colour\" option causes Leela to \\\nevaluate one side of the position only. The top move is automatically played on \\\nthe board upon reaching the node limit (see the Engine menu). This allows you to \\\nplay against Leela.\n\nThe \"self-play\" option causes Leela to play itself.\n\nHigher temperature makes the moves less predictable, but at some cost to move \\\ncorrectness. Meanwhile, TempDecayMoves specifies how many moves the temperature \\\neffect lasts for. These settings have no effect on analysis, only actual move \\\ngeneration.`;\n\n\nexports.save_not_enabled = `Save is disabled until you read the following \\\nwarning.\n\nNibbler does not append to PGN files, nor does it save collections. It only \\\nwrites the current game to file. When you try to save, you will be prompted with \\\na standard \"Save As\" dialog. If you save to a file that already exists, that \\\nfile will be DESTROYED and REPLACED with a file containing only the current \\\ngame.\n\nYou can enable save in the dev menu.`;\n\n\nexports.engine_not_present = `Engine not found. Please find the engine via the \\\nEngine menu. You might also need to locate the weights (neural network) file.`;\n\n\nexports.engine_failed_to_start = `Engine failed to start.`;\n\n\nexports.uncaught_exception = `There may have been an uncaught exception. If you \\\ncould open the dev tools and the console tab therein, and report the contents to \\\nthe author (ideally with a screenshot) that would be grand.`;\n\n\nexports.renderer_crash = `The renderer process has crashed. Experience suggests \\\nthis happens when Leela runs out of RAM. If this doesn't apply, please tell the \\\nauthor how you made it happen.`;\n\n\nexports.renderer_hang = `The renderer process may have hung. Please tell the \\\nauthor how you made this happen.`;\n\n\nexports.about_sizes = `You can get more fine-grained control of font, board, \\\ngraph, and window sizes via Nibbler's config file (which can be found via the \\\nDev menu).`;\n\n\nexports.about_hashes = `You can set the Hash value directly via Nibbler's \\\n${engineconfig_io.filename} file (which can be found via the Dev menu).`;\n\n\nexports.thread_warning = `Note that, for systems using a GPU, 2 threads is usually \\\nsufficient for Leela, and increasing this number can actually make Leela weaker! \\\nMore threads should probably only be used on CPU-only systems, if at all.\n\nIf no tick is present in this menu, the default is being used, which is probably \\\nwhat you want.`;\n\n\nexports.adding_scripts = `Nibbler has a scripts folder, inside which you can \\\nplace scripts of raw input to send to the engine. A small example file is \\\nprovided. This is for advanced users and devs who understand the UCI protocol.\n\nNote that this is for configuration only.`;\n\n\nexports.invalid_script = `Bad script; scripts are for configuration only.`;\n\n\nexports.wrong_engine_exe = `That is almost certainly the wrong file. What we \\\nneed is likely to be called lc0.exe or lc0.`;\n\n\nexports.send_fail = `Sending to the engine failed. This usually means it has \\\ncrashed.`;\n\n\nexports.invalid_pieces_directory = `Did not find all pieces required!`;\n\n\nexports.about_custom_pieces = `To use a custom piece set, select a folder \\\ncontaining SVG or PNG files with names such as \"Q.png\" (or \"Q.svg\") for white \\\nand \"_Q.png\" (or \"_Q.svg\") for black.`;\n\n\nexports.desync = `Desync... (restart engine via Engine menu)`;\n\n\nexports.c960_warning = `We appear to have entered a game of Chess960, however \\\nthis engine does not support Chess960. Who knows what will happen. Probably not \\\ngood things. Maybe bad things.`;\n\n\nexports.bad_bin_book = `This book contained unsorted keys and is therefore not a \\\nvalid Polyglot book.`;\n\n\nexports.file_too_big = `Sorry, this file is probably too large to be safely \\\nloaded in Nibbler. If you want, you can suppress this warning in the Dev menu, \\\nand try to load the file anyway.`;\n\n\nexports.pgn_book_too_big = `This file is impractically large for a PGN book - \\\nconsider converting it to Polyglot (.bin) format. If you want, you can suppress \\\nthis warning in the Dev menu, and try to load the file anyway.`;\n\n\nexports.engine_options_reset = `As of v2.1.1, Nibbler will store engine options \\\nseparately for each engine. To facilite this, your engine options have been \\\nreset. If you were using special (hand-edited) options, they are still present \\\nin your ${config_io.filename} file, and can be manually moved to \\\n${engineconfig_io.filename}.`;\n\n\nexports.too_soon_to_set_options = `Please wait till the engine has loaded before \\\nsetting options.`;\n\n\nexports.lichess_token_needed = `This likely will not work until you set a Lichess \\\nAPI token.`;\n\n"
  },
  {
    "path": "files/src/modules/running_as_electron.js",
    "content": "\"use strict\";\n\nconst path = require(\"path\");\n\n// Is there not a better way? Perhaps some Electron API somewhere?\n\nmodule.exports = () => {\n\treturn path.basename(process.argv[0]).toLowerCase().includes(\"electron\");\n};\n"
  },
  {
    "path": "files/src/modules/stringify.js",
    "content": "\"use strict\";\n\n// Given anything, create a string from it.\n// Helps with sending messages over IPC, displaying alerts, etc.\n\nmodule.exports = (msg) => {\n\n\ttry {\n\n\t\tif (msg instanceof Error) {\n\t\t\tmsg = msg.toString();\n\t\t}\n\t\tif (typeof msg === \"object\") {\n\t\t\tmsg = JSON.stringify(msg);\n\t\t}\n\t\tif (typeof msg === \"undefined\") {\n\t\t\tmsg = \"undefined\";\n\t\t}\n\t\tmsg = msg.toString().trim();\n\t\treturn msg;\n\n\t} catch (err) {\n\n\t\treturn \"stringify() failed\";\n\n\t}\n};\n"
  },
  {
    "path": "files/src/modules/translate.js",
    "content": "\"use strict\";\n\nconst translations = require(\"./translations\");\n\nlet startup_language = null;\n\nexports.register_startup_language = function(s) {\t\t// Will have to be called in both processes (assuming renderer ever uses this at all).\n\tstartup_language = s;\n}\n\nexports.translate = function(key, force_language = null) {\n\n\t// Note that we usually use the language which was in config.json at startup so\n\t// that in-flight calls to translate() return consistent results even if the user\n\t// switches config.language at some point. (Thus, the user will need to restart\n\t// to see any change.)\n\n\tlet language = force_language || startup_language;\n\n\tif (translations[language] && translations[language][key]) {\n\t\treturn translations[language][key];\n\t} else {\n\t\treturn key;\n\t}\n}\n\nexports.t = exports.translate;\n\nexports.all_languages = function() {\n\treturn Object.keys(translations);\n}\n\n"
  },
  {
    "path": "files/src/modules/translations.js",
    "content": "\"use strict\";\n\nconst config_io = require(\"./config_io\");\nconst engineconfig_io = require(\"./engineconfig_io\");\n\n// Some special fake dynamic keys (not actually dynamic in any way)...\n\nconst show_config = `Show ${config_io.filename}`;\nconst show_engineconfig = `Show ${engineconfig_io.filename}`;\nconst reload_engineconfig = `Reload ${engineconfig_io.filename} (and restart engine)`;\n\n// In general keys will just be the English version.\n// Exceptions will be in UPPERCASE_FORMAT.\n\nlet translations = {\n\n\t\"English\": {\n\t\t\"RESTART_REQUIRED\": \"The GUI must now be restarted.\",\n\t},\n\n\t// FRENCH .....................................................................................\n\n\t\"Français\": {\n\t\t\"File\": \"Fichier\",\n\t\t\t\"About\": \"À propos\",\n\t\t\t\"New game\": \"Nouvelle partie\",\n\t\t\t\"New 960 game\": \"Nouvelle partie 960\",\n\t\t\t\"Open PGN...\": \"Ouvrir PGN...\",\n\t\t\t\"Load FEN / PGN from clipboard\": \"Charger FEN / PGN du presse-papiers\",\n\t\t\t\"Save this game...\": \"Sauvegarder cette partie...\",\n\t\t\t\"Write PGN to clipboard\": \"Copier PGN dans le presse-papiers\",\n\t\t\t\"PGN saved statistics\": \"Statistiques PGN sauvegardées\",\n\t\t\t\t\"EV\": \"EV\",\n\t\t\t\t\"Centipawns\": \"Centipions\",\n\t\t\t\t\"N (%)\": \"N (%)\",\n\t\t\t\t\"N (absolute)\": \"N (absolu)\",\n\t\t\t\t\"...out of total\": \"...sur total\",\n\t\t\t\t\"Depth (A/B only)\": \"Profondeur (A/B uniquement)\",\n\t\t\t\"Cut\": \"Couper\",\n\t\t\t\"Copy\": \"Copier\",\n\t\t\t\"Paste\": \"Coller\",\n\t\t\t\"Quit\": \"Quitter\",\n\t\t\"Tree\": \"Arbre\",\n\t\t\t\"Play engine choice\": \"Jouer le choix du moteur\",\n\t\t\t\t\"1st\": \"1er\",\n\t\t\t\t\"2nd\": \"2ème\",\n\t\t\t\t\"3rd\": \"3ème\",\n\t\t\t\t\"4th\": \"4ème\",\n\t\t\t\"Root\": \"Racine\",\n\t\t\t\"End\": \"Fin\",\n\t\t\t\"Backward\": \"Reculer\",\n\t\t\t\"Forward\": \"Avancer\",\n\t\t\t\"Previous sibling\": \"Variante précédente\",\n\t\t\t\"Next sibling\": \"Variante suivante\",\n\t\t\t\"Return to main line\": \"Retour à la ligne principale\",\n\t\t\t\"Promote line to main line\": \"Promouvoir en ligne principale\",\n\t\t\t\"Promote line by 1 level\": \"Promouvoir d'un niveau\",\n\t\t\t\"Delete node\": \"Supprimer le nœud\",\n\t\t\t\"Delete children\": \"Supprimer les variantes\",\n\t\t\t\"Delete siblings\": \"Supprimer les alternatives\",\n\t\t\t\"Delete ALL other lines\": \"Supprimer TOUTES les autres lignes\",\n\t\t\t\"Show PGN games list\": \"Afficher la liste des parties PGN\",\n\t\t\t\"Escape\": \"Échapper\",\n\t\t\"Analysis\": \"Analyse\",\n\t\t\t\"Go\": \"Démarrer\",\n\t\t\t\"Go and lock engine\": \"Démarrer et verrouiller le moteur\",\n\t\t\t\"Return to locked position\": \"Retour à la position verrouillée\",\n\t\t\t\"Halt\": \"Arrêter\",\n\t\t\t\"Auto-evaluate line\": \"Auto-évaluer la ligne\",\n\t\t\t\"Auto-evaluate line, backwards\": \"Auto-évaluer la ligne, en arrière\",\n\t\t\t\"Show focus (searchmoves) buttons\": \"Afficher les boutons de focus\",\n\t\t\t\"Clear focus\": \"Effacer le focus\",\n\t\t\t\"Invert focus\": \"Inverser le focus\",\n\t\t\t\"Winrate POV\": \"PDV taux de victoire\",\n\t\t\t\t\"Current\": \"Actuel\",\n\t\t\t\t\"White\": \"Blancs\",\n\t\t\t\t\"Black\": \"Noirs\",\n\t\t\t\"Centipawn POV\": \"PDV centipions\",\n\t\t\t\"Win / draw / loss POV\": \"PDV gain / nulle / perte\",\n\t\t\t\"PV clicks\": \"Clics PV\",\n\t\t\t\t\"Do nothing\": \"Ne rien faire\",\n\t\t\t\t\"Go there\": \"Y aller\",\n\t\t\t\t\"Add to tree\": \"Ajouter à l'arbre\",\n\t\t\t\"Write infobox to clipboard\": \"Copier infobox dans le presse-papiers\",\n\t\t\t\"Forget all analysis\": \"Oublier toutes les analyses\",\n\t\t\"Display\": \"Affichage\",\n\t\t\t\"Flip board\": \"Retourner l'échiquier\",\n\t\t\t\"Arrows\": \"Flèches\",\n\t\t\t\"Piece-click spotlight\": \"Surbrillance pièce-clic\",\n\t\t\t\"Always show actual move (if known)\": \"Toujours montrer le coup joué (si connu)\",\n\t\t\t\"...with unique colour\": \"...avec couleur unique\",\n\t\t\t\"...with outline\": \"...avec contour\",\n\t\t\t\"Arrowhead type\": \"Type de flèche\",\n\t\t\t\t\"Winrate\": \"Taux de victoire\",\n\t\t\t\t\"Node %\": \"Nœud %\",\n\t\t\t\t\"Policy\": \"Policy\",\n\t\t\t\t\"MultiPV rank\": \"Rang MultiPV\",\n\t\t\t\t\"Moves Left Head\": \"Coups restants\",\n\t\t\t\"Arrow filter (Lc0)\": \"Filtre flèches (Lc0)\",\n\t\t\t\t\"All moves\": \"Tous les coups\",\n\t\t\t\t\"Top move\": \"Meilleur coup\",\n\t\t\t\"Arrow filter (others)\": \"Filtre flèches (autres)\",\n\t\t\t\t\"Diff < 15%\": \"Diff < 15%\",\n\t\t\t\t\"Diff < 10%\": \"Diff < 10%\",\n\t\t\t\t\"Diff < 5%\": \"Diff < 5%\",\n\t\t\t\"Infobox stats\": \"Stats infobox\",\n\t\t\t\t\"N - nodes (%)\": \"N - nœuds (%)\",\n\t\t\t\t\"N - nodes (absolute)\": \"N - nœuds (absolu)\",\n\t\t\t\t\"P - policy\": \"P - Policy\",\n\t\t\t\t\"V - static evaluation\": \"V - évaluation statique\",\n\t\t\t\t\"Q - evaluation\": \"Q - évaluation\",\n\t\t\t\t\"U - uncertainty\": \"U - incertitude\",\n\t\t\t\t\"S - search priority\": \"S - priorité de recherche\",\n\t\t\t\t\"M - moves left\": \"M - coups restants\",\n\t\t\t\t\"WDL - win / draw / loss\": \"WDL - gain / nulle / perte\",\n\t\t\t\t\"Linebreak before stats\": \"Retour à la ligne avant stats\",\n\t\t\t\"PV move numbers\": \"Numéros des coups PV\",\n\t\t\t\"Online API\": \"API en ligne\",\n\t\t\t\t\"None\": \"Aucun\",\n\t\t\t\t\"ChessDB.cn evals\": \"Évals ChessDB.cn\",\n\t\t\t\t\"Lichess results (masters)\": \"Résultats Lichess (maîtres)\",\n\t\t\t\t\"Lichess results (plebs)\": \"Résultats Lichess (ordinaires)\",\n\t\t\t\t\"Set Lichess API token\": \"Définir le jeton API Lichess\",\n\t\t\t\"Allow API after move 25\": \"Autoriser API après coup 25\",\n\t\t\t\"Draw PV on mouseover\": \"Afficher PV au survol\",\n\t\t\t\"Draw PV method\": \"Méthode d'affichage PV\",\n\t\t\t\t\"Animate\": \"Animer\",\n\t\t\t\t\"Single move\": \"Coup unique\",\n\t\t\t\t\"Final position\": \"Position finale\",\n\t\t\t\"Pieces\": \"Pièces\",\n\t\t\t\t\"Choose pieces folder...\": \"Choisir dossier pièces...\",\n\t\t\t\t\"Default\": \"Par défaut\",\n\t\t\t\t\"About custom pieces\": \"À propos des pièces personnalisées\",\n\t\t\t\"Background\": \"Arrière-plan\",\n\t\t\t\t\"Choose background image...\": \"Choisir image d'arrière-plan...\",\n\t\t\t\"Book frequency arrows\": \"Flèches fréquence bibliothèque\",\n\t\t\t\"Lichess frequency arrows\": \"Flèches fréquence Lichess\",\n\t\t\"Sizes\": \"Tailles\",\n\t\t\t\"Infobox font\": \"Police infobox\",\n\t\t\t\"Move history font\": \"Police historique coups\",\n\t\t\t\"Board\": \"Échiquier\",\n\t\t\t\t\"Giant\": \"Géant\",\n\t\t\t\t\"Large\": \"Grand\",\n\t\t\t\t\"Medium\": \"Moyen\",\n\t\t\t\t\"Small\": \"Petit\",\n\t\t\t\"Graph\": \"Graphique\",\n\t\t\t\"Graph lines\": \"Lignes graphique\",\n\t\t\t\"I want other size options!\": \"Je veux d'autres options de taille !\",\n\t\t\"Engine\": \"Moteur\",\n\t\t\t\"Choose engine...\": \"Choisir moteur...\",\n\t\t\t\"Choose known engine...\": \"Choisir moteur connu...\",\n\t\t\t\"Weights\": \"Poids\",\n\t\t\t\t\"Lc0 WeightsFile...\": \"Fichier poids Lc0...\",\n\t\t\t\t\"Stockfish EvalFile...\": \"Fichier éval Stockfish...\",\n\t\t\t\t\"Set to <auto>\": \"Définir sur <auto>\",\n\t\t\t\"Backend\": \"Backend\",\n\t\t\t\"Choose Syzygy path...\": \"Choisir chemin Syzygy...\",\n\t\t\t\"Unset\": \"Désactiver\",\n\t\t\t\"Limit - normal\": \"Limite - normal\",\n\t\t\t\t\"Unlimited\": \"Illimité\",\n\t\t\t\t\"Up slightly\": \"Augmenter légèrement\",\n\t\t\t\t\"Down slightly\": \"Diminuer légèrement\",\n\t\t\t\"Limit - auto-eval / play\": \"Limite - auto-éval / jeu\",\n\t\t\t\"Limit by time instead of nodes\": \"Limiter par temps au lieu des nœuds\",\n\t\t\t\"Threads\": \"Threads\",\n\t\t\t\t\"Warning about threads\": \"Avertissement threads\",\n\t\t\t\"Hash\": \"Hash\",\n\t\t\t\t\"I want other hash options!\": \"Je veux d'autres options de hash !\",\n\t\t\t\"MultiPV\": \"MultiPV\",\n\t\t\t\"Contempt Mode\": \"Mode mépris\",\n\t\t\t\t\"White analysis\": \"Analyse blancs\",\n\t\t\t\t\"Black analysis\": \"Analyse noirs\",\n\t\t\t\"Contempt\": \"Mépris\",\n\t\t\t\"WDL Calibration Elo\": \"Calibration Elo WDL\",\n\t\t\t\t\"Use default WDL\": \"Utiliser WDL par défaut\",\n\t\t\t\"WDL Eval Objectivity\": \"Objectivité éval WDL\",\n\t\t\t\t\"Yes\": \"Oui\",\n\t\t\t\t\"No\": \"Non\",\n\t\t\t\"Score Type\": \"Type de score\",\n\t\t\t\"Custom scripts\": \"Scripts personnalisés\",\n\t\t\t\t\"How to add scripts\": \"Comment ajouter des scripts\",\n\t\t\t\t\"Show scripts folder\": \"Afficher dossier scripts\",\n\t\t\t\"Restart engine\": \"Redémarrer moteur\",\n\t\t\t\"Soft engine reset\": \"Réinitialisation douce moteur\",\n\t\t\"Play\": \"Jouer\",\n\t\t\t\"Play this colour\": \"Jouer cette couleur\",\n\t\t\t\"Start self-play\": \"Démarrer auto-jeu\",\n\t\t\t\"Use Polyglot book...\": \"Utiliser bibliothèque Polyglot...\",\n\t\t\t\"Use PGN book...\": \"Utiliser bibliothèque PGN...\",\n\t\t\t\"Unload book / abort load\": \"Décharger biblio / annuler chargement\",\n\t\t\t\"Book depth limit\": \"Limite profondeur bibliothèque\",\n\t\t\t\"Temperature\": \"Température\",\n\t\t\t\"Temp Decay Moves\": \"Décroissance temp. coups\",\n\t\t\t\t\"Infinite\": \"Infini\",\n\t\t\t\"About play modes\": \"À propos des modes de jeu\",\n\t\t\"Dev\": \"Dev\",\n\t\t\t\"Toggle Developer Tools\": \"Basculer outils développeur\",\n\t\t\t\"Toggle Debug CSS\": \"Basculer CSS debug\",\n\t\t\t\"Permanently enable save\": \"Activer sauvegarde permanente\",\n\t\t\t[show_config]: \"Afficher config.json\",\n\t\t\t[show_engineconfig]: \"Afficher engines.json\",\n\t\t\t[reload_engineconfig]: \"Recharger engines.json (et redémarrer moteur)\",\n\t\t\t\"Random move\": \"Coup aléatoire\",\n\t\t\t\"Disable hardware acceleration for GUI\": \"Désactiver accélération matérielle GUI\",\n\t\t\t\"Spin rate\": \"Taux rotation\",\n\t\t\t\t\"Frenetic\": \"Frénétique\",\n\t\t\t\t\"Fast\": \"Rapide\",\n\t\t\t\t\"Normal\": \"Normal\",\n\t\t\t\t\"Relaxed\": \"Détendu\",\n\t\t\t\t\"Lazy\": \"Paresseux\",\n\t\t\t\"Show engine state\": \"Afficher état moteur\",\n\t\t\t\"List sent options\": \"Lister options envoyées\",\n\t\t\t\"Show error log\": \"Afficher journal erreurs\",\n\t\t\t\"Hacks and kludges\": \"Hacks et bidouilles\",\n\t\t\t\t\"Allow arbitrary scripts\": \"Autoriser scripts arbitraires\",\n\t\t\t\t\"Accept any file size\": \"Accepter toute taille fichier\",\n\t\t\t\t\"Allow stopped analysis\": \"Autoriser analyse arrêtée\",\n\t\t\t\t\"Never hide focus buttons\": \"Ne jamais cacher boutons focus\",\n\t\t\t\t\"Never grayout move info\": \"Ne jamais griser info coup\",\n\t\t\t\t\"Use lowerbound / upperbound info\": \"Utiliser info borne inf/sup\",\n\t\t\t\t\"Suppress ucinewgame\": \"Supprimer ucinewgame\",\n\t\t\t\"Log RAM state to console\": \"Logger état RAM console\",\n\t\t\t\"Fire GC\": \"Lancer GC\",\n\t\t\t\"Logging\": \"Journalisation\",\n\t\t\t\t\"Use logfile...\": \"Utiliser fichier journal...\",\n\t\t\t\t\"Disable logging\": \"Désactiver journalisation\",\n\t\t\t\t\"Clear log when opening\": \"Effacer journal à l'ouverture\",\n\t\t\t\t\"Use unique logfile each time\": \"Utiliser fichier journal unique\",\n\t\t\t\t\"Log illegal moves\": \"Logger coups illégaux\",\n\t\t\t\t\"Log positions\": \"Logger positions\",\n\t\t\t\t\"Log info lines\": \"Logger lignes info\",\n\t\t\t\t\"...including useless lines\": \"...lignes inutiles incluses\",\n\t\t\"Language\": \"Langue\",\n\n\t\t\"RESTART_REQUIRED\": \"L'interface doit maintenant être redémarrée.\",\n\t},\n\n\t// SPANISH ....................................................................................\n\n\t\"Español\": {\n\t\t\"File\": \"Archivo\",\n\t\t\t\"About\": \"Acerca de\",\n\t\t\t\"New game\": \"Nueva partida\",\n\t\t\t\"New 960 game\": \"Nueva partida 960\",\n\t\t\t\"Open PGN...\": \"Abrir PGN...\",\n\t\t\t\"Load FEN / PGN from clipboard\": \"Cargar FEN / PGN del portapapeles\",\n\t\t\t\"Save this game...\": \"Guardar esta partida...\",\n\t\t\t\"Write PGN to clipboard\": \"Copiar PGN al portapapeles\",\n\t\t\t\"PGN saved statistics\": \"Estadísticas guardadas en PGN\",\n\t\t\t\t\"EV\": \"EV\",\n\t\t\t\t\"Centipawns\": \"Centipeones\",\n\t\t\t\t\"N (%)\": \"N (%)\",\n\t\t\t\t\"N (absolute)\": \"N (absoluto)\",\n\t\t\t\t\"...out of total\": \"...del total\",\n\t\t\t\t\"Depth (A/B only)\": \"Profundidad (solo A/B)\",\n\t\t\t\"Cut\": \"Cortar\",\n\t\t\t\"Copy\": \"Copiar\",\n\t\t\t\"Paste\": \"Pegar\",\n\t\t\t\"Quit\": \"Salir\",\n\t\t\"Tree\": \"Árbol\",\n\t\t\t\"Play engine choice\": \"Jugar elección del motor\",\n\t\t\t\t\"1st\": \"1ro\",\n\t\t\t\t\"2nd\": \"2do\",\n\t\t\t\t\"3rd\": \"3ro\",\n\t\t\t\t\"4th\": \"4to\",\n\t\t\t\"Root\": \"Raíz\",\n\t\t\t\"End\": \"Final\",\n\t\t\t\"Backward\": \"Atrás\",\n\t\t\t\"Forward\": \"Adelante\",\n\t\t\t\"Previous sibling\": \"Variante anterior\",\n\t\t\t\"Next sibling\": \"Variante siguiente\",\n\t\t\t\"Return to main line\": \"Volver a línea principal\",\n\t\t\t\"Promote line to main line\": \"Promover a línea principal\",\n\t\t\t\"Promote line by 1 level\": \"Promover línea 1 nivel\",\n\t\t\t\"Delete node\": \"Eliminar nodo\",\n\t\t\t\"Delete children\": \"Eliminar variantes\",\n\t\t\t\"Delete siblings\": \"Eliminar hermanos\",\n\t\t\t\"Delete ALL other lines\": \"Eliminar TODAS las otras líneas\",\n\t\t\t\"Show PGN games list\": \"Mostrar lista de partidas PGN\",\n\t\t\t\"Escape\": \"Esc\",\n\t\t\"Analysis\": \"Análisis\",\n\t\t\t\"Go\": \"Iniciar\",\n\t\t\t\"Go and lock engine\": \"Iniciar y bloquear motor\",\n\t\t\t\"Return to locked position\": \"Volver a posición bloqueada\",\n\t\t\t\"Halt\": \"Detener\",\n\t\t\t\"Auto-evaluate line\": \"Auto-evaluar línea\",\n\t\t\t\"Auto-evaluate line, backwards\": \"Auto-evaluar línea, hacia atrás\",\n\t\t\t\"Show focus (searchmoves) buttons\": \"Mostrar botones de enfoque\",\n\t\t\t\"Clear focus\": \"Limpiar enfoque\",\n\t\t\t\"Invert focus\": \"Invertir enfoque\",\n\t\t\t\"Winrate POV\": \"Perspectiva de victoria\",\n\t\t\t\t\"Current\": \"Actual\",\n\t\t\t\t\"White\": \"Blancas\",\n\t\t\t\t\"Black\": \"Negras\",\n\t\t\t\"Centipawn POV\": \"Perspectiva de centipeones\",\n\t\t\t\"Win / draw / loss POV\": \"Perspectiva victoria / tablas / derrota\",\n\t\t\t\"PV clicks\": \"Clics en VP\",\n\t\t\t\t\"Do nothing\": \"No hacer nada\",\n\t\t\t\t\"Go there\": \"Ir allí\",\n\t\t\t\t\"Add to tree\": \"Añadir al árbol\",\n\t\t\t\"Write infobox to clipboard\": \"Copiar info al portapapeles\",\n\t\t\t\"Forget all analysis\": \"Olvidar todo el análisis\",\n\t\t\"Display\": \"Visualización\",\n\t\t\t\"Flip board\": \"Girar tablero\",\n\t\t\t\"Arrows\": \"Flechas\",\n\t\t\t\"Piece-click spotlight\": \"Resaltar movimientos legales\",\n\t\t\t\"Always show actual move (if known)\": \"Mostrar siempre jugada real\",\n\t\t\t\"...with unique colour\": \"...con color único\",\n\t\t\t\"...with outline\": \"...con contorno\",\n\t\t\t\"Arrowhead type\": \"Tipo de flecha\",\n\t\t\t\t\"Winrate\": \"Porcentaje victoria\",\n\t\t\t\t\"Node %\": \"Nodo %\",\n\t\t\t\t\"Policy\": \"Política\",\n\t\t\t\t\"MultiPV rank\": \"Rango MultiPV\",\n\t\t\t\t\"Moves Left Head\": \"Jugadas restantes\",\n\t\t\t\"Arrow filter (Lc0)\": \"Filtro flechas (Lc0)\",\n\t\t\t\t\"All moves\": \"Todas las jugadas\",\n\t\t\t\t\"Top move\": \"Mejor jugada\",\n\t\t\t\"Arrow filter (others)\": \"Filtro flechas (otros)\",\n\t\t\t\t\"Diff < 15%\": \"Dif < 15%\",\n\t\t\t\t\"Diff < 10%\": \"Dif < 10%\",\n\t\t\t\t\"Diff < 5%\": \"Dif < 5%\",\n\t\t\t\"Infobox stats\": \"Estadísticas\",\n\t\t\t\t\"N - nodes (%)\": \"N - nodos (%)\",\n\t\t\t\t\"N - nodes (absolute)\": \"N - nodos (absoluto)\",\n\t\t\t\t\"P - policy\": \"P - política\",\n\t\t\t\t\"V - static evaluation\": \"V - evaluación estática\",\n\t\t\t\t\"Q - evaluation\": \"Q - evaluación\",\n\t\t\t\t\"U - uncertainty\": \"U - incertidumbre\",\n\t\t\t\t\"S - search priority\": \"S - prioridad búsqueda\",\n\t\t\t\t\"M - moves left\": \"M - jugadas restantes\",\n\t\t\t\t\"WDL - win / draw / loss\": \"WDL - victoria / tablas / derrota\",\n\t\t\t\t\"Linebreak before stats\": \"Salto línea antes de stats\",\n\t\t\t\"PV move numbers\": \"Números de jugada VP\",\n\t\t\t\"Online API\": \"API en línea\",\n\t\t\t\t\"None\": \"Ninguno\",\n\t\t\t\t\"ChessDB.cn evals\": \"Evaluaciones ChessDB.cn\",\n\t\t\t\t\"Lichess results (masters)\": \"Resultados Lichess (maestros)\",\n\t\t\t\t\"Lichess results (plebs)\": \"Resultados Lichess (usuarios)\",\n\t\t\t\t\"Set Lichess API token\": \"Establecer token API de Lichess\",\n\t\t\t\"Allow API after move 25\": \"Permitir API después jugada 25\",\n\t\t\t\"Draw PV on mouseover\": \"Mostrar VP al pasar ratón\",\n\t\t\t\"Draw PV method\": \"Método dibujo VP\",\n\t\t\t\t\"Animate\": \"Animar\",\n\t\t\t\t\"Single move\": \"Jugada única\",\n\t\t\t\t\"Final position\": \"Posición final\",\n\t\t\t\"Pieces\": \"Piezas\",\n\t\t\t\t\"Choose pieces folder...\": \"Elegir carpeta piezas...\",\n\t\t\t\t\"Default\": \"Predeterminado\",\n\t\t\t\t\"About custom pieces\": \"Sobre piezas personalizadas\",\n\t\t\t\"Background\": \"Fondo\",\n\t\t\t\t\"Choose background image...\": \"Elegir imagen de fondo...\",\n\t\t\t\"Book frequency arrows\": \"Flechas frecuencia libro\",\n\t\t\t\"Lichess frequency arrows\": \"Flechas frecuencia Lichess\",\n\t\t\"Sizes\": \"Tamaños\",\n\t\t\t\"Infobox font\": \"Fuente info\",\n\t\t\t\"Move history font\": \"Fuente historial\",\n\t\t\t\"Board\": \"Tablero\",\n\t\t\t\t\"Giant\": \"Gigante\",\n\t\t\t\t\"Large\": \"Grande\",\n\t\t\t\t\"Medium\": \"Mediano\",\n\t\t\t\t\"Small\": \"Pequeño\",\n\t\t\t\"Graph\": \"Gráfico\",\n\t\t\t\"Graph lines\": \"Líneas gráfico\",\n\t\t\t\"I want other size options!\": \"¡Quiero otras opciones de tamaño!\",\n\t\t\"Engine\": \"Motor\",\n\t\t\t\"Choose engine...\": \"Elegir motor...\",\n\t\t\t\"Choose known engine...\": \"Elegir motor conocido...\",\n\t\t\t\"Weights\": \"Pesos\",\n\t\t\t\t\"Lc0 WeightsFile...\": \"Archivo pesos Lc0...\",\n\t\t\t\t\"Stockfish EvalFile...\": \"Archivo eval Stockfish...\",\n\t\t\t\t\"Set to <auto>\": \"Establecer a <auto>\",\n\t\t\t\"Backend\": \"Backend\",\n\t\t\t\"Choose Syzygy path...\": \"Elegir ruta Syzygy...\",\n\t\t\t\"Unset\": \"Desactivar\",\n\t\t\t\"Limit - normal\": \"Límite - normal\",\n\t\t\t\t\"Unlimited\": \"Ilimitado\",\n\t\t\t\t\"Up slightly\": \"Aumentar poco\",\n\t\t\t\t\"Down slightly\": \"Reducir poco\",\n\t\t\t\"Limit - auto-eval / play\": \"Límite - auto-eval / juego\",\n\t\t\t\"Limit by time instead of nodes\": \"Limitar por tiempo en vez de nodos\",\n\t\t\t\"Threads\": \"Hilos\",\n\t\t\t\t\"Warning about threads\": \"Advertencia sobre hilos\",\n\t\t\t\"Hash\": \"Hash\",\n\t\t\t\t\"I want other hash options!\": \"¡Quiero otras opciones de hash!\",\n\t\t\t\"MultiPV\": \"MultiPV\",\n\t\t\t\"Contempt Mode\": \"Modo desprecio\",\n\t\t\t\t\"White analysis\": \"Análisis blancas\",\n\t\t\t\t\"Black analysis\": \"Análisis negras\",\n\t\t\t\"Contempt\": \"Desprecio\",\n\t\t\t\"WDL Calibration Elo\": \"Calibración Elo WDL\",\n\t\t\t\t\"Use default WDL\": \"Usar WDL predeterminado\",\n\t\t\t\"WDL Eval Objectivity\": \"Objetividad eval WDL\",\n\t\t\t\t\"Yes\": \"Sí\",\n\t\t\t\t\"No\": \"No\",\n\t\t\t\"Score Type\": \"Tipo puntuación\",\n\t\t\t\"Custom scripts\": \"Scripts personalizados\",\n\t\t\t\t\"How to add scripts\": \"Cómo añadir scripts\",\n\t\t\t\t\"Show scripts folder\": \"Mostrar carpeta scripts\",\n\t\t\t\"Restart engine\": \"Reiniciar motor\",\n\t\t\t\"Soft engine reset\": \"Reinicio suave motor\",\n\t\t\"Play\": \"Jugar\",\n\t\t\t\"Play this colour\": \"Jugar este color\",\n\t\t\t\"Start self-play\": \"Iniciar auto-juego\",\n\t\t\t\"Use Polyglot book...\": \"Usar libro Polyglot...\",\n\t\t\t\"Use PGN book...\": \"Usar libro PGN...\",\n\t\t\t\"Unload book / abort load\": \"Descargar libro / abortar carga\",\n\t\t\t\"Book depth limit\": \"Límite profundidad libro\",\n\t\t\t\"Temperature\": \"Temperatura\",\n\t\t\t\"Temp Decay Moves\": \"Decaimiento temp jugadas\",\n\t\t\t\t\"Infinite\": \"Infinito\",\n\t\t\t\"About play modes\": \"Sobre modos juego\",\n\t\t\"Dev\": \"Desarrollo\",\n\t\t\t\"Toggle Developer Tools\": \"Alternar herramientas desarrollo\",\n\t\t\t\"Toggle Debug CSS\": \"Alternar CSS depuración\",\n\t\t\t\"Permanently enable save\": \"Habilitar guardado permanente\",\n\t\t\t[show_config]: \"Mostrar config.json\",\n\t\t\t[show_engineconfig]: \"Mostrar engines.json\",\n\t\t\t[reload_engineconfig]: \"Recargar engines.json (y reiniciar motor)\",\n\t\t\t\"Random move\": \"Jugada aleatoria\",\n\t\t\t\"Disable hardware acceleration for GUI\": \"Desactivar aceleración hardware GUI\",\n\t\t\t\"Spin rate\": \"Velocidad actualización\",\n\t\t\t\t\"Frenetic\": \"Frenético\",\n\t\t\t\t\"Fast\": \"Rápido\",\n\t\t\t\t\"Normal\": \"Normal\",\n\t\t\t\t\"Relaxed\": \"Relajado\",\n\t\t\t\t\"Lazy\": \"Perezoso\",\n\t\t\t\"Show engine state\": \"Mostrar estado motor\",\n\t\t\t\"List sent options\": \"Listar opciones enviadas\",\n\t\t\t\"Show error log\": \"Mostrar registro errores\",\n\t\t\t\"Hacks and kludges\": \"Trucos y parches\",\n\t\t\t\t\"Allow arbitrary scripts\": \"Permitir scripts arbitrarios\",\n\t\t\t\t\"Accept any file size\": \"Aceptar cualquier tamaño archivo\",\n\t\t\t\t\"Allow stopped analysis\": \"Permitir análisis detenido\",\n\t\t\t\t\"Never hide focus buttons\": \"Nunca ocultar botones enfoque\",\n\t\t\t\t\"Never grayout move info\": \"Nunca atenuar info jugadas\",\n\t\t\t\t\"Use lowerbound / upperbound info\": \"Usar info límite inferior/superior\",\n\t\t\t\t\"Suppress ucinewgame\": \"Suprimir ucinewgame\",\n\t\t\t\"Log RAM state to console\": \"Registrar estado RAM en consola\",\n\t\t\t\"Fire GC\": \"Ejecutar GC\",\n\t\t\t\"Logging\": \"Registro\",\n\t\t\t\t\"Use logfile...\": \"Usar archivo registro...\",\n\t\t\t\t\"Disable logging\": \"Desactivar registro\",\n\t\t\t\t\"Clear log when opening\": \"Limpiar registro al abrir\",\n\t\t\t\t\"Use unique logfile each time\": \"Usar archivo registro único\",\n\t\t\t\t\"Log illegal moves\": \"Registrar jugadas ilegales\",\n\t\t\t\t\"Log positions\": \"Registrar posiciones\",\n\t\t\t\t\"Log info lines\": \"Registrar líneas info\",\n\t\t\t\t\"...including useless lines\": \"...incluyendo líneas inútiles\",\n\t\t\"Language\": \"Idioma\",\n\n\t\t\"RESTART_REQUIRED\": \"La interfaz debe reiniciarse ahora.\"\n\t},\n\n\t// PORTUGUESE .................................................................................\n\n\t\"Português\": {\n\t\t\"File\": \"Ficheiro\",\n\t\t\t\"About\": \"Sobre\",\n\t\t\t\"New game\": \"Novo jogo\",\n\t\t\t\"New 960 game\": \"Novo jogo 960\",\n\t\t\t\"Open PGN...\": \"Abrir PGN...\",\n\t\t\t\"Load FEN / PGN from clipboard\": \"Carregar FEN / PGN da área de transferência\",\n\t\t\t\"Save this game...\": \"Guardar este jogo...\",\n\t\t\t\"Write PGN to clipboard\": \"Copiar PGN para área de transferência\",\n\t\t\t\"PGN saved statistics\": \"Estatísticas guardadas no PGN\",\n\t\t\t\t\"EV\": \"EV\",\n\t\t\t\t\"Centipawns\": \"Centipawns\",\n\t\t\t\t\"N (%)\": \"N (%)\",\n\t\t\t\t\"N (absolute)\": \"N (absoluto)\",\n\t\t\t\t\"...out of total\": \"...do total\",\n\t\t\t\t\"Depth (A/B only)\": \"Profundidade (apenas A/B)\",\n\t\t\t\"Cut\": \"Cortar\",\n\t\t\t\"Copy\": \"Copiar\",\n\t\t\t\"Paste\": \"Colar\",\n\t\t\t\"Quit\": \"Sair\",\n\t\t\"Tree\": \"Árvore\",\n\t\t\t\"Play engine choice\": \"Jogar escolha do motor\",\n\t\t\t\t\"1st\": \"1º\",\n\t\t\t\t\"2nd\": \"2º\",\n\t\t\t\t\"3rd\": \"3º\",\n\t\t\t\t\"4th\": \"4º\",\n\t\t\t\"Root\": \"Raiz\",\n\t\t\t\"End\": \"Fim\",\n\t\t\t\"Backward\": \"Recuar\",\n\t\t\t\"Forward\": \"Avançar\",\n\t\t\t\"Previous sibling\": \"Variante anterior\",\n\t\t\t\"Next sibling\": \"Variante seguinte\",\n\t\t\t\"Return to main line\": \"Voltar à linha principal\",\n\t\t\t\"Promote line to main line\": \"Promover linha a principal\",\n\t\t\t\"Promote line by 1 level\": \"Promover linha 1 nível\",\n\t\t\t\"Delete node\": \"Eliminar nó\",\n\t\t\t\"Delete children\": \"Eliminar descendentes\",\n\t\t\t\"Delete siblings\": \"Eliminar variantes\",\n\t\t\t\"Delete ALL other lines\": \"Eliminar TODAS as outras linhas\",\n\t\t\t\"Show PGN games list\": \"Mostrar lista de jogos PGN\",\n\t\t\t\"Escape\": \"Sair\",\n\t\t\"Analysis\": \"Análise\",\n\t\t\t\"Go\": \"Iniciar\",\n\t\t\t\"Go and lock engine\": \"Iniciar e fixar motor\",\n\t\t\t\"Return to locked position\": \"Voltar à posição fixa\",\n\t\t\t\"Halt\": \"Parar\",\n\t\t\t\"Auto-evaluate line\": \"Auto-avaliar linha\",\n\t\t\t\"Auto-evaluate line, backwards\": \"Auto-avaliar linha, inversamente\",\n\t\t\t\"Show focus (searchmoves) buttons\": \"Mostrar botões de foco (searchmoves)\",\n\t\t\t\"Clear focus\": \"Limpar foco\",\n\t\t\t\"Invert focus\": \"Inverter foco\",\n\t\t\t\"Winrate POV\": \"POV taxa de vitória\",\n\t\t\t\t\"Current\": \"Atual\",\n\t\t\t\t\"White\": \"Brancas\",\n\t\t\t\t\"Black\": \"Pretas\",\n\t\t\t\"Centipawn POV\": \"POV centipawns\",\n\t\t\t\"Win / draw / loss POV\": \"POV vitória / empate / derrota\",\n\t\t\t\"PV clicks\": \"Cliques PV\",\n\t\t\t\t\"Do nothing\": \"Não fazer nada\",\n\t\t\t\t\"Go there\": \"Ir para lá\",\n\t\t\t\t\"Add to tree\": \"Adicionar à árvore\",\n\t\t\t\"Write infobox to clipboard\": \"Copiar caixa de informação\",\n\t\t\t\"Forget all analysis\": \"Esquecer toda a análise\",\n\t\t\"Display\": \"Exibir\",\n\t\t\t\"Flip board\": \"Rodar tabuleiro\",\n\t\t\t\"Arrows\": \"Setas\",\n\t\t\t\"Piece-click spotlight\": \"Destaque ao clicar peça\",\n\t\t\t\"Always show actual move (if known)\": \"Mostrar sempre lance atual (se conhecido)\",\n\t\t\t\"...with unique colour\": \"...com cor única\",\n\t\t\t\"...with outline\": \"...com contorno\",\n\t\t\t\"Arrowhead type\": \"Tipo de seta\",\n\t\t\t\t\"Winrate\": \"Taxa de vitória\",\n\t\t\t\t\"Node %\": \"% nós\",\n\t\t\t\t\"Policy\": \"Política\",\n\t\t\t\t\"MultiPV rank\": \"Rank MultiPV\",\n\t\t\t\t\"Moves Left Head\": \"Lances restantes\",\n\t\t\t\"Arrow filter (Lc0)\": \"Filtro de setas (Lc0)\",\n\t\t\t\t\"All moves\": \"Todos os lances\",\n\t\t\t\t\"Top move\": \"Melhor lance\",\n\t\t\t\"Arrow filter (others)\": \"Filtro de setas (outros)\",\n\t\t\t\t\"Diff < 15%\": \"Dif < 15%\",\n\t\t\t\t\"Diff < 10%\": \"Dif < 10%\",\n\t\t\t\t\"Diff < 5%\": \"Dif < 5%\",\n\t\t\t\"Infobox stats\": \"Estatísticas\",\n\t\t\t\t\"N - nodes (%)\": \"N - nós (%)\",\n\t\t\t\t\"N - nodes (absolute)\": \"N - nós (absoluto)\",\n\t\t\t\t\"P - policy\": \"P - política\",\n\t\t\t\t\"V - static evaluation\": \"V - avaliação estática\",\n\t\t\t\t\"Q - evaluation\": \"Q - avaliação\",\n\t\t\t\t\"U - uncertainty\": \"U - incerteza\",\n\t\t\t\t\"S - search priority\": \"S - prioridade de busca\",\n\t\t\t\t\"M - moves left\": \"M - lances restantes\",\n\t\t\t\t\"WDL - win / draw / loss\": \"WDL - vitória / empate / derrota\",\n\t\t\t\t\"Linebreak before stats\": \"Quebra de linha antes das estatísticas\",\n\t\t\t\"PV move numbers\": \"Números dos lances PV\",\n\t\t\t\"Online API\": \"API online\",\n\t\t\t\t\"None\": \"Nenhum\",\n\t\t\t\t\"ChessDB.cn evals\": \"Avaliações ChessDB.cn\",\n\t\t\t\t\"Lichess results (masters)\": \"Resultados Lichess (mestres)\",\n\t\t\t\t\"Lichess results (plebs)\": \"Resultados Lichess (amadores)\",\n\t\t\t\t\"Set Lichess API token\": \"Definir token API do Lichess\",\n\t\t\t\"Allow API after move 25\": \"Permitir API após lance 25\",\n\t\t\t\"Draw PV on mouseover\": \"Mostrar PV ao passar rato\",\n\t\t\t\"Draw PV method\": \"Método de mostrar PV\",\n\t\t\t\t\"Animate\": \"Animar\",\n\t\t\t\t\"Single move\": \"Lance único\",\n\t\t\t\t\"Final position\": \"Posição final\",\n\t\t\t\"Pieces\": \"Peças\",\n\t\t\t\t\"Choose pieces folder...\": \"Escolher pasta de peças...\",\n\t\t\t\t\"Default\": \"Padrão\",\n\t\t\t\t\"About custom pieces\": \"Sobre peças personalizadas\",\n\t\t\t\"Background\": \"Fundo\",\n\t\t\t\t\"Choose background image...\": \"Escolher imagem de fundo...\",\n\t\t\t\"Book frequency arrows\": \"Setas de frequência do livro\",\n\t\t\t\"Lichess frequency arrows\": \"Setas de frequência do Lichess\",\n\t\t\"Sizes\": \"Tamanhos\",\n\t\t\t\"Infobox font\": \"Fonte da caixa de informação\",\n\t\t\t\"Move history font\": \"Fonte do histórico de lances\",\n\t\t\t\"Board\": \"Tabuleiro\",\n\t\t\t\t\"Giant\": \"Gigante\",\n\t\t\t\t\"Large\": \"Grande\",\n\t\t\t\t\"Medium\": \"Médio\",\n\t\t\t\t\"Small\": \"Pequeno\",\n\t\t\t\"Graph\": \"Gráfico\",\n\t\t\t\"Graph lines\": \"Linhas do gráfico\",\n\t\t\t\"I want other size options!\": \"Quero outras opções de tamanho!\",\n\t\t\"Engine\": \"Motor\",\n\t\t\t\"Choose engine...\": \"Escolher motor...\",\n\t\t\t\"Choose known engine...\": \"Escolher motor conhecido...\",\n\t\t\t\"Weights\": \"Pesos\",\n\t\t\t\t\"Lc0 WeightsFile...\": \"Ficheiro de pesos Lc0...\",\n\t\t\t\t\"Stockfish EvalFile...\": \"Ficheiro de avaliação Stockfish...\",\n\t\t\t\t\"Set to <auto>\": \"Definir como <auto>\",\n\t\t\t\"Backend\": \"Backend\",\n\t\t\t\"Choose Syzygy path...\": \"Escolher caminho Syzygy...\",\n\t\t\t\"Unset\": \"Remover\",\n\t\t\t\"Limit - normal\": \"Limite - normal\",\n\t\t\t\t\"Unlimited\": \"Ilimitado\",\n\t\t\t\t\"Up slightly\": \"Aumentar ligeiramente\",\n\t\t\t\t\"Down slightly\": \"Diminuir ligeiramente\",\n\t\t\t\"Limit - auto-eval / play\": \"Limite - auto-avaliação / jogo\",\n\t\t\t\"Limit by time instead of nodes\": \"Limitar por tempo em vez de nós\",\n\t\t\t\"Threads\": \"Threads\",\n\t\t\t\t\"Warning about threads\": \"Aviso sobre threads\",\n\t\t\t\"Hash\": \"Hash\",\n\t\t\t\t\"I want other hash options!\": \"Quero outras opções de hash!\",\n\t\t\t\"MultiPV\": \"MultiPV\",\n\t\t\t\"Contempt Mode\": \"Modo de desprezo\",\n\t\t\t\t\"White analysis\": \"Análise das brancas\",\n\t\t\t\t\"Black analysis\": \"Análise das pretas\",\n\t\t\t\"Contempt\": \"Desprezo\",\n\t\t\t\"WDL Calibration Elo\": \"Calibração WDL Elo\",\n\t\t\t\t\"Use default WDL\": \"Usar WDL padrão\",\n\t\t\t\"WDL Eval Objectivity\": \"Objetividade da avaliação WDL\",\n\t\t\t\t\"Yes\": \"Sim\",\n\t\t\t\t\"No\": \"Não\",\n\t\t\t\"Score Type\": \"Tipo de pontuação\",\n\t\t\t\"Custom scripts\": \"Scripts personalizados\",\n\t\t\t\t\"How to add scripts\": \"Como adicionar scripts\",\n\t\t\t\t\"Show scripts folder\": \"Mostrar pasta de scripts\",\n\t\t\t\"Restart engine\": \"Reiniciar motor\",\n\t\t\t\"Soft engine reset\": \"Reinicialização suave do motor\",\n\t\t\"Play\": \"Jogar\",\n\t\t\t\"Play this colour\": \"Jogar esta cor\",\n\t\t\t\"Start self-play\": \"Iniciar auto-jogo\",\n\t\t\t\"Use Polyglot book...\": \"Usar livro Polyglot...\",\n\t\t\t\"Use PGN book...\": \"Usar livro PGN...\",\n\t\t\t\"Unload book / abort load\": \"Descarregar livro / abortar carregamento\",\n\t\t\t\"Book depth limit\": \"Limite de profundidade do livro\",\n\t\t\t\"Temperature\": \"Temperatura\",\n\t\t\t\"Temp Decay Moves\": \"Decaimento de temperatura\",\n\t\t\t\t\"Infinite\": \"Infinito\",\n\t\t\t\"About play modes\": \"Sobre modos de jogo\",\n\t\t\"Dev\": \"Dev\",\n\t\t\t\"Toggle Developer Tools\": \"Alternar ferramentas de desenvolvimento\",\n\t\t\t\"Toggle Debug CSS\": \"Alternar CSS de depuração\",\n\t\t\t\"Permanently enable save\": \"Ativar guardar permanentemente\",\n\t\t\t[show_config]: \"Mostrar config.json\",\n\t\t\t[show_engineconfig]: \"Mostrar engines.json\",\n\t\t\t[reload_engineconfig]: \"Recarregar engines.json (e reiniciar motor)\",\n\t\t\t\"Random move\": \"Lance aleatório\",\n\t\t\t\"Disable hardware acceleration for GUI\": \"Desativar aceleração de hardware para GUI\",\n\t\t\t\"Spin rate\": \"Taxa de atualização\",\n\t\t\t\t\"Frenetic\": \"Frenético\",\n\t\t\t\t\"Fast\": \"Rápido\",\n\t\t\t\t\"Normal\": \"Normal\",\n\t\t\t\t\"Relaxed\": \"Relaxado\",\n\t\t\t\t\"Lazy\": \"Preguiçoso\",\n\t\t\t\"Show engine state\": \"Mostrar estado do motor\",\n\t\t\t\"List sent options\": \"Listar opções enviadas\",\n\t\t\t\"Show error log\": \"Mostrar registo de erros\",\n\t\t\t\"Hacks and kludges\": \"Hacks e gambiarras\",\n\t\t\t\t\"Allow arbitrary scripts\": \"Permitir scripts arbitrários\",\n\t\t\t\t\"Accept any file size\": \"Aceitar qualquer tamanho de ficheiro\",\n\t\t\t\t\"Allow stopped analysis\": \"Permitir análise parada\",\n\t\t\t\t\"Never hide focus buttons\": \"Nunca ocultar botões de foco\",\n\t\t\t\t\"Never grayout move info\": \"Nunca esmaecer info de lance\",\n\t\t\t\t\"Use lowerbound / upperbound info\": \"Usar info de limite inferior/superior\",\n\t\t\t\t\"Suppress ucinewgame\": \"Suprimir ucinewgame\",\n\t\t\t\"Log RAM state to console\": \"Registar estado RAM na consola\",\n\t\t\t\"Fire GC\": \"Executar GC\",\n\t\t\t\"Logging\": \"Registo\",\n\t\t\t\t\"Use logfile...\": \"Usar ficheiro de registo...\",\n\t\t\t\t\"Disable logging\": \"Desativar registo\",\n\t\t\t\t\"Clear log when opening\": \"Limpar registo ao abrir\",\n\t\t\t\t\"Use unique logfile each time\": \"Usar ficheiro de registo único cada vez\",\n\t\t\t\t\"Log illegal moves\": \"Registar lances ilegais\",\n\t\t\t\t\"Log positions\": \"Registar posições\",\n\t\t\t\t\"Log info lines\": \"Registar linhas de info\",\n\t\t\t\t\"...including useless lines\": \"...incluindo linhas inúteis\",\n\t\t\"Language\": \"Idioma\",\n\n\t\t\"RESTART_REQUIRED\": \"O GUI deve ser reiniciado agora.\"\n\t},\n\n\t// GERMAN .....................................................................................\n\n\t\"Deutsch\": {\n\t\t\"File\": \"Datei\",\n\t\t\t\"About\": \"Über\",\n\t\t\t\"New game\": \"Neue Partie\",\n\t\t\t\"New 960 game\": \"Neue 960-Partie\",\n\t\t\t\"Open PGN...\": \"PGN öffnen...\",\n\t\t\t\"Load FEN / PGN from clipboard\": \"FEN / PGN aus Zwischenablage laden\",\n\t\t\t\"Save this game...\": \"Diese Partie speichern...\",\n\t\t\t\"Write PGN to clipboard\": \"PGN in Zwischenablage kopieren\",\n\t\t\t\"PGN saved statistics\": \"PGN-Statistiken speichern\",\n\t\t\t\t\"EV\": \"EV\",\n\t\t\t\t\"Centipawns\": \"Bauerneinheiten\",\n\t\t\t\t\"N (%)\": \"N (%)\",\n\t\t\t\t\"N (absolute)\": \"N (absolut)\",\n\t\t\t\t\"...out of total\": \"...von Gesamt\",\n\t\t\t\t\"Depth (A/B only)\": \"Tiefe (nur A/B)\",\n\t\t\t\"Cut\": \"Ausschneiden\",\n\t\t\t\"Copy\": \"Kopieren\",\n\t\t\t\"Paste\": \"Einfügen\",\n\t\t\t\"Quit\": \"Beenden\",\n\t\t\"Tree\": \"Baum\",\n\t\t\t\"Play engine choice\": \"Engine-Zug spielen\",\n\t\t\t\t\"1st\": \"1.\",\n\t\t\t\t\"2nd\": \"2.\",\n\t\t\t\t\"3rd\": \"3.\",\n\t\t\t\t\"4th\": \"4.\",\n\t\t\t\"Root\": \"Wurzel\",\n\t\t\t\"End\": \"Ende\",\n\t\t\t\"Backward\": \"Zurück\",\n\t\t\t\"Forward\": \"Vorwärts\",\n\t\t\t\"Previous sibling\": \"Vorheriger Zweig\",\n\t\t\t\"Next sibling\": \"Nächster Zweig\",\n\t\t\t\"Return to main line\": \"Zur Hauptvariante\",\n\t\t\t\"Promote line to main line\": \"Zur Hauptvariante befördern\",\n\t\t\t\"Promote line by 1 level\": \"Um 1 Ebene befördern\",\n\t\t\t\"Delete node\": \"Knoten löschen\",\n\t\t\t\"Delete children\": \"Nachfolger löschen\",\n\t\t\t\"Delete siblings\": \"Geschwister löschen\",\n\t\t\t\"Delete ALL other lines\": \"ALLE anderen Varianten löschen\",\n\t\t\t\"Show PGN games list\": \"PGN-Partieliste anzeigen\",\n\t\t\t\"Escape\": \"Escape\",\n\t\t\"Analysis\": \"Analyse\",\n\t\t\t\"Go\": \"Starten\",\n\t\t\t\"Go and lock engine\": \"Starten und Analyseposition fixieren\",\n\t\t\t\"Return to locked position\": \"Zur Analyseposition zurückkehren\",\n\t\t\t\"Halt\": \"Anhalten\",\n\t\t\t\"Auto-evaluate line\": \"Variante automatisch analysieren\",\n\t\t\t\"Auto-evaluate line, backwards\": \"Variante rückwärts analysieren\",\n\t\t\t\"Show focus (searchmoves) buttons\": \"Fokus-Buttons anzeigen\",\n\t\t\t\"Clear focus\": \"Fokus löschen\",\n\t\t\t\"Invert focus\": \"Fokus umkehren\",\n\t\t\t\"Winrate POV\": \"Gewinnrate POV\",\n\t\t\t\t\"Current\": \"Aktuell\",\n\t\t\t\t\"White\": \"Weiß\",\n\t\t\t\t\"Black\": \"Schwarz\",\n\t\t\t\"Centipawn POV\": \"Bauerneinheiten POV\",\n\t\t\t\"Win / draw / loss POV\": \"Sieg / Remis / Niederlage POV\",\n\t\t\t\"PV clicks\": \"PV-Klicks\",\n\t\t\t\t\"Do nothing\": \"Nichts tun\",\n\t\t\t\t\"Go there\": \"Hinspringen\",\n\t\t\t\t\"Add to tree\": \"Zum Baum hinzufügen\",\n\t\t\t\"Write infobox to clipboard\": \"Infobox in Zwischenablage\",\n\t\t\t\"Forget all analysis\": \"Analyse zurücksetzen\",\n\t\t\"Display\": \"Anzeige\",\n\t\t\t\"Flip board\": \"Brett drehen\",\n\t\t\t\"Arrows\": \"Pfeile\",\n\t\t\t\"Piece-click spotlight\": \"Zugmöglichkeiten markieren\",\n\t\t\t\"Always show actual move (if known)\": \"Ausgeführten Zug anzeigen\",\n\t\t\t\"...with unique colour\": \"...in eigener Farbe\",\n\t\t\t\"...with outline\": \"...mit Umriss\",\n\t\t\t\"Arrowhead type\": \"Pfeilspitzen-Typ\",\n\t\t\t\t\"Winrate\": \"Gewinnrate\",\n\t\t\t\t\"Node %\": \"Knoten %\",\n\t\t\t\t\"Policy\": \"Policy\",\n\t\t\t\t\"MultiPV rank\": \"MultiPV-Rang\",\n\t\t\t\t\"Moves Left Head\": \"Verbleibende Züge\",\n\t\t\t\"Arrow filter (Lc0)\": \"Pfeilfilter (Lc0)\",\n\t\t\t\t\"All moves\": \"Alle Züge\",\n\t\t\t\t\"Top move\": \"Bester Zug\",\n\t\t\t\"Arrow filter (others)\": \"Pfeilfilter (andere)\",\n\t\t\t\t\"Diff < 15%\": \"Diff < 15%\",\n\t\t\t\t\"Diff < 10%\": \"Diff < 10%\",\n\t\t\t\t\"Diff < 5%\": \"Diff < 5%\",\n\t\t\t\"Infobox stats\": \"Infobox-Statistiken\",\n\t\t\t\t\"N - nodes (%)\": \"N - Knoten (%)\",\n\t\t\t\t\"N - nodes (absolute)\": \"N - Knoten (absolut)\",\n\t\t\t\t\"P - policy\": \"P - Policy\",\n\t\t\t\t\"V - static evaluation\": \"V - Statische Bewertung\",\n\t\t\t\t\"Q - evaluation\": \"Q - Bewertung\",\n\t\t\t\t\"U - uncertainty\": \"U - Unsicherheit\",\n\t\t\t\t\"S - search priority\": \"S - Suchpriorität\",\n\t\t\t\t\"M - moves left\": \"M - Verbleibende Züge\",\n\t\t\t\t\"WDL - win / draw / loss\": \"WDL - Sieg / Remis / Niederlage\",\n\t\t\t\t\"Linebreak before stats\": \"Zeilenumbruch vor Statistiken\",\n\t\t\t\"PV move numbers\": \"PV-Zugnummern\",\n\t\t\t\"Online API\": \"Online-API\",\n\t\t\t\t\"None\": \"Keine\",\n\t\t\t\t\"ChessDB.cn evals\": \"ChessDB.cn Bewertungen\",\n\t\t\t\t\"Lichess results (masters)\": \"Lichess Ergebnisse (Meister)\",\n\t\t\t\t\"Lichess results (plebs)\": \"Lichess Ergebnisse (Amateure)\",\n\t\t\t\t\"Set Lichess API token\": \"Lichess-API-Token festlegen\",\n\t\t\t\"Allow API after move 25\": \"API nach Zug 25 erlauben\",\n\t\t\t\"Draw PV on mouseover\": \"PV bei Mausberührung\",\n\t\t\t\"Draw PV method\": \"PV-Anzeigemethode\",\n\t\t\t\t\"Animate\": \"Animieren\",\n\t\t\t\t\"Single move\": \"Einzelzug\",\n\t\t\t\t\"Final position\": \"Endposition\",\n\t\t\t\"Pieces\": \"Figuren\",\n\t\t\t\t\"Choose pieces folder...\": \"Figurenordner wählen...\",\n\t\t\t\t\"Default\": \"Standard\",\n\t\t\t\t\"About custom pieces\": \"Über eigene Figuren\",\n\t\t\t\"Background\": \"Hintergrund\",\n\t\t\t\t\"Choose background image...\": \"Hintergrundbild wählen...\",\n\t\t\t\"Book frequency arrows\": \"Eröffnungsbuch-Häufigkeitspfeile\",\n\t\t\t\"Lichess frequency arrows\": \"Lichess-Häufigkeitspfeile\",\n\t\t\"Sizes\": \"Größen\",\n\t\t\t\"Infobox font\": \"Infobox-Schrift\",\n\t\t\t\"Move history font\": \"Zugverlauf-Schrift\",\n\t\t\t\"Board\": \"Brett\",\n\t\t\t\t\"Giant\": \"Riesig\",\n\t\t\t\t\"Large\": \"Groß\",\n\t\t\t\t\"Medium\": \"Mittel\",\n\t\t\t\t\"Small\": \"Klein\",\n\t\t\t\"Graph\": \"Graph\",\n\t\t\t\"Graph lines\": \"Graphlinien\",\n\t\t\t\"I want other size options!\": \"Ich möchte andere Größenoptionen!\",\n\t\t\"Engine\": \"Engine\",\n\t\t\t\"Choose engine...\": \"Engine wählen...\",\n\t\t\t\"Choose known engine...\": \"Bekannte Engine wählen...\",\n\t\t\t\"Weights\": \"Gewichte\",\n\t\t\t\t\"Lc0 WeightsFile...\": \"Lc0 Gewichtsdatei...\",\n\t\t\t\t\"Stockfish EvalFile...\": \"Stockfish Evaluierungsdatei...\",\n\t\t\t\t\"Set to <auto>\": \"Auf <auto> setzen\",\n\t\t\t\"Backend\": \"Backend\",\n\t\t\t\"Choose Syzygy path...\": \"Syzygy-Pfad wählen...\",\n\t\t\t\"Unset\": \"Zurücksetzen\",\n\t\t\t\"Limit - normal\": \"Limit - normal\",\n\t\t\t\t\"Unlimited\": \"Unbegrenzt\",\n\t\t\t\t\"Up slightly\": \"Leicht erhöhen\",\n\t\t\t\t\"Down slightly\": \"Leicht verringern\",\n\t\t\t\"Limit - auto-eval / play\": \"Limit - Auto-Eval / Spiel\",\n\t\t\t\"Limit by time instead of nodes\": \"Zeitlimit statt Knotenlimit\",\n\t\t\t\"Threads\": \"Threads\",\n\t\t\t\t\"Warning about threads\": \"Warnung zu Threads\",\n\t\t\t\"Hash\": \"Hash\",\n\t\t\t\t\"I want other hash options!\": \"Ich möchte andere Hash-Optionen!\",\n\t\t\t\"MultiPV\": \"MultiPV\",\n\t\t\t\"Contempt Mode\": \"Contempt-Modus\",\n\t\t\t\t\"White analysis\": \"Weiß-Analyse\",\n\t\t\t\t\"Black analysis\": \"Schwarz-Analyse\",\n\t\t\t\"Contempt\": \"Contempt\",\n\t\t\t\"WDL Calibration Elo\": \"WDL-Kalibrierungs-Elo\",\n\t\t\t\t\"Use default WDL\": \"Standard-WDL verwenden\",\n\t\t\t\"WDL Eval Objectivity\": \"WDL-Eval-Objektivität\",\n\t\t\t\t\"Yes\": \"Ja\",\n\t\t\t\t\"No\": \"Nein\",\n\t\t\t\"Score Type\": \"Bewertungstyp\",\n\t\t\t\"Custom scripts\": \"Eigene Skripte\",\n\t\t\t\t\"How to add scripts\": \"Skripte hinzufügen\",\n\t\t\t\t\"Show scripts folder\": \"Skriptordner anzeigen\",\n\t\t\t\"Restart engine\": \"Engine neustarten\",\n\t\t\t\"Soft engine reset\": \"Engine soft reset\",\n\t\t\"Play\": \"Partie\",\n\t\t\t\"Play this colour\": \"Diese Farbe spielen\",\n\t\t\t\"Start self-play\": \"Selbstspiel starten\",\n\t\t\t\"Use Polyglot book...\": \"Polyglot-Buch verwenden...\",\n\t\t\t\"Use PGN book...\": \"PGN-Buch verwenden...\",\n\t\t\t\"Unload book / abort load\": \"Buch entladen / Laden abbrechen\",\n\t\t\t\"Book depth limit\": \"Buchlimit-Tiefe\",\n\t\t\t\"Temperature\": \"Temperatur\",\n\t\t\t\"Temp Decay Moves\": \"Temp-Verfall-Züge\",\n\t\t\t\t\"Infinite\": \"Unendlich\",\n\t\t\t\"About play modes\": \"Über Spielmodi\",\n\t\t\"Dev\": \"Entwicklung\",\n\t\t\t\"Toggle Developer Tools\": \"Entwicklertools ein/aus\",\n\t\t\t\"Toggle Debug CSS\": \"Debug-CSS ein/aus\",\n\t\t\t\"Permanently enable save\": \"Speichern dauerhaft aktivieren\",\n\t\t\t[show_config]: \"config.json anzeigen\",\n\t\t\t[show_engineconfig]: \"engines.json anzeigen\",\n\t\t\t[reload_engineconfig]: \"engines.json neu laden (und Engine neustarten)\",\n\t\t\t\"Random move\": \"Zufallszug\",\n\t\t\t\"Disable hardware acceleration for GUI\": \"Hardware-Beschleunigung für GUI deaktivieren\",\n\t\t\t\"Spin rate\": \"Aktualisierungsrate\",\n\t\t\t\t\"Frenetic\": \"Frenetisch\",\n\t\t\t\t\"Fast\": \"Schnell\",\n\t\t\t\t\"Normal\": \"Normal\",\n\t\t\t\t\"Relaxed\": \"Entspannt\",\n\t\t\t\t\"Lazy\": \"Träge\",\n\t\t\t\"Show engine state\": \"Engine-Status anzeigen\",\n\t\t\t\"List sent options\": \"Gesendete Optionen anzeigen\",\n\t\t\t\"Show error log\": \"Fehlerprotokoll anzeigen\",\n\t\t\t\"Hacks and kludges\": \"Hacks und Tricks\",\n\t\t\t\t\"Allow arbitrary scripts\": \"Beliebige Skripte erlauben\",\n\t\t\t\t\"Accept any file size\": \"Beliebige Dateigröße akzeptieren\",\n\t\t\t\t\"Allow stopped analysis\": \"Gestoppte Analyse erlauben\",\n\t\t\t\t\"Never hide focus buttons\": \"Fokus-Buttons nie verstecken\",\n\t\t\t\t\"Never grayout move info\": \"Zuginfo nie ausgrauen\",\n\t\t\t\t\"Use lowerbound / upperbound info\": \"Unter-/Obergrenze-Info verwenden\",\n\t\t\t\t\"Suppress ucinewgame\": \"ucinewgame unterdrücken\",\n\t\t\t\"Log RAM state to console\": \"RAM-Status protokollieren\",\n\t\t\t\"Fire GC\": \"GC ausführen\",\n\t\t\t\"Logging\": \"Protokollierung\",\n\t\t\t\t\"Use logfile...\": \"Protokolldatei verwenden...\",\n\t\t\t\t\"Disable logging\": \"Protokollierung deaktivieren\",\n\t\t\t\t\"Clear log when opening\": \"Protokoll beim Öffnen löschen\",\n\t\t\t\t\"Use unique logfile each time\": \"Eindeutige Protokolldatei je Start\",\n\t\t\t\t\"Log illegal moves\": \"Ungültige Züge protokollieren\",\n\t\t\t\t\"Log positions\": \"Positionen protokollieren\",\n\t\t\t\t\"Log info lines\": \"Info-Zeilen protokollieren\",\n\t\t\t\t\"...including useless lines\": \"...inkl. nutzloser Zeilen\",\n\t\t\"Language\": \"Sprache\",\n\n\t\t\"RESTART_REQUIRED\": \"Die GUI muss neu gestartet werden.\"\n\t},\n\n\t// NORWEGIAN ..................................................................................\n\n\t\"Norsk\": {\n\t\t\"File\": \"Fil\",\n\t\t\t\"About\": \"Om\",\n\t\t\t\"New game\": \"Nytt parti\",\n\t\t\t\"New 960 game\": \"Nytt 960-parti\",\n\t\t\t\"Open PGN...\": \"Åpne PGN...\",\n\t\t\t\"Load FEN / PGN from clipboard\": \"Last inn FEN / PGN fra utklippstavle\",\n\t\t\t\"Save this game...\": \"Lagre dette partiet...\",\n\t\t\t\"Write PGN to clipboard\": \"Kopier PGN til utklippstavle\",\n\t\t\t\"PGN saved statistics\": \"PGN lagrede statistikker\",\n\t\t\t\t\"EV\": \"EV\",\n\t\t\t\t\"Centipawns\": \"Centipawns\",\n\t\t\t\t\"N (%)\": \"N (%)\",\n\t\t\t\t\"N (absolute)\": \"N (absolutt)\",\n\t\t\t\t\"...out of total\": \"...av totalt\",\n\t\t\t\t\"Depth (A/B only)\": \"Dybde (kun A/B)\",\n\t\t\t\"Cut\": \"Klipp ut\",\n\t\t\t\"Copy\": \"Kopier\",\n\t\t\t\"Paste\": \"Lim inn\",\n\t\t\t\"Quit\": \"Avslutt\",\n\t\t\"Tree\": \"Tre\",\n\t\t\t\"Play engine choice\": \"Spill motorens valg\",\n\t\t\t\t\"1st\": \"1.\",\n\t\t\t\t\"2nd\": \"2.\",\n\t\t\t\t\"3rd\": \"3.\",\n\t\t\t\t\"4th\": \"4.\",\n\t\t\t\"Root\": \"Rot\",\n\t\t\t\"End\": \"Slutt\",\n\t\t\t\"Backward\": \"Tilbake\",\n\t\t\t\"Forward\": \"Fremover\",\n\t\t\t\"Previous sibling\": \"Forrige variant\",\n\t\t\t\"Next sibling\": \"Neste variant\",\n\t\t\t\"Return to main line\": \"Tilbake til hovedlinjen\",\n\t\t\t\"Promote line to main line\": \"Forfrem til hovedlinje\",\n\t\t\t\"Promote line by 1 level\": \"Forfrem linje ett nivå\",\n\t\t\t\"Delete node\": \"Slett node\",\n\t\t\t\"Delete children\": \"Slett barn\",\n\t\t\t\"Delete siblings\": \"Slett søsken\",\n\t\t\t\"Delete ALL other lines\": \"Slett ALLE andre linjer\",\n\t\t\t\"Show PGN games list\": \"Vis PGN-partiliste\",\n\t\t\t\"Escape\": \"Escape\",\n\t\t\"Analysis\": \"Analyse\",\n\t\t\t\"Go\": \"Start\",\n\t\t\t\"Go and lock engine\": \"Start og lås motor\",\n\t\t\t\"Return to locked position\": \"Tilbake til låst stilling\",\n\t\t\t\"Halt\": \"Stopp\",\n\t\t\t\"Auto-evaluate line\": \"Auto-evaluer linje\",\n\t\t\t\"Auto-evaluate line, backwards\": \"Auto-evaluer linje bakover\",\n\t\t\t\"Show focus (searchmoves) buttons\": \"Vis fokus-knapper (searchmoves)\",\n\t\t\t\"Clear focus\": \"Fjern fokus\",\n\t\t\t\"Invert focus\": \"Inverter fokus\",\n\t\t\t\"Winrate POV\": \"Vinstrate POV\",\n\t\t\t\t\"Current\": \"Nåværende\",\n\t\t\t\t\"White\": \"Hvit\",\n\t\t\t\t\"Black\": \"Svart\",\n\t\t\t\"Centipawn POV\": \"Centipawn POV\",\n\t\t\t\"Win / draw / loss POV\": \"Vinn / remis / tap POV\",\n\t\t\t\"PV clicks\": \"PV-klikk\",\n\t\t\t\t\"Do nothing\": \"Gjør ingenting\",\n\t\t\t\t\"Go there\": \"Gå dit\",\n\t\t\t\t\"Add to tree\": \"Legg til i tre\",\n\t\t\t\"Write infobox to clipboard\": \"Kopier infoboks til utklippstavle\",\n\t\t\t\"Forget all analysis\": \"Glem all analyse\",\n\t\t\"Display\": \"Visning\",\n\t\t\t\"Flip board\": \"Snu brett\",\n\t\t\t\"Arrows\": \"Piler\",\n\t\t\t\"Piece-click spotlight\": \"Brikke-klikk spotlight\",\n\t\t\t\"Always show actual move (if known)\": \"Alltid vis faktisk trekk (hvis kjent)\",\n\t\t\t\"...with unique colour\": \"...med unik farge\",\n\t\t\t\"...with outline\": \"...med omriss\",\n\t\t\t\"Arrowhead type\": \"Pilhode-type\",\n\t\t\t\t\"Winrate\": \"Vinstrate\",\n\t\t\t\t\"Node %\": \"Node %\",\n\t\t\t\t\"Policy\": \"Policy\",\n\t\t\t\t\"MultiPV rank\": \"MultiPV rang\",\n\t\t\t\t\"Moves Left Head\": \"Gjenværende trekk\",\n\t\t\t\"Arrow filter (Lc0)\": \"Pilfilter (Lc0)\",\n\t\t\t\t\"All moves\": \"Alle trekk\",\n\t\t\t\t\"Top move\": \"Beste trekk\",\n\t\t\t\"Arrow filter (others)\": \"Pilfilter (andre)\",\n\t\t\t\t\"Diff < 15%\": \"Diff < 15%\",\n\t\t\t\t\"Diff < 10%\": \"Diff < 10%\",\n\t\t\t\t\"Diff < 5%\": \"Diff < 5%\",\n\t\t\t\"Infobox stats\": \"Infoboks-statistikk\",\n\t\t\t\t\"N - nodes (%)\": \"N - noder (%)\",\n\t\t\t\t\"N - nodes (absolute)\": \"N - noder (absolutt)\",\n\t\t\t\t\"P - policy\": \"P - policy\",\n\t\t\t\t\"V - static evaluation\": \"V - statisk evaluering\",\n\t\t\t\t\"Q - evaluation\": \"Q - evaluering\",\n\t\t\t\t\"U - uncertainty\": \"U - usikkerhet\",\n\t\t\t\t\"S - search priority\": \"S - søkeprioritet\",\n\t\t\t\t\"M - moves left\": \"M - trekk igjen\",\n\t\t\t\t\"WDL - win / draw / loss\": \"WDL - vinn / remis / tap\",\n\t\t\t\t\"Linebreak before stats\": \"Linjeskift før statistikk\",\n\t\t\t\"PV move numbers\": \"PV trekknumre\",\n\t\t\t\"Online API\": \"Online API\",\n\t\t\t\t\"None\": \"Ingen\",\n\t\t\t\t\"ChessDB.cn evals\": \"ChessDB.cn evalueringer\",\n\t\t\t\t\"Lichess results (masters)\": \"Lichess resultater (mestre)\",\n\t\t\t\t\"Lichess results (plebs)\": \"Lichess resultater (amatører)\",\n\t\t\t\t\"Set Lichess API token\": \"Angi Lichess API-token\",\n\t\t\t\"Allow API after move 25\": \"Tillat API etter trekk 25\",\n\t\t\t\"Draw PV on mouseover\": \"Vis PV ved musepeker\",\n\t\t\t\"Draw PV method\": \"PV visningsmetode\",\n\t\t\t\t\"Animate\": \"Animer\",\n\t\t\t\t\"Single move\": \"Enkelt trekk\",\n\t\t\t\t\"Final position\": \"Sluttstilling\",\n\t\t\t\"Pieces\": \"Brikker\",\n\t\t\t\t\"Choose pieces folder...\": \"Velg brikkemappe...\",\n\t\t\t\t\"Default\": \"Standard\",\n\t\t\t\t\"About custom pieces\": \"Om egendefinerte brikker\",\n\t\t\t\"Background\": \"Bakgrunn\",\n\t\t\t\t\"Choose background image...\": \"Velg bakgrunnsbilde...\",\n\t\t\t\"Book frequency arrows\": \"Åpningsbokfrekvens-piler\",\n\t\t\t\"Lichess frequency arrows\": \"Lichess-frekvens-piler\",\n\t\t\"Sizes\": \"Størrelser\",\n\t\t\t\"Infobox font\": \"Infoboks-skrift\",\n\t\t\t\"Move history font\": \"Trekkhistorie-skrift\",\n\t\t\t\"Board\": \"Brett\",\n\t\t\t\t\"Giant\": \"Kjempe\",\n\t\t\t\t\"Large\": \"Stor\",\n\t\t\t\t\"Medium\": \"Medium\",\n\t\t\t\t\"Small\": \"Liten\",\n\t\t\t\"Graph\": \"Graf\",\n\t\t\t\"Graph lines\": \"Graf-linjer\",\n\t\t\t\"I want other size options!\": \"Jeg vil ha andre størrelsesvalg!\",\n\t\t\"Engine\": \"Motor\",\n\t\t\t\"Choose engine...\": \"Velg motor...\",\n\t\t\t\"Choose known engine...\": \"Velg kjent motor...\",\n\t\t\t\"Weights\": \"Vekter\",\n\t\t\t\t\"Lc0 WeightsFile...\": \"Lc0 vektfil...\",\n\t\t\t\t\"Stockfish EvalFile...\": \"Stockfish evalueringsfil...\",\n\t\t\t\t\"Set to <auto>\": \"Sett til <auto>\",\n\t\t\t\"Backend\": \"Backend\",\n\t\t\t\"Choose Syzygy path...\": \"Velg Syzygy-sti...\",\n\t\t\t\"Unset\": \"Fjern\",\n\t\t\t\"Limit - normal\": \"Grense - normal\",\n\t\t\t\t\"Unlimited\": \"Ubegrenset\",\n\t\t\t\t\"Up slightly\": \"Litt opp\",\n\t\t\t\t\"Down slightly\": \"Litt ned\",\n\t\t\t\"Limit - auto-eval / play\": \"Grense - auto-eval / spill\",\n\t\t\t\"Limit by time instead of nodes\": \"Begrens med tid i stedet for noder\",\n\t\t\t\"Threads\": \"Tråder\",\n\t\t\t\t\"Warning about threads\": \"Advarsel om tråder\",\n\t\t\t\"Hash\": \"Hash\",\n\t\t\t\t\"I want other hash options!\": \"Jeg vil ha andre hash-valg!\",\n\t\t\t\"MultiPV\": \"MultiPV\",\n\t\t\t\"Contempt Mode\": \"Contempt-modus\",\n\t\t\t\t\"White analysis\": \"Hvit analyse\",\n\t\t\t\t\"Black analysis\": \"Svart analyse\",\n\t\t\t\"Contempt\": \"Contempt\",\n\t\t\t\"WDL Calibration Elo\": \"WDL kalibrering Elo\",\n\t\t\t\t\"Use default WDL\": \"Bruk standard WDL\",\n\t\t\t\"WDL Eval Objectivity\": \"WDL eval objektivitet\",\n\t\t\t\t\"Yes\": \"Ja\",\n\t\t\t\t\"No\": \"Nei\",\n\t\t\t\"Score Type\": \"Poengtype\",\n\t\t\t\"Custom scripts\": \"Egendefinerte skript\",\n\t\t\t\t\"How to add scripts\": \"Hvordan legge til skript\",\n\t\t\t\t\"Show scripts folder\": \"Vis skriptmappe\",\n\t\t\t\"Restart engine\": \"Start motor på nytt\",\n\t\t\t\"Soft engine reset\": \"Myk tilbakestilling av motor\",\n\t\t\"Play\": \"Spill\",\n\t\t\t\"Play this colour\": \"Spill denne fargen\",\n\t\t\t\"Start self-play\": \"Start selvspill\",\n\t\t\t\"Use Polyglot book...\": \"Bruk Polyglot-bok...\",\n\t\t\t\"Use PGN book...\": \"Bruk PGN-bok...\",\n\t\t\t\"Unload book / abort load\": \"Fjern bok / avbryt lasting\",\n\t\t\t\"Book depth limit\": \"Bokdybdegrense\",\n\t\t\t\"Temperature\": \"Temperatur\",\n\t\t\t\"Temp Decay Moves\": \"Temp nedbrytingstrekk\",\n\t\t\t\t\"Infinite\": \"Uendelig\",\n\t\t\t\"About play modes\": \"Om spillmoduser\",\n\t\t\"Dev\": \"Utvikling\",\n\t\t\t\"Toggle Developer Tools\": \"Utviklerverktøy av/på\",\n\t\t\t\"Toggle Debug CSS\": \"Debug CSS av/på\",\n\t\t\t\"Permanently enable save\": \"Aktiver lagring permanent\",\n\t\t\t[show_config]: \"Vis config.json\",\n\t\t\t[show_engineconfig]: \"Vis engines.json\",\n\t\t\t[reload_engineconfig]: \"Last engines.json på nytt (og start motor på nytt)\",\n\t\t\t\"Random move\": \"Tilfeldig trekk\",\n\t\t\t\"Disable hardware acceleration for GUI\": \"Deaktiver maskinvareakselerasjon for GUI\",\n\t\t\t\"Spin rate\": \"Oppdateringsfrekvens\",\n\t\t\t\t\"Frenetic\": \"Frenetisk\",\n\t\t\t\t\"Fast\": \"Rask\",\n\t\t\t\t\"Normal\": \"Normal\",\n\t\t\t\t\"Relaxed\": \"Avslappet\",\n\t\t\t\t\"Lazy\": \"Lat\",\n\t\t\t\"Show engine state\": \"Vis motorstatus\",\n\t\t\t\"List sent options\": \"List sendte alternativer\",\n\t\t\t\"Show error log\": \"Vis feillogg\",\n\t\t\t\"Hacks and kludges\": \"Hacks og kludges\",\n\t\t\t\t\"Allow arbitrary scripts\": \"Tillat vilkårlige skript\",\n\t\t\t\t\"Accept any file size\": \"Aksepter alle filstørrelser\",\n\t\t\t\t\"Allow stopped analysis\": \"Tillat stoppet analyse\",\n\t\t\t\t\"Never hide focus buttons\": \"Aldri skjul fokusknapper\",\n\t\t\t\t\"Never grayout move info\": \"Aldri gråtone trekkinfo\",\n\t\t\t\t\"Use lowerbound / upperbound info\": \"Bruk lowerbound / upperbound info\",\n\t\t\t\t\"Suppress ucinewgame\": \"Undertrykk ucinewgame\",\n\t\t\t\"Log RAM state to console\": \"Logg RAM-status til konsoll\",\n\t\t\t\"Fire GC\": \"Kjør GC\",\n\t\t\t\"Logging\": \"Logging\",\n\t\t\t\t\"Use logfile...\": \"Bruk loggfil...\",\n\t\t\t\t\"Disable logging\": \"Deaktiver logging\",\n\t\t\t\t\"Clear log when opening\": \"Tøm logg ved åpning\",\n\t\t\t\t\"Use unique logfile each time\": \"Bruk unik loggfil hver gang\",\n\t\t\t\t\"Log illegal moves\": \"Logg ugyldige trekk\",\n\t\t\t\t\"Log positions\": \"Logg stillinger\",\n\t\t\t\t\"Log info lines\": \"Logg info-linjer\",\n\t\t\t\t\"...including useless lines\": \"...inkludert ubrukelige linjer\",\n\t\t\"Language\": \"Språk\",\n\n\t\t\"RESTART_REQUIRED\": \"GUI må startes på nytt.\"\n\t},\n\n\t// POLISH .....................................................................................\n\n\t\"Polski\": {\n\t\t\"File\": \"Plik\",\n\t\t\t\"About\": \"O programie\",\n\t\t\t\"New game\": \"Nowa partia\",\n\t\t\t\"New 960 game\": \"Nowa partia 960\",\n\t\t\t\"Open PGN...\": \"Otwórz PGN...\",\n\t\t\t\"Load FEN / PGN from clipboard\": \"Wczytaj FEN / PGN ze schowka\",\n\t\t\t\"Save this game...\": \"Zapisz partię...\",\n\t\t\t\"Write PGN to clipboard\": \"Zapisz PGN do schowka\",\n\t\t\t\"PGN saved statistics\": \"Statystyki zapisane w PGN\",\n\t\t\t\t\"EV\": \"EV\",\n\t\t\t\t\"Centipawns\": \"Setne piona\",\n\t\t\t\t\"N (%)\": \"N (%)\",\n\t\t\t\t\"N (absolute)\": \"N (bezwzględne)\",\n\t\t\t\t\"...out of total\": \"...z całości\",\n\t\t\t\t\"Depth (A/B only)\": \"Głębokość (tylko A/B)\",\n\t\t\t\"Cut\": \"Wytnij\",\n\t\t\t\"Copy\": \"Kopiuj\",\n\t\t\t\"Paste\": \"Wklej\",\n\t\t\t\"Quit\": \"Zamknij\",\n\t\t\"Tree\": \"Drzewo\",\n\t\t\t\"Play engine choice\": \"Zagraj wybór silnika\",\n\t\t\t\t\"1st\": \"1-szy\",\n\t\t\t\t\"2nd\": \"2-gi\",\n\t\t\t\t\"3rd\": \"3-ci\",\n\t\t\t\t\"4th\": \"4-ty\",\n\t\t\t\"Root\": \"Początek\",\n\t\t\t\"End\": \"Koniec\",\n\t\t\t\"Backward\": \"Wstecz\",\n\t\t\t\"Forward\": \"Naprzód\",\n\t\t\t\"Previous sibling\": \"Poprzedni wariant\",\n\t\t\t\"Next sibling\": \"Następny wariant\",\n\t\t\t\"Return to main line\": \"Powrót do głównej linii\",\n\t\t\t\"Promote line to main line\": \"Promuj linię na główną\",\n\t\t\t\"Promote line by 1 level\": \"Promuj linię o 1 poziom\",\n\t\t\t\"Delete node\": \"Usuń ruch\",\n\t\t\t\"Delete children\": \"Usuń poboczne\",\n\t\t\t\"Delete siblings\": \"Usuń równorzędne\",\n\t\t\t\"Delete ALL other lines\": \"Usuń WSZYSTKIE inne linie\",\n\t\t\t\"Show PGN games list\": \"Pokaż listę partii PGN\",\n\t\t\t\"Escape\": \"Wyjście\",\n\t\t\"Analysis\": \"Analiza\",\n\t\t\t\"Go\": \"Start\",\n\t\t\t\"Go and lock engine\": \"Rusz i zablokuj silnik\",\n\t\t\t\"Return to locked position\": \"Powrót do zablokowanej pozycji\",\n\t\t\t\"Halt\": \"Stój\",\n\t\t\t\"Auto-evaluate line\": \"Auto-analiza linii\",\n\t\t\t\"Auto-evaluate line, backwards\": \"Auto-analiza linii, odwrotnie\",\n\t\t\t\"Show focus (searchmoves) buttons\": \"Pokaż przycisk skupić (searchmoves)\",\n\t\t\t\"Clear focus\": \"Usuń skupienie\",\n\t\t\t\"Invert focus\": \"Odwróć skupienie\",\n\t\t\t\"Winrate POV\": \"Perspektywa szans\",\n\t\t\t\t\"Current\": \"Aktualny\",\n\t\t\t\t\"White\": \"Biały\",\n\t\t\t\t\"Black\": \"Czarny\",\n\t\t\t\"Centipawn POV\": \"Perspektywa CP\",\n\t\t\t\"Win / draw / loss POV\": \"Perspektywa Wygrana / remis / przegrana\",\n\t\t\t\"PV clicks\": \"Kliknięcia PV\",\n\t\t\t\t\"Do nothing\": \"Nic nie rób\",\n\t\t\t\t\"Go there\": \"Przejdź tam\",\n\t\t\t\t\"Add to tree\": \"Dodaj do drzewa\",\n\t\t\t\"Write infobox to clipboard\": \"Zapisz infobox do schowka\",\n\t\t\t\"Forget all analysis\": \"Zapomnij analizę\",\n\t\t\"Display\": \"Widok\",\n\t\t\t\"Flip board\": \"Obróć szachownicę\",\n\t\t\t\"Arrows\": \"Strzałki\",\n\t\t\t\"Piece-click spotlight\": \"Podświetlanie figur przy kliknięciu\",\n\t\t\t\"Always show actual move (if known)\": \"Pokazuj faktyczny ruch (jeśli znany)\",\n\t\t\t\"...with unique colour\": \"...z unikalnym kolorem\",\n\t\t\t\"...with outline\": \"...z konturem\",\n\t\t\t\"Arrowhead type\": \"Typ strzałek\",\n\t\t\t\t\"Winrate\": \"Szansa wygranej\",\n\t\t\t\t\"Node %\": \"Węzły %\",\n\t\t\t\t\"Policy\": \"Polityka\",\n\t\t\t\t\"MultiPV rank\": \"Ranga MultiPV\",\n\t\t\t\t\"Moves Left Head\": \"Przewidywana długość (ML)\",\n\t\t\t\"Arrow filter (Lc0)\": \"Filtr strzałek (Lc0)\",\n\t\t\t\t\"All moves\": \"Wszystkie ruchy\",\n\t\t\t\t\"Top move\": \"Najlepszy ruch\",\n\t\t\t\"Arrow filter (others)\": \"Filtr strzałek (inne)\",\n\t\t\t\t\"Diff < 15%\": \"Różnica < 15%\",\n\t\t\t\t\"Diff < 10%\": \"Różnica < 10%\",\n\t\t\t\t\"Diff < 5%\": \"Różnica < 5%\",\n\t\t\t\"Infobox stats\": \"Statystyki infoboxu\",\n\t\t\t\t\"N - nodes (%)\": \"N - węzły (%)\",\n\t\t\t\t\"N - nodes (absolute)\": \"N - węzły (bezwzględne)\",\n\t\t\t\t\"P - policy\": \"P - polityka\",\n\t\t\t\t\"V - static evaluation\": \"V - ocena statyczna\",\n\t\t\t\t\"Q - evaluation\": \"Q - ocena\",\n\t\t\t\t\"U - uncertainty\": \"U - niepewność\",\n\t\t\t\t\"S - search priority\": \"S - priorytet wyszukiwania\",\n\t\t\t\t\"M - moves left\": \"M - pozostało ruchów\",\n\t\t\t\t\"WDL - win / draw / loss\": \"WDL - wygrana / remis / przegrana\",\n\t\t\t\t\"Linebreak before stats\": \"Nowy wiersz przed statystykami\",\n\t\t\t\"PV move numbers\": \"Numery ruchów PV\",\n\t\t\t\"Online API\": \"API online\",\n\t\t\t\t\"None\": \"Żaden\",\n\t\t\t\t\"ChessDB.cn evals\": \"Oceny ChessDB.cn\",\n\t\t\t\t\"Lichess results (masters)\": \"Wyniki Lichess (mistrzowie)\",\n\t\t\t\t\"Lichess results (plebs)\": \"Wyniki Lichess (amatorzy)\",\n\t\t\t\t\"Set Lichess API token\": \"Ustaw token API Lichess\",\n\t\t\t\"Allow API after move 25\": \"Zezwól na API po ruchu 25\",\n\t\t\t\"Draw PV on mouseover\": \"Pokaż PV przy najechaniu\",\n\t\t\t\"Draw PV method\": \"Metoda rysowania PV\",\n\t\t\t\t\"Animate\": \"Animacja\",\n\t\t\t\t\"Single move\": \"Pojedynczy ruch\",\n\t\t\t\t\"Final position\": \"Pozycja końcowa\",\n\t\t\t\"Pieces\": \"Figury\",\n\t\t\t\t\"Choose pieces folder...\": \"Wybierz folder figur...\",\n\t\t\t\t\"Default\": \"Domyślne\",\n\t\t\t\t\"About custom pieces\": \"O własnych figurach\",\n\t\t\t\"Background\": \"Tło\",\n\t\t\t\t\"Choose background image...\": \"Wybierz obraz tła...\",\n\t\t\t\"Book frequency arrows\": \"Strzałki częstości debiutowej\",\n\t\t\t\"Lichess frequency arrows\": \"Strzałki częstości Lichess\",\n\t\t\"Sizes\": \"Rozmiary\",\n\t\t\t\"Infobox font\": \"Czcionka infoboxu\",\n\t\t\t\"Move history font\": \"Czcionka historii ruchów\",\n\t\t\t\"Board\": \"Szachownica\",\n\t\t\t\t\"Giant\": \"Ogromna\",\n\t\t\t\t\"Large\": \"Duża\",\n\t\t\t\t\"Medium\": \"Średnia\",\n\t\t\t\t\"Small\": \"Mała\",\n\t\t\t\"Graph\": \"Wykres\",\n\t\t\t\"Graph lines\": \"Linie wykresu\",\n\t\t\t\"I want other size options!\": \"Chcę innych opcji rozmiaru!\",\n\t\t\"Engine\": \"Silnik\",\n\t\t\t\"Choose engine...\": \"Wybierz silnik...\",\n\t\t\t\"Choose known engine...\": \"Wybierz znany silnik...\",\n\t\t\t\"Weights\": \"Neurony\",\n\t\t\t\t\"Lc0 WeightsFile...\": \"Plik neuronowy Lc0...\",\n\t\t\t\t\"Stockfish EvalFile...\": \"Plik NNUE Stockfish'a...\",\n\t\t\t\t\"Set to <auto>\": \"Ustaw na <auto>\",\n\t\t\t\"Backend\": \"Backend\",\n\t\t\t\"Choose Syzygy path...\": \"Wybierz ścieżkę Syzygy...\",\n\t\t\t\"Unset\": \"Usuń Syzygy\",\n\t\t\t\"Limit - normal\": \"Limit - normalny\",\n\t\t\t\t\"Unlimited\": \"Bez limitu\",\n\t\t\t\t\"Up slightly\": \"Zwiększ trochę\",\n\t\t\t\t\"Down slightly\": \"Zmniejsz trochę\",\n\t\t\t\"Limit - auto-eval / play\": \"Limit - auto-analizy / gry\",\n\t\t\t\"Limit by time instead of nodes\": \"Limituj czasem zamiast węzłami\",\n\t\t\t\"Threads\": \"Wątki\",\n\t\t\t\t\"Warning about threads\": \"Ostrzeżenie o wątkach\",\n\t\t\t\"Hash\": \"Pamięć\",\n\t\t\t\t\"I want other hash options!\": \"Chcę innych opcji Ram'u!\",\n\t\t\t\"MultiPV\": \"MultiPV\",\n\t\t\t\"Contempt Mode\": \"Tryb pogardy\",\n\t\t\t\t\"White analysis\": \"Analiza białych\",\n\t\t\t\t\"Black analysis\": \"Analiza czarnych\",\n\t\t\t\"Contempt\": \"Pogarda\",\n\t\t\t\"WDL Calibration Elo\": \"Kalibracja WDL Elo\",\n\t\t\t\t\"Use default WDL\": \"Użyj domyślnego WDL\",\n\t\t\t\"WDL Eval Objectivity\": \"Obiektywność oceny WDL\",\n\t\t\t\t\"Yes\": \"Tak\",\n\t\t\t\t\"No\": \"Nie\",\n\t\t\t\"Score Type\": \"Typ wyniku\",\n\t\t\t\"Custom scripts\": \"Własne skrypty\",\n\t\t\t\t\"How to add scripts\": \"Jak dodać skrypty\",\n\t\t\t\t\"Show scripts folder\": \"Pokaż folder skryptów\",\n\t\t\t\"Restart engine\": \"Zrestartuj silnik\",\n\t\t\t\"Soft engine reset\": \"Miękki reset silnika\",\n\t\t\"Play\": \"Gra\",\n\t\t\t\"Play this colour\": \"Graj tym kolorem\",\n\t\t\t\"Start self-play\": \"Rozpocznij grę automatyczną\",\n\t\t\t\"Use Polyglot book...\": \"Użyj księgi Polyglot...\",\n\t\t\t\"Use PGN book...\": \"Użyj księgi PGN...\",\n\t\t\t\"Unload book / abort load\": \"Wyładuj księgę / przerwij\",\n\t\t\t\"Book depth limit\": \"Limit głębokości księgi\",\n\t\t\t\"Temperature\": \"Temperatura\",\n\t\t\t\"Temp Decay Moves\": \"Zanik temperatury\",\n\t\t\t\t\"Infinite\": \"Nieskończony\",\n\t\t\t\"About play modes\": \"O trybach gry\",\n\t\t\"Dev\": \"Dev\",\n\t\t\t\"Toggle Developer Tools\": \"Przełącz narzędzia deweloperskie\",\n\t\t\t\"Toggle Debug CSS\": \"Przełącz debugowanie CSS\",\n\t\t\t\"Permanently enable save\": \"Trwale włącz zapis\",\n\t\t\t[show_config]: \"Pokaż config.json\",\n\t\t\t[show_engineconfig]: \"Pokaż engines.json\",\n\t\t\t[reload_engineconfig]: \"Przeładuj engines.json (i zrestartuj silnik)\",\n\t\t\t\"Random move\": \"Losowy ruch\",\n\t\t\t\"Disable hardware acceleration for GUI\": \"Wyłącz akcelerację sprzętową GUI\",\n\t\t\t\"Spin rate\": \"Częstość odświeżania\",\n\t\t\t\t\"Frenetic\": \"Frenetyczna\",\n\t\t\t\t\"Fast\": \"Szybka\",\n\t\t\t\t\"Normal\": \"Normalna\",\n\t\t\t\t\"Relaxed\": \"Zrelaksowana\",\n\t\t\t\t\"Lazy\": \"Leniwa\",\n\t\t\t\"Show engine state\": \"Pokaż stan silnika\",\n\t\t\t\"List sent options\": \"Lista wysłanych opcji\",\n\t\t\t\"Show error log\": \"Pokaż log błędów\",\n\t\t\t\"Hacks and kludges\": \"Modyfikacje i obejścia\",\n\t\t\t\t\"Allow arbitrary scripts\": \"Zezwól na dowolne skrypty\",\n\t\t\t\t\"Accept any file size\": \"Akceptuj każdy rozmiar pliku\",\n\t\t\t\t\"Allow stopped analysis\": \"Zezwól na zatrzymaną analizę\",\n\t\t\t\t\"Never hide focus buttons\": \"Nigdy nie ukrywaj przycisków skupienia\",\n\t\t\t\t\"Never grayout move info\": \"Nigdy nie wyszarzaj info o ruchu\",\n\t\t\t\t\"Use lowerbound / upperbound info\": \"Użyj info o dolnej/górnej granicy\",\n\t\t\t\t\"Suppress ucinewgame\": \"Pomiń ucinewgame\",\n\t\t\t\"Log RAM state to console\": \"Loguj stan RAM do konsoli\",\n\t\t\t\"Fire GC\": \"Uruchom GC\",\n\t\t\t\"Logging\": \"Logowanie\",\n\t\t\t\t\"Use logfile...\": \"Użyj pliku logu...\",\n\t\t\t\t\"Disable logging\": \"Wyłącz logowanie\",\n\t\t\t\t\"Clear log when opening\": \"Wyczyść log przy otwarciu\",\n\t\t\t\t\"Use unique logfile each time\": \"Użyj unikalnego pliku logu\",\n\t\t\t\t\"Log illegal moves\": \"Loguj nieprawidłowe ruchy\",\n\t\t\t\t\"Log positions\": \"Loguj pozycje\",\n\t\t\t\t\"Log info lines\": \"Loguj linie info\",\n\t\t\t\t\"...including useless lines\": \"...włącznie z bezużytecznymi liniami\",\n\t\t\"Language\": \"Język\",\n\n\t\t\"RESTART_REQUIRED\": \"Program musi zostać uruchomiony ponownie.\"\n\t},\n\n\t// TURKISH ....................................................................................\n\n\t\"Türkçe\": {\n\t\t\"File\": \"Dosya\",\n\t\t\t\"About\": \"Hakkında\",\n\t\t\t\"New game\": \"Yeni oyun\",\n\t\t\t\"New 960 game\": \"Yeni 960 oyunu\",\n\t\t\t\"Open PGN...\": \"PGN aç...\",\n\t\t\t\"Load FEN / PGN from clipboard\": \"Panodan FEN / PGN yükle\",\n\t\t\t\"Save this game...\": \"Bu oyunu kaydet...\",\n\t\t\t\"Write PGN to clipboard\": \"PGN'yi panoya kopyala\",\n\t\t\t\"PGN saved statistics\": \"PGN kayıt istatistikleri\",\n\t\t\t\t\"EV\": \"EV\",\n\t\t\t\t\"Centipawns\": \"Centipawns\",\n\t\t\t\t\"N (%)\": \"N (%)\",\n\t\t\t\t\"N (absolute)\": \"N (mutlak)\",\n\t\t\t\t\"...out of total\": \"...toplam içinde\",\n\t\t\t\t\"Depth (A/B only)\": \"Derinlik (sadece A/B)\",\n\t\t\t\"Cut\": \"Kes\",\n\t\t\t\"Copy\": \"Kopyala\",\n\t\t\t\"Paste\": \"Yapıştır\",\n\t\t\t\"Quit\": \"Çıkış\",\n\t\t\"Tree\": \"Ağaç\",\n\t\t\t\"Play engine choice\": \"Motor seçimini oyna\",\n\t\t\t\t\"1st\": \"1.\",\n\t\t\t\t\"2nd\": \"2.\",\n\t\t\t\t\"3rd\": \"3.\",\n\t\t\t\t\"4th\": \"4.\",\n\t\t\t\"Root\": \"Başlangıç\",\n\t\t\t\"End\": \"Son\",\n\t\t\t\"Backward\": \"Geri\",\n\t\t\t\"Forward\": \"İleri\",\n\t\t\t\"Previous sibling\": \"Önceki varyant\",\n\t\t\t\"Next sibling\": \"Sonraki varyant\",\n\t\t\t\"Return to main line\": \"Ana varyanta dön\",\n\t\t\t\"Promote line to main line\": \"Varyantı ana varyant yap\",\n\t\t\t\"Promote line by 1 level\": \"Varyantı 1 seviye yükselt\",\n\t\t\t\"Delete node\": \"Hamleyi sil\",\n\t\t\t\"Delete children\": \"Alt hamleleri sil\",\n\t\t\t\"Delete siblings\": \"Kardeş hamleleri sil\",\n\t\t\t\"Delete ALL other lines\": \"TÜM diğer varyantları sil\",\n\t\t\t\"Show PGN games list\": \"PGN oyun listesini göster\",\n\t\t\t\"Escape\": \"Çıkış\",\n\t\t\"Analysis\": \"Analiz\",\n\t\t\t\"Go\": \"Başla\",\n\t\t\t\"Go and lock engine\": \"Başla ve motoru kilitle\",\n\t\t\t\"Return to locked position\": \"Kilitli pozisyona dön\",\n\t\t\t\"Halt\": \"Durdur\",\n\t\t\t\"Auto-evaluate line\": \"Varyantı otomatik değerlendir\",\n\t\t\t\"Auto-evaluate line, backwards\": \"Varyantı geriye doğru değerlendir\",\n\t\t\t\"Show focus (searchmoves) buttons\": \"Odak (searchmoves) düğmelerini göster\",\n\t\t\t\"Clear focus\": \"Odağı temizle\",\n\t\t\t\"Invert focus\": \"Odağı tersine çevir\",\n\t\t\t\"Winrate POV\": \"Kazanma oranı bakış açısı\",\n\t\t\t\t\"Current\": \"Mevcut\",\n\t\t\t\t\"White\": \"Beyaz\",\n\t\t\t\t\"Black\": \"Siyah\",\n\t\t\t\"Centipawn POV\": \"Centipawn bakış açısı\",\n\t\t\t\"Win / draw / loss POV\": \"Kazanç / beraberlik / kayıp bakış açısı\",\n\t\t\t\"PV clicks\": \"PV tıklamaları\",\n\t\t\t\t\"Do nothing\": \"Hiçbir şey yapma\",\n\t\t\t\t\"Go there\": \"Oraya git\",\n\t\t\t\t\"Add to tree\": \"Ağaca ekle\",\n\t\t\t\"Write infobox to clipboard\": \"Bilgi kutusunu panoya kopyala\",\n\t\t\t\"Forget all analysis\": \"Tüm analizi unut\",\n\t\t\"Display\": \"Görünüm\",\n\t\t\t\"Flip board\": \"Tahtayı çevir\",\n\t\t\t\"Arrows\": \"Oklar\",\n\t\t\t\"Piece-click spotlight\": \"Taş tıklama ışıklandırması\",\n\t\t\t\"Always show actual move (if known)\": \"Gerçek hamleyi her zaman göster (biliniyorsa)\",\n\t\t\t\"...with unique colour\": \"...benzersiz renkle\",\n\t\t\t\"...with outline\": \"...çerçeveyle\",\n\t\t\t\"Arrowhead type\": \"Ok ucu tipi\",\n\t\t\t\t\"Winrate\": \"Kazanma oranı\",\n\t\t\t\t\"Node %\": \"Düğüm %\",\n\t\t\t\t\"Policy\": \"Policy\",\n\t\t\t\t\"MultiPV rank\": \"MultiPV sırası\",\n\t\t\t\t\"Moves Left Head\": \"Kalan Hamleler\",\n\t\t\t\"Arrow filter (Lc0)\": \"Ok filtresi (Lc0)\",\n\t\t\t\t\"All moves\": \"Tüm hamleler\",\n\t\t\t\t\"Top move\": \"En iyi hamle\",\n\t\t\t\"Arrow filter (others)\": \"Ok filtresi (diğerleri)\",\n\t\t\t\t\"Diff < 15%\": \"Fark < %15\",\n\t\t\t\t\"Diff < 10%\": \"Fark < %10\",\n\t\t\t\t\"Diff < 5%\": \"Fark < %5\",\n\t\t\t\"Infobox stats\": \"Bilgi kutusu istatistikleri\",\n\t\t\t\t\"N - nodes (%)\": \"N - düğümler (%)\",\n\t\t\t\t\"N - nodes (absolute)\": \"N - düğümler (mutlak)\",\n\t\t\t\t\"P - policy\": \"P - policy\",\n\t\t\t\t\"V - static evaluation\": \"V - statik değerlendirme\",\n\t\t\t\t\"Q - evaluation\": \"Q - değerlendirme\",\n\t\t\t\t\"U - uncertainty\": \"U - belirsizlik\",\n\t\t\t\t\"S - search priority\": \"S - arama önceliği\",\n\t\t\t\t\"M - moves left\": \"M - kalan hamleler\",\n\t\t\t\t\"WDL - win / draw / loss\": \"WDL - kazanç / beraberlik / kayıp\",\n\t\t\t\t\"Linebreak before stats\": \"İstatistiklerden önce satır sonu\",\n\t\t\t\"PV move numbers\": \"PV hamle numaraları\",\n\t\t\t\"Online API\": \"Çevrimiçi API\",\n\t\t\t\t\"None\": \"Hiçbiri\",\n\t\t\t\t\"ChessDB.cn evals\": \"ChessDB.cn değerlendirmeleri\",\n\t\t\t\t\"Lichess results (masters)\": \"Lichess sonuçları (ustalar)\",\n\t\t\t\t\"Lichess results (plebs)\": \"Lichess sonuçları (normal)\",\n\t\t\t\t\"Set Lichess API token\": \"Lichess API jetonunu ayarla\",\n\t\t\t\"Allow API after move 25\": \"25. hamleden sonra API'ye izin ver\",\n\t\t\t\"Draw PV on mouseover\": \"Fare üzerindeyken PV'yi çiz\",\n\t\t\t\"Draw PV method\": \"PV çizim yöntemi\",\n\t\t\t\t\"Animate\": \"Animasyon\",\n\t\t\t\t\"Single move\": \"Tek hamle\",\n\t\t\t\t\"Final position\": \"Son konum\",\n\t\t\t\"Pieces\": \"Taşlar\",\n\t\t\t\t\"Choose pieces folder...\": \"Taş klasörü seç...\",\n\t\t\t\t\"Default\": \"Varsayılan\",\n\t\t\t\t\"About custom pieces\": \"Özel taşlar hakkında\",\n\t\t\t\"Background\": \"Arkaplan\",\n\t\t\t\t\"Choose background image...\": \"Arkaplan resmi seç...\",\n\t\t\t\"Book frequency arrows\": \"Açılış kitabı sıklık okları\",\n\t\t\t\"Lichess frequency arrows\": \"Lichess sıklık okları\",\n\t\t\"Sizes\": \"Boyutlar\",\n\t\t\t\"Infobox font\": \"Bilgi kutusu yazı tipi\",\n\t\t\t\"Move history font\": \"Hamle geçmişi yazı tipi\",\n\t\t\t\"Board\": \"Tahta\",\n\t\t\t\t\"Giant\": \"Çok büyük\",\n\t\t\t\t\"Large\": \"Büyük\",\n\t\t\t\t\"Medium\": \"Orta\",\n\t\t\t\t\"Small\": \"Küçük\",\n\t\t\t\"Graph\": \"Grafik\",\n\t\t\t\"Graph lines\": \"Grafik çizgileri\",\n\t\t\t\"I want other size options!\": \"Başka boyut seçenekleri istiyorum!\",\n\t\t\"Engine\": \"Motor\",\n\t\t\t\"Choose engine...\": \"Motor seç...\",\n\t\t\t\"Choose known engine...\": \"Bilinen motor seç...\",\n\t\t\t\"Weights\": \"Ağırlıklar\",\n\t\t\t\t\"Lc0 WeightsFile...\": \"Lc0 WeightsFile...\",\n\t\t\t\t\"Stockfish EvalFile...\": \"Stockfish EvalFile...\",\n\t\t\t\t\"Set to <auto>\": \"<auto> olarak ayarla\",\n\t\t\t\"Backend\": \"Backend\",\n\t\t\t\"Choose Syzygy path...\": \"Syzygy yolu seç...\",\n\t\t\t\"Unset\": \"Kaldır\",\n\t\t\t\"Limit - normal\": \"Limit - normal\",\n\t\t\t\t\"Unlimited\": \"Limitsiz\",\n\t\t\t\t\"Up slightly\": \"Biraz artır\",\n\t\t\t\t\"Down slightly\": \"Biraz azalt\",\n\t\t\t\"Limit - auto-eval / play\": \"Limit - otomatik değerlendirme / oyun\",\n\t\t\t\"Limit by time instead of nodes\": \"Düğüm yerine zamanla sınırla\",\n\t\t\t\"Threads\": \"İş parçacığı\",\n\t\t\t\t\"Warning about threads\": \"İş parçacığı uyarısı\",\n\t\t\t\"Hash\": \"Hash\",\n\t\t\t\t\"I want other hash options!\": \"Başka hash seçenekleri istiyorum!\",\n\t\t\t\"MultiPV\": \"MultiPV\",\n\t\t\t\"Contempt Mode\": \"Contempt Modu\",\n\t\t\t\t\"White analysis\": \"Beyaz analizi\",\n\t\t\t\t\"Black analysis\": \"Siyah analizi\",\n\t\t\t\"Contempt\": \"Contempt\",\n\t\t\t\"WDL Calibration Elo\": \"WDL Kalibrasyon Elo\",\n\t\t\t\t\"Use default WDL\": \"Varsayılan WDL kullan\",\n\t\t\t\"WDL Eval Objectivity\": \"WDL Değerlendirme Objektifliği\",\n\t\t\t\t\"Yes\": \"Evet\",\n\t\t\t\t\"No\": \"Hayır\",\n\t\t\t\"Score Type\": \"Skor Tipi\",\n\t\t\t\"Custom scripts\": \"Özel scriptler\",\n\t\t\t\t\"How to add scripts\": \"Script nasıl eklenir\",\n\t\t\t\t\"Show scripts folder\": \"Script klasörünü göster\",\n\t\t\t\"Restart engine\": \"Motoru yeniden başlat\",\n\t\t\t\"Soft engine reset\": \"Yumuşak motor sıfırlama\",\n\t\t\"Play\": \"Oyna\",\n\t\t\t\"Play this colour\": \"Bu rengi oyna\",\n\t\t\t\"Start self-play\": \"Kendi kendine oyunu başlat\",\n\t\t\t\"Use Polyglot book...\": \"Polyglot kitabı kullan...\",\n\t\t\t\"Use PGN book...\": \"PGN kitabı kullan...\",\n\t\t\t\"Unload book / abort load\": \"Kitabı kaldır / yüklemeyi iptal et\",\n\t\t\t\"Book depth limit\": \"Kitap derinlik limiti\",\n\t\t\t\"Temperature\": \"Sıcaklık\",\n\t\t\t\"Temp Decay Moves\": \"Sıcaklık Azalma Hamleleri\",\n\t\t\t\t\"Infinite\": \"Sonsuz\",\n\t\t\t\"About play modes\": \"Oyun modları hakkında\",\n\t\t\"Dev\": \"Geliştirici\",\n\t\t\t\"Toggle Developer Tools\": \"Geliştirici Araçlarını Aç/Kapat\",\n\t\t\t\"Toggle Debug CSS\": \"Hata Ayıklama CSS'ini Aç/Kapat\",\n\t\t\t\"Permanently enable save\": \"Kaydetmeyi kalıcı olarak etkinleştir\",\n\t\t\t[show_config]: \"config.json'ı göster\",\n\t\t\t[show_engineconfig]: \"engines.json'ı göster\",\n\t\t\t[reload_engineconfig]: \"engines.json yenile + motor yeniden başlat\",\n\t\t\t\"Random move\": \"Rastgele hamle\",\n\t\t\t\"Disable hardware acceleration for GUI\": \"GUI donanım hızlandırma kapalı\",\n\t\t\t\"Spin rate\": \"Yenileme hızı\",\n\t\t\t\t\"Frenetic\": \"Çok hızlı\",\n\t\t\t\t\"Fast\": \"Hızlı\",\n\t\t\t\t\"Normal\": \"Normal\",\n\t\t\t\t\"Relaxed\": \"Rahat\",\n\t\t\t\t\"Lazy\": \"Yavaş\",\n\t\t\t\"Show engine state\": \"Motor durumunu göster\",\n\t\t\t\"List sent options\": \"Gönderilen seçenekleri listele\",\n\t\t\t\"Show error log\": \"Hata günlüğünü göster\",\n\t\t\t\"Hacks and kludges\": \"Geçici çözümler\",\n\t\t\t\t\"Allow arbitrary scripts\": \"Rastgele scriptlere izin ver\",\n\t\t\t\t\"Accept any file size\": \"Her dosya boyutunu kabul et\",\n\t\t\t\t\"Allow stopped analysis\": \"Durmuş analize izin ver\",\n\t\t\t\t\"Never hide focus buttons\": \"Odak düğmelerini asla gizleme\",\n\t\t\t\t\"Never grayout move info\": \"Hamle bilgisini asla grilendirme\",\n\t\t\t\t\"Use lowerbound / upperbound info\": \"Alt sınır / üst sınır bilgisini kullan\",\n\t\t\t\t\"Suppress ucinewgame\": \"ucinewgame'i bastır\",\n\t\t\t\"Log RAM state to console\": \"RAM durumunu konsola kaydet\",\n\t\t\t\"Fire GC\": \"Çöp toplayıcıyı çalıştır\",\n\t\t\t\"Logging\": \"Günlük kaydı\",\n\t\t\t\t\"Use logfile...\": \"Günlük dosyası kullan...\",\n\t\t\t\t\"Disable logging\": \"Günlük kaydını devre dışı bırak\",\n\t\t\t\t\"Clear log when opening\": \"Açarken günlüğü temizle\",\n\t\t\t\t\"Use unique logfile each time\": \"Her seferinde benzersiz günlük dosyası kullan\",\n\t\t\t\t\"Log illegal moves\": \"Geçersiz hamleleri kaydet\",\n\t\t\t\t\"Log positions\": \"Konumları kaydet\",\n\t\t\t\t\"Log info lines\": \"Bilgi satırlarını kaydet\",\n\t\t\t\t\"...including useless lines\": \"...gereksiz satırlar dahil\",\n\t\t\"Language\": \"Dil\",\n\n\t\t\"RESTART_REQUIRED\": \"Arayüz şimdi yeniden başlatılmalı.\"\n\t},\n\n\t// RUSSIAN ....................................................................................\n\n\t\"Русский\": {\n\t\t\"File\": \"Файл\",\n\t\t\t\"About\": \"О программе\",\n\t\t\t\"New game\": \"Новая партия\",\n\t\t\t\"New 960 game\": \"Новая партия 960\",\n\t\t\t\"Open PGN...\": \"Открыть PGN...\",\n\t\t\t\"Load FEN / PGN from clipboard\": \"Загрузить FEN / PGN из буфера\",\n\t\t\t\"Save this game...\": \"Сохранить партию...\",\n\t\t\t\"Write PGN to clipboard\": \"Копировать PGN в буфер\",\n\t\t\t\"PGN saved statistics\": \"Статистика в PGN\",\n\t\t\t\t\"EV\": \"Оценка\",\n\t\t\t\t\"Centipawns\": \"Сотые пешки\",\n\t\t\t\t\"N (%)\": \"N (%)\",\n\t\t\t\t\"N (absolute)\": \"N (абс.)\",\n\t\t\t\t\"...out of total\": \"...из всего\",\n\t\t\t\t\"Depth (A/B only)\": \"Глубина (А/Б)\",\n\t\t\t\"Cut\": \"Вырезать\",\n\t\t\t\"Copy\": \"Копировать\",\n\t\t\t\"Paste\": \"Вставить\",\n\t\t\t\"Quit\": \"Выход\",\n\t\t\"Tree\": \"Дерево\",\n\t\t\t\"Play engine choice\": \"Сыграть выбор движка\",\n\t\t\t\t\"1st\": \"1-й\",\n\t\t\t\t\"2nd\": \"2-й\",\n\t\t\t\t\"3rd\": \"3-й\",\n\t\t\t\t\"4th\": \"4-й\",\n\t\t\t\"Root\": \"В начало\",\n\t\t\t\"End\": \"В конец\",\n\t\t\t\"Backward\": \"Назад\",\n\t\t\t\"Forward\": \"Вперёд\",\n\t\t\t\"Previous sibling\": \"Пред. вариант\",\n\t\t\t\"Next sibling\": \"След. вариант\",\n\t\t\t\"Return to main line\": \"Вернуться в главную линию\",\n\t\t\t\"Promote line to main line\": \"Сделать главной линией\",\n\t\t\t\"Promote line by 1 level\": \"Повысить на уровень\",\n\t\t\t\"Delete node\": \"Удалить ход\",\n\t\t\t\"Delete children\": \"Удалить продолжения\",\n\t\t\t\"Delete siblings\": \"Удалить варианты\",\n\t\t\t\"Delete ALL other lines\": \"Удалить ВСЕ другие линии\",\n\t\t\t\"Show PGN games list\": \"Показать список партий\",\n\t\t\t\"Escape\": \"Escape\",\n\t\t\"Analysis\": \"Анализ\",\n\t\t\t\"Go\": \"Старт\",\n\t\t\t\"Go and lock engine\": \"Старт с фиксацией\",\n\t\t\t\"Return to locked position\": \"К фиксированной позиции\",\n\t\t\t\"Halt\": \"Стоп\",\n\t\t\t\"Auto-evaluate line\": \"Авто-анализ линии\",\n\t\t\t\"Auto-evaluate line, backwards\": \"Авто-анализ линии назад\",\n\t\t\t\"Show focus (searchmoves) buttons\": \"Показать кнопки фокуса\",\n\t\t\t\"Clear focus\": \"Очистить фокус\",\n\t\t\t\"Invert focus\": \"Инвертировать фокус\",\n\t\t\t\"Winrate POV\": \"Шанс победы\",\n\t\t\t\t\"Current\": \"Текущий\",\n\t\t\t\t\"White\": \"Белые\",\n\t\t\t\t\"Black\": \"Чёрные\",\n\t\t\t\"Centipawn POV\": \"Оценка в пешках\",\n\t\t\t\"Win / draw / loss POV\": \"Победа/ничья/проигрыш\",\n\t\t\t\"PV clicks\": \"Клики по вариантам\",\n\t\t\t\t\"Do nothing\": \"Ничего\",\n\t\t\t\t\"Go there\": \"Перейти\",\n\t\t\t\t\"Add to tree\": \"Добавить в дерево\",\n\t\t\t\"Write infobox to clipboard\": \"Копировать инфо в буфер\",\n\t\t\t\"Forget all analysis\": \"Забыть весь анализ\",\n\t\t\"Display\": \"Вид\",\n\t\t\t\"Flip board\": \"Перевернуть доску\",\n\t\t\t\"Arrows\": \"Стрелки\",\n\t\t\t\"Piece-click spotlight\": \"Подсветка ходов\",\n\t\t\t\"Always show actual move (if known)\": \"Показывать реальный ход\",\n\t\t\t\"...with unique colour\": \"...уникальным цветом\",\n\t\t\t\"...with outline\": \"...с контуром\",\n\t\t\t\"Arrowhead type\": \"Тип стрелок\",\n\t\t\t\t\"Winrate\": \"Шанс победы\",\n\t\t\t\t\"Node %\": \"Узлы %\",\n\t\t\t\t\"Policy\": \"Политика\",\n\t\t\t\t\"MultiPV rank\": \"Ранг MultiPV\",\n\t\t\t\t\"Moves Left Head\": \"Оставшиеся ходы\",\n\t\t\t\"Arrow filter (Lc0)\": \"Фильтр стрелок (Lc0)\",\n\t\t\t\t\"All moves\": \"Все ходы\",\n\t\t\t\t\"Top move\": \"Лучший ход\",\n\t\t\t\"Arrow filter (others)\": \"Фильтр стрелок (другие)\",\n\t\t\t\t\"Diff < 15%\": \"Разн. < 15%\",\n\t\t\t\t\"Diff < 10%\": \"Разн. < 10%\",\n\t\t\t\t\"Diff < 5%\": \"Разн. < 5%\",\n\t\t\t\"Infobox stats\": \"Статистика\",\n\t\t\t\t\"N - nodes (%)\": \"N - узлы (%)\",\n\t\t\t\t\"N - nodes (absolute)\": \"N - узлы (абс.)\",\n\t\t\t\t\"P - policy\": \"P - политика\",\n\t\t\t\t\"V - static evaluation\": \"V - статич. оценка\",\n\t\t\t\t\"Q - evaluation\": \"Q - оценка\",\n\t\t\t\t\"U - uncertainty\": \"U - неопред.\",\n\t\t\t\t\"S - search priority\": \"S - приоритет\",\n\t\t\t\t\"M - moves left\": \"M - ходов осталось\",\n\t\t\t\t\"WDL - win / draw / loss\": \"WDL - победа/ничья/проигрыш\",\n\t\t\t\t\"Linebreak before stats\": \"Перенос перед статистикой\",\n\t\t\t\"PV move numbers\": \"Нумерация ходов\",\n\t\t\t\"Online API\": \"Онлайн API\",\n\t\t\t\t\"None\": \"Нет\",\n\t\t\t\t\"ChessDB.cn evals\": \"Оценки ChessDB.cn\",\n\t\t\t\t\"Lichess results (masters)\": \"Lichess (мастера)\",\n\t\t\t\t\"Lichess results (plebs)\": \"Lichess (любители)\",\n\t\t\t\t\"Set Lichess API token\": \"Установить токен API Lichess\",\n\t\t\t\"Allow API after move 25\": \"Разрешить API после 25 хода\",\n\t\t\t\"Draw PV on mouseover\": \"Показывать вариант при наведении\",\n\t\t\t\"Draw PV method\": \"Метод показа варианта\",\n\t\t\t\t\"Animate\": \"Анимация\",\n\t\t\t\t\"Single move\": \"Один ход\",\n\t\t\t\t\"Final position\": \"Конечная позиция\",\n\t\t\t\"Pieces\": \"Фигуры\",\n\t\t\t\t\"Choose pieces folder...\": \"Выбрать папку фигур...\",\n\t\t\t\t\"Default\": \"По умолчанию\",\n\t\t\t\t\"About custom pieces\": \"О своих фигурах\",\n\t\t\t\"Background\": \"Фон\",\n\t\t\t\t\"Choose background image...\": \"Выбрать фоновое изображение...\",\n\t\t\t\"Book frequency arrows\": \"Стрелки частот дебюта\",\n\t\t\t\"Lichess frequency arrows\": \"Стрелки частот Lichess\",\n\t\t\"Sizes\": \"Размеры\",\n\t\t\t\"Infobox font\": \"Шрифт инфо\",\n\t\t\t\"Move history font\": \"Шрифт истории\",\n\t\t\t\"Board\": \"Доска\",\n\t\t\t\t\"Giant\": \"Огромный\",\n\t\t\t\t\"Large\": \"Большой\",\n\t\t\t\t\"Medium\": \"Средний\",\n\t\t\t\t\"Small\": \"Малый\",\n\t\t\t\"Graph\": \"График\",\n\t\t\t\"Graph lines\": \"Линии графика\",\n\t\t\t\"I want other size options!\": \"Хочу другие размеры!\",\n\t\t\"Engine\": \"Движок\",\n\t\t\t\"Choose engine...\": \"Выбрать движок...\",\n\t\t\t\"Choose known engine...\": \"Выбрать из известных...\",\n\t\t\t\"Weights\": \"Веса\",\n\t\t\t\t\"Lc0 WeightsFile...\": \"Файл весов Lc0...\",\n\t\t\t\t\"Stockfish EvalFile...\": \"Файл оценки Stockfish...\",\n\t\t\t\t\"Set to <auto>\": \"Установить <авто>\",\n\t\t\t\"Backend\": \"Бэкенд\",\n\t\t\t\"Choose Syzygy path...\": \"Путь к Syzygy...\",\n\t\t\t\"Unset\": \"Сбросить\",\n\t\t\t\"Limit - normal\": \"Лимит - обычный\",\n\t\t\t\t\"Unlimited\": \"Без лимита\",\n\t\t\t\t\"Up slightly\": \"Чуть больше\",\n\t\t\t\t\"Down slightly\": \"Чуть меньше\",\n\t\t\t\"Limit - auto-eval / play\": \"Лимит - авто-анализ/игра\",\n\t\t\t\"Limit by time instead of nodes\": \"Лимит по времени\",\n\t\t\t\"Threads\": \"Потоки\",\n\t\t\t\t\"Warning about threads\": \"Внимание о потоках\",\n\t\t\t\"Hash\": \"Хэш\",\n\t\t\t\t\"I want other hash options!\": \"Хочу другие опции хэша!\",\n\t\t\t\"MultiPV\": \"MultiPV\",\n\t\t\t\"Contempt Mode\": \"Режим презрения\",\n\t\t\t\t\"White analysis\": \"Анализ белых\",\n\t\t\t\t\"Black analysis\": \"Анализ чёрных\",\n\t\t\t\"Contempt\": \"Презрение\",\n\t\t\t\"WDL Calibration Elo\": \"Калибровка WDL Эло\",\n\t\t\t\t\"Use default WDL\": \"WDL по умолчанию\",\n\t\t\t\"WDL Eval Objectivity\": \"Объективность WDL\",\n\t\t\t\t\"Yes\": \"Да\",\n\t\t\t\t\"No\": \"Нет\",\n\t\t\t\"Score Type\": \"Тип оценки\",\n\t\t\t\"Custom scripts\": \"Свои скрипты\",\n\t\t\t\t\"How to add scripts\": \"Как добавить скрипты\",\n\t\t\t\t\"Show scripts folder\": \"Показать папку скриптов\",\n\t\t\t\"Restart engine\": \"Перезапуск движка\",\n\t\t\t\"Soft engine reset\": \"Мягкий сброс движка\",\n\t\t\"Play\": \"Игра\",\n\t\t\t\"Play this colour\": \"Играть этим цветом\",\n\t\t\t\"Start self-play\": \"Начать игру с собой\",\n\t\t\t\"Use Polyglot book...\": \"Использовать книгу Polyglot...\",\n\t\t\t\"Use PGN book...\": \"Использовать книгу PGN...\",\n\t\t\t\"Unload book / abort load\": \"Выгрузить/прервать загрузку\",\n\t\t\t\"Book depth limit\": \"Лимит глубины книги\",\n\t\t\t\"Temperature\": \"Температура\",\n\t\t\t\"Temp Decay Moves\": \"Убывание температуры\",\n\t\t\t\t\"Infinite\": \"Бесконечно\",\n\t\t\t\"About play modes\": \"О режимах игры\",\n\t\t\"Dev\": \"Разработка\",\n\t\t\t\"Toggle Developer Tools\": \"Инструменты разработчика\",\n\t\t\t\"Toggle Debug CSS\": \"Отладка CSS\",\n\t\t\t\"Permanently enable save\": \"Включить сохранение\",\n\t\t\t[show_config]: \"Показать config.json\",\n\t\t\t[show_engineconfig]: \"Показать engines.json\",\n\t\t\t[reload_engineconfig]: \"Перезагрузить engines.json\",\n\t\t\t\"Random move\": \"Случайный ход\",\n\t\t\t\"Disable hardware acceleration for GUI\": \"Отключить аппаратное ускорение\",\n\t\t\t\"Spin rate\": \"Частота обновления\",\n\t\t\t\t\"Frenetic\": \"Безумная\",\n\t\t\t\t\"Fast\": \"Быстрая\",\n\t\t\t\t\"Normal\": \"Нормальная\",\n\t\t\t\t\"Relaxed\": \"Спокойная\",\n\t\t\t\t\"Lazy\": \"Ленивая\",\n\t\t\t\"Show engine state\": \"Состояние движка\",\n\t\t\t\"List sent options\": \"Список отправленных опций\",\n\t\t\t\"Show error log\": \"Показать лог ошибок\",\n\t\t\t\"Hacks and kludges\": \"Хаки и костыли\",\n\t\t\t\t\"Allow arbitrary scripts\": \"Разрешить любые скрипты\",\n\t\t\t\t\"Accept any file size\": \"Принимать любой размер\",\n\t\t\t\t\"Allow stopped analysis\": \"Разрешить остановленный анализ\",\n\t\t\t\t\"Never hide focus buttons\": \"Не скрывать кнопки фокуса\",\n\t\t\t\t\"Never grayout move info\": \"Не затемнять инфо ходов\",\n\t\t\t\t\"Use lowerbound / upperbound info\": \"Использовать границы оценки\",\n\t\t\t\t\"Suppress ucinewgame\": \"Подавить ucinewgame\",\n\t\t\t\"Log RAM state to console\": \"Лог RAM в консоль\",\n\t\t\t\"Fire GC\": \"Запуск GC\",\n\t\t\t\"Logging\": \"Логирование\",\n\t\t\t\t\"Use logfile...\": \"Использовать лог-файл...\",\n\t\t\t\t\"Disable logging\": \"Отключить логирование\",\n\t\t\t\t\"Clear log when opening\": \"Очищать при открытии\",\n\t\t\t\t\"Use unique logfile each time\": \"Уникальный файл каждый раз\",\n\t\t\t\t\"Log illegal moves\": \"Лог невозможных ходов\",\n\t\t\t\t\"Log positions\": \"Лог позиций\",\n\t\t\t\t\"Log info lines\": \"Лог инфо-строк\",\n\t\t\t\t\"...including useless lines\": \"...включая бесполезные\",\n\t\t\"Language\": \"Язык\",\n\n\t\t\"RESTART_REQUIRED\": \"Требуется перезапуск программы\"\n\t},\n\n\t// UKRAINIAN ..................................................................................\n\n\t\"Українська\": {\n\t\t\"File\": \"Файл\",\n\t\t\t\"About\": \"Про програму\",\n\t\t\t\"New game\": \"Нова гра\",\n\t\t\t\"New 960 game\": \"Нова гра 960\",\n\t\t\t\"Open PGN...\": \"Відкрити PGN...\",\n\t\t\t\"Load FEN / PGN from clipboard\": \"Завантажити FEN / PGN з буфера\",\n\t\t\t\"Save this game...\": \"Зберегти гру...\",\n\t\t\t\"Write PGN to clipboard\": \"Копіювати PGN до буфера\",\n\t\t\t\"PGN saved statistics\": \"Статистика збереження PGN\",\n\t\t\t\t\"EV\": \"EV\",\n\t\t\t\t\"Centipawns\": \"Сентипішки\",\n\t\t\t\t\"N (%)\": \"N (%)\",\n\t\t\t\t\"N (absolute)\": \"N (абсолютне)\",\n\t\t\t\t\"...out of total\": \"...від загального\",\n\t\t\t\t\"Depth (A/B only)\": \"Глибина (тільки A/B)\",\n\t\t\t\"Cut\": \"Вирізати\",\n\t\t\t\"Copy\": \"Копіювати\",\n\t\t\t\"Paste\": \"Вставити\",\n\t\t\t\"Quit\": \"Вийти\",\n\t\t\"Tree\": \"Дерево\",\n\t\t\t\"Play engine choice\": \"Зіграти вибір двигуна\",\n\t\t\t\t\"1st\": \"1-й\",\n\t\t\t\t\"2nd\": \"2-й\",\n\t\t\t\t\"3rd\": \"3-й\",\n\t\t\t\t\"4th\": \"4-й\",\n\t\t\t\"Root\": \"Початок\",\n\t\t\t\"End\": \"Кінець\",\n\t\t\t\"Backward\": \"Назад\",\n\t\t\t\"Forward\": \"Вперед\",\n\t\t\t\"Previous sibling\": \"Попередній варіант\",\n\t\t\t\"Next sibling\": \"Наступний варіант\",\n\t\t\t\"Return to main line\": \"До головної лінії\",\n\t\t\t\"Promote line to main line\": \"Зробити лінію головною\",\n\t\t\t\"Promote line by 1 level\": \"Підвищити лінію на 1 рівень\",\n\t\t\t\"Delete node\": \"Видалити вузол\",\n\t\t\t\"Delete children\": \"Видалити дочірні\",\n\t\t\t\"Delete siblings\": \"Видалити сусідні\",\n\t\t\t\"Delete ALL other lines\": \"Видалити ВСІ інші лінії\",\n\t\t\t\"Show PGN games list\": \"Показати список партій PGN\",\n\t\t\t\"Escape\": \"Escape\",\n\t\t\"Analysis\": \"Аналіз\",\n\t\t\t\"Go\": \"Старт\",\n\t\t\t\"Go and lock engine\": \"Старт і заблокувати двигун\",\n\t\t\t\"Return to locked position\": \"До заблокованої позиції\",\n\t\t\t\"Halt\": \"Стоп\",\n\t\t\t\"Auto-evaluate line\": \"Автоаналіз лінії\",\n\t\t\t\"Auto-evaluate line, backwards\": \"Автоаналіз лінії назад\",\n\t\t\t\"Show focus (searchmoves) buttons\": \"Показати кнопки фокусу\",\n\t\t\t\"Clear focus\": \"Очистити фокус\",\n\t\t\t\"Invert focus\": \"Інвертувати фокус\",\n\t\t\t\"Winrate POV\": \"Шанси на перемогу від\",\n\t\t\t\t\"Current\": \"Поточний\",\n\t\t\t\t\"White\": \"Білі\",\n\t\t\t\t\"Black\": \"Чорні\",\n\t\t\t\"Centipawn POV\": \"Сентипішки від\",\n\t\t\t\"Win / draw / loss POV\": \"Вигр./ніч./прогр. від\",\n\t\t\t\"PV clicks\": \"Кліки PV\",\n\t\t\t\t\"Do nothing\": \"Нічого\",\n\t\t\t\t\"Go there\": \"Перейти\",\n\t\t\t\t\"Add to tree\": \"Додати до дерева\",\n\t\t\t\"Write infobox to clipboard\": \"Копіювати інфобокс\",\n\t\t\t\"Forget all analysis\": \"Забути весь аналіз\",\n\t\t\"Display\": \"Відображення\",\n\t\t\t\"Flip board\": \"Перевернути дошку\",\n\t\t\t\"Arrows\": \"Стрілки\",\n\t\t\t\"Piece-click spotlight\": \"Підсвітка ходів фігури\",\n\t\t\t\"Always show actual move (if known)\": \"Показувати зроблений хід\",\n\t\t\t\"...with unique colour\": \"...унікальним кольором\",\n\t\t\t\"...with outline\": \"...з контуром\",\n\t\t\t\"Arrowhead type\": \"Тип стрілок\",\n\t\t\t\t\"Winrate\": \"Шанс перемоги\",\n\t\t\t\t\"Node %\": \"Вузли %\",\n\t\t\t\t\"Policy\": \"Політика\",\n\t\t\t\t\"MultiPV rank\": \"Ранг MultiPV\",\n\t\t\t\t\"Moves Left Head\": \"Ходів до кінця\",\n\t\t\t\"Arrow filter (Lc0)\": \"Фільтр стрілок (Lc0)\",\n\t\t\t\t\"All moves\": \"Всі ходи\",\n\t\t\t\t\"Top move\": \"Найкращий хід\",\n\t\t\t\"Arrow filter (others)\": \"Фільтр стрілок (інші)\",\n\t\t\t\t\"Diff < 15%\": \"Різниця < 15%\",\n\t\t\t\t\"Diff < 10%\": \"Різниця < 10%\",\n\t\t\t\t\"Diff < 5%\": \"Різниця < 5%\",\n\t\t\t\"Infobox stats\": \"Статистика інфобоксу\",\n\t\t\t\t\"N - nodes (%)\": \"N - вузли (%)\",\n\t\t\t\t\"N - nodes (absolute)\": \"N - вузли (абс.)\",\n\t\t\t\t\"P - policy\": \"P - політика\",\n\t\t\t\t\"V - static evaluation\": \"V - статична оцінка\",\n\t\t\t\t\"Q - evaluation\": \"Q - оцінка\",\n\t\t\t\t\"U - uncertainty\": \"U - невизначеність\",\n\t\t\t\t\"S - search priority\": \"S - пріоритет пошуку\",\n\t\t\t\t\"M - moves left\": \"M - ходів до кінця\",\n\t\t\t\t\"WDL - win / draw / loss\": \"WDL - виграш/нічия/програш\",\n\t\t\t\t\"Linebreak before stats\": \"Перенос перед статистикою\",\n\t\t\t\"PV move numbers\": \"Номери ходів PV\",\n\t\t\t\"Online API\": \"Онлайн API\",\n\t\t\t\t\"None\": \"Немає\",\n\t\t\t\t\"ChessDB.cn evals\": \"Оцінки ChessDB.cn\",\n\t\t\t\t\"Lichess results (masters)\": \"Результати Lichess (майстри)\",\n\t\t\t\t\"Lichess results (plebs)\": \"Результати Lichess (гравці)\",\n\t\t\t\t\"Set Lichess API token\": \"Встановити токен API Lichess\",\n\t\t\t\"Allow API after move 25\": \"Дозволити API після 25 ходу\",\n\t\t\t\"Draw PV on mouseover\": \"Показувати PV при наведенні\",\n\t\t\t\"Draw PV method\": \"Метод показу PV\",\n\t\t\t\t\"Animate\": \"Анімація\",\n\t\t\t\t\"Single move\": \"Один хід\",\n\t\t\t\t\"Final position\": \"Кінцева позиція\",\n\t\t\t\"Pieces\": \"Фігури\",\n\t\t\t\t\"Choose pieces folder...\": \"Вибрати теку фігур...\",\n\t\t\t\t\"Default\": \"За замовчуванням\",\n\t\t\t\t\"About custom pieces\": \"Про власні фігури\",\n\t\t\t\"Background\": \"Фон\",\n\t\t\t\t\"Choose background image...\": \"Вибрати зображення фону...\",\n\t\t\t\"Book frequency arrows\": \"Стрілки частоти дебютів\",\n\t\t\t\"Lichess frequency arrows\": \"Стрілки частоти Lichess\",\n\t\t\"Sizes\": \"Розміри\",\n\t\t\t\"Infobox font\": \"Шрифт інфобоксу\",\n\t\t\t\"Move history font\": \"Шрифт історії ходів\",\n\t\t\t\"Board\": \"Дошка\",\n\t\t\t\t\"Giant\": \"Величезна\",\n\t\t\t\t\"Large\": \"Велика\",\n\t\t\t\t\"Medium\": \"Середня\",\n\t\t\t\t\"Small\": \"Мала\",\n\t\t\t\"Graph\": \"Графік\",\n\t\t\t\"Graph lines\": \"Лінії графіка\",\n\t\t\t\"I want other size options!\": \"Хочу інші розміри!\",\n\t\t\"Engine\": \"Двигун\",\n\t\t\t\"Choose engine...\": \"Вибрати двигун...\",\n\t\t\t\"Choose known engine...\": \"Вибрати відомий двигун...\",\n\t\t\t\"Weights\": \"Ваги\",\n\t\t\t\t\"Lc0 WeightsFile...\": \"Файл ваг Lc0...\",\n\t\t\t\t\"Stockfish EvalFile...\": \"Файл оцінки Stockfish...\",\n\t\t\t\t\"Set to <auto>\": \"Встановити <авто>\",\n\t\t\t\"Backend\": \"Бекенд\",\n\t\t\t\"Choose Syzygy path...\": \"Шлях до Syzygy...\",\n\t\t\t\"Unset\": \"Скинути\",\n\t\t\t\"Limit - normal\": \"Ліміт - звичайний\",\n\t\t\t\t\"Unlimited\": \"Без обмежень\",\n\t\t\t\t\"Up slightly\": \"Трохи більше\",\n\t\t\t\t\"Down slightly\": \"Трохи менше\",\n\t\t\t\"Limit - auto-eval / play\": \"Ліміт - автоаналіз/гра\",\n\t\t\t\"Limit by time instead of nodes\": \"Ліміт часу замість вузлів\",\n\t\t\t\"Threads\": \"Потоки\",\n\t\t\t\t\"Warning about threads\": \"Попередження про потоки\",\n\t\t\t\"Hash\": \"Хеш\",\n\t\t\t\t\"I want other hash options!\": \"Хочу інші опції хешу!\",\n\t\t\t\"MultiPV\": \"MultiPV\",\n\t\t\t\"Contempt Mode\": \"Режим зневаги\",\n\t\t\t\t\"White analysis\": \"Аналіз білих\",\n\t\t\t\t\"Black analysis\": \"Аналіз чорних\",\n\t\t\t\"Contempt\": \"Зневага\",\n\t\t\t\"WDL Calibration Elo\": \"WDL калібрування Elo\",\n\t\t\t\t\"Use default WDL\": \"Типовий WDL\",\n\t\t\t\"WDL Eval Objectivity\": \"WDL об'єктивність оцінки\",\n\t\t\t\t\"Yes\": \"Так\",\n\t\t\t\t\"No\": \"Ні\",\n\t\t\t\"Score Type\": \"Тип оцінки\",\n\t\t\t\"Custom scripts\": \"Власні скрипти\",\n\t\t\t\t\"How to add scripts\": \"Як додавати скрипти\",\n\t\t\t\t\"Show scripts folder\": \"Показати теку скриптів\",\n\t\t\t\"Restart engine\": \"Перезапустити двигун\",\n\t\t\t\"Soft engine reset\": \"М'який перезапуск двигуна\",\n\t\t\"Play\": \"Гра\",\n\t\t\t\"Play this colour\": \"Грати цим кольором\",\n\t\t\t\"Start self-play\": \"Почати самогру\",\n\t\t\t\"Use Polyglot book...\": \"Використати книгу Polyglot...\",\n\t\t\t\"Use PGN book...\": \"Використати книгу PGN...\",\n\t\t\t\"Unload book / abort load\": \"Вивантажити/скасувати книгу\",\n\t\t\t\"Book depth limit\": \"Ліміт глибини книги\",\n\t\t\t\"Temperature\": \"Температура\",\n\t\t\t\"Temp Decay Moves\": \"Спад температури ходів\",\n\t\t\t\t\"Infinite\": \"Нескінченно\",\n\t\t\t\"About play modes\": \"Про режими гри\",\n\t\t\"Dev\": \"Розробка\",\n\t\t\t\"Toggle Developer Tools\": \"Інструменти розробника\",\n\t\t\t\"Toggle Debug CSS\": \"Перемкнути Debug CSS\",\n\t\t\t\"Permanently enable save\": \"Постійно увімкнути збереження\",\n\t\t\t[show_config]: \"Показати config.json\",\n\t\t\t[show_engineconfig]: \"Показати engines.json\",\n\t\t\t[reload_engineconfig]: \"Перезавантажити engines.json\",\n\t\t\t\"Random move\": \"Випадковий хід\",\n\t\t\t\"Disable hardware acceleration for GUI\": \"Вимкнути апаратне прискорення\",\n\t\t\t\"Spin rate\": \"Частота оновлення\",\n\t\t\t\t\"Frenetic\": \"Шалена\",\n\t\t\t\t\"Fast\": \"Швидка\",\n\t\t\t\t\"Normal\": \"Нормальна\",\n\t\t\t\t\"Relaxed\": \"Розслаблена\",\n\t\t\t\t\"Lazy\": \"Лінива\",\n\t\t\t\"Show engine state\": \"Показати стан двигуна\",\n\t\t\t\"List sent options\": \"Список надісланих опцій\",\n\t\t\t\"Show error log\": \"Показати журнал помилок\",\n\t\t\t\"Hacks and kludges\": \"Хаки та костилі\",\n\t\t\t\t\"Allow arbitrary scripts\": \"Дозволити довільні скрипти\",\n\t\t\t\t\"Accept any file size\": \"Приймати будь-який розмір\",\n\t\t\t\t\"Allow stopped analysis\": \"Дозволити зупинений аналіз\",\n\t\t\t\t\"Never hide focus buttons\": \"Не ховати кнопки фокусу\",\n\t\t\t\t\"Never grayout move info\": \"Не затемняти інфо ходу\",\n\t\t\t\t\"Use lowerbound / upperbound info\": \"Використовувати межі\",\n\t\t\t\t\"Suppress ucinewgame\": \"Пропускати ucinewgame\",\n\t\t\t\"Log RAM state to console\": \"Логувати стан RAM\",\n\t\t\t\"Fire GC\": \"Запустити GC\",\n\t\t\t\"Logging\": \"Логування\",\n\t\t\t\t\"Use logfile...\": \"Використати лог-файл...\",\n\t\t\t\t\"Disable logging\": \"Вимкнути логування\",\n\t\t\t\t\"Clear log when opening\": \"Очищати лог при відкритті\",\n\t\t\t\t\"Use unique logfile each time\": \"Унікальний лог-файл щоразу\",\n\t\t\t\t\"Log illegal moves\": \"Логувати неправильні ходи\",\n\t\t\t\t\"Log positions\": \"Логувати позиції\",\n\t\t\t\t\"Log info lines\": \"Логувати інфо-рядки\",\n\t\t\t\t\"...including useless lines\": \"...включно з непотрібними\",\n\t\t\"Language\": \"Мова\",\n\n\t\t\"RESTART_REQUIRED\": \"Необхідно перезапустити GUI.\"\n\t},\n\n\t// BELARUSIAN .................................................................................\n\n\t\"Беларуская\": {\n\t\t\"File\": \"Файл\",\n\t\t\t\"About\": \"Пра праграму\",\n\t\t\t\"New game\": \"Новая гульня\",\n\t\t\t\"New 960 game\": \"Новая гульня 960\",\n\t\t\t\"Open PGN...\": \"Адкрыць PGN...\",\n\t\t\t\"Load FEN / PGN from clipboard\": \"Загрузіць FEN / PGN з буфера абмену\",\n\t\t\t\"Save this game...\": \"Захаваць гэтую гульню...\",\n\t\t\t\"Write PGN to clipboard\": \"Запісаць PGN у буфер абмену\",\n\t\t\t\"PGN saved statistics\": \"PGN захаваная статыстыка\",\n\t\t\t\t\"EV\": \"Ацэнка\",\n\t\t\t\t\"Centipawns\": \"Сантыпешкі\",\n\t\t\t\t\"N (%)\": \"N (%)\",\n\t\t\t\t\"N (absolute)\": \"N (абсалют)\",\n\t\t\t\t\"...out of total\": \"...з агульнай колькасці\",\n\t\t\t\t\"Depth (A/B only)\": \"Глыбіня (толькі A/B)\",\n\t\t\t\"Cut\": \"Выразаць\",\n\t\t\t\"Copy\": \"Капіяваць\",\n\t\t\t\"Paste\": \"Уставіць\",\n\t\t\t\"Quit\": \"Выйсці\",\n\t\t\"Tree\": \"Дрэва\",\n\t\t\t\"Play engine choice\": \"Згуляць выбар рухавіка\",\n\t\t\t\t\"1st\": \"1-ы\",\n\t\t\t\t\"2nd\": \"2-і\",\n\t\t\t\t\"3rd\": \"3-і\",\n\t\t\t\t\"4th\": \"4-ы\",\n\t\t\t\"Root\": \"Корань\",\n\t\t\t\"End\": \"Канец\",\n\t\t\t\"Backward\": \"Назад\",\n\t\t\t\"Forward\": \"Наперад\",\n\t\t\t\"Previous sibling\": \"Папярэдні брат\",\n\t\t\t\"Next sibling\": \"Наступны брат\",\n\t\t\t\"Return to main line\": \"Вярнуцца да галоўнай лініі\",\n\t\t\t\"Promote line to main line\": \"Зрабіць лінію галоўнай\",\n\t\t\t\"Promote line by 1 level\": \"Павысіць лінію на 1 узровень\",\n\t\t\t\"Delete node\": \"Выдаліць вузел\",\n\t\t\t\"Delete children\": \"Выдаліць дзяцей\",\n\t\t\t\"Delete siblings\": \"Выдаліць братоў\",\n\t\t\t\"Delete ALL other lines\": \"Выдаліць УСЕ іншыя лініі\",\n\t\t\t\"Show PGN games list\": \"Паказаць спіс гульняў PGN\",\n\t\t\t\"Escape\": \"Esc\",\n\t\t\"Analysis\": \"Аналіз\",\n\t\t\t\"Go\": \"Старт\",\n\t\t\t\"Go and lock engine\": \"Старт і зафіксаваць рухавік\",\n\t\t\t\"Return to locked position\": \"Вярнуцца да зафіксаванай пазіцыі\",\n\t\t\t\"Halt\": \"Спыніць\",\n\t\t\t\"Auto-evaluate line\": \"Аўтаматычна ацаніць лінію\",\n\t\t\t\"Auto-evaluate line, backwards\": \"Аўтаматычна ацаніць лінію, назад\",\n\t\t\t\"Show focus (searchmoves) buttons\": \"Паказаць кнопкі фокуса (хадоў пошуку)\",\n\t\t\t\"Clear focus\": \"Ачысціць фокус\",\n\t\t\t\"Invert focus\": \"Інвертаваць фокус\",\n\t\t\t\"Winrate POV\": \"Вінрэйт POV\",\n\t\t\t\t\"Current\": \"Бягучы\",\n\t\t\t\t\"White\": \"Белыя\",\n\t\t\t\t\"Black\": \"Чорныя\",\n\t\t\t\"Centipawn POV\": \"Сантыпешкі POV\",\n\t\t\t\"Win / draw / loss POV\": \"Перамога / нічыя / параза POV\",\n\t\t\t\"PV clicks\": \"Націсканні PV\",\n\t\t\t\t\"Do nothing\": \"Нічога не рабіць\",\n\t\t\t\t\"Go there\": \"Перайсці туды\",\n\t\t\t\t\"Add to tree\": \"Дадаць у дрэва\",\n\t\t\t\"Write infobox to clipboard\": \"Запісаць інфармацыйную панэль у буфер абмену\",\n\t\t\t\"Forget all analysis\": \"Забыць увесь аналіз\",\n\t\t\"Display\": \"Паказ\",\n\t\t\t\"Flip board\": \"Перавярнуць дошку\",\n\t\t\t\"Arrows\": \"Стрэлкі\",\n\t\t\t\"Piece-click spotlight\": \"Клік па фігуры - пражэктар\",\n\t\t\t\"Always show actual move (if known)\": \"Заўсёды паказваць фактычны ход (калі вядомы)\",\n\t\t\t\"...with unique colour\": \"...з унікальным колерам\",\n\t\t\t\"...with outline\": \"...з контурам\",\n\t\t\t\"Arrowhead type\": \"Тып наканечніка стрэлкі\",\n\t\t\t\t\"Winrate\": \"Вінрэйт\",\n\t\t\t\t\"Node %\": \"Вузел %\",\n\t\t\t\t\"Policy\": \"Палітыка\",\n\t\t\t\t\"MultiPV rank\": \"Ранг MultiPV\",\n\t\t\t\t\"Moves Left Head\": \"Хады да канца\",\n\t\t\t\"Arrow filter (Lc0)\": \"Фільтр стрэлак (Lc0)\",\n\t\t\t\t\"All moves\": \"Усе хады\",\n\t\t\t\t\"Top move\": \"Лепшы ход\",\n\t\t\t\"Arrow filter (others)\": \"Фільтр стрэлак (іншыя)\",\n\t\t\t\t\"Diff < 15%\": \"Розн. < 15%\",\n\t\t\t\t\"Diff < 10%\": \"Розн. < 10%\",\n\t\t\t\t\"Diff < 5%\": \"Розн. < 5%\",\n\t\t\t\"Infobox stats\": \"Статыстыка інфармацыйнай панэлі\",\n\t\t\t\t\"N - nodes (%)\": \"N - вузлы (%)\",\n\t\t\t\t\"N - nodes (absolute)\": \"N - вузлы (абсалют)\",\n\t\t\t\t\"P - policy\": \"P - палітыка\",\n\t\t\t\t\"V - static evaluation\": \"V - статычная ацэнка\",\n\t\t\t\t\"Q - evaluation\": \"Q - ацэнка\",\n\t\t\t\t\"U - uncertainty\": \"U - нявызначанасць\",\n\t\t\t\t\"S - search priority\": \"S - прыярытэт пошуку\",\n\t\t\t\t\"M - moves left\": \"M - хады да канца\",\n\t\t\t\t\"WDL - win / draw / loss\": \"WDL - перамога / нічыя / параза\",\n\t\t\t\t\"Linebreak before stats\": \"Перанос радка перад статыстыкай\",\n\t\t\t\"PV move numbers\": \"Нумары хадоў PV\",\n\t\t\t\"Online API\": \"Онлайн API\",\n\t\t\t\t\"None\": \"Няма\",\n\t\t\t\t\"ChessDB.cn evals\": \"Ацэнкі ChessDB.cn\",\n\t\t\t\t\"Lichess results (masters)\": \"Вынікі Lichess (майстры)\",\n\t\t\t\t\"Lichess results (plebs)\": \"Вынікі Lichess (звычайныя гульцы)\",\n\t\t\t\t\"Set Lichess API token\": \"Усталяваць токен API Lichess\",\n\t\t\t\"Allow API after move 25\": \"Дазволіць API пасля 25 ходу\",\n\t\t\t\"Draw PV on mouseover\": \"Маляваць PV пры навядзенні\",\n\t\t\t\"Draw PV method\": \"Метад малявання PV\",\n\t\t\t\t\"Animate\": \"Анімаваць\",\n\t\t\t\t\"Single move\": \"Адзін ход\",\n\t\t\t\t\"Final position\": \"Канчатковая пазіцыя\",\n\t\t\t\"Pieces\": \"Фігуры\",\n\t\t\t\t\"Choose pieces folder...\": \"Выбраць тэчку з фігурамі...\",\n\t\t\t\t\"Default\": \"Па змаўчанні\",\n\t\t\t\t\"About custom pieces\": \"Пра карыстальніцкія фігуры\",\n\t\t\t\"Background\": \"Фон\",\n\t\t\t\t\"Choose background image...\": \"Выбраць фонавую выяву...\",\n\t\t\t\"Book frequency arrows\": \"Стрэлкі частаты кнігі\",\n\t\t\t\"Lichess frequency arrows\": \"Стрэлкі частаты Lichess\",\n\t\t\"Sizes\": \"Памеры\",\n\t\t\t\"Infobox font\": \"Шрыфт інфармацыйнай панэлі\",\n\t\t\t\"Move history font\": \"Шрыфт гісторыі хадоў\",\n\t\t\t\"Board\": \"Дошка\",\n\t\t\t\t\"Giant\": \"Гіганцкая\",\n\t\t\t\t\"Large\": \"Вялікая\",\n\t\t\t\t\"Medium\": \"Сярэдняя\",\n\t\t\t\t\"Small\": \"Малая\",\n\t\t\t\"Graph\": \"Графік\",\n\t\t\t\"Graph lines\": \"Лініі графіка\",\n\t\t\t\"I want other size options!\": \"Я хачу іншыя варыянты памеру!\",\n\t\t\"Engine\": \"Рухавік\",\n\t\t\t\"Choose engine...\": \"Выбраць рухавік...\",\n\t\t\t\"Choose known engine...\": \"Выбраць вядомы рухавік...\",\n\t\t\t\"Weights\": \"Вагі\",\n\t\t\t\t\"Lc0 WeightsFile...\": \"Файл вагаў Lc0...\",\n\t\t\t\t\"Stockfish EvalFile...\": \"Файл ацэнкі Stockfish...\",\n\t\t\t\t\"Set to <auto>\": \"Устанавіць <аўто>\",\n\t\t\t\"Backend\": \"Бэкэнд\",\n\t\t\t\"Choose Syzygy path...\": \"Выбраць шлях Syzygy...\",\n\t\t\t\"Unset\": \"Не зададзена\",\n\t\t\t\"Limit - normal\": \"Ліміт - звычайны\",\n\t\t\t\t\"Unlimited\": \"Неабмежавана\",\n\t\t\t\t\"Up slightly\": \"Злёгку ўверх\",\n\t\t\t\t\"Down slightly\": \"Злёгку ўніз\",\n\t\t\t\"Limit - auto-eval / play\": \"Ліміт - аўтаацэнка / гульня\",\n\t\t\t\"Limit by time instead of nodes\": \"Лімітаваць па часе, а не па вузлах\",\n\t\t\t\"Threads\": \"Патокі\",\n\t\t\t\t\"Warning about threads\": \"Папярэджанне пра патокі\",\n\t\t\t\"Hash\": \"Хэш\",\n\t\t\t\t\"I want other hash options!\": \"Я хачу іншыя варыянты хэшу!\",\n\t\t\t\"MultiPV\": \"MultiPV\",\n\t\t\t\"Contempt Mode\": \"Рэжым пагарды\",\n\t\t\t\t\"White analysis\": \"Аналіз белых\",\n\t\t\t\t\"Black analysis\": \"Аналіз чорных\",\n\t\t\t\"Contempt\": \"Пагарда\",\n\t\t\t\"WDL Calibration Elo\": \"WDL Каліброўка Эла\",\n\t\t\t\t\"Use default WDL\": \"Выкарыстоўваць WDL па змаўчанні\",\n\t\t\t\"WDL Eval Objectivity\": \"WDL Аб'ектыўнасць ацэнкі\",\n\t\t\t\t\"Yes\": \"Так\",\n\t\t\t\t\"No\": \"Не\",\n\t\t\t\"Score Type\": \"Тып ліку\",\n\t\t\t\"Custom scripts\": \"Карыстальніцкія скрыпты\",\n\t\t\t\t\"How to add scripts\": \"Як дадаць скрыпты\",\n\t\t\t\t\"Show scripts folder\": \"Паказаць тэчку са скрыптамі\",\n\t\t\t\"Restart engine\": \"Перазапусціць рухавік\",\n\t\t\t\"Soft engine reset\": \"Мяккі скід рухавіка\",\n\t\t\"Play\": \"Гуляць\",\n\t\t\t\"Play this colour\": \"Гуляць гэтым колерам\",\n\t\t\t\"Start self-play\": \"Пачаць самагульню\",\n\t\t\t\"Use Polyglot book...\": \"Выкарыстоўваць кнігу Polyglot...\",\n\t\t\t\"Use PGN book...\": \"Выкарыстоўваць кнігу PGN...\",\n\t\t\t\"Unload book / abort load\": \"Выгрузіць кнігу / адмяніць загрузку\",\n\t\t\t\"Book depth limit\": \"Ліміт глыбіні кнігі\",\n\t\t\t\"Temperature\": \"Тэмпература\",\n\t\t\t\"Temp Decay Moves\": \"Хады згасання тэмпературы\",\n\t\t\t\t\"Infinite\": \"Бясконца\",\n\t\t\t\"About play modes\": \"Пра рэжымы гульні\",\n\t\t\"Dev\": \"Распрацоўка\",\n\t\t\t\"Toggle Developer Tools\": \"Пераключыць інструменты распрацоўніка\",\n\t\t\t\"Toggle Debug CSS\": \"Пераключыць CSS для адладкі\",\n\t\t\t\"Permanently enable save\": \"Заўсёды дазволіць захаванне\",\n\t\t\t[show_config]: \"Паказаць config.json\",\n\t\t\t[show_engineconfig]: \"Паказаць engines.json\",\n\t\t\t[reload_engineconfig]: \"Перазагрузіць engines.json (і перазапусціць рухавік)\",\n\t\t\t\"Random move\": \"Выпадковы ход\",\n\t\t\t\"Disable hardware acceleration for GUI\": \"Адключыць апаратнае паскарэнне для GUI\",\n\t\t\t\"Spin rate\": \"Хуткасць пракруткі\",\n\t\t\t\t\"Frenetic\": \"Шалёны\",\n\t\t\t\t\"Fast\": \"Хуткі\",\n\t\t\t\t\"Normal\": \"Звычайны\",\n\t\t\t\t\"Relaxed\": \"Спакойны\",\n\t\t\t\t\"Lazy\": \"Лянівы\",\n\t\t\t\"Show engine state\": \"Паказаць стан рухавіка\",\n\t\t\t\"List sent options\": \"Спіс адпраўленых опцый\",\n\t\t\t\"Show error log\": \"Паказаць часопіс памылак\",\n\t\t\t\"Hacks and kludges\": \"Хакі і хітрыкі\",\n\t\t\t\t\"Allow arbitrary scripts\": \"Дазволіць адвольны скрыпты\",\n\t\t\t\t\"Accept any file size\": \"Прымаць любы памер файла\",\n\t\t\t\t\"Allow stopped analysis\": \"Дазволіць спынены аналіз\",\n\t\t\t\t\"Never hide focus buttons\": \"Ніколі не хаваць кнопкі фокуса\",\n\t\t\t\t\"Never grayout move info\": \"Ніколі не рабіць інфармацыю пра ход шэрай\",\n\t\t\t\t\"Use lowerbound / upperbound info\": \"Выкарыстоўваць інфармацыю пра ніжнюю / верхнюю мяжу\",\n\t\t\t\t\"Suppress ucinewgame\": \"Падавіць ucinewgame\",\n\t\t\t\"Log RAM state to console\": \"Запісаць стан RAM у кансоль\",\n\t\t\t\"Fire GC\": \"Запусціць GC\",\n\t\t\t\"Logging\": \"Вядзенне часопіса\",\n\t\t\t\t\"Use logfile...\": \"Выкарыстоўваць файл часопіса...\",\n\t\t\t\t\"Disable logging\": \"Адключыць вядзенне часопіса\",\n\t\t\t\t\"Clear log when opening\": \"Ачысціць часопіс пры адкрыцці\",\n\t\t\t\t\"Use unique logfile each time\": \"Выкарыстоўваць унікальны файл часопіса кожны раз\",\n\t\t\t\t\"Log illegal moves\": \"Запісваць нелегальныя хады\",\n\t\t\t\t\"Log positions\": \"Запісваць пазіцыі\",\n\t\t\t\t\"Log info lines\": \"Запісваць інфармацыйныя радкі\",\n\t\t\t\t\"...including useless lines\": \"...у тым ліку бескарысныя радкі\",\n\t\t\"Language\": \"Мова\",\n\n\t\t\"RESTART_REQUIRED\": \"Праграму трэба перазапусціць.\",\n\t},\n\n\t// TRADITIONAL CHINESE ........................................................................\n\n\t\"繁體中文\": {\n\t\t\"File\": \"檔案\",\n\t\t\t\"About\": \"關於\",\n\t\t\t\"New game\": \"新棋局\",\n\t\t\t\"New 960 game\": \"新 960 棋局\",\n\t\t\t\"Open PGN...\": \"打開 PGN…\",\n\t\t\t\"Load FEN / PGN from clipboard\": \"從剪貼簿載入 FEN / PGN\",\n\t\t\t\"Save this game...\": \"儲存此棋局…\",\n\t\t\t\"Write PGN to clipboard\": \"將 PGN 寫入剪貼簿\",\n\t\t\t\"PGN saved statistics\": \"PGN 儲存統計\",\n\t\t\t\t\"EV\": \"評估值\",\n\t\t\t\t\"Centipawns\": \"百分子\",\n\t\t\t\t\"N (%)\": \"節點數 (%)\",\n\t\t\t\t\"N (absolute)\": \"節點數 (絕對值)\",\n\t\t\t\t\"...out of total\": \"…總節點數\",\n\t\t\t\t\"Depth (A/B only)\": \"深度 (僅 A/B)\",\n\t\t\t\"Cut\": \"剪下\",\n\t\t\t\"Copy\": \"複製\",\n\t\t\t\"Paste\": \"貼上\",\n\t\t\t\"Quit\": \"退出\",\n\t\t\"Tree\": \"樹狀結構\",\n\t\t\t\"Play engine choice\": \"執行引擎選擇\",\n\t\t\t\t\"1st\": \"第一\",\n\t\t\t\t\"2nd\": \"第二\",\n\t\t\t\t\"3rd\": \"第三\",\n\t\t\t\t\"4th\": \"第四\",\n\t\t\t\"Root\": \"根節點\",\n\t\t\t\"End\": \"末梢節點\",\n\t\t\t\"Backward\": \"向後\",\n\t\t\t\"Forward\": \"向前\",\n\t\t\t\"Previous sibling\": \"前一兄弟節點\",\n\t\t\t\"Next sibling\": \"下一兄弟節點\",\n\t\t\t\"Return to main line\": \"返回主變化\",\n\t\t\t\"Promote line to main line\": \"將分支升為主變化\",\n\t\t\t\"Promote line by 1 level\": \"升級分支 1 級\",\n\t\t\t\"Delete node\": \"刪除節點\",\n\t\t\t\"Delete children\": \"刪除子節點\",\n\t\t\t\"Delete siblings\": \"刪除兄弟節點\",\n\t\t\t\"Delete ALL other lines\": \"刪除所有其他變化\",\n\t\t\t\"Show PGN games list\": \"顯示 PGN 棋局列表\",\n\t\t\t\"Escape\": \"取消\",\n\t\t\"Analysis\": \"分析\",\n\t\t\t\"Go\": \"執行\",\n\t\t\t\"Go and lock engine\": \"執行並鎖定引擎\",\n\t\t\t\"Return to locked position\": \"返回鎖定位置\",\n\t\t\t\"Halt\": \"停止\",\n\t\t\t\"Auto-evaluate line\": \"自動評估變化\",\n\t\t\t\"Auto-evaluate line, backwards\": \"自動反向評估變化\",\n\t\t\t\"Show focus (searchmoves) buttons\": \"顯示焦點（搜尋走法）按鈕\",\n\t\t\t\"Clear focus\": \"清除焦點\",\n\t\t\t\"Invert focus\": \"反轉焦點\",\n\t\t\t\"Winrate POV\": \"勝率視角\",\n\t\t\t\t\"Current\": \"當前\",\n\t\t\t\t\"White\": \"白方\",\n\t\t\t\t\"Black\": \"黑方\",\n\t\t\t\"Centipawn POV\": \"百分子視角\",\n\t\t\t\"Win / draw / loss POV\": \"勝/和/負視角\",\n\t\t\t\"PV clicks\": \"PV 點擊\",\n\t\t\t\t\"Do nothing\": \"不執行任何操作\",\n\t\t\t\t\"Go there\": \"移動到此處\",\n\t\t\t\t\"Add to tree\": \"加入樹結構\",\n\t\t\t\"Write infobox to clipboard\": \"將信息框寫入剪貼簿\",\n\t\t\t\"Forget all analysis\": \"忘記所有分析\",\n\t\t\"Display\": \"顯示\",\n\t\t\t\"Flip board\": \"翻轉棋盤\",\n\t\t\t\"Arrows\": \"箭頭\",\n\t\t\t\"Piece-click spotlight\": \"棋子點擊高亮\",\n\t\t\t\"Always show actual move (if known)\": \"始終顯示實際走法（如果已知）\",\n\t\t\t\"...with unique colour\": \"…使用唯一顏色\",\n\t\t\t\"...with outline\": \"…加上外框\",\n\t\t\t\"Arrowhead type\": \"箭頭類型\",\n\t\t\t\t\"Winrate\": \"勝率\",\n\t\t\t\t\"Node %\": \"節點 %\",\n\t\t\t\t\"Policy\": \"策略值\",\n\t\t\t\t\"MultiPV rank\": \"多 PV 排名\",\n\t\t\t\t\"Moves Left Head\": \"剩餘走法\",\n\t\t\t\"Arrow filter (Lc0)\": \"箭頭過濾器（Lc0）\",\n\t\t\t\t\"All moves\": \"所有走法\",\n\t\t\t\t\"Top move\": \"最佳走法\",\n\t\t\t\"Arrow filter (others)\": \"箭頭過濾器（其他）\",\n\t\t\t\t\"Diff < 15%\": \"差異 < 15%\",\n\t\t\t\t\"Diff < 10%\": \"差異 < 10%\",\n\t\t\t\t\"Diff < 5%\": \"差異 < 5%\",\n\t\t\t\"Infobox stats\": \"信息框統計\",\n\t\t\t\t\"N - nodes (%)\": \"N - 節點數 (%)\",\n\t\t\t\t\"N - nodes (absolute)\": \"N - 節點數 (絕對值)\",\n\t\t\t\t\"P - policy\": \"P - 策略值\",\n\t\t\t\t\"V - static evaluation\": \"V - 靜態評估\",\n\t\t\t\t\"Q - evaluation\": \"Q - 評估值\",\n\t\t\t\t\"U - uncertainty\": \"U - 不確定性\",\n\t\t\t\t\"S - search priority\": \"S - 搜索優先級\",\n\t\t\t\t\"M - moves left\": \"M - 剩餘走法\",\n\t\t\t\t\"WDL - win / draw / loss\": \"WDL - 勝 / 和 / 負\",\n\t\t\t\t\"Linebreak before stats\": \"統計前換行\",\n\t\t\t\"PV move numbers\": \"PV 走法編號\",\n\t\t\t\"Online API\": \"線上 API\",\n\t\t\t\t\"None\": \"無\",\n\t\t\t\t\"ChessDB.cn evals\": \"ChessDB.cn 評估\",\n\t\t\t\t\"Lichess results (masters)\": \"Lichess 結果（高手）\",\n\t\t\t\t\"Lichess results (plebs)\": \"Lichess 結果（普通玩家）\",\n\t\t\t\t\"Set Lichess API token\": \"設定 Lichess API 權杖\",\n\t\t\t\"Allow API after move 25\": \"允許第 25 步後使用 API\",\n\t\t\t\"Draw PV on mouseover\": \"滑鼠懸停顯示 PV\",\n\t\t\t\"Draw PV method\": \"PV 顯示方式\",\n\t\t\t\t\"Animate\": \"動畫化\",\n\t\t\t\t\"Single move\": \"單步\",\n\t\t\t\t\"Final position\": \"終局\",\n\t\t\t\"Pieces\": \"棋子\",\n\t\t\t\t\"Choose pieces folder...\": \"選擇棋子資料夾…\",\n\t\t\t\t\"Default\": \"預設\",\n\t\t\t\t\"About custom pieces\": \"關於自定義棋子\",\n\t\t\t\"Background\": \"背景\",\n\t\t\t\t\"Choose background image...\": \"選擇背景圖片…\",\n\t\t\t\"Book frequency arrows\": \"開局書頻率箭頭\",\n\t\t\t\"Lichess frequency arrows\": \"Lichess 頻率箭頭\",\n\t\t\"Sizes\": \"大小\",\n\t\t\t\"Infobox font\": \"信息框字體\",\n\t\t\t\"Move history font\": \"走法歷史字體\",\n\t\t\t\"Board\": \"棋盤\",\n\t\t\t\t\"Giant\": \"巨大\",\n\t\t\t\t\"Large\": \"大\",\n\t\t\t\t\"Medium\": \"中\",\n\t\t\t\t\"Small\": \"小\",\n\t\t\t\"Graph\": \"圖表\",\n\t\t\t\"Graph lines\": \"圖表線條\",\n\t\t\t\"I want other size options!\": \"我想要其他大小選項！\",\n\t\t\"Engine\": \"引擎\",\n\t\t\t\"Choose engine...\": \"選擇引擎…\",\n\t\t\t\"Choose known engine...\": \"選擇已知引擎…\",\n\t\t\t\"Weights\": \"權重檔案\",\n\t\t\t\t\"Lc0 WeightsFile...\": \"Lc0 權重檔案…\",\n\t\t\t\t\"Stockfish EvalFile...\": \"Stockfish 評估檔案…\",\n\t\t\t\t\"Set to <auto>\": \"設定為 <自動>\",\n\t\t\t\"Backend\": \"後端\",\n\t\t\t\"Choose Syzygy path...\": \"選擇 Syzygy 路徑…\",\n\t\t\t\"Unset\": \"取消設定\",\n\t\t\t\"Limit - normal\": \"限制 - 正常\",\n\t\t\t\t\"Unlimited\": \"無限制\",\n\t\t\t\t\"Up slightly\": \"稍微增加\",\n\t\t\t\t\"Down slightly\": \"稍微減少\",\n\t\t\t\"Limit - auto-eval / play\": \"限制 - 自動評估/遊戲\",\n\t\t\t\"Limit by time instead of nodes\": \"以時間而非節點限制\",\n\t\t\t\"Threads\": \"執行緒\",\n\t\t\t\t\"Warning about threads\": \"執行緒警告\",\n\t\t\t\"Hash\": \"雜湊\",\n\t\t\t\t\"I want other hash options!\": \"我想要其他雜湊選項！\",\n\t\t\t\"MultiPV\": \"多 PV\",\n\t\t\t\"Contempt Mode\": \"偏見模式\",\n\t\t\t\t\"White analysis\": \"白方分析\",\n\t\t\t\t\"Black analysis\": \"黑方分析\",\n\t\t\t\"Contempt\": \"偏見值\",\n\t\t\t\"WDL Calibration Elo\": \"WDL 校準 Elo\",\n\t\t\t\t\"Use default WDL\": \"使用預設 WDL\",\n\t\t\t\"WDL Eval Objectivity\": \"WDL 評估客觀性\",\n\t\t\t\t\"Yes\": \"是\",\n\t\t\t\t\"No\": \"否\",\n\t\t\t\"Score Type\": \"分數類型\",\n\t\t\t\"Custom scripts\": \"自定義腳本\",\n\t\t\t\t\"How to add scripts\": \"如何添加腳本\",\n\t\t\t\t\"Show scripts folder\": \"顯示腳本資料夾\",\n\t\t\t\"Restart engine\": \"重啟引擎\",\n\t\t\t\"Soft engine reset\": \"軟重置引擎\",\n\t\t\"Play\": \"對弈\",\n\t\t\t\"Play this colour\": \"用此顏色對弈\",\n\t\t\t\"Start self-play\": \"開始自我對弈\",\n\t\t\t\"Use Polyglot book...\": \"使用 Polyglot 開局書…\",\n\t\t\t\"Use PGN book...\": \"使用 PGN 開局書…\",\n\t\t\t\"Unload book / abort load\": \"卸載開局書/中止載入\",\n\t\t\t\"Book depth limit\": \"開局書深度限制\",\n\t\t\t\"Temperature\": \"溫度\",\n\t\t\t\"Temp Decay Moves\": \"溫度遞減步數\",\n\t\t\t\t\"Infinite\": \"無限\",\n\t\t\t\"About play modes\": \"關於對弈模式\",\n\t\t\"Dev\": \"開發\",\n\t\t\t\"Toggle Developer Tools\": \"切換開發者工具\",\n\t\t\t\"Toggle Debug CSS\": \"切換除錯 CSS\",\n\t\t\t\"Permanently enable save\": \"永久啟用儲存\",\n\t\t\t[show_config]: \"顯示 config.json\",\n\t\t\t[show_engineconfig]: \"顯示 engines.json\",\n\t\t\t[reload_engineconfig]: \"重新載入 engines.json（並重啟引擎）\",\n\t\t\t\"Random move\": \"隨機走法\",\n\t\t\t\"Disable hardware acceleration for GUI\": \"禁用 GUI 硬體加速\",\n\t\t\t\"Spin rate\": \"旋轉速度\",\n\t\t\t\t\"Frenetic\": \"極快\",\n\t\t\t\t\"Fast\": \"快\",\n\t\t\t\t\"Normal\": \"正常\",\n\t\t\t\t\"Relaxed\": \"放鬆\",\n\t\t\t\t\"Lazy\": \"懶散\",\n\t\t\t\"Show engine state\": \"顯示引擎狀態\",\n\t\t\t\"List sent options\": \"列出已發送選項\",\n\t\t\t\"Show error log\": \"顯示錯誤日誌\",\n\t\t\t\"Hacks and kludges\": \"進階設定\",\n\t\t\t\t\"Allow arbitrary scripts\": \"允許任意腳本\",\n\t\t\t\t\"Accept any file size\": \"接受任意檔案大小\",\n\t\t\t\t\"Allow stopped analysis\": \"允許停止的分析\",\n\t\t\t\t\"Never hide focus buttons\": \"永不隱藏焦點按鈕\",\n\t\t\t\t\"Never grayout move info\": \"永不灰顯走法信息\",\n\t\t\t\t\"Use lowerbound / upperbound info\": \"使用下限/上限信息\",\n\t\t\t\t\"Suppress ucinewgame\": \"抑制 ucinewgame\",\n\t\t\t\"Log RAM state to console\": \"記錄 RAM 狀態至控制台\",\n\t\t\t\"Fire GC\": \"啟動垃圾回收\",\n\t\t\t\"Logging\": \"記錄\",\n\t\t\t\t\"Use logfile...\": \"使用日誌檔案…\",\n\t\t\t\t\"Disable logging\": \"禁用記錄\",\n\t\t\t\t\"Clear log when opening\": \"打開時清除日誌\",\n\t\t\t\t\"Use unique logfile each time\": \"每次使用唯一日誌檔案\",\n\t\t\t\t\"Log illegal moves\": \"記錄非法走法\",\n\t\t\t\t\"Log positions\": \"記錄位置\",\n\t\t\t\t\"Log info lines\": \"記錄信息行\",\n\t\t\t\t\"...including useless lines\": \"…包括無用行\",\n\t\t\"Language\": \"語言\",\n\n\t\t\"RESTART_REQUIRED\": \"需要關閉並重新起動程式\"\n\t},\n\n\t// SIMPLIFIED CHINESE .........................................................................\n\n\t\"简体中文\": {\n\t\t\"File\": \"文件\",\n\t\t\t\"About\": \"关于\",\n\t\t\t\"New game\": \"新棋局\",\n\t\t\t\"New 960 game\": \"新 960 棋局\",\n\t\t\t\"Open PGN...\": \"打开 PGN...\",\n\t\t\t\"Load FEN / PGN from clipboard\": \"从剪贴板加载 FEN / PGN\",\n\t\t\t\"Save this game...\": \"保存此棋局...\",\n\t\t\t\"Write PGN to clipboard\": \"将 PGN 写入剪贴板\",\n\t\t\t\"PGN saved statistics\": \"PGN 保存统计\",\n\t\t\t\t\"EV\": \"评估值\",\n\t\t\t\t\"Centipawns\": \"百分子\",\n\t\t\t\t\"N (%)\": \"节点数 (%)\",\n\t\t\t\t\"N (absolute)\": \"节点数 (绝对值)\",\n\t\t\t\t\"...out of total\": \"...总节点数\",\n\t\t\t\t\"Depth (A/B only)\": \"深度 (仅 A/B)\",\n\t\t\t\"Cut\": \"剪切\",\n\t\t\t\"Copy\": \"复制\",\n\t\t\t\"Paste\": \"粘贴\",\n\t\t\t\"Quit\": \"退出\",\n\t\t\"Tree\": \"树状结构\",\n\t\t\t\"Play engine choice\": \"执行引擎走法\",\n\t\t\t\t\"1st\": \"第一\",\n\t\t\t\t\"2nd\": \"第二\",\n\t\t\t\t\"3rd\": \"第三\",\n\t\t\t\t\"4th\": \"第四\",\n\t\t\t\"Root\": \"根节点\",\n\t\t\t\"End\": \"末梢节点\",\n\t\t\t\"Backward\": \"向后\",\n\t\t\t\"Forward\": \"向前\",\n\t\t\t\"Previous sibling\": \"前一兄弟节点\",\n\t\t\t\"Next sibling\": \"下一兄弟节点\",\n\t\t\t\"Return to main line\": \"返回主变化\",\n\t\t\t\"Promote line to main line\": \"将分支升为主变化\",\n\t\t\t\"Promote line by 1 level\": \"升级分支 1 级\",\n\t\t\t\"Delete node\": \"删除节点\",\n\t\t\t\"Delete children\": \"删除子节点\",\n\t\t\t\"Delete siblings\": \"删除兄弟节点\",\n\t\t\t\"Delete ALL other lines\": \"删除所有其他变化\",\n\t\t\t\"Show PGN games list\": \"显示 PGN 棋局列表\",\n\t\t\t\"Escape\": \"取消\",\n\t\t\"Analysis\": \"分析\",\n\t\t\t\"Go\": \"执行\",\n\t\t\t\"Go and lock engine\": \"执行并锁定引擎\",\n\t\t\t\"Return to locked position\": \"返回锁定位置\",\n\t\t\t\"Halt\": \"停止\",\n\t\t\t\"Auto-evaluate line\": \"自动评估变化\",\n\t\t\t\"Auto-evaluate line, backwards\": \"自动反向评估变化\",\n\t\t\t\"Show focus (searchmoves) buttons\": \"显示焦点（搜索走法）按钮\",\n\t\t\t\"Clear focus\": \"清除焦点\",\n\t\t\t\"Invert focus\": \"反转焦点\",\n\t\t\t\"Winrate POV\": \"胜率视角\",\n\t\t\t\t\"Current\": \"当前\",\n\t\t\t\t\"White\": \"白方\",\n\t\t\t\t\"Black\": \"黑方\",\n\t\t\t\"Centipawn POV\": \"百分子视角\",\n\t\t\t\"Win / draw / loss POV\": \"胜/和/负视角\",\n\t\t\t\"PV clicks\": \"PV 点击\",\n\t\t\t\t\"Do nothing\": \"不执行任何操作\",\n\t\t\t\t\"Go there\": \"移动到此处\",\n\t\t\t\t\"Add to tree\": \"加入树结构\",\n\t\t\t\"Write infobox to clipboard\": \"将信息框写入剪贴板\",\n\t\t\t\"Forget all analysis\": \"忘记所有分析\",\n\t\t\"Display\": \"显示\",\n\t\t\t\"Flip board\": \"翻转棋盘\",\n\t\t\t\"Arrows\": \"箭头\",\n\t\t\t\"Piece-click spotlight\": \"棋子点击高亮\",\n\t\t\t\"Always show actual move (if known)\": \"始终显示实际走法（如果已知）\",\n\t\t\t\"...with unique colour\": \"...使用唯一颜色\",\n\t\t\t\"...with outline\": \"...加上外框\",\n\t\t\t\"Arrowhead type\": \"箭头类型\",\n\t\t\t\t\"Winrate\": \"胜率\",\n\t\t\t\t\"Node %\": \"节点 %\",\n\t\t\t\t\"Policy\": \"策略值\",\n\t\t\t\t\"MultiPV rank\": \"多 PV 排名\",\n\t\t\t\t\"Moves Left Head\": \"剩余走法\",\n\t\t\t\"Arrow filter (Lc0)\": \"箭头过滤器（Lc0）\",\n\t\t\t\t\"All moves\": \"所有走法\",\n\t\t\t\t\"Top move\": \"最佳走法\",\n\t\t\t\"Arrow filter (others)\": \"箭头过滤器（其他）\",\n\t\t\t\t\"Diff < 15%\": \"差异 < 15%\",\n\t\t\t\t\"Diff < 10%\": \"差异 < 10%\",\n\t\t\t\t\"Diff < 5%\": \"差异 < 5%\",\n\t\t\t\"Infobox stats\": \"信息框统计\",\n\t\t\t\t\"N - nodes (%)\": \"N - 节点数 (%)\",\n\t\t\t\t\"N - nodes (absolute)\": \"N - 节点数 (绝对值)\",\n\t\t\t\t\"P - policy\": \"P - 策略值\",\n\t\t\t\t\"V - static evaluation\": \"V - 静态评估\",\n\t\t\t\t\"Q - evaluation\": \"Q - 评估值\",\n\t\t\t\t\"U - uncertainty\": \"U - 不确定性\",\n\t\t\t\t\"S - search priority\": \"S - 搜索优先级\",\n\t\t\t\t\"M - moves left\": \"M - 剩余走法\",\n\t\t\t\t\"WDL - win / draw / loss\": \"WDL - 胜 / 和 / 负\",\n\t\t\t\t\"Linebreak before stats\": \"统计前换行\",\n\t\t\t\"PV move numbers\": \"PV 走法编号\",\n\t\t\t\"Online API\": \"在线 API\",\n\t\t\t\t\"None\": \"无\",\n\t\t\t\t\"ChessDB.cn evals\": \"ChessDB.cn 评估\",\n\t\t\t\t\"Lichess results (masters)\": \"Lichess 结果（大师）\",\n\t\t\t\t\"Lichess results (plebs)\": \"Lichess 结果（普通玩家）\",\n\t\t\t\t\"Set Lichess API token\": \"设置 Lichess API 令牌\",\n\t\t\t\"Allow API after move 25\": \"允许第 25 步后使用 API\",\n\t\t\t\"Draw PV on mouseover\": \"鼠标悬停显示 PV\",\n\t\t\t\"Draw PV method\": \"PV 显示方式\",\n\t\t\t\t\"Animate\": \"动画化\",\n\t\t\t\t\"Single move\": \"单步\",\n\t\t\t\t\"Final position\": \"终局\",\n\t\t\t\"Pieces\": \"棋子\",\n\t\t\t\t\"Choose pieces folder...\": \"选择棋子文件夹...\",\n\t\t\t\t\"Default\": \"默认\",\n\t\t\t\t\"About custom pieces\": \"关于自定义棋子\",\n\t\t\t\"Background\": \"背景\",\n\t\t\t\t\"Choose background image...\": \"选择背景图片...\",\n\t\t\t\"Book frequency arrows\": \"开局书频率箭头\",\n\t\t\t\"Lichess frequency arrows\": \"Lichess 频率箭头\",\n\t\t\"Sizes\": \"大小\",\n\t\t\t\"Infobox font\": \"信息框字体\",\n\t\t\t\"Move history font\": \"走法历史字体\",\n\t\t\t\"Board\": \"棋盘\",\n\t\t\t\t\"Giant\": \"巨大\",\n\t\t\t\t\"Large\": \"大\",\n\t\t\t\t\"Medium\": \"中\",\n\t\t\t\t\"Small\": \"小\",\n\t\t\t\"Graph\": \"图表\",\n\t\t\t\"Graph lines\": \"图表线条\",\n\t\t\t\"I want other size options!\": \"我想要其他大小选项！\",\n\t\t\"Engine\": \"引擎\",\n\t\t\t\"Choose engine...\": \"选择引擎...\",\n\t\t\t\"Choose known engine...\": \"选择已知引擎...\",\n\t\t\t\"Weights\": \"权重文件\",\n\t\t\t\t\"Lc0 WeightsFile...\": \"Lc0 权重文件...\",\n\t\t\t\t\"Stockfish EvalFile...\": \"Stockfish 评估文件...\",\n\t\t\t\t\"Set to <auto>\": \"设置为 <自动>\",\n\t\t\t\"Backend\": \"后端\",\n\t\t\t\"Choose Syzygy path...\": \"选择 Syzygy 路径...\",\n\t\t\t\"Unset\": \"取消设置\",\n\t\t\t\"Limit - normal\": \"限制 - 正常\",\n\t\t\t\t\"Unlimited\": \"无限制\",\n\t\t\t\t\"Up slightly\": \"稍微增加\",\n\t\t\t\t\"Down slightly\": \"稍微减少\",\n\t\t\t\"Limit - auto-eval / play\": \"限制 - 自动评估/游戏\",\n\t\t\t\"Limit by time instead of nodes\": \"以时间而非节点限制\",\n\t\t\t\"Threads\": \"线程\",\n\t\t\t\t\"Warning about threads\": \"线程警告\",\n\t\t\t\"Hash\": \"哈希\",\n\t\t\t\t\"I want other hash options!\": \"我想要其他哈希选项！\",\n\t\t\t\"MultiPV\": \"多 PV\",\n\t\t\t\"Contempt Mode\": \"偏见模式\",\n\t\t\t\t\"White analysis\": \"白方分析\",\n\t\t\t\t\"Black analysis\": \"黑方分析\",\n\t\t\t\"Contempt\": \"偏见值\",\n\t\t\t\"WDL Calibration Elo\": \"WDL 校准 Elo\",\n\t\t\t\t\"Use default WDL\": \"使用默认 WDL\",\n\t\t\t\"WDL Eval Objectivity\": \"WDL 评估客观性\",\n\t\t\t\t\"Yes\": \"是\",\n\t\t\t\t\"No\": \"否\",\n\t\t\t\"Score Type\": \"分数类型\",\n\t\t\t\"Custom scripts\": \"自定义脚本\",\n\t\t\t\t\"How to add scripts\": \"如何添加脚本\",\n\t\t\t\t\"Show scripts folder\": \"显示脚本文件夹\",\n\t\t\t\"Restart engine\": \"重启引擎\",\n\t\t\t\"Soft engine reset\": \"软重置引擎\",\n\t\t\"Play\": \"对弈\",\n\t\t\t\"Play this colour\": \"用此颜色对弈\",\n\t\t\t\"Start self-play\": \"开始自我对弈\",\n\t\t\t\"Use Polyglot book...\": \"使用 Polyglot 开局书...\",\n\t\t\t\"Use PGN book...\": \"使用 PGN 开局书...\",\n\t\t\t\"Unload book / abort load\": \"卸载开局书 / 中止加载\",\n\t\t\t\"Book depth limit\": \"开局书深度限制\",\n\t\t\t\"Temperature\": \"温度\",\n\t\t\t\"Temp Decay Moves\": \"温度递减步数\",\n\t\t\t\t\"Infinite\": \"无限\",\n\t\t\t\"About play modes\": \"关于对弈模式\",\n\t\t\"Dev\": \"开发\",\n\t\t\t\"Toggle Developer Tools\": \"切换开发者工具\",\n\t\t\t\"Toggle Debug CSS\": \"切换调试 CSS\",\n\t\t\t\"Permanently enable save\": \"永久启用保存\",\n\t\t\t[show_config]: \"显示 config.json\",\n\t\t\t[show_engineconfig]: \"显示 engines.json\",\n\t\t\t[reload_engineconfig]: \"重新加载 engines.json（并重启引擎）\",\n\t\t\t\"Random move\": \"随机走法\",\n\t\t\t\"Disable hardware acceleration for GUI\": \"禁用 GUI 硬件加速\",\n\t\t\t\"Spin rate\": \"旋转速度\",\n\t\t\t\t\"Frenetic\": \"极快\",\n\t\t\t\t\"Fast\": \"快\",\n\t\t\t\t\"Normal\": \"正常\",\n\t\t\t\t\"Relaxed\": \"放松\",\n\t\t\t\t\"Lazy\": \"懒散\",\n\t\t\t\"Show engine state\": \"显示引擎状态\",\n\t\t\t\"List sent options\": \"列出已发送选项\",\n\t\t\t\"Show error log\": \"显示错误日志\",\n\t\t\t\"Hacks and kludges\": \"高级设置\",\n\t\t\t\t\"Allow arbitrary scripts\": \"允许任意脚本\",\n\t\t\t\t\"Accept any file size\": \"接受任意文件大小\",\n\t\t\t\t\"Allow stopped analysis\": \"允许停止的分析\",\n\t\t\t\t\"Never hide focus buttons\": \"永不隐藏焦点按钮\",\n\t\t\t\t\"Never grayout move info\": \"永不灰显走法信息\",\n\t\t\t\t\"Use lowerbound / upperbound info\": \"使用下限/上限信息\",\n\t\t\t\t\"Suppress ucinewgame\": \"抑制 ucinewgame\",\n\t\t\t\"Log RAM state to console\": \"记录 RAM 状态至控制台\",\n\t\t\t\"Fire GC\": \"执行垃圾回收\",\n\t\t\t\"Logging\": \"日志记录\",\n\t\t\t\t\"Use logfile...\": \"使用日志文件...\",\n\t\t\t\t\"Disable logging\": \"禁用日志记录\",\n\t\t\t\t\"Clear log when opening\": \"打开时清除日志\",\n\t\t\t\t\"Use unique logfile each time\": \"每次使用唯一日志文件\",\n\t\t\t\t\"Log illegal moves\": \"记录非法走法\",\n\t\t\t\t\"Log positions\": \"记录位置\",\n\t\t\t\t\"Log info lines\": \"记录信息行\",\n\t\t\t\t\"...including useless lines\": \"...包括无用行\",\n\t\t\"Language\": \"语言\",\n\n\t\t\"RESTART_REQUIRED\": \"需要关闭并重新启动程序\"\n\t},\n\n\t// HINDI ......................................................................................\n\n\t\"हिंदी\": {\n\t\t\"File\": \"फ़ाइल\",\n\t\t\t\"About\": \"परिचय\",\n\t\t\t\"New game\": \"नया खेल\",\n\t\t\t\"New 960 game\": \"नया 960 खेल\",\n\t\t\t\"Open PGN...\": \"PGN खोलें...\",\n\t\t\t\"Load FEN / PGN from clipboard\": \"क्लिपबोर्ड से FEN / PGN लोड करें\",\n\t\t\t\"Save this game...\": \"यह खेल सहेजें...\",\n\t\t\t\"Write PGN to clipboard\": \"PGN को क्लिपबोर्ड में लिखें\",\n\t\t\t\"PGN saved statistics\": \"PGN सहेजे गए आंकड़े\",\n\t\t\t\t\"EV\": \"EV\",\n\t\t\t\t\"Centipawns\": \"Centipawns\",\n\t\t\t\t\"N (%)\": \"N (%)\",\n\t\t\t\t\"N (absolute)\": \"N (पूर्ण)\",\n\t\t\t\t\"...out of total\": \"कुल में से\",\n\t\t\t\t\"Depth (A/B only)\": \"Depth (A/B केवल)\",\n\t\t\t\"Cut\": \"काटें\",\n\t\t\t\"Copy\": \"कॉपी करें\",\n\t\t\t\"Paste\": \"पेस्ट करें\",\n\t\t\t\"Quit\": \"बंद करें\",\n\t\t\"Tree\": \"ट्री\",\n\t\t\t\"Play engine choice\": \"इंजन की पसंद खेलें\",\n\t\t\t\t\"1st\": \"पहला\",\n\t\t\t\t\"2nd\": \"दूसरा\",\n\t\t\t\t\"3rd\": \"तीसरा\",\n\t\t\t\t\"4th\": \"चौथा\",\n\t\t\t\"Root\": \"मूल\",\n\t\t\t\"End\": \"अंत\",\n\t\t\t\"Backward\": \"पीछे\",\n\t\t\t\"Forward\": \"आगे\",\n\t\t\t\"Previous sibling\": \"पिछला सिबलिंग\",\n\t\t\t\"Next sibling\": \"अगला सिबलिंग\",\n\t\t\t\"Return to main line\": \"मुख्य लाइन पर वापस जाएं\",\n\t\t\t\"Promote line to main line\": \"लाइन को मुख्य लाइन बनाएं\",\n\t\t\t\"Promote line by 1 level\": \"लाइन को 1 स्तर बढ़ाएं\",\n\t\t\t\"Delete node\": \"नोड हटाएं\",\n\t\t\t\"Delete children\": \"चिल्ड्रन हटाएं\",\n\t\t\t\"Delete siblings\": \"सिबलिंग्स हटाएं\",\n\t\t\t\"Delete ALL other lines\": \"सभी अन्य लाइनें हटाएं\",\n\t\t\t\"Show PGN games list\": \"PGN खेलों की सूची दिखाएं\",\n\t\t\t\"Escape\": \"एस्केप\",\n\t\t\"Analysis\": \"विश्लेषण\",\n\t\t\t\"Go\": \"शुरू करें\",\n\t\t\t\"Go and lock engine\": \"इंजन को लॉक करें और शुरू करें\",\n\t\t\t\"Return to locked position\": \"लॉक की गई स्थिति पर वापस जाएं\",\n\t\t\t\"Halt\": \"रोकें\",\n\t\t\t\"Auto-evaluate line\": \"लाइन का स्वतः मूल्यांकन करें\",\n\t\t\t\"Auto-evaluate line, backwards\": \"लाइन का पीछे से स्वतः मूल्यांकन करें\",\n\t\t\t\"Show focus (searchmoves) buttons\": \"फोकस (searchmoves) बटन दिखाएं\",\n\t\t\t\"Clear focus\": \"फोकस हटाएं\",\n\t\t\t\"Invert focus\": \"फोकस उलटें\",\n\t\t\t\"Winrate POV\": \"जीत दर POV\",\n\t\t\t\t\"Current\": \"वर्तमान\",\n\t\t\t\t\"White\": \"सफेद\",\n\t\t\t\t\"Black\": \"काला\",\n\t\t\t\"Centipawn POV\": \"Centipawn POV\",\n\t\t\t\"Win / draw / loss POV\": \"जीत / ड्रॉ / हार POV\",\n\t\t\t\"PV clicks\": \"PV क्लिक\",\n\t\t\t\t\"Do nothing\": \"कुछ न करें\",\n\t\t\t\t\"Go there\": \"वहां जाएं\",\n\t\t\t\t\"Add to tree\": \"ट्री में जोड़ें\",\n\t\t\t\"Write infobox to clipboard\": \"जानकारी बॉक्स को क्लिपबोर्ड में लिखें\",\n\t\t\t\"Forget all analysis\": \"सभी विश्लेषण भूल जाएं\",\n\t\t\"Display\": \"प्रदर्शन\",\n\t\t\t\"Flip board\": \"बोर्ड पलटें\",\n\t\t\t\"Arrows\": \"तीर\",\n\t\t\t\"Piece-click spotlight\": \"मोहरा-क्लिक स्पॉटलाइट\",\n\t\t\t\"Always show actual move (if known)\": \"वास्तविक चाल हमेशा दिखाएं (यदि ज्ञात हो)\",\n\t\t\t\"...with unique colour\": \"...विशिष्ट रंग के साथ\",\n\t\t\t\"...with outline\": \"...आउटलाइन के साथ\",\n\t\t\t\"Arrowhead type\": \"तीर का प्रकार\",\n\t\t\t\t\"Winrate\": \"जीत दर\",\n\t\t\t\t\"Node %\": \"Node %\",\n\t\t\t\t\"Policy\": \"Policy\",\n\t\t\t\t\"MultiPV rank\": \"MultiPV रैंक\",\n\t\t\t\t\"Moves Left Head\": \"शेष चालें Head\",\n\t\t\t\"Arrow filter (Lc0)\": \"तीर फ़िल्टर (Lc0)\",\n\t\t\t\t\"All moves\": \"सभी चालें\",\n\t\t\t\t\"Top move\": \"शीर्ष चाल\",\n\t\t\t\"Arrow filter (others)\": \"तीर फ़िल्टर (अन्य)\",\n\t\t\t\t\"Diff < 15%\": \"अंतर < 15%\",\n\t\t\t\t\"Diff < 10%\": \"अंतर < 10%\",\n\t\t\t\t\"Diff < 5%\": \"अंतर < 5%\",\n\t\t\t\"Infobox stats\": \"जानकारी बॉक्स आंकड़े\",\n\t\t\t\t\"N - nodes (%)\": \"N - nodes (%)\",\n\t\t\t\t\"N - nodes (absolute)\": \"N - nodes (पूर्ण)\",\n\t\t\t\t\"P - policy\": \"P - policy\",\n\t\t\t\t\"V - static evaluation\": \"V - static evaluation\",\n\t\t\t\t\"Q - evaluation\": \"Q - evaluation\",\n\t\t\t\t\"U - uncertainty\": \"U - uncertainty\",\n\t\t\t\t\"S - search priority\": \"S - search priority\",\n\t\t\t\t\"M - moves left\": \"M - शेष चालें\",\n\t\t\t\t\"WDL - win / draw / loss\": \"WDL - जीत / ड्रॉ / हार\",\n\t\t\t\t\"Linebreak before stats\": \"आंकड़ों से पहले लाइनब्रेक\",\n\t\t\t\"PV move numbers\": \"PV चाल संख्या\",\n\t\t\t\"Online API\": \"ऑनलाइन API\",\n\t\t\t\t\"None\": \"कोई नहीं\",\n\t\t\t\t\"ChessDB.cn evals\": \"ChessDB.cn evals\",\n\t\t\t\t\"Lichess results (masters)\": \"Lichess परिणाम (मास्टर्स)\",\n\t\t\t\t\"Lichess results (plebs)\": \"Lichess परिणाम (सामान्य)\",\n\t\t\t\t\"Set Lichess API token\": \"Lichess API टोकन सेट करें\",\n\t\t\t\"Allow API after move 25\": \"25वीं चाल के बाद API की अनुमति दें\",\n\t\t\t\"Draw PV on mouseover\": \"माउसओवर पर PV दिखाएं\",\n\t\t\t\"Draw PV method\": \"PV दिखाने का तरीका\",\n\t\t\t\t\"Animate\": \"एनिमेट करें\",\n\t\t\t\t\"Single move\": \"एक चाल\",\n\t\t\t\t\"Final position\": \"अंतिम स्थिति\",\n\t\t\t\"Pieces\": \"मोहरे\",\n\t\t\t\t\"Choose pieces folder...\": \"मोहरों की फ़ोल्डर चुनें...\",\n\t\t\t\t\"Default\": \"डिफ़ॉल्ट\",\n\t\t\t\t\"About custom pieces\": \"कस्टम मोहरों के बारे में\",\n\t\t\t\"Background\": \"पृष्ठभूमि\",\n\t\t\t\t\"Choose background image...\": \"पृष्ठभूमि छवि चुनें...\",\n\t\t\t\"Book frequency arrows\": \"बुक फ्रीक्वेंसी तीर\",\n\t\t\t\"Lichess frequency arrows\": \"Lichess फ्रीक्वेंसी तीर\",\n\t\t\"Sizes\": \"आकार\",\n\t\t\t\"Infobox font\": \"जानकारी बॉक्स फ़ॉन्ट\",\n\t\t\t\"Move history font\": \"चाल इतिहास फ़ॉन्ट\",\n\t\t\t\"Board\": \"बोर्ड\",\n\t\t\t\t\"Giant\": \"विशाल\",\n\t\t\t\t\"Large\": \"बड़ा\",\n\t\t\t\t\"Medium\": \"मध्यम\",\n\t\t\t\t\"Small\": \"छोटा\",\n\t\t\t\"Graph\": \"ग्राफ़\",\n\t\t\t\"Graph lines\": \"ग्राफ़ रेखाएं\",\n\t\t\t\"I want other size options!\": \"मुझे अन्य आकार विकल्प चाहिए!\",\n\t\t\"Engine\": \"इंजन\",\n\t\t\t\"Choose engine...\": \"इंजन चुनें...\",\n\t\t\t\"Choose known engine...\": \"ज्ञात इंजन चुनें...\",\n\t\t\t\"Weights\": \"वेट्स\",\n\t\t\t\t\"Lc0 WeightsFile...\": \"Lc0 WeightsFile...\",\n\t\t\t\t\"Stockfish EvalFile...\": \"Stockfish EvalFile...\",\n\t\t\t\t\"Set to <auto>\": \"<auto> पर सेट करें\",\n\t\t\t\"Backend\": \"बैकएंड\",\n\t\t\t\"Choose Syzygy path...\": \"Syzygy पथ चुनें...\",\n\t\t\t\"Unset\": \"अनसेट करें\",\n\t\t\t\"Limit - normal\": \"सीमा - सामान्य\",\n\t\t\t\t\"Unlimited\": \"असीमित\",\n\t\t\t\t\"Up slightly\": \"थोड़ा ऊपर\",\n\t\t\t\t\"Down slightly\": \"थोड़ा नीचे\",\n\t\t\t\"Limit - auto-eval / play\": \"सीमा - स्वतः-मूल्यांकन / खेल\",\n\t\t\t\"Limit by time instead of nodes\": \"नोड्स के बजाय समय से सीमित करें\",\n\t\t\t\"Threads\": \"थ्रेड्स\",\n\t\t\t\t\"Warning about threads\": \"थ्रेड्स के बारे में चेतावनी\",\n\t\t\t\"Hash\": \"हैश\",\n\t\t\t\t\"I want other hash options!\": \"मुझे अन्य हैश विकल्प चाहिए!\",\n\t\t\t\"MultiPV\": \"MultiPV\",\n\t\t\t\"Contempt Mode\": \"तिरस्कार मोड\",\n\t\t\t\t\"White analysis\": \"सफेद विश्लेषण\",\n\t\t\t\t\"Black analysis\": \"काला विश्लेषण\",\n\t\t\t\"Contempt\": \"तिरस्कार\",\n\t\t\t\"WDL Calibration Elo\": \"WDL कैलिब्रेशन Elo\",\n\t\t\t\t\"Use default WDL\": \"डिफ़ॉल्ट WDL का उपयोग करें\",\n\t\t\t\"WDL Eval Objectivity\": \"WDL Eval वस्तुनिष्ठता\",\n\t\t\t\t\"Yes\": \"हाँ\",\n\t\t\t\t\"No\": \"नहीं\",\n\t\t\t\"Score Type\": \"स्कोर प्रकार\",\n\t\t\t\"Custom scripts\": \"कस्टम स्क्रिप्ट\",\n\t\t\t\t\"How to add scripts\": \"स्क्रिप्ट कैसे जोड़ें\",\n\t\t\t\t\"Show scripts folder\": \"स्क्रिप्ट फ़ोल्डर दिखाएं\",\n\t\t\t\"Restart engine\": \"इंजन पुनः प्रारंभ करें\",\n\t\t\t\"Soft engine reset\": \"सॉफ्ट इंजन रीसेट\",\n\t\t\"Play\": \"खेलें\",\n\t\t\t\"Play this colour\": \"इस रंग से खेलें\",\n\t\t\t\"Start self-play\": \"सेल्फ-प्ले शुरू करें\",\n\t\t\t\"Use Polyglot book...\": \"Polyglot बुक का उपयोग करें...\",\n\t\t\t\"Use PGN book...\": \"PGN बुक का उपयोग करें...\",\n\t\t\t\"Unload book / abort load\": \"बुक अनलोड करें / लोड रोकें\",\n\t\t\t\"Book depth limit\": \"बुक गहराई सीमा\",\n\t\t\t\"Temperature\": \"तापमान\",\n\t\t\t\"Temp Decay Moves\": \"Temp Decay Moves\",\n\t\t\t\t\"Infinite\": \"अनंत\",\n\t\t\t\"About play modes\": \"खेल मोड के बारे में\",\n\t\t\"Dev\": \"डेव\",\n\t\t\t\"Toggle Developer Tools\": \"डेवलपर टूल्स टॉगल करें\",\n\t\t\t\"Toggle Debug CSS\": \"डीबग CSS टॉगल करें\",\n\t\t\t\"Permanently enable save\": \"सहेजना स्थायी रूप से सक्षम करें\",\n\t\t\t[show_config]: \"config.json दिखाएं\",\n\t\t\t[show_engineconfig]: \"engines.json दिखाएं\",\n\t\t\t[reload_engineconfig]: \"engines.json पुनः लोड करें (और इंजन पुनः प्रारंभ करें)\",\n\t\t\t\"Random move\": \"रैंडम चाल\",\n\t\t\t\"Disable hardware acceleration for GUI\": \"GUI के लिए हार्डवेयर एक्सेलरेशन अक्षम करें\",\n\t\t\t\"Spin rate\": \"स्पिन दर\",\n\t\t\t\t\"Frenetic\": \"उन्मत्त\",\n\t\t\t\t\"Fast\": \"तेज़\",\n\t\t\t\t\"Normal\": \"सामान्य\",\n\t\t\t\t\"Relaxed\": \"आराम से\",\n\t\t\t\t\"Lazy\": \"आलसी\",\n\t\t\t\"Show engine state\": \"इंजन स्थिति दिखाएं\",\n\t\t\t\"List sent options\": \"भेजे गए विकल्प सूचीबद्ध करें\",\n\t\t\t\"Show error log\": \"त्रुटि लॉग दिखाएं\",\n\t\t\t\"Hacks and kludges\": \"हैक्स और क्लज\",\n\t\t\t\t\"Allow arbitrary scripts\": \"मनमाने स्क्रिप्ट की अनुमति दें\",\n\t\t\t\t\"Accept any file size\": \"किसी भी फ़ाइल आकार को स्वीकार करें\",\n\t\t\t\t\"Allow stopped analysis\": \"रुका हुआ विश्लेषण अनुमत करें\",\n\t\t\t\t\"Never hide focus buttons\": \"फोकस बटन कभी न छिपाएं\",\n\t\t\t\t\"Never grayout move info\": \"चाल की जानकारी कभी धूमिल न करें\",\n\t\t\t\t\"Use lowerbound / upperbound info\": \"लोअरबाउंड / अपरबाउंड जानकारी का उपयोग करें\",\n\t\t\t\t\"Suppress ucinewgame\": \"ucinewgame दबाएं\",\n\t\t\t\"Log RAM state to console\": \"RAM स्थिति को कंसोल में लॉग करें\",\n\t\t\t\"Fire GC\": \"GC चलाएं\",\n\t\t\t\"Logging\": \"लॉगिंग\",\n\t\t\t\t\"Use logfile...\": \"लॉगफ़ाइल का उपयोग करें...\",\n\t\t\t\t\"Disable logging\": \"लॉगिंग अक्षम करें\",\n\t\t\t\t\"Clear log when opening\": \"खोलते समय लॉग साफ़ करें\",\n\t\t\t\t\"Use unique logfile each time\": \"हर बार अलग लॉगफ़ाइल का उपयोग करें\",\n\t\t\t\t\"Log illegal moves\": \"अवैध चालें लॉग करें\",\n\t\t\t\t\"Log positions\": \"स्थितियां लॉग करें\",\n\t\t\t\t\"Log info lines\": \"जानकारी पंक्तियां लॉग करें\",\n\t\t\t\t\"...including useless lines\": \"...बेकार पंक्तियों सहित\",\n\t\t\"Language\": \"भाषा\",\n\n\t\t\"RESTART_REQUIRED\": \"GUI को अब पुनः प्रारंभ किया जाना चाहिए।\"\n\t},\n\n\t// BENGALI ....................................................................................\n\n\t\"বাংলা\": {\n\t\t\"File\": \"ফাইল\",\n\t\t\t\"About\": \"সম্পর্কে\",\n\t\t\t\"New game\": \"নতুন খেলা\",\n\t\t\t\"New 960 game\": \"নতুন ৯৬০ খেলা\",\n\t\t\t\"Open PGN...\": \"PGN খুলুন...\",\n\t\t\t\"Load FEN / PGN from clipboard\": \"ক্লিপবোর্ড থেকে FEN / PGN লোড করুন\",\n\t\t\t\"Save this game...\": \"এই খেলাটি সংরক্ষণ করুন...\",\n\t\t\t\"Write PGN to clipboard\": \"ক্লিপবোর্ডে PGN লিখুন\",\n\t\t\t\"PGN saved statistics\": \"সংরক্ষিত PGN পরিসংখ্যান\",\n\t\t\t\t\"EV\": \"EV\",\n\t\t\t\t\"Centipawns\": \"সেন্টিপন\",\n\t\t\t\t\"N (%)\": \"N (%)\",\n\t\t\t\t\"N (absolute)\": \"N (পূর্ণ)\",\n\t\t\t\t\"...out of total\": \"মোট থেকে...\",\n\t\t\t\t\"Depth (A/B only)\": \"গভীরতা (শুধু A/B)\",\n\t\t\t\"Cut\": \"কাট\",\n\t\t\t\"Copy\": \"কপি\",\n\t\t\t\"Paste\": \"পেস্ট\",\n\t\t\t\"Quit\": \"প্রস্থান\",\n\t\t\"Tree\": \"গেমট্রি\",\n\t\t\t\"Play engine choice\": \"ইঞ্জিন পছন্দ চালান\",\n\t\t\t\t\"1st\": \"১ম\",\n\t\t\t\t\"2nd\": \"২য়\",\n\t\t\t\t\"3rd\": \"৩য়\",\n\t\t\t\t\"4th\": \"৪র্থ\",\n\t\t\t\"Root\": \"মূল\",\n\t\t\t\"End\": \"শেষ\",\n\t\t\t\"Backward\": \"পিছনে\",\n\t\t\t\"Forward\": \"সামনে\",\n\t\t\t\"Previous sibling\": \"পূর্ববর্তী শাখা\",\n\t\t\t\"Next sibling\": \"পরবর্তী শাখা\",\n\t\t\t\"Return to main line\": \"মূল লাইনে ফিরুন\",\n\t\t\t\"Promote line to main line\": \"লাইনকে মূল লাইনে উন্নীত করুন\",\n\t\t\t\"Promote line by 1 level\": \"লাইনকে ১ স্তর উন্নীত করুন\",\n\t\t\t\"Delete node\": \"নোড মুছুন\",\n\t\t\t\"Delete children\": \"চাইল্ড মুছুন\",\n\t\t\t\"Delete siblings\": \"সিবলিং মুছুন\",\n\t\t\t\"Delete ALL other lines\": \"অন্য সব লাইন মুছুন\",\n\t\t\t\"Show PGN games list\": \"PGN খেলার তালিকা দেখান\",\n\t\t\t\"Escape\": \"প্রস্থান\",\n\t\t\"Analysis\": \"বিশ্লেষণ\",\n\t\t\t\"Go\": \"শুরু\",\n\t\t\t\"Go and lock engine\": \"ইঞ্জিন লক করে শুরু করুন\",\n\t\t\t\"Return to locked position\": \"লক করা অবস্থানে ফিরুন\",\n\t\t\t\"Halt\": \"থামুন\",\n\t\t\t\"Auto-evaluate line\": \"স্বয়ংক্রিয় লাইন মূল্যায়ন\",\n\t\t\t\"Auto-evaluate line, backwards\": \"স্বয়ংক্রিয় লাইন মূল্যায়ন, পিছনে\",\n\t\t\t\"Show focus (searchmoves) buttons\": \"ফোকাস (সার্চমুভস) বাটন দেখান\",\n\t\t\t\"Clear focus\": \"ফোকাস মুছুন\",\n\t\t\t\"Invert focus\": \"ফোকাস উল্টান\",\n\t\t\t\"Winrate POV\": \"জয়হার দৃষ্টিকোণ\",\n\t\t\t\t\"Current\": \"বর্তমান\",\n\t\t\t\t\"White\": \"সাদা\",\n\t\t\t\t\"Black\": \"কালো\",\n\t\t\t\"Centipawn POV\": \"সেন্টিপন দৃষ্টিকোণ\",\n\t\t\t\"Win / draw / loss POV\": \"জয় / ড্র / হার দৃষ্টিকোণ\",\n\t\t\t\"PV clicks\": \"PV ক্লিক\",\n\t\t\t\t\"Do nothing\": \"কিছু করবেন না\",\n\t\t\t\t\"Go there\": \"সেখানে যান\",\n\t\t\t\t\"Add to tree\": \"ট্রিতে যোগ করুন\",\n\t\t\t\"Write infobox to clipboard\": \"তথ্যবাক্স ক্লিপবোর্ডে লিখুন\",\n\t\t\t\"Forget all analysis\": \"সব বিশ্লেষণ ভুলে যান\",\n\t\t\"Display\": \"প্রদর্শন করুন\",\n\t\t\t\"Flip board\": \"বোর্ড উল্টান\",\n\t\t\t\"Arrows\": \"তীর\",\n\t\t\t\"Piece-click spotlight\": \"পিস-ক্লিক স্পটলাইট\",\n\t\t\t\"Always show actual move (if known)\": \"প্রকৃত চাল দেখান (জানা থাকলে)\",\n\t\t\t\"...with unique colour\": \"...বিশেষ রঙের সাথে\",\n\t\t\t\"...with outline\": \"...আউটলাইন সহ\",\n\t\t\t\"Arrowhead type\": \"তীরের মাথার ধরন\",\n\t\t\t\t\"Winrate\": \"জয়হার\",\n\t\t\t\t\"Node %\": \"নোড %\",\n\t\t\t\t\"Policy\": \"পলিসি\",\n\t\t\t\t\"MultiPV rank\": \"MultiPV র‍্যাঙ্ক\",\n\t\t\t\t\"Moves Left Head\": \"বাকি চাল\",\n\t\t\t\"Arrow filter (Lc0)\": \"তীর ফিল্টার (Lc0)\",\n\t\t\t\t\"All moves\": \"সব চাল\",\n\t\t\t\t\"Top move\": \"সেরা চাল\",\n\t\t\t\"Arrow filter (others)\": \"তীর ফিল্টার (অন্যান্য)\",\n\t\t\t\t\"Diff < 15%\": \"পার্থক্য < ১৫%\",\n\t\t\t\t\"Diff < 10%\": \"পার্থক্য < ১০%\",\n\t\t\t\t\"Diff < 5%\": \"পার্থক্য < ৫%\",\n\t\t\t\"Infobox stats\": \"তথ্যবাক্স পরিসংখ্যান\",\n\t\t\t\t\"N - nodes (%)\": \"N - নোড (%)\",\n\t\t\t\t\"N - nodes (absolute)\": \"N - নোড (পূর্ণ)\",\n\t\t\t\t\"P - policy\": \"P - পলিসি\",\n\t\t\t\t\"V - static evaluation\": \"V - স্থির মূল্যায়ন\",\n\t\t\t\t\"Q - evaluation\": \"Q - মূল্যায়ন\",\n\t\t\t\t\"U - uncertainty\": \"U - অনিশ্চয়তা\",\n\t\t\t\t\"S - search priority\": \"S - অনুসন্ধান অগ্রাধিকার\",\n\t\t\t\t\"M - moves left\": \"M - বাকি চাল\",\n\t\t\t\t\"WDL - win / draw / loss\": \"WDL - জয় / ড্র / হার\",\n\t\t\t\t\"Linebreak before stats\": \"পরিসংখ্যানের আগে লাইনব্রেক\",\n\t\t\t\"PV move numbers\": \"PV চাল সংখ্যা\",\n\t\t\t\"Online API\": \"অনলাইন API\",\n\t\t\t\t\"None\": \"কোনটি নয়\",\n\t\t\t\t\"ChessDB.cn evals\": \"ChessDB.cn মূল্যায়ন\",\n\t\t\t\t\"Lichess results (masters)\": \"Lichess ফলাফল (মাস্টার্স)\",\n\t\t\t\t\"Lichess results (plebs)\": \"Lichess ফলাফল (সাধারণ)\",\n\t\t\t\t\"Set Lichess API token\": \"Lichess API টোকেন সেট করুন\",\n\t\t\t\"Allow API after move 25\": \"২৫তম চালের পর API অনুমতি দিন\",\n\t\t\t\"Draw PV on mouseover\": \"মাউস হোভারে PV আঁকুন\",\n\t\t\t\"Draw PV method\": \"PV আঁকার পদ্ধতি\",\n\t\t\t\t\"Animate\": \"অ্যানিমেট\",\n\t\t\t\t\"Single move\": \"একক চাল\",\n\t\t\t\t\"Final position\": \"চূড়ান্ত অবস্থান\",\n\t\t\t\"Pieces\": \"পিস\",\n\t\t\t\t\"Choose pieces folder...\": \"পিস ফোল্ডার বাছুন...\",\n\t\t\t\t\"Default\": \"ডিফল্ট\",\n\t\t\t\t\"About custom pieces\": \"কাস্টম পিস সম্পর্কে\",\n\t\t\t\"Background\": \"পটভূমি\",\n\t\t\t\t\"Choose background image...\": \"পটভূমি ছবি বাছুন...\",\n\t\t\t\"Book frequency arrows\": \"বুক ফ্রিকোয়েন্সি তীর\",\n\t\t\t\"Lichess frequency arrows\": \"Lichess ফ্রিকোয়েন্সি তীর\",\n\t\t\"Sizes\": \"আকার\",\n\t\t\t\"Infobox font\": \"তথ্যবাক্স ফন্ট\",\n\t\t\t\"Move history font\": \"চাল ইতিহাস ফন্ট\",\n\t\t\t\"Board\": \"বোর্ড\",\n\t\t\t\t\"Giant\": \"বিশাল\",\n\t\t\t\t\"Large\": \"বড়\",\n\t\t\t\t\"Medium\": \"মাঝারি\",\n\t\t\t\t\"Small\": \"ছোট\",\n\t\t\t\"Graph\": \"গ্রাফ\",\n\t\t\t\"Graph lines\": \"গ্রাফ লাইন\",\n\t\t\t\"I want other size options!\": \"আমি অন্য আকারের অপশন চাই!\",\n\t\t\"Engine\": \"ইঞ্জিন\",\n\t\t\t\"Choose engine...\": \"ইঞ্জিন বাছুন...\",\n\t\t\t\"Choose known engine...\": \"পরিচিত ইঞ্জিন বাছুন...\",\n\t\t\t\"Weights\": \"ওয়েট\",\n\t\t\t\t\"Lc0 WeightsFile...\": \"Lc0 ওয়েট ফাইল...\",\n\t\t\t\t\"Stockfish EvalFile...\": \"Stockfish মূল্যায়ন ফাইল...\",\n\t\t\t\t\"Set to <auto>\": \"<auto>-তে সেট করুন\",\n\t\t\t\"Backend\": \"ব্যাকএন্ড\",\n\t\t\t\"Choose Syzygy path...\": \"Syzygy পাথ বাছুন...\",\n\t\t\t\"Unset\": \"আনসেট\",\n\t\t\t\"Limit - normal\": \"সীমা - স্বাভাবিক\",\n\t\t\t\t\"Unlimited\": \"অসীম\",\n\t\t\t\t\"Up slightly\": \"সামান্য বাড়ান\",\n\t\t\t\t\"Down slightly\": \"সামান্য কমান\",\n\t\t\t\"Limit - auto-eval / play\": \"সীমা - অটো-মূল্যায়ন / খেলা\",\n\t\t\t\"Limit by time instead of nodes\": \"নোডের পরিবর্তে সময় দ্বারা সীমিত করুন\",\n\t\t\t\"Threads\": \"থ্রেড\",\n\t\t\t\t\"Warning about threads\": \"থ্রেড সম্পর্কে সতর্কতা\",\n\t\t\t\"Hash\": \"হ্যাশ\",\n\t\t\t\t\"I want other hash options!\": \"আমি অন্য হ্যাশ অপশন চাই!\",\n\t\t\t\"MultiPV\": \"MultiPV\",\n\t\t\t\"Contempt Mode\": \"Contempt মোড\",\n\t\t\t\t\"White analysis\": \"সাদার বিশ্লেষণ\",\n\t\t\t\t\"Black analysis\": \"কালোর বিশ্লেষণ\",\n\t\t\t\"Contempt\": \"Contempt\",\n\t\t\t\"WDL Calibration Elo\": \"WDL ক্যালিব্রেশন Elo\",\n\t\t\t\t\"Use default WDL\": \"ডিফল্ট WDL ব্যবহার করুন\",\n\t\t\t\"WDL Eval Objectivity\": \"WDL মূল্যায়ন বাস্তবতা\",\n\t\t\t\t\"Yes\": \"হ্যাঁ\",\n\t\t\t\t\"No\": \"না\",\n\t\t\t\"Score Type\": \"স্কোর টাইপ\",\n\t\t\t\"Custom scripts\": \"কাস্টম স্ক্রিপ্ট\",\n\t\t\t\t\"How to add scripts\": \"স্ক্রিপ্ট কিভাবে যোগ করবেন\",\n\t\t\t\t\"Show scripts folder\": \"স্ক্রিপ্ট ফোল্ডার দেখান\",\n\t\t\t\"Restart engine\": \"ইঞ্জিন রিস্টার্ট করুন\",\n\t\t\t\"Soft engine reset\": \"সফট ইঞ্জিন রিসেট\",\n\t\t\"Play\": \"খেলুন\",\n\t\t\t\"Play this colour\": \"এই রং খেলুন\",\n\t\t\t\"Start self-play\": \"সেলফ-প্লে শুরু করুন\",\n\t\t\t\"Use Polyglot book...\": \"পলিগ্লট বুক ব্যবহার করুন...\",\n\t\t\t\"Use PGN book...\": \"PGN বুক ব্যবহার করুন...\",\n\t\t\t\"Unload book / abort load\": \"বুক আনলোড / লোড বাতিল করুন\",\n\t\t\t\"Book depth limit\": \"বুক ডেপথ সীমা\",\n\t\t\t\"Temperature\": \"তাপমাত্রা\",\n\t\t\t\"Temp Decay Moves\": \"টেম্প ডিকে মুভস\",\n\t\t\t\t\"Infinite\": \"অসীম\",\n\t\t\t\"About play modes\": \"খেলার মোড সম্পর্কে\",\n\t\t\"Dev\": \"ডেভ\",\n\t\t\t\"Toggle Developer Tools\": \"ডেভেলপার টুল টগল করুন\",\n\t\t\t\"Toggle Debug CSS\": \"ডিবাগ CSS টগল করুন\",\n\t\t\t\"Permanently enable save\": \"স্থায়ীভাবে সেভ সক্রিয় করুন\",\n\t\t\t[show_config]: \"config.json দেখান\",\n\t\t\t[show_engineconfig]: \"engines.json দেখান\",\n\t\t\t[reload_engineconfig]: \"engines.json রিলোড করুন (এবং ইঞ্জিন রিস্টার্ট করুন)\",\n\t\t\t\"Random move\": \"এলোমেলো চাল\",\n\t\t\t\"Disable hardware acceleration for GUI\": \"GUI-এর হার্ডওয়্যার অ্যাক্সিলারেশন নিষ্ক্রিয় করুন\",\n\t\t\t\"Spin rate\": \"স্পিন রেট\",\n\t\t\t\t\"Frenetic\": \"উন্মত্ত\",\n\t\t\t\t\"Fast\": \"দ্রুত\",\n\t\t\t\t\"Normal\": \"স্বাভাবিক\",\n\t\t\t\t\"Relaxed\": \"শিথিল\",\n\t\t\t\t\"Lazy\": \"অলস\",\n\t\t\t\"Show engine state\": \"ইঞ্জিন অবস্থা দেখান\",\n\t\t\t\"List sent options\": \"পাঠানো অপশন তালিকা\",\n\t\t\t\"Show error log\": \"ত্রুটি লগ দেখান\",\n\t\t\t\"Hacks and kludges\": \"হ্যাকস এবং ক্লাজ\",\n\t\t\t\t\"Allow arbitrary scripts\": \"অনির্দিষ্ট স্ক্রিপ্ট অনুমতি দিন\",\n\t\t\t\t\"Accept any file size\": \"যেকোনো ফাইল সাইজ গ্রহণ করুন\",\n\t\t\t\t\"Allow stopped analysis\": \"থামানো বিশ্লেষণ অনুমতি দিন\",\n\t\t\t\t\"Never hide focus buttons\": \"ফোকাস বাটন কখনও লুকাবেন না\",\n\t\t\t\t\"Never grayout move info\": \"চাল তথ্য কখনও ম্লান করবেন না\",\n\t\t\t\t\"Use lowerbound / upperbound info\": \"লোয়ারবাউন্ড / আপারবাউন্ড তথ্য ব্যবহার করুন\",\n\t\t\t\t\"Suppress ucinewgame\": \"ucinewgame দমন করুন\",\n\t\t\t\"Log RAM state to console\": \"কনসোলে RAM অবস্থা লগ করুন\",\n\t\t\t\"Fire GC\": \"GC চালু করুন\",\n\t\t\t\"Logging\": \"লগিং\",\n\t\t\t\t\"Use logfile...\": \"লগফাইল ব্যবহার করুন...\",\n\t\t\t\t\"Disable logging\": \"লগিং নিষ্ক্রিয় করুন\",\n\t\t\t\t\"Clear log when opening\": \"খোলার সময় লগ মুছুন\",\n\t\t\t\t\"Use unique logfile each time\": \"প্রতিবার অনন্য লগফাইল ব্যবহার করুন\",\n\t\t\t\t\"Log illegal moves\": \"অবৈধ চাল লগ করুন\",\n\t\t\t\t\"Log positions\": \"অবস্থান লগ করুন\",\n\t\t\t\t\"Log info lines\": \"তথ্য লাইন লগ করুন\",\n\t\t\t\t\"...including useless lines\": \"...অপ্রয়োজনীয় লাইন সহ\",\n\t\t\"Language\": \"ভাষা\",\n\n\t\t\"RESTART_REQUIRED\": \"GUI এখন পুনরায় চালু করতে হবে।\"\n\t},\n\n\t// FARSI ......................................................................................\n\n\t\"فارسی\": {\n\t\t\"File\": \"پرونده\",\n\t\t\t\"About\": \"درباره\",\n\t\t\t\"New game\": \"بازی جدید\",\n\t\t\t\"New 960 game\": \"بازی ۹۶۰ جدید\",\n\t\t\t\"Open PGN...\": \"باز کردن PGN...\",\n\t\t\t\"Load FEN / PGN from clipboard\": \"بارگیری FEN / PGN از کلیپ‌بورد\",\n\t\t\t\"Save this game...\": \"ذخیره این بازی...\",\n\t\t\t\"Write PGN to clipboard\": \"نوشتن PGN در کلیپ‌بورد\",\n\t\t\t\"PGN saved statistics\": \"آمار ذخیره‌شده PGN\",\n\t\t\t\t\"EV\": \"ارزش مورد انتظار\",\n\t\t\t\t\"Centipawns\": \"سنتی‌پاون\",\n\t\t\t\t\"N (%)\": \"تعداد (٪)\",\n\t\t\t\t\"N (absolute)\": \"تعداد (مطلق)\",\n\t\t\t\t\"...out of total\": \"از کل\",\n\t\t\t\t\"Depth (A/B only)\": \"عمق (فقط A/B)\",\n\t\t\t\"Cut\": \"برش\",\n\t\t\t\"Copy\": \"کپی\",\n\t\t\t\"Paste\": \"چسباندن\",\n\t\t\t\"Quit\": \"خروج\",\n\t\t\"Tree\": \"درخت\",\n\t\t\t\"Play engine choice\": \"انتخاب موتور\",\n\t\t\t\t\"1st\": \"۱ام\",\n\t\t\t\t\"2nd\": \"۲ام\",\n\t\t\t\t\"3rd\": \"۳ام\",\n\t\t\t\t\"4th\": \"۴ام\",\n\t\t\t\"Root\": \"ریشه\",\n\t\t\t\"End\": \"پایان\",\n\t\t\t\"Backward\": \"عقب\",\n\t\t\t\"Forward\": \"جلو\",\n\t\t\t\"Previous sibling\": \"حرکت قبلی\",\n\t\t\t\"Next sibling\": \"حرکت بعدی\",\n\t\t\t\"Return to main line\": \"بازگشت به خط اصلی\",\n\t\t\t\"Promote line to main line\": \"ارتقا به خط اصلی\",\n\t\t\t\"Promote line by 1 level\": \"ارتقا یک سطح\",\n\t\t\t\"Delete node\": \"حذف گره\",\n\t\t\t\"Delete children\": \"حذف فرزندان\",\n\t\t\t\"Delete siblings\": \"حذف هم‌سطح‌ها\",\n\t\t\t\"Delete ALL other lines\": \"حذف تمام خطوط دیگر\",\n\t\t\t\"Show PGN games list\": \"نمایش لیست بازی‌های PGN\",\n\t\t\t\"Escape\": \"خروج\",\n\t\t\"Analysis\": \"تحلیل\",\n\t\t\t\"Go\": \"شروع\",\n\t\t\t\"Go and lock engine\": \"شروع و قفل موتور\",\n\t\t\t\"Return to locked position\": \"بازگشت به وضعیت قفل شده\",\n\t\t\t\"Halt\": \"توقف\",\n\t\t\t\"Auto-evaluate line\": \"ارزیابی خودکار خط\",\n\t\t\t\"Auto-evaluate line, backwards\": \"ارزیابی خودکار خط، معکوس\",\n\t\t\t\"Show focus (searchmoves) buttons\": \"نمایش دکمه‌های تمرکز\",\n\t\t\t\"Clear focus\": \"پاک کردن تمرکز\",\n\t\t\t\"Invert focus\": \"معکوس کردن تمرکز\",\n\t\t\t\"Winrate POV\": \"دیدگاه نرخ برد\",\n\t\t\t\t\"Current\": \"فعلی\",\n\t\t\t\t\"White\": \"سفید\",\n\t\t\t\t\"Black\": \"سیاه\",\n\t\t\t\"Centipawn POV\": \"دیدگاه سنتی‌پاون\",\n\t\t\t\"Win / draw / loss POV\": \"دیدگاه برد/مساوی/باخت\",\n\t\t\t\"PV clicks\": \"کلیک‌های PV\",\n\t\t\t\t\"Do nothing\": \"هیچ کاری نکن\",\n\t\t\t\t\"Go there\": \"برو آنجا\",\n\t\t\t\t\"Add to tree\": \"اضافه به درخت\",\n\t\t\t\"Write infobox to clipboard\": \"نوشتن جعبه اطلاعات در کلیپ‌بورد\",\n\t\t\t\"Forget all analysis\": \"فراموش کردن همه تحلیل‌ها\",\n\t\t\"Display\": \"نمایش\",\n\t\t\t\"Flip board\": \"چرخاندن صفحه\",\n\t\t\t\"Arrows\": \"پیکان‌ها\",\n\t\t\t\"Piece-click spotlight\": \"نورافکن کلیک مهره\",\n\t\t\t\"Always show actual move (if known)\": \"نمایش همیشگی حرکت واقعی\",\n\t\t\t\"...with unique colour\": \"با رنگ منحصر به فرد\",\n\t\t\t\"...with outline\": \"با حاشیه\",\n\t\t\t\"Arrowhead type\": \"نوع سر پیکان\",\n\t\t\t\t\"Winrate\": \"نرخ برد\",\n\t\t\t\t\"Node %\": \"گره ٪\",\n\t\t\t\t\"Policy\": \"سیاست\",\n\t\t\t\t\"MultiPV rank\": \"رتبه MultiPV\",\n\t\t\t\t\"Moves Left Head\": \"حرکات باقی‌مانده\",\n\t\t\t\"Arrow filter (Lc0)\": \"فیلتر پیکان (Lc0)\",\n\t\t\t\t\"All moves\": \"همه حرکت‌ها\",\n\t\t\t\t\"Top move\": \"حرکت برتر\",\n\t\t\t\"Arrow filter (others)\": \"فیلتر پیکان (دیگران)\",\n\t\t\t\t\"Diff < 15%\": \"اختلاف < ۱۵٪\",\n\t\t\t\t\"Diff < 10%\": \"اختلاف < ۱۰٪\",\n\t\t\t\t\"Diff < 5%\": \"اختلاف < ۵٪\",\n\t\t\t\"Infobox stats\": \"آمار جعبه اطلاعات\",\n\t\t\t\t\"N - nodes (%)\": \"N - گره‌ها (٪)\",\n\t\t\t\t\"N - nodes (absolute)\": \"N - گره‌ها (مطلق)\",\n\t\t\t\t\"P - policy\": \"P - سیاست\",\n\t\t\t\t\"V - static evaluation\": \"V - ارزیابی استاتیک\",\n\t\t\t\t\"Q - evaluation\": \"Q - ارزیابی\",\n\t\t\t\t\"U - uncertainty\": \"U - عدم قطعیت\",\n\t\t\t\t\"S - search priority\": \"S - اولویت جستجو\",\n\t\t\t\t\"M - moves left\": \"M - حرکات باقی‌مانده\",\n\t\t\t\t\"WDL - win / draw / loss\": \"WDL - برد/مساوی/باخت\",\n\t\t\t\t\"Linebreak before stats\": \"خط جدید قبل از آمار\",\n\t\t\t\"PV move numbers\": \"شماره حرکت‌های PV\",\n\t\t\t\"Online API\": \"API آنلاین\",\n\t\t\t\t\"None\": \"هیچکدام\",\n\t\t\t\t\"ChessDB.cn evals\": \"ارزیابی‌های ChessDB.cn\",\n\t\t\t\t\"Lichess results (masters)\": \"نتایج Lichess (استادان)\",\n\t\t\t\t\"Lichess results (plebs)\": \"نتایج Lichess (عادی)\",\n\t\t\t\t\"Set Lichess API token\": \"تنظیم توکن API لیچس\",\n\t\t\t\"Allow API after move 25\": \"اجازه API بعد از حرکت ۲۵\",\n\t\t\t\"Draw PV on mouseover\": \"ترسیم PV هنگام حرکت موس\",\n\t\t\t\"Draw PV method\": \"روش ترسیم PV\",\n\t\t\t\t\"Animate\": \"متحرک\",\n\t\t\t\t\"Single move\": \"حرکت تکی\",\n\t\t\t\t\"Final position\": \"وضعیت نهایی\",\n\t\t\t\"Pieces\": \"مهره‌ها\",\n\t\t\t\t\"Choose pieces folder...\": \"انتخاب پوشه مهره‌ها...\",\n\t\t\t\t\"Default\": \"پیش‌فرض\",\n\t\t\t\t\"About custom pieces\": \"درباره مهره‌های سفارشی\",\n\t\t\t\"Background\": \"پس‌زمینه\",\n\t\t\t\t\"Choose background image...\": \"انتخاب تصویر پس‌زمینه...\",\n\t\t\t\"Book frequency arrows\": \"پیکان‌های فراوانی کتاب\",\n\t\t\t\"Lichess frequency arrows\": \"پیکان‌های فراوانی Lichess\",\n\t\t\"Sizes\": \"اندازه‌ها\",\n\t\t\t\"Infobox font\": \"فونت جعبه اطلاعات\",\n\t\t\t\"Move history font\": \"فونت تاریخچه حرکات\",\n\t\t\t\"Board\": \"صفحه\",\n\t\t\t\t\"Giant\": \"بسیار بزرگ\",\n\t\t\t\t\"Large\": \"بزرگ\",\n\t\t\t\t\"Medium\": \"متوسط\",\n\t\t\t\t\"Small\": \"کوچک\",\n\t\t\t\"Graph\": \"نمودار\",\n\t\t\t\"Graph lines\": \"خطوط نمودار\",\n\t\t\t\"I want other size options!\": \"گزینه‌های اندازه دیگر می‌خواهم!\",\n\t\t\"Engine\": \"موتور\",\n\t\t\t\"Choose engine...\": \"انتخاب موتور...\",\n\t\t\t\"Choose known engine...\": \"انتخاب موتور شناخته شده...\",\n\t\t\t\"Weights\": \"وزن‌ها\",\n\t\t\t\t\"Lc0 WeightsFile...\": \"فایل وزن Lc0...\",\n\t\t\t\t\"Stockfish EvalFile...\": \"فایل ارزیابی Stockfish...\",\n\t\t\t\t\"Set to <auto>\": \"تنظیم به <خودکار>\",\n\t\t\t\"Backend\": \"پشتیبان\",\n\t\t\t\"Choose Syzygy path...\": \"انتخاب مسیر Syzygy...\",\n\t\t\t\"Unset\": \"حذف تنظیم\",\n\t\t\t\"Limit - normal\": \"محدودیت - عادی\",\n\t\t\t\t\"Unlimited\": \"نامحدود\",\n\t\t\t\t\"Up slightly\": \"کمی بالا\",\n\t\t\t\t\"Down slightly\": \"کمی پایین\",\n\t\t\t\"Limit - auto-eval / play\": \"محدودیت - ارزیابی/بازی خودکار\",\n\t\t\t\"Limit by time instead of nodes\": \"محدودیت زمانی به جای گره‌ها\",\n\t\t\t\"Threads\": \"نخ‌ها\",\n\t\t\t\t\"Warning about threads\": \"هشدار درباره نخ‌ها\",\n\t\t\t\"Hash\": \"هش\",\n\t\t\t\t\"I want other hash options!\": \"گزینه‌های هش دیگر می‌خواهم!\",\n\t\t\t\"MultiPV\": \"MultiPV\",\n\t\t\t\"Contempt Mode\": \"حالت تحقیر\",\n\t\t\t\t\"White analysis\": \"تحلیل سفید\",\n\t\t\t\t\"Black analysis\": \"تحلیل سیاه\",\n\t\t\t\"Contempt\": \"تحقیر\",\n\t\t\t\"WDL Calibration Elo\": \"تنظیم Elo WDL\",\n\t\t\t\t\"Use default WDL\": \"استفاده از WDL پیش‌فرض\",\n\t\t\t\"WDL Eval Objectivity\": \"عینیت ارزیابی WDL\",\n\t\t\t\t\"Yes\": \"بله\",\n\t\t\t\t\"No\": \"خیر\",\n\t\t\t\"Score Type\": \"نوع امتیاز\",\n\t\t\t\"Custom scripts\": \"اسکریپت‌های سفارشی\",\n\t\t\t\t\"How to add scripts\": \"نحوه افزودن اسکریپت‌ها\",\n\t\t\t\t\"Show scripts folder\": \"نمایش پوشه اسکریپت‌ها\",\n\t\t\t\"Restart engine\": \"راه‌اندازی مجدد موتور\",\n\t\t\t\"Soft engine reset\": \"بازنشانی نرم موتور\",\n\t\t\"Play\": \"بازی\",\n\t\t\t\"Play this colour\": \"بازی با این رنگ\",\n\t\t\t\"Start self-play\": \"شروع بازی با خود\",\n\t\t\t\"Use Polyglot book...\": \"استفاده از کتاب Polyglot...\",\n\t\t\t\"Use PGN book...\": \"استفاده از کتاب PGN...\",\n\t\t\t\"Unload book / abort load\": \"لغو بارگیری کتاب\",\n\t\t\t\"Book depth limit\": \"محدودیت عمق کتاب\",\n\t\t\t\"Temperature\": \"دما\",\n\t\t\t\"Temp Decay Moves\": \"کاهش دمای حرکات\",\n\t\t\t\t\"Infinite\": \"بی‌نهایت\",\n\t\t\t\"About play modes\": \"درباره حالت‌های بازی\",\n\t\t\"Dev\": \"توسعه\",\n\t\t\t\"Toggle Developer Tools\": \"ابزار توسعه‌دهنده\",\n\t\t\t\"Toggle Debug CSS\": \"CSS اشکال‌زدایی\",\n\t\t\t\"Permanently enable save\": \"فعال‌سازی دائمی ذخیره\",\n\t\t\t[show_config]: \"نمایش config.json\",\n\t\t\t[show_engineconfig]: \"نمایش engines.json\",\n\t\t\t[reload_engineconfig]: \"بارگیری مجدد engines.json (و راه‌اندازی مجدد موتور)\",\n\t\t\t\"Random move\": \"حرکت تصادفی\",\n\t\t\t\"Disable hardware acceleration for GUI\": \"غیرفعال‌سازی شتاب سخت‌افزاری برای رابط کاربری\",\n\t\t\t\"Spin rate\": \"نرخ چرخش\",\n\t\t\t\t\"Frenetic\": \"شتابان\",\n\t\t\t\t\"Fast\": \"سریع\",\n\t\t\t\t\"Normal\": \"عادی\",\n\t\t\t\t\"Relaxed\": \"آرام\",\n\t\t\t\t\"Lazy\": \"کند\",\n\t\t\t\"Show engine state\": \"نمایش وضعیت موتور\",\n\t\t\t\"List sent options\": \"لیست گزینه‌های ارسال شده\",\n\t\t\t\"Show error log\": \"نمایش گزارش خطا\",\n\t\t\t\"Hacks and kludges\": \"هک‌ها و وصله‌ها\",\n\t\t\t\t\"Allow arbitrary scripts\": \"اجازه اسکریپت‌های دلخواه\",\n\t\t\t\t\"Accept any file size\": \"پذیرش هر اندازه فایل\",\n\t\t\t\t\"Allow stopped analysis\": \"اجازه تحلیل متوقف شده\",\n\t\t\t\t\"Never hide focus buttons\": \"عدم مخفی کردن دکمه‌های تمرکز\",\n\t\t\t\t\"Never grayout move info\": \"عدم کم‌رنگ کردن اطلاعات حرکت\",\n\t\t\t\t\"Use lowerbound / upperbound info\": \"استفاده از اطلاعات کران پایین/بالا\",\n\t\t\t\t\"Suppress ucinewgame\": \"جلوگیری از ucinewgame\",\n\t\t\t\"Log RAM state to console\": \"ثبت وضعیت RAM در کنسول\",\n\t\t\t\"Fire GC\": \"اجرای GC\",\n\t\t\t\"Logging\": \"ثبت گزارش\",\n\t\t\t\t\"Use logfile...\": \"استفاده از فایل گزارش...\",\n\t\t\t\t\"Disable logging\": \"غیرفعال کردن ثبت گزارش\",\n\t\t\t\t\"Clear log when opening\": \"پاک کردن گزارش هنگام باز کردن\",\n\t\t\t\t\"Use unique logfile each time\": \"استفاده از فایل گزارش یکتا هر بار\",\n\t\t\t\t\"Log illegal moves\": \"ثبت حرکات غیرمجاز\",\n\t\t\t\t\"Log positions\": \"ثبت موقعیت‌ها\",\n\t\t\t\t\"Log info lines\": \"ثبت خطوط اطلاعات\",\n\t\t\t\t\"...including useless lines\": \"شامل خطوط بی‌فایده\",\n\t\t\"Language\": \"زبان\",\n\n\t\t\"RESTART_REQUIRED\": \"رابط کاربری باید مجدداً راه‌اندازی شود.\"\n\t},\n\n\t// HEBREW .....................................................................................\n\n\t\"עברית\": {\n\t\t\"File\": \"קובץ\",\n\t\t\t\"About\": \"על אודות\",\n\t\t\t\"New game\": \"התחל משחק חדש\",\n\t\t\t\"New 960 game\": \"משחק 960 חדש\",\n\t\t\t\"Open PGN...\": \"פתח קובץ PGN...\",\n\t\t\t\"Load FEN / PGN from clipboard\": \"טען FEN / PGN מהלוח\",\n\t\t\t\"Save this game...\": \"שמור משחק זה...\",\n\t\t\t\"Write PGN to clipboard\": \"העתק PGN ללוח\",\n\t\t\t\"PGN saved statistics\": \"סטטיסטיקת PGN שמורה\",\n\t\t\t\t\"EV\": \"ערך צפוי\",\n\t\t\t\t\"Centipawns\": \"מאיות רגלי\",\n\t\t\t\t\"N (%)\": \"צמתים (%)\",\n\t\t\t\t\"N (absolute)\": \"צמתים (מוחלט)\",\n\t\t\t\t\"...out of total\": \"...מתוך סך הכל\",\n\t\t\t\t\"Depth (A/B only)\": \"עומק החיפוש\",\n\t\t\t\"Cut\": \"גזור\",\n\t\t\t\"Copy\": \"העתק\",\n\t\t\t\"Paste\": \"הדבק\",\n\t\t\t\"Quit\": \"סגור\",\n\t\t\"Tree\": \"עץ\",\n\t\t\t\"Play engine choice\": \"בחר מהלך מנוע\",\n\t\t\t\t\"1st\": \"ראשון\",\n\t\t\t\t\"2nd\": \"שני\",\n\t\t\t\t\"3rd\": \"שלישי\",\n\t\t\t\t\"4th\": \"רביעי\",\n\t\t\t\"Root\": \"שורש\",\n\t\t\t\"End\": \"סוף\",\n\t\t\t\"Backward\": \"אחורה\",\n\t\t\t\"Forward\": \"קדימה\",\n\t\t\t\"Previous sibling\": \"אח קודם\",\n\t\t\t\"Next sibling\": \"אח הבא\",\n\t\t\t\"Return to main line\": \"חזור למהלך הראשי\",\n\t\t\t\"Promote line to main line\": \"קדם קו לראשי\",\n\t\t\t\"Promote line by 1 level\": \"קדם קו ברמה אחת\",\n\t\t\t\"Delete node\": \"מחק צומת\",\n\t\t\t\"Delete children\": \"מחק צאצאים\",\n\t\t\t\"Delete siblings\": \"מחק אחים\",\n\t\t\t\"Delete ALL other lines\": \"מחק את כל הקווים אחרים\",\n\t\t\t\"Show PGN games list\": \"הצג רשימת משחקים\",\n\t\t\t\"Escape\": \"ביטול\",\n\t\t\"Analysis\": \"ניתוח\",\n\t\t\t\"Go\": \"התחל\",\n\t\t\t\"Go and lock engine\": \"התחל ונעל מנוע ניתוח\",\n\t\t\t\"Return to locked position\": \"חזור לעמדה נעולה\",\n\t\t\t\"Halt\": \"עצור\",\n\t\t\t\"Auto-evaluate line\": \"נתח קו אוטומטית\",\n\t\t\t\"Auto-evaluate line, backwards\": \"נתח קו אוטומטית, אחורה\",\n\t\t\t\"Show focus (searchmoves) buttons\": \"הצג כפתורי מיקוד\",\n\t\t\t\"Clear focus\": \"נקה מיקוד\",\n\t\t\t\"Invert focus\": \"הפוך מיקוד\",\n\t\t\t\"Winrate POV\": \"נקודת מבט סיכויי ניצחון\",\n\t\t\t\t\"Current\": \"נוכחי\",\n\t\t\t\t\"White\": \"לבן\",\n\t\t\t\t\"Black\": \"שחור\",\n\t\t\t\"Centipawn POV\": \"נקודת מבט מאיות רגלי\",\n\t\t\t\"Win / draw / loss POV\": \"נקודת מבט ניצחון/תיקו/הפסד\",\n\t\t\t\"PV clicks\": \"לחיצות PV\",\n\t\t\t\t\"Do nothing\": \"אל תעשה כלום\",\n\t\t\t\t\"Go there\": \"עבור לשם\",\n\t\t\t\t\"Add to tree\": \"הוסף לעץ\",\n\t\t\t\"Write infobox to clipboard\": \"העתק חלונית מידע ללוח\",\n\t\t\t\"Forget all analysis\": \"שכח כל הניתוחים\",\n\t\t\"Display\": \"תצוגה\",\n\t\t\t\"Flip board\": \"הפוך לוח\",\n\t\t\t\"Arrows\": \"חצים\",\n\t\t\t\"Piece-click spotlight\": \"הדגשת מהלכים חוקיים בלחיצה\",\n\t\t\t\"Always show actual move (if known)\": \"הצג תמיד מהלך בפועל\",\n\t\t\t\"...with unique colour\": \"...בצבע ייחודי\",\n\t\t\t\"...with outline\": \"...עם מתאר\",\n\t\t\t\"Arrowhead type\": \"סוג ראש חץ\",\n\t\t\t\t\"Winrate\": \"אחוזי ניצחון\",\n\t\t\t\t\"Node %\": \"אחוז צמתים\",\n\t\t\t\t\"Policy\": \"התפלגות אפריורי\",\n\t\t\t\t\"MultiPV rank\": \"דרוג MultiPV\",\n\t\t\t\t\"Moves Left Head\": \"ראש חיזוי מהלכים נותרים\",\n\t\t\t\"Arrow filter (Lc0)\": \"סינון חצים (Lc0)\",\n\t\t\t\t\"All moves\": \"כל המהלכים\",\n\t\t\t\t\"Top move\": \"המהלך המועדף\",\n\t\t\t\"Arrow filter (others)\": \"סינון חצים (אחרים)\",\n\t\t\t\t\"Diff < 15%\": \"הפרש הערכה < 15%\",\n\t\t\t\t\"Diff < 10%\": \"הפרש הערכה < 10%\",\n\t\t\t\t\"Diff < 5%\": \"הפרש הערכה < 5%\",\n\t\t\t\"Infobox stats\": \"חלונית מידע\",\n\t\t\t\t\"N - nodes (%)\": \"צמתים (%)\",\n\t\t\t\t\"N - nodes (absolute)\": \"צמתים (מספר כולל)\",\n\t\t\t\t\"P - policy\": \"הסתברויות רשת\",\n\t\t\t\t\"V - static evaluation\": \"הערכת עמדה ללא חיפוש\",\n\t\t\t\t\"Q - evaluation\": \"הערכת עמדה עם חיפוש\",\n\t\t\t\t\"U - uncertainty\": \"בונוס למהלכים מבטיחים לא-נחקרים\",\n\t\t\t\t\"S - search priority\": \"עדיפות חיפוש\",\n\t\t\t\t\"M - moves left\": \"מהלכים שנותרו\",\n\t\t\t\t\"WDL - win / draw / loss\": \"ניצחון/תיקו/הפסד\",\n\t\t\t\t\"Linebreak before stats\": \"שורה חדשה לפני נתונים\",\n\t\t\t\"PV move numbers\": \"מספרי מהלכים PV\",\n\t\t\t\"Online API\": \"API\",\n\t\t\t\t\"None\": \"ללא\",\n\t\t\t\t\"ChessDB.cn evals\": \"הערכות ChessDB.cn\",\n\t\t\t\t\"Lichess results (masters)\": \"תוצאות Lichess (אמנים)\",\n\t\t\t\t\"Lichess results (plebs)\": \"תוצאות Lichess (כל השחקנים)\",\n\t\t\t\t\"Set Lichess API token\": \"הגדרת טוקן API של Lichess\",\n\t\t\t\"Allow API after move 25\": \"אפשר API אחרי מהלך 25\",\n\t\t\t\"Draw PV on mouseover\": \"צייר PV במעבר עכבר\",\n\t\t\t\"Draw PV method\": \"שיטת ציור PV\",\n\t\t\t\t\"Animate\": \"אנימציה\",\n\t\t\t\t\"Single move\": \"מהלך בודד\",\n\t\t\t\t\"Final position\": \"עמדה סופית\",\n\t\t\t\"Pieces\": \"כלים\",\n\t\t\t\t\"Choose pieces folder...\": \"בחר תיקיית כלים...\",\n\t\t\t\t\"Default\": \"ברירת מחדל\",\n\t\t\t\t\"About custom pieces\": \"אודות כלים מותאמים\",\n\t\t\t\"Background\": \"רקע\",\n\t\t\t\t\"Choose background image...\": \"בחר תמונת רקע...\",\n\t\t\t\"Book frequency arrows\": \"חיצי שכיחות בספר פתיחות\",\n\t\t\t\"Lichess frequency arrows\": \"חיצי שכיחות ב-Lichess\",\n\t\t\"Sizes\": \"גדלים\",\n\t\t\t\"Infobox font\": \"גופן חלונית מידע\",\n\t\t\t\"Move history font\": \"גופן היסטוריית מהלכים\",\n\t\t\t\"Board\": \"לוח\",\n\t\t\t\t\"Giant\": \"ענק\",\n\t\t\t\t\"Large\": \"גדול\",\n\t\t\t\t\"Medium\": \"בינוני\",\n\t\t\t\t\"Small\": \"קטן\",\n\t\t\t\"Graph\": \"גרף\",\n\t\t\t\"Graph lines\": \"קווי גרף\",\n\t\t\t\"I want other size options!\": \"אני רוצה אפשרויות גודל אחרות!\",\n\t\t\"Engine\": \"מנוע ניתוח\",\n\t\t\t\"Choose engine...\": \"בחר מנוע ניתוח...\",\n\t\t\t\"Choose known engine...\": \"בחר מנוע ניתוח מוכר...\",\n\t\t\t\"Weights\": \"משקולות\",\n\t\t\t\t\"Lc0 WeightsFile...\": \"קובץ משקולות Lc0...\",\n\t\t\t\t\"Stockfish EvalFile...\": \"קובץ הערכה Stockfish...\",\n\t\t\t\t\"Set to <auto>\": \"הגדר לאוטומטי\",\n\t\t\t\"Backend\": \"Backend\",\n\t\t\t\"Choose Syzygy path...\": \"בחר נתיב Syzygy...\",\n\t\t\t\"Unset\": \"בטל הגדרה\",\n\t\t\t\"Limit - normal\": \"מגבלה - רגיל\",\n\t\t\t\t\"Unlimited\": \"ללא הגבלה\",\n\t\t\t\t\"Up slightly\": \"עלה מעט\",\n\t\t\t\t\"Down slightly\": \"רד מעט\",\n\t\t\t\"Limit - auto-eval / play\": \"מגבלה - הערכה אוטומטית / משחק\",\n\t\t\t\"Limit by time instead of nodes\": \"הגבל לפי זמן במקום צמתים\",\n\t\t\t\"Threads\": \"תהליכונים\",\n\t\t\t\t\"Warning about threads\": \"אזהרה לגבי תהליכונים\",\n\t\t\t\"Hash\": \"זיכרון מטמון\",\n\t\t\t\t\"I want other hash options!\": \"אני רוצה אפשרויות מטמון אחרות!\",\n\t\t\t\"MultiPV\": \"ניתוח מרובה\",\n\t\t\t\"Contempt Mode\": \"מצב הטיית תיקו\",\n\t\t\t\t\"White analysis\": \"ניתוח לבן\",\n\t\t\t\t\"Black analysis\": \"ניתוח שחור\",\n\t\t\t\"Contempt\": \"הטיית תיקו\",\n\t\t\t\"WDL Calibration Elo\": \"כיול WDL Elo\",\n\t\t\t\t\"Use default WDL\": \"השתמש ב-WDL ברירת מחדל\",\n\t\t\t\"WDL Eval Objectivity\": \"אובייקטיביות הערכת WDL\",\n\t\t\t\t\"Yes\": \"כן\",\n\t\t\t\t\"No\": \"לא\",\n\t\t\t\"Score Type\": \"סוג ניקוד\",\n\t\t\t\"Custom scripts\": \"סקריפטים מותאמים\",\n\t\t\t\t\"How to add scripts\": \"איך להוסיף סקריפטים\",\n\t\t\t\t\"Show scripts folder\": \"הצג תיקיית סקריפטים\",\n\t\t\t\"Restart engine\": \"הפעל מחדש מנוע ניתוח\",\n\t\t\t\"Soft engine reset\": \"איפוס חלקי למנוע ניתוח\",\n\t\t\"Play\": \"משחק\",\n\t\t\t\"Play this colour\": \"שחק בצבע זה\",\n\t\t\t\"Start self-play\": \"התחל משחק עצמי\",\n\t\t\t\"Use Polyglot book...\": \"השתמש בספר פתיחות Polyglot...\",\n\t\t\t\"Use PGN book...\": \"השתמש בספר פתיחות PGN...\",\n\t\t\t\"Unload book / abort load\": \"בטל טעינת ספר פתיחות\",\n\t\t\t\"Book depth limit\": \"מגבלת עומק ספר פתיחות\",\n\t\t\t\"Temperature\": \"רמת אקראיות\",\n\t\t\t\"Temp Decay Moves\": \"מספר מהלכים לדעיכת אקראיות\",\n\t\t\t\t\"Infinite\": \"אינסוף\",\n\t\t\t\"About play modes\": \"אודות מצבי משחק\",\n\t\t\"Dev\": \"פיתוח\",\n\t\t\t\"Toggle Developer Tools\": \"הצג/הסתר כלי פיתוח\",\n\t\t\t\"Toggle Debug CSS\": \"הצג/הסתר CSS לדיבוג\",\n\t\t\t\"Permanently enable save\": \"אפשר שמירה לצמיתות\",\n\t\t\t[show_config]: \"הצג config.json\",\n\t\t\t[show_engineconfig]: \"הצג engines.json\",\n\t\t\t[reload_engineconfig]: \"טען מחדש engines.json (והפעל מנוע מחדש)\",\n\t\t\t\"Random move\": \"מהלך אקראי\",\n\t\t\t\"Disable hardware acceleration for GUI\": \"בטל האצת חומרה לממשק\",\n\t\t\t\"Spin rate\": \"תדירות עדכון\",\n\t\t\t\t\"Frenetic\": \"קדחתני\",\n\t\t\t\t\"Fast\": \"מהיר\",\n\t\t\t\t\"Normal\": \"רגיל\",\n\t\t\t\t\"Relaxed\": \"רגוע\",\n\t\t\t\t\"Lazy\": \"עצלן\",\n\t\t\t\"Show engine state\": \"הצג מצב מנוע ניתוח\",\n\t\t\t\"List sent options\": \"הצג אפשרויות שנשלחו\",\n\t\t\t\"Show error log\": \"הצג יומן שגיאות\",\n\t\t\t\"Hacks and kludges\": \"אפשרויות מתקדמות ומעקפים\",\n\t\t\t\t\"Allow arbitrary scripts\": \"אפשר סקריפטים שרירותיים\",\n\t\t\t\t\"Accept any file size\": \"קבל כל גודל קובץ\",\n\t\t\t\t\"Allow stopped analysis\": \"אפשר ניתוח שהופסק\",\n\t\t\t\t\"Never hide focus buttons\": \"לעולם אל תסתיר כפתורי מיקוד\",\n\t\t\t\t\"Never grayout move info\": \"לעולם אל תאפיר מידע מהלכים\",\n\t\t\t\t\"Use lowerbound / upperbound info\": \"השתמש במידע גבול תחתון/עליון\",\n\t\t\t\t\"Suppress ucinewgame\": \"דכא ucinewgame\",\n\t\t\t\"Log RAM state to console\": \"רשום מצב RAM למסוף\",\n\t\t\t\"Fire GC\": \"כפה איסוף זיכרון\",\n\t\t\t\"Logging\": \"רישום\",\n\t\t\t\t\"Use logfile...\": \"השתמש בקובץ רישום...\",\n\t\t\t\t\"Disable logging\": \"בטל רישום\",\n\t\t\t\t\"Clear log when opening\": \"נקה רישום בפתיחה\",\n\t\t\t\t\"Use unique logfile each time\": \"השתמש בקובץ רישום ייחודי בכל פעם\",\n\t\t\t\t\"Log illegal moves\": \"רשום מהלכים לא חוקיים\",\n\t\t\t\t\"Log positions\": \"רשום עמדות\",\n\t\t\t\t\"Log info lines\": \"רשום שורות מידע\",\n\t\t\t\t\"...including useless lines\": \"...כולל שורות חסרות תועלת\",\n\t\t\"Language\": \"שפה\",\n\n\t\t\"RESTART_REQUIRED\": \"יש להפעיל את התוכנה מחדש כדי להחיל את השינויים\"\n\t},\n\n\t// ARABIC .....................................................................................\n\n\t\"العربية\": {\n\t\t\"File\": \"ملف\",\n\t\t\t\"About\": \"حول\",\n\t\t\t\"New game\": \"لعبة جديدة\",\n\t\t\t\"New 960 game\": \"لعبة 960 جديدة\",\n\t\t\t\"Open PGN...\": \"فتح PGN...\",\n\t\t\t\"Load FEN / PGN from clipboard\": \"تحميل FEN / PGN من الحافظة\",\n\t\t\t\"Save this game...\": \"حفظ هذه اللعبة...\",\n\t\t\t\"Write PGN to clipboard\": \"نسخ PGN إلى الحافظة\",\n\t\t\t\"PGN saved statistics\": \"إحصائيات PGN المحفوظة\",\n\t\t\t\t\"EV\": \"EV\",\n\t\t\t\t\"Centipawns\": \"Centipawns\",\n\t\t\t\t\"N (%)\": \"N (%)\",\n\t\t\t\t\"N (absolute)\": \"N (مطلق)\",\n\t\t\t\t\"...out of total\": \"من المجموع\",\n\t\t\t\t\"Depth (A/B only)\": \"العمق (A/B فقط)\",\n\t\t\t\"Cut\": \"قص\",\n\t\t\t\"Copy\": \"نسخ\",\n\t\t\t\"Paste\": \"لصق\",\n\t\t\t\"Quit\": \"خروج\",\n\t\t\"Tree\": \"شجرة\",\n\t\t\t\"Play engine choice\": \"لعب اختيار المحرك\",\n\t\t\t\t\"1st\": \"الأول\",\n\t\t\t\t\"2nd\": \"الثاني\",\n\t\t\t\t\"3rd\": \"الثالث\",\n\t\t\t\t\"4th\": \"الرابع\",\n\t\t\t\"Root\": \"الجذر\",\n\t\t\t\"End\": \"النهاية\",\n\t\t\t\"Backward\": \"للخلف\",\n\t\t\t\"Forward\": \"للأمام\",\n\t\t\t\"Previous sibling\": \"التفرع السابق\",\n\t\t\t\"Next sibling\": \"التفرع التالي\",\n\t\t\t\"Return to main line\": \"العودة إلى الخط الرئيسي\",\n\t\t\t\"Promote line to main line\": \"ترقية الخط إلى الخط الرئيسي\",\n\t\t\t\"Promote line by 1 level\": \"ترقية الخط مستوى واحد\",\n\t\t\t\"Delete node\": \"حذف العقدة\",\n\t\t\t\"Delete children\": \"حذف الفروع\",\n\t\t\t\"Delete siblings\": \"حذف التفرعات\",\n\t\t\t\"Delete ALL other lines\": \"حذف جميع الخطوط الأخرى\",\n\t\t\t\"Show PGN games list\": \"عرض قائمة ألعاب PGN\",\n\t\t\t\"Escape\": \"خروج\",\n\t\t\"Analysis\": \"تحليل\",\n\t\t\t\"Go\": \"ابدأ\",\n\t\t\t\"Go and lock engine\": \"ابدأ وثبت المحرك\",\n\t\t\t\"Return to locked position\": \"العودة إلى الوضع المثبت\",\n\t\t\t\"Halt\": \"توقف\",\n\t\t\t\"Auto-evaluate line\": \"تقييم تلقائي للخط\",\n\t\t\t\"Auto-evaluate line, backwards\": \"تقييم تلقائي للخط، للخلف\",\n\t\t\t\"Show focus (searchmoves) buttons\": \"إظهار أزرار التركيز\",\n\t\t\t\"Clear focus\": \"مسح التركيز\",\n\t\t\t\"Invert focus\": \"عكس التركيز\",\n\t\t\t\"Winrate POV\": \"وجهة نظر نسبة الفوز\",\n\t\t\t\t\"Current\": \"الحالي\",\n\t\t\t\t\"White\": \"الأبيض\",\n\t\t\t\t\"Black\": \"الأسود\",\n\t\t\t\"Centipawn POV\": \"وجهة نظر Centipawn\",\n\t\t\t\"Win / draw / loss POV\": \"وجهة نظر الفوز/التعادل/الخسارة\",\n\t\t\t\"PV clicks\": \"نقرات PV\",\n\t\t\t\t\"Do nothing\": \"لا شيء\",\n\t\t\t\t\"Go there\": \"اذهب هناك\",\n\t\t\t\t\"Add to tree\": \"أضف إلى الشجرة\",\n\t\t\t\"Write infobox to clipboard\": \"نسخ صندوق المعلومات إلى الحافظة\",\n\t\t\t\"Forget all analysis\": \"نسيان كل التحليلات\",\n\t\t\"Display\": \"عرض\",\n\t\t\t\"Flip board\": \"قلب اللوحة\",\n\t\t\t\"Arrows\": \"الأسهم\",\n\t\t\t\"Piece-click spotlight\": \"تسليط الضوء على النقرات\",\n\t\t\t\"Always show actual move (if known)\": \"إظهار النقلة الفعلية دائماً (إن وجدت)\",\n\t\t\t\"...with unique colour\": \"...بلون مميز\",\n\t\t\t\"...with outline\": \"...مع إطار\",\n\t\t\t\"Arrowhead type\": \"نوع رأس السهم\",\n\t\t\t\t\"Winrate\": \"نسبة الفوز\",\n\t\t\t\t\"Node %\": \"العقد %\",\n\t\t\t\t\"Policy\": \"السياسة\",\n\t\t\t\t\"MultiPV rank\": \"ترتيب MultiPV\",\n\t\t\t\t\"Moves Left Head\": \"النقلات المتبقية\",\n\t\t\t\"Arrow filter (Lc0)\": \"فلتر الأسهم (Lc0)\",\n\t\t\t\t\"All moves\": \"كل النقلات\",\n\t\t\t\t\"Top move\": \"أفضل نقلة\",\n\t\t\t\"Arrow filter (others)\": \"فلتر الأسهم (أخرى)\",\n\t\t\t\t\"Diff < 15%\": \"الفرق < 15%\",\n\t\t\t\t\"Diff < 10%\": \"الفرق < 10%\",\n\t\t\t\t\"Diff < 5%\": \"الفرق < 5%\",\n\t\t\t\"Infobox stats\": \"إحصائيات صندوق المعلومات\",\n\t\t\t\t\"N - nodes (%)\": \"N - العقد (%)\",\n\t\t\t\t\"N - nodes (absolute)\": \"N - العقد (مطلق)\",\n\t\t\t\t\"P - policy\": \"P - السياسة\",\n\t\t\t\t\"V - static evaluation\": \"V - التقييم الثابت\",\n\t\t\t\t\"Q - evaluation\": \"Q - التقييم\",\n\t\t\t\t\"U - uncertainty\": \"U - عدم اليقين\",\n\t\t\t\t\"S - search priority\": \"S - أولوية البحث\",\n\t\t\t\t\"M - moves left\": \"M - النقلات المتبقية\",\n\t\t\t\t\"WDL - win / draw / loss\": \"WDL - فوز/تعادل/خسارة\",\n\t\t\t\t\"Linebreak before stats\": \"سطر جديد قبل الإحصائيات\",\n\t\t\t\"PV move numbers\": \"أرقام نقلات PV\",\n\t\t\t\"Online API\": \"Online API\",\n\t\t\t\t\"None\": \"لا شيء\",\n\t\t\t\t\"ChessDB.cn evals\": \"تقييمات ChessDB.cn\",\n\t\t\t\t\"Lichess results (masters)\": \"نتائج Lichess (الأساتذة)\",\n\t\t\t\t\"Lichess results (plebs)\": \"نتائج Lichess (العامة)\",\n\t\t\t\t\"Set Lichess API token\": \"تعيين رمز API الخاص بـ Lichess\",\n\t\t\t\"Allow API after move 25\": \"السماح بـ API بعد النقلة 25\",\n\t\t\t\"Draw PV on mouseover\": \"رسم PV عند تمرير الماوس\",\n\t\t\t\"Draw PV method\": \"طريقة رسم PV\",\n\t\t\t\t\"Animate\": \"متحرك\",\n\t\t\t\t\"Single move\": \"نقلة واحدة\",\n\t\t\t\t\"Final position\": \"الوضع النهائي\",\n\t\t\t\"Pieces\": \"القطع\",\n\t\t\t\t\"Choose pieces folder...\": \"اختيار مجلد القطع...\",\n\t\t\t\t\"Default\": \"افتراضي\",\n\t\t\t\t\"About custom pieces\": \"حول القطع المخصصة\",\n\t\t\t\"Background\": \"الخلفية\",\n\t\t\t\t\"Choose background image...\": \"اختيار صورة الخلفية...\",\n\t\t\t\"Book frequency arrows\": \"أسهم تكرار الكتاب\",\n\t\t\t\"Lichess frequency arrows\": \"أسهم تكرار Lichess\",\n\t\t\"Sizes\": \"الأحجام\",\n\t\t\t\"Infobox font\": \"خط صندوق المعلومات\",\n\t\t\t\"Move history font\": \"خط تاريخ النقلات\",\n\t\t\t\"Board\": \"اللوحة\",\n\t\t\t\t\"Giant\": \"ضخم\",\n\t\t\t\t\"Large\": \"كبير\",\n\t\t\t\t\"Medium\": \"متوسط\",\n\t\t\t\t\"Small\": \"صغير\",\n\t\t\t\"Graph\": \"الرسم البياني\",\n\t\t\t\"Graph lines\": \"خطوط الرسم البياني\",\n\t\t\t\"I want other size options!\": \"أريد خيارات أحجام أخرى!\",\n\t\t\"Engine\": \"المحرك\",\n\t\t\t\"Choose engine...\": \"اختيار المحرك...\",\n\t\t\t\"Choose known engine...\": \"اختيار محرك معروف...\",\n\t\t\t\"Weights\": \"الأوزان\",\n\t\t\t\t\"Lc0 WeightsFile...\": \"ملف أوزان Lc0...\",\n\t\t\t\t\"Stockfish EvalFile...\": \"ملف تقييم Stockfish...\",\n\t\t\t\t\"Set to <auto>\": \"تعيين إلى <auto>\",\n\t\t\t\"Backend\": \"Backend\",\n\t\t\t\"Choose Syzygy path...\": \"اختيار مسار Syzygy...\",\n\t\t\t\"Unset\": \"إلغاء التعيين\",\n\t\t\t\"Limit - normal\": \"الحد - عادي\",\n\t\t\t\t\"Unlimited\": \"غير محدود\",\n\t\t\t\t\"Up slightly\": \"زيادة قليلة\",\n\t\t\t\t\"Down slightly\": \"نقص قليل\",\n\t\t\t\"Limit - auto-eval / play\": \"الحد - تقييم تلقائي / لعب\",\n\t\t\t\"Limit by time instead of nodes\": \"الحد بالوقت بدلاً من العقد\",\n\t\t\t\"Threads\": \"Threads\",\n\t\t\t\t\"Warning about threads\": \"تحذير حول Threads\",\n\t\t\t\"Hash\": \"Hash\",\n\t\t\t\t\"I want other hash options!\": \"أريد خيارات hash أخرى!\",\n\t\t\t\"MultiPV\": \"MultiPV\",\n\t\t\t\"Contempt Mode\": \"وضع Contempt\",\n\t\t\t\t\"White analysis\": \"تحليل الأبيض\",\n\t\t\t\t\"Black analysis\": \"تحليل الأسود\",\n\t\t\t\"Contempt\": \"Contempt\",\n\t\t\t\"WDL Calibration Elo\": \"معايرة WDL Elo\",\n\t\t\t\t\"Use default WDL\": \"استخدام WDL الافتراضي\",\n\t\t\t\"WDL Eval Objectivity\": \"موضوعية تقييم WDL\",\n\t\t\t\t\"Yes\": \"نعم\",\n\t\t\t\t\"No\": \"لا\",\n\t\t\t\"Score Type\": \"نوع النتيجة\",\n\t\t\t\"Custom scripts\": \"نصوص مخصصة\",\n\t\t\t\t\"How to add scripts\": \"كيفية إضافة النصوص\",\n\t\t\t\t\"Show scripts folder\": \"عرض مجلد النصوص\",\n\t\t\t\"Restart engine\": \"إعادة تشغيل المحرك\",\n\t\t\t\"Soft engine reset\": \"إعادة تعيين المحرك\",\n\t\t\"Play\": \"لعب\",\n\t\t\t\"Play this colour\": \"لعب هذا اللون\",\n\t\t\t\"Start self-play\": \"بدء اللعب الذاتي\",\n\t\t\t\"Use Polyglot book...\": \"استخدام كتاب Polyglot...\",\n\t\t\t\"Use PGN book...\": \"استخدام كتاب PGN...\",\n\t\t\t\"Unload book / abort load\": \"إلغاء تحميل الكتاب\",\n\t\t\t\"Book depth limit\": \"حد عمق الكتاب\",\n\t\t\t\"Temperature\": \"Temperature\",\n\t\t\t\"Temp Decay Moves\": \"Temp Decay Moves\",\n\t\t\t\t\"Infinite\": \"لا نهائي\",\n\t\t\t\"About play modes\": \"حول أوضاع اللعب\",\n\t\t\"Dev\": \"Dev\",\n\t\t\t\"Toggle Developer Tools\": \"تبديل أدوات المطور\",\n\t\t\t\"Toggle Debug CSS\": \"تبديل Debug CSS\",\n\t\t\t\"Permanently enable save\": \"تمكين الحفظ دائماً\",\n\t\t\t[show_config]: \"عرض config.json\",\n\t\t\t[show_engineconfig]: \"عرض engines.json\",\n\t\t\t[reload_engineconfig]: \"إعادة تحميل engines.json (وإعادة تشغيل المحرك)\",\n\t\t\t\"Random move\": \"نقلة عشوائية\",\n\t\t\t\"Disable hardware acceleration for GUI\": \"تعطيل تسريع الأجهزة للواجهة\",\n\t\t\t\"Spin rate\": \"معدل الدوران\",\n\t\t\t\t\"Frenetic\": \"محموم\",\n\t\t\t\t\"Fast\": \"سريع\",\n\t\t\t\t\"Normal\": \"عادي\",\n\t\t\t\t\"Relaxed\": \"مريح\",\n\t\t\t\t\"Lazy\": \"كسول\",\n\t\t\t\"Show engine state\": \"عرض حالة المحرك\",\n\t\t\t\"List sent options\": \"قائمة الخيارات المرسلة\",\n\t\t\t\"Show error log\": \"عرض سجل الأخطاء\",\n\t\t\t\"Hacks and kludges\": \"الحلول المؤقتة\",\n\t\t\t\t\"Allow arbitrary scripts\": \"السماح بالنصوص العشوائية\",\n\t\t\t\t\"Accept any file size\": \"قبول أي حجم ملف\",\n\t\t\t\t\"Allow stopped analysis\": \"السماح بالتحليل المتوقف\",\n\t\t\t\t\"Never hide focus buttons\": \"عدم إخفاء أزرار التركيز\",\n\t\t\t\t\"Never grayout move info\": \"عدم تعتيم معلومات النقلة\",\n\t\t\t\t\"Use lowerbound / upperbound info\": \"استخدام معلومات الحد الأدنى/الأعلى\",\n\t\t\t\t\"Suppress ucinewgame\": \"إخفاء ucinewgame\",\n\t\t\t\"Log RAM state to console\": \"تسجيل حالة RAM في وحدة التحكم\",\n\t\t\t\"Fire GC\": \"تشغيل GC\",\n\t\t\t\"Logging\": \"التسجيل\",\n\t\t\t\t\"Use logfile...\": \"استخدام ملف السجل...\",\n\t\t\t\t\"Disable logging\": \"تعطيل التسجيل\",\n\t\t\t\t\"Clear log when opening\": \"مسح السجل عند الفتح\",\n\t\t\t\t\"Use unique logfile each time\": \"استخدام ملف سجل فريد كل مرة\",\n\t\t\t\t\"Log illegal moves\": \"تسجيل النقلات غير القانونية\",\n\t\t\t\t\"Log positions\": \"تسجيل المواقع\",\n\t\t\t\t\"Log info lines\": \"تسجيل خطوط المعلومات\",\n\t\t\t\t\"...including useless lines\": \"...بما في ذلك الخطوط غير المفيدة\",\n\t\t\"Language\": \"اللغة\",\n\n\t\t\"RESTART_REQUIRED\": \"يجب إعادة تشغيل الواجهة الآن\"\n\t}\n\n};\n\n// Check dictionaries for missing items... log to console in main process.\n\nfunction checker() {\n\n\tlet base_dict = translations[\"Français\"];\n\tlet base_keys = Object.keys(base_dict);\n\n\tfor (let language of Object.keys(translations)) {\n\n\t\tif (language === \"English\") {\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet other_dict = translations[language];\n\t\tlet other_keys = Object.keys(other_dict);\n\n\t\tfor (let key of base_keys) {\n\t\t\tif (other_dict[key] === undefined) {\n\t\t\t\tconsole.log(`${language} missing: ${key}`);\n\t\t\t}\n\t\t}\n\n\t\tfor (let key of other_keys) {\n\t\t\tif (base_dict[key] === undefined) {\n\t\t\t\tconsole.log(`${language} has extra: ${key}`);\n\t\t\t}\n\t\t}\n\t}\n}\n\nchecker();\n\n\nmodule.exports = translations;\n"
  },
  {
    "path": "files/src/nibbler.css",
    "content": "html {\n\theight: 100%;\n}\n\nbody {\n\tbackground-color: #080808;\n\tborder: 0;\n\tcolor: #eeeeee;\n\tcursor: default;\n\tmargin: 0;\n\toverflow: hidden;\n\tpadding: 0;\n\tpointer-events: none;\t\t/* These must be overriden for things that need pointer / select */\n\tuser-select: none;\t\t\t/* These must be overriden for things that need pointer / select */\n}\n\n::-webkit-scrollbar {\n\tpointer-events: auto;\n\tbackground-color: #181818;\n}\n\n::-webkit-scrollbar-thumb {\n\tpointer-events: auto;\n\tbackground-color: #444444;\n}\n\n#gridder {\n\tdisplay: grid;\n\theight: 100vh;\n\tgrid-template-columns: min-content 1fr;\n\tgrid-template-rows: min-content min-content 1fr;\n\tgrid-template-areas:\n\t\t\"a b\"\n\t\t\"f f\"\n\t\t\"g g\";\n}\n\n#rightgridder {\n\tgrid-area: b;\n\tdisplay: grid;\n\tmargin: 1em 0 0 0;\n\theight: 0;\t\t\t\t\t\t\t/* js needs to keep this equal to the boardsize */\n\tgrid-template-columns: none;\n\tgrid-template-rows: min-content 1fr min-content;\n\tgrid-template-areas:\n\t\t\"c\"\n\t\t\"d\"\n\t\t\"e\";\n}\n\n#boardsquares {\n\tgrid-area: a;\n\tmargin: 1em 0 0 1em;\n\tbackground-size: cover;\n\tborder-collapse: collapse;\n\ttable-layout: fixed;\n\tz-index: 1;\n}\n\n#canvas {\n\tgrid-area: a;\n\tmargin: 1em 0 0 1em;\n\tdisplay: block;\n\toutline-offset: 6px;\n\tz-index: 2;\n}\n\n#boardfriends {\n\tgrid-area: a;\n\tmargin: 1em 0 0 1em;\n\tborder-collapse: collapse;\n\tpointer-events: auto;\n\ttable-layout: fixed;\n\tz-index: 3;\n}\n\n.dragging-piece {\n    cursor: grabbing !important;\n}\n\n#statusbox {\n\tgrid-area: c;\n\tmargin: 0 0 0 1em;\n\tborder: none;\n\tdisplay: block;\n\tfont-family: monospace, monospace;\n\tpointer-events: auto;\n\toverflow: hidden;\n\twhite-space: pre;\n}\n\n#infobox {\n\tgrid-area: d;\n\tmargin: 1em 1em 0 1em;\n\tdisplay: block;\n\tcolor: #cccccc;\t\t\t\t\t\t/* only used for Lc0 stderr output at startup */\n\tfont-family: monospace, monospace;\n\toverflow-x: hidden;\n\toverflow-y: auto;\n\tpadding-right: 10px;\t\t\t\t/* so the text doesn't get so near the scroll bar */\n\tpointer-events: auto;\n\twhite-space: pre-wrap;\n}\n\n#graph {\n\tgrid-area: e;\n\talign-self: end;\n\tdisplay: block;\n\tmargin: 10px 0 0 1em;\n\tpointer-events: auto;\n}\n\ninput[type=text]:focus {\n\toutline: 2px dashed gray;\n\toutline-offset: 4px;\n}\n\n#fenbox {\n\tgrid-area: f;\n\tmargin: 1em 1em 0 1em;\n\tbackground-color: #080808;\n\tborder: none;\n\tcaret-color: white;\n\tcolor: #6cccee;\n\tdisplay: block;\n\tfont-family: monospace, monospace;\n\tfont-size: 100%;\n\tpointer-events: auto;\n\tuser-select: auto;\n}\n\n#movelist {\n\tgrid-area: g;\n\tmargin: 1em 1em 1em 1em;\n\tdisplay: block;\n\tcolor: #999999;\n\tfont-family: monospace, monospace;\n\toverflow-x: hidden;\n\toverflow-y: auto;\n\tpadding-right: 10px;\t\t\t\t/* so the text doesn't get so near the scroll bar */\n\tpointer-events: auto;\n\twhite-space: pre-wrap;\n}\n\n#promotiontable {\n\tborder-collapse: collapse;\n\tdisplay: none;\n\tpointer-events: auto;\n\tposition: fixed;\n\ttable-layout: fixed;\n\tz-index: 4;\n}\n\n#fullbox {\n\tbackground-color: #080808;\n\tdisplay: none;\t\t\t\t\t\t/* better than visibility: hidden - never intercepts inputs */\n\tfont-family: monospace, monospace;\n\tfont-size: 100%;\n\theight: 100%;\n\tleft: 0;\n\toverflow-y: auto;\n\tpointer-events: auto;\n\tposition: fixed;\n\ttop: 0;\n\twidth: 100%;\n\tz-index: 6;\n}\n\n#fullbox_content {\n\toverflow: hidden;\n\tpadding: 1em;\n\twhite-space: pre;\n}\n\n#config_item_input {\n\tbackground: #101010;\n\tborder: 1px solid #444444;\n\tbox-sizing: border-box;\n\tcolor: #dddddd;\n\tfont-family: inherit;\n\tfont-size: inherit;\n\tmax-width: 100%;\n\twidth: 100%;\n}\n\n#config_item_input:focus {\n\tborder-color: #444444;\n\toutline: none;\n}\n\ntd {\n\tbackground-color: transparent;\n\tbackground-size: contain;\n\tborder: 0;\n\tmargin: 0;\n\tpadding: 0;\n}\n\na, a:link, a:visited, a:hover, a:active {\t/* I think this is now only used for the \"Nibbler in normal browser\" message. */\n\tcolor: #6cccee;\n}\n\nul {\n\tlist-style: none;\n}\n\n.pink {\n\tcolor: #ffaaaa;\n}\n\n.white {\n\tcolor: #eeeeee;\n}\n\n.gray {\n\tcolor: #999999;\n}\n\n.darkgray {\n\tcolor: #666666;\n}\n\n.red {\n\tcolor: #ff6666;\n}\n\n.yellow {\n\tcolor: #ffff00;\n}\n\n.green {\n\tcolor: #66ff66;\n}\n\n.blue {\n\tcolor: #6cccee;\n}\n\n.infoline {\n\tmargin-bottom: 1em;\n}\n\n.enginechooser {\n\tmargin-bottom: 1em;\n}\n\n.enginechooser:hover {\n\tcolor: #6cccee;\n}\n\n.pgnchooser:hover {\n\tbackground-color: #202020;\n}\n\n.ocm_highlight {\n\tbackground-color: #770000;\n}\n\n.hover_highlight {\n\tbackground-color: #222244;\n}\n\nspan.movelist_highlight_blue {\n\tbackground-color: #222244;\n\tcolor: #6cccee;\n}\n\nspan.movelist_highlight_yellow {\n\tbackground-color: #444422;\n\tcolor: #ffff00;\n}\n\nspan.nobr {\n\twhite-space: nowrap;\t\t\t\t/* Used for O-O and O-O-O moves */\n}\n"
  },
  {
    "path": "files/src/nibbler.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t<!-- https://content-security-policy.com/ -->\n\t<meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src https://www.chessdb.cn https://explorer.lichess.org\">\n\t<title>Nibbler</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"nibbler.css\">\n</head>\n\n<body>\n\n\t<!-- note to self, only use id for things the JS needs to see, they pollute the namespace -->\n\n\t<div id=\"gridder\">\n\n\t\t<table id=\"boardsquares\"></table>\n\t\t<canvas id=\"canvas\"></canvas>\n\t\t<table id=\"boardfriends\"></table>\n\n\t\t<div id=\"rightgridder\">\n\n\t\t\t<div id=\"statusbox\">Starting up...</div>\n\t\t\t<div id=\"infobox\"></div>\n\t\t\t<canvas id=\"graph\"></canvas>\n\n\t\t</div>\n\n\t\t<input type=\"text\" id=\"fenbox\" value=\"\">\n\n\t\t<div id=\"movelist\"></div>\n\n\t</div>\n\n\t<!-- Special hidden items that show up when needed -->\n\n\t<div id=\"fullbox\"><div id=\"fullbox_content\"></div></div>\n\n\t<table id=\"promotiontable\">\n\t\t<tr>\n\t\t\t<td class=\"promotion_q\"></td>\t\t<!-- Each promotion TD has a unique class to locate it with -->\n\t\t\t<td class=\"promotion_r\"></td>\t\t<!-- because the actual move will be stored in the id.      -->\n\t\t\t<td class=\"promotion_b\"></td>\n\t\t\t<td class=\"promotion_n\"></td>\n\t\t</tr>\n\t</table>\n\n\t<!-- Most of our components are fairly well encapsulated and could be in\n\tmodule form. Still, for the time being we have everything in one namespace\n\tand so load them in a particular order... -->\n\n\t<script src=\"renderer/10_globals.js\"></script>\n\t<script src=\"renderer/20_utils.js\"></script>\n\t<script src=\"renderer/30_point.js\"></script>\n\t<script src=\"renderer/31_sliders.js\"></script>\n\t<script src=\"renderer/40_position.js\"></script>\n\t<script src=\"renderer/41_fen.js\"></script>\n\t<script src=\"renderer/42_perft.js\"></script>\n\t<script src=\"renderer/43_chess960.js\"></script>\n\t<script src=\"renderer/50_table.js\"></script>\n\t<script src=\"renderer/51_node.js\"></script>\n\t<script src=\"renderer/52_sorted_moves.js\"></script>\n\t<script src=\"renderer/55_winrate_graph.js\"></script>\n\t<script src=\"renderer/60_pgn_utils.js\"></script>\n\t<script src=\"renderer/61_pgn_parse.js\"></script>\n\t<script src=\"renderer/63_polyglot.js\"></script>\n\t<script src=\"renderer/65_loaders.js\"></script>\n\t<script src=\"renderer/71_tree_handler.js\"></script>\n\t<script src=\"renderer/72_tree_draw.js\"></script>\n\t<script src=\"renderer/75_looker.js\"></script>\n\t<script src=\"renderer/80_info.js\"></script>\n\t<script src=\"renderer/81_arrows.js\"></script>\n\t<script src=\"renderer/82_infobox.js\"></script>\n\t<script src=\"renderer/83_statusbox.js\"></script>\n\t<script src=\"renderer/90_engine.js\"></script>\n\t<script src=\"renderer/95_hub.js\"></script>\n\t<script src=\"renderer/97_drag.js\"></script>\n\t<script src=\"renderer/99_start.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "files/src/package.json",
    "content": "{\n\t\"name\": \"Nibbler\",\n\t\"version\": \"2.5.9\",\n\t\"author\": \"Rooklift\",\n\t\"description\": \"Leela Chess Zero (Lc0) interface\",\n\t\"license\": \"GPL-3.0\",\n\t\"main\": \"main.js\",\n\t\"repository\": {\n\t\t\"type\": \"git\",\n\t\t\"url\": \"https://github.com/rooklift/nibbler\"\n\t}\n}\n"
  },
  {
    "path": "files/src/renderer/10_globals.js",
    "content": "\"use strict\";\n\n// HTML stuff.......................................................\n//\n// All of this may be redundant since id-havers are in the global\n// namespace automatically. But declaring them const has some value.\n\nconst boardfriends = document.getElementById(\"boardfriends\");\nconst boardsquares = document.getElementById(\"boardsquares\");\nconst canvas = document.getElementById(\"canvas\");\nconst fenbox = document.getElementById(\"fenbox\");\nconst graph = document.getElementById(\"graph\");\nconst rightgridder = document.getElementById(\"rightgridder\");\nconst infobox = document.getElementById(\"infobox\");\nconst movelist = document.getElementById(\"movelist\");\nconst fullbox = document.getElementById(\"fullbox\");\nconst fullbox_content = document.getElementById(\"fullbox_content\");\nconst promotiontable = document.getElementById(\"promotiontable\");\nconst statusbox = document.getElementById(\"statusbox\");\n\n// If require isn't available, we're in a browser:\n\ntry {\n\trequire(\"./modules/empty\");\n} catch (err) {\n\tstatusbox.innerHTML = `\n\tRunning Nibbler in a normal browser doesn't work. For the full app, see the\n\t<a href=\"https://github.com/rooklift/nibbler/releases\">Releases section</a> of the repo.<br><br>\n\n\tIt has also been observed not to work if your path contains a % character.`;\n}\n\n// Requires.........................................................\n\nconst background = require(\"./modules/background\");\nconst child_process = require(\"child_process\");\nconst clipboard = require(\"electron\").clipboard;\nconst config_io = require(\"./modules/config_io\");\nconst custom_uci = require(\"./modules/custom_uci\");\nconst engineconfig_io = require(\"./modules/engineconfig_io\");\nconst fs = require(\"fs\");\nconst images = require(\"./modules/images\");\nconst ipcRenderer = require(\"electron\").ipcRenderer;\nconst messages = require(\"./modules/messages\");\nconst path = require(\"path\");\nconst querystring = require(\"querystring\");\nconst readline = require(\"readline\");\nconst stringify = require(\"./modules/stringify\");\nconst translate = require(\"./modules/translate\");\nconst util = require(\"util\");\n\n// Prior to v32, given a file object from an event (e.g. from dragging the file onto the window)\n// we could simply access its path, but afterwards we need to use a helper function...\n\nlet webUtils = require(\"electron\").webUtils;\nconst get_path_for_file = (webUtils && webUtils.getPathForFile) ? webUtils.getPathForFile : file => file.path;\n\n// Globals..........................................................\n\nconst boardctx = canvas.getContext(\"2d\");\nconst graphctx = graph.getContext(\"2d\");\nconst decoder = new util.TextDecoder(\"utf8\");\t// https://github.com/electron/electron/issues/18733\n\nlet [load_err1, config]       = config_io.load();\nlet [load_err2, engineconfig] = engineconfig_io.load();\n\ntranslate.register_startup_language(config.language);\n\nlet next_node_id = 1;\nlet live_nodes = Object.create(null);\n\n// Replace the renderer's built-in alert()..........................\n\nlet alert = (msg) => {\n\tipcRenderer.send(\"alert\", stringify(msg));\n};\n\n// Get the images loading...........................................\n\nif (images.validate_folder(config.override_piece_directory)) {\n\timages.load_from(config.override_piece_directory);\n} else {\n\timages.load_from(path.join(__dirname, \"pieces\"));\n}\n\n// Standard options, for either type of engine......................\n// Note that UCI_Chess960 is handled specially by engine.js\n\nconst forced_lc0_options = {\t\t// These are sent without checking if they are known by the engine, so it doesn't matter\n\t\"LogLiveStats\": true,\t\t\t// if Leela is hiding them. Nevertheless, the user can still override them in engines.json.\n\t\"MoveOverheadMs\": 0,\n\t\"MultiPV\": 500,\n\t\"ScoreType\": \"WDL_mu\",\n\t\"SmartPruningFactor\": 0,\n\t\"UCI_ShowWDL\": true,\n\t\"VerboseMoveStats\": true,\n};\n\nconst standard_lc0_options = {\t\t// These are only sent if known by the engine.\n\t\"ContemptMode\": \"white_side_analysis\",\n\t\"Contempt\": 0,\n\t\"WDLCalibrationElo\": 0,\n\t\"WDLEvalObjectivity\": 0,\n};\n\nconst forced_ab_options = {};\n\nconst standard_ab_options = {\n\t\"Contempt\": 0,\n\t\"Move Overhead\": 0,\n\t\"UCI_ShowWDL\": true,\n};\n\n// Yeah this seemed a good idea at the time.........................\n\nconst limit_options = [\n\t1, 2, 5, 10, 20, 50, 100, 125, 160, 200, 250, 320, 400, 500, 640, 800,\n\t1000, 1250, 1600, 2000, 2500, 3200, 4000, 5000, 6400, 8000, 10000, 12500,\n\t16000, 20000, 25000, 32000, 40000, 50000, 64000, 80000, 100000, 125000,\n\t160000, 200000, 250000, 320000, 400000, 500000, 640000, 800000, 1000000,\n\t1250000, 1600000, 2000000, 2500000, 3200000, 4000000, 5000000, 6400000,\n\t8000000, 10000000, 12500000, 16000000, 20000000, 25000000, 32000000,\n\t40000000, 50000000, 64000000, 80000000, 100000000, 125000000, 160000000,\n\t200000000, 250000000, 320000000, 400000000, 500000000, 640000000,\n\t800000000, 1000000000\n];\n\nlimit_options.sort((a, b) => a - b);\n"
  },
  {
    "path": "files/src/renderer/20_utils.js",
    "content": "\"use strict\";\n\n// At some point I tried caching the results of XY() and S()\n// but for XY(), object lookups were slower than calculating,\n// and for S(), it just isn't called enough to matter.\n\nfunction XY(s) {\t\t\t\t// e.g. \"b7\" --> [1, 1]\n\tif (typeof s !== \"string\" || s.length !== 2) {\n\t\treturn [-1, -1];\n\t}\n\ts = s.toLowerCase();\n\tlet x = s.charCodeAt(0) - 97;\n\tlet y = 8 - parseInt(s[1], 10);\n\tif (x < 0 || x > 7 || y < 0 || y > 7 || Number.isNaN(y)) {\n\t\treturn [-1, -1];\n\t}\n\treturn [x, y];\n}\n\nfunction S(x, y) {\t\t\t\t// e.g. (1, 1) --> \"b7\"\n\tif (typeof x !== \"number\" || typeof y !== \"number\" || x < 0 || x > 7 || y < 0 || y > 7) {\n\t\treturn \"??\";\n\t}\n\tlet xs = String.fromCharCode(x + 97);\n\tlet ys = String.fromCharCode((8 - y) + 48);\n\treturn xs + ys;\n}\n\nfunction InfoVal(s, key) {\n\n\t// Given some string like \"info depth 8 seldepth 22 time 469 nodes 3918 score cp 46 hashfull 13 nps 8353 tbhits 0 multipv 1 pv d2d4 g8f6\"\n\t// pull the value for the key out, e.g. in this example, key \"nps\" returns \"8353\" (as a string).\n\t//\n\t// Since Lc0's info strings often have the value ending in \")\", we strip that out.\n\n\tif (typeof s !== \"string\" || typeof key !== \"string\") {\n\t\treturn \"\";\n\t}\n\n\tlet tokens = s.split(\" \").filter(z => z !== \"\");\n\n\tfor (let i = 0; i < tokens.length - 1; i++) {\n\t\tif (tokens[i] === key) {\n\t\t\tif (tokens[i + 1].endsWith(\")\")) {\n\t\t\t\treturn tokens[i + 1].slice(0, -1);\n\t\t\t} else {\n\t\t\t\treturn tokens[i + 1];\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\";\n}\n\nfunction InfoValMany(s, keys) {\n\n\t// Optimised version of InfoVal for when many values can be pulled out of the same string.\n\n\tlet ret = Object.create(null);\n\n\tlet tokens = s.split(\" \").filter(z => z !== \"\");\n\n\tfor (let key of keys) {\n\t\tlet ok = false;\n\t\tfor (let i = 0; i < tokens.length - 1; i++) {\n\t\t\tif (tokens[i] === key) {\n\t\t\t\tif (tokens[i + 1].endsWith(\")\")) {\n\t\t\t\t\tret[key] = tokens[i + 1].slice(0, -1);\n\t\t\t\t} else {\n\t\t\t\t\tret[key] = tokens[i + 1];\n\t\t\t\t}\n\t\t\t\tok = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!ok) {\n\t\t\tret[key] = \"\";\n\t\t}\n\t}\n\n\treturn ret;\n}\n\nfunction InfoPV(s) {\n\n\t// Pull the PV out.\n\n\tif (typeof s !== \"string\") {\n\t\treturn [];\n\t}\n\n\tlet tokens = s.split(\" \").filter(z => z !== \"\");\n\tlet pv_index = null;\n\n\tfor (let i = 0; i < tokens.length; i++) {\n\t\tif (tokens[i] === \"pv\") {\n\t\t\tpv_index = i;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tlet ret = [];\n\n\tif (pv_index !== null) {\n\n\t\tfor (let i = pv_index + 1; i < tokens.length; i++) {\n\n\t\t\tlet token = tokens[i];\n\n\t\t\tif (token.length < 4 || token.length > 5) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tlet codes = [token.charCodeAt(0), token.charCodeAt(1), token.charCodeAt(2), token.charCodeAt(3)];\n\n\t\t\tif (codes[0] < 97 || codes[0] > 104) break;\t\t// a - h\n\t\t\tif (codes[1] < 49 || codes[1] > 56) break;\t\t// 1 - 8\n\t\t\tif (codes[2] < 97 || codes[2] > 104) break;\n\t\t\tif (codes[3] < 49 || codes[3] > 56) break;\n\n\t\t\tret.push(token);\n\t\t}\n\t}\n\n\treturn ret;\n}\n\nfunction C960_PV_Converter(pv, board) {\n\n\t// Change standard UCI format castling moves in the PV\n\t// into our favoured c960 format. In place.\n\n\tlet fix_e1g1 = board.state[4][7] === \"K\" && !board.castling.includes(\"G\");\n\tlet fix_e1c1 = board.state[4][7] === \"K\" && !board.castling.includes(\"C\");\n\tlet fix_e8g8 = board.state[4][0] === \"k\" && !board.castling.includes(\"g\");\n\tlet fix_e8c8 = board.state[4][0] === \"k\" && !board.castling.includes(\"c\");\n\n\t// Those are the best tests to use here (especially considering that we\n\t// seem to be allowing arbitrary / weird castling rights like GHgh).\n\n\tfor (let i = 0; i < pv.length; i++) {\n\n\t\tlet token = pv[i];\n\n\t\tif (fix_e1g1 && token === \"e1g1\") {\n\t\t\tpv[i] = \"e1h1\";\n\t\t} else if (fix_e1c1 && token === \"e1c1\") {\n\t\t\tpv[i] = \"e1a1\";\n\t\t} else if (fix_e8g8 && token === \"e8g8\") {\n\t\t\tpv[i] = \"e8h8\";\n\t\t} else if (fix_e8c8 && token === \"e8c8\") {\n\t\t\tpv[i] = \"e8a8\";\n\t\t}\n\n\t\tlet start = token.slice(0, 2);\n\n\t\tif (start === \"e1\") {\n\t\t\tfix_e1g1 = false;\n\t\t\tfix_e1c1 = false;\n\t\t}\n\n\t\tif (start === \"e8\") {\n\t\t\tfix_e8g8 = false;\n\t\t\tfix_e8c8 = false;\n\t\t}\n\t}\n}\n\nfunction InfoWDL(s) {\n\n\tif (typeof s !== \"string\") {\n\t\treturn null;\n\t}\n\n\tlet tokens = s.split(\" \").filter(z => z !== \"\");\n\n\tlet ret = null;\n\n\tfor (let i = 0; i < tokens.length - 3; i++) {\n\t\tif (tokens[i] === \"wdl\") {\n\t\t\tret = tokens.slice(i + 1, i + 4);\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (Array.isArray(ret) === false || ret.length !== 3) {\n\t\treturn null;\n\t}\n\n\tfor (let n = 0; n < 3; n++) {\n\t\tlet tmp = parseInt(ret[n], 10);\n\t\tif (Number.isNaN(tmp)) {\n\t\t\treturn null;\n\t\t}\n\t\tret[n] = tmp;\n\t}\n\n\treturn ret;\n}\n\nfunction CompareArrays(a, b) {\n\n\tif (Array.isArray(a) === false || Array.isArray(b) === false) {\n\t\treturn false;\n\t}\n\n\tif (a.length !== b.length) {\n\t\treturn false;\n\t}\n\n\tfor (let n = 0; n < a.length; n++) {\n\t\tif (a[n] !== b[n]) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nfunction ArrayStartsWith(a, b) {\t\t// where b is itself an array\n\n\tif (Array.isArray(a) === false || Array.isArray(b) === false) {\n\t\treturn false;\n\t}\n\n\tif (a.length < b.length) {\n\t\treturn false;\n\t}\n\n\tfor (let n = 0; n < b.length; n++) {\n\t\tif (a[n] !== b[n]) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nfunction OppositeColour(s) {\n\tif (s === \"w\" || s === \"W\") return \"b\";\n\tif (s === \"b\" || s === \"B\") return \"w\";\n\treturn \"\";\n}\n\nfunction ReplaceAll(s, search, replace) {\n\tif (!s.includes(search)) return s;\t\t\t// Seems to improve speed overall.\n\treturn s.split(search).join(replace);\n}\n\nfunction SafeStringHTML(s) {\n\tif (typeof s !== \"string\") {\n\t\treturn undefined;\n\t}\n\ts = ReplaceAll(s,  `&`  ,  `&amp;`   );\t\t// This needs to be first of course.\n\ts = ReplaceAll(s,  `<`  ,  `&lt;`    );\n\ts = ReplaceAll(s,  `>`  ,  `&gt;`    );\n\ts = ReplaceAll(s,  `'`  ,  `&apos;`  );\n\ts = ReplaceAll(s,  `\"`  ,  `&quot;`  );\n\treturn s;\n}\n\nfunction UnsafeStringHTML(s) {\n\tif (typeof s !== \"string\") {\n\t\treturn undefined;\n\t}\n\ts = ReplaceAll(s,  `&quot;`  ,  `\"`  );\n\ts = ReplaceAll(s,  `&apos;`  ,  `'`  );\n\ts = ReplaceAll(s,  `&gt;`    ,  `>`  );\n\ts = ReplaceAll(s,  `&lt;`    ,  `<`  );\n\ts = ReplaceAll(s,  `&amp;`   ,  `&`  );\t\t// So I guess do this last.\n\treturn s;\n}\n\nfunction SafeStringPGN(s) {\n\tif (typeof s !== \"string\") {\n\t\treturn undefined;\n\t}\n\ts = ReplaceAll(s,  `\\\\`  ,  `\\\\\\\\`  );\t\t// Must be first.\n\ts = ReplaceAll(s,  `\"`   ,  `\\\\\"`   );\n\treturn s;\n}\n\nfunction UnsafeStringPGN(s) {\n\tif (typeof s !== \"string\") {\n\t\treturn undefined;\n\t}\n\ts = ReplaceAll(s,  `\\\\\"`   ,  `\"`   );\n\ts = ReplaceAll(s,  `\\\\\\\\`  ,  `\\\\`  );\t\t// So this ought to be last.\n\treturn s;\n}\n\nfunction Log(s) {\n\n\t// config.logfile  - name of desired log file (or null)\n\t// Log.logfilename - name of currently open log file (undefined if none)\n\t// Log.stream      - actual write stream\n\n\tif (typeof config.logfile !== \"string\" || config.logfile === \"\") {\n\t\tif (Log.logfilename) {\n\t\t\tconsole.log(`Closing ${Log.logfilename}`);\n\t\t\tLog.stream.end();\n\t\t\tLog.stream = undefined;\n\t\t\tLog.logfilename = undefined;\n\t\t}\n\t\treturn;\n\t}\n\n\t// So at this point, we know config.logfile is some string...\n\n\tif (Log.logfilename !== config.logfile) {\n\t\tif (Log.logfilename) {\n\t\t\tconsole.log(`Closing log ${Log.logfilename}`);\n\t\t\tLog.stream.end();\n\t\t\tLog.stream = undefined;\n\t\t\tLog.logfilename = undefined;\n\t\t}\n\n\t\tlet actual_filepath = config.logfile_timestamp ? UniqueFilepath(config.logfile) : config.logfile;\n\t\t// Note that this isn't saved even temporarily - as far as the rest of the logic is concerned, we are logging to config.logfile\n\n\t\tconsole.log(`Logging to ${actual_filepath}`);\n\t\tlet flags = (config.clear_log) ? \"w\" : \"a\";\n\t\tlet stream = fs.createWriteStream(actual_filepath, {flags: flags});\t\t// Want var \"stream\" available via closure for the below...\n\n\t\tstream.on(\"error\", (err) => {\n\t\t\tconsole.log(err);\n\t\t\tstream.end();\n\t\t\tif (Log.stream === stream) {\n\t\t\t\tLog.stream = undefined;\n\t\t\t\tLog.logfilename = undefined;\n\t\t\t\tconfig.logfile = null;\n\t\t\t\tipcRenderer.send(\"ack_logfile\", config.logfile);\n\t\t\t}\n  \t\t});\n\n  \t\tLog.stream = stream;\n\t\tLog.logfilename = config.logfile;\n\t}\n\n\tLog.stream.write(s + \"\\n\");\n}\n\nfunction LogBoth(s) {\n\tconsole.log(s);\n\tLog(s);\n}\n\nfunction UniqueFilepath(filepath) {\n\n\tconst alpha = \"abcdefghijklmnopqrstuvwxyz\";\n\n\tlet extname = path.extname(filepath);\n\tlet basename = path.basename(filepath, extname);\n\tlet dirname = path.dirname(filepath);\n\n\tlet dt = new Date();\n\n\tlet y = dt.getFullYear().toString();\n\tlet m = (dt.getMonth() + 1).toString();\n\tlet d = dt.getDate().toString();\n\tlet h = dt.getHours().toString();\n\tlet n = dt.getMinutes().toString();\n\tlet s = dt.getSeconds().toString();\n\n\tif (m.length === 1) m = \"0\" + m;\n\tif (d.length === 1) d = \"0\" + d;\n\tif (h.length === 1) h = \"0\" + h;\n\tif (n.length === 1) n = \"0\" + n;\n\tif (s.length === 1) s = \"0\" + s;\n\n\tlet newbase = `${basename}-${y}-${m}-${d}-${h}${n}${s}`;\n\n\tfor (let n = 0; n < 26; n++) {\n\t\tlet test = path.join(dirname, newbase) + alpha[n] + extname;\n\t\tif (!fs.existsSync(test)) {\n\t\t\treturn test;\n\t\t}\n\t}\n\n\t// If you start 27 instances of Nibbler within a second, that's your problem.\n\n\treturn filepath;\n}\n\nfunction New2DArray(width, height, defval) {\n\n\tlet ret = [];\n\n\tfor (let x = 0; x < width; x++) {\n\t\tret.push([]);\n\t\tfor (let y = 0; y < height; y++) {\n\t\t\tret[x].push(defval);\n\t\t}\n\t}\n\n\treturn ret;\n}\n\nfunction CanvasCoords(x, y) {\n\n\t// Given the x, y coordinates on the board (a8 is 0, 0)\n\t// return an object with the canvas coordinates for\n\t// the square, and also the centre.\n\t//\n\t//      x1,y1--------\n\t//        |         |\n\t//        |  cx,cy  |\n\t//        |         |\n\t//        --------x2,y2\n\n\tlet css = config.square_size;\n\tlet x1 = x * css;\n\tlet y1 = y * css;\n\tlet x2 = x1 + css;\n\tlet y2 = y1 + css;\n\n\tif (config.flip) {\n\t\t[x1, x2] = [(css * 8) - x2, (css * 8) - x1];\n\t\t[y1, y2] = [(css * 8) - y2, (css * 8) - y1];\n\t}\n\n\tlet cx = x1 + css / 2;\n\tlet cy = y1 + css / 2;\n\n\treturn {x1, y1, x2, y2, cx, cy};\n}\n\nfunction EventPathString(event, prefix) {\n\n\t// Given an event with event.path like [\"foo\", \"bar\", \"searchmove_e2e4\", \"whatever\"]\n\t// return the string \"e2e4\", assuming the prefix matches. Else return null.\n\n\tif (!event || typeof prefix !== \"string\") {\n\t\treturn null;\n\t}\n\n\tlet path = event.path || (event.composedPath && event.composedPath());\n\n\tif (path) {\n\t\tfor (let item of path) {\n\t\t\tif (typeof item.id === \"string\") {\n\t\t\t\tif (item.id.startsWith(prefix)) {\n\t\t\t\t\treturn item.id.slice(prefix.length);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\nfunction EventPathN(event, prefix) {\n\n\t// As above, but returning a number, or null.\n\n\tlet s = EventPathString(event, prefix);\n\n\tif (typeof s !== \"string\") {\n\t\treturn null;\n\t}\n\n\tlet n = parseInt(s, 10);\n\n\tif (Number.isNaN(n)) {\n\t\treturn null;\n\t}\n\n\treturn n;\n}\n\nfunction SwapElements(obj1, obj2) {\n\n\t// https://stackoverflow.com/questions/10716986/swap-2-html-elements-and-preserve-event-listeners-on-them\n\n\tlet temp = document.createElement(\"div\");\n\tobj1.parentNode.insertBefore(temp, obj1);\n\tobj2.parentNode.insertBefore(obj1, obj2);\n\ttemp.parentNode.insertBefore(obj2, temp);\n\ttemp.parentNode.removeChild(temp);\n}\n\nfunction NString(n) {\n\n\tconst thousand = 1000;\n\tconst million = 1000000;\n\tconst billion = 1000000000;\n\n\tif (typeof n !== \"number\") {\n\t\treturn \"?\";\n\t}\n\n\tif (n < thousand) {\n\t\treturn n.toString();\n\t}\n\n\tif (n < 100 * thousand) {\n\t\treturn (n / thousand).toFixed(1) + \"k\";\n\t}\n\n\tif (n < 999.5 * thousand) {\n\t\treturn (n / thousand).toFixed(0) + \"k\";\n\t}\n\n\tif (n < 100 * million) {\n\t\treturn (n / million).toFixed(1) + \"M\";\n\t}\n\n\tif (n < 999.5 * million) {\n\t\treturn (n / million).toFixed(0) + \"M\";\n\t}\n\n\tif (n < 100 * billion) {\n\t\treturn (n / billion).toFixed(1) + \"B\";\n\t}\n\n\treturn (n / billion).toFixed(0) + \"B\";\n}\n\nfunction DateString(dt) {\n\tlet y = dt.getFullYear();\n\tlet m = dt.getMonth() + 1;\n\tlet d = dt.getDate();\n\tlet parts = [\n\t\ty.toString(),\n\t\t(m > 9 ? \"\" : \"0\") + m.toString(),\n\t\t(d > 9 ? \"\" : \"0\") + d.toString(),\n\t];\n\treturn parts.join(\".\");\n}\n\nfunction QfromPawns(pawns) {\n\n\t// Note carefully: the arg is pawns not centipawns.\n\n\tif (typeof pawns !== \"number\") {\n\t\treturn 0;\n\t}\n\n\tlet winrate = 1 / (1 + Math.pow(10, -pawns / 4));\n\tlet q = winrate * 2 - 1;\n\n\tif (q > 0.998) q = 0.998;\n\tif (q < -0.998) q = -0.998;\n\n\treturn q;\n}\n\nfunction QfromWDL(wdl) {\n\n\tif (Array.isArray(wdl) === false || wdl.length !== 3) {\n\t\treturn 0;\n\t}\n\n\tlet winrate = (wdl[0] + (wdl[1] * 0.5)) / 1000;\n\tlet q = winrate * 2 - 1;\n\n\tif (q > 0.998) q = 0.998;\n\tif (q < -0.998) q = -0.998;\n\n\treturn q;\n}\n\nfunction Value(q) {\t\t\t\t\t// Rescale Q to 0..1 range.\n\tif (typeof q !== \"number\") {\n\t\treturn 0;\n\t}\n\tif (q < -1) {\n\t\treturn 0;\n\t}\n\tif (q > 1) {\n\t\treturn 1;\n\t}\n\treturn (q + 1) / 2;\n}\n\nfunction SmoothStep(x) {\n\tif (x < 0) x = 0;\n\tif (x > 1) x = 1;\n\treturn (-2 * x * x * x) + (3 * x * x);\n}\n\nfunction Sign(n) {\n\tif (n < 0) return -1;\n\tif (n > 0) return 1;\n\treturn 0;\n}\n\nfunction CommaNum(n) {\n\n\tif (typeof n !== \"number\") {\n\t\treturn JSON.stringify(n);\n\t}\n\n\tif (n < 1000) {\n\t\treturn n.toString();\n\t}\n\n\tlet ret = \"\";\n\n\tlet n_string = n.toString();\n\n\tfor (let i = 0; i < n_string.length; i++) {\n\t\tret += n_string[i];\n\t\tif ((n_string.length - i) % 3 === 1 && n_string.length - i > 1) {\n\t\t\tret += \",\";\n\t\t}\n\t}\n\n\treturn ret;\n}\n\nfunction DurationString(ms) {\n\n\tlet hours = Math.floor(ms / 3600000);\n\tms -= hours * 3600000;\n\n\tlet minutes = Math.floor(ms / 60000);\n\tms -= minutes * 60000;\n\n\tlet seconds = Math.floor(ms / 1000);\n\n\tif (hours > 0) {\n\t\treturn `${hours}h ${minutes}m`;\n\t}\n\n\tif (minutes > 0) {\n\t\treturn `${minutes}m ${seconds}s`;\n\t}\n\n\treturn `${seconds}s`;\n}\n\nfunction NumbersBetween(a, b) {\n\n\t// Given integers a and b, return a list of integers between the two, inclusive.\n\n\tlet add = a < b ? 1 : -1;\n\n\tlet ret = [];\n\n\tfor (let x = a; x !== b; x += add) {\n\t\tret.push(x);\n\t}\n\n\tret.push(b);\n\n\treturn ret;\n}\n\nfunction RandInt(min, max) {\n\tif (typeof max !== \"number\") {\t\t// DWIM.\n\t\tmax = min;\n\t\tmin = 0;\n\t}\n\tif (min >= max) {\n\t\treturn min;\n\t}\n\tlet ret = Math.floor(Math.random() * (max - min)) + min;\n\tif (ret >= max) {\t\t// Probably impossible.\n\t\tret = min;\n\t}\n\treturn ret;\n}\n\nfunction RandChoice(arr) {\n\tif (Array.isArray(arr) === false || arr.length === 0) {\n\t\treturn undefined;\n\t}\n\treturn arr[RandInt(0, arr.length)];\n}\n\nfunction HighlightString(s, prefix, classname) {\n\n\t// Highlights the thing after the prefix\n\n\tif (s.startsWith(prefix) === false) {\n\t\treturn s;\n\t}\n\n\treturn prefix + `<span class=\"${classname}\">` + s.slice(prefix.length) + `</span>`;\n}\n\nfunction StringIsNumeric(s) {\n\tfor (let i = 0; i < s.length; i++) {\n\t\tlet code = s.charCodeAt(i);\n\t\tif (code < 48 || code > 57) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n}\n\nfunction FileExceedsGigabyte(filename, multiplier = 1) {\n\ttry {\n\t\tlet filesize = fs.statSync(filename).size;\n\t\tif (filesize >= 1073741824 * multiplier) {\n\t\t\treturn true;\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t} catch (err) {\n\t\tconsole.log(\"While checking file size: \", err.toString());\n\t\treturn false;\t\t// Eh, who knows\n\t}\n}\n"
  },
  {
    "path": "files/src/renderer/30_point.js",
    "content": "\"use strict\";\n\nfunction Point(a, b) {\n\n\t// Each Point is represented by a single object so that naive equality checking works, i.e.\n\t// Point(x, y) === Point(x, y) should be true. Since object comparisons in JS will be false\n\t// unless they are the same object, we do the following...\n\t//\n\t// Returns null on invalid input, therefore the caller should take care to ensure that the\n\t// value is not null before accessing .x or .y or .s!\n\n\tif (Point.xy_lookup === undefined) {\n\t\tPoint.xy_lookup = New2DArray(8, 8, null);\n\t\tfor (let x = 0; x < 8; x++) {\n\t\t\tfor (let y = 0; y < 8; y++) {\n\t\t\t\tlet s = S(x, y);\n\t\t\t\tlet point = Object.freeze({x, y, s});\n\t\t\t\tPoint.xy_lookup[x][y] = point;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Point(\"a8\") or Point(0, 0) are both valid.\n\n\tif (b === undefined) {\n\t\t[a, b] = XY(a);\t\t\t// Possibly [-1, -1] if invalid\n\t}\n\n\tlet col = Point.xy_lookup[a];\n\tif (col === undefined) return null;\n\n\tlet ret = col[b];\n\tif (ret === undefined) return null;\n\n\treturn ret;\n}\n\nfunction PointsBetween(a, b) {\n\n\t// Given points a and b, return a list of points between the two, inclusive.\n\n\tif (!a && !b) return [];\n\tif (!a) return [b];\n\tif (!b) return [a];\n\n\tif (a === b) {\n\t\treturn [a];\n\t}\n\n\tlet ok = false;\n\n\tif (a.x === b.x) {\n\t\tok = true;\n\t}\n\n\tif (a.y === b.y) {\n\t\tok = true;\n\t}\n\n\tif (Math.abs(a.x - b.x) === Math.abs(a.y - b.y)) {\n\t\tok = true;\n\t}\n\n\tif (ok === false) {\n\t\treturn [a, b];\n\t}\n\n\tlet stepx = Sign(b.x - a.x);\n\tlet stepy = Sign(b.y - a.y);\n\n\tlet x = a.x;\n\tlet y = a.y;\n\n\tlet ret = [];\n\n\twhile (1) {\n\t\tret.push(Point(x, y));\n\t\tif (x === b.x && y === b.y) {\n\t\t\treturn ret;\n\t\t}\n\t\tx += stepx;\n\t\ty += stepy;\n\t}\n}\n"
  },
  {
    "path": "files/src/renderer/31_sliders.js",
    "content": "\"use strict\";\n\n// This makes an object storing \"sliders\" for every piece except K and k which are handled\n// differently. A slider is a list of vectors, which are distances from the origin.\n\nfunction generate_movegen_sliders() {\n\n\tlet invert = n => n === 0 ? 0 : -n;\t\t\t\t\t\t\t// Flip sign without introducing -0\n\tlet rotate = xy => [invert(xy[1]), xy[0]];\t\t\t\t\t// Rotate a single vector of form [x,y]\n\tlet flip = xy => [invert(xy[0]), xy[1]];\t\t\t\t\t// Flip a single vector, horizontally\n\n\tlet ret = Object.create(null);\n\n\t// For each of B, R, N, make an initial slider and place it in a new list as item 0...\n\tret.B = [[[1,1], [2,2], [3,3], [4,4], [5,5], [6,6], [7,7]]];\n\tret.R = [[[1,0], [2,0], [3,0], [4,0], [5,0], [6,0], [7,0]]];\n\tret.N = [[[1,2]]];\n\n\t// Add 3 rotations for each...\n\tfor (let n = 0; n < 3; n++) {\n\t\tret.B.push(ret.B[n].map(rotate));\n\t\tret.R.push(ret.R[n].map(rotate));\n\t\tret.N.push(ret.N[n].map(rotate));\n\t}\n\n\t// Add the knight mirrors (knights have 8 sliders of length 1)...\n\tret.N = ret.N.concat(ret.N.map(slider => slider.map(flip)));\n\n\t// Make the queen from the rook and bishop...\n\tret.Q = ret.B.concat(ret.R);\n\n\t// The black lowercase versions can point to the same objects...\n\tfor (let key of Object.keys(ret)) {\n\t\tret[key.toLowerCase()] = ret[key];\n\t}\n\n\t// Make the pawns...\n\tret.P = [[[0,-1], [0,-2]], [[-1,-1]], [[1,-1]]];\n\tret.p = [[[0,1], [0,2]], [[-1,1]], [[1,1]]];\n\n\treturn ret;\n}\n\nlet movegen_sliders = generate_movegen_sliders();\n"
  },
  {
    "path": "files/src/renderer/40_position.js",
    "content": "\"use strict\";\n\n\n//\t\tNote that ALL CASTLING MOVES are expected to be in format KING-TO-ROOK (e.g. e1h1).\n//\t\tThat is, only Chess960 format is allowed.\n//\n//\t\tThere are awkward ramifications if we allowed one move to have two representations,\n//\t\tso we don't. We either convert old-format moves to new-format as soon as we receive\n//\t\tthem, or we treat them as illegal.\n\n\nconst position_prototype = {\n\n\tmove: function(s) {\n\n\t\t// s is some valid UCI move like \"d1f3\" or \"e7e8q\". For the most part, this function\n\t\t// assumes the move is legal - all sorts of weird things can happen if this isn't so.\n\t\t//\n\t\t// As an exception, note that position.illegal() does call this to make a temp board\n\t\t// that can be used to test for moves that leave the king in check, so this method\n\t\t// must \"work\" for such illegal moves.\n\n\t\tif (typeof s !== \"string\" || s.length < 4) {\n\t\t\tconsole.log(\"position_prototype.move called with arg\", s);\n\t\t\treturn this;\n\t\t}\n\n\t\t// s = this.c960_castling_converter(s);\t\t// Too many ramifications to think about.\n\n\t\tlet [x1, y1] = XY(s.slice(0, 2));\n\t\tlet [x2, y2] = XY(s.slice(2, 4));\n\n\t\tif (x1 < 0 || y1 < 0 || x1 > 7 || y1 > 7 || x2 < 0 || y2 < 0 || x2 > 7 || y2 > 7) {\n\t\t\tconsole.log(\"position_prototype.move called with arg\", s);\n\t\t\treturn this;\n\t\t}\n\n\t\tif (this.state[x1][y1] === \"\") {\n\t\t\tconsole.log(\"position_prototype.move called with empty source, arg was\", s);\n\t\t\treturn this;\n\t\t}\n\n\t\tlet ret = this.copy();\n\n\t\tlet promotion_char = s.length > 4 ? s[4].toLowerCase() : \"q\";\n\n\t\tlet white_flag = ret.is_white(Point(x1, y1));\n\t\tlet pawn_flag = ret.state[x1][y1] === \"P\" || ret.state[x1][y1] === \"p\";\n\t\tlet castle_flag = (ret.state[x2][y2] === \"R\" && white_flag) || (ret.state[x2][y2] === \"r\" && white_flag === false);\n\t\tlet capture_flag = castle_flag === false && ret.state[x2][y2];\n\n\t\tif (pawn_flag && x1 !== x2) {\t\t// Make sure capture_flag is set even for enpassant captures\n\t\t\tcapture_flag = true;\n\t\t}\n\n\t\t// Update castling info...\n\n\t\tif (y1 === 7 && ret.state[x1][y1] === \"K\") {\n\t\t\tret.__delete_white_castling();\n\t\t}\n\n\t\tif (y1 === 0 && ret.state[x1][y1] === \"k\") {\n\t\t\tret.__delete_black_castling();\n\t\t}\n\n\t\tif (y1 === 7 && ret.state[x1][y1] === \"R\") {\t\t\t// White rook moved.\n\t\t\tlet ch = String.fromCharCode(x1 + 65);\n\t\t\tret.__delete_castling_char(ch);\n\t\t}\n\n\t\tif (y2 === 7 && ret.state[x2][y2] === \"R\") {\t\t\t// White rook was captured (or castled onto).\n\t\t\tlet ch = String.fromCharCode(x2 + 65);\n\t\t\tret.__delete_castling_char(ch);\n\t\t}\n\n\t\tif (y1 === 0 && ret.state[x1][y1] === \"r\") {\t\t\t// Black rook moved.\n\t\t\tlet ch = String.fromCharCode(x1 + 97);\n\t\t\tret.__delete_castling_char(ch);\n\t\t}\n\n\t\tif (y2 === 0 && ret.state[x2][y2] === \"r\") {\t\t\t// Black rook was captured (or castled onto).\n\t\t\tlet ch = String.fromCharCode(x2 + 97);\n\t\t\tret.__delete_castling_char(ch);\n\t\t}\n\n\t\t// Update halfmove and fullmove...\n\n\t\tif (white_flag === false) {\n\t\t\tret.fullmove++;\n\t\t}\n\n\t\tif (pawn_flag || capture_flag) {\n\t\t\tret.halfmove = 0;\n\t\t} else {\n\t\t\tret.halfmove++;\n\t\t}\n\n\t\t// Handle the moves of castling...\n\n\t\tif (castle_flag) {\n\n\t\t\tlet k_ch = ret.state[x1][y1];\n\t\t\tlet r_ch = ret.state[x2][y2];\n\n\t\t\tif (x2 > x1) {\t\t// Kingside castling\n\n\t\t\t\tret.state[x1][y1] = \"\";\n\t\t\t\tret.state[x2][y2] = \"\";\n\t\t\t\tret.state[6][y1] = k_ch;\n\t\t\t\tret.state[5][y1] = r_ch;\n\n\t\t\t} else {\t\t\t// Queenside castling\n\n\t\t\t\tret.state[x1][y1] = \"\";\n\t\t\t\tret.state[x2][y2] = \"\";\n\t\t\t\tret.state[2][y1] = k_ch;\n\t\t\t\tret.state[3][y1] = r_ch;\n\n\t\t\t}\n\t\t}\n\n\t\t// Handle enpassant captures...\n\n\t\tif (pawn_flag && capture_flag && ret.state[x2][y2] === \"\") {\n\t\t\tret.state[x2][y1] = \"\";\n\t\t}\n\n\t\t// Set the enpassant square... only if potential capturing pawns are present. Note\n\t\t// there are some subtleties where the pawns could be present but the capture is\n\t\t// illegal. Therefore, when comparing positions for triple-rep checks, we do some\n\t\t// extra tests, see the compare() method.\n\t\t//\n\t\t// Note that the code below relies on Point() generating null for offboard\n\t\t// coordinates, and ret.piece() accepting that null.\n\n\t\tret.enpassant = null;\n\n\t\tif (pawn_flag && y1 === 6 && y2 === 4) {\t\t// White pawn advanced 2\n\t\t\tif (ret.piece(Point(x1 - 1, 4)) === \"p\" || ret.piece(Point(x1 + 1, 4)) === \"p\") {\n\t\t\t\tret.enpassant = Point(x1, 5);\n\t\t\t}\n\t\t}\n\n\t\tif (pawn_flag && y1 === 1 && y2 === 3) {\t\t// Black pawn advanced 2\n\t\t\tif (ret.piece(Point(x1 - 1, 3)) === \"P\" || ret.piece(Point(x1 + 1, 3)) === \"P\") {\n\t\t\t\tret.enpassant = Point(x1, 2);\n\t\t\t}\n\t\t}\n\n\t\t// Actually make the move (except we already did castling)...\n\n\t\tif (castle_flag === false) {\n\t\t\tret.state[x2][y2] = ret.state[x1][y1];\n\t\t\tret.state[x1][y1] = \"\";\n\t\t}\n\n\t\t// Handle promotions...\n\n\t\tif (y2 === 0 && pawn_flag) {\n\t\t\tret.state[x2][y2] = promotion_char.toUpperCase();\n\t\t}\n\n\t\tif (y2 === 7 && pawn_flag) {\n\t\t\tret.state[x2][y2] = promotion_char;\t\t// Always lowercase.\n\t\t}\n\n\t\t// Swap who the current player is...\n\n\t\tret.active = white_flag ? \"b\" : \"w\";\n\n\t\treturn ret;\n\t},\n\n\t__delete_castling_char: function(delete_char) {\n\t\tlet new_rights = \"\";\n\t\tfor (let ch of this.castling) {\n\t\t\tif (ch !== delete_char) {\n\t\t\t\tnew_rights += ch;\n\t\t\t}\n\t\t}\n\t\tthis.castling = new_rights;\n\t},\n\n\t__delete_white_castling: function() {\n\t\tlet new_rights = \"\";\n\t\tfor (let ch of this.castling) {\n\t\t\tif (\"a\" <= ch && ch <= \"h\") {\t\t// i.e. black survives\n\t\t\t\tnew_rights += ch;\n\t\t\t}\n\t\t}\n\t\tthis.castling = new_rights;\n\t},\n\n\t__delete_black_castling: function() {\n\t\tlet new_rights = \"\";\n\t\tfor (let ch of this.castling) {\n\t\t\tif (\"A\" <= ch && ch <= \"H\") {\t\t// i.e. white survives\n\t\t\t\tnew_rights += ch;\n\t\t\t}\n\t\t}\n\t\tthis.castling = new_rights;\n\t},\n\n\tillegal: function(s) {\n\n\t\t// Returns \"\" if the move is legal, otherwise returns the reason it isn't.\n\n\t\tif (typeof s !== \"string\") {\n\t\t\treturn \"not a string\";\n\t\t}\n\n\t\t// s = this.c960_castling_converter(s);\t\t// Too many ramifications to think about.\n\n\t\tlet [x1, y1] = XY(s.slice(0, 2));\n\t\tlet [x2, y2] = XY(s.slice(2, 4));\n\n\t\tif (x1 < 0 || y1 < 0 || x1 > 7 || y1 > 7 || x2 < 0 || y2 < 0 || x2 > 7 || y2 > 7) {\n\t\t\treturn \"off board\";\n\t\t}\n\n\t\tif (this.active === \"w\" && this.is_white(Point(x1, y1)) === false) {\n\t\t\treturn \"wrong colour source\";\n\t\t}\n\n\t\tif (this.active === \"b\" && this.is_black(Point(x1, y1)) === false) {\n\t\t\treturn \"wrong colour source\";\n\t\t}\n\n\t\t// Colours must not be the same, except for castling.\n\t\t// Note that king-onto-rook is the only valid castling move...\n\n\t\tif (this.same_colour(Point(x1, y1), Point(x2, y2))) {\n\t\t\tif (this.state[x1][y1] === \"K\" && this.state[x2][y2] === \"R\") {\n\t\t\t\treturn this.illegal_castling(x1, y1, x2, y2);\n\t\t\t} else if (this.state[x1][y1] === \"k\" && this.state[x2][y2] === \"r\") {\n\t\t\t\treturn this.illegal_castling(x1, y1, x2, y2);\n\t\t\t} else {\n\t\t\t\treturn \"source and destination have same colour\";\n\t\t\t}\n\t\t}\n\n\t\tif ([\"N\", \"n\"].includes(this.state[x1][y1])) {\n\t\t\tif (Math.abs(x2 - x1) + Math.abs(y2 - y1) !== 3) {\n\t\t\t\treturn \"illegal knight movement\";\n\t\t\t}\n\t\t\tif (Math.abs(x2 - x1) === 0 || Math.abs(y2 - y1) === 0) {\n\t\t\t\treturn \"illegal knight movement\";\n\t\t\t}\n\t\t}\n\n\t\tif ([\"B\", \"b\"].includes(this.state[x1][y1])) {\n\t\t\tif (Math.abs(x2 - x1) !== Math.abs(y2 - y1)) {\n\t\t\t\treturn \"illegal bishop movement\";\n\t\t\t}\n\t\t}\n\n\t\tif ([\"R\", \"r\"].includes(this.state[x1][y1])) {\n\t\t\tif (Math.abs(x2 - x1) > 0 && Math.abs(y2 - y1) > 0) {\n\t\t\t\treturn \"illegal rook movement\";\n\t\t\t}\n\t\t}\n\n\t\tif ([\"Q\", \"q\"].includes(this.state[x1][y1])) {\n\t\t\tif (Math.abs(x2 - x1) !== Math.abs(y2 - y1)) {\n\t\t\t\tif (Math.abs(x2 - x1) > 0 && Math.abs(y2 - y1) > 0) {\n\t\t\t\t\treturn \"illegal queen movement\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Pawns...\n\n\t\tif ([\"P\", \"p\"].includes(this.state[x1][y1])) {\n\n\t\t\tif (Math.abs(x2 - x1) === 0) {\n\t\t\t\tif (this.state[x2][y2]) {\n\t\t\t\t\treturn \"pawn cannot capture forwards\";\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (Math.abs(x2 - x1) > 1) {\n\t\t\t\treturn \"pawn cannot move that far sideways\";\n\t\t\t}\n\n\t\t\tif (Math.abs(x2 - x1) === 1) {\n\n\t\t\t\tif (this.state[x2][y2] === \"\") {\n\t\t\t\t\tif (this.enpassant !== Point(x2, y2)) {\n\t\t\t\t\t\treturn \"pawn cannot capture thin air\";\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (Math.abs(y2 - y1) !== 1) {\n\t\t\t\t\treturn \"pawn must move 1 forward when capturing\";\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (this.state[x1][y1] === \"P\") {\n\t\t\t\tif (y1 !== 6) {\n\t\t\t\t\tif (y2 - y1 !== -1) {\n\t\t\t\t\t\treturn \"pawn must move forwards 1\";\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (y2 - y1 !== -1 && y2 - y1 !== -2) {\n\t\t\t\t\t\treturn \"pawn must move forwards 1 or 2\";\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (this.state[x1][y1] === \"p\") {\n\t\t\t\tif (y1 !== 1) {\n\t\t\t\t\tif (y2 - y1 !== 1) {\n\t\t\t\t\t\treturn \"pawn must move forwards 1\";\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (y2 - y1 !== 1 && y2 - y1 !== 2) {\n\t\t\t\t\t\treturn \"pawn must move forwards 1 or 2\";\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Kings...\n\n\t\tif ([\"K\", \"k\"].includes(this.state[x1][y1])) {\n\n\t\t\tif (Math.abs(y2 - y1) > 1) {\n\t\t\t\treturn \"illegal king movement\";\n\t\t\t}\n\n\t\t\tif (Math.abs(x2 - x1) > 1) {\n\t\t\t\treturn \"illegal king movement\";\n\t\t\t}\n\t\t}\n\n\t\t// Check for blockers (pieces between source and dest).\n\n\t\tif ([\"K\", \"Q\", \"R\", \"B\", \"P\", \"k\", \"q\", \"r\", \"b\", \"p\"].includes(this.state[x1][y1])) {\n\t\t\tif (this.los(x1, y1, x2, y2) === false) {\n\t\t\t\treturn \"movement blocked\";\n\t\t\t}\n\t\t}\n\n\t\t// Check promotion and string lengths...\n\t\t// We DO NOT tolerate missing promotion characters.\n\n\t\tif ((y1 === 1 && this.state[x1][y1] === \"P\") || (y1 === 6 && this.state[x1][y1] === \"p\")) {\n\n\t\t\tif (s.length !== 5) {\n\t\t\t\treturn \"bad string length\";\n\t\t\t}\n\n\t\t\tlet promotion = s[4];\n\n\t\t\tif (promotion !== \"q\" && promotion !== \"r\" && promotion !== \"b\" && promotion !== \"n\") {\n\t\t\t\treturn \"move requires a valid promotion piece\";\n\t\t\t}\n\n\t\t} else {\n\n\t\t\tif (s.length !== 4) {\n\t\t\t\treturn \"bad string length\";\n\t\t\t}\n\n\t\t}\n\n\t\t// Check for check...\n\n\t\tlet tmp = this.move(s);\n\t\tif (tmp.can_capture_king()) {\n\t\t\treturn \"king in check\";\n\t\t}\n\n\t\treturn \"\";\n\t},\n\n\tillegal_castling: function(x1, y1, x2, y2) {\n\n\t\t// We can assume a king is on [x1, y1] and a same-colour rook is on [x2, y2]\n\n\t\tif (y1 !== y2) {\n\t\t\treturn \"cannot castle vertically\";\n\t\t}\n\n\t\tlet colour = this.colour(Point(x1, y1));\n\n\t\tif (colour === \"w\" && y1 !== 7) {\n\t\t\treturn \"cannot castle off the back rank\";\n\t\t}\n\n\t\tif (colour === \"b\" && y1 !== 0) {\n\t\t\treturn \"cannot castle off the back rank\";\n\t\t}\n\n\t\t// Check for the required castling rights character...\n\n\t\tlet required_ch;\n\n\t\tif (colour === \"w\") {\n\t\t\trequired_ch = Point(x2, y2).s[0].toUpperCase();\n\t\t} else {\n\t\t\trequired_ch = Point(x2, y2).s[0];\n\t\t}\n\n\t\tif (this.castling.includes(required_ch) === false) {\n\t\t\treturn `lost the right to castle - needed ${required_ch}`;\n\t\t}\n\n\t\tlet king_target_x;\n\t\tlet rook_target_x;\n\n\t\tif (x1 < x2) {\t\t\t\t// Castling kingside\n\t\t\tking_target_x = 6;\n\t\t\trook_target_x = 5;\n\t\t} else {\t\t\t\t\t// Castling queenside\n\t\t\tking_target_x = 2;\n\t\t\trook_target_x = 3;\n\t\t}\n\n\t\tlet king_path = NumbersBetween(x1, king_target_x);\n\t\tlet rook_path = NumbersBetween(x2, rook_target_x);\n\n\t\t// Check for blockers and checks...\n\n\t\tfor (let x of king_path) {\n\t\t\tif (this.attacked(Point(x, y1), this.active)) {\n\t\t\t\treturn \"cannot castle [out of / through / into] check\";\n\t\t\t}\n\t\t\tif (x === x1 || x === x2) {\n\t\t\t\tcontinue;\t\t\t\t\t// After checking for checks\n\t\t\t}\n\t\t\tif (this.state[x][y1]) {\n\t\t\t\treturn \"castling blocked for king movement\";\n\t\t\t}\n\t\t}\n\n\t\tfor (let x of rook_path) {\n\t\t\tif (x === x1 || x === x2) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (this.state[x][y1]) {\n\t\t\t\treturn \"castling blocked for rook movement\";\n\t\t\t}\n\t\t}\n\n\t\t// Check that the king doesn't end up in check anyway...\n\t\t// q1nnkbbr/p1pppppp/8/1P6/8/3NN3/1PPPPPPP/rR2KBBR w BHh - 0 5\n\n\t\tlet tmp = this.move(Point(x1, y1).s + Point(x2, y2).s);\n\n\t\tif (tmp.attacked(Point(king_target_x, y1), this.active)) {\n\t\t\treturn \"king ends in check\";\n\t\t}\n\n\t\treturn \"\";\n\t},\n\n\tsequence_illegal: function(moves) {\n\n\t\tlet pos = this;\n\n\t\tfor (let s of moves) {\n\t\t\tlet reason = pos.illegal(s);\n\t\t\tif (reason) {\n\t\t\t\treturn `${s} - ${reason}`;\n\t\t\t}\n\t\t\tpos = pos.move(s);\n\t\t}\n\n\t\treturn \"\";\n\t},\n\n\tcan_capture_king: function() {\n\n\t\t// Can the side to move capture the opponent's king? Helper function for illegal() etc.\n\t\t// But this is slow, do not use when king location is known - just call attacked() instead.\n\n\t\tlet kch = this.active === \"w\" ? \"k\" : \"K\";\t\t\t// i.e. the INACTIVE king\n\t\tlet opp_colour = this.active === \"w\" ? \"b\" : \"w\";\n\n\t\tfor (let x = 0; x < 8; x++) {\n\t\t\tfor (let y = 0; y < 8; y++) {\n\t\t\t\tif (this.state[x][y] === kch) {\n\t\t\t\t\treturn this.attacked(Point(x, y), opp_colour);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\t\t// King not actually present...\n\t},\n\n\tking_in_check: function() {\n\n\t\t// Don't call this if the king position is already\n\t\t// known since this method uses an expensive find().\n\n\t\tlet kch = this.active === \"w\" ? \"K\" : \"k\";\n\t\tlet king_loc = this.find(kch)[0];\n\n\t\tif (king_loc === undefined) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn this.attacked(king_loc, this.active);\n\t},\n\n\tlos: function(x1, y1, x2, y2) {\t\t// Returns false if there is no \"line of sight\" between the 2 points.\n\n\t\t// Check the line is straight....\n\n\t\tif (Math.abs(x2 - x1) > 0 && Math.abs(y2 - y1) > 0) {\n\t\t\tif (Math.abs(x2 - x1) !== Math.abs(y2 - y1)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\tlet step_x;\n\t\tlet step_y;\n\n\t\tif (x1 === x2) step_x = 0;\n\t\tif (x1 < x2) step_x = 1;\n\t\tif (x1 > x2) step_x = -1;\n\n\t\tif (y1 === y2) step_y = 0;\n\t\tif (y1 < y2) step_y = 1;\n\t\tif (y1 > y2) step_y = -1;\n\n\t\tlet x = x1;\n\t\tlet y = y1;\n\n\t\twhile (true) {\n\n\t\t\tx += step_x;\n\t\t\ty += step_y;\n\n\t\t\tif (x === x2 && y === y2) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif (this.state[x][y]) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t},\n\n\tattacked: function(target, my_colour) {\n\n\t\tif (!my_colour) {\n\t\t\tthrow \"attacked(): no colour given\";\n\t\t}\n\n\t\tif (!target) {\t\t// Because it was null from Point(foo) perhaps.\n\t\t\treturn false;\n\t\t}\n\n\t\t// Attacks along the lines...\n\n\t\tfor (let step_x = -1; step_x <= 1; step_x++) {\n\n\t\t\tfor (let step_y = -1; step_y <= 1; step_y++) {\n\n\t\t\t\tif (step_x === 0 && step_y === 0) continue;\n\n\t\t\t\tif (this.line_attack(target, step_x, step_y, my_colour)) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Knights...\n\n\t\tfor (let d of [[-2, -1], [-2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2], [2, -1], [2, 1]]) {\n\n\t\t\tlet x = target.x + d[0];\n\t\t\tlet y = target.y + d[1];\n\n\t\t\tif (x < 0 || x > 7 || y < 0 || y > 7) continue;\n\n\t\t\tif ([\"N\", \"n\"].includes(this.state[x][y])) {\n\t\t\t\tif (this.colour(Point(x, y)) === my_colour) continue;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t},\n\n\tline_attack: function(target, step_x, step_y, my_colour) {\n\n\t\t// Is the target square under attack via the line specified by step_x and step_y (which are both -1, 0, or 1) ?\n\n\t\tif (!my_colour) {\n\t\t\tthrow \"line_attack(): no colour given\";\n\t\t}\n\n\t\tif (!target) {\t\t// Because it was null from Point(foo) perhaps.\n\t\t\treturn false;\n\t\t}\n\n\t\tif (step_x === 0 && step_y === 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\tlet x = target.x;\n\t\tlet y = target.y;\n\n\t\tlet ranged_attackers = [\"Q\", \"q\", \"R\", \"r\"];\t// Ranged attackers that can go in a cardinal direction.\n\t\tif (step_x !== 0 && step_y !== 0) {\n\t\t\tranged_attackers = [\"Q\", \"q\", \"B\", \"b\"];\t// Ranged attackers that can go in a diagonal direction.\n\t\t}\n\n\t\tlet iteration = 0;\n\n\t\twhile (true) {\n\n\t\t\titeration++;\n\n\t\t\tx += step_x;\n\t\t\ty += step_y;\n\n\t\t\tif (x < 0 || x > 7 || y < 0 || y > 7) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tif (this.state[x][y] === \"\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// So there's something here. Must return now.\n\n\t\t\tif (this.colour(Point(x, y)) === my_colour) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// We now know the piece is hostile. This allows us to mostly not care\n\t\t\t// about distinctions between \"Q\" and \"q\", \"R\" and \"r\", etc.\n\n\t\t\t// Is it one of the ranged attacker types?\n\n\t\t\tif (ranged_attackers.includes(this.state[x][y])) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// Pawns and kings are special cases (attacking iff it's the first iteration)\n\n\t\t\tif (iteration === 1) {\n\n\t\t\t\tif ([\"K\", \"k\"].includes(this.state[x][y])) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tif (Math.abs(step_x) === 1) {\n\n\t\t\t\t\tif (this.state[x][y] === \"p\" && step_y === -1) {\t// Black pawn in attacking position\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (this.state[x][y] === \"P\" && step_y === 1) {\t\t// White pawn in attacking position\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\t},\n\n\tfind: function(piece, startx, starty, endx, endy) {\n\n\t\t// Find all pieces of the specified type (colour-specific).\n\t\t// Search range is INCLUSIVE. Result returned as a list of points.\n\t\t// You can call this function with just a piece to search the whole board.\n\n\t\tif (startx === undefined) startx = 0;\n\t\tif (starty === undefined) starty = 0;\n\t\tif (endx === undefined) endx = 7;\n\t\tif (endy === undefined) endy = 7;\n\n\t\t// Calling with out of bounds args should also work...\n\n\t\tif (startx < 0) startx = 0;\n\t\tif (startx > 7) startx = 7;\n\t\tif (starty < 0) starty = 0;\n\t\tif (starty > 7) starty = 7;\n\t\tif (endx < 0) endx = 0;\n\t\tif (endx > 7) endx = 7;\n\t\tif (endy < 0) endy = 0;\n\t\tif (endy > 7) endy = 7;\n\n\t\tlet ret = [];\n\n\t\tfor (let x = startx; x <= endx; x++) {\n\t\t\tfor (let y = starty; y <= endy; y++) {\n\t\t\t\tif (this.state[x][y] === piece) {\n\t\t\t\t\tret.push(Point(x, y));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t},\n\n\tfind_castling_move: function(long_flag) {\t\t// Returns a (possibly illegal) castling move (e.g. \"e1h1\") or \"\"\n\n\t\tlet king_loc;\n\n\t\tif (this.active === \"w\") {\n\t\t\tking_loc = this.find(\"K\", 0, 7, 7, 7)[0];\n\t\t} else {\n\t\t\tking_loc = this.find(\"k\", 0, 0, 7, 0)[0];\n\t\t}\n\n\t\tif (king_loc === undefined) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\tlet possible_rights_chars;\n\n\t\tif (this.active === \"w\") {\n\t\t\tpossible_rights_chars = [\"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\", \"H\"];\n\t\t} else {\n\t\t\tpossible_rights_chars = [\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\"];\n\t\t}\n\n\t\tif (long_flag) {\n\t\t\tpossible_rights_chars = possible_rights_chars.slice(0, king_loc.x);\n\t\t\tpossible_rights_chars.reverse();\t\t// So we propose the shortest move first, if more than 1 is allowed by the rights.\n\t\t} else {\n\t\t\tpossible_rights_chars = possible_rights_chars.slice(king_loc.x + 1);\n\t\t}\n\n\t\tfor (let ch of possible_rights_chars) {\n\t\t\tif (this.castling.includes(ch)) {\n\t\t\t\tif (this.active === \"w\") {\n\t\t\t\t\treturn king_loc.s + ch.toLowerCase() + \"1\";\n\t\t\t\t} else {\n\t\t\t\t\treturn king_loc.s + ch + \"8\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn \"\";\n\t},\n\n\tparse_pgn: function(s) {\t\t// Returns a UCI move and an error message.\n\n\t\t// Replace fruity dash characters with proper ASCII dash \"-\"\n\n\t\tfor (let n of [8208, 8210, 8211, 8212, 8213, 8722]) {\n\t\t\ts = ReplaceAll(s, String.fromCodePoint(n), \"-\");\n\t\t}\n\n\t\t// If the string contains any dots it'll be something like \"1.e4\" or \"...e4\" or whatnot...\n\n\t\tlet lio = s.lastIndexOf(\".\");\n\t\tif (lio !== -1) {\n\t\t\ts = s.slice(lio + 1);\n\t\t}\n\n\t\t// At this point, if s is actually a UCI string (which it won't be in real PGN) we can return it.\n\t\t// This is a hack to allow pasting of stuff from non-PGN sources I guess...\n\n\t\tif (s.length === 4 || (s.length === 5 && [\"q\", \"r\", \"b\", \"n\"].includes(s[4]))) {\n\t\t\tif (s[0] >= \"a\" && s[0] <= \"h\" &&\n\t\t\t\ts[1] >= \"1\" && s[1] <= \"8\" &&\n\t\t\t\ts[2] >= \"a\" && s[2] <= \"h\" &&\n\t\t\t\ts[3] >= \"1\" && s[3] <= \"8\"\n\t\t\t) {\n\t\t\t\tlet tmp = this.c960_castling_converter(s);\n\t\t\t\tif (!this.illegal(tmp)) {\n\t\t\t\t\treturn [tmp, \"\"];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Delete things we don't need...\n\n\t\ts = ReplaceAll(s, \"x\", \"\");\n\t\ts = ReplaceAll(s, \"+\", \"\");\n\t\ts = ReplaceAll(s, \"#\", \"\");\n\t\ts = ReplaceAll(s, \"!\", \"\");\n\t\ts = ReplaceAll(s, \"?\", \"\");\n\n\t\t// Fix castling with zeroes...\n\n\t\ts = ReplaceAll(s, \"0-0-0\", \"O-O-O\");\n\t\ts = ReplaceAll(s, \"0-0\", \"O-O\");\n\n\t\tif (s.toUpperCase() === \"O-O\") {\n\n\t\t\tlet mv = this.find_castling_move(false);\n\n\t\t\tif (mv && !this.illegal(mv)) {\n\t\t\t\treturn [mv, \"\"];\n\t\t\t} else {\n\t\t\t\treturn [\"\", \"illegal castling\"];\n\t\t\t}\n\t\t}\n\n\t\tif (s.toUpperCase() === \"O-O-O\") {\n\n\t\t\tlet mv = this.find_castling_move(true);\n\n\t\t\tif (mv && !this.illegal(mv)) {\n\t\t\t\treturn [mv, \"\"];\n\t\t\t} else {\n\t\t\t\treturn [\"\", \"illegal castling\"];\n\t\t\t}\n\t\t}\n\n\t\t// Just in case, delete any \"-\" characters (after handling castling, of course)...\n\n\t\ts = ReplaceAll(s, \"-\", \"\");\n\n\t\t// If an = sign is present, save promotion string, then delete it from s...\n\n\t\tlet promotion = \"\";\n\n\t\tif (s[s.length - 2] === \"=\") {\n\t\t\tpromotion = s[s.length - 1].toLowerCase();\n\t\t\ts = s.slice(0, -2);\n\t\t}\n\n\t\t// A lax writer might also write the promotion string without an equals sign...\n\n\t\tif (promotion === \"\") {\n\t\t\tif ([\"Q\", \"R\", \"B\", \"N\", \"q\", \"r\", \"b\", \"n\"].includes(s[s.length - 1])) {\n\t\t\t\tpromotion = s[s.length - 1].toLowerCase();\n\t\t\t\ts = s.slice(0, -1);\n\t\t\t}\n\t\t}\n\n\t\t// If the piece isn't specified (with an uppercase letter) then it's a pawn move.\n\t\t// Let's add P to the start of the string to keep the string format consistent...\n\n\t\tif ([\"K\", \"Q\", \"R\", \"B\", \"N\", \"P\"].includes(s[0]) === false) {\n\t\t\ts = \"P\" + s;\n\t\t}\n\n\t\t// Now this works...\n\n\t\tlet piece = s[0];\n\n\t\t// We care about the colour of the piece, so make black pieces lowercase...\n\n\t\tif (this.active === \"b\") {\n\t\t\tpiece = piece.toLowerCase();\n\t\t}\n\n\t\t// The last 2 characters specify the target point. We've removed all trailing\n\t\t// garbage that could interfere with this fact.\n\n\t\tlet dest = Point(s.slice(s.length - 2, s.length));\n\t\tif (!dest) {\n\t\t\treturn [\"\", \"invalid destination\"];\n\t\t}\n\n\t\t// Any characters between the piece and target should be disambiguators...\n\n\t\tlet disambig = s.slice(1, -2);\n\n\t\tlet startx = 0;\n\t\tlet endx = 7;\n\n\t\tlet starty = 0;\n\t\tlet endy = 7;\n\n\t\tfor (let c of disambig) {\n\t\t\tif (c >= \"a\" && c <= \"h\") {\n\t\t\t\tstartx = c.charCodeAt(0) - 97;\n\t\t\t\tendx = startx;\n\t\t\t}\n\t\t\tif (c >= \"1\" && c <= \"8\") {\n\t\t\t\tstarty = 7 - (c.charCodeAt(0) - 49);\n\t\t\t\tendy = starty;\n\t\t\t}\n\t\t}\n\n\t\t// If it's a pawn and hasn't been disambiguated then it is moving forwards...\n\n\t\tif (piece === \"P\" || piece === \"p\") {\n\t\t\tif (disambig.length === 0) {\n\t\t\t\tstartx = dest.x;\n\t\t\t\tendx = dest.x;\n\t\t\t}\n\t\t}\n\n\t\tlet sources = this.find(piece, startx, starty, endx, endy);\n\n\t\tif (sources.length === 0) {\n\t\t\treturn [\"\", \"piece not found\"];\n\t\t}\n\n\t\tlet possible_moves = [];\n\n\t\tfor (let source of sources) {\n\t\t\tpossible_moves.push(source.s + dest.s + promotion);\n\t\t}\n\n\t\tlet valid_moves = [];\n\n\t\tfor (let move of possible_moves) {\n\t\t\tif (this.illegal(move) === \"\") {\n\t\t\t\tvalid_moves.push(move);\n\t\t\t}\n\t\t}\n\n\t\tif (valid_moves.length === 1) {\n\t\t\treturn [valid_moves[0], \"\"];\n\t\t}\n\n\t\tif (valid_moves.length === 0) {\n\t\t\treturn [\"\", \"piece found but move illegal\"];\n\t\t}\n\n\t\tif (valid_moves.length > 1) {\n\t\t\treturn [\"\", `ambiguous moves: [${valid_moves}]`];\n\t\t}\n\t},\n\n\tpiece: function(point) {\n\t\tif (!point) return \"\";\n\t\treturn this.state[point.x][point.y];\n\t},\n\n\tis_white: function(point) {\n\t\tlet piece = this.piece(point);\n\t\treturn [\"K\", \"Q\", \"R\", \"B\", \"N\", \"P\"].includes(piece);\t\t// Can't do \"KQRBNP\".includes() as that catches \"\".\n\t},\n\n\tis_black: function(point) {\n\t\tlet piece = this.piece(point);\n\t\treturn [\"k\", \"q\", \"r\", \"b\", \"n\", \"p\"].includes(piece);\t\t// Can't do \"kqrbnp\".includes() as that catches \"\".\n\t},\n\n\tis_empty: function(point) {\n\t\treturn this.piece(point) === \"\";\n\t},\n\n\tcolour: function(point) {\n\t\tlet piece = this.piece(point);\n\t\tif (piece === \"\") {\n\t\t\treturn \"\";\n\t\t}\n\t\tif ([\"K\", \"Q\", \"R\", \"B\", \"N\", \"P\"].includes(piece)) {\n\t\t\treturn \"w\";\n\t\t}\n\t\treturn \"b\";\n\t},\n\n\tsame_colour: function(point1, point2) {\n\t\treturn this.colour(point1) === this.colour(point2);\n\t},\n\n\tmovegen: function(one_only = false) {\n\n\t\tlet moves = [];\n\n\t\tfor (let x = 0; x < 8; x++) {\n\n\t\t\tfor (let y = 0; y < 8; y++) {\n\n\t\t\t\tlet source = Point(x, y);\n\n\t\t\t\tif (this.colour(source) !== this.active) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlet piece = this.state[x][y];\n\n\t\t\t\tif (piece !== \"K\" && piece !== \"k\") {\t\t// We don't include kings because castling is troublesome.\n\n\t\t\t\t\tfor (let slider of movegen_sliders[piece]) {\n\n\t\t\t\t\t\t// The sliders are lists where, if one move is blocked, every subsequent move in the slider is also\n\t\t\t\t\t\t// blocked. Note that the test is \"blocked / offboard\". The test is not \"is illegal\" - sometimes one\n\t\t\t\t\t\t// move will be illegal but a move further down the slider will be legal - e.g. if it blocks a check.\n\n\t\t\t\t\t\tfor (let [dx, dy] of slider) {\n\n\t\t\t\t\t\t\tlet x2 = x + dx;\n\t\t\t\t\t\t\tlet y2 = y + dy;\n\n\t\t\t\t\t\t\tif (x2 < 0 || x2 > 7 || y2 < 0 || y2 > 7) {\t\t// No move further along the slider will be legal.\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tlet dest = Point(x2, y2);\n\t\t\t\t\t\t\tlet dest_colour = this.colour(dest);\n\n\t\t\t\t\t\t\tif (dest_colour === this.active) {\t\t\t\t// No move further along the slider will be legal.\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tlet move = source.s + dest.s;\n\n\t\t\t\t\t\t\tif ((piece === \"P\" && dest.y === 0) || (piece === \"p\" && dest.y === 7)) {\n\t\t\t\t\t\t\t\tif (this.illegal(move + \"q\") === \"\") {\n\t\t\t\t\t\t\t\t\tmoves.push(move + \"q\");\n\t\t\t\t\t\t\t\t\tif (one_only) {\n\t\t\t\t\t\t\t\t\t\treturn moves;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tmoves.push(move + \"r\");\n\t\t\t\t\t\t\t\t\tmoves.push(move + \"b\");\n\t\t\t\t\t\t\t\t\tmoves.push(move + \"n\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (this.illegal(move) === \"\") {\n\t\t\t\t\t\t\t\t\tmoves.push(move);\n\t\t\t\t\t\t\t\t\tif (one_only) {\n\t\t\t\t\t\t\t\t\t\treturn moves;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (dest_colour !== \"\") {\t\t\t\t\t\t// No move further along the slider will be legal.\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t} else {\n\n\t\t\t\t\t// King moves that involve vertical direction...\n\n\t\t\t\t\tfor (let dx of [-1, 0, 1]) {\n\t\t\t\t\t\tfor (let dy of [-1, 1]) {\n\t\t\t\t\t\t\tlet x2 = x + dx;\n\t\t\t\t\t\t\tlet y2 = y + dy;\n\t\t\t\t\t\t\tif (x2 < 0 || x2 > 7 || y2 < 0 || y2 > 7) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tlet dest = Point(x2, y2);\n\t\t\t\t\t\t\tlet move = source.s + dest.s;\n\t\t\t\t\t\t\tif (this.illegal(move) === \"\") {\n\t\t\t\t\t\t\t\tmoves.push(move);\n\t\t\t\t\t\t\t\tif (one_only) {\n\t\t\t\t\t\t\t\t\treturn moves;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Horizontal king moves (including castling moves)...\n\n\t\t\t\t\tfor (let x2 = 0; x2 < 8; x2++) {\n\t\t\t\t\t\tlet dest = Point(x2, y);\n\t\t\t\t\t\tlet move = source.s + dest.s;\n\t\t\t\t\t\tif (this.illegal(move) === \"\") {\n\t\t\t\t\t\t\tmoves.push(move);\n\t\t\t\t\t\t\tif (one_only) {\n\t\t\t\t\t\t\t\treturn moves;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn moves;\n\t},\n\n\tnice_movegen: function() {\n\t\treturn this.movegen().map(s => this.nice_string(s));\n\t},\n\n\tno_moves: function() {\n\t\treturn this.movegen(true).length === 0;\n\t},\n\n\tc960_castling_converter: function(s) {\n\n\t\t// Given some move s, convert it to the new Chess 960 castling format if needed.\n\n\t\tif (s === \"e1g1\" && this.state[4][7] === \"K\" && this.castling.includes(\"G\") === false) return \"e1h1\";\n\t\tif (s === \"e1c1\" && this.state[4][7] === \"K\" && this.castling.includes(\"C\") === false) return \"e1a1\";\n\t\tif (s === \"e8g8\" && this.state[4][0] === \"k\" && this.castling.includes(\"g\") === false) return \"e8h8\";\n\t\tif (s === \"e8c8\" && this.state[4][0] === \"k\" && this.castling.includes(\"c\") === false) return \"e8a8\";\n\t\treturn s;\n\t},\n\n\tnice_string: function(s) {\n\n\t\t// Given some raw (but valid) UCI move string, return a nice human-readable\n\t\t// string for display in the browser window. This string should never be\n\t\t// examined by the caller, merely displayed.\n\t\t//\n\t\t// Note that as of 1.1.6, all castling moves are expected to be king-onto-rook,\n\t\t// that is, Chess960 format.\n\n\t\t// s = this.c960_castling_converter(s);\t\t// Too many ramifications to think about.\n\n\t\tlet source = Point(s.slice(0, 2));\n\t\tlet dest = Point(s.slice(2, 4));\n\n\t\tif (!source || !dest) {\n\t\t\treturn \"??\";\n\t\t}\n\n\t\tlet piece = this.piece(source);\n\n\t\tif (piece === \"\") {\n\t\t\treturn \"??\";\n\t\t}\n\n\t\tlet check = \"\";\n\t\tlet next_board = this.move(s);\n\t\tlet opponent_king_char = this.active === \"w\" ? \"k\" : \"K\";\n\t\tlet opponent_king_square = this.find(opponent_king_char)[0];\t// Might be undefined on corrupt board...\n\n\t\tif (opponent_king_square && next_board.attacked(opponent_king_square, next_board.colour(opponent_king_square))) {\n\t\t\tif (next_board.no_moves()) {\n\t\t\t\tcheck = \"#\";\n\t\t\t} else {\n\t\t\t\tcheck = \"+\";\n\t\t\t}\n\t\t}\n\n\t\tif ([\"K\", \"k\", \"Q\", \"q\", \"R\", \"r\", \"B\", \"b\", \"N\", \"n\"].includes(piece)) {\n\n\t\t\tif ([\"K\", \"k\"].includes(piece)) {\n\t\t\t\tif (this.colour(dest) === this.colour(source)) {\n\t\t\t\t\tif (dest.x > source.x) {\n\t\t\t\t\t\treturn `O-O${check}`;\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn `O-O-O${check}`;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Would the move be ambiguous?\n\t\t\t// IMPORTANT: note that the actual move will not necessarily be valid_moves[0].\n\n\t\t\tlet possible_sources = this.find(piece);\n\t\t\tlet possible_moves = [];\n\t\t\tlet valid_moves = [];\n\n\t\t\tfor (let foo of possible_sources) {\n\t\t\t\tpossible_moves.push(foo.s + dest.s);\t\t// e.g. \"g1f3\" - note we are only dealing with pieces, so no worries about promotion\n\t\t\t}\n\n\t\t\tfor (let move of possible_moves) {\n\t\t\t\tif (this.illegal(move) === \"\") {\n\t\t\t\t\tvalid_moves.push(move);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (valid_moves.length > 2) {\n\n\t\t\t\t// Full disambiguation.\n\n\t\t\t\tif (this.piece(dest) === \"\") {\n\t\t\t\t\treturn piece.toUpperCase() + source.s + dest.s + check;\n\t\t\t\t} else {\n\t\t\t\t\treturn piece.toUpperCase() + source.s + \"x\" + dest.s + check;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (valid_moves.length === 2) {\n\n\t\t\t\t// Partial disambiguation.\n\n\t\t\t\tlet source1 = Point(valid_moves[0].slice(0, 2));\n\t\t\t\tlet source2 = Point(valid_moves[1].slice(0, 2));\n\n\t\t\t\tlet disambiguator;\n\n\t\t\t\tif (source1.x === source2.x) {\n\t\t\t\t\tdisambiguator = source.s[1];\t\t// Note source (the true source), not source1\n\t\t\t\t} else {\n\t\t\t\t\tdisambiguator = source.s[0];\t\t// Note source (the true source), not source1\n\t\t\t\t}\n\n\t\t\t\tif (this.piece(dest) === \"\") {\n\t\t\t\t\treturn piece.toUpperCase() + disambiguator + dest.s + check;\n\t\t\t\t} else {\n\t\t\t\t\treturn piece.toUpperCase() + disambiguator + \"x\" + dest.s + check;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// No disambiguation.\n\n\t\t\tif (this.piece(dest) === \"\") {\n\t\t\t\treturn piece.toUpperCase() + dest.s + check;\n\t\t\t} else {\n\t\t\t\treturn piece.toUpperCase() + \"x\" + dest.s + check;\n\t\t\t}\n\t\t}\n\n\t\t// So it's a pawn. Pawn moves are never ambiguous.\n\n\t\tlet ret;\n\n\t\tif (source.x === dest.x) {\n\t\t\tret = dest.s;\n\t\t} else {\n\t\t\tret = source.s[0] + \"x\" + dest.s;\n\t\t}\n\n\t\tif (s.length > 4) {\n\t\t\tret += \"=\";\n\t\t\tret += s[4].toUpperCase();\n\t\t}\n\n\t\tret += check;\n\n\t\treturn ret;\n\t},\n\n\tnext_number_string: function() {\n\t\tif (this.active === \"w\") {\n\t\t\treturn `${this.fullmove}.`;\n\t\t} else {\n\t\t\treturn `${this.fullmove}...`;\n\t\t}\n\t},\n\n\tfen: function(friendly_flag, book_flag) {\n\n\t\t// friendly_flag - for when the engine isn't the consumer.\n\t\t// book_flag - for when we should omit the move numbers.\n\n\t\tlet s = \"\";\n\n\t\tfor (let y = 0; y < 8; y++) {\n\n\t\t\tlet x = 0;\n\t\t\tlet blanks = 0;\n\n\t\t\twhile (true) {\n\n\t\t\t\tif (this.state[x][y] === \"\") {\n\t\t\t\t\tblanks++;\n\t\t\t\t} else {\n\t\t\t\t\tif (blanks > 0) {\n\t\t\t\t\t\ts += blanks.toString();\n\t\t\t\t\t\tblanks = 0;\n\t\t\t\t\t}\n\t\t\t\t\ts += this.state[x][y];\n\t\t\t\t}\n\n\t\t\t\tx++;\n\n\t\t\t\tif (x >= 8) {\n\t\t\t\t\tif (blanks > 0) {\n\t\t\t\t\t\ts += blanks.toString();\n\t\t\t\t\t}\n\t\t\t\t\tif (y < 7) {\n\t\t\t\t\t\ts += \"/\";\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlet ep_string = this.enpassant ? this.enpassant.s : \"-\";\n\t\tlet castling_string = this.castling !== \"\" ? this.castling : \"-\";\n\n\t\t// While internally we always use Chess960 format, we can return a more friendly\n\t\t// FEN if asked (and if the position is normal Chess). Relies on our normalchess\n\t\t// flag being accurate... (potential for bugs there).\n\n\t\tif (friendly_flag && this.normalchess && castling_string !== \"-\") {\n\t\t\tlet new_castling_string = \"\";\n\t\t\tif (castling_string.includes(\"H\")) new_castling_string += \"K\";\n\t\t\tif (castling_string.includes(\"A\")) new_castling_string += \"Q\";\n\t\t\tif (castling_string.includes(\"h\")) new_castling_string += \"k\";\n\t\t\tif (castling_string.includes(\"a\")) new_castling_string += \"q\";\n\t\t\tcastling_string = new_castling_string;\n\t\t}\n\n\t\tif (book_flag) {\n\t\t\treturn s + ` ${this.active} ${castling_string} ${ep_string}`;\n\t\t} else {\n\t\t\treturn s + ` ${this.active} ${castling_string} ${ep_string} ${this.halfmove} ${this.fullmove}`;\n\t\t}\n\t},\n\n\tinsufficient_material: function() {\n\n\t\t// There are some subtleties around help-mates and also positions where\n\t\t// mate is forced despite there not being enough material if the pieces\n\t\t// were elsewhere. This code below should have no false positives...\n\n\t\tlet minors = 0;\n\n\t\tfor (let x = 0; x < 8; x++) {\n\t\t\tfor (let y = 0; y < 8; y++) {\n\t\t\t\tswitch (this.state[x][y]) {\n\t\t\t\tcase \"Q\":\n\t\t\t\tcase \"q\":\n\t\t\t\tcase \"R\":\n\t\t\t\tcase \"r\":\n\t\t\t\tcase \"P\":\n\t\t\t\tcase \"p\":\n\t\t\t\t\treturn false;\n\t\t\t\tcase \"B\":\n\t\t\t\tcase \"b\":\n\t\t\t\tcase \"N\":\n\t\t\t\tcase \"n\":\n\t\t\t\t\tminors++;\n\t\t\t\t\tif (minors >= 2) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t},\n\n\tgraphic: function() {\n\t\tlet units = [];\n\t\tfor (let y = 0; y < 8; y++) {\n\t\t\tunits.push(\"\\n\");\n\t\t\tfor (let x = 0; x < 8; x++) {\n\t\t\t\tunits.push(this.state[x][y] === \"\" ? \".\" : this.state[x][y]);\n\t\t\t\tif (x < 7) {\n\t\t\t\t\tunits.push(\" \");\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (y === 7) {\n\t\t\t\tunits.push(\"  \");\n\t\t\t\tunits.push(this.fen(false));\n\t\t\t}\n\t\t}\n\t\tunits.push(\"\\n\");\n\t\treturn units.join(\"\");\n\t},\n\n\thas_legal_ep_capture: function() {\n\n\t\tif (!this.enpassant) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Find the one or two pawns of the correct colour and location that could do the e.p. capture.\n\t\t// Check whether these moves are actually legal. If either of them is, return true.\n\n\t\tlet ep = this.enpassant;\n\t\tlet pawn_char;\n\t\tlet source_y;\n\n\t\tif (this.active === \"w\") {\n\t\t\tpawn_char = \"P\";\n\t\t\tsource_y = ep.y + 1;\n\t\t} else {\n\t\t\tpawn_char = \"p\";\n\t\t\tsource_y = ep.y - 1;\n\t\t}\n\n\t\tfor (let dx = -1; dx <= 1; dx += 2) {\n\t\t\tlet source = Point(ep.x + dx, source_y);\n\t\t\tif (!source) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (this.piece(source) !== pawn_char) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!this.illegal(source.s + ep.s)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t},\n\n\tcompare: function(other, strict = false) {\n\t\tif (this.active !== other.active) {\n\t\t\treturn false;\n\t\t}\n\t\tif (this.castling !== other.castling) {\n\t\t\treturn false;\n\t\t}\n\t\tfor (let x = 0; x < 8; x++) {\n\t\t\tfor (let y = 0; y < 8; y++) {\n\t\t\t\tif (this.state[x][y] !== other.state[x][y]) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (strict) {\n\t\t\tlet this_real_ep = this.has_legal_ep_capture() ? this.enpassant : null;\n\t\t\tlet other_real_ep = other.has_legal_ep_capture() ? other.enpassant : null;\n\t\t\tif (this_real_ep !== other_real_ep) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} else {\n\t\t\tif (this.enpassant !== other.enpassant) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\treturn true;\n\t},\n\n\tcopy: function() {\n\t\treturn NewPosition(this.state, this.active, this.castling, this.enpassant, this.halfmove, this.fullmove, this.normalchess);\n\t},\n};\n\nfunction NewPosition(state = null, active = \"w\", castling = \"\", enpassant = null, halfmove = 0, fullmove = 1, normalchess = false) {\n\n\tlet p = Object.create(position_prototype);\n\n\tp.state = [\n\t\t[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n\t\t[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n\t\t[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n\t\t[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n\t\t[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n\t\t[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n\t\t[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n\t\t[\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"\"],\n\t];\n\n\tif (state) {\n\t\tfor (let x = 0; x < 8; x++) {\n\t\t\tfor (let y = 0; y < 8; y++) {\n\t\t\t\tlet piece = state[x][y];\n\t\t\t\tif (piece) {\n\t\t\t\t\tp.state[x][y] = piece;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tp.active = active;\n\tp.castling = castling;\n\tp.enpassant = enpassant;\n\tp.halfmove = halfmove;\n\tp.fullmove = fullmove;\n\tp.normalchess = normalchess;\n\n\treturn p;\n}\n"
  },
  {
    "path": "files/src/renderer/41_fen.js",
    "content": "\"use strict\";\n\nfunction LoadFEN(fen) {\n\n\tif (fen.length > 200) {\n\t\tthrow \"Invalid FEN - size\";\n\t}\n\n\tlet ret = NewPosition();\n\n\tfen = ReplaceAll(fen, \"\\t\", \" \");\n\tfen = ReplaceAll(fen, \"\\n\", \" \");\n\tfen = ReplaceAll(fen, \"\\r\", \" \");\n\n\tlet tokens = fen.split(\" \").filter(z => z !== \"\");\n\n\tif (tokens.length === 1) tokens.push(\"w\");\n\tif (tokens.length === 2) tokens.push(\"-\");\n\tif (tokens.length === 3) tokens.push(\"-\");\n\tif (tokens.length === 4) tokens.push(\"0\");\n\tif (tokens.length === 5) tokens.push(\"1\");\n\n\tif (tokens.length !== 6) {\n\t\tthrow \"Invalid FEN - token count\";\n\t}\n\n\tif (tokens[0].endsWith(\"/\")) {\t\t\t\t// Some FEN writer does this\n\t\ttokens[0] = tokens[0].slice(0, -1);\n\t}\n\n\tlet rows = tokens[0].split(\"/\");\n\n\tif (rows.length > 8) {\n\t\tthrow \"Invalid FEN - board row count\";\n\t}\n\n\tfor (let y = 0; y < rows.length; y++) {\n\n\t\tlet x = 0;\n\n\t\tfor (let c of rows[y]) {\n\n\t\t\tif (x > 7) {\n\t\t\t\tthrow \"Invalid FEN - row length\";\n\t\t\t}\n\n\t\t\tif ([\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"].includes(c)) {\n\t\t\t\tx += parseInt(c, 10);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ([\"K\", \"k\", \"Q\", \"q\", \"R\", \"r\", \"B\", \"b\", \"N\", \"n\", \"P\", \"p\"].includes(c)) {\n\t\t\t\tret.state[x][y] = c;\n\t\t\t\tx++;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tthrow \"Invalid FEN - unknown piece\";\n\t\t}\n\t}\n\n\ttokens[1] = tokens[1].toLowerCase();\n\tif (tokens[1] !== \"w\" && tokens[1] !== \"b\") {\n\t\tthrow \"Invalid FEN - active player\";\n\t}\n\tret.active = tokens[1];\n\n\tret.halfmove = parseInt(tokens[4], 10);\n\tif (Number.isNaN(ret.halfmove)) {\n\t\tthrow \"Invalid FEN - halfmoves\";\n\t}\n\n\tret.fullmove = parseInt(tokens[5], 10);\n\tif (Number.isNaN(ret.fullmove)) {\n\t\tthrow \"Invalid FEN - fullmoves\";\n\t}\n\n\t// Some more validity checks...\n\n\tlet white_kings = 0;\n\tlet black_kings = 0;\n\n\tfor (let x = 0; x < 8; x++) {\n\t\tfor (let y = 0; y < 8; y++) {\n\t\t\tif (ret.state[x][y] === \"K\") white_kings++;\n\t\t\tif (ret.state[x][y] === \"k\") black_kings++;\n\t\t}\n\t}\n\n\tif (white_kings !== 1 || black_kings !== 1) {\n\t\tthrow \"Invalid FEN - number of kings\";\n\t}\n\n\tfor (let x = 0; x < 8; x++) {\n\t\tfor (let y of [0, 7]) {\n\t\t\tif (ret.state[x][y] === \"P\" || ret.state[x][y] === \"p\") {\n\t\t\t\tthrow \"Invalid FEN - pawn position\";\n\t\t\t}\n\t\t}\n\t}\n\n\tlet opponent_king_char = ret.active === \"w\" ? \"k\" : \"K\";\n\tlet opponent_king_square = ret.find(opponent_king_char)[0];\n\n\tif (ret.attacked(opponent_king_square, ret.colour(opponent_king_square))) {\n\t\tthrow \"Invalid FEN - non-mover's king in check\";\n\t}\n\n\t// Some hard things. Do these in the right order!\n\n\tret.castling = CastlingRights(ret, tokens[2]);\n\tret.enpassant = EnPassantSquare(ret, tokens[3]);\t// Requires ret.active to be correct.\n\tret.normalchess = IsNormalChessPosition(ret);\t\t// Requires ret.castling to be correct.\n\n\treturn ret;\n}\n\n\nfunction CastlingRights(board, s) {\t\t\t\t\t\t// s is the castling string from a FEN\n\n\tlet dict = Object.create(null);\t\t\t\t\t\t// Will contain keys like \"A\" to \"H\" and \"a\" to \"h\"\n\n\t// WHITE\n\n\tlet wk_location = board.find(\"K\", 0, 7, 7, 7)[0];\t// Will be undefined if not on back rank.\n\n\tif (wk_location) {\n\n\t\tfor (let ch of s) {\n\t\t\tif ([\"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\", \"H\"].includes(ch)) {\n\t\t\t\tlet point = Point(ch.toLowerCase() + \"1\");\n\t\t\t\tif (board.piece(point) === \"R\") {\n\t\t\t\t\tdict[ch] = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (ch === \"Q\") {\n\t\t\t\tif (board.state[0][7] === \"R\") {\t\t// Compatibility with regular Chess FEN.\n\t\t\t\t\tdict.A = true;\n\t\t\t\t} else {\n\t\t\t\t\tlet left_rooks = board.find(\"R\", 0, 7, wk_location.x, 7);\n\t\t\t\t\tfor (let rook of left_rooks) {\n\t\t\t\t\t\tdict[rook.s[0].toUpperCase()] = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (ch === \"K\") {\n\t\t\t\tif (board.state[7][7] === \"R\") {\t\t// Compatibility with regular Chess FEN.\n\t\t\t\t\tdict.H = true;\n\t\t\t\t} else {\n\t\t\t\t\tlet right_rooks = board.find(\"R\", wk_location.x, 7, 7, 7);\n\t\t\t\t\tfor (let rook of right_rooks) {\n\t\t\t\t\t\tdict[rook.s[0].toUpperCase()] = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// BLACK\n\n\tlet bk_location = board.find(\"k\", 0, 0, 7, 0)[0];\n\n\tif (bk_location) {\n\n\t\tfor (let ch of s) {\n\t\t\tif ([\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\"].includes(ch)) {\n\t\t\t\tlet point = Point(ch + \"8\");\n\t\t\t\tif (board.piece(point) === \"r\") {\n\t\t\t\t\tdict[ch] = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (ch === \"q\") {\n\t\t\t\tif (board.state[0][0] === \"r\") {\t\t// Compatibility with regular Chess FEN.\n\t\t\t\t\tdict.a = true;\n\t\t\t\t} else {\n\t\t\t\t\tlet left_rooks = board.find(\"r\", 0, 0, bk_location.x, 0);\n\t\t\t\t\tfor (let rook of left_rooks) {\n\t\t\t\t\t\tdict[rook.s[0]] = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (ch === \"k\") {\n\t\t\t\tif (board.state[7][0] === \"r\") {\t\t// Compatibility with regular Chess FEN.\n\t\t\t\t\tdict.h = true;\n\t\t\t\t} else {\n\t\t\t\t\tlet right_rooks = board.find(\"r\", bk_location.x, 0, 7, 0);\n\t\t\t\t\tfor (let rook of right_rooks) {\n\t\t\t\t\t\tdict[rook.s[0]] = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tlet ret = \"\";\n\n\tfor (let ch of \"ABCDEFGHabcdefgh\") {\n\t\tif (dict[ch]) {\n\t\t\tret += ch;\n\t\t}\n\t}\n\n\treturn ret;\n\n\t// FIXME: check at most 1 castling possibility on left and right of each king?\n\t// At the moment we support more arbitrary castling rights, maybe that's OK.\n}\n\n\nfunction EnPassantSquare(board, s) {\t// board.active must be correct. s is the en-passant string from a FEN.\n\n\t// Suffers from the same subtleties as the enpassant setter in position.move(), see there for comments.\n\n\tlet p = Point(s.toLowerCase());\n\n\tif (!p) {\n\t\treturn null;\n\t}\n\n\tif (board.active === \"w\") {\n\t\tif (p.y !== 2) {\n\t\t\treturn null;\n\t\t}\n\t\t// Check the takeable pawn exists...\n\t\tif (board.piece(Point(p.x, 3)) !== \"p\") {\n\t\t\treturn null;\n\t\t}\n\t\t// Check the capture square is actually empty...\n\t\tif (board.piece(Point(p.x, 2)) !== \"\") {\n\t\t\treturn null;\n\t\t}\n\t\t// Check potential capturer exists...\n\t\tif (board.piece(Point(p.x - 1, 3)) !== \"P\" && board.piece(Point(p.x + 1, 3)) !== \"P\") {\n\t\t\treturn null;\n\t\t}\n\t\treturn p;\n\t}\n\n\tif (board.active === \"b\") {\n\t\tif (p.y !== 5) {\n\t\t\treturn null;\n\t\t}\n\t\t// Check the takeable pawn exists...\n\t\tif (board.piece(Point(p.x, 4)) !== \"P\") {\n\t\t\treturn null;\n\t\t}\n\t\t// Check the capture square is actually empty...\n\t\tif (board.piece(Point(p.x, 5)) !== \"\") {\n\t\t\treturn null;\n\t\t}\n\t\t// Check potential capturer exists...\n\t\tif (board.piece(Point(p.x - 1, 4)) !== \"p\" && board.piece(Point(p.x + 1, 4)) !== \"p\") {\n\t\t\treturn null;\n\t\t}\n\t\treturn p;\n\t}\n\n\treturn null;\n}\n\n\nfunction IsNormalChessPosition(board) {\n\n\tfor (let ch of \"bcdefgBCDEFG\") {\n\t\tif (board.castling.includes(ch)) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif (board.castling.includes(\"A\") || board.castling.includes(\"H\")) {\n\t\tif (board.state[4][7] !== \"K\") {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tif (board.castling.includes(\"a\") || board.castling.includes(\"h\")) {\n\t\tif (board.state[4][0] !== \"k\") {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// So it can be considered a normal Chess position.\n\n\treturn true;\n}\n"
  },
  {
    "path": "files/src/renderer/42_perft.js",
    "content": "\"use strict\";\n\n/* Perft notes:\n\nThe correct perft value for a position and depth is the number of leaf nodes at\nthat depth (or equivalently, the number of legal move sequences of that length).\n\nSome important points:\n\n- Rules about \"Triple Repetition\" and \"Insufficient Material\" are ignored.\n- Terminal nodes (mates) at a shallower depth are not counted.\n- But they are counted if they are at the correct depth.\n\nIn Stockfish:\n\n  setoption name UCI_Chess960 value true\n  position fen <whatever>\n  go perft 4\n\n*/\n\nfunction perft(pos, depth, print_moves) {\n\tlet moves = pos.movegen();\n\tif (depth === 1) {\n\t\treturn moves.length;\n\t} else {\n\t\tlet count = 0;\n\t\tfor (let mv of moves) {\n\t\t\tlet val = perft(pos.move(mv), depth - 1, false);\n\t\t\tif (print_moves) {\n\t\t\t\tperft_print_move(pos, mv, val);\n\t\t\t}\n\t\t\tcount += val;\n\t\t}\n\t\treturn count;\n\t}\n}\n\nfunction perft_print_move(pos, mv, val) {\n\tlet nice = pos.nice_string(mv);\n\tconsole.log(`${mv + (mv.length === 4 ? \" \" : \"\")}   ${nice + \" \".repeat(7 - nice.length)}`, val);\n}\n\n// -------------------------------------------------------------------------------------------------------------------\n\nlet perft_known_values = {\n\t\"8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1\":                              [0, 14,  191,  2812,   43238,    674624],\n\t\"1nr1nk1r/1b5B/p1p1qp2/b2pp1pP/3P2P1/P3P2N/1Pp2P2/BNR2KQR w CHch g6 0 1\": [0, 28,  964, 27838,  992438,  30218648],\n\t\"Qr3knr/P1bp1p1p/2pn1q2/4p3/2PP2pB/1p1N1bP1/BP2PP1P/1R3KNR w BHbh - 0 1\": [0, 31, 1122, 34613, 1253934,  40393041],\n\t\"r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1\":   [0, 48, 2039, 97862, 4085603, 193690690],\n\t\"r3k2r/1P1pp1P1/8/2P2P2/2p2p2/8/1p1PP1p1/R3K2R w KQkq - 0 1\":             [0, 43, 1286, 39109, 1134150,  33406158],\n};\n\nfunction Perft(fen, depth) {\n\tif (!fen || !depth) throw \"Need FEN and depth\";\n\tlet starttime = performance.now();\n\tlet board = LoadFEN(fen);\n\tlet val = perft(board, depth, true);\n\tconsole.log(`Total.......... ${val} (${((performance.now() - starttime) / 1000).toFixed(1)} seconds)`);\n\tif (perft_known_values[fen] && perft_known_values[fen][depth]) {\n\t\tif (perft_known_values[fen][depth] === val) {\n\t\t\tconsole.log(\"Known good result\");\n\t\t} else {\n\t\t\tconsole.log(`Known BAD result -- expected ${perft_known_values[fen][depth]}`);\n\t\t}\n\t}\n\treturn val;\n}\n\nfunction PerftFileTest(filename, depth, verbose = false) {\n\n\tif (!filename || !depth) throw \"Need filename and depth\";\n\n\tlet starttime = performance.now();\n\tlet contents = fs.readFileSync(filename).toString();\n\tlet lines = contents.split(\"\\n\").map(z => z.trim()).filter(z => z !== \"\");\n\n\tfor (let n = 0; n < lines.length; n++) {\n\n\t\tlet blobs = lines[n].split(\";\");\n\t\tlet result = perft(LoadFEN(blobs[0]), depth, false);\n\n\t\tif (lines[n].includes(result.toString())) {\n\t\t\tif (verbose) {\n\t\t\t\tconsole.log(`ok -- ${n + 1} / ${lines.length} -- ${blobs[0]}`);\n\t\t\t}\n\t\t} else {\n\t\t\tconsole.log(`FAILED -- ${n + 1} / ${lines.length} -- ${blobs[0]}`);\n\t\t}\n\t}\n\n\tconsole.log(`Elapsed: (${((performance.now() - starttime) / 1000).toFixed(1)} seconds)`);\n}\n"
  },
  {
    "path": "files/src/renderer/43_chess960.js",
    "content": "\"use strict\";\n\nfunction c960_arrangement(n) {\n\n\t// Given n, generate a string like \"RNBQKBNR\".\n\t// AFAIK, matches the scheme of Reinhard Scharnagl.\n\n\tif (n < 0) {\n\t\tn *= -1;\n\t}\n\tn = Math.floor(n) % 960;\n\n\tlet pieces = [\".\", \".\", \".\", \".\", \".\", \".\", \".\", \".\"];\n\n\t// Helper function to place a piece at an \"index\",\n\t// but considering only empty spots.\n\n\tlet insert = (i, piece) => {\n\t\tfor (let n = 0; n < 8; n++) {\n\t\t\tif (pieces[n] === \".\" && --i < 0) {\t\t// Careful! Remember short-circuit rules etc.\n\t\t\t\tpieces[n] = piece;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t};\n\n\t// Place bishops in final positions...\n\n\tpieces[(Math.floor(n / 4) % 4) * 2] = \"B\";\n\tpieces[(n % 4) * 2 + 1] = \"B\";\n\n\t// Place queen in one of 6 remaining spots...\n\n\tlet qi = Math.floor(n / 16) % 6;\n\tinsert(qi, \"Q\");\n\n\t// Knights are arranged in one of 10 possible configurations\n\t// (considering only the remaining spots)...\n\n\tlet ni1 = [0, 0, 0, 0, 1, 1, 1, 2, 2, 3][Math.floor(n / 96)];\n\tlet ni2 = [1, 2, 3, 4, 2, 3, 4, 3, 4, 4][Math.floor(n / 96)];\n\n\tinsert(ni2, \"N\");\t\t// Must be done in this order,\n\tinsert(ni1, \"N\");\t\t// works because ni2 > ni1\n\n\t// Place left rook, king, right rook in first available spots...\n\n\tinsert(0, \"R\");\n\tinsert(0, \"K\");\n\tinsert(0, \"R\");\n\n\treturn pieces.join(\"\");\n}\n\nfunction c960_fen(n) {\n\n\t// Given n, produce a full FEN.\n\n\tlet pieces = c960_arrangement(n);\t// The uppercase version.\n\n\tlet s = `${pieces.toLowerCase()}/pppppppp/8/8/8/8/PPPPPPPP/${pieces}`;\n\n\tlet castling_rights = \"\";\n\n\tfor (let i = 0; i < 8; i++) {\n\t\tif (pieces[i] === \"R\") {\n\t\t\tcastling_rights += String.fromCharCode(i + 65);\n\t\t}\n\t}\n\n\tcastling_rights += castling_rights.toLowerCase();\n\n\treturn `${s} w ${castling_rights} - 0 1`;\n}\n"
  },
  {
    "path": "files/src/renderer/50_table.js",
    "content": "\"use strict\";\n\n// The table object stores info from the engine about a game-tree (PGN) node.\n\nfunction NewTable() {\n\tlet table = Object.create(table_prototype);\n\ttable.clear();\n\treturn table;\n}\n\nconst table_prototype = {\n\n\tclear: function() {\n\t\tthis.moveinfo = Object.create(null);\t// move --> info\n\t\tthis.version = 0;\t\t\t\t\t\t// Incremented on any change\n\t\tthis.nodes = 0;\t\t\t\t\t\t\t// Stat sent by engine\n\t\tthis.nps = 0;\t\t\t\t\t\t\t// Stat sent by engine\n\t\tthis.tbhits = 0;\t\t\t\t\t\t// Stat sent by engine\n\t\tthis.time = 0;\t\t\t\t\t\t\t// Stat sent by engine\n\t\tthis.limit = null;\t\t\t\t\t\t// The limit of the last search that updated this.\n\t\tthis.terminal = null;\t\t\t\t\t// null = unknown, \"\" = not terminal, \"Non-empty string\" = terminal reason\n\t\tthis.graph_y = null;\t\t\t\t\t// Used by grapher only, value from White's POV between 0 and 1\n\t\tthis.graph_y_version = 0;\t\t\t\t// Which version (above) was used to generate the graph_y value\n\t\tthis.already_autopopulated = false;\n\t},\n\n\tget_graph_y: function() {\n\n\t\t// Naphthalin's scheme: based on centipawns.\n\n\t\tif (this.graph_y_version === this.version) {\n\t\t\treturn this.graph_y;\n\t\t} else {\n\t\t\tlet info = SortedMoveInfoFromTable(this)[0];\n\t\t\tif (info && !info.__ghost && info.__touched && (this.nodes > 1 || this.limit === 1)) {\n\t\t\t\tlet cp = info.cp;\n\t\t\t\tif (info.board.active === \"b\") {\n\t\t\t\t\tcp *= -1;\n\t\t\t\t}\n\t\t\t\tthis.graph_y = 1 / (1 + Math.pow(0.5, cp / 100));\n\t\t\t} else {\n\t\t\t\tthis.graph_y = null;\n\t\t\t}\n\t\t\tthis.graph_y_version = this.version;\n\t\t\treturn this.graph_y;\n\t\t}\n\t},\n\n\tset_terminal_info: function(reason, ev) {\t// ev is ignored if reason is \"\" (i.e. not a terminal position)\n\t\tif (reason) {\n\t\t\tthis.terminal = reason;\n\t\t\tthis.graph_y = ev;\n\t\t\tthis.graph_y_version = this.version;\n\t\t} else {\n\t\t\tthis.terminal = \"\";\n\t\t}\n\t},\n\n\tautopopulate: function(node) {\n\n\t\tif (!node) {\n\t\t\tthrow \"autopopulate() requires node argument\";\n\t\t}\n\n\t\tif (this.already_autopopulated) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (node.destroyed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet moves = node.board.movegen();\n\n\t\tfor (let move of moves) {\n\t\t\tif (node.table.moveinfo[move] === undefined) {\n\t\t\t\tnode.table.moveinfo[move] = NewInfo(node.board, move);\n\t\t\t}\n\t\t}\n\n\t\tthis.already_autopopulated = true;\n\t}\n};\n\n// --------------------------------------------------------------------------------------------\n// The info object stores info received from the engine about a move. The actual updating of\n// the object takes place in info.js and the ih.receive() method there.\n\nfunction NewInfo(board, move) {\n\n\tlet info = Object.create(info_prototype);\n\n\tinfo.board = board;\n\tinfo.move = move;\n\tinfo.__ghost = false;\t\t\t// If not false, this is temporary inferred info.\n\tinfo.__touched = false;\t\t\t// Has this ever actually been updated?\n\tinfo.leelaish = false;\t\t\t// Whether the most recent update to this info was from an engine considered Leelaish.\n\tinfo.pv = [move];\t\t\t\t// Validated as a legal sequence upon reception.\n\tinfo.cycle = 0;\t\t\t\t\t// How many \"go\" commands Nibbler has emitted.\n\tinfo.subcycle = 0;\t\t\t\t// How many \"blocks\" of info we have seen (delineated by multipv 1 info).\n\n\tinfo.nice_pv_cache = [board.nice_string(move)];\n\n\tinfo.clear_stats();\n\treturn info;\n}\n\nconst info_prototype = {\n\n\t// I'm not sure I've been conscientious everywhere in the code about checking whether these things are\n\t// of the right type, so for that reason most are set to some neutralish value by default.\n\t//\n\t// Exceptions: m, v, wdl (and note that all of these can be set to null by info.js)\n\n\tclear_stats: function() {\n\t\tthis.cp = 0;\n\t\tthis.depth = 0;\n\t\tthis.m = null;\n\t\tthis.mate = 0;\t\t\t\t// 0 can be the \"not present\" value.\n\t\tthis.multipv = 1;\n\t\tthis.n = 0;\n\t\tthis.p = 0;\t\t\t\t\t// Note P is received and stored as a percent, e.g. 31.76 is a reasonable P.\n\t\tthis.q = 0;\n\t\tthis.s = 1;\t\t\t\t\t// Known as Q+U before Lc0 v0.25-rc2\n\t\tthis.seldepth = 0;\n\t\tthis.u = 1;\n\t\tthis.uci_nodes = 0;\t\t\t// The number of nodes reported by the UCI info lines (i.e. for the whole position).\n\t\tthis.v = null;\n\t\tthis.vms_order = 0;\t\t\t// VerboseMoveStats order, 0 means not present, 1 is the worst, higher is better.\n\t\tthis.wdl = null;\t\t\t// Either null or a length 3 array of ints.\n\t},\n\n\tset_pv: function(pv) {\n\t\tthis.pv = Array.from(pv);\n\t\tthis.nice_pv_cache = null;\n\t},\n\n\tnice_pv: function() {\n\n\t\t// Human readable moves.\n\n\t\tif (this.nice_pv_cache) {\n\t\t\treturn Array.from(this.nice_pv_cache);\n\t\t}\n\n\t\tlet tmp_board = this.board;\n\n\t\tif (!this.pv || this.pv.length === 0) {\t\t\t// Should be impossible.\n\t\t\tthis.pv = [this.move];\n\t\t}\n\n\t\tlet ret = [];\n\n\t\tfor (let move of this.pv) {\n\n\t\t\t// if (tmp_board.illegal(move)) break;\t\t// Should be impossible as of 1.8.4: PVs are validated upon reception, and the only other\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t// way they can get changed is by maybe_infer_info(), which hopefully is sound.\n\t\t\tret.push(tmp_board.nice_string(move));\n\t\t\ttmp_board = tmp_board.move(move);\n\t\t}\n\n\t\tthis.nice_pv_cache = ret;\n\t\treturn Array.from(this.nice_pv_cache);\n\t},\n\n\tvalue: function() {\n\t\treturn Value(this.q);\t\t// Rescaled to 0..1\n\t},\n\n\tvalue_string: function(dp, pov) {\n\t\tif (!this.__touched || typeof this.q !== \"number\") {\n\t\t\treturn \"?\";\n\t\t}\n\t\tif (this.leelaish && this.n === 0) {\n\t\t\treturn \"?\";\n\t\t}\n\t\tlet val = this.value();\n\t\tif ((pov === \"w\" && this.board.active === \"b\") || (pov === \"b\" && this.board.active === \"w\")) {\n\t\t\tval = 1 - val;\n\t\t}\n\t\treturn (val * 100).toFixed(dp);\n\t},\n\n\tcp_string: function(pov) {\n\t\tif (!this.__touched || typeof this.cp !== \"number\") {\n\t\t\treturn \"?\";\n\t\t}\n\t\tif (this.leelaish && this.n === 0) {\n\t\t\treturn \"?\";\n\t\t}\n\t\tlet cp = this.cp;\n\t\tif ((pov === \"w\" && this.board.active === \"b\") || (pov === \"b\" && this.board.active === \"w\")) {\n\t\t\tcp = 0 - cp;\n\t\t}\n\t\tlet ret = (cp / 100).toFixed(2);\n\t\tif (cp > 0) {\n\t\t\tret = \"+\" + ret;\n\t\t}\n\t\treturn ret;\n\t},\n\n\tmate_string: function(pov) {\n\t\tif (typeof this.mate !== \"number\" || this.mate === 0) {\n\t\t\treturn \"?\";\n\t\t}\n\t\tlet mate = this.mate;\n\t\tif ((pov === \"w\" && this.board.active === \"b\") || (pov === \"b\" && this.board.active === \"w\")) {\n\t\t\tmate = 0 - mate;\n\t\t}\n\t\tif (mate < 0) {\n\t\t\treturn `(-M${0 - mate})`;\n\t\t} else {\n\t\t\treturn `(+M${mate})`;\n\t\t}\n\t},\n\n\twdl_string: function(pov) {\n\t\tif (Array.isArray(this.wdl) === false || this.wdl.length !== 3) {\n\t\t\treturn \"?\";\n\t\t}\n\t\tif ((pov === \"w\" && this.board.active === \"b\") || (pov === \"b\" && this.board.active === \"w\")) {\n\t\t\treturn `${this.wdl[2]} ${this.wdl[1]} ${this.wdl[0]}`;\n\t\t} else {\n\t\t\treturn `${this.wdl[0]} ${this.wdl[1]} ${this.wdl[2]}`;\n\t\t}\n\t},\n\n\tstats_list: function(opts, total_nodes) {\t\t// We pass total_nodes rather than use this.uci_nodes which can be obsolete (e.g. due to searchmoves)\n\n\t\tif (this.__ghost) {\n\t\t\treturn [\"Inferred\"];\n\t\t}\n\n\t\tlet ret = [];\n\n\t\tif (opts.ev) {\n\t\t\tret.push(`EV: ${this.value_string(1, opts.ev_pov)}%`);\n\t\t}\n\n\t\tif (opts.cp) {\n\t\t\tret.push(`CP: ${this.cp_string(opts.cp_pov)}`);\n\t\t}\n\n\t\t// N is fairly complicated...\n\n\t\tif (this.leelaish) {\n\n\t\t\tif (typeof this.n === \"number\" && total_nodes) {\t\t// i.e. total_nodes is not zero or undefined\n\n\t\t\t\tlet n_string = \"\";\n\n\t\t\t\tif (opts.n) {\n\t\t\t\t\tn_string += ` N: ${(100 * this.n / total_nodes).toFixed(2)}%`;\n\t\t\t\t}\n\n\t\t\t\tif (opts.n_abs) {\n\t\t\t\t\tif (opts.n) {\n\t\t\t\t\t\tn_string += ` [${NString(this.n)}]`;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tn_string += ` N: ${NString(this.n)}`;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (opts.of_n) {\n\t\t\t\t\tn_string += ` of ${NString(total_nodes)}`;\n\t\t\t\t}\n\n\t\t\t\tif (n_string !== \"\") {\n\t\t\t\t\tret.push(n_string.trim());\n\t\t\t\t}\n\n\t\t\t} else {\n\n\t\t\t\tif (opts.n || opts.n_abs || opts.of_n) {\n\t\t\t\t\tret.push(\"N: ?\");\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\n\t\t// Everything else...\n\n\t\tif (!this.leelaish) {\n\t\t\tif (opts.depth) {\n\t\t\t\tif (typeof this.depth === \"number\" && this.depth > 0) {\n\t\t\t\t\tret.push(`Depth: ${this.depth}`);\n\t\t\t\t} else {\n\t\t\t\t\tret.push(`Depth: 0`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (this.leelaish) {\n\t\t\tif (opts.p) {\n\t\t\t\tif (typeof this.p === \"number\" && this.p > 0) {\n\t\t\t\t\tret.push(`P: ${this.p}%`);\n\t\t\t\t} else {\n\t\t\t\t\tret.push(`P: ?`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (opts.v) {\n\t\t\t\tif (typeof this.v === \"number\") {\n\t\t\t\t\tret.push(`V: ${this.v.toFixed(3)}`);\n\t\t\t\t} else {\n\t\t\t\t\tret.push(`V: ?`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (opts.q) {\n\t\t\tif (typeof this.q === \"number\") {\n\t\t\t\tret.push(`Q: ${this.q.toFixed(3)}`);\n\t\t\t} else {\n\t\t\t\tret.push(`Q: ?`);\n\t\t\t}\n\t\t}\n\n\t\tif (this.leelaish) {\n\t\t\tif (opts.u) {\n\t\t\t\tif (typeof this.u === \"number\" && this.n > 0) {\t\t\t\t\t\t// Checking n is correct.\n\t\t\t\t\tret.push(`U: ${this.u.toFixed(3)}`);\n\t\t\t\t} else {\n\t\t\t\t\tret.push(`U: ?`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (opts.s) {\n\t\t\t\tif (typeof this.s === \"number\" && this.n > 0) {\t\t\t\t\t\t// Checking n is correct.\n\t\t\t\t\tret.push(`S: ${this.s.toFixed(5)}`);\n\t\t\t\t} else {\n\t\t\t\t\tret.push(`S: ?`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (opts.m) {\n\t\t\t\tif (typeof this.m === \"number\") {\n\t\t\t\t\tif (this.m > 0) {\n\t\t\t\t\t\tret.push(`M: ${this.m.toFixed(1)}`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tret.push(`M: 0`);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tret.push(`M: ?`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (opts.wdl) {\n\t\t\tret.push(`WDL: ${this.wdl_string(opts.wdl_pov)}`);\n\t\t}\n\n\t\treturn ret;\n\t}\n};\n"
  },
  {
    "path": "files/src/renderer/51_node.js",
    "content": "\"use strict\";\n\nfunction NewNode(parent, move, board_for_root) {\t\t// move must be legal; board is only relevant for root nodes\n\n\tlet node = Object.create(node_prototype);\n\tnode.id = next_node_id++;\n\tlive_nodes[node.id.toString()] = node;\n\n\tif (parent) {\n\t\tparent.children.push(node);\n\t\tnode.parent = parent;\n\t\tnode.move = move;\n\t\tnode.board = parent.board.move(move);\n\t\tnode.depth = parent.depth + 1;\n\t\tnode.graph_length_knower = parent.graph_length_knower;\t\t// 1 object every node points to, a bit lame\n\t} else {\n\t\tnode.parent = null;\n\t\tnode.move = null;\n\t\tnode.board = board_for_root;\n\t\tnode.depth = 0;\n\t\tnode.graph_length_knower = {val: config.graph_minimum_length};\n\t}\n\n\tif (node.depth + 1 > node.graph_length_knower.val) {\n\t\tnode.graph_length_knower.val = node.depth + 1;\n\t}\n\n\tnode.table = NewTable();\n\tnode.searchmoves = [];\n\tnode.__nice_move = null;\n\tnode.destroyed = false;\n\tnode.children = [];\n\n\treturn node;\n}\n\nfunction NewRoot(board) {\t\t\t\t\t// Arg is a board (position) object, not a FEN\n\n\tif (!board) {\n\t\tboard = LoadFEN(\"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\");\n\t}\n\n\tlet root = NewNode(null, null, board);\n\n\t// Tags. Only root gets these. Get overwritten by the PGN loader.\n\t// Internally, these get kept as HTML-safe, PGN-unsafe.\n\n\troot.tags = Object.create(null);\n\troot.tags.Event = \"?\";\n\troot.tags.Site = \"?\";\n\troot.tags.Date = DateString(new Date());\n\troot.tags.Round = \"?\";\n\troot.tags.White = \"White\";\n\troot.tags.Black = \"Black\";\n\troot.tags.Result = \"*\";\n\n\treturn root;\n}\n\nconst node_prototype = {\n\n\tmake_move: function(s, force_new_node) {\n\n\t\t// s must be exactly a legal move, including having promotion char iff needed (e.g. e2e1q)\n\n\t\tif (!force_new_node) {\n\t\t\tfor (let child of this.children) {\n\t\t\t\tif (child.move === s) {\n\t\t\t\t\treturn child;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn NewNode(this, s, null);\n\t},\n\n\thistory: function() {\n\n\t\tlet ret = [];\n\t\tlet node = this;\n\n\t\twhile (node.move) {\n\t\t\tret.push(node.move);\n\t\t\tnode = node.parent;\n\t\t}\n\n\t\tret.reverse();\n\t\treturn ret;\n\t},\n\n\thistory_old_format: function() {\t\t// For engines that can't handle Chess960 format stuff.\n\n\t\tlet ret = [];\n\t\tlet node = this;\n\n\t\twhile (node.move) {\n\t\t\tret.push(node.move_old_format());\n\t\t\tnode = node.parent;\n\t\t}\n\n\t\tret.reverse();\n\t\treturn ret;\n\t},\n\n\tmove_old_format: function() {\n\t\tlet move = this.move;\n\t\tif (move === \"e1h1\" && this.parent.board.state[4][7] === \"K\") return \"e1g1\";\n\t\tif (move === \"e1a1\" && this.parent.board.state[4][7] === \"K\") return \"e1c1\";\n\t\tif (move === \"e8h8\" && this.parent.board.state[4][0] === \"k\") return \"e8g8\";\n\t\tif (move === \"e8a8\" && this.parent.board.state[4][0] === \"k\") return \"e8c8\";\n\t\treturn move;\n\t},\n\n\tnode_history: function() {\n\n\t\tlet ret = [];\n\t\tlet node = this;\n\n\t\twhile (node) {\n\t\t\tret.push(node);\n\t\t\tnode = node.parent;\n\t\t}\n\n\t\tret.reverse();\n\t\treturn ret;\n\t},\n\n\tall_graph_values: function() {\n\n\t\t// Call this on any node in the line will give the same result.\n\n\t\tlet ret = [];\n\t\tlet node = this.get_end();\n\n\t\twhile (node) {\n\t\t\tret.push(node.table.get_graph_y());\n\t\t\tnode = node.parent;\n\t\t}\n\n\t\tret.reverse();\n\t\treturn ret;\n\t},\n\n\tfuture_history: function() {\n\t\treturn this.get_end().history();\n\t},\n\n\tfuture_node_history: function() {\n\t\treturn this.get_end().node_history();\n\t},\n\n\tget_root: function() {\n\n\t\tlet node = this;\n\n\t\twhile (node.parent) {\n\t\t\tnode = node.parent;\n\t\t}\n\n\t\treturn node;\n\t},\n\n\tget_end: function() {\n\n\t\tlet node = this;\n\n\t\twhile (node.children.length > 0) {\n\t\t\tnode = node.children[0];\n\t\t}\n\n\t\treturn node;\n\t},\n\n\treturn_to_main_line_helper: function() {\n\n\t\t// Returns the node that \"return to main line\" should go to.\n\n\t\tlet ret = this;\n\t\tlet node = this;\n\n\t\twhile (node.parent) {\n\t\t\tif (node.parent.children[0] !== node) {\n\t\t\t\tret = node.parent;\n\t\t\t}\n\t\t\tnode = node.parent;\n\t\t}\n\n\t\treturn ret;\n\t},\n\n\tis_main_line: function() {\n\n\t\tlet node = this;\n\n\t\twhile (node.parent) {\n\t\t\tif (node.parent.children[0] !== node) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tnode = node.parent;\n\t\t}\n\n\t\treturn true;\n\t},\n\n\tis_same_line: function(other) {\n\n\t\t// This is not testing whether one is an ancestor of the other, but\n\t\t// rather whether the main lines of each end in the same place.\n\n\t\t// Easy case is when one is the parent of the other...\n\n\t\tif (this.parent === other) return other.children[0] === this;\n\t\tif (other.parent === this) return this.children[0] === other;\n\n\t\treturn this.get_end() === other.get_end();\n\t},\n\n\tis_triple_rep: function() {\n\n\t\t// Are there enough ancestors since the last pawn move or capture?\n\n\t\tif (this.board.halfmove < 8) {\n\t\t\treturn false;\n\t\t}\n\n\t\tlet ancestor = this;\n\t\tlet hits = 0;\n\n\t\twhile (ancestor.parent && ancestor.parent.parent) {\n\t\t\tancestor = ancestor.parent.parent;\n\t\t\tif (ancestor.board.compare(this.board, hits >= 1)) {\t// Strict mode (about e.p. capture being legal) if we just need 1 more (earlier) hit.\n\t\t\t\thits++;\n\t\t\t\tif (hits >= 2) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// All further ancestors are the wrong side of a pawn move or capture?\n\n\t\t\tif (ancestor.board.halfmove < 2) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t},\n\n\tnice_move: function() {\n\n\t\tif (this.__nice_move) {\n\t\t\treturn this.__nice_move;\n\t\t}\n\n\t\tif (!this.move || !this.parent) {\n\t\t\tthis.__nice_move = \"??\";\n\t\t} else {\n\t\t\tthis.__nice_move = this.parent.board.nice_string(this.move);\n\t\t}\n\n\t\treturn this.__nice_move;\n\t},\n\n\ttoken: function(stats_flag, force_number_flag) {\n\n\t\t// The complete token when writing the move, including number string if necessary,\n\t\t// which depends on position within variations etc and so cannot easily be cached.\n\t\t// We don't do brackets because closing brackets are complicated.\n\n\t\tif (!this.move || !this.parent) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\tlet need_number_string = false;\n\n\t\tif (force_number_flag) need_number_string = true;\n\t\tif (!this.parent.parent) need_number_string = true;\n\t\tif (this.parent.board.active === \"w\") need_number_string = true;\n\t\tif (this.parent.children[0] !== this) need_number_string = true;\n\n\t\t// There are some other cases where we are supposed to have numbers but the logic\n\t\t// escapes me right now.\n\n\t\tlet s = \"\";\n\n\t\tif (need_number_string) {\n\t\t\ts += this.parent.board.next_number_string() + \" \";\n\t\t}\n\n\t\ts += this.nice_move();\n\n\t\tif (stats_flag) {\n\t\t\tlet stats = this.make_stats();\n\t\t\tif (stats !== \"\") {\n\t\t\t\ts += \" {\" + stats + \"}\";\n\t\t\t}\n\t\t}\n\n\t\treturn s;\n\t},\n\n\tmake_stats: function() {\n\n\t\tif (!this.parent) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\tlet info = this.parent.table.moveinfo[this.move];\n\t\tlet total_nodes = this.parent.table.nodes;\n\n\t\tif (!info || info.__ghost || info.__touched === false) {\n\t\t\treturn \"\";\n\t\t}\n\n\t\tlet sl = info.stats_list({\n\t\t\tev_pov:        config.ev_pov,\n\t\t\tcp_pov:        config.cp_pov,\n\t\t\twdl_pov:       config.wdl_pov,\n\t\t\tev:            config.pgn_ev,\n\t\t\tcp:            config.pgn_cp,\n\t\t\tn:             config.pgn_n,\n\t\t\tn_abs:         config.pgn_n_abs,\n\t\t\tof_n:          config.pgn_of_n,\n\t\t\tdepth:         config.pgn_depth,\n\t\t\twdl:           config.pgn_wdl,\n\t\t\tp:             config.pgn_p,\n\t\t\tm:             config.pgn_m,\n\t\t\tv:             config.pgn_v,\n\t\t\tq:             config.pgn_q,\n\t\t\tu:             config.pgn_u,\n\t\t\ts:             config.pgn_s,\n\t\t}, total_nodes);\n\n\t\treturn sl.join(\", \");\t\t\t// Will be \"\" on empty list\n\t},\n\n\tend_nodes: function() {\n\t\tif (this.children.length === 0) {\n\t\t\treturn [this];\n\t\t} else {\n\t\t\tlet list = [];\n\t\t\tfor (let child of this.children) {\n\t\t\t\tlist = list.concat(child.end_nodes());\n\t\t\t}\n\t\t\treturn list;\n\t\t}\n\t},\n\n\tterminal_reason: function() {\n\n\t\t// Returns \"\" if not a terminal position, otherwise returns the reason.\n\t\t// Also updates table.graph_y if needed.\n\n\t\tif (typeof this.table.terminal === \"string\") {\n\t\t\treturn this.table.terminal;\n\t\t}\n\n\t\tlet board = this.board;\n\n\t\tif (board.no_moves()) {\n\t\t\tif (board.king_in_check()) {\n\t\t\t\tthis.table.set_terminal_info(\"Checkmate\", board.active === \"w\" ? 0 : 1);\t// The PGN writer checks for this exact string! (Lame...)\n\t\t\t} else {\n\t\t\t\tthis.table.set_terminal_info(\"Stalemate\", 0.5);\n\t\t\t}\n\t\t} else if (board.insufficient_material()) {\n\t\t\tthis.table.set_terminal_info(\"Insufficient Material\", 0.5);\n\t\t} else if (board.halfmove >= 100) {\n\t\t\tthis.table.set_terminal_info(\"50 Move Rule\", 0.5);\n\t\t} else if (this.is_triple_rep()) {\n\t\t\tthis.table.set_terminal_info(\"Triple Repetition\", 0.5);\n\t\t} else {\n\t\t\tthis.table.set_terminal_info(\"\", null);\n\t\t}\n\n\t\treturn this.table.terminal;\n\t},\n\n\tvalidate_searchmoves: function(arr) {\n\n\t\t// Returns a new array with only legal searchmoves.\n\n\t\tif (Array.isArray(arr) === false) {\n\t\t\tarr = [];\n\t\t}\n\n\t\tlet valid_list = [];\n\n\t\tfor (let move of arr) {\n\t\t\tif (this.board.illegal(move) === \"\") {\n\t\t\t\tvalid_list.push(move);\n\t\t\t}\n\t\t}\n\n\t\treturn valid_list;\n\t},\n\n\tdetach: function() {\n\n\t\t// Returns the node that the hub should point to,\n\t\t// which is the parent unless the call is a bad one.\n\n\t\tlet parent = this.parent;\n\t\tif (!parent) return this;\t\t\t// Fail\n\n\t\tparent.children = parent.children.filter(child => child !== this);\n\n\t\tthis.parent = null;\n\t\tDestroyTree(this);\n\t\treturn parent;\n\t},\n\n\tpolyglot_book: function(filepath) {\t\t// Partially written by Claude, secret method for Naphthalin, saves .bin to filepath\n\n\t\tlet book = [];\n\t\tAddTreeToBook(this, book);\n\n\t\tlet polyglot_entries = [];\n\n\t\tfor (let entry of book) {\n\n\t\t\tlet [x1, y1] = XY(entry.move.slice(0, 2));\n\t\t\tlet [x2, y2] = XY(entry.move.slice(2, 4));\n\t\t\tlet promotion = entry.move.slice(4);\n\n\t\t\tlet move_val = 0;\n\t\t\tlet promotion_val = 0;\n\n\t\t\tif (promotion) {\n\t\t\t\tswitch (promotion.toLowerCase()) {\n\t\t\t\t\tcase \"n\": promotion_val = 1; break;\n\t\t\t\t\tcase \"b\": promotion_val = 2; break;\n\t\t\t\t\tcase \"r\": promotion_val = 3; break;\n\t\t\t\t\tcase \"q\": promotion_val = 4; break;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmove_val |= (x2 & 0b111);\t\t\t\t\t\t// bits 0-2: to file\n\t\t\tmove_val |= (((7 - y2) & 0b111) << 3);\t\t\t// bits 3-5: to rank\n\t\t\tmove_val |= ((x1 & 0b111) << 6);\t\t\t\t// bits 6-8: from file\n\t\t\tmove_val |= (((7 - y1) & 0b111) << 9);  \t\t// bits 9-11: from rank\n\t\t\tmove_val |= ((promotion_val & 0b111) << 12);\t// bits 12-14: promotion piece\n\n\t\t\tpolyglot_entries.push((entry.key << 64n) + (BigInt(move_val) << 48n) + (1n << 32n));\n\t\t}\n\n\t\tpolyglot_entries.sort((a, b) => (a < b) ? -1 : (a > b) ? 1 : 0);\n\n\t\tlet buffer = Buffer.alloc(polyglot_entries.length * 16);\n\n\t\tfor (let i = 0; i < polyglot_entries.length; i++) {\n\t\t\tlet entry = polyglot_entries[i];\n\t\t\tlet position = i * 16;\n\n\t\t\tfor (let j = 0; j < 16; j++) {\n\t\t\t\tlet bit_shift = BigInt(15 - j) * 8n;\n\t\t\t\tlet byte_val = Number((entry >> bit_shift) & 0xFFn);\n\t\t\t\tbuffer[position + j] = byte_val;\n\t\t\t}\n\t\t}\n\n\t\tfs.writeFileSync(filepath, buffer);\n\t},\n};\n\n// ---------------------------------------------------------------------------------------------------------\n// On the theory that it might help the garbage collector, we can\n// destroy trees when we're done with them. Whether this is helpful\n// in general I don't know, but we also take this opportunity to\n// clear nodes from the live_list.\n\nfunction DestroyTree(node) {\n\tif (!node || node.destroyed) {\n\t\tconsole.log(\"Warning: DestroyTree() called with invalid arg\");\n\t\treturn;\n\t}\n\t__destroy_tree(node.get_root());\n}\n\nfunction __destroy_tree(node) {\n\n\t// Non-recursive when possible...\n\n\twhile (node.children.length === 1) {\n\n\t\tlet child = node.children[0];\n\n\t\tnode.parent = null;\n\t\tnode.board = null;\n\t\tnode.children = null;\n\t\tnode.searchmoves = null;\n\t\tnode.table = null;\n\t\tnode.graph_length_knower = null;\n\t\tnode.destroyed = true;\n\n\t\tdelete live_nodes[node.id.toString()];\n\n\t\tnode = child;\n\t}\n\n\t// Recursive when necessary...\n\n\tlet children = node.children;\n\n\tnode.parent = null;\n\tnode.board = null;\n\tnode.children = null;\n\tnode.searchmoves = null;\n\tnode.table = null;\n\tnode.graph_length_knower = null;\n\tnode.destroyed = true;\n\n\tdelete live_nodes[node.id.toString()];\n\n\tfor (let child of children) {\n\t\t__destroy_tree(child);\n\t}\n}\n\n// ---------------------------------------------------------------------------------------------------------\n// Reset analysis and searchmove selections, recursively.\n\nfunction CleanTree(node) {\n\tif (!node || node.destroyed) {\n\t\treturn;\n\t}\n\t__clean_tree(node.get_root());\n}\n\nfunction __clean_tree(node) {\n\n\t// Non-recursive when possible...\n\n\twhile (node.children.length === 1) {\n\t\tnode.table.clear();\n\t\tnode.searchmoves = [];\n\t\tnode = node.children[0];\n\t}\n\n\t// Recursive when necessary...\n\n\tnode.table.clear();\n\tnode.searchmoves = [];\n\n\tfor (let child of node.children) {\n\t\t__clean_tree(child);\n\t}\n}\n\n// ------------------------------------------------------------------------------------------------------\n// Add positions to a book, using the given tree. No sorting here, needs to be done after completion.\n\nfunction AddTreeToBook(node, book) {\n\n\tif (!book || Array.isArray(book) === false) {\n\t\tthrow \"AddTreeToBook called without valid array\";\n\t}\n\n\tif (!node || node.destroyed) {\n\t\treturn book;\n\t}\n\n\t__add_tree_to_book(node.get_root(), book);\n\n\treturn book;\n}\n\nfunction __add_tree_to_book(node, book) {\n\n\t// Non-recursive when possible...\n\n\twhile (node.children.length === 1) {\n\n\t\tlet key = KeyFromBoard(node.board);\n\t\tlet move = node.children[0].move;\n\n\t\tbook.push({\t\t\t\t\t\t\t// Duplicates allowed. This is improper.\n\t\t\tkey: key,\n\t\t\tmove: move,\n\t\t\tweight: 1,\n\t\t});\n\n\t\tnode = node.children[0];\n\t}\n\n\tif (node.children.length === 0) {\t\t// Do this test here, not at the start, since it can become true.\n\t\treturn;\n\t}\n\n\t// Recursive when necessary...\n\n\tlet key = KeyFromBoard(node.board);\n\n\tfor (let child of node.children) {\n\n\t\tbook.push({\t\t\t\t\t\t\t// Duplicates allowed. This is improper.\n\t\t\tkey: key,\n\t\t\tmove: child.move,\n\t\t\tweight: 1,\n\t\t});\n\n\t\t__add_tree_to_book(child, book);\n\t}\n}\n"
  },
  {
    "path": "files/src/renderer/52_sorted_moves.js",
    "content": "\"use strict\";\n\nfunction SortedMoveInfo(node) {\n\n\tif (!node || node.destroyed) {\n\t\treturn [];\n\t}\n\n\treturn SortedMoveInfoFromTable(node.table);\n}\n\nfunction SortedMoveInfoFromTable(table) {\n\n\t// There are a lot of subtleties around sorting the moves...\n\t//\n\t// - We want to allow other engines than Lc0.\n\t// - We want to work with low MultiPV values.\n\t// - Old and stale data can be left in our cache if MultiPV is low.\n\t// - We want to work with searchmoves, which is bound to leave stale info in the table.\n\t// - We can try and track the age of the data by various means, but these are fallible.\n\n\tlet info_list = [];\n\tlet latest_cycle = 0;\n\tlet latest_subcycle = 0;\n\n\tfor (let o of Object.values(table.moveinfo)) {\n\t\tinfo_list.push(o);\n\t\tif (o.cycle > latest_cycle) latest_cycle = o.cycle;\n\t\tif (o.subcycle > latest_subcycle) latest_subcycle = o.subcycle;\n\t}\n\n\t// It's important that the sort be transitive. I believe it is.\n\n\tinfo_list.sort((a, b) => {\n\n\t\tconst a_is_best = -1;\t\t\t\t\t\t// return -1 to sort a to the left\n\t\tconst b_is_best = 1;\t\t\t\t\t\t// return 1 to sort a to the right\n\n\t\t// Info that hasn't been touched must be worse...\n\n\t\tif (a.__touched && !b.__touched) return a_is_best;\n\t\tif (!a.__touched && b.__touched) return b_is_best;\n\n\t\t// Always prefer info from the current \"go\" specifically.\n\t\t// As well as being correct generally, it also moves searchmoves to the top.\n\n\t\tif (a.cycle === latest_cycle && b.cycle !== latest_cycle) return a_is_best;\n\t\tif (a.cycle !== latest_cycle && b.cycle === latest_cycle) return b_is_best;\n\n\t\t// Prefer info from the current \"block\" of info specifically.\n\n\t\tif (a.subcycle === latest_subcycle && b.subcycle !== latest_subcycle) return a_is_best;\n\t\tif (a.subcycle !== latest_subcycle && b.subcycle === latest_subcycle) return b_is_best;\n\n\t\t// If one info is leelaish and the other isn't, that can only mean that the A/B\n\t\t// engine is the one that ran last (since Lc0 will cause all info to become\n\t\t// leelaish), therefore any moves the A/B engine has touched must be \"better\".\n\n\t\tif (!a.leelaish && b.leelaish) return a_is_best;\n\t\tif (a.leelaish && !b.leelaish) return b_is_best;\n\n\t\t// ----------------------------------- LEELA AND LEELA-LIKE ENGINES ----------------------------------- //\n\n\t\tif (a.leelaish && b.leelaish) {\n\n\t\t\t// Mate - positive good, negative bad.\n\t\t\t// Note our info struct uses 0 when not given.\n\n\t\t\tif (Sign(a.mate) !== Sign(b.mate)) {\t\t// negative is worst, 0 is neutral, positive is best\n\t\t\t\tif (a.mate > b.mate) return a_is_best;\n\t\t\t\tif (a.mate < b.mate) return b_is_best;\n\t\t\t} else {\t\t\t\t\t\t\t\t\t// lower (i.e. towards -Inf) is better regardless of who's mating\n\t\t\t\tif (a.mate < b.mate) return a_is_best;\n\t\t\t\tif (a.mate > b.mate) return b_is_best;\n\t\t\t}\n\n\t\t\t// Ordering by VerboseMoveStats (suggestion of Napthalin)...\n\n\t\t\tif (a.vms_order > b.vms_order) return a_is_best;\n\t\t\tif (a.vms_order < b.vms_order) return b_is_best;\n\n\t\t\t// Leela N score (node count) - higher is better (shouldn't be possible to get here now)...\n\n\t\t\tif (a.n > b.n) return a_is_best;\n\t\t\tif (a.n < b.n) return b_is_best;\n\t\t}\n\n\t\t// ---------------------------------------- ALPHA-BETA ENGINES ---------------------------------------- //\n\n\t\tif (a.leelaish === false && b.leelaish === false) {\n\n\t\t\t// Specifically within the latest subcycle, prefer lower multipv. I don't think this\n\t\t\t// breaks transitivity because the latest subcycle is always sorted left (see above).\n\n\t\t\tif (a.subcycle === latest_subcycle && b.subcycle === latest_subcycle) {\n\t\t\t\tif (a.multipv < b.multipv) return a_is_best;\n\t\t\t\tif (a.multipv > b.multipv) return b_is_best;\n\t\t\t}\n\n\t\t\t// Otherwise sort by depth.\n\n\t\t\tif (a.depth > b.depth) return a_is_best;\n\t\t\tif (a.depth < b.depth) return b_is_best;\n\n\t\t\t// Sort by CP if we somehow get here.\n\n\t\t\tif (a.cp > b.cp) return a_is_best;\n\t\t\tif (a.cp < b.cp) return b_is_best;\n\t\t}\n\n\t\t// Sort alphabetically...\n\n\t\tif (a.nice_pv_cache && b.nice_pv_cache) {\n\t\t\tif (a.nice_pv_cache[0] < b.nice_pv_cache[0]) return a_is_best;\n\t\t\tif (a.nice_pv_cache[0] > b.nice_pv_cache[0]) return b_is_best;\n\t\t}\n\n\t\treturn 0;\n\t});\n\n\treturn info_list;\n}\n"
  },
  {
    "path": "files/src/renderer/55_winrate_graph.js",
    "content": "\"use strict\";\n\nfunction NewGrapher() {\n\n\tlet grapher = Object.create(null);\n\n\tgrapher.dragging = false;\t\t\t// Used by the event handlers in start.js\n\n\tgrapher.clear_graph = function() {\n\n\t\tlet boundingrect = graph.getBoundingClientRect();\n\t\tlet width = window.innerWidth - boundingrect.left - 16;\n\t\tlet height = boundingrect.bottom - boundingrect.top;\n\n\t\t// This clears the canvas...\n\n\t\tgraph.width = width;\n\t\tgraph.height = height;\n\t};\n\n\tgrapher.draw = function(node, force) {\n\t\tif (config.graph_height <= 0) {\n\t\t\treturn;\n\t\t}\n\t\tthis.draw_everything(node);\n\t};\n\n\tgrapher.draw_everything = function(node) {\n\n\t\tthis.clear_graph();\n\t\tlet width = graph.width;\t\t// After the above.\n\t\tlet height = graph.height;\n\n\t\tlet eval_list = node.all_graph_values();\n\t\tthis.draw_horizontal_lines(width, height, [1/3, 2/3]);\n\t\tthis.draw_position_line(eval_list.length, node);\n\n\t\t// We make lists of contiguous edges that can be drawn at once...\n\n\t\tlet runs = this.make_runs(eval_list, width, height, node.graph_length_knower.val);\n\n\t\t// Draw our normal runs...\n\n\t\tgraphctx.strokeStyle = \"white\";\n\t\tgraphctx.lineWidth = config.graph_line_width;\n\t\tgraphctx.lineJoin = \"round\";\n\t\tgraphctx.setLineDash([]);\n\n\t\tfor (let run of runs.normal_runs) {\n\t\t\tgraphctx.beginPath();\n\t\t\tgraphctx.moveTo(run[0].x1, run[0].y1);\n\t\t\tfor (let edge of run) {\n\t\t\t\tgraphctx.lineTo(edge.x2, edge.y2);\n\t\t\t}\n\t\t\tgraphctx.stroke();\n\t\t}\n\n\t\t// Draw our dashed runs...\n\n\t\tgraphctx.strokeStyle = \"#999999\";\n\t\tgraphctx.lineWidth = config.graph_line_width;\n\t\tgraphctx.setLineDash([config.graph_line_width, config.graph_line_width]);\n\n\t\tfor (let run of runs.dashed_runs) {\n\t\t\tgraphctx.beginPath();\n\t\t\tgraphctx.moveTo(run[0].x1, run[0].y1);\n\t\t\tfor (let edge of run) {\n\t\t\t\tgraphctx.lineTo(edge.x2, edge.y2);\n\t\t\t}\n\t\t\tgraphctx.stroke();\n\t\t}\n\t};\n\n\tgrapher.make_runs = function(eval_list, width, height, graph_length) {\n\n\t\t// Returns an object with 2 arrays (normal_runs and dashed_runs).\n\t\t// Each of those is an array of arrays of contiguous edges that can be drawn at once.\n\n\t\tlet all_edges = [];\n\n\t\tlet last_x = null;\n\t\tlet last_y = null;\n\t\tlet last_n = null;\n\n\t\t// This loop creates all edges that we are going to draw, and marks each\n\t\t// edge as dashed or not...\n\n\t\tfor (let n = 0; n < eval_list.length; n++) {\n\n\t\t\tlet e = eval_list[n];\n\n\t\t\tif (e !== null) {\n\n\t\t\t\tlet x = width * n / graph_length;\n\n\t\t\t\tlet y = (1 - e) * height;\n\t\t\t\tif (y < 1) y = 1;\n\t\t\t\tif (y > height - 2) y = height - 2;\n\n\t\t\t\tif (last_x !== null) {\n\t\t\t\t\tall_edges.push({\n\t\t\t\t\t\tx1: last_x,\n\t\t\t\t\t\ty1: last_y,\n\t\t\t\t\t\tx2: x,\n\t\t\t\t\t\ty2: y,\n\t\t\t\t\t\tdashed: n - last_n !== 1,\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tlast_x = x;\n\t\t\t\tlast_y = y;\n\t\t\t\tlast_n = n;\n\t\t\t}\n\t\t}\n\n\t\t// Now we make runs of contiguous edges that share a style...\n\n\t\tlet normal_runs = [];\n\t\tlet dashed_runs = [];\n\n\t\tlet run = [];\n\t\tlet current_meta_list = normal_runs;\t// Will point at normal_runs or dashed_runs.\n\n\t\tfor (let edge of all_edges) {\n\t\t\tif ((edge.dashed && current_meta_list !== dashed_runs) || (!edge.dashed && current_meta_list !== normal_runs)) {\n\t\t\t\tif (run.length > 0) {\n\t\t\t\t\tcurrent_meta_list.push(run);\n\t\t\t\t}\n\t\t\t\tcurrent_meta_list = edge.dashed ? dashed_runs : normal_runs;\n\t\t\t\trun = [];\n\t\t\t}\n\t\t\trun.push(edge);\n\t\t}\n\t\tif (run.length > 0) {\n\t\t\tcurrent_meta_list.push(run);\n\t\t}\n\n\t\treturn {normal_runs, dashed_runs};\n\t};\n\n\tgrapher.draw_horizontal_lines = function(width, height, y_fractions = [0.5]) {\n\n\t\t// Avoid anti-aliasing... (FIXME: we assumed graph size was even)\n\t\tlet pixel_y_adjustment = config.graph_line_width % 2 === 0 ? 0 : -0.5;\n\n\t\tgraphctx.strokeStyle = \"#666666\";\n\t\tgraphctx.lineWidth = config.graph_line_width;\n\t\tgraphctx.setLineDash([config.graph_line_width, config.graph_line_width]);\n\n\t\tfor (let y_fraction of y_fractions) {\n\t\t\tgraphctx.beginPath();\n\t\t\tgraphctx.moveTo(0, height * y_fraction + pixel_y_adjustment);\n\t\t\tgraphctx.lineTo(width, height * y_fraction + pixel_y_adjustment);\n\t\t\tgraphctx.stroke();\n\t\t}\n\t};\n\n\tgrapher.draw_position_line = function(eval_list_length, node) {\n\n\t\tif (eval_list_length < 2) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet width = graph.width;\n\t\tlet height = graph.height;\n\n\t\t// Avoid anti-aliasing...\n\t\tlet pixel_x_adjustment = config.graph_line_width % 2 === 0 ? 0 : 0.5;\n\n\t\tlet x = Math.floor(width * node.depth / node.graph_length_knower.val) + pixel_x_adjustment;\n\n\t\tgraphctx.strokeStyle = node.is_main_line() ? \"#6cccee\" : \"#ffff00\";\n\t\tgraphctx.lineWidth = config.graph_line_width;\n\t\tgraphctx.setLineDash([config.graph_line_width, config.graph_line_width]);\n\n\t\tgraphctx.beginPath();\n\t\tgraphctx.moveTo(x, 0);\n\t\tgraphctx.lineTo(x, height);\n\t\tgraphctx.stroke();\n\n\t};\n\n\tgrapher.node_from_click = function(node, event) {\n\n\t\tif (!event || config.graph_height <= 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\tlet mousex = event.offsetX;\n\t\tif (typeof mousex !== \"number\") {\n\t\t\treturn null;\n\t\t}\n\n\t\tlet width = graph.width;\n\t\tif (typeof width !== \"number\" || width < 1) {\n\t\t\treturn null;\n\t\t}\n\n\t\tlet node_list = node.future_node_history();\n\t\tif (node_list.length === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// OK, everything is valid...\n\n\t\tlet click_depth = Math.round(node.graph_length_knower.val * mousex / width);\n\n\t\tif (click_depth < 0) click_depth = 0;\n\t\tif (click_depth >= node_list.length) click_depth = node_list.length - 1;\n\n\t\treturn node_list[click_depth];\n\t};\n\n\treturn grapher;\n}\n"
  },
  {
    "path": "files/src/renderer/60_pgn_utils.js",
    "content": "\"use strict\";\n\nfunction split_buffer(buf) {\n\n\t// Split a binary buffer into an array of binary buffers corresponding to lines.\n\n\tlet lines = [];\n\n\tlet push = (arr) => {\n\t\tif (arr.length > 0 && arr[arr.length - 1] === 13) {\t\t// Discard \\r\n\t\t\tlines.push(Buffer.from(arr.slice(0, -1)));\n\t\t} else {\n\t\t\tlines.push(Buffer.from(arr));\n\t\t}\n\t};\n\n\tlet a = 0;\n\tlet b;\n\n\tif (buf.length > 3 && buf[0] === 239 && buf[1] === 187 && buf[2] === 191) {\n\t\ta = 3;\t\t\t// 1st slice will skip byte order mark (BOM).\n\t}\n\n\tfor (b = 0; b < buf.length; b++) {\n\t\tlet ch = buf[b];\n\t\tif (ch === 10) {\t\t\t\t\t// Split on \\n\n\t\t\tlet line = buf.slice(a, b);\n\t\t\tpush(line);\n\t\t\ta = b + 1;\n\t\t}\n\t}\n\n\tif (a !== b) {\t\t// We haven't added the last line before EOF.\n\t\tlet line = buf.slice(a, b);\n\t\tpush(line);\n\t}\n\n\treturn lines;\n}\n\nfunction new_byte_pusher(size) {\n\n\tif (!size || size <= 0) {\n\t\tsize = 16;\n\t}\n\n\t// I bet Node has something like this, but I didn't read the docs.\n\n\treturn {\n\n\t\tstorage: new Uint8Array(size),\n\t\tlength: 0,\t\t\t\t\t\t\t// Both the length and also the next index to write to.\n\n\t\tpush: function(c) {\n\t\t\tif (this.length >= this.storage.length) {\n\t\t\t\tlet new_storage = new Uint8Array(this.storage.length * 2);\n\t\t\t\tfor (let n = 0; n < this.storage.length; n++) {\n\t\t\t\t\tnew_storage[n] = this.storage[n];\n\t\t\t\t}\n\t\t\t\tthis.storage = new_storage;\n\t\t\t}\n\t\t\tthis.storage[this.length] = c;\n\t\t\tthis.length++;\n\t\t},\n\n\t\treset: function() {\n\t\t\tthis.length = 0;\n\t\t},\n\n\t\tbytes: function() {\n\t\t\treturn this.storage.slice(0, this.length);\n\t\t},\n\n\t\tstring: function() {\n\t\t\treturn decoder.decode(this.bytes());\n\t\t}\n\t};\n}\n\nfunction new_pgndata(buf, indices) {\t\t// Made by the PGN file loader. Used by the hub.\n\n\tlet ret = {buf, indices};\n\tret.source = \"Unknown source\";\n\n\tret.count = function() {\n\t\treturn this.indices.length;\n\t};\n\n\tret.getrecord = function(n) {\n\t\tif (typeof n !== \"number\" || n < 0 || n >= this.indices.length) {\n\t\t\treturn null;\n\t\t}\n\t\treturn PreParsePGN(this.buf.slice(this.indices[n], this.indices[n + 1]));\t\t// if n + 1 is out-of-bounds, still works.\n\t};\n\n\tret.string = function(n) {\n\t\tif (typeof n !== \"number\" || n < 0 || n >= this.indices.length) {\n\t\t\treturn \"\";\n\t\t}\n\t\treturn this.buf.slice(this.indices[n], this.indices[n + 1]).toString();\t\t\t// For debugging.\n\t};\n\n\treturn ret;\n}\n\n// ------------------------------------------------------------------------------------------------------------------------------\n\nfunction SavePGN(filename, node) {\n\tlet s = make_pgn_string(node);\n\ttry {\n\t\tfs.writeFileSync(filename, s);\n\t} catch (err) {\n\t\talert(err);\n\t}\n}\n\nfunction PGNToClipboard(node) {\n\tlet s = make_pgn_string(node);\n\tclipboard.writeText(s);\n}\n\n// ------------------------------------------------------------------------------------------------------------------------------\n\nfunction make_pgn_string(node) {\n\n\tlet root = node.get_root();\n\tlet start_fen = root.board.fen(true);\n\n\tif (!root.tags) {\t\t\t\t\t\t\t// This should be impossible.\n\t\troot.tags = Object.create(null);\n\t}\n\n\t// Let's set the Result tag if possible...\n\n\tlet main_line_end = root.get_end();\n\tlet terminal_reason = main_line_end.terminal_reason();\n\n\tif (terminal_reason === \"\") {\n\t\t// Pass - leave it unchanged since we know nothing\n\t} else if (terminal_reason === \"Checkmate\") {\n\t\troot.tags.Result = main_line_end.board.active === \"w\" ? \"0-1\" : \"1-0\";\n\t} else {\n\t\troot.tags.Result = \"1/2-1/2\";\n\t}\n\n\t// Convert tag object to PGN formatted strings...\n\n\tlet tags = [];\n\n\tfor (let t of [\"Event\", \"Site\", \"Date\", \"Round\", \"White\", \"Black\", \"Result\"]) {\n\t\tif (root.tags[t]) {\n\t\t\tlet val = SafeStringPGN(UnsafeStringHTML(root.tags[t]));\t\t// Undo HTML escaping then add PGN escaping.\n\t\t\ttags.push(`[${t} \"${val}\"]`);\n\t\t}\n\t}\n\n\tif (start_fen !== \"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\") {\n\t\tif (root.board.normalchess === false) {\n\t\t\ttags.push(`[Variant \"Chess960\"]`);\n\t\t}\n\t\ttags.push(`[FEN \"${start_fen}\"]`);\n\t\ttags.push(`[SetUp \"1\"]`);\n\t}\n\n\tlet movetext = make_movetext(root);\n\tlet final = tags.join(\"\\n\") + \"\\n\\n\" + movetext + \"\\n\";\n\treturn final;\n}\n\nfunction make_movetext(node) {\n\n\tlet root = node.get_root();\n\tlet ordered_nodes = get_ordered_nodes(root);\n\n\tlet tokens = [];\n\n\tfor (let item of ordered_nodes) {\n\n\t\tif (item === root) continue;\n\n\t\t// As it stands, item could be a \"(\" or \")\" string, or an actual node...\n\n\t\tif (typeof item === \"string\") {\n\t\t\ttokens.push(item);\n\t\t} else {\n\t\t\tlet item_token = item.token(true);\n\t\t\tlet subtokens = item_token.split(\" \").filter(z => z !== \"\");\n\t\t\tfor (let subtoken of subtokens) {\n\t\t\t\ttokens.push(subtoken);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (root.tags && root.tags.Result) {\n\t\ttokens.push(root.tags.Result);\n\t} else {\n\t\ttokens.push(\"*\");\n\t}\n\n\t// Now it's all about wrapping to 80 chars...\n\n\tlet lines = [];\n\tlet line = \"\";\n\n\tfor (let token of tokens) {\n\t\tif (line.length + token.length > 79) {\n\t\t\tif (line !== \"\") {\n\t\t\t\tlines.push(line);\n\t\t\t}\n\t\t\tline = token;\n\t\t} else {\n\t\t\tif (line.length > 0 && line.endsWith(\"(\") === false && token !== \")\") {\n\t\t\t\tline += \" \";\n\t\t\t}\n\t\t\tline += token;\n\t\t}\n\t}\n\tif (line !== \"\") {\n\t\tlines.push(line);\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\n// The following is to order the nodes into the order they would be written\n// to screen or PGN. The result does contain root, which shouldn't be drawn.\n//\n// As a crude hack, the list also contains \"(\" and \")\" elements to indicate\n// where brackets should be drawn.\n\nfunction get_ordered_nodes(node) {\n\tlet list = [];\n\t__order_nodes(node, list, false);\n\treturn list;\n}\n\nfunction __order_nodes(node, list, skip_self_flag) {\n\n\t// Write this node itself...\n\n\tif (!skip_self_flag) {\n\t\tlist.push(node);\n\t}\n\n\t// Write descendents as long as there's no branching,\n\t// or return if we reach a node with no children.\n\n\twhile (node.children.length === 1) {\n\t\tnode = node.children[0];\n\t\tlist.push(node);\n\t}\n\n\tif (node.children.length === 0) {\n\t\treturn;\n\t}\n\n\t// So multiple child nodes exist...\n\n\tlet main_child = node.children[0];\n\tlist.push(main_child);\n\n\tfor (let child of node.children.slice(1)) {\n\t\tlist.push(\"(\");\n\t\t__order_nodes(child, list, false);\n\t\tlist.push(\")\");\n\t}\n\n\t__order_nodes(main_child, list, true);\n}\n"
  },
  {
    "path": "files/src/renderer/61_pgn_parse.js",
    "content": "\"use strict\";\n\nfunction new_pgn_record() {\n\treturn {\n\t\ttags: Object.create(null),\n\t\tmovebufs: []\n\t};\n}\n\nfunction PreParsePGN(buf) {\t\t\t\t\t\t\t\t// buf should be the buffer for a single game, only.\n\n\t// Partial parse of the buffer. Generates a tags object and a list of buffers, each of which is a line\n\t// in the movetext. Not so sure this approach makes sense any more, if it ever did. In particular,\n\t// there's no really great reason why the movetext needs to be split into lines at all.\n\t//\n\t// Never fails. Always returns a valid object (though possibly containing illegal movetext).\n\n\tlet game = new_pgn_record();\n\tlet lines = split_buffer(buf);\n\n\tfor (let rawline of lines) {\n\n\t\tif (rawline.length === 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (rawline[0] === 37) {\t\t\t\t\t\t// Percent % sign is a special comment type.\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet tagline;\n\n\t\tif (game.movebufs.length === 0) {\t\t\t\t// If we have movetext then this can't be a tag line.\n\t\t\tif (rawline[0] === 91) {\n\t\t\t\tlet s = decoder.decode(rawline).trim();\n\t\t\t\tif (s.endsWith(`]`)) {\n\t\t\t\t\ttagline = s;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (tagline) {\n\n\t\t\ttagline = tagline.slice(1, -1).trim();\t\t// So now it's like:\t\tFoo \"bar etc\"\n\n\t\t\tlet first_space_i = tagline.indexOf(` `);\n\n\t\t\tif (first_space_i === -1) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet key = tagline.slice(0, first_space_i).trim();\n\t\t\tlet value = tagline.slice(first_space_i + 1).trim();\n\n\t\t\tif (value.startsWith(`\"`)) value = value.slice(1);\n\t\t\tif (value.endsWith(`\"`)) value = value.slice(0, -1);\n\t\t\tvalue = value.trim();\n\n\t\t\tgame.tags[key] = SafeStringHTML(UnsafeStringPGN(value));\t\t// Undo PGN escaping then add HTML escaping.\n\n\t\t} else {\n\n\t\t\tgame.movebufs.push(rawline);\n\n\t\t}\n\t}\n\n\treturn game;\n}\n\nfunction LoadPGNRecord(o) {\t\t\t\t// This can throw!\n\n\t// Parse of the objects produced above, to generate a game tree.\n\t// Tags are placed into the root's own tags object.\n\n\tlet startpos;\n\n\tif (o.tags.FEN) {\t\t\t\t\t// && o.tags.SetUp === \"1\"  - but some writers don't do this.\n\t\ttry {\n\t\t\tstartpos = LoadFEN(o.tags.FEN);\n\t\t} catch (err) {\n\t\t\tthrow err;\t\t\t\t\t// Rethrow - the try/catch here is just to be explicit about this case.\n\t\t}\n\t} else {\n\t\tstartpos = LoadFEN(\"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\");\n\t}\n\n\tlet root = NewRoot(startpos);\n\tlet node = root;\n\n\tlet inside_brace = false;\t\t\t// {} are comments. Braces do not nest.\n\n\tlet callstack = [];\t\t\t\t\t// When a parenthesis \"(\" opens, we record the node to \"return\" to later, on the \"callstack\".\n\n\tlet token = new_byte_pusher();\n\n\tlet finished = false;\n\n\tfor (let rawline of o.movebufs) {\n\n\t\tif (rawline.length === 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (rawline[0] === 37) {\t\t// Percent % sign is a special comment type.\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (let i = 0; i < rawline.length; i++) {\n\n\t\t\t// Note that, when adding characters to our current token, we peek forwards\n\t\t\t// to check if it's the end of the token. Therefore, it's safe for these\n\t\t\t// special characters to fire a continue immediately.\n\n\t\t\tlet c = rawline[i];\n\n\t\t\tif (c === 123) {\t\t\t\t\t\t\t\t\t// The opening brace { for a comment\n\t\t\t\tinside_brace = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (inside_brace) {\n\t\t\t\tif (c === 125) {\t\t\t\t\t\t\t\t// The closing brace }\n\t\t\t\t\tinside_brace = false;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (c === 40) {\t\t\t\t\t\t\t\t\t\t// The opening parenthesis (\n\t\t\t\tcallstack.push(node);\n\t\t\t\tnode = node.parent;\t\t\t\t\t\t\t\t// Unplay the last move.\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (c === 41) {\t\t\t\t\t\t\t\t\t\t// The closing parenthesis )\n\t\t\t\tnode = callstack[callstack.length - 1];\n\t\t\t\tcallstack = callstack.slice(0, -1);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// So...\n\n\t\t\ttoken.push(c);\n\n\t\t\t// Is the current token complete?\n\t\t\t// We'll start a new token when we see any of the following...\n\n\t\t\tlet peek = rawline[i + 1];\n\n\t\t\tif (\n\t\t\tpeek === undefined\t\t||\t\t\t// end of line\n\t\t\tpeek <= 32\t\t\t\t||\t\t\t// whitespace\n\t\t\tpeek === 40\t\t\t\t||\t\t\t// (\n\t\t\tpeek === 41\t\t\t\t||\t\t\t// )\n\t\t\tpeek === 46\t\t\t\t||\t\t\t// .\n\t\t\tpeek === 123) {\t\t\t\t\t\t// {\n\n\t\t\t\tlet s = token.string().trim();\n\t\t\t\ttoken.reset();\t\t\t\t\t// For the next round.\n\n\t\t\t\t// The above conditional means \".\" can only appear as the first character.\n\t\t\t\t// Strings like \"...\" get decomposed to a series of \".\" tokens since each one terminates the token in front of it.\n\n\t\t\t\tif (s[0] === \".\") {\n\t\t\t\t\ts = s.slice(1);\t\t\t\t// s is now guaranteed not to start with \".\"\n\t\t\t\t}\n\n\t\t\t\t// Parse s.\n\n\t\t\t\tif (s === \"\" || s === \"+\" || s.startsWith(\"$\") || StringIsNumeric(s)) {\n\t\t\t\t\t// Useless token.\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (s === \"1/2-1/2\" || s === \"1-0\" || s === \"0-1\" || s === \"*\") {\n\t\t\t\t\tfinished = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t// Probably an actual move...\n\n\t\t\t\tlet [move, error] = node.board.parse_pgn(s);\n\n\t\t\t\tif (error) {\n\n\t\t\t\t\t// If the problem specifically is one of Kd4, Ke4, Kd5, Ke5, it's probably just a DGT board thing\n\t\t\t\t\t// due to the kings being moved to indicate the result.\n\n\t\t\t\t\tif (s.includes(\"Kd4\") || s.includes(\"Ke4\") || s.includes(\"Kd5\") || s.includes(\"Ke5\") ||\n\t\t\t\t\t\ts.includes(\"Kxd4\") || s.includes(\"Kxe4\") || s.includes(\"Kxd5\") || s.includes(\"Kxe5\"))\n\t\t\t\t\t{\n\t\t\t\t\t\tfinished = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tDestroyTree(root);\n\t\t\t\t\t\tthrow `\"${s}\" -- ${error}`;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tnode = node.make_move(move, true);\n\t\t\t}\n\t\t}\n\n\t\tif (finished) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Save all tags into the root.\n\n\tif (!root.tags) {\n\t\troot.tags = Object.create(null);\n\t}\n\tfor (let key of Object.keys(o.tags)) {\n\t\troot.tags[key] = o.tags[key];\n\t}\n\n\treturn root;\n}\n"
  },
  {
    "path": "files/src/renderer/63_polyglot.js",
    "content": "\"use strict\";\n\n// http://hgm.nubati.net/book_format.html\n\n// Note on bitwise operations on BigInt values: everything is treated as infinite-length twos-compliment,\n// which means negatives will never be accidentally introduced. (For normal Numbers, bitwise operations\n// coerce to 32-bit signed.)\n\nconst PolyglotPieceXorVals = [\t\t// the trailing n here means BigInt\n\t0x9d39247e33776d41n, 0x2af7398005aaa5c7n, 0x44db015024623547n, 0x9c15f73e62a76ae2n,\n\t0x75834465489c0c89n, 0x3290ac3a203001bfn, 0x0fbbad1f61042279n, 0xe83a908ff2fb60can,\n\t0x0d7e765d58755c10n, 0x1a083822ceafe02dn, 0x9605d5f0e25ec3b0n, 0xd021ff5cd13a2ed5n,\n\t0x40bdf15d4a672e32n, 0x011355146fd56395n, 0x5db4832046f3d9e5n, 0x239f8b2d7ff719ccn,\n\t0x05d1a1ae85b49aa1n, 0x679f848f6e8fc971n, 0x7449bbff801fed0bn, 0x7d11cdb1c3b7adf0n,\n\t0x82c7709e781eb7ccn, 0xf3218f1c9510786cn, 0x331478f3af51bbe6n, 0x4bb38de5e7219443n,\n\t0xaa649c6ebcfd50fcn, 0x8dbd98a352afd40bn, 0x87d2074b81d79217n, 0x19f3c751d3e92ae1n,\n\t0xb4ab30f062b19abfn, 0x7b0500ac42047ac4n, 0xc9452ca81a09d85dn, 0x24aa6c514da27500n,\n\t0x4c9f34427501b447n, 0x14a68fd73c910841n, 0xa71b9b83461cbd93n, 0x03488b95b0f1850fn,\n\t0x637b2b34ff93c040n, 0x09d1bc9a3dd90a94n, 0x3575668334a1dd3bn, 0x735e2b97a4c45a23n,\n\t0x18727070f1bd400bn, 0x1fcbacd259bf02e7n, 0xd310a7c2ce9b6555n, 0xbf983fe0fe5d8244n,\n\t0x9f74d14f7454a824n, 0x51ebdc4ab9ba3035n, 0x5c82c505db9ab0fan, 0xfcf7fe8a3430b241n,\n\t0x3253a729b9ba3dden, 0x8c74c368081b3075n, 0xb9bc6c87167c33e7n, 0x7ef48f2b83024e20n,\n\t0x11d505d4c351bd7fn, 0x6568fca92c76a243n, 0x4de0b0f40f32a7b8n, 0x96d693460cc37e5dn,\n\t0x42e240cb63689f2fn, 0x6d2bdcdae2919661n, 0x42880b0236e4d951n, 0x5f0f4a5898171bb6n,\n\t0x39f890f579f92f88n, 0x93c5b5f47356388bn, 0x63dc359d8d231b78n, 0xec16ca8aea98ad76n,\n\t0x5355f900c2a82dc7n, 0x07fb9f855a997142n, 0x5093417aa8a7ed5en, 0x7bcbc38da25a7f3cn,\n\t0x19fc8a768cf4b6d4n, 0x637a7780decfc0d9n, 0x8249a47aee0e41f7n, 0x79ad695501e7d1e8n,\n\t0x14acbaf4777d5776n, 0xf145b6beccdea195n, 0xdabf2ac8201752fcn, 0x24c3c94df9c8d3f6n,\n\t0xbb6e2924f03912ean, 0x0ce26c0b95c980d9n, 0xa49cd132bfbf7cc4n, 0xe99d662af4243939n,\n\t0x27e6ad7891165c3fn, 0x8535f040b9744ff1n, 0x54b3f4fa5f40d873n, 0x72b12c32127fed2bn,\n\t0xee954d3c7b411f47n, 0x9a85ac909a24eaa1n, 0x70ac4cd9f04f21f5n, 0xf9b89d3e99a075c2n,\n\t0x87b3e2b2b5c907b1n, 0xa366e5b8c54f48b8n, 0xae4a9346cc3f7cf2n, 0x1920c04d47267bbdn,\n\t0x87bf02c6b49e2ae9n, 0x092237ac237f3859n, 0xff07f64ef8ed14d0n, 0x8de8dca9f03cc54en,\n\t0x9c1633264db49c89n, 0xb3f22c3d0b0b38edn, 0x390e5fb44d01144bn, 0x5bfea5b4712768e9n,\n\t0x1e1032911fa78984n, 0x9a74acb964e78cb3n, 0x4f80f7a035dafb04n, 0x6304d09a0b3738c4n,\n\t0x2171e64683023a08n, 0x5b9b63eb9ceff80cn, 0x506aacf489889342n, 0x1881afc9a3a701d6n,\n\t0x6503080440750644n, 0xdfd395339cdbf4a7n, 0xef927dbcf00c20f2n, 0x7b32f7d1e03680ecn,\n\t0xb9fd7620e7316243n, 0x05a7e8a57db91b77n, 0xb5889c6e15630a75n, 0x4a750a09ce9573f7n,\n\t0xcf464cec899a2f8an, 0xf538639ce705b824n, 0x3c79a0ff5580ef7fn, 0xede6c87f8477609dn,\n\t0x799e81f05bc93f31n, 0x86536b8cf3428a8cn, 0x97d7374c60087b73n, 0xa246637cff328532n,\n\t0x043fcae60cc0eba0n, 0x920e449535dd359en, 0x70eb093b15b290ccn, 0x73a1921916591cbdn,\n\t0x56436c9fe1a1aa8dn, 0xefac4b70633b8f81n, 0xbb215798d45df7afn, 0x45f20042f24f1768n,\n\t0x930f80f4e8eb7462n, 0xff6712ffcfd75ea1n, 0xae623fd67468aa70n, 0xdd2c5bc84bc8d8fcn,\n\t0x7eed120d54cf2dd9n, 0x22fe545401165f1cn, 0xc91800e98fb99929n, 0x808bd68e6ac10365n,\n\t0xdec468145b7605f6n, 0x1bede3a3aef53302n, 0x43539603d6c55602n, 0xaa969b5c691ccb7an,\n\t0xa87832d392efee56n, 0x65942c7b3c7e11aen, 0xded2d633cad004f6n, 0x21f08570f420e565n,\n\t0xb415938d7da94e3cn, 0x91b859e59ecb6350n, 0x10cff333e0ed804an, 0x28aed140be0bb7ddn,\n\t0xc5cc1d89724fa456n, 0x5648f680f11a2741n, 0x2d255069f0b7dab3n, 0x9bc5a38ef729abd4n,\n\t0xef2f054308f6a2bcn, 0xaf2042f5cc5c2858n, 0x480412bab7f5be2an, 0xaef3af4a563dfe43n,\n\t0x19afe59ae451497fn, 0x52593803dff1e840n, 0xf4f076e65f2ce6f0n, 0x11379625747d5af3n,\n\t0xbce5d2248682c115n, 0x9da4243de836994fn, 0x066f70b33fe09017n, 0x4dc4de189b671a1cn,\n\t0x51039ab7712457c3n, 0xc07a3f80c31fb4b4n, 0xb46ee9c5e64a6e7cn, 0xb3819a42abe61c87n,\n\t0x21a007933a522a20n, 0x2df16f761598aa4fn, 0x763c4a1371b368fdn, 0xf793c46702e086a0n,\n\t0xd7288e012aeb8d31n, 0xde336a2a4bc1c44bn, 0x0bf692b38d079f23n, 0x2c604a7a177326b3n,\n\t0x4850e73e03eb6064n, 0xcfc447f1e53c8e1bn, 0xb05ca3f564268d99n, 0x9ae182c8bc9474e8n,\n\t0xa4fc4bd4fc5558can, 0xe755178d58fc4e76n, 0x69b97db1a4c03dfen, 0xf9b5b7c4acc67c96n,\n\t0xfc6a82d64b8655fbn, 0x9c684cb6c4d24417n, 0x8ec97d2917456ed0n, 0x6703df9d2924e97en,\n\t0xc547f57e42a7444en, 0x78e37644e7cad29en, 0xfe9a44e9362f05fan, 0x08bd35cc38336615n,\n\t0x9315e5eb3a129acen, 0x94061b871e04df75n, 0xdf1d9f9d784ba010n, 0x3bba57b68871b59dn,\n\t0xd2b7adeeded1f73fn, 0xf7a255d83bc373f8n, 0xd7f4f2448c0ceb81n, 0xd95be88cd210ffa7n,\n\t0x336f52f8ff4728e7n, 0xa74049dac312ac71n, 0xa2f61bb6e437fdb5n, 0x4f2a5cb07f6a35b3n,\n\t0x87d380bda5bf7859n, 0x16b9f7e06c453a21n, 0x7ba2484c8a0fd54en, 0xf3a678cad9a2e38cn,\n\t0x39b0bf7dde437ba2n, 0xfcaf55c1bf8a4424n, 0x18fcf680573fa594n, 0x4c0563b89f495ac3n,\n\t0x40e087931a00930dn, 0x8cffa9412eb642c1n, 0x68ca39053261169fn, 0x7a1ee967d27579e2n,\n\t0x9d1d60e5076f5b6fn, 0x3810e399b6f65ba2n, 0x32095b6d4ab5f9b1n, 0x35cab62109dd038an,\n\t0xa90b24499fcfafb1n, 0x77a225a07cc2c6bdn, 0x513e5e634c70e331n, 0x4361c0ca3f692f12n,\n\t0xd941aca44b20a45bn, 0x528f7c8602c5807bn, 0x52ab92beb9613989n, 0x9d1dfa2efc557f73n,\n\t0x722ff175f572c348n, 0x1d1260a51107fe97n, 0x7a249a57ec0c9ba2n, 0x04208fe9e8f7f2d6n,\n\t0x5a110c6058b920a0n, 0x0cd9a497658a5698n, 0x56fd23c8f9715a4cn, 0x284c847b9d887aaen,\n\t0x04feabfbbdb619cbn, 0x742e1e651c60ba83n, 0x9a9632e65904ad3cn, 0x881b82a13b51b9e2n,\n\t0x506e6744cd974924n, 0xb0183db56ffc6a79n, 0x0ed9b915c66ed37en, 0x5e11e86d5873d484n,\n\t0xf678647e3519ac6en, 0x1b85d488d0f20cc5n, 0xdab9fe6525d89021n, 0x0d151d86adb73615n,\n\t0xa865a54edcc0f019n, 0x93c42566aef98ffbn, 0x99e7afeabe000731n, 0x48cbff086ddf285an,\n\t0x7f9b6af1ebf78bafn, 0x58627e1a149bba21n, 0x2cd16e2abd791e33n, 0xd363eff5f0977996n,\n\t0x0ce2a38c344a6eedn, 0x1a804aadb9cfa741n, 0x907f30421d78c5den, 0x501f65edb3034d07n,\n\t0x37624ae5a48fa6e9n, 0x957baf61700cff4en, 0x3a6c27934e31188an, 0xd49503536abca345n,\n\t0x088e049589c432e0n, 0xf943aee7febf21b8n, 0x6c3b8e3e336139d3n, 0x364f6ffa464ee52en,\n\t0xd60f6dcedc314222n, 0x56963b0dca418fc0n, 0x16f50edf91e513afn, 0xef1955914b609f93n,\n\t0x565601c0364e3228n, 0xecb53939887e8175n, 0xbac7a9a18531294bn, 0xb344c470397bba52n,\n\t0x65d34954daf3cebdn, 0xb4b81b3fa97511e2n, 0xb422061193d6f6a7n, 0x071582401c38434dn,\n\t0x7a13f18bbedc4ff5n, 0xbc4097b116c524d2n, 0x59b97885e2f2ea28n, 0x99170a5dc3115544n,\n\t0x6f423357e7c6a9f9n, 0x325928ee6e6f8794n, 0xd0e4366228b03343n, 0x565c31f7de89ea27n,\n\t0x30f5611484119414n, 0xd873db391292ed4fn, 0x7bd94e1d8e17debcn, 0xc7d9f16864a76e94n,\n\t0x947ae053ee56e63cn, 0xc8c93882f9475f5fn, 0x3a9bf55ba91f81can, 0xd9a11fbb3d9808e4n,\n\t0x0fd22063edc29fcan, 0xb3f256d8aca0b0b9n, 0xb03031a8b4516e84n, 0x35dd37d5871448afn,\n\t0xe9f6082b05542e4en, 0xebfafa33d7254b59n, 0x9255abb50d532280n, 0xb9ab4ce57f2d34f3n,\n\t0x693501d628297551n, 0xc62c58f97dd949bfn, 0xcd454f8f19c5126an, 0xbbe83f4ecc2bdecbn,\n\t0xdc842b7e2819e230n, 0xba89142e007503b8n, 0xa3bc941d0a5061cbn, 0xe9f6760e32cd8021n,\n\t0x09c7e552bc76492fn, 0x852f54934da55cc9n, 0x8107fccf064fcf56n, 0x098954d51fff6580n,\n\t0x23b70edb1955c4bfn, 0xc330de426430f69dn, 0x4715ed43e8a45c0an, 0xa8d7e4dab780a08dn,\n\t0x0572b974f03ce0bbn, 0xb57d2e985e1419c7n, 0xe8d9ecbe2cf3d73fn, 0x2fe4b17170e59750n,\n\t0x11317ba87905e790n, 0x7fbf21ec8a1f45ecn, 0x1725cabfcb045b00n, 0x964e915cd5e2b207n,\n\t0x3e2b8bcbf016d66dn, 0xbe7444e39328a0acn, 0xf85b2b4fbcde44b7n, 0x49353fea39ba63b1n,\n\t0x1dd01aafcd53486an, 0x1fca8a92fd719f85n, 0xfc7c95d827357afan, 0x18a6a990c8b35ebdn,\n\t0xcccb7005c6b9c28dn, 0x3bdbb92c43b17f26n, 0xaa70b5b4f89695a2n, 0xe94c39a54a98307fn,\n\t0xb7a0b174cff6f36en, 0xd4dba84729af48adn, 0x2e18bc1ad9704a68n, 0x2de0966daf2f8b1cn,\n\t0xb9c11d5b1e43a07en, 0x64972d68dee33360n, 0x94628d38d0c20584n, 0xdbc0d2b6ab90a559n,\n\t0xd2733c4335c6a72fn, 0x7e75d99d94a70f4dn, 0x6ced1983376fa72bn, 0x97fcaacbf030bc24n,\n\t0x7b77497b32503b12n, 0x8547eddfb81ccb94n, 0x79999cdff70902cbn, 0xcffe1939438e9b24n,\n\t0x829626e3892d95d7n, 0x92fae24291f2b3f1n, 0x63e22c147b9c3403n, 0xc678b6d860284a1cn,\n\t0x5873888850659ae7n, 0x0981dcd296a8736dn, 0x9f65789a6509a440n, 0x9ff38fed72e9052fn,\n\t0xe479ee5b9930578cn, 0xe7f28ecd2d49eecdn, 0x56c074a581ea17fen, 0x5544f7d774b14aefn,\n\t0x7b3f0195fc6f290fn, 0x12153635b2c0cf57n, 0x7f5126dbba5e0ca7n, 0x7a76956c3eafb413n,\n\t0x3d5774a11d31ab39n, 0x8a1b083821f40cb4n, 0x7b4a38e32537df62n, 0x950113646d1d6e03n,\n\t0x4da8979a0041e8a9n, 0x3bc36e078f7515d7n, 0x5d0a12f27ad310d1n, 0x7f9d1a2e1ebe1327n,\n\t0xda3a361b1c5157b1n, 0xdcdd7d20903d0c25n, 0x36833336d068f707n, 0xce68341f79893389n,\n\t0xab9090168dd05f34n, 0x43954b3252dc25e5n, 0xb438c2b67f98e5e9n, 0x10dcd78e3851a492n,\n\t0xdbc27ab5447822bfn, 0x9b3cdb65f82ca382n, 0xb67b7896167b4c84n, 0xbfced1b0048eac50n,\n\t0xa9119b60369ffebdn, 0x1fff7ac80904bf45n, 0xac12fb171817eee7n, 0xaf08da9177dda93dn,\n\t0x1b0cab936e65c744n, 0xb559eb1d04e5e932n, 0xc37b45b3f8d6f2ban, 0xc3a9dc228caac9e9n,\n\t0xf3b8b6675a6507ffn, 0x9fc477de4ed681dan, 0x67378d8eccef96cbn, 0x6dd856d94d259236n,\n\t0xa319ce15b0b4db31n, 0x073973751f12dd5en, 0x8a8e849eb32781a5n, 0xe1925c71285279f5n,\n\t0x74c04bf1790c0efen, 0x4dda48153c94938an, 0x9d266d6a1cc0542cn, 0x7440fb816508c4fen,\n\t0x13328503df48229fn, 0xd6bf7baee43cac40n, 0x4838d65f6ef6748fn, 0x1e152328f3318dean,\n\t0x8f8419a348f296bfn, 0x72c8834a5957b511n, 0xd7a023a73260b45cn, 0x94ebc8abcfb56daen,\n\t0x9fc10d0f989993e0n, 0xde68a2355b93cae6n, 0xa44cfe79ae538bben, 0x9d1d84fcce371425n,\n\t0x51d2b1ab2ddfb636n, 0x2fd7e4b9e72cd38cn, 0x65ca5b96b7552210n, 0xdd69a0d8ab3b546dn,\n\t0x604d51b25fbf70e2n, 0x73aa8a564fb7ac9en, 0x1a8c1e992b941148n, 0xaac40a2703d9bea0n,\n\t0x764dbeae7fa4f3a6n, 0x1e99b96e70a9be8bn, 0x2c5e9deb57ef4743n, 0x3a938fee32d29981n,\n\t0x26e6db8ffdf5adfen, 0x469356c504ec9f9dn, 0xc8763c5b08d1908cn, 0x3f6c6af859d80055n,\n\t0x7f7cc39420a3a545n, 0x9bfb227ebdf4c5cen, 0x89039d79d6fc5c5cn, 0x8fe88b57305e2ab6n,\n\t0xa09e8c8c35ab96den, 0xfa7e393983325753n, 0xd6b6d0ecc617c699n, 0xdfea21ea9e7557e3n,\n\t0xb67c1fa481680af8n, 0xca1e3785a9e724e5n, 0x1cfc8bed0d681639n, 0xd18d8549d140caean,\n\t0x4ed0fe7e9dc91335n, 0xe4dbf0634473f5d2n, 0x1761f93a44d5aefen, 0x53898e4c3910da55n,\n\t0x734de8181f6ec39an, 0x2680b122baa28d97n, 0x298af231c85bafabn, 0x7983eed3740847d5n,\n\t0x66c1a2a1a60cd889n, 0x9e17e49642a3e4c1n, 0xedb454e7badc0805n, 0x50b704cab602c329n,\n\t0x4cc317fb9cddd023n, 0x66b4835d9eafea22n, 0x219b97e26ffc81bdn, 0x261e4e4c0a333a9dn,\n\t0x1fe2cca76517db90n, 0xd7504dfa8816edbbn, 0xb9571fa04dc089c8n, 0x1ddc0325259b27den,\n\t0xcf3f4688801eb9aan, 0xf4f5d05c10cab243n, 0x38b6525c21a42b0en, 0x36f60e2ba4fa6800n,\n\t0xeb3593803173e0cen, 0x9c4cd6257c5a3603n, 0xaf0c317d32adaa8an, 0x258e5a80c7204c4bn,\n\t0x8b889d624d44885dn, 0xf4d14597e660f855n, 0xd4347f66ec8941c3n, 0xe699ed85b0dfb40dn,\n\t0x2472f6207c2d0484n, 0xc2a1e7b5b459aeb5n, 0xab4f6451cc1d45ecn, 0x63767572ae3d6174n,\n\t0xa59e0bd101731a28n, 0x116d0016cb948f09n, 0x2cf9c8ca052f6e9fn, 0x0b090a7560a968e3n,\n\t0xabeeddb2dde06ff1n, 0x58efc10b06a2068dn, 0xc6e57a78fbd986e0n, 0x2eab8ca63ce802d7n,\n\t0x14a195640116f336n, 0x7c0828dd624ec390n, 0xd74bbe77e6116ac7n, 0x804456af10f5fb53n,\n\t0xebe9ea2adf4321c7n, 0x03219a39ee587a30n, 0x49787fef17af9924n, 0xa1e9300cd8520548n,\n\t0x5b45e522e4b1b4efn, 0xb49c3b3995091a36n, 0xd4490ad526f14431n, 0x12a8f216af9418c2n,\n\t0x001f837cc7350524n, 0x1877b51e57a764d5n, 0xa2853b80f17f58een, 0x993e1de72d36d310n,\n\t0xb3598080ce64a656n, 0x252f59cf0d9f04bbn, 0xd23c8e176d113600n, 0x1bda0492e7e4586en,\n\t0x21e0bd5026c619bfn, 0x3b097adaf088f94en, 0x8d14dedb30be846en, 0xf95cffa23af5f6f4n,\n\t0x3871700761b3f743n, 0xca672b91e9e4fa16n, 0x64c8e531bff53b55n, 0x241260ed4ad1e87dn,\n\t0x106c09b972d2e822n, 0x7fba195410e5ca30n, 0x7884d9bc6cb569d8n, 0x0647dfedcd894a29n,\n\t0x63573ff03e224774n, 0x4fc8e9560f91b123n, 0x1db956e450275779n, 0xb8d91274b9e9d4fbn,\n\t0xa2ebee47e2fbfce1n, 0xd9f1f30ccd97fb09n, 0xefed53d75fd64e6bn, 0x2e6d02c36017f67fn,\n\t0xa9aa4d20db084e9bn, 0xb64be8d8b25396c1n, 0x70cb6af7c2d5bcf0n, 0x98f076a4f7a2322en,\n\t0xbf84470805e69b5fn, 0x94c3251f06f90cf3n, 0x3e003e616a6591e9n, 0xb925a6cd0421aff3n,\n\t0x61bdd1307c66e300n, 0xbf8d5108e27e0d48n, 0x240ab57a8b888b20n, 0xfc87614baf287e07n,\n\t0xef02cdd06ffdb432n, 0xa1082c0466df6c0an, 0x8215e577001332c8n, 0xd39bb9c3a48db6cfn,\n\t0x2738259634305c14n, 0x61cf4f94c97df93dn, 0x1b6baca2ae4e125bn, 0x758f450c88572e0bn,\n\t0x959f587d507a8359n, 0xb063e962e045f54dn, 0x60e8ed72c0dff5d1n, 0x7b64978555326f9fn,\n\t0xfd080d236da814ban, 0x8c90fd9b083f4558n, 0x106f72fe81e2c590n, 0x7976033a39f7d952n,\n\t0xa4ec0132764ca04bn, 0x733ea705fae4fa77n, 0xb4d8f77bc3e56167n, 0x9e21f4f903b33fd9n,\n\t0x9d765e419fb69f6dn, 0xd30c088ba61ea5efn, 0x5d94337fbfaf7f5bn, 0x1a4e4822eb4d7a59n,\n\t0x6ffe73e81b637fb3n, 0xddf957bc36d8b9can, 0x64d0e29eea8838b3n, 0x08dd9bdfd96b9f63n,\n\t0x087e79e5a57d1d13n, 0xe328e230e3e2b3fbn, 0x1c2559e30f0946ben, 0x720bf5f26f4d2eaan,\n\t0xb0774d261cc609dbn, 0x443f64ec5a371195n, 0x4112cf68649a260en, 0xd813f2fab7f5c5can,\n\t0x660d3257380841een, 0x59ac2c7873f910a3n, 0xe846963877671a17n, 0x93b633abfa3469f8n,\n\t0xc0c0f5a60ef4cdcfn, 0xcaf21ecd4377b28cn, 0x57277707199b8175n, 0x506c11b9d90e8b1dn,\n\t0xd83cc2687a19255fn, 0x4a29c6465a314cd1n, 0xed2df21216235097n, 0xb5635c95ff7296e2n,\n\t0x22af003ab672e811n, 0x52e762596bf68235n, 0x9aeba33ac6ecc6b0n, 0x944f6de09134dfb6n,\n\t0x6c47bec883a7de39n, 0x6ad047c430a12104n, 0xa5b1cfdba0ab4067n, 0x7c45d833aff07862n,\n\t0x5092ef950a16da0bn, 0x9338e69c052b8e7bn, 0x455a4b4cfe30e3f5n, 0x6b02e63195ad0cf8n,\n\t0x6b17b224bad6bf27n, 0xd1e0ccd25bb9c169n, 0xde0c89a556b9ae70n, 0x50065e535a213cf6n,\n\t0x9c1169fa2777b874n, 0x78edefd694af1eedn, 0x6dc93d9526a50e68n, 0xee97f453f06791edn,\n\t0x32ab0edb696703d3n, 0x3a6853c7e70757a7n, 0x31865ced6120f37dn, 0x67fef95d92607890n,\n\t0x1f2b1d1f15f6dc9cn, 0xb69e38a8965c6b65n, 0xaa9119ff184cccf4n, 0xf43c732873f24c13n,\n\t0xfb4a3d794a9a80d2n, 0x3550c2321fd6109cn, 0x371f77e76bb8417en, 0x6bfa9aae5ec05779n,\n\t0xcd04f3ff001a4778n, 0xe3273522064480can, 0x9f91508bffcfc14an, 0x049a7f41061a9e60n,\n\t0xfcb6be43a9f2fe9bn, 0x08de8a1c7797da9bn, 0x8f9887e6078735a1n, 0xb5b4071dbfc73a66n,\n\t0x230e343dfba08d33n, 0x43ed7f5a0fae657dn, 0x3a88a0fbbcb05c63n, 0x21874b8b4d2dbc4fn,\n\t0x1bdea12e35f6a8c9n, 0x53c065c6c8e63528n, 0xe34a1d250e7a8d6bn, 0xd6b04d3b7651dd7en,\n\t0x5e90277e7cb39e2dn, 0x2c046f22062dc67dn, 0xb10bb459132d0a26n, 0x3fa9ddfb67e2f199n,\n\t0x0e09b88e1914f7afn, 0x10e8b35af3eeab37n, 0x9eedeca8e272b933n, 0xd4c718bc4ae8ae5fn,\n\t0x81536d601170fc20n, 0x91b534f885818a06n, 0xec8177f83f900978n, 0x190e714fada5156en,\n\t0xb592bf39b0364963n, 0x89c350c893ae7dc1n, 0xac042e70f8b383f2n, 0xb49b52e587a1ee60n,\n\t0xfb152fe3ff26da89n, 0x3e666e6f69ae2c15n, 0x3b544ebe544c19f9n, 0xe805a1e290cf2456n,\n\t0x24b33c9d7ed25117n, 0xe74733427b72f0c1n, 0x0a804d18b7097475n, 0x57e3306d881edb4fn,\n\t0x4ae7d6a36eb5dbcbn, 0x2d8d5432157064c8n, 0xd1e649de1e7f268bn, 0x8a328a1cedfe552cn,\n\t0x07a3aec79624c7dan, 0x84547ddc3e203c94n, 0x990a98fd5071d263n, 0x1a4ff12616eefc89n,\n\t0xf6f7fd1431714200n, 0x30c05b1ba332f41cn, 0x8d2636b81555a786n, 0x46c9feb55d120902n,\n\t0xccec0a73b49c9921n, 0x4e9d2827355fc492n, 0x19ebb029435dcb0fn, 0x4659d2b743848a2cn,\n\t0x963ef2c96b33be31n, 0x74f85198b05a2e7dn, 0x5a0f544dd2b1fb18n, 0x03727073c2e134b1n,\n\t0xc7f6aa2de59aea61n, 0x352787baa0d7c22fn, 0x9853eab63b5e0b35n, 0xabbdcdd7ed5c0860n,\n\t0xcf05daf5ac8d77b0n, 0x49cad48cebf4a71en, 0x7a4c10ec2158c4a6n, 0xd9e92aa246bf719en,\n\t0x13ae978d09fe5557n, 0x730499af921549ffn, 0x4e4b705b92903ba4n, 0xff577222c14f0a3an,\n\t0x55b6344cf97aafaen, 0xb862225b055b6960n, 0xcac09afbddd2cdb4n, 0xdaf8e9829fe96b5fn,\n\t0xb5fdfc5d3132c498n, 0x310cb380db6f7503n, 0xe87fbb46217a360en, 0x2102ae466ebb1148n,\n\t0xf8549e1a3aa5e00dn, 0x07a69afdcc42261an, 0xc4c118bfe78feaaen, 0xf9f4892ed96bd438n,\n\t0x1af3dbe25d8f45dan, 0xf5b4b0b0d2deeeb4n, 0x962aceefa82e1c84n, 0x046e3ecaaf453ce9n,\n\t0xf05d129681949a4cn, 0x964781ce734b3c84n, 0x9c2ed44081ce5fbdn, 0x522e23f3925e319en,\n\t0x177e00f9fc32f791n, 0x2bc60a63a6f3b3f2n, 0x222bbfae61725606n, 0x486289ddcc3d6780n,\n\t0x7dc7785b8efdfc80n, 0x8af38731c02ba980n, 0x1fab64ea29a2ddf7n, 0xe4d9429322cd065an,\n\t0x9da058c67844f20cn, 0x24c0e332b70019b0n, 0x233003b5a6cfe6adn, 0xd586bd01c5c217f6n,\n\t0x5e5637885f29bc2bn, 0x7eba726d8c94094bn, 0x0a56a5f0bfe39272n, 0xd79476a84ee20d06n,\n\t0x9e4c1269baa4bf37n, 0x17efee45b0dee640n, 0x1d95b0a5fcf90bc6n, 0x93cbe0b699c2585dn,\n\t0x65fa4f227a2b6d79n, 0xd5f9e858292504d5n, 0xc2b5a03f71471a6fn, 0x59300222b4561e00n,\n\t0xce2f8642ca0712dcn, 0x7ca9723fbb2e8988n, 0x2785338347f2ba08n, 0xc61bb3a141e50e8cn,\n\t0x150f361dab9dec26n, 0x9f6a419d382595f4n, 0x64a53dc924fe7ac9n, 0x142de49fff7a7c3dn,\n\t0x0c335248857fa9e7n, 0x0a9c32d5eae45305n, 0xe6c42178c4bbb92en, 0x71f1ce2490d20b07n,\n\t0xf1bcc3d275afe51an, 0xe728e8c83c334074n, 0x96fbf83a12884624n, 0x81a1549fd6573da5n,\n\t0x5fa7867caf35e149n, 0x56986e2ef3ed091bn, 0x917f1dd5f8886c61n, 0xd20d8c88c8ffe65fn,\n];\n\nconst PolyglotCastleXorVals = [\n\t0x31d71dce64b2c310n, 0xf165b587df898190n, 0xa57e6339dd2cf3a0n, 0x1ef6e6dbb1961ec9n,\n];\n\nconst PolyglotEnPassantXorVals = [\n\t0x70cc73d90bc26e24n, 0xe21a6b35df0c3ad7n, 0x003a93d8b2806962n, 0x1c99ded33cb890a1n,\n\t0xcf3145de0add4289n, 0xd0e4427a5514fb72n, 0x77c621cc9fb3a483n, 0x67a34dac4356550bn,\n];\n\nconst PolyglotActiveXorVal = 0xf8d626aaaf278509n;\n\n// ------------------------------------------------------------------------------------------------------------------------\n\nconst PolyglotMoveLookup = [];\t\t// Lookup table for book blob bytes 8-9. Most of the moves are impossible, but meh.\n\nfor (let n = 0; n < 65536; n++) {\n\n\tlet to_file    =  (n >>  0) & 0x07;\n\tlet to_row     =  (n >>  3) & 0x07;\n\tlet from_file  =  (n >>  6) & 0x07;\n\tlet from_row   =  (n >>  9) & 0x07;\n\tlet promval    =  (n >> 12) & 0x07;\n\n\tlet source = Point(from_file, 7 - from_row);\n\tlet dest   = Point(to_file,   7 - to_row);\n\n\tlet promch = [\"\", \"n\", \"b\", \"r\", \"q\", \"\", \"\", \"\"][promval];\n\n\tPolyglotMoveLookup.push(source.s + dest.s + promch);\n}\n\n// ------------------------------------------------------------------------------------------------------------------------\n\nfunction KeyFromBoard(board) {\n\n\tif (!board) return \"\";\n\n\tlet key = 0n;\n\n\t// Note to anyone reading this trying to make their own Polyglot routines:\n\t// My board (0,0) is a8, not a1. Otherwise, you'd use y and not (7 - y) in the index calc.\n\n\tfor (let x = 0; x < 8; x++) {\n\t\tfor (let y = 0; y < 8; y++) {\n\t\t\tif (!board.state[x][y]) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet piecekind = \"pPnNbBrRqQkK\".indexOf(board.state[x][y]);\n\t\t\tif (piecekind === -1) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlet index = (64 * piecekind) + (8 * (7 - y)) + x;\t\t\t\t\t// I mean here.\n\t\t\tkey ^= PolyglotPieceXorVals[index];\n\t\t}\n\t}\n\n\tif (board.castling.includes(\"H\")) key ^= PolyglotCastleXorVals[0];\n\tif (board.castling.includes(\"A\")) key ^= PolyglotCastleXorVals[1];\n\tif (board.castling.includes(\"h\")) key ^= PolyglotCastleXorVals[2];\n\tif (board.castling.includes(\"a\")) key ^= PolyglotCastleXorVals[3];\n\n\t// Happily, the format's idea of when an en passant square should be included is identical to mine...\n\t// \"If the opponent has performed a double pawn push and there is now a pawn next to it belonging to the player to move.\"\n\n\tif (board.enpassant) {\n\t\tkey ^= PolyglotEnPassantXorVals[board.enpassant.x];\n\t}\n\n\tif (board.active === \"w\") {\n\t\tkey ^= PolyglotActiveXorVal;\n\t}\n\n\treturn key;\n}\n\nfunction ParsePolyglotBlob(buf, off) {\t\t// Args are Buffer + offset.\n\n\tif (buf instanceof Buffer === false) {\n\t\tthrow \"ParsePolyglotBlob() bad book\";\n\t}\n\n\tif (off < 0 || off > buf.length - 16) {\n\t\tthrow \"ParsePolyglotBlob() bad offset\";\n\t}\n\n\t// Bytes 0-7 represent the key as a big-endian number.\n\n\tlet hi = (buf[off++] * 16777216) + (buf[off++] * 65536) + (buf[off++] * 256) + buf[off++];\n\tlet lo = (buf[off++] * 16777216) + (buf[off++] * 65536) + (buf[off++] * 256) + buf[off++];\n\tlet key = (BigInt(hi) << 32n) + BigInt(lo);\n\n\t// Bytes 8-9 represent the move as a big-endian bitfield, uh...\n\n\tlet move = PolyglotMoveLookup[(buf[off++] * 256) + buf[off++]];\n\n\t// Bytes 10-11 represent the quality as a big-endian number.\n\n\tlet weight = (buf[off++] * 256) + buf[off++];\n\n\treturn {key, move, weight};\n}\n\nfunction SortAndDeclutterPGNBook(book) {\n\n\t// book must be an array of objects of form {key, move, weight}\n\n\tif (book instanceof Buffer) {\n\t\tthrow \"Cannot call SortAndDeclutterPGNBook() on a Buffer.\";\n\t}\n\n\tif (Array.isArray(book) === false) {\n\t\tthrow \"SortAndDeclutterPGNBook() bad arg\";\n\t}\n\n\tif (book.length === 0) {\n\t\treturn;\n\t}\n\n\tbook.sort((a, b) => {\t\t\t\t\t// Sort by key AND move to make deduplication possible.\n\t\tif (a.key < b.key) return -1;\n\t\tif (a.key > b.key) return 1;\n\t\tif (a.move < b.move) return -1;\n\t\tif (a.move > b.move) return 1;\n\t\treturn 0;\n\t});\n\n\t// Now we deduplicate the book in place... (algorithm relies on length >= 1)\n\n\tlet i = 0;\t\t\t// Slow index\n\tlet j = 1;\t\t\t// Fast index\n\n\twhile (true) {\n\n\t\tif (j >= book.length) {\n\t\t\tbook.length = i + 1;\n\t\t\tbreak;\n\t\t}\n\n\t\tif (book[i].key === book[j].key && book[i].move === book[j].move) {\n\n\t\t\tbook[i].weight++;\n\t\t\tj++;\n\n\t\t} else {\n\n\t\t\tbook[i + 1] = book[j];\n\t\t\ti++;\n\t\t\tj++;\n\n\t\t}\n\t}\n}\n\nfunction BookAtLogicalIndex(book, i) {\n\tif (book instanceof Buffer) {\n\t\treturn ParsePolyglotBlob(book, i * 16);\n\t} else {\n\t\treturn book[i];\n\t}\n}\n\nfunction BookLogicalLength(book) {\n\tif (book instanceof Buffer) {\n\t\treturn Math.floor(book.length / 16);\n\t} else if (Array.isArray(book)) {\n\t\treturn book.length;\n\t} else {\n\t\treturn 0;\n\t}\n}\n\nfunction BookProbe(key, book) {\n\n\t// book is either the raw buffer, or an array of objects of form {key, move, weight}\n\n\tlet logical_length = BookLogicalLength(book);\t\t\t// returns 0 on most bad args\n\n\tif (!key || logical_length === 0) {\n\t\treturn [];\n\t}\n\n\tlet hit;\n\tlet cur;\n\n\tlet mid;\n\tlet lowerbound = 0;\n\tlet upperbound = logical_length - 1;\n\n\twhile (true) {\n\n\t\tif (lowerbound > upperbound) {\n\n\t\t\tconsole.log(\"BookProbe(): lowerbound > upperbound\");\n\t\t\tbreak;\n\n\t\t} else {\n\n\t\t\tmid = Math.floor((upperbound + lowerbound) / 2);\t\t// If upper and lower are neighbours, mid is the left one.\n\t\t\tcur = BookAtLogicalIndex(book, mid);\n\n\t\t\tif (cur.key === key) {\n\t\t\t\thit = cur;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (lowerbound === upperbound) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (cur.key < key) {\n\t\t\t\tlowerbound = mid + 1;\t\t// +1 is used here so the neighbours case does change lower.\n\t\t\t} else {\n\t\t\t\tupperbound = mid;\t\t\t// In the neighbours case, upper becomes equal to lower. Can't do -1 or it would go to the left of lower.\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t}\n\n\tif (hit === undefined) {\n\t\treturn [];\n\t}\n\n\tlet ret = [hit];\n\n\tlet left = mid;\n\tlet right = mid;\n\n\twhile (left > 0) {\n\t\tcur = BookAtLogicalIndex(book, --left);\n\t\tif (cur.key === key) {\n\t\t\tret.unshift(cur);\n\t\t} else {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\twhile (right < logical_length - 1) {\n\t\tcur = BookAtLogicalIndex(book, ++right);\n\t\tif (cur.key === key) {\n\t\t\tret.push(cur);\n\t\t} else {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn ret;\n}\n\nfunction BookSortedTest(book) {\t\t// Returns true if a cursory inspection suggests the book is sorted.\n\n\tlet logical_length = BookLogicalLength(book);\n\n\tif (logical_length === 0) {\n\t\treturn true;\n\t}\n\n\tlet indices = [];\n\n\tfor (let n = 0; n < 100; n++) {\n\t\tindices.push(RandInt(0, logical_length));\n\t}\n\n\tindices.sort((a, b) => {\n\t\treturn a - b;\n\t});\n\n\tlet check = 0n;\n\n\tfor (let index of indices) {\n\n\t\tlet object = BookAtLogicalIndex(book, index);\n\n\t\tif (object.key < check) {\n\t\t\treturn false;\n\t\t}\n\n\t\tcheck = object.key;\n\t}\n\n\treturn true;\n}\n\n// For debugging...........................................................................................................\n\nfunction HubProbe() {\n\tlet objects = BookProbe(KeyFromBoard(hub.tree.node.board), hub.book);\n\tlet ret = [];\n\tfor (let o of objects) {\n\t\tret.push({\n\t\t\thex: BigIntToHex(o.key),\n\t\t\tkey: o.key,\n\t\t\tmove: o.move,\n\t\t\tweight: o.weight,\n\t\t});\n\t}\n\treturn ret;\n}\n\nfunction BigIntToHex(big) {\n\tlet s = big.toString(16);\n\twhile (s.length < 16) s = \"0\" + s;\n\treturn s;\n}\n\nfunction BookStressTest() {\n\n\t// Given a randomly chosen singleton (position with 1 move),\n\t// does BookProbe() actually find it?\n\n\tif (!hub.book) return \"Need hub to have a book!\";\n\n\tlet trials = 0;\n\tlet successes = 0;\n\tlet logical_length = BookLogicalLength(hub.book);\n\n\tfor (let n = 0; n < 100000; n++) {\n\n\t\tlet i = RandInt(1, logical_length - 1);\t\t// So that's actually a value between 1 and logical_length - 2, inclusive.\n\n\t\tlet left_o = BookAtLogicalIndex(hub.book, i - 1);\n\t\tlet mid_o = BookAtLogicalIndex(hub.book, i);\n\t\tlet right_o = BookAtLogicalIndex(hub.book, i + 1);\n\n\t\tif (left_o.key === mid_o.key || right_o.key === mid_o.key) {\n\t\t\tcontinue;\n\t\t}\n\t\ttrials++;\n\t\tlet proberesults = BookProbe(mid_o.key, hub.book);\n\t\tif (proberesults.length === 1) {\n\t\t\tsuccesses++;\n\t\t} else {\n\t\t\tconsole.log(\"Missed:\", mid_o.key);\n\t\t}\n\t}\n\n\treturn `${trials} trials, ${successes} successes, ${trials - successes} failures.`;\n}\n"
  },
  {
    "path": "files/src/renderer/65_loaders.js",
    "content": "\"use strict\";\n\n// Non-blocking loader objects.\n//\n// Implementation rule: The callback property is non-null iff it's still possible that the load will succeed.\n// If callback === null this implies that shutdown() has already been called at least once.\n//\n// Also, every loader starts itself via setTimeout so that the caller can finish whatever it was doing first.\n// This prevents some weird inconsistency with order-of-events (whether it matters I don't know).\n// ------------------------------------------------------------------------------------------------------------------------------\n\nfunction NewFastPGNLoader(foo, callback) {\n\n\t// foo is allowed to be filepath or Buffer\n\n\tif (typeof foo !== \"string\" && foo instanceof Buffer === false) {\n\t\tthrow \"NewFastPGNLoader() bad call\";\n\t}\n\n\tlet loader = Object.create(null);\n\tloader.type = \"pgn\";\n\tloader.starttime = performance.now();\n\n\tloader.callback = callback;\n\tloader.msg = \"Loading PGN...\";\n\tloader.buf = null;\n\tloader.indices = [];\n\n\tloader.off = 0;\n\tloader.phase = 1;\n\tloader.search = Buffer.from(\"\\n\\n[\");\n\tloader.fix = 2;\t\t\t\t\t\t\t\t\t\t// Where the [ char will be\n\n\tloader.shutdown = function() {\n\t\tthis.callback = null;\n\t\tthis.msg = \"\";\n\t\tthis.buf = null;\n\t\tthis.indices = null;\n\t};\n\n\tloader.load = function(foo) {\n\t\tif (this.callback) {\n\t\t\tif (foo instanceof Buffer) {\n\t\t\t\tthis.buf = foo;\n\t\t\t\tthis.continue();\n\t\t\t} else {\n\t\t\t\tfs.readFile(foo, (err, data) => {\n\t\t\t\t\tif (this.callback) {\t\t\t\t// Must test again, because this is later.\n\t\t\t\t\t\tif (err) {\n\t\t\t\t\t\t\tlet cb = this.callback; cb(err, null);\n\t\t\t\t\t\t\tthis.shutdown();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.buf = data;\n\t\t\t\t\t\t\tthis.continue();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t};\n\n\tloader.continue = function() {\n\n\t\tif (!this.callback) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.indices.length === 0 && this.buf.length > 0) {\n\t\t\tthis.indices.push(0);\n\t\t}\n\n\t\tlet continuetime = performance.now();\n\n\t\twhile (true) {\n\n\t\t\tlet index = this.buf.indexOf(this.search, this.off);\n\n\t\t\tif (index === -1) {\n\t\t\t\tif (this.phase === 1) {\n\t\t\t\t\tthis.phase = 2;\n\t\t\t\t\tthis.search = Buffer.from(\"\\n\\r\\n[\");\n\t\t\t\t\tthis.fix = 3;\n\t\t\t\t\tthis.off = 0;\n\t\t\t\t\tcontinue;\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.indices.push(index + this.fix);\n\t\t\tthis.off = index + 1;\n\n\t\t\tif (this.indices.length % 100 === 0) {\n\t\t\t\tif (performance.now() - continuetime > 10) {\n\t\t\t\t\tthis.msg = `Loading PGN... ${this.indices.length} games`;\n\t\t\t\t\tsetTimeout(() => {this.continue();}, 10);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Once, after the while loop is broken...\n\n\t\tthis.indices.sort((a, b) => a - b);\n\n\t\tlet ret = new_pgndata(this.buf, this.indices);\n\t\tlet cb = this.callback; cb(null, ret);\n\t\tthis.shutdown();\n\t};\n\n\tsetTimeout(() => {loader.load(foo);}, 0);\n\treturn loader;\n}\n\n// ------------------------------------------------------------------------------------------------------------------------------\n\nfunction NewPolyglotBookLoader(filename, callback) {\n\n\tlet loader = Object.create(null);\n\tloader.type = \"book\";\n\tloader.starttime = performance.now();\n\n\tloader.callback = callback;\n\tloader.msg = \"Loading book...\";\n\n\tloader.shutdown = function() {\n\t\tthis.callback = null;\n\t\tthis.msg = \"\";\n\t};\n\n\tloader.load = function(filename) {\n\t\tif (this.callback) {\n\t\t\tfs.readFile(filename, (err, data) => {\n\t\t\t\tif (this.callback) {\t\t\t\t\t// Must test again, because this is later.\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\tlet cb = this.callback; cb(err, null);\n\t\t\t\t\t\tthis.shutdown();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlet cb = this.callback; cb(null, data);\n\t\t\t\t\t\tthis.shutdown();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n\n\tsetTimeout(() => {loader.load(filename);}, 0);\n\treturn loader;\n}\n\n// ------------------------------------------------------------------------------------------------------------------------------\n\nfunction NewPGNBookLoader(filename, callback) {\n\n\tlet loader = Object.create(null);\n\tloader.type = \"book\";\n\tloader.starttime = performance.now();\n\n\tloader.callback = callback;\n\tloader.msg = \"Loading book...\";\n\tloader.buf = null;\n\tloader.book = [];\n\tloader.pgndata = null;\n\tloader.fastloader = null;\n\n\tloader.n = 0;\n\n\tloader.shutdown = function() {\n\t\tthis.callback = null;\n\t\tthis.msg = \"\";\n\t\tthis.buf = null;\n\t\tthis.book = null;\n\t\tthis.pgndata = null;\n\t\tif (this.fastloader) {\n\t\t\tthis.fastloader.shutdown();\n\t\t\tthis.fastloader = null;\n\t\t}\n\t};\n\n\tloader.load = function(filename) {\n\t\tif (this.callback) {\n\t\t\tthis.fastloader = NewFastPGNLoader(filename, (err, pgndata) => {\n\t\t\t\tif (this.callback) {\t\t\t\t\t// Must test again, because this is later.\n\t\t\t\t\tif (err) {\n\t\t\t\t\t\tlet cb = this.callback; cb(err, null);\n\t\t\t\t\t\tthis.shutdown();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.pgndata = pgndata;\n\t\t\t\t\t\tthis.continue();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n\n\tloader.continue = function() {\n\n\t\tif (!this.callback) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet continuetime = performance.now();\n\t\tlet count = this.pgndata.count();\n\n\t\twhile (true) {\n\n\t\t\tif (this.n >= count) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tlet o = this.pgndata.getrecord(this.n++);\n\n\t\t\ttry {\n\t\t\t\tlet root = LoadPGNRecord(o);\t\t\t\t\t// Note that this calls DestroyTree() itself if it must throw.\n\t\t\t\tthis.book = AddTreeToBook(root, this.book);\n\t\t\t\tDestroyTree(root);\n\t\t\t} catch (err) {\n\t\t\t\t//\n\t\t\t}\n\n\t\t\tif (performance.now() - continuetime > 10) {\n\t\t\t\tthis.msg = `Loading book... ${(100 * (this.n / count)).toFixed(0)}%`;\n\t\t\t\tsetTimeout(() => {this.continue();}, 10);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Once, after the while loop is broken...\n\n\t\tSortAndDeclutterPGNBook(this.book);\n\t\tlet ret = this.book;\t\t\t\t\t\t// Just in case I ever replace the direct cb() with a setTimeout (shutdown would cause this.book to be null).\n\t\tlet cb = this.callback; cb(null, ret);\n\t\tthis.shutdown();\n\t};\n\n\tsetTimeout(() => {loader.load(filename);}, 0);\n\treturn loader;\n}\n"
  },
  {
    "path": "files/src/renderer/71_tree_handler.js",
    "content": "\"use strict\";\n\n// The point is that updating the node should trigger an immediate redraw. The caller doesn't need\n// to care about redrawing. Ideally, this object should be able to make good decisions about how\n// to best redraw.\n\nfunction NewTreeHandler() {\n\tlet handler = Object.create(null);\n\tObject.assign(handler, tree_manipulation_props);\n\tObject.assign(handler, tree_draw_props);\n\thandler.root = NewRoot();\n\thandler.node = handler.root;\n\thandler.node.table.autopopulate(handler.node);\n\treturn handler;\n}\n\nlet tree_manipulation_props = {\n\n\t// Since we use Object.assign(), it's bad form to have any deep objects in the props.\n\n\ttree_version: 0,\t\t// Increment every time the tree structure changes.\n\troot: null,\n\tnode: null,\n\n\t// Where relevant, return values of the methods are whether this.node changed -\n\t// i.e. whether the hub has to call position_changed()\n\n\treplace_tree: function(root) {\n\t\tDestroyTree(this.root);\n\t\tthis.root = root;\n\t\tthis.node = root;\n\t\tthis.node.table.autopopulate(this.node);\n\t\tthis.tree_version++;\n\t\tthis.dom_from_scratch();\n\t\treturn true;\n\t},\n\n\tset_node: function(node) {\n\n\t\t// Note that we may call dom_easy_highlight_change() so don't\n\t\t// rely on this to draw any nodes that never got drawn.\n\n\t\tif (!node || node === this.node || node.destroyed) {\n\t\t\treturn false;\n\t\t}\n\n\t\tlet original_node = this.node;\n\t\tthis.node = node;\n\n\t\tif (original_node.is_same_line(this.node)) {\t\t// This test is super-fast if one node is a parent of the other\n\t\t\tthis.dom_easy_highlight_change();\n\t\t} else {\n\t\t\tthis.dom_from_scratch();\n\t\t}\n\n\t\treturn true;\n\t},\n\n\tprev: function() {\n\t\treturn this.set_node(this.node.parent);\t\t\t\t// OK if undefined\n\t},\n\n\tnext: function() {\n\t\treturn this.set_node(this.node.children[0]);\t\t// OK if undefined\n\t},\n\n\tgoto_root: function() {\n\t\treturn this.set_node(this.root);\n\t},\n\n\tgoto_end: function() {\n\t\treturn this.set_node(this.node.get_end());\n\t},\n\n\tprevious_sibling: function() {\n\t\tif (!this.node.parent || this.node.parent.children.length < 2) {\n\t\t\treturn false;\n\t\t}\n\t\tif (this.node.parent.children[0] === this.node) {\n\t\t\treturn this.set_node(this.node.parent.children[this.node.parent.children.length - 1]);\n\t\t}\n\t\tfor (let i = this.node.parent.children.length - 1; i > 0; i--) {\n\t\t\tif (this.node.parent.children[i] === this.node) {\n\t\t\t\treturn this.set_node(this.node.parent.children[i - 1]);\n\t\t\t}\n\t\t}\n\t\treturn false;\t\t// Can't get here.\n\t},\n\n\tnext_sibling: function() {\n\t\tif (!this.node.parent || this.node.parent.children.length < 2) {\n\t\t\treturn false;\n\t\t}\n\t\tif (this.node.parent.children[this.node.parent.children.length - 1] === this.node) {\n\t\t\treturn this.set_node(this.node.parent.children[0]);\n\t\t}\n\t\tfor (let i = 0; i < this.node.parent.children.length - 1; i++) {\n\t\t\tif (this.node.parent.children[i] === this.node) {\n\t\t\t\treturn this.set_node(this.node.parent.children[i + 1]);\n\t\t\t}\n\t\t}\n\t\treturn false;\t\t// Can't get here.\n\t},\n\n\treturn_to_main_line: function() {\n\t\tlet node = this.node.return_to_main_line_helper();\n\t\tif (this.node === node) {\n\t\t\treturn false;\n\t\t}\n\t\tthis.node = node;\n\t\tthis.dom_from_scratch();\n\t\treturn true;\n\t},\n\n\tdelete_node: function() {\n\n\t\tif (!this.node.parent) {\n\t\t\tthis.delete_children();\n\t\t\treturn false;\n\t\t}\n\n\t\tlet parent = this.node.parent;\n\t\tthis.node.detach();\n\t\tthis.node = parent;\n\t\tthis.tree_version++;\n\t\tthis.dom_from_scratch();\n\t\treturn true;\n\t},\n\n\tmake_move: function(s) {\n\n\t\t// s must be exactly a legal move, including having promotion char iff needed (e.g. e2e1q)\n\n\t\tlet next_node_id__initial = next_node_id;\n\t\tthis.node = this.node.make_move(s);\n\n\t\tif (next_node_id !== next_node_id__initial) {\t\t// NewNode() was called\n\t\t\tthis.tree_version++;\n\t\t}\n\n\t\tthis.dom_from_scratch();\t\t\t// Could potentially call something else here.\n\t\treturn true;\n\t},\n\n\tmake_move_sequence: function(moves, set_this_node = true) {\n\n\t\tif (Array.isArray(moves) === false || moves.length === 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\tlet next_node_id__initial = next_node_id;\n\n\t\tlet node = this.node;\n\t\tfor (let s of moves) {\n\t\t\tnode = node.make_move(s);\t\t// Calling the node's make_move() method, not handler's\n\t\t}\n\n\t\tif (set_this_node) {\n\t\t\tthis.node = node;\n\t\t}\n\n\t\tif (next_node_id !== next_node_id__initial) {\t\t// NewNode() was called\n\t\t\tthis.tree_version++;\n\t\t}\n\n\t\tthis.dom_from_scratch();\n\t\treturn true;\n\t},\n\n\tadd_move_sequence: function(moves) {\n\t\treturn this.make_move_sequence(moves, false);\n\t},\n\n\t// -------------------------------------------------------------------------------------------------------------\n\t// The following methods don't ever change this.node - so the caller has no action to take. No return value.\n\n\tpromote_to_main_line: function() {\n\n\t\tlet node = this.node;\n\t\tlet changed = false;\n\n\t\twhile (node.parent) {\n\t\t\tif (node.parent.children[0] !== node) {\n\t\t\t\tfor (let n = 1; n < node.parent.children.length; n++) {\n\t\t\t\t\tif (node.parent.children[n] === node) {\n\t\t\t\t\t\tnode.parent.children[n] = node.parent.children[0];\n\t\t\t\t\t\tnode.parent.children[0] = node;\n\t\t\t\t\t\tchanged = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tnode = node.parent;\n\t\t}\n\n\t\tif (changed) {\n\t\t\tthis.tree_version++;\n\t\t\tthis.dom_from_scratch();\n\t\t}\n\t},\n\n\tpromote: function() {\n\n\t\tlet node = this.node;\n\t\tlet changed = false;\n\n\t\twhile (node.parent) {\n\t\t\tif (node.parent.children[0] !== node) {\n\t\t\t\tfor (let n = 1; n < node.parent.children.length; n++) {\n\t\t\t\t\tif (node.parent.children[n] === node) {\n\t\t\t\t\t\tlet swapper = node.parent.children[n - 1];\n\t\t\t\t\t\tnode.parent.children[n - 1] = node;\n\t\t\t\t\t\tnode.parent.children[n] = swapper;\n\t\t\t\t\t\tchanged = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\t\t// 1 tree change only\n\t\t\t}\n\t\t\tnode = node.parent;\n\t\t}\n\n\t\tif (changed) {\n\t\t\tthis.tree_version++;\n\t\t\tthis.dom_from_scratch();\n\t\t}\n\t},\n\n\tdelete_other_lines: function() {\n\n\t\tthis.promote_to_main_line();\n\n\t\tlet changed = false;\n\t\tlet node = this.root;\n\n\t\twhile (node.children.length > 0) {\n\t\t\tfor (let child of node.children.slice(1)) {\n\t\t\t\tchild.detach();\n\t\t\t\tchanged = true;\n\t\t\t}\n\t\t\tnode = node.children[0];\n\t\t}\n\n\t\tif (changed) {\n\t\t\tthis.tree_version++;\n\t\t\tthis.dom_from_scratch();\t\t// This may be the 2nd draw since promote_to_main_line() may have drawn. Bah.\n\t\t}\n\t},\n\n\tdelete_children: function() {\n\n\t\tif (this.node.children.length > 0) {\n\t\t\tfor (let child of this.node.children) {\n\t\t\t\tchild.detach();\n\t\t\t}\n\t\t\tthis.tree_version++;\n\t\t\tthis.dom_from_scratch();\n\t\t}\n\t},\n\n\tdelete_siblings: function() {\n\n\t\tlet changed = false;\n\n\t\tif (this.node.parent) {\n\t\t\tfor (let sibling of this.node.parent.children) {\n\t\t\t\tif (sibling !== this.node) {\n\t\t\t\t\tsibling.detach();\n\t\t\t\t\tchanged = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (changed) {\n\t\t\tthis.tree_version++;\n\t\t\tthis.dom_from_scratch();\n\t\t}\n\t},\n\n\t// -------------------------------------------------------------------------------------------------------------\n\n\thandle_click: function(event) {\n\n\t\tlet n = EventPathN(event, \"node_\");\n\t\tif (typeof n !== \"number\") {\n\t\t\treturn false;\n\t\t}\n\n\t\tlet node = live_nodes[n.toString()];\n\n\t\tif (!node || node.destroyed) {\t\t// Probably the check for .destroyed is unnecessary.\n\t\t\treturn false;\n\t\t}\n\n\t\treturn this.set_node(node);\n\t},\n};\n\n"
  },
  {
    "path": "files/src/renderer/72_tree_draw.js",
    "content": "\"use strict\";\n\nlet tree_draw_props = {\n\n\t// Since we use Object.assign(), it's bad form to have any deep objects in the props.\n\n\tordered_nodes_cache: null,\n\tordered_nodes_cache_version: -1,\n\n\tdom_easy_highlight_change: function() {\n\n\t\t// When the previously highlighted node and the newly highlighted node are on the same line,\n\t\t// with the same end-of-line, meaning no gray / white changes are needed.\n\n\t\tlet dom_highlight = this.get_movelist_highlight();\n\t\tlet highlight_class;\n\n\t\tif (dom_highlight && dom_highlight.classList.contains(\"movelist_highlight_yellow\")) {\n\t\t\thighlight_class = \"movelist_highlight_yellow\";\n\t\t} else {\n\t\t\thighlight_class = \"movelist_highlight_blue\";\n\t\t}\n\n\t\tif (dom_highlight) {\n\t\t\tdom_highlight.classList.remove(\"movelist_highlight_blue\");\n\t\t\tdom_highlight.classList.remove(\"movelist_highlight_yellow\");\n\t\t}\n\n\t\tlet dom_node = document.getElementById(`node_${this.node.id}`);\n\n\t\tif (dom_node) {\n\t\t\tdom_node.classList.add(highlight_class);\n\t\t}\n\n\t\tthis.fix_scrollbar_position();\n\t},\n\n\tdom_from_scratch: function() {\n\n\t\t// Some prep-work (we need to undo all this at the end)...\n\n\t\tlet line_end = this.node.get_end();\n\n\t\tlet foo = line_end;\n\t\twhile (foo) {\n\t\t\tfoo.current_line = true;\t// These nodes will be coloured white, others gray\n\t\t\tfoo = foo.parent;\n\t\t}\n\n\t\tlet main_line_end = this.root.get_end();\n\t\tmain_line_end.main_line_end = true;\n\n\t\t// Begin...\n\n\t\tif (this.ordered_nodes_cache_version !== this.tree_version) {\n\t\t\tthis.ordered_nodes_cache = get_ordered_nodes(this.root);\n\t\t\tthis.ordered_nodes_cache_version = this.tree_version;\n\t\t}\n\n\t\tlet pseudoelements = [];\t\t// Objects containing opening span string `<span foo>` and text string\n\n\t\tfor (let item of this.ordered_nodes_cache) {\n\n\t\t\tif (item === this.root) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// As a crude hack, the item can be a bracket string.\n\t\t\t// Deal with that first...\n\n\t\t\tif (typeof item === \"string\") {\n\t\t\t\tpseudoelements.push({\n\t\t\t\t\topener: \"\",\n\t\t\t\t\ttext: item,\n\t\t\t\t\tcloser: \"\"\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// So item is a real node...\n\n\t\t\tlet node = item;\n\t\t\tlet classes = [];\n\n\t\t\tif (node === this.node) {\n\t\t\t\tif (node.is_main_line()) {\n\t\t\t\t\tclasses.push(\"movelist_highlight_blue\");\n\t\t\t\t} else {\n\t\t\t\t\tclasses.push(\"movelist_highlight_yellow\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (node.current_line) {\n\t\t\t\tclasses.push(\"white\");\t\t// Otherwise, inherits gray colour from movelist CSS\n\t\t\t}\n\n\t\t\tpseudoelements.push({\n\t\t\t\topener: `<span class=\"${classes.join(\" \")}\" id=\"node_${node.id}\">`,\n\t\t\t\ttext: node.token(),\n\t\t\t\tcloser: `</span>`\n\t\t\t});\n\t\t}\n\n\t\tlet all_spans = [];\n\n\t\tfor (let n = 0; n < pseudoelements.length; n++) {\n\n\t\t\tlet p = pseudoelements[n];\n\t\t\tlet nextp = pseudoelements[n + 1];\t\t// Possibly undefined\n\n\t\t\tif (!nextp || (p.text !== \"(\" && nextp.text !== \")\")) {\n\t\t\t\tp.text += \" \";\n\t\t\t}\n\n\t\t\tall_spans.push(`${p.opener}${p.text}${p.closer}`);\n\t\t}\n\n\t\tmovelist.innerHTML = all_spans.join(\"\");\n\n\t\t// Undo the damage to our tree from the start...\n\n\t\tfoo = line_end;\n\t\twhile(foo) {\n\t\t\tdelete foo.current_line;\n\t\t\tfoo = foo.parent;\n\t\t}\n\n\t\tdelete main_line_end.main_line_end;\n\n\t\t// And finally...\n\n\t\tthis.fix_scrollbar_position();\n\t},\n\n\t// Helpers...\n\n\tget_movelist_highlight: function() {\n\t\tlet elements = document.getElementsByClassName(\"movelist_highlight_blue\");\n\t\tif (elements && elements.length > 0) {\n\t\t\treturn elements[0];\n\t\t}\n\t\telements = document.getElementsByClassName(\"movelist_highlight_yellow\");\n\t\tif (elements && elements.length > 0) {\n\t\t\treturn elements[0];\n\t\t}\n\t\treturn null;\n\t},\n\n\tfix_scrollbar_position: function() {\n\t\tlet highlight = this.get_movelist_highlight();\n\t\tif (highlight) {\n\t\t\tlet top = highlight.offsetTop - movelist.offsetTop;\n\t\t\tif (top < movelist.scrollTop) {\n\t\t\t\tmovelist.scrollTop = top;\n\t\t\t}\n\t\t\tlet bottom = top + highlight.offsetHeight;\n\t\t\tif (bottom > movelist.scrollTop + movelist.offsetHeight) {\n\t\t\t\tmovelist.scrollTop = bottom - movelist.offsetHeight;\n\t\t\t}\n\t\t} else {\n\t\t\tmovelist.scrollTop = 0;\n\t\t}\n\t},\n};\n"
  },
  {
    "path": "files/src/renderer/75_looker.js",
    "content": "\"use strict\";\n\n// Rate limit strategy - thanks to Sopel:\n//\n// .running holds the item in-flight.\n// .pending holds a single item to send after.\n//\n// Note: Don't store the retrieved info in the node.table, because the logic\n// there is already a bit convoluted with __touched, __ghost and whatnot (sadly).\n//\n// Note: format of entries in the DB is {type: \"foo\", moves: {}}\n// where moves is a map of string --> object\n\nfunction NewLooker() {\n\tlet looker = Object.create(null);\n\tlooker.running = null;\n\tlooker.pending = null;\n\tlooker.all_dbs = Object.create(null);\n\tlooker.bans = Object.create(null);\t\t\t// db --> time of last rate-limit\n\tObject.assign(looker, looker_props);\n\treturn looker;\n}\n\nlet looker_props = {\n\n\tclear_queue: function() {\n\t\tthis.running = null;\n\t\tthis.pending = null;\n\t},\n\n\tadd_to_queue: function(board) {\n\n\t\tif (!config.looker_api || !board.normalchess) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!config.look_past_25 && board.fullmove > 25) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Is there a reason the test for whether we've already looked up this\n\t\t// position isn't done here, but is done later at query_api()? I forget.\n\n\t\tlet query = {\t\t\t\t\t\t\t// Since queries are objects, different queries can always be told apart.\n\t\t\tboard: board,\n\t\t\tdb_name: config.looker_api\n\t\t};\n\n\t\tif (!this.running) {\n\t\t\tthis.send_query(query);\n\t\t} else {\n\t\t\tthis.pending = query;\n\t\t}\n\t},\n\n\tsend_query: function(query) {\n\n\t\tthis.running = query;\n\n\t\t// It is ESSENTIAL that every call to send_query() eventually generates a call to query_complete()\n\t\t// so that the item gets removed from the queue. While we don't really need to use promises, doing\n\t\t// it as follows lets me just have a single place where query_complete() is called. I guess.\n\n\t\tthis.query_api(query).catch(error => {\n\t\t\tconsole.log(\"Query failed:\", error);\n\t\t}).finally(() => {\n\t\t\tthis.query_complete(query);\n\t\t});\n\t},\n\n\tquery_complete: function(query) {\n\n\t\tif (this.running !== query) {\t\t\t// Possible if clear_queue() was called.\n\t\t\treturn;\n\t\t}\n\n\t\tlet next_query = this.pending;\n\n\t\tthis.running = null;\n\t\tthis.pending = null;\n\n\t\tif (next_query) {\n\t\t\tthis.send_query(next_query);\n\t\t}\n\t},\n\n\tget_db: function(db_name) {\t\t\t\t\t// Creates it if needed.\n\n\t\tif (typeof db_name !== \"string\") {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (!this.all_dbs[db_name]) {\n\t\t\tthis.all_dbs[db_name] = Object.create(null);\n\t\t}\n\n\t\treturn this.all_dbs[db_name];\n\t},\n\n\tnew_entry: function(db_name, board) {\t\t// Creates a new (empty) entry in the database (to be populated elsewhere) and returns it.\n\n\t\tlet entry = {\n\t\t\ttype: db_name,\n\t\t\tmoves: {},\n\t\t};\n\n\t\tlet db = this.get_db(db_name);\n\t\tdb[board.fen()] = entry;\n\t\treturn entry;\n\t},\n\n\tlookup: function(db_name, board) {\n\n\t\t// Return the full entry for a position. When repeatedly called with the same params, this should\n\t\t// return the same object (unless it changes of course). Returns null if not available.\n\n\t\tlet db = this.get_db(db_name);\n\t\tif (db) {\t\t\t\t\t\t\t\t// Remember get_db() can return null.\n\t\t\tlet ret = db[board.fen()];\n\t\t\tif (ret) {\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t}\n\t\treturn null;\t\t\t\t\t\t\t// I guess we tend to like null over undefined. (Bad habit?)\n\t},\n\n\tset_ban: function(db_name) {\n\t\tthis.bans[db_name] = performance.now();\n\t},\n\n\tquery_api(query) {\t\t// Returns a promise, which is solely used by the caller to attach some cleanup catch/finally()\n\n\t\tif (this.lookup(query.db_name, query.board)) {\t\t\t\t\t\t\t// We already have a result for this board.\n\t\t\treturn Promise.resolve();\t\t\t\t\t\t\t\t\t\t\t// Consider this case a satisfactory result.\n\t\t}\n\n\t\tif (this.bans[query.db_name]) {\n\t\t\tif (performance.now() - this.bans[query.db_name] < 60000) {\t\t\t// No requests within 1 minute of the ban.\n\t\t\t\treturn Promise.resolve();\t\t\t\t\t\t\t\t\t\t// Consider this case a satisfactory result.\n\t\t\t}\n\t\t}\n\n\t\tlet friendly_fen = query.board.fen(true);\n\t\tlet fen_for_web = ReplaceAll(friendly_fen, \" \", \"%20\");\n\n\t\tlet url;\n\n\t\tif (query.db_name === \"chessdbcn\") {\n\t\t\turl = `https://www.chessdb.cn/cdb.php?action=queryall&json=1&board=${fen_for_web}`;\n\t\t} else if (query.db_name === \"lichess_masters\") {\n\t\t\turl = `https://explorer.lichess.org/masters?topGames=0&fen=${fen_for_web}`;\n\t\t} else if (query.db_name === \"lichess_plebs\") {\n\t\t\turl = `https://explorer.lichess.org/lichess?variant=standard&topGames=0&recentGames=0&fen=${fen_for_web}`;\n\t\t} else {\n\t\t\treturn Promise.reject(new Error(\"Bad db_name\"));\n\t\t}\n\n\t\tlet fetch_options = {\n\t\t\theaders: {\"User-Agent\": \"Nibbler\"}\n\t\t};\n\n\t\tif ((query.db_name === \"lichess_masters\" || query.db_name === \"lichess_plebs\") && config.lichess_token) {\n\t\t\tfetch_options.headers[\"Authorization\"] = `Bearer ${config.lichess_token}`;\n\t\t}\n\n\t\treturn fetch(url, fetch_options).then(response => {\n\t\t\tif (response.status === 429) {\t\t\t\t\t\t\t\t\t\t// rate limit hit\n\t\t\t\tthis.set_ban(query.db_name);\n\t\t\t\thub.set_special_message(\"429 Too Many Requests\", \"red\", 5000);\t// relies on hub being in script/global scope, which it is\n\t\t\t\tthrow new Error(\"rate limited\");\n\t\t\t}\n\t\t\tif (!response.ok) {\t\t\t\t\t\t\t\t\t\t\t\t\t// ok means status in range 200-299\n\t\t\t\tthrow new Error(\"response.ok was false\");\n\t\t\t}\n\t\t\treturn response.json();\n\t\t}).then(raw_object => {\n\t\t\tthis.handle_response_object(query, raw_object);\n\t\t});\n\t},\n\n\thandle_response_object: function(query, raw_object) {\n\n\t\tlet board = query.board;\n\t\tlet o = this.new_entry(query.db_name, board);\n\n\t\t// If the raw_object is invalid, now's the time to return - after the empty object\n\t\t// has been stored in the database, so we don't do this lookup again.\n\n\t\tif (typeof raw_object !== \"object\" || raw_object === null || Array.isArray(raw_object.moves) === false) {\n\t\t\treturn;\t\t\t// This can happen e.g. if the position is checkmate.\n\t\t}\n\n\t\t// Our Lichess moves need to know the total number of games so they can return valid stats.\n\t\t// While the total is available as raw_object.white + raw_object.black + raw_object.draws,\n\t\t// it's probably better to sum up the items that we're given.\n\n\t\tlet lichess_position_total = 0;\n\n\t\tif (query.db_name === \"lichess_masters\" || query.db_name === \"lichess_plebs\") {\n\t\t\tfor (let raw_item of raw_object.moves) {\n\t\t\t\tlichess_position_total += raw_item.white + raw_item.black + raw_item.draws;\n\t\t\t}\n\t\t}\n\n\t\t// Now add moves to the entry...\n\n\t\tfor (let raw_item of raw_object.moves) {\n\n\t\t\tlet move = raw_item.uci;\n\t\t\tmove = board.c960_castling_converter(move);\n\n\t\t\tif (query.db_name === \"chessdbcn\") {\n\t\t\t\to.moves[move] = new_chessdbcn_move(board, raw_item);\n\t\t\t} else if (query.db_name === \"lichess_masters\" || query.db_name === \"lichess_plebs\") {\n\t\t\t\to.moves[move] = new_lichess_move(board, raw_item, lichess_position_total);\n\t\t\t}\n\t\t}\n\n\t\t// Note that even if we get no info, we still leave the empty object o in the database,\n\t\t// and this allows us to know that we've done this search already.\n\t},\n};\n\n\n// Below are some functions which use the info a server sends about a single move to create our\n// own object containing just what we need (and with a prototype containing some useful methods).\n\n\nfunction new_chessdbcn_move(board, raw_item) {\t\t\t// The object with info about a single move in a chessdbcn object.\n\tlet ret = Object.create(chessdbcn_move_props);\n\tret.active = board.active;\n\tret.score = raw_item.score / 100;\n\treturn ret;\n}\n\nlet chessdbcn_move_props = {\n\n\ttext: function(pov) {\t\t\t\t\t\t\t\t// pov can be null for current\n\n\t\tlet score = this.score;\n\n\t\tif ((pov === \"w\" && this.active === \"b\") || (pov === \"b\" && this.active === \"w\")) {\n\t\t\tscore = 0 - this.score;\n\t\t}\n\n\t\tlet s = score.toFixed(2);\n\t\tif (s !== \"0.00\" && s[0] !== \"-\") {\n\t\t\ts = \"+\" + s;\n\t\t}\n\n\t\treturn `API: <span class=\"blue\">${s}</span>`;\n\t},\n\n\tsort_score: function() {\n\t\treturn this.score;\n\t},\n};\n\nfunction new_lichess_move(board, raw_item, position_total) {\t\t// The object with info about a single move in a lichess object.\n\tlet ret = Object.create(lichess_move_props);\n\tret.active = board.active;\n\tret.white = raw_item.white;\n\tret.black = raw_item.black;\n\tret.draws = raw_item.draws;\n\tret.total = raw_item.white + raw_item.draws + raw_item.black;\n\tret.position_total = position_total;\n\treturn ret;\n}\n\nlet lichess_move_props = {\n\n\ttext: function(pov) {\t\t\t\t\t\t\t\t// pov can be null for current\n\n\t\tlet actual_pov = pov ? pov : this.active;\n\t\tlet wins = actual_pov === \"w\" ? this.white : this.black;\n\t\tlet ev = (wins + (this.draws / 2)) / this.total;\n\n\t\tlet win_string = (ev * 100).toFixed(1);\n\t\tlet weight_string = (100 * this.total / this.position_total).toFixed(0);\n\n\t\treturn `API win: <span class=\"blue\">${win_string}%</span> freq: <span class=\"blue\">${weight_string}%</span> [${NString(this.total)}]`;\n\t},\n\n\tsort_score: function() {\n\t\treturn this.total;\n\t},\n};\n\n"
  },
  {
    "path": "files/src/renderer/80_info.js",
    "content": "\"use strict\";\n\nfunction NewInfoHandler() {\n\n\tlet ih = Object.create(null);\n\tObject.assign(ih, info_misc_props);\n\tObject.assign(ih, info_receiver_props);\n\tObject.assign(ih, arrow_props);\n\tObject.assign(ih, infobox_props);\n\n\t// Array of possible one-click moves. Updated by draw_arrows(). Used elsewhere.\n\tih.one_click_moves = New2DArray(8, 8, null);\n\n\t// Clickable elements in the infobox. Updated by draw_infobox(). Used elsewhere.\n\tih.info_clickers = [];\n\tih.info_clickers_node_id = null;\n\n\t// Infobox stuff, used solely to skip redraws...\n\tih.last_drawn_node_id = null;\n\tih.last_drawn_version = null;\n\tih.last_drawn_highlight = null;\n\tih.last_drawn_highlight_class = null;\n\tih.last_drawn_length = 0;\n\tih.last_drawn_searchmoves = [];\n\tih.last_drawn_allow_inactive_focus = null;\n\tih.last_drawn_lookup_object = null;\n\n\t// Info about engine cycles. These aren't reset even when the engine resets.\n\tih.engine_cycle = 0;\t\t// Count of \"go\" commands emitted. Since Engine can change, can't store this in Engine objects\n\tih.engine_subcycle = 0;\t\t// Count of how many times we have seen \"multipv 1\" - each time it's a new \"block\" of info\n\tih.ever_updated_a_table = false;\n\n\t// Info about the current engine...\n\t// Note that, when the engine is restarted, hub must call reset_engine_info() to fix these. A bit lame.\n\tih.engine_start_time = performance.now();\n\tih.engine_sent_info = false;\n\tih.engine_sent_q = false;\n\tih.engine_sent_errors = false;\n\tih.error_time = 0;\n\tih.error_log = \"\";\n\tih.next_vms_order_int = 1;\n\n\treturn ih;\n}\n\nlet info_misc_props = {\n\n\treset_engine_info: function() {\n\t\tthis.engine_start_time = performance.now();\n\t\tthis.engine_sent_info = false;\n\t\tthis.engine_sent_q = false;\n\t\tthis.engine_sent_errors = false;\n\t\tthis.error_time = 0;\n\t\tthis.error_log = \"\";\n\t\tthis.next_vms_order_int = 1;\n\t},\n\n\tdisplaying_error_log: function() {\n\n\t\t// Recent error...\n\n\t\tif (this.engine_sent_errors && performance.now() - this.error_time < 10000) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// Engine hasn't yet sent info, and was recently started...\n\n\t\tif (!this.engine_sent_info) {\n\t\t\tif (performance.now() - this.engine_start_time < 5000) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\t// We have never updated a table (meaning we never received useful info from an engine)\n\t\t// and we aren't displaying API info...\n\n\t\tif (!this.ever_updated_a_table && !config.looker_api) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t},\n};\n\nlet info_receiver_props = {\n\n\terr_receive: function(s) {\n\n\t\tif (typeof s !== \"string\") {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.error_log.length > 50000) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet s_low = s.toLowerCase();\n\n\t\tif (s_low.includes(\"warning\") || s_low.includes(\"error\") || s_low.includes(\"unknown\") || s_low.includes(\"failed\") || s_low.includes(\"exception\")) {\n\t\t\tthis.engine_sent_errors = true;\n\t\t\tthis.error_log += `<span class=\"red\">${s}</span><br>`;\n\t\t\tthis.error_time = performance.now();\n\t\t} else {\n\t\t\tthis.error_log += `${s}<br>`;\n\t\t}\n\t},\n\n\treceive: function(engine, search, s) {\n\n\t\tlet node = search.node;\n\n\t\tif (typeof s !== \"string\" || !node || node.destroyed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet board = node.board;\n\n\t\tif (s.startsWith(\"info\") && s.includes(\" pv \") && ((!s.includes(\"lowerbound\") && !s.includes(\"upperbound\")) || config.accept_bounds)) {\n\n\t\t\tif (config.log_info_lines) Log(\"< \" + s);\n\n\t\t\t// info depth 8 seldepth 31 time 3029 nodes 23672 score cp 27 wdl 384 326 290 nps 7843 tbhits 0 multipv 1\n\t\t\t// pv d2d4 g8f6 c2c4 e7e6 g1f3 d7d5 b1c3 f8b4 c1g5 d5c4 e2e4 c7c5 f1c4 h7h6 g5f6 d8f6 e1h1 c5d4 e4e5 f6d8 c3e4\n\n\t\t\tlet infovals = InfoValMany(s, [\"pv\", \"cp\", \"mate\", \"multipv\", \"nodes\", \"nps\", \"time\", \"depth\", \"seldepth\", \"tbhits\"]);\n\n\t\t\tlet tmp;\n\t\t\tlet move_info;\n\t\t\tlet move = infovals[\"pv\"];\n\t\t\tmove = board.c960_castling_converter(move);\n\n\t\t\tif (node.table.moveinfo[move] && !node.table.moveinfo[move].__ghost) {\t\t// We already have move info for this move.\n\t\t\t\tmove_info = node.table.moveinfo[move];\n\t\t\t} else {\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// We don't.\n\t\t\t\tif (board.illegal(move)) {\n\t\t\t\t\tif (config.log_illegal_moves) {\n\t\t\t\t\t\tLog(`INVALID / ILLEGAL MOVE RECEIVED: ${move}`);\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tmove_info = NewInfo(board, move);\n\t\t\t\tnode.table.moveinfo[move] = move_info;\n\t\t\t}\n\n\t\t\tlet move_cycle_pre_update = move_info.cycle;\n\t\t\tlet move_depth_pre_update = move_info.depth;\n\n\t\t\t// ---------------------------------------------------------------------------------------------------------------------\n\n\t\t\tif (!engine.leelaish) {\n\t\t\t\tmove_info.clear_stats();\t\t\t\t// The stats we get this way are all that the engine has, so clear everything.\n\t\t\t}\n\t\t\tmove_info.leelaish = engine.leelaish;\n\n\t\t\tthis.engine_sent_info = true;\t\t\t\t// After the move legality check; i.e. we want REAL info\n\t\t\tthis.ever_updated_a_table = true;\n\t\t\tnode.table.version++;\n\t\t\tnode.table.limit = search.limit;\n\n\t\t\tmove_info.cycle = this.engine_cycle;\n\t\t\tmove_info.__touched = true;\n\n\t\t\t// ---------------------------------------------------------------------------------------------------------------------\n\n\t\t\tlet did_set_q_from_mate = false;\n\n\t\t\ttmp = parseInt(infovals[\"cp\"], 10);\n\t\t\tif (Number.isNaN(tmp) === false) {\n\t\t\t\tmove_info.cp = tmp;\n\t\t\t\tif (this.engine_sent_q === false) {\n\t\t\t\t\tmove_info.q = QfromPawns(tmp / 100);\t\t// Potentially overwritten later by the better QfromWDL()\n\t\t\t\t}\n\t\t\t\tmove_info.mate = 0;\t\t\t\t\t\t\t\t// Engines will send one of cp or mate, so mate gets reset when receiving cp\n\t\t\t}\n\n\t\t\ttmp = parseInt(infovals[\"mate\"], 10);\n\t\t\tif (Number.isNaN(tmp) === false) {\n\t\t\t\tmove_info.mate = tmp;\n\t\t\t\tif (tmp !== 0) {\n\t\t\t\t\tmove_info.q = tmp > 0 ? 1 : -1;\n\t\t\t\t\tmove_info.cp = tmp > 0 ? 32000 : -32000;\n\t\t\t\t\tdid_set_q_from_mate = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttmp = parseInt(infovals[\"multipv\"], 10);\n\t\t\tif (Number.isNaN(tmp) === false) {\n\t\t\t\tmove_info.multipv = tmp;\n\t\t\t\tif (tmp === 1) {\n\t\t\t\t\tthis.engine_subcycle++;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.engine_subcycle++;\n\t\t\t}\n\t\t\tmove_info.subcycle = this.engine_subcycle;\n\n\t\t\ttmp = parseInt(infovals[\"nodes\"], 10);\n\t\t\tif (Number.isNaN(tmp) === false) {\n\t\t\t\tmove_info.uci_nodes = tmp;\n\t\t\t\tnode.table.nodes = tmp;\n\t\t\t}\n\n\t\t\ttmp = parseInt(infovals[\"nps\"], 10);\n\t\t\tif (Number.isNaN(tmp) === false) {\n\t\t\t\tnode.table.nps = tmp;\t\t\t\t\t// Note this is stored in the node.table, not the move_info\n\t\t\t}\n\n\t\t\ttmp = parseInt(infovals[\"time\"], 10);\n\t\t\tif (Number.isNaN(tmp) === false) {\n\t\t\t\tnode.table.time = tmp;\t\t\t\t\t// Note this is stored in the node.table, not the move_info\n\t\t\t}\n\n\t\t\ttmp = parseInt(infovals[\"tbhits\"], 10);\n\t\t\tif (Number.isNaN(tmp) === false) {\n\t\t\t\tnode.table.tbhits = tmp;\t\t\t\t// Note this is stored in the node.table, not the move_info\n\t\t\t}\n\n\t\t\ttmp = parseInt(infovals[\"depth\"], 10);\n\t\t\tif (Number.isNaN(tmp) === false) {\n\t\t\t\tmove_info.depth = tmp;\n\t\t\t}\n\n\t\t\ttmp = parseInt(infovals[\"seldepth\"], 10);\n\t\t\tif (Number.isNaN(tmp) === false) {\n\t\t\t\tmove_info.seldepth = tmp;\n\t\t\t}\n\n\t\t\tmove_info.wdl = InfoWDL(s);\n\t\t\tif (this.engine_sent_q === false && !did_set_q_from_mate && Array.isArray(move_info.wdl)) {\n\t\t\t\tmove_info.q = QfromWDL(move_info.wdl);\n\t\t\t}\n\n\t\t\t// If the engine isn't respecting Chess960 castling format, the PV\n\t\t\t// may contain old-fashioned castling moves...\n\n\t\t\tlet new_pv = InfoPV(s);\n\t\t\tC960_PV_Converter(new_pv, board);\n\n\t\t\tif (CompareArrays(new_pv, move_info.pv) === false) {\n\t\t\t\tif (!board.sequence_illegal(new_pv)) {\n\t\t\t\t\tif (move_cycle_pre_update === move_info.cycle\n\t\t\t\t\t\t&& ArrayStartsWith(move_info.pv, new_pv)\n\t\t\t\t\t\t&& move_depth_pre_update >= move_info.depth - 1\n\t\t\t\t\t) {\n\t\t\t\t\t\t// Skip the update. This partially mitigates Stockfish sending unresolved PVs.\n\t\t\t\t\t\t// We don't skip the update if the old PV is too old - issue noticed by Nagisa.\n\t\t\t\t\t} else {\n\t\t\t\t\t\tmove_info.set_pv(new_pv);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tmove_info.set_pv([move]);\n\t\t\t\t}\n\t\t\t}\n\n\t\t} else if (s.startsWith(\"info string\") && !s.includes(\"NNUE evaluation\")) {\n\n\t\t\tif (config.log_info_lines) Log(\"< \" + s);\n\n\t\t\t// info string d2d4  (293 ) N:   12005 (+169) (P: 22.38%) (WL:  0.09480) (D:  0.326)\n\t\t\t// (M:  7.4) (Q:  0.09480) (U: 0.01211) (Q+U:  0.10691) (V:  0.0898)\n\n\t\t\t// Ceres has been known to send these in Euro decimal format e.g. Q: 0,094\n\t\t\t// We'll have to replace all commas...\n\n\t\t\ts = ReplaceAll(s, \",\", \".\");\n\n\t\t\tlet infovals = InfoValMany(s, [\"string\", \"N:\", \"(D:\", \"(U:\", \"(Q+U:\", \"(S:\", \"(P:\", \"(Q:\", \"(V:\", \"(M:\"]);\n\n\t\t\tlet tmp;\n\t\t\tlet move_info;\n\t\t\tlet move = infovals[\"string\"];\n\n\t\t\tif (move === \"node\") {\t\t\t\t\t\t// Mostly ignore these lines, but...\n\t\t\t\tthis.next_vms_order_int = 1;\t\t\t// ...use them to note that the VerboseMoveStats have completed. A bit sketchy?\n\t\t\t\ttmp = parseInt(infovals[\"N:\"], 10);\n\t\t\t\tif (Number.isNaN(tmp) === false) {\n\t\t\t\t\tnode.table.nodes = tmp;\t\t\t\t// ...and use this line to ensure a valid nodes count for the table. (Mostly helps with Ceres.)\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tmove = board.c960_castling_converter(move);\n\n\t\t\tif (node.table.moveinfo[move] && !node.table.moveinfo[move].__ghost) {\t\t// We already have move info for this move.\n\t\t\t\tmove_info = node.table.moveinfo[move];\n\t\t\t} else {\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// We don't.\n\t\t\t\tif (board.illegal(move)) {\n\t\t\t\t\tif (config.log_illegal_moves) {\n\t\t\t\t\t\tLog(`INVALID / ILLEGAL MOVE RECEIVED: ${move}`);\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tmove_info = NewInfo(board, move);\n\t\t\t\tnode.table.moveinfo[move] = move_info;\n\t\t\t}\n\n\t\t\t// ---------------------------------------------------------------------------------------------------------------------\n\n\t\t\tengine.leelaish = true;\t\t\t\t\t\t// Note this isn't the main way engine.leelaish gets set (because reasons)\n\t\t\tmove_info.leelaish = true;\n\n\t\t\tthis.engine_sent_info = true;\t\t\t\t// After the move legality check; i.e. we want REAL info\n\t\t\tthis.ever_updated_a_table = true;\n\t\t\tnode.table.version++;\n\t\t\tnode.table.limit = search.limit;\n\n\t\t\t// move_info.cycle = this.engine_cycle;\t\t// No... we get VMS lines even when excluded by searchmoves.\n\t\t\t// move_info.subcycle = this.engine_subcycle;\n\t\t\tmove_info.__touched = true;\n\n\t\t\t// ---------------------------------------------------------------------------------------------------------------------\n\n\t\t\tmove_info.vms_order = this.next_vms_order_int++;\n\n\t\t\ttmp = parseInt(infovals[\"N:\"], 10);\n\t\t\tif (Number.isNaN(tmp) === false) {\n\t\t\t\tmove_info.n = tmp;\n\t\t\t}\n\n\t\t\ttmp = parseFloat(infovals[\"(U:\"]);\n\t\t\tif (Number.isNaN(tmp) === false) {\n\t\t\t\tmove_info.u = tmp;\n\t\t\t}\n\n\t\t\ttmp = parseFloat(infovals[\"(Q+U:\"]);\t\t// Q+U, old name for S\n\t\t\tif (Number.isNaN(tmp) === false) {\n\t\t\t\tmove_info.s = tmp;\n\t\t\t}\n\n\t\t\ttmp = parseFloat(infovals[\"(S:\"]);\n\t\t\tif (Number.isNaN(tmp) === false) {\n\t\t\t\tmove_info.s = tmp;\n\t\t\t}\n\n\t\t\ttmp = parseFloat(infovals[\"(P:\"]);\t\t\t// P, parseFloat will ignore the trailing %\n\t\t\tif (Number.isNaN(tmp) === false) {\n\t\t\t\tmove_info.p = tmp;\n\t\t\t}\n\n\t\t\ttmp = parseFloat(infovals[\"(Q:\"]);\n\t\t\tif (Number.isNaN(tmp) === false) {\n\t\t\t\tthis.engine_sent_q = true;\n\t\t\t\tmove_info.q = tmp;\n\t\t\t}\n\n\t\t\ttmp = parseFloat(infovals[\"(V:\"]);\n\t\t\tif (Number.isNaN(tmp) === false) {\n\t\t\t\tmove_info.v = tmp;\n\t\t\t} else {\n\t\t\t\tmove_info.v = null;\t\t\t\t\t\t// V sometimes is -.----- (we used to not do anything here, preserving any old (but confusing) value)\n\t\t\t}\n\n\t\t\ttmp = parseFloat(infovals[\"(M:\"]);\n\t\t\tif (Number.isNaN(tmp) === false) {\n\t\t\t\tmove_info.m = tmp;\n\t\t\t} else {\n\t\t\t\tmove_info.m = null;\t\t\t\t\t\t// M sometimes is -.----- (we used to not do anything here, preserving any old (but confusing) value)\n\t\t\t}\n\n\t\t} else if (s.startsWith(\"info\") && s.includes(\" pv \") && (s.includes(\"lowerbound\") || s.includes(\"upperbound\"))) {\n\n\t\t\tif (config.log_info_lines) Log(\"< \" + s);\n\n\t\t\tlet infovals = InfoValMany(s, [\"pv\", \"multipv\"]);\n\n\t\t\tlet tmp;\n\t\t\tlet move_info;\n\t\t\tlet move = infovals[\"pv\"];\n\t\t\tmove = board.c960_castling_converter(move);\n\n\t\t\tif (node.table.moveinfo[move] && !node.table.moveinfo[move].__ghost) {\t\t// We already have move info for this move.\n\t\t\t\tmove_info = node.table.moveinfo[move];\n\t\t\t}\n\n\t\t\tif (move_info) {\n\t\t\t\ttmp = parseInt(infovals[\"multipv\"], 10);\n\t\t\t\tif (Number.isNaN(tmp) === false) {\n\t\t\t\t\tmove_info.multipv = tmp;\n\t\t\t\t\tmove_info.subcycle = this.engine_subcycle;\n\t\t\t\t}\n\t\t\t}\n\n\t\t} else {\n\n\t\t\tif (config.log_info_lines && config.log_useless_info) Log(\"< \" + s);\n\n\t\t}\n\t},\n};\n"
  },
  {
    "path": "files/src/renderer/81_arrows.js",
    "content": "\"use strict\";\n\nlet arrow_props = {\n\n\tdraw_arrows: function(node, specific_source, show_move) {\t\t// If not nullish, specific_source is a Point() and show_move is a string\n\n\t\t// Function is responsible for updating the one_click_moves array.\n\n\t\tfor (let x = 0; x < 8; x++) {\n\t\t\tfor (let y = 0; y < 8; y++) {\n\t\t\t\tthis.one_click_moves[x][y] = null;\n\t\t\t}\n\t\t}\n\n\t\tif (!config.arrows_enabled || !node || node.destroyed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet full_list = SortedMoveInfo(node);\n\n\t\tif (full_list.length === 0) {\t\t// Keep this test early so we can assume full_list[0] exists later.\n\t\t\treturn;\n\t\t}\n\n\t\tlet best_info = full_list[0];\t\t// Note that, since we may filter the list, it might not contain best_info later.\n\n\t\tlet info_list = [];\n\t\tlet arrows = [];\n\t\tlet heads = [];\n\n\t\tlet mode;\n\t\tlet show_move_was_forced = false;\t// Will become true if the show_move is only in the list because of the show_move arg\n\t\tlet show_move_head = null;\n\n\t\tif (specific_source) {\n\t\t\tmode = \"specific\";\n\t\t} else if (full_list[0].__ghost) {\n\t\t\tmode = \"ghost\";\n\t\t} else if (full_list[0].__touched === false) {\n\t\t\tmode = \"untouched\";\n\t\t} else if (full_list[0].leelaish === false) {\n\t\t\tmode = \"ab\";\n\t\t} else {\n\t\t\tmode = \"normal\";\n\t\t}\n\n\t\tswitch (mode) {\n\n\t\tcase \"normal\":\n\n\t\t\tinfo_list = full_list;\n\t\t\tbreak;\n\n\t\tcase \"ab\":\n\n\t\t\tfor (let info of full_list) {\n\t\t\t\tif (info.__touched && info.subcycle >= full_list[0].subcycle) {\n\t\t\t\t\tinfo_list.push(info);\n\t\t\t\t} else if (info.move === show_move) {\n\t\t\t\t\tinfo_list.push(info);\n\t\t\t\t\tshow_move_was_forced = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase \"ghost\":\n\n\t\t\tfor (let info of full_list) {\n\t\t\t\tif (info.__ghost) {\n\t\t\t\t\tinfo_list.push(info);\n\t\t\t\t} else if (info.move === show_move) {\n\t\t\t\t\tinfo_list.push(info);\n\t\t\t\t\tshow_move_was_forced = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase \"untouched\":\n\n\t\t\tfor (let info of full_list) {\n\t\t\t\tif (info.move === show_move) {\n\t\t\t\t\tinfo_list.push(info);\n\t\t\t\t\tshow_move_was_forced = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase \"specific\":\n\n\t\t\tfor (let info of full_list) {\n\t\t\t\tif (info.move.slice(0, 2) === specific_source.s) {\n\t\t\t\t\tinfo_list.push(info);\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\n\t\t}\n\n\t\t// ------------------------------------------------------------------------------------------------------------\n\n\t\tfor (let i = 0; i < info_list.length; i++) {\n\n\t\t\tlet loss = 0;\n\n\t\t\tif (typeof best_info.q === \"number\" && typeof info_list[i].q === \"number\") {\n\t\t\t\tloss = best_info.value() - info_list[i].value();\n\t\t\t}\n\n\t\t\tlet ok = true;\n\n\t\t\t// Filter for normal (Leelaish) mode...\n\n\t\t\tif (mode === \"normal\") {\n\n\t\t\t\tif (config.arrow_filter_type === \"top\") {\n\t\t\t\t\tif (i !== 0) {\n\t\t\t\t\t\tok = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (config.arrow_filter_type === \"N\") {\n\t\t\t\t\tif (typeof info_list[i].n !== \"number\" || info_list[i].n === 0) {\n\t\t\t\t\t\tok = false;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlet n_fraction = info_list[i].n / node.table.nodes;\n\t\t\t\t\t\tif (n_fraction < config.arrow_filter_value) {\n\t\t\t\t\t\t\tok = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Moves proven to lose...\n\n\t\t\t\tif (typeof info_list[i].u === \"number\" && info_list[i].u === 0 && info_list[i].value() === 0) {\n\t\t\t\t\tif (config.arrow_filter_type !== \"all\") {\n\t\t\t\t\t\tok = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// If the show_move would be filtered out, note that fact...\n\n\t\t\t\tif (!ok && info_list[i].move === show_move) {\n\t\t\t\t\tshow_move_was_forced = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Filter for ab mode...\n\t\t\t// Note that we don't set show_move_was_forced for ab mode.\n\t\t\t// If it wasn't already set, then we have good info for this move.\n\n\t\t\tif (mode === \"ab\") {\n\t\t\t\tif (loss >= config.ab_filter_threshold) {\n\t\t\t\t\tok = false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Go ahead, if the various tests don't filter the move out...\n\n\t\t\tif (ok || i === 0 || info_list[i].move === show_move) {\n\n\t\t\t\tlet [x1, y1] = XY(info_list[i].move.slice(0, 2));\n\t\t\t\tlet [x2, y2] = XY(info_list[i].move.slice(2, 4));\n\n\t\t\t\tlet colour;\n\n\t\t\t\tif (info_list[i].move === show_move && config.next_move_unique_colour) {\n\t\t\t\t\tcolour = config.actual_move_colour;\n\t\t\t\t} else if (info_list[i].move === show_move && show_move_was_forced) {\n\t\t\t\t\tcolour = config.terrible_colour;\n\t\t\t\t} else if (info_list[i].__touched === false) {\n\t\t\t\t\tcolour = config.terrible_colour;\n\t\t\t\t} else if (info_list[i] === best_info) {\n\t\t\t\t\tcolour = config.best_colour;\n\t\t\t\t} else if (loss < config.bad_move_threshold) {\n\t\t\t\t\tcolour = config.good_colour;\n\t\t\t\t} else if (loss < config.terrible_move_threshold) {\n\t\t\t\t\tcolour = config.bad_colour;\n\t\t\t\t} else {\n\t\t\t\t\tcolour = config.terrible_colour;\n\t\t\t\t}\n\n\t\t\t\tlet x_head_adjustment = 0;\t\t\t\t// Adjust head of arrow for castling moves...\n\t\t\t\tlet normal_castling_flag = false;\n\n\t\t\t\tif (node.board && node.board.colour(Point(x1, y1)) === node.board.colour(Point(x2, y2))) {\n\n\t\t\t\t\t// So the move is a castling move (reminder: as of 1.1.6 castling format is king-onto-rook).\n\n\t\t\t\t\tif (node.board.normalchess) {\n\t\t\t\t\t\tnormal_castling_flag = true;\t// ...and we are playing normal Chess (not 960).\n\t\t\t\t\t}\n\n\t\t\t\t\tif (x2 > x1) {\n\t\t\t\t\t\tx_head_adjustment = normal_castling_flag ? -1 : -0.5;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tx_head_adjustment = normal_castling_flag ? 2 : 0.5;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tarrows.push({\n\t\t\t\t\tcolour: colour,\n\t\t\t\t\tx1: x1,\n\t\t\t\t\ty1: y1,\n\t\t\t\t\tx2: x2 + x_head_adjustment,\n\t\t\t\t\ty2: y2,\n\t\t\t\t\tinfo: info_list[i]\n\t\t\t\t});\n\n\t\t\t\t// If there is no one_click_move set for the target square, then set it\n\t\t\t\t// and also set an arrowhead to be drawn later.\n\n\t\t\t\tif (normal_castling_flag) {\n\t\t\t\t\tif (!this.one_click_moves[x2 + x_head_adjustment][y2]) {\n\t\t\t\t\t\theads.push({\n\t\t\t\t\t\t\tcolour: colour,\n\t\t\t\t\t\t\tx2: x2 + x_head_adjustment,\n\t\t\t\t\t\t\ty2: y2,\n\t\t\t\t\t\t\tinfo: info_list[i]\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis.one_click_moves[x2 + x_head_adjustment][y2] = info_list[i].move;\n\t\t\t\t\t\tif (info_list[i].move === show_move) {\n\t\t\t\t\t\t\tshow_move_head = heads[heads.length - 1];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (!this.one_click_moves[x2][y2]) {\n\t\t\t\t\t\theads.push({\n\t\t\t\t\t\t\tcolour: colour,\n\t\t\t\t\t\t\tx2: x2 + x_head_adjustment,\n\t\t\t\t\t\t\ty2: y2,\n\t\t\t\t\t\t\tinfo: info_list[i]\n\t\t\t\t\t\t});\n\t\t\t\t\t\tthis.one_click_moves[x2][y2] = info_list[i].move;\n\t\t\t\t\t\tif (info_list[i].move === show_move) {\n\t\t\t\t\t\t\tshow_move_head = heads[heads.length - 1];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// It looks best if the longest arrows are drawn underneath. Manhattan distance is good enough.\n\t\t// For the sake of displaying the best pawn promotion (of the 4 possible), sort ties are broken\n\t\t// by node counts, with lower drawn first. [Eh, what about Stockfish? Meh, it doesn't affect\n\t\t// the heads, merely the colour of the lines, so it's not a huge problem I think.]\n\n\t\tarrows.sort((a, b) => {\n\t\t\tif (Math.abs(a.x2 - a.x1) + Math.abs(a.y2 - a.y1) < Math.abs(b.x2 - b.x1) + Math.abs(b.y2 - b.y1)) {\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tif (Math.abs(a.x2 - a.x1) + Math.abs(a.y2 - a.y1) > Math.abs(b.x2 - b.x1) + Math.abs(b.y2 - b.y1)) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tif (a.info.n < b.info.n) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tif (a.info.n > b.info.n) {\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\treturn 0;\n\t\t});\n\n\t\tboardctx.lineWidth = config.arrow_width;\n\t\tboardctx.textAlign = \"center\";\n\t\tboardctx.textBaseline = \"middle\";\n\t\tboardctx.font = config.board_font;\n\n\t\tfor (let o of arrows) {\n\n\t\t\tlet cc1 = CanvasCoords(o.x1, o.y1);\n\t\t\tlet cc2 = CanvasCoords(o.x2, o.y2);\n\n\t\t\tif (o.info.move === show_move && config.next_move_outline) {\t\t// Draw the outline at the layer just below the actual arrow.\n\t\t\t\tboardctx.strokeStyle = \"black\";\n\t\t\t\tboardctx.fillStyle = \"black\";\n\t\t\t\tboardctx.lineWidth = config.arrow_width + 4;\n\t\t\t\tboardctx.beginPath();\n\t\t\t\tboardctx.moveTo(cc1.cx, cc1.cy);\n\t\t\t\tboardctx.lineTo(cc2.cx, cc2.cy);\n\t\t\t\tboardctx.stroke();\n\t\t\t\tboardctx.lineWidth = config.arrow_width;\n\n\t\t\t\tif (show_move_head) {\t\t\t// This is the best layer to draw the head outline.\n\t\t\t\t\tboardctx.beginPath();\n\t\t\t\t\tboardctx.arc(cc2.cx, cc2.cy, config.arrowhead_radius + 2, 0, 2 * Math.PI);\n\t\t\t\t\tboardctx.fill();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tboardctx.strokeStyle = o.colour;\n\t\t\tboardctx.fillStyle = o.colour;\n\t\t\tboardctx.beginPath();\n\t\t\tboardctx.moveTo(cc1.cx, cc1.cy);\n\t\t\tboardctx.lineTo(cc2.cx, cc2.cy);\n\t\t\tboardctx.stroke();\n\t\t}\n\n\t\tfor (let o of heads) {\n\n\t\t\tlet cc2 = CanvasCoords(o.x2, o.y2);\n\n\t\t\tboardctx.fillStyle = o.colour;\n\t\t\tboardctx.beginPath();\n\t\t\tboardctx.arc(cc2.cx, cc2.cy, config.arrowhead_radius, 0, 2 * Math.PI);\n\t\t\tboardctx.fill();\n\t\t\tboardctx.fillStyle = \"black\";\n\n\t\t\tlet s = \"?\";\n\n\t\t\tswitch (config.arrowhead_type) {\n\t\t\tcase 0:\n\t\t\t\ts = o.info.value_string(0, config.ev_pov);\n\t\t\t\tif (s === \"100\" && o.info.q < 1.0) {\n\t\t\t\t\ts = \"99\";\t\t\t\t\t\t\t\t// Don't round up to 100.\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 1:\n\t\t\t\tif (node.table.nodes > 0) {\n\t\t\t\t\ts = (100 * o.info.n / node.table.nodes).toFixed(0);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\tif (o.info.p > 0) {\n\t\t\t\t\ts = o.info.p.toFixed(0);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 3:\n\t\t\t\ts = o.info.multipv;\n\t\t\t\tbreak;\n\t\t\tcase 4:\n\t\t\t\tif (typeof o.info.m === \"number\") {\n\t\t\t\t\ts = o.info.m.toFixed(0);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\ts = \"!\";\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (o.info.__touched === false) {\n\t\t\t\ts = \"?\";\n\t\t\t}\n\n\t\t\tif (show_move_was_forced && o.info.move === show_move) {\n\t\t\t\ts = \"?\";\n\t\t\t}\n\n\t\t\tboardctx.fillText(s, cc2.cx, cc2.cy + 1);\n\t\t}\n\n\t\tdraw_arrows_last_mode = mode;\t\t// For debugging only.\n\t},\n\n\t// ----------------------------------------------------------------------------------------------------------\n\t// We have a special function for the book explorer mode. Explorer mode is very nicely isolated from the rest\n\t// of the app. The info_list here is just a list of objects each containing only \"move\" and \"weight\" - where\n\t// the weights have been normalised to the 0-1 scale and the list has been sorted.\n\t//\n\t// Note that info_list here MUST NOT BE MODIFIED.\n\n\tdraw_explorer_arrows: function(node, info_list, specific_source) {\t\t// If not nullish, specific_source is a Point()\n\n\t\tfor (let x = 0; x < 8; x++) {\n\t\t\tfor (let y = 0; y < 8; y++) {\n\t\t\t\tthis.one_click_moves[x][y] = null;\n\t\t\t}\n\t\t}\n\n\t\tif (!node || node.destroyed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet arrows = [];\n\t\tlet heads = [];\n\n\t\tfor (let i = 0; i < info_list.length; i++) {\n\n\t\t\tif (specific_source && specific_source.s !== info_list[i].move.slice(0, 2)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet [x1, y1] = XY(info_list[i].move.slice(0, 2));\n\t\t\tlet [x2, y2] = XY(info_list[i].move.slice(2, 4));\n\n\t\t\tlet colour = i === 0 ? config.best_colour : config.good_colour;\n\n\t\t\tlet x_head_adjustment = 0;\t\t\t\t// Adjust head of arrow for castling moves...\n\t\t\tlet normal_castling_flag = false;\n\n\t\t\tif (node.board && node.board.colour(Point(x1, y1)) === node.board.colour(Point(x2, y2))) {\n\n\t\t\t\tif (node.board.normalchess) {\n\t\t\t\t\tnormal_castling_flag = true;\t// ...and we are playing normal Chess (not 960).\n\t\t\t\t}\n\n\t\t\t\tif (x2 > x1) {\n\t\t\t\t\tx_head_adjustment = normal_castling_flag ? -1 : -0.5;\n\t\t\t\t} else {\n\t\t\t\t\tx_head_adjustment = normal_castling_flag ? 2 : 0.5;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tarrows.push({\n\t\t\t\tcolour: colour,\n\t\t\t\tx1: x1,\n\t\t\t\ty1: y1,\n\t\t\t\tx2: x2 + x_head_adjustment,\n\t\t\t\ty2: y2,\n\t\t\t\tinfo: info_list[i]\n\t\t\t});\n\n\t\t\t// If there is no one_click_move set for the target square, then set it\n\t\t\t// and also set an arrowhead to be drawn later.\n\n\t\t\tif (normal_castling_flag) {\n\t\t\t\tif (!this.one_click_moves[x2 + x_head_adjustment][y2]) {\n\t\t\t\t\theads.push({\n\t\t\t\t\t\tcolour: colour,\n\t\t\t\t\t\tx2: x2 + x_head_adjustment,\n\t\t\t\t\t\ty2: y2,\n\t\t\t\t\t\tinfo: info_list[i]\n\t\t\t\t\t});\n\t\t\t\t\tthis.one_click_moves[x2 + x_head_adjustment][y2] = info_list[i].move;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (!this.one_click_moves[x2][y2]) {\n\t\t\t\t\theads.push({\n\t\t\t\t\t\tcolour: colour,\n\t\t\t\t\t\tx2: x2 + x_head_adjustment,\n\t\t\t\t\t\ty2: y2,\n\t\t\t\t\t\tinfo: info_list[i]\n\t\t\t\t\t});\n\t\t\t\t\tthis.one_click_moves[x2][y2] = info_list[i].move;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tarrows.sort((a, b) => {\n\t\t\tif (Math.abs(a.x2 - a.x1) + Math.abs(a.y2 - a.y1) < Math.abs(b.x2 - b.x1) + Math.abs(b.y2 - b.y1)) {\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tif (Math.abs(a.x2 - a.x1) + Math.abs(a.y2 - a.y1) > Math.abs(b.x2 - b.x1) + Math.abs(b.y2 - b.y1)) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\treturn 0;\n\t\t});\n\n\t\tboardctx.lineWidth = config.arrow_width;\n\t\tboardctx.textAlign = \"center\";\n\t\tboardctx.textBaseline = \"middle\";\n\t\tboardctx.font = config.board_font;\n\n\t\tfor (let o of arrows) {\n\n\t\t\tlet cc1 = CanvasCoords(o.x1, o.y1);\n\t\t\tlet cc2 = CanvasCoords(o.x2, o.y2);\n\n\t\t\tboardctx.strokeStyle = o.colour;\n\t\t\tboardctx.fillStyle = o.colour;\n\t\t\tboardctx.beginPath();\n\t\t\tboardctx.moveTo(cc1.cx, cc1.cy);\n\t\t\tboardctx.lineTo(cc2.cx, cc2.cy);\n\t\t\tboardctx.stroke();\n\t\t}\n\n\t\tfor (let o of heads) {\n\n\t\t\tlet cc2 = CanvasCoords(o.x2, o.y2);\n\n\t\t\tboardctx.fillStyle = o.colour;\n\t\t\tboardctx.beginPath();\n\t\t\tboardctx.arc(cc2.cx, cc2.cy, config.arrowhead_radius, 0, 2 * Math.PI);\n\t\t\tboardctx.fill();\n\t\t\tboardctx.fillStyle = \"black\";\n\n\t\t\tlet s = \"?\";\n\n\t\t\tif (typeof o.info.weight === \"number\") {\n\t\t\t\ts = (100 * o.info.weight).toFixed(0);\n\t\t\t}\n\n\t\t\tboardctx.fillText(s, cc2.cx, cc2.cy + 1);\n\t\t}\n\t}\n};\n\n\n\n// For debugging...\nlet draw_arrows_last_mode = null;\n"
  },
  {
    "path": "files/src/renderer/82_infobox.js",
    "content": "\"use strict\";\n\nlet infobox_props = {\n\n\tdraw_infobox: function(node, mouse_point, active_square, active_colour, hoverdraw_div, allow_inactive_focus, lookup_object) {\n\n\t\tlet searchmoves = node.searchmoves;\n\n\t\tif (this.displaying_error_log()) {\n\t\t\tinfobox.innerHTML = this.error_log;\n\t\t\tthis.last_drawn_version = null;\n\t\t\treturn;\n\t\t}\n\n\t\tif (!node || node.destroyed) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet info_list;\n\n\t\tif (node.terminal_reason()) {\n\t\t\tinfo_list = [];\n\t\t} else {\n\t\t\tinfo_list = SortedMoveInfo(node);\n\t\t}\n\n\t\t// A lookup_object should always have type (string) and moves (object).\n\n\t\tlet ltype        = lookup_object ? lookup_object.type  : null;\n\t\tlet lookup_moves = lookup_object ? lookup_object.moves : null;\n\n\t\t// If we are using an online API, and the list has some \"untouched\" info, we\n\t\t// may be able to sort them using the API info.\n\n\t\tif (ltype === \"chessdbcn\" || ltype === \"lichess_masters\" || ltype === \"lichess_plebs\") {\n\n\t\t\tlet touched_list = [];\n\t\t\tlet untouched_list = [];\n\n\t\t\tfor (let info of info_list) {\n\t\t\t\tif (info.__touched) {\n\t\t\t\t\ttouched_list.push(info);\n\t\t\t\t} else {\n\t\t\t\t\tuntouched_list.push(info);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst a_is_best = -1;\n\t\t\tconst b_is_best = 1;\n\n\t\t\tuntouched_list.sort((a, b) => {\n\t\t\t\tif (lookup_moves[a.move] && !lookup_moves[b.move]) return a_is_best;\n\t\t\t\tif (!lookup_moves[a.move] && lookup_moves[b.move]) return b_is_best;\n\t\t\t\tif (!lookup_moves[a.move] && !lookup_moves[b.move]) return 0;\n\t\t\t\treturn lookup_moves[b.move].sort_score() - lookup_moves[a.move].sort_score();\n\t\t\t});\n\n\t\t\tinfo_list = touched_list.concat(untouched_list);\n\t\t}\n\n\t\tlet best_subcycle = info_list.length > 0 ? info_list[0].subcycle : 0;\n\t\tif (best_subcycle === 0) {\t\t// Because all info was autopopulated\n\t\t\tbest_subcycle = -1;\t\t\t// Causes all info to be gray\n\t\t}\n\n\t\tif (typeof config.max_info_lines === \"number\" && config.max_info_lines > 0) {\t\t// Hidden option, request of rwbc\n\t\t\tinfo_list = info_list.slice(0, config.max_info_lines);\n\t\t}\n\n\t\t// We might be highlighting some div...\n\n\t\tlet highlight_move = null;\n\t\tlet highlight_class = null;\n\n\t\t// We'll highlight it if it's a valid OCM *and* clicking there now would make it happen...\n\n\t\tif (mouse_point && this.one_click_moves[mouse_point.x][mouse_point.y]) {\n\t\t\tif (!active_square || this.one_click_moves[mouse_point.x][mouse_point.y].slice(0, 2) === active_square.s) {\n\t\t\t\thighlight_move = this.one_click_moves[mouse_point.x][mouse_point.y];\n\t\t\t\thighlight_class = \"ocm_highlight\";\n\t\t\t}\n\t\t}\n\n\t\tif (typeof hoverdraw_div === \"number\" && hoverdraw_div >= 0 && hoverdraw_div < info_list.length) {\n\t\t\thighlight_move = info_list[hoverdraw_div].move;\n\t\t\thighlight_class = \"hover_highlight\";\n\t\t}\n\n\t\t// We cannot skip the draw if...\n\n\t\tlet no_skip_reasons = [];\n\n\t\tif (node.id !== this.last_drawn_node_id)                                no_skip_reasons.push(\"node\");\n\t\tif (node.table.version !== this.last_drawn_version)                     no_skip_reasons.push(\"table version\");\n\t\tif (highlight_move !== this.last_drawn_highlight_move)                  no_skip_reasons.push(\"highlight move\");\n\t\tif (highlight_class !== this.last_drawn_highlight_class)                no_skip_reasons.push(\"highlight class\");\n\t\tif (info_list.length !== this.last_drawn_length)                        no_skip_reasons.push(\"info list length\");\n\t\tif (allow_inactive_focus !== this.last_drawn_allow_inactive_focus)      no_skip_reasons.push(\"allow inactive focus\");\n\t\tif (CompareArrays(searchmoves, this.last_drawn_searchmoves) === false)  no_skip_reasons.push(\"searchmoves\");\n\t\tif (lookup_object !== this.last_drawn_lookup_object)                    no_skip_reasons.push(\"lookup object\");\n\n\t\tdraw_infobox_no_skip_reasons = no_skip_reasons.join(\", \");\t// For debugging only.\n\n\t\tif (no_skip_reasons.length === 0) {\n\t\t\tdraw_infobox_total_skips++;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.last_drawn_node_id = node.id;\n\t\tthis.last_drawn_version = node.table.version;\n\t\tthis.last_drawn_highlight_move = highlight_move;\n\t\tthis.last_drawn_highlight_class = highlight_class;\n\t\tthis.last_drawn_length = info_list.length;\n\t\tthis.last_drawn_allow_inactive_focus = allow_inactive_focus;\n\t\tthis.last_drawn_searchmoves = Array.from(searchmoves);\n\t\tthis.last_drawn_lookup_object = lookup_object;\n\n\t\tthis.info_clickers = [];\n\t\tthis.info_clickers_node_id = node.id;\n\n\t\tlet substrings = [];\n\t\tlet clicker_index = 0;\n\t\tlet div_index = 0;\n\n\t\tfor (let info of info_list) {\n\n\t\t\t// The div containing the PV etc...\n\n\t\t\tlet divclass = \"infoline\";\n\n\t\t\tif (info.subcycle !== best_subcycle && !config.never_grayout_infolines) {\n\t\t\t\tdivclass += \" \" + \"gray\";\n\t\t\t}\n\n\t\t\tif (info.move === highlight_move) {\n\t\t\t\tdivclass += \" \" + highlight_class;\n\t\t\t}\n\n\t\t\tsubstrings.push(`<div id=\"infoline_${div_index++}\" class=\"${divclass}\">`);\n\n\t\t\t// The \"focus\" button...\n\n\t\t\tif (config.searchmoves_buttons) {\n\t\t\t\tif (searchmoves.includes(info.move)) {\n\t\t\t\t\tsubstrings.push(`<span id=\"searchmove_${info.move}\" class=\"yellow\">${config.focus_on_text} </span>`);\n\t\t\t\t} else {\n\t\t\t\t\tif (allow_inactive_focus) {\n\t\t\t\t\t\tsubstrings.push(`<span id=\"searchmove_${info.move}\" class=\"gray\">${config.focus_off_text} </span>`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// The value...\n\n\t\t\tlet value_string = \"?\";\n\t\t\tif (config.show_cp) {\n\t\t\t\tif (typeof info.mate === \"number\" && info.mate !== 0) {\n\t\t\t\t\tvalue_string = info.mate_string(config.cp_pov);\n\t\t\t\t} else {\n\t\t\t\t\tvalue_string = info.cp_string(config.cp_pov);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tvalue_string = info.value_string(1, config.ev_pov);\n\t\t\t\tif (value_string !== \"?\") {\n\t\t\t\t\tvalue_string += \"%\";\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (info.subcycle === best_subcycle || config.never_grayout_infolines) {\n\t\t\t\tsubstrings.push(`<span class=\"blue\">${value_string} </span>`);\n\t\t\t} else {\n\t\t\t\tsubstrings.push(`${value_string} `);\n\t\t\t}\n\n\t\t\t// The PV...\n\n\t\t\tlet colour = active_colour;\n\t\t\tlet movenum = node.board.fullmove;\t\t\t// Only matters for config.infobox_pv_move_numbers\n\t\t\tlet nice_pv = info.nice_pv();\n\n\t\t\tfor (let i = 0; i < nice_pv.length; i++) {\n\t\t\t\tlet spanclass = \"\";\n\t\t\t\tif (info.subcycle === best_subcycle || config.never_grayout_infolines) {\n\t\t\t\t\tspanclass = colour === \"w\" ? \"white\" : \"pink\";\n\t\t\t\t}\n\t\t\t\tif (nice_pv[i].includes(\"O-O\")) {\n\t\t\t\t\tspanclass += (spanclass.length > 0) ? \" nobr\" : \"nobr\";\n\t\t\t\t}\n\n\t\t\t\tlet numstring = \"\";\n\t\t\t\tif (config.infobox_pv_move_numbers) {\n\t\t\t\t\tif (colour === \"w\") {\n\t\t\t\t\t\tnumstring = `${movenum}. `;\n\t\t\t\t\t} else if (colour === \"b\" && i === 0) {\n\t\t\t\t\t\tnumstring = `${movenum}... `;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tsubstrings.push(`<span id=\"infobox_${clicker_index++}\" class=\"${spanclass}\">${numstring}${nice_pv[i]} </span>`);\n\t\t\t\tthis.info_clickers.push({\n\t\t\t\t\tmove: info.pv[i],\n\t\t\t\t\tis_start: i === 0,\n\t\t\t\t\tis_end: i === nice_pv.length - 1,\n\t\t\t\t});\n\t\t\t\tcolour = OppositeColour(colour);\n\t\t\t\tif (colour === \"w\") {\n\t\t\t\t\tmovenum++;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// The extra stats...\n\n\t\t\tlet extra_stat_strings = [];\n\n\t\t\tif (info.__touched) {\n\n\t\t\t\tlet stats_list = info.stats_list(\n\t\t\t\t\t{\n\t\t\t\t\t\tn:             config.show_n,\n\t\t\t\t\t\tn_abs:         config.show_n_abs,\n\t\t\t\t\t\tdepth:         config.show_depth,\n\t\t\t\t\t\twdl:           config.show_wdl,\n\t\t\t\t\t\twdl_pov:       config.wdl_pov,\n\t\t\t\t\t\tp:             config.show_p,\n\t\t\t\t\t\tm:             config.show_m,\n\t\t\t\t\t\tv:             config.show_v,\n\t\t\t\t\t\tq:             config.show_q,\n\t\t\t\t\t\tu:             config.show_u,\n\t\t\t\t\t\ts:             config.show_s,\n\t\t\t\t\t}, node.table.nodes);\n\n\t\t\t\textra_stat_strings = extra_stat_strings.concat(stats_list);\n\t\t\t}\n\n\t\t\tif (config.looker_api) {\n\t\t\t\tlet api_string = \"API: ?\";\n\t\t\t\tif (ltype && lookup_moves) {\n\t\t\t\t\tlet pov = null;\n\t\t\t\t\tif (ltype === \"chessdbcn\") {\n\t\t\t\t\t\tpov = config.cp_pov;\n\t\t\t\t\t} else if (ltype === \"lichess_masters\" || ltype === \"lichess_plebs\") {\n\t\t\t\t\t\tpov = config.ev_pov;\n\t\t\t\t\t}\n\t\t\t\t\tlet o = lookup_moves[info.move];\n\t\t\t\t\tif (typeof o === \"object\" && o !== null) {\n\t\t\t\t\t\tapi_string = o.text(pov);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\textra_stat_strings.push(api_string);\n\t\t\t}\n\n\t\t\tif (extra_stat_strings.length > 0) {\n\t\t\t\tif (config.infobox_stats_newline) {\n\t\t\t\t\tsubstrings.push(\"<br>\");\n\t\t\t\t}\n\t\t\t\tsubstrings.push(`<span class=\"gray\">(${extra_stat_strings.join(', ')})</span>`);\n\t\t\t}\n\n\t\t\t// Close the whole div...\n\n\t\t\tsubstrings.push(\"</div>\");\n\n\t\t}\n\n\t\tinfobox.innerHTML = substrings.join(\"\");\n\t},\n\n\tmust_draw_infobox: function() {\n\t\tthis.last_drawn_version = null;\n\t},\n\n\tclickers_are_valid_for_node: function(node) {\n\t\tif (!node || !this.info_clickers_node_id) {\n\t\t\treturn false;\n\t\t}\n\t\treturn node.id === this.info_clickers_node_id;\n\t},\n\n\tmoves_from_click_n: function(n, desired_length = null) {\n\n\t\tif (typeof n !== \"number\" || Number.isNaN(n)) {\n\t\t\treturn [];\n\t\t}\n\n\t\tif (!this.info_clickers || n < 0 || n >= this.info_clickers.length) {\n\t\t\treturn [];\n\t\t}\n\n\t\tlet move_list = [];\n\n\t\t// Work backwards until we get to the start of the line...\n\n\t\tfor (let i = n; i >= 0; i--) {\n\t\t\tlet object = this.info_clickers[i];\n\t\t\tmove_list.push(object.move);\n\t\t\tif (object.is_start) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tmove_list.reverse();\n\n\t\t// If a PV length is specified, either truncate or extend as needed...\n\n\t\tif (typeof desired_length === \"number\") {\n\t\t\tif (move_list.length > desired_length) {\n\t\t\t\tmove_list = move_list.slice(0, desired_length);\n\t\t\t} else if (move_list.length < desired_length) {\n\t\t\t\tfor (let i = n + 1; i < this.info_clickers.length; i++) {\n\t\t\t\t\tlet object = this.info_clickers[i];\n\t\t\t\t\tif (object.is_start) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tmove_list.push(object.move);\t\t\t\t\t// Note the different order of stataments compared to the above.\n\t\t\t\t\tif (move_list.length >= desired_length) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn move_list;\n\t},\n\n};\n\n\n\n// For debugging...\nlet draw_infobox_total_skips = 0;\nlet draw_infobox_no_skip_reasons = \"\";\n"
  },
  {
    "path": "files/src/renderer/83_statusbox.js",
    "content": "\"use strict\";\n\nfunction NewStatusHandler() {\n\n\tlet sh = Object.create(null);\n\n\tsh.special_message = null;\n\tsh.special_message_class = \"yellow\";\n\tsh.special_message_timeout = performance.now();\n\n\tsh.set_special_message = function(s, css_class, duration) {\n\t\tif (!css_class) css_class = \"yellow\";\n\t\tif (!duration) duration = 3000;\n\t\tthis.special_message = s;\n\t\tthis.special_message_class = css_class;\n\t\tthis.special_message_timeout = performance.now() + duration;\n\t};\n\n\tsh.draw_statusbox = function(node, engine, analysing_other, loading_message, book_is_loaded) {\n\n\t\tif (loading_message) {\n\n\t\t\tstatusbox.innerHTML = `<span class=\"yellow\">${loading_message}</span> <span class=\"red\" id=\"loadabort_clicker\">(abort?)</span>`;\n\n\t\t} else if (config.show_engine_state) {\n\n\t\t\tlet cl;\n\t\t\tlet status;\n\n\t\t\tif (engine.search_running.node && engine.search_running === engine.search_desired) {\n\t\t\t\tcl = \"green\";\n\t\t\t\tstatus = \"running\";\n\t\t\t} else if (engine.search_running !== engine.search_desired) {\n\t\t\t\tcl = \"yellow\";\n\t\t\t\tstatus = \"desync\";\n\t\t\t} else {\n\t\t\t\tcl = \"yellow\";\n\t\t\t\tstatus = \"stopped\";\n\t\t\t}\n\n\t\t\tstatusbox.innerHTML =\n\t\t\t`<span class=\"${cl}\">${status}</span>, ` +\n\t\t\t`${config.behaviour}, ` +\n\t\t\t`${engine.last_send}`;\n\n\t\t} else if (!engine.ever_received_uciok) {\n\n\t\t\tstatusbox.innerHTML = `<span class=\"yellow\">Awaiting uciok from engine</span>`;\n\n\t\t} else if (!engine.ever_received_readyok) {\n\n\t\t\tstatusbox.innerHTML = `<span class=\"yellow\">Awaiting readyok from engine</span>`;\n\n\t\t} else if (this.special_message && performance.now() < this.special_message_timeout) {\n\n\t\t\tstatusbox.innerHTML = `<span class=\"${this.special_message_class}\">${this.special_message}</span>`;\n\n\t\t} else if (engine.unresolved_stop_time && performance.now() - engine.unresolved_stop_time > 500) {\n\n\t\t\tstatusbox.innerHTML = `<span class=\"yellow\">${messages.desync}</span>`;\n\n\t\t} else if (analysing_other) {\n\n\t\t\tstatusbox.innerHTML = `<span id=\"lock_return_clicker\" class=\"blue\">Locked to ${analysing_other} (return?)</span>`;\n\n\t\t} else if (node.terminal_reason()) {\n\n\t\t\tstatusbox.innerHTML = `<span class=\"yellow\">${node.terminal_reason()}</span>`;\n\n\t\t} else if (!node || node.destroyed) {\n\n\t\t\tstatusbox.innerHTML = `<span class=\"red\">draw_statusbox - !node || node.destroyed</span>`;\n\n\t\t} else {\n\n\t\t\tlet status_string = \"\";\n\n\t\t\tif (config.behaviour === \"halt\" && !engine.search_running.node) {\n\t\t\t\tstatus_string += `<span id=\"gobutton_clicker\" class=\"yellow\">HALTED (go?) </span>`;\n\t\t\t} else if (config.behaviour === \"halt\" && engine.search_running.node) {\n\t\t\t\tstatus_string += `<span class=\"yellow\">HALTING... </span>`;\n\t\t\t} else if (config.behaviour === \"analysis_locked\") {\n\t\t\t\tstatus_string += `<span class=\"blue\">Locked! </span>`;\n\t\t\t} else if (config.behaviour === \"play_white\" && node.board.active !== \"w\") {\n\t\t\t\tstatus_string += `<span class=\"yellow\">YOUR MOVE </span>`;\n\t\t\t} else if (config.behaviour === \"play_black\" && node.board.active !== \"b\") {\n\t\t\t\tstatus_string += `<span class=\"yellow\">YOUR MOVE </span>`;\n\t\t\t} else if (config.behaviour === \"self_play\") {\n\t\t\t\tstatus_string += `<span class=\"green\">Self-play! </span>`;\n\t\t\t} else if (config.behaviour === \"auto_analysis\") {\n\t\t\t\tstatus_string += `<span class=\"green\">Auto-eval! </span>`;\n\t\t\t} else if (config.behaviour === \"back_analysis\") {\n\t\t\t\tstatus_string += `<span class=\"green\">Back-eval! </span>`;\n\t\t\t} else if (config.behaviour === \"analysis_free\") {\n\t\t\t\tif (hub.engine.sent_options.contempt !== undefined && hub.engine.sent_options.contempt !== \"0\") {\n\t\t\t\t\tstatus_string += `<span id=\"haltbutton_clicker\" class=\"green\">Contempt active! </span>`;\n\t\t\t\t} else {\n\t\t\t\t\tstatus_string += `<span id=\"haltbutton_clicker\" class=\"green\">ANALYSIS (halt?) </span>`;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (config.book_explorer) {\n\n\t\t\t\tlet warn = book_is_loaded ? \"\" : \" (No book loaded)\";\n\t\t\t\tstatus_string += `<span class=\"blue\">Book frequency arrows only!${warn}</span>`;\n\n\t\t\t} else if (config.lichess_explorer) {\n\n\t\t\t\tlet warn = (config.looker_api === \"lichess_masters\" || config.looker_api === \"lichess_plebs\") ? \"\" : \" (API not selected)\";\n\t\t\t\tstatus_string += `<span class=\"blue\">Lichess frequency arrows only!${warn}</span>`;\n\n\t\t\t} else {\n\n\t\t\t\tstatus_string += `<span class=\"gray\">${NString(node.table.nodes)} ${node.table.nodes === 1 ? \"node\" : \"nodes\"}`;\n\t\t\t\tstatus_string += `, ${DurationString(node.table.time)} (N/s: ${NString(node.table.nps)})`;\n\t\t\t\tif (engineconfig[engine.filepath].options[\"SyzygyPath\"] || node.table.tbhits > 0) {\n\t\t\t\t\tstatus_string += `, ${NString(node.table.tbhits)} ${node.table.tbhits === 1 ? \"tbhit\" : \"tbhits\"}`;\n\t\t\t\t}\n\t\t\t\tstatus_string += `</span>`;\n\n\t\t\t\tif (!engine.search_running.node && engine.search_completed.node === node) {\n\n\t\t\t\t\tlet stoppedtext = \"\";\n\n\t\t\t\t\tif (config.behaviour !== \"halt\") {\n\t\t\t\t\t\tstoppedtext = ` <span class=\"blue\">(stopped)</span>`;\n\t\t\t\t\t}\n/*\n\t\t\t\t\t// The following doesn't make sense if a time limit rather than a move limit is in force.\n\n\t\t\t\t\tif (typeof engineconfig[engine.filepath].search_nodes === \"number\" && engineconfig[engine.filepath].search_nodes > 0) {\n\t\t\t\t\t\tif (node.table.nodes >= engineconfig[engine.filepath].search_nodes) {\n\t\t\t\t\t\t\tstoppedtext = ` <span class=\"blue\">(limit met)</span>`;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n*/\n\t\t\t\t\tstatus_string += stoppedtext;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tstatusbox.innerHTML = status_string;\n\t\t}\n\t};\n\n\treturn sh;\n}\n"
  },
  {
    "path": "files/src/renderer/90_engine.js",
    "content": "\"use strict\";\n\n/*\n\nWe are in one of these states (currently implicit in the logic):\n\t1. Inactive\n\t2. Running a search\n\t3. Changing the search\n\t4. Ending the search\n\n(1) Inactive................................................................................\n\n\tA \"bestmove\" should not arrive. If the user wants to start a search, we send it and\n\tenter state 2.\n\n(2) Running a search........................................................................\n\n\tA \"bestmove\" might arrive, in which case the search ends and we go into state 1. The\n\t\"bestmove\" line must be passed to hub.receive_bestmove().\n\n\tAlternatively, the user may demand a search with new parameters, in which case we send\n\t\"stop\" and enter state 3. Or the user may halt, in which case we send \"stop\" and enter\n\tstate 4.\n\n(3) Changing the search.....................................................................\n\n\tA \"stop\" has been sent and we are waiting for a \"bestmove\" response. When it arrives,\n\twe can send the new search and go back to state 2. The \"bestmove\" line itself can be\n\tdiscarded since it is not relevant to the desired search.\n\n\tIn state 3, if the user changes the desired search, we simply replace the old desired\n\tsearch (which never started) with the new desired search (which may be the null search,\n\tin which case we have entered state 4).\n\n(4) Ending the search.......................................................................\n\n\tJust like state 3, except the desired search is the null search. When a \"bestmove\"\n\tarrives, we go to state 1.\n\n*/\n\nconst GUI_WANTS_TO_KNOW = [\"Backend\", \"EvalFile\", \"WeightsFile\", \"SyzygyPath\", \"Threads\", \"Hash\", \"MultiPV\",\n\t\"ContemptMode\", \"Contempt\", \"WDLCalibrationElo\", \"WDLEvalObjectivity\", \"ScoreType\", \"Temperature\", \"TempDecayMoves\"];\n\nlet NoSearch = Object.freeze({\n\tnode: null,\n\tlimit: null,\n\tlimit_by_time: false,\n\tsearchmoves: Object.freeze([])\n});\n\nfunction SearchParams(node = null, limit = null, limit_by_time = false, searchmoves = null) {\n\n\tif (!node) return NoSearch;\n\n\tlet validated;\n\n\tif (Array.isArray(searchmoves)) {\n\t\tvalidated = node.validate_searchmoves(searchmoves);\t\t// returns a new array\n\t} else {\n\t\tvalidated = [];\n\t}\n\n\tObject.freeze(validated);\t\t\t// under no circumstances refactor this to freeze the original searchmoves\n\n\treturn Object.freeze({\n\t\tnode: node,\n\t\tlimit: limit,\n\t\tlimit_by_time: limit_by_time,\n\t\tsearchmoves: validated\n\t});\n}\n\nfunction NewEngine(hub) {\n\n\tlet eng = Object.create(null);\n\n\teng.hub = hub;\n\teng.exe = null;\n\teng.scanner = null;\n\teng.err_scanner = null;\n\n\teng.filepath = \"\";\t\t\t\t\t// Used to decide what entry in engineconfig to use. Start as \"\", which has defaults for the dummy engine.\n\n\teng.last_send = null;\n\teng.unresolved_stop_time = null;\n\teng.ever_received_uciok = false;\n\teng.ever_received_readyok = false;\n\teng.have_quit = false;\n\teng.suppress_cycle_info = null;\t\t// Stupid hack to allow \"forget all analysis\" to work; info lines from this cycle are ignored.\n\n\teng.known_options = Object.create(null);\t\t// Keys are always lowercase.\n\teng.sent_options = Object.create(null);\t\t\t// Keys are always lowercase. Values are always strings.\n\teng.setoption_queue = [];\n\n\teng.warn_send_fail = true;\n\teng.leelaish = false;\t\t\t\t// Most likely set by hub upon an \"id name\" line, though can also be set by info_handler.\n\n\teng.search_running = NoSearch;\t\t// The search actually being run right now.\n\teng.search_desired = NoSearch;\t\t// The search we want Leela to be running. Often the same object as above.\n\teng.search_completed = NoSearch;\t// Whatever object search_running was when the last \"bestmove\" came.\n\n\t// -------------------------------------------------------------------------------------------\n\n\teng.send = function(msg, force) {\n\n\t\t// Importantly, setoption messages are normally held back until the engine is not running.\n\n\t\tmsg = msg.trim();\n\n\t\tif (msg.startsWith(\"setoption\")) {\n\n\t\t\tif (this.search_running.node && !force) {\n\t\t\t\tthis.setoption_queue.push(msg);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet lower = msg.toLowerCase();\n\t\t\tlet i1 = lower.indexOf(\" name \");\n\t\t\tlet i2 = lower.indexOf(\" value \");\n\n\t\t\tif (i1 !== -1 && i2 !== -1 && i2 > i1) {\n\n\t\t\t\tlet key = lower.slice(i1 + 6, i2).trim();\t\t\t// Keys are always lowercase.\n\t\t\t\tlet val = msg.slice(i2 + 7).trim();\n\n\t\t\t\tif (key.length > 0) {\n\t\t\t\t\tthis.sent_options[key] = val;\n\t\t\t\t\tthis.send_ack_setoption(key);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Do this test here so the sent_options / ack stuff happens even when there is no engine\n\t\t// loaded, this helps our menu check marks to be correct.\n\n\t\tif (!this.exe) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Send the message...\n\n\t\ttry {\n\t\t\tthis.exe.stdin.write(msg);\n\t\t\tthis.exe.stdin.write(\"\\n\");\n\t\t\tLog(\"--> \" + msg);\n\t\t\tthis.last_send = msg;\n\t\t} catch (err) {\n\t\t\tLog(\"(failed) --> \" + msg);\n\t\t\tif (this.last_send !== null && this.warn_send_fail) {\n\t\t\t\talert(messages.send_fail);\n\t\t\t\tthis.warn_send_fail = false;\n\t\t\t}\n\t\t}\n\t};\n\n\teng.send_desired = function() {\n\n\t\tif (this.search_running.node) {\n\t\t\tthrow \"send_desired() called but search was running\";\n\t\t}\n\n\t\tlet node = this.search_desired.node;\n\n\t\tif (!node || node.destroyed || node.terminal_reason()) {\n\t\t\tthis.search_running = NoSearch;\n\t\t\tthis.search_desired = NoSearch;\n\t\t\treturn;\n\t\t}\n\n\t\tlet root_fen = node.get_root().board.fen(!this.in_960_mode());\n\t\tlet setup = `fen ${root_fen}`;\n\n\t\tif (!this.in_960_mode() && setup === \"fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\") {\n\t\t\tsetup = \"startpos\";\t\t// May as well send this format if we're not in 960 mode.\n\t\t}\n\n\t\tlet moves;\n\n\t\tif (!this.in_960_mode()) {\n\t\t\tmoves = node.history_old_format();\n\t\t} else {\n\t\t\tmoves = node.history();\n\t\t}\n\n\t\tif (moves.length === 0) {\n\t\t\tthis.send(`position ${setup}`);\n\t\t} else {\n\t\t\tthis.send(`position ${setup} moves ${moves.join(\" \")}`);\n\t\t}\n\n\t\tif (config.log_positions) {\n\t\t\tLog(node.board.graphic());\n\t\t}\n\n\t\tlet s;\n\t\tlet n = this.search_desired.limit;\n\n\t\tif (!n) {\n\t\t\ts = \"go infinite\";\n\t\t} else if (this.search_desired.limit_by_time) {\n\t\t\ts = `go movetime ${n}`;\n\t\t} else {\n\t\t\ts = `go nodes ${n}`;\n\t\t}\n\n\t\tif (config.searchmoves_buttons && this.search_desired.searchmoves.length > 0) {\n\t\t\ts += \" searchmoves\";\n\t\t\tfor (let move of this.search_desired.searchmoves) {\n\t\t\t\ts += \" \" + move;\n\t\t\t}\n\t\t}\n\n\t\tthis.send(s);\n\t\tthis.search_running = this.search_desired;\n\t\tthis.suppress_cycle_info = null;\n\t\tthis.hub.info_handler.engine_cycle++;\n\t\tthis.hub.info_handler.engine_subcycle++;\n\t};\n\n\teng.set_search_desired = function(node, limit, limit_by_time, searchmoves) {\n\n\t\tif (!this.ever_received_uciok || !this.ever_received_readyok) {\n\t\t\tconsole.log(\"set_search_desired() aborted - too early\");\n\t\t\treturn;\n\t\t}\n\n\t\tlet params = SearchParams(node, limit, limit_by_time, searchmoves);\n\n\t\t// It is correct to check these against the *desired* search\n\t\t// (which may or may not be the one currently running).\n\n\t\tif (this.search_desired.node === params.node) {\n\t\t\tif (this.search_desired.limit === params.limit) {\n\t\t\t\tif (this.search_desired.limit_by_time === params.limit_by_time) {\n\t\t\t\t\tif (CompareArrays(this.search_desired.searchmoves, params.searchmoves)) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.search_desired = params;\n\n\t\t// If a search is running, stop it... we will send the new position (if applicable) after receiving bestmove.\n\t\t// If no search is running, start the new search immediately.\n\n\t\tif (this.search_running.node) {\n\t\t\tthis.send(\"stop\");\n\t\t\tif (!this.unresolved_stop_time) {\n\t\t\t\tthis.unresolved_stop_time = performance.now();\n\t\t\t}\n\t\t} else {\n\t\t\tif (this.search_desired.node) {\n\t\t\t\tthis.send_desired();\n\t\t\t}\n\t\t}\n\n\t};\n\n\teng.send_queued_setoptions = function() {\n\t\tfor (let msg of this.setoption_queue) {\n\t\t\tthis.send(msg, true);\t\t\t\t\t// Use the force flag in case we haven't set search_running to its correct value.\n\t\t}\n\t\tthis.setoption_queue = [];\n\t};\n\n\teng.send_ucinewgame = function() {\t\t\t\t// Engine should be halted before calling this.\n\t\tif (!this.ever_received_uciok || !this.ever_received_readyok) {\n\t\t\tconsole.log(\"send_ucinewgame() aborted - too early\");\n\t\t\treturn;\t\t\t\t// This is OK. When we actually get these, hub will send ucinewgame.\n\t\t}\n\t\tthis.send(\"ucinewgame\");\n\t};\n\n\teng.handle_bestmove_line = function(line) {\n\n\t\tthis.search_completed = this.search_running;\n\t\tthis.search_running = NoSearch;\n\n\t\tthis.unresolved_stop_time = null;\n\n\t\t// If this.search_desired === this.search_running then the search that just completed is\n\t\t// the most recent one requested by the hub; we have nothing to replace it with.\n\t\t//\n\t\t// Note that, in certain cases (e.g. a halt followed instantly by a resume) search_desired\n\t\t// and search_running will have identical properties but be different objects; in that case\n\t\t// it is correct to send the desired object as a new search.\n\n\t\tlet no_new_search   = this.search_desired === this.search_completed || !this.search_desired.node;\n\t\tlet report_bestmove = this.search_desired === this.search_completed && this.search_completed.node;\n\n\t\tif (no_new_search) {\n\t\t\tthis.search_desired = NoSearch;\n\t\t\tif (report_bestmove) {\n\t\t\t\tLog(\"< \" + line);\n\t\t\t\tthis.send_queued_setoptions();\t\t\t\t\t\t\t\t\t// After logging the incoming.\n\t\t\t\tthis.hub.receive_bestmove(line, this.search_completed.node);\t// May trigger a new search, so do it last.\n\t\t\t} else {\n\t\t\t\tLog(\"(ignore halted) < \" + line);\n\t\t\t\tthis.send_queued_setoptions();\t\t\t\t\t\t\t\t\t// After logging the incoming.\n\t\t\t}\n\t\t} else {\n\t\t\tLog(\"(ignore old) < \" + line);\n\t\t\tthis.send_queued_setoptions();\t\t\t\t\t\t\t\t\t\t// After logging the incoming.\n\t\t\tthis.send_desired();\n\t\t}\n\t};\n\n\teng.handle_info_line = function(line) {\n\n\t\tif (line.startsWith(\"info string ERROR\")) {\t\t\t\t\t\t\t\t// Stockfish sends these.\n\t\t\tLog(\"< \" + line);\n\t\t\tthis.hub.info_handler.err_receive(line.slice(12));\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.search_running.node) {\n\t\t\tif (config.log_info_lines) Log(\"(ignore !node) < \" + line);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.search_running.node.destroyed) {\n\t\t\tif (config.log_info_lines) Log(\"(ignore destroyed) < \" + line);\n\t\t\treturn;\n\t\t}\n\n\t\t// Stockfish has a nasty habit of sending super short PVs when you stop its search.\n\t\t// To get around that, we ignore info from SF if it comes during transition.\n\n\t\tif (!this.leelaish && this.search_desired.node !== this.search_running.node) {\n\t\t\tif (config.log_info_lines) Log(\"(ignore A/B late) < \" + line);\n\t\t\treturn;\n\t\t}\n\n\t\t// Hub can set a cycle to be suppressed (e.g. for the sake of making \"forget all analysis\" work).\n\t\t// This feels a bit sketchy, but will be OK as long as the next \"go\" is guaranteed to increment the cycle number.\n\n\t\tif (this.suppress_cycle_info === this.hub.info_handler.engine_cycle) {\n\t\t\tif (config.log_info_lines) Log(\"(ignore suppressed) < \" + line);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.hub.info_handler.receive(this, this.search_running, line);\t\t// Responsible for logging lines that get this far.\n\t};\n\n\teng.setoption = function(name, value) {\n\t\tlet s = `setoption name ${name} value ${value}`;\n\t\tthis.send(s);\n\t\treturn s;\t\t\t// Just so the caller can pop s up as a message if it wants.\n\t};\n\n\teng.pressbutton = function(name) {\n\t\tlet s = `setoption name ${name}`;\n\t\tthis.send(s);\n\t\treturn s;\t\t\t// Just so the caller can pop s up as a message if it wants.\n\t};\n\n\teng.send_ack_setoption = function(name) {\n\t\tlet key = name.toLowerCase();\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// Keys are always stored in lowercase.\n\t\tlet val = typeof this.sent_options[key] === \"string\" ? this.sent_options[key] : \"\";\t\t\t// Values are strings, if present\n\t\tlet o = {key, val};\n\t\tipcRenderer.send(\"ack_setoption\", o);\n\t\treturn o;\n\t};\n\n\teng.in_960_mode = function() {\n\t\treturn this.sent_options[\"uci_chess960\"] === \"true\";\t\t\t\t// The string \"true\" since these values are always strings.\n\t};\n\n\teng.known = function(s) {\n\t\treturn this.known_options[s.toLowerCase()] !== undefined;\n\t};\n\n\teng.send_ack_engine = function() {\n\t\tipcRenderer.send(\"ack_engine\", this.filepath);\n\t};\n\n\teng.setup = function(filepath, args) {\t\t// Returns true on success, false otherwise.\n\n\t\tLog(\"\");\n\t\tLog(`Launching ${filepath}`);\n\t\tif (args.length > 0) Log(`Args: ${JSON.stringify(args)}`);\n\t\tLog(\"\");\n\n\t\ttry {\n\t\t\tif (path.basename(filepath).toLowerCase().includes(\"lc0\")) {\t\t// Stupid hack to make Lc0 show all its options.\n\t\t\t\tif (args.includes(\"--show-hidden\") === false) {\n\t\t\t\t\targs = [\"--show-hidden\"].concat(args);\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.exe = child_process.spawn(filepath, args, {cwd: path.dirname(filepath)});\n\t\t} catch (err) {\n\t\t\tconsole.log(`engine.setup() failed: ${err.toString()}`);\n\t\t\treturn false;\n\t\t}\n\n\t\tthis.filepath = filepath;\n\t\tthis.send_ack_engine();\t\t\t// After this.filepath is set.\n\n\t\t// Main process wants to keep track of what these things are set to (for menu checks).\n\t\t// These will all ack the value \"\" to main.js since no value has been set yet...\n\n\t\tthis.sent_options = Object.create(null);\t\t// Blank anything we \"sent\" up till now.\n\n\t\tfor (let key of GUI_WANTS_TO_KNOW) {\n\t\t\tthis.send_ack_setoption(key);\n\t\t}\n\n\t\tthis.exe.once(\"error\", (err) => {\n\t\t\talert(err);\n\t\t});\n\n\t\tthis.scanner = readline.createInterface({\n\t\t\tinput: this.exe.stdout,\n\t\t\toutput: undefined,\n\t\t\tterminal: false\n\t\t});\n\n\t\tthis.err_scanner = readline.createInterface({\n\t\t\tinput: this.exe.stderr,\n\t\t\toutput: undefined,\n\t\t\tterminal: false\n\t\t});\n\n\t\tthis.err_scanner.on(\"line\", (line) => {\n\t\t\tif (this.have_quit) return;\n\t\t\tLog(\". \" + line);\n\t\t\tthis.hub.err_receive(SafeStringHTML(line));\n\t\t});\n\n\t\tthis.scanner.on(\"line\", (line) => {\n\n\t\t\tif (this.have_quit) return;\n\n\t\t\tif (line.startsWith(\"bestmove\")) {\n\t\t\t\tthis.handle_bestmove_line(line);\t\t// Will do logging, possibly adding a reason for rejection.\n\t\t\t} else if (line.startsWith(\"info\")) {\n\t\t\t\tthis.handle_info_line(line);\t\t\t// Will do logging, possibly adding a reason for rejection.\n\t\t\t} else {\n\t\t\t\tLog(\"< \" + line);\n\t\t\t\tif (line.startsWith(\"option\")) {\n\t\t\t\t\tlet a = line.indexOf(\" name \");\n\t\t\t\t\tlet b = line.indexOf(\" type \");\n\t\t\t\t\tif (a !== -1 && b != -1) {\n\t\t\t\t\t\tlet optname = line.slice(a + 6, b).trim().toLowerCase();\n\t\t\t\t\t\tthis.known_options[optname] = line.slice(b + 1);\n\t\t\t\t\t\tif (optname === \"uci_chess960\") {\t\t\t\t\t// As a special thing, always set UCI_Chess960 where possible.\n\t\t\t\t\t\t\tthis.setoption(\"UCI_Chess960\", true);\t\t\t// (Why is this not just done in globals.js? I forget...)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (line.startsWith(\"uciok\")) {\n\t\t\t\t\tthis.ever_received_uciok = true;\n\t\t\t\t}\n\t\t\t\tif (line.startsWith(\"readyok\")) {\n\t\t\t\t\tthis.ever_received_readyok = true;\n\t\t\t\t}\n\t\t\t\tthis.hub.receive_misc(SafeStringHTML(line));\n\t\t\t}\n\n\t\t});\n\n\t\treturn true;\n\t};\n\n\teng.shutdown = function() {\t\t\t\t// Note: Don't reuse the engine object.\n\t\tthis.have_quit = true;\n\t\tthis.send(\"quit\");\n\t\tif (this.exe) {\n\t\t\tsetTimeout(() => {\n\t\t\t\tthis.exe.kill();\n\t\t\t}, 2000);\n\t\t}\n\t};\n\n\treturn eng;\n}\n"
  },
  {
    "path": "files/src/renderer/95_hub.js",
    "content": "\"use strict\";\n\nfunction NewHub() {\n\n\tlet hub = Object.create(null);\n\n\thub.engine = NewEngine(hub);\t\t\t\t\t\t// Just a dummy object with no exe. Fixed by start.js later.\n\thub.tree = NewTreeHandler();\n\thub.grapher = NewGrapher();\n\thub.looker = NewLooker();\n\thub.info_handler = NewInfoHandler();\n\thub.status_handler = NewStatusHandler();\n\n\t// Various state we have to keep track of...\n\n\thub.loaders = [];\t\t\t\t\t\t\t\t\t// The loaders can have shutdown() called on them to stop ASAP.\n\thub.book = null;\t\t\t\t\t\t\t\t\t// Either a Polyglot buffer, or an array of {key, move, weight}.\n\thub.pgndata = null;\t\t\t\t\t\t\t\t\t// Object representing the loaded PGN file.\n\thub.engine_choices = [];\t\t\t\t\t\t\t// Made by show_fast_engine_chooser() when needed.\n\thub.fullbox_config_item = null;\t\t\t\t\t\t// Name of config item currently being edited in fullbox.\n\thub.fullbox_web_link = null;\t\t\t\t\t\t// Web link which can be clicked on in the config editor.\n\thub.pgn_choices_start = 0;\t\t\t\t\t\t\t// Where we are in the PGN Chooser screen.\n\thub.friendly_draws = New2DArray(8, 8, null);\t\t// What pieces are drawn in boardfriends. Used to skip redraws.\n\thub.enemy_draws = New2DArray(8, 8, null);\t\t\t// What pieces are drawn in boardsquares. Used to skip redraws.\n\thub.dirty_squares = New2DArray(8, 8, null);\t\t\t// What squares have some coloured background.\n\thub.active_square = null;\t\t\t\t\t\t\t// Clicked square, shown in blue.\n\thub.hoverdraw_div = -1;\t\t\t\t\t\t\t\t// Which div is hovered; used by draw_infobox().\n\thub.hoverdraw_depth = 0;\t\t\t\t\t\t\t// How deep in the hover PV we are.\n\thub.tick = 0;\t\t\t\t\t\t\t\t\t\t// How many draw loops we've been through. Used to animate hoverdraw.\n\thub.position_change_time = performance.now();\t\t// Time of the last position change. Used for cooldown on hoverdraw.\n\thub.node_to_clean = hub.tree.node;\t\t\t\t\t// The next node to be cleaned up (done when exiting it).\n\thub.leela_lock_node = null;\t\t\t\t\t\t\t// Non-null only when in \"analysis_locked\" mode.\n\n\thub.looker.add_to_queue(hub.tree.node.board);\t\t// Maybe make initial call to API such as ChessDN.cn...\n\tObject.assign(hub, hub_props);\n\treturn hub;\n}\n\nlet hub_props = {\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\t// Core methods wrt our main state...\n\n\tbehave: function(reason) {\t\t\t\t\t\t\t// reason should be \"position\" or \"behaviour\"\n\n\t\t// Called when position changes.\n\t\t// Called when behaviour changes.\n\t\t//\n\t\t// Each branch should do one of the following:\n\t\t//\n\t\t//\t\tCall __go() to start a new search\n\t\t//\t\tCall __halt() to ensure the engine isn't running\n\t\t//\t\tNothing, iff the correct search is already running\n\n\t\tif (reason !== \"position\" && reason !== \"behaviour\") {\n\t\t\tthrow \"behave(): bad call\";\n\t\t}\n\n\t\tswitch (config.behaviour) {\n\n\t\tcase \"halt\":\n\n\t\t\tthis.__halt();\n\t\t\tbreak;\n\n\t\tcase \"analysis_free\":\n\n\t\t\t// Note that the 2nd part of the condition is needed because changing behaviour can change what node_limit()\n\t\t\t// returns, therefore we might already be running a search for the right node but with the wrong limit.\n\t\t\t// THIS IS TRUE THROUGHOUT THIS FUNCTION.\n\n\t\t\tif (this.engine.search_desired.node !== this.tree.node || this.engine.search_desired.limit !== this.node_limit()) {\n\t\t\t\tthis.__go(this.tree.node);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase \"auto_analysis\":\n\t\tcase \"back_analysis\":\n\n\t\t\tif (this.tree.node.terminal_reason()) {\n\t\t\t\tthis.continue_auto_analysis();\t\t\t\t// This can get a bit recursive, do we care?\n\t\t\t} else if (this.engine.search_desired.node !== this.tree.node || this.engine.search_desired.limit !== this.node_limit()) {\n\t\t\t\tthis.__go(this.tree.node);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase \"analysis_locked\":\n\n\t\t\t// Moving shouldn't trigger anything, except that re-entering the correct node changes behaviour to halt\n\t\t\t// iff the search is completed.\n\n\t\t\tif (reason === \"position\") {\n\n\t\t\t\tif (this.tree.node === this.leela_lock_node) {\n\t\t\t\t\tif (!this.engine.search_desired.node) {\n\t\t\t\t\t\tthis.set_behaviour_direct(\"halt\");\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t} else {\n\n\t\t\t\tif (this.engine.search_desired.node !== this.leela_lock_node || this.engine.search_desired.limit !== this.node_limit()) {\n\t\t\t\t\tthis.__go(this.leela_lock_node);\n\t\t\t\t}\n\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase \"self_play\":\n\t\tcase \"play_white\":\n\t\tcase \"play_black\":\n\n\t\t\tif ((config.behaviour === \"self_play\") ||\n\t\t\t\t(config.behaviour === \"play_white\" && this.tree.node.board.active === \"w\") ||\n\t\t\t\t(config.behaviour === \"play_black\" && this.tree.node.board.active === \"b\")) {\n\n\t\t\t\tif (this.maybe_setup_book_move()) {\n\t\t\t\t\tthis.__halt();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tif (this.engine.search_desired.node !== this.tree.node || this.engine.search_desired.limit !== this.node_limit()) {\n\t\t\t\t\tthis.__go(this.tree.node);\n\t\t\t\t}\n\n\t\t\t} else {\t\t\t// Play single colour mode, wrong colour.\n\n\t\t\t\tthis.__halt();\n\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t},\n\n\tposition_changed: function(new_game_flag, avoid_confusion) {\n\n\t\t// Called right after this.tree.node is changed, meaning we are now drawing a different position.\n\n\t\tthis.escape();\n\t\tdrag_handler.cancel_drag();\n\n\t\tthis.hoverdraw_div = -1;\n\t\tthis.position_change_time = performance.now();\n\t\tfenbox.value = this.tree.node.board.fen(true);\n\n\t\tif (new_game_flag) {\n\t\t\tthis.node_to_clean = null;\n\t\t\tthis.leela_lock_node = null;\n\t\t\tthis.set_behaviour(\"halt\");\t\t\t\t\t// Will cause \"stop\" to be sent.\n\t\t\tif (!config.suppress_ucinewgame) {\n\t\t\t\tthis.engine.send_ucinewgame();\t\t\t// Must happen after \"stop\" is sent.\n\t\t\t}\n\t\t\tthis.send_title();\n\t\t\tif (this.engine.ever_received_uciok && !this.engine.in_960_mode() && this.tree.node.board.normalchess === false) {\n\t\t\t\talert(messages.c960_warning);\n\t\t\t}\n\t\t}\n\n\t\tif (this.tree.node.table.already_autopopulated === false) {\n\t\t\tthis.tree.node.table.autopopulate(this.tree.node);\n\t\t}\n\n\t\t// When entering a position, clear its searchmoves, unless it's the analysis_locked node.\n\n\t\tif (this.leela_lock_node !== this.tree.node) {\n\t\t\tthis.tree.node.searchmoves = [];\n\t\t}\n\n\t\t// Caller can tell us the change would cause user confusion for some modes...\n\n\t\tif (avoid_confusion) {\n\t\t\tif ([\"play_white\", \"play_black\", \"self_play\", \"auto_analysis\", \"back_analysis\"].includes(config.behaviour)) {\n\t\t\t\tthis.set_behaviour(\"halt\");\n\t\t\t}\n\t\t}\n\n\t\tthis.maybe_infer_info();\t\t\t\t\t\t// Before node_exit_cleanup() so that previous ghost info is available when moving forwards.\n\t\tthis.behave(\"position\");\n\t\tthis.draw();\n\n\t\tthis.node_exit_cleanup();\t\t\t\t\t\t// This feels like the right time to do this.\n\t\tthis.node_to_clean = this.tree.node;\n\n\t\tthis.looker.add_to_queue(this.tree.node.board);\n\t},\n\n\tset_behaviour: function(s) {\n\n\t\tif (!this.engine.ever_received_uciok || !this.engine.ever_received_readyok) {\n\t\t\ts = \"halt\";\n\t\t}\n\n\t\t// Don't do anything if behaviour is already correct. But\n\t\t// \"halt\" always triggers a behave() call for safety reasons.\n\n\t\tif (s === config.behaviour) {\n\t\t\tswitch (s) {\n\t\t\tcase \"halt\":\n\t\t\t\tbreak;\t\t\t\t\t// i.e. do NOT immediately return\n\t\t\tcase \"analysis_locked\":\n\t\t\t\tif (this.leela_lock_node !== this.tree.node) {\n\t\t\t\t\tbreak;\t\t\t\t// i.e. do NOT immediately return\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\tcase \"analysis_free\":\n\t\t\t\tif (!this.engine.search_desired.node) {\n\t\t\t\t\tbreak;\t\t\t\t// i.e. do NOT immediately return\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\tdefault:\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tthis.set_behaviour_direct(s);\n\t\tthis.behave(\"behaviour\");\n\t},\n\n\tset_behaviour_direct: function(s) {\n\t\tthis.leela_lock_node = (s === \"analysis_locked\") ? this.tree.node : null;\n\t\tconfig.behaviour = s;\n\t},\n\n\ttoggle_go: function() {\n\t\tif ([\"analysis_free\", \"self_play\", \"auto_analysis\", \"back_analysis\"].includes(config.behaviour)) {\n\t\t\tthis.set_behaviour(\"halt\");\n\t\t} else if (config.behaviour === \"halt\") {\n\t\t\tthis.set_behaviour(\"analysis_free\");\n\t\t}\n\t},\n\n\tplay_this_colour: function() {\n\t\tif (this.tree.node.board.active === \"w\") {\n\t\t\tthis.set_behaviour(\"play_white\");\n\t\t} else {\n\t\t\tthis.set_behaviour(\"play_black\");\n\t\t}\n\t},\n\n\thandle_search_params_change: function() {\n\n\t\t// If there's already a search desired, we can just let __go() figure out what the new parameters should be.\n\t\t// If they match what is already desired then set_search_desired() will ignore the call.\n\n\t\tif (this.engine.search_desired.node) {\n\t\t\tthis.__go(this.engine.search_desired.node);\n\t\t}\n\n\t\t// If there's no search desired, changing params probably shouldn't start one. As of 1.8.3, when a search\n\t\t// completes due to hitting the (normal) node limit, behaviour gets changed back to \"halt\" in one way or\n\t\t// another (unless config.allow_stopped_analysis is set).\n\t},\n\n\tcontinue_auto_analysis: function() {\n\n\t\tlet ok;\n\n\t\tif (config.behaviour === \"auto_analysis\") {\n\t\t\tok = this.tree.next();\n\t\t} else if (config.behaviour === \"back_analysis\") {\n\t\t\tok = this.tree.prev();\n\t\t}\n\n\t\tif (ok) {\n\t\t\tthis.position_changed(false, false);\n\t\t} else {\n\t\t\tthis.set_behaviour(\"halt\");\n\t\t}\n\t},\n\n\tmaybe_setup_book_move: function() {\n\n\t\tif (!this.book || this.tree.node.terminal_reason()) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (typeof config.book_depth === \"number\" && this.tree.node.depth >= config.book_depth * 2) {\n\t\t\treturn false;\n\t\t}\n\n\t\tlet move;\n\n\t\tlet objects = BookProbe(KeyFromBoard(this.tree.node.board), this.book);\n\t\tlet total_weight = 0;\n\n\t\tif (Array.isArray(objects)) {\n\t\t\tfor (let o of objects) {\n\t\t\t\ttotal_weight += o.weight;\n\t\t\t}\n\t\t}\n\n\t\tif (total_weight <= 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\tlet rng = RandInt(0, total_weight);\n\t\tlet weight_seen = 0;\n\t\tfor (let o of objects) {\t\t\t// The order doesn't matter at all when you think about it. No need to sort.\n\t\t\tweight_seen += o.weight;\n\t\t\tif (rng < weight_seen) {\n\t\t\t\tmove = o.move;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (!move) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (this.tree.node.board.illegal(move)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tlet correct_node = this.tree.node;\n\t\tlet correct_behaviour = config.behaviour;\n\n\t\t// Use a setTimeout to prevent recursion (since move() will cause a call to behave())\n\n\t\tsetTimeout(() => {\n\t\t\tif (this.tree.node === correct_node && config.behaviour === correct_behaviour) {\n\t\t\t\tthis.move(move);\n\t\t\t}\n\t\t}, 0);\n\n\t\treturn true;\n\t},\n\n\tmaybe_infer_info: function() {\n\n\t\t// This function creates \"ghost\" info in the info table when possible and necessary;\n\t\t// such info is inferred from ancestral info. It is also deleted upon leaving the node.\n\t\t//\n\t\t// The whole thing is a bit sketchy, maybe.\n\n\t\tif (config.behaviour === \"play_white\" || config.behaviour === \"play_black\") {\n\t\t\treturn;\n\t\t}\n\n\t\tlet node = this.tree.node;\n\n\t\tif (node.terminal_reason()) {\n\t\t\treturn;\n\t\t}\n\t\tif (!node.parent) {\n\t\t\treturn;\n\t\t}\n\n\t\tfor (let info of Object.values(node.table.moveinfo)) {\n\t\t\tif (info.__touched) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// So the current node has no real info.\n\n\t\tlet moves = [node.move];\n\t\tlet ancestor = null;\n\n\t\tlet foo = node.parent;\n\n\t\twhile (foo) {\n\n\t\t\tfor (let info of Object.values(foo.table.moveinfo)) {\n\t\t\t\tif (info.__touched) {\n\t\t\t\t\tancestor = foo;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!ancestor) {\n\t\t\t\tmoves.push(foo.move);\n\t\t\t\tfoo = foo.parent;\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (!ancestor) {\n\t\t\treturn;\n\t\t}\n\n\t\t// So we found the closest ancestor with info.\n\n\t\tmoves.reverse();\n\n\t\tlet oldinfo = ancestor.table.moveinfo[moves[0]];\n\n\t\tif (!oldinfo) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (Array.isArray(oldinfo.pv) === false || oldinfo.pv.length <= moves.length) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet pv = Array.from(oldinfo.pv);\n\n\t\tfor (let n = 0; n < moves.length; n++) {\n\t\t\tif (pv[n] !== moves[n]) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// So, everything matches and we can use the PV...\n\n\t\tlet nextmove = pv[moves.length];\n\t\tpv = pv.slice(moves.length);\n\n\t\tlet new_info = NewInfo(node.board, nextmove);\n\t\tnew_info.set_pv(pv);\n\t\tnew_info.__ghost = true;\n\t\tnew_info.__touched = true;\n\t\tnew_info.subcycle = 1;\t\t// Crude hack, makes draw_infobox() make other moves gray.\n\t\tnew_info.q = oldinfo.q;\n\t\tnew_info.cp = oldinfo.cp;\n\t\tnew_info.multipv = 1;\n\n\t\t// Flip our evals if the colour changes...\n\n\t\tif (oldinfo.board.active !== node.board.active) {\n\t\t\tif (typeof new_info.q === \"number\") {\n\t\t\t\tnew_info.q *= -1;\n\t\t\t}\n\t\t\tif (typeof new_info.cp === \"number\") {\n\t\t\t\tnew_info.cp *= -1;\n\t\t\t}\n\t\t}\n\n\t\tnode.table.moveinfo[nextmove] = new_info;\n\t},\n\n\tnode_exit_cleanup: function() {\n\n\t\tif (!this.node_to_clean || this.node_to_clean.destroyed) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove ghost info; which is only allowed in the node we're currently looking at...\n\t\t// By remove, I mean, replace it with a neutral info object.\n\n\t\tfor (let key of Object.keys(this.node_to_clean.table.moveinfo)) {\n\t\t\tif (this.node_to_clean.table.moveinfo[key].__ghost) {\n\t\t\t\tthis.node_to_clean.table.moveinfo[key] = NewInfo(this.node_to_clean.board, key);\n\t\t\t}\n\t\t}\n\t},\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\t// Spin, our main loop...\n\n\tspin: function() {\n\t\tthis.tick++;\n\t\tthis.draw();\n\t\tthis.purge_finished_loaders();\n\t\tthis.maybe_save_window_size();\n\t\tsetTimeout(this.spin.bind(this), config.update_delay);\n\t},\n\n\tpurge_finished_loaders: function() {\n\t\tthis.loaders = this.loaders.filter(o => o.callback);\n\t},\n\n\tmaybe_save_window_size: function() {\n\t\tif (this.window_resize_time && performance.now() - this.window_resize_time > 1000) {\n\t\t\tthis.window_resize_time = null;\n\t\t\tthis.save_window_size();\n\t\t}\n\t},\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\t// Drawing properties...\n\n\tdraw: function() {\n\n\t\t// We do the :hover reaction first. This way, we are detecting hover based on the previous cycle's state.\n\t\t// This should prevent the sort of flicker that can occur if we try to detect hover based on changes we\n\t\t// just made (i.e. if we drew then detected hover instantly).\n\n\t\tlet did_hoverdraw = this.hoverdraw();\n\n\t\tif (did_hoverdraw) {\n\t\t\tcanvas.style.outline = \"2px dashed #b4b4b4\";\n\t\t} else {\n\t\t\tthis.hoverdraw_div = -1;\n\t\t\tboardfriends.style.display = \"block\";\n\t\t\tcanvas.style.outline = \"none\";\n\t\t\tthis.draw_move_and_active_squares(this.tree.node.move, this.active_square);\n\t\t\tthis.draw_enemies_in_table(this.tree.node.board);\n\t\t\tthis.draw_canvas_arrows();\n\t\t\tthis.draw_friendlies_in_table(this.tree.node.board);\n\t\t}\n\n\t\tthis.draw_statusbox();\n\t\tthis.draw_infobox();\n\n\t\tthis.grapher.draw(this.tree.node);\n\t},\n\n\tdraw_friendlies_in_table: function(board) {\n\n\t\tfor (let x = 0; x < 8; x++) {\n\t\t\tfor (let y = 0; y < 8; y++) {\n\n\t\t\t\tlet piece_to_draw = \"\";\n\n\t\t\t\tif (board.colour(Point(x, y)) === board.active) {\n\t\t\t\t\tpiece_to_draw = board.state[x][y];\n\t\t\t\t}\n\n\t\t\t\tif (piece_to_draw === this.friendly_draws[x][y]) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// So if we get to here, we need to draw...\n\n\t\t\t\tthis.friendly_draws[x][y] = piece_to_draw;\n\n\t\t\t\tlet s = S(x, y);\n\t\t\t\tlet td = document.getElementById(\"overlay_\" + s);\n\n\t\t\t\tif (piece_to_draw === \"\") {\n\t\t\t\t\ttd.style[\"background-image\"] = \"none\";\n\t\t\t\t} else {\n\t\t\t\t\ttd.style[\"background-image\"] = images[piece_to_draw].string_for_bg_style;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\tdraw_enemies_in_table: function(board) {\n\n\t\tfor (let x = 0; x < 8; x++) {\n\t\t\tfor (let y = 0; y < 8; y++) {\n\n\t\t\t\tlet piece_to_draw = \"\";\n\n\t\t\t\tif (board.colour(Point(x, y)) === OppositeColour(board.active)) {\n\t\t\t\t\tpiece_to_draw = board.state[x][y];\n\t\t\t\t}\n\n\t\t\t\tif (piece_to_draw === this.enemy_draws[x][y]) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// So if we get to here, we need to draw...\n\n\t\t\t\tthis.enemy_draws[x][y] = piece_to_draw;\n\n\t\t\t\tlet s = S(x, y);\n\t\t\t\tlet td = document.getElementById(\"underlay_\" + s);\n\n\t\t\t\tif (piece_to_draw === \"\") {\n\t\t\t\t\ttd.style[\"background-image\"] = \"none\";\n\t\t\t\t} else {\n\t\t\t\t\ttd.style[\"background-image\"] = images[piece_to_draw].string_for_bg_style;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\tdraw_move_and_active_squares: function(move, active_square) {\n\n\t\t// These constants are stupidly used in set_active_square() also.\n\n\t\tconst EMPTY = 0;\n\t\tconst HIGHLIGHT = 1;\n\t\tconst ACTIVE = 2;\n\n\t\tif (!this.dmaas_scratch) {\n\t\t\tthis.dmaas_scratch = New2DArray(8, 8, null);\n\t\t}\n\n\t\t// First, set each element of the array to indicate what state we want\n\t\t// its background-color to be in.\n\n\t\tfor (let x = 0; x < 8; x++) {\n\t\t\tfor (let y = 0; y < 8; y++) {\n\t\t\t\tthis.dmaas_scratch[x][y] = EMPTY;\n\t\t\t}\n\t\t}\n\n\t\tlet move_points = [];\n\n\t\tif (typeof move === \"string\") {\n\t\t\tlet source = Point(move.slice(0, 2));\n\t\t\tlet dest = Point(move.slice(2, 4));\n\t\t\tif (source && dest) {\n\t\t\t\tmove_points = PointsBetween(source, dest);\n\t\t\t}\n\t\t}\n\n\t\tfor (let p of move_points) {\n\t\t\tthis.dmaas_scratch[p.x][p.y] = HIGHLIGHT;\n\t\t}\n\n\t\tif (active_square) {\n\t\t\tthis.dmaas_scratch[active_square.x][active_square.y] = ACTIVE;\n\t\t}\n\n\t\t// Now the dmaas_scratch array has what we actually want.\n\t\t// We check whether each square is already so, and change it otherwise.\n\n\t\tfor (let x = 0; x < 8; x++) {\n\n\t\t\tfor (let y = 0; y < 8; y++) {\n\n\t\t\t\tswitch (this.dmaas_scratch[x][y]) {\n\n\t\t\t\tcase EMPTY:\n\n\t\t\t\t\tif (this.dirty_squares[x][y] !== EMPTY) {\n\t\t\t\t\t\tlet s = S(x, y);\n\t\t\t\t\t\tlet td = document.getElementById(\"underlay_\" + s);\n\t\t\t\t\t\ttd.style[\"background-color\"] = \"transparent\";\n\t\t\t\t\t\tthis.dirty_squares[x][y] = EMPTY;\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase HIGHLIGHT:\n\n\t\t\t\t\tif (this.dirty_squares[x][y] !== HIGHLIGHT) {\n\t\t\t\t\t\tlet s = S(x, y);\n\t\t\t\t\t\tlet td = document.getElementById(\"underlay_\" + s);\n\t\t\t\t\t\ttd.style[\"background-color\"] = config.move_squares_with_alpha;\n\t\t\t\t\t\tthis.dirty_squares[x][y] = HIGHLIGHT;\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\n\t\t\t\tcase ACTIVE:\n\n\t\t\t\t\tif (this.dirty_squares[x][y] !== ACTIVE) {\n\t\t\t\t\t\tlet s = S(x, y);\n\t\t\t\t\t\tlet td = document.getElementById(\"underlay_\" + s);\n\t\t\t\t\t\ttd.style[\"background-color\"] = config.active_square;\n\t\t\t\t\t\tthis.dirty_squares[x][y] = ACTIVE;\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\thoverdraw: function() {\n\n\t\tif (!config.hover_draw || this.info_handler.clickers_are_valid_for_node(this.tree.node) === false) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (performance.now() - this.position_change_time < 1000) {\n\t\t\treturn false;\n\t\t}\n\n\t\tlet overlist = document.querySelectorAll(\":hover\");\n\n\t\t// Find what div we are over by looking for infoline_n\n\n\t\tlet div = null;\n\t\tlet div_index = null;\n\n\t\tfor (let item of overlist) {\n\t\t\tif (typeof item.id === \"string\" && item.id.startsWith(\"infoline_\")) {\n\t\t\t\tdiv = item;\n\t\t\t\tdiv_index = parseInt(item.id.slice(\"infoline_\".length), 10);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (!div || typeof div_index !== \"number\" || Number.isNaN(div_index)) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Find what infobox clicker we are over by looking for infobox_n\n\n\t\tlet click_n = null;\n\n\t\tfor (let item of overlist) {\n\t\t\tif (typeof item.id === \"string\" && item.id.startsWith(\"infobox_\")) {\n\t\t\t\tclick_n = parseInt(item.id.slice(\"infobox_\".length), 10);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (typeof click_n !== \"number\" || Number.isNaN(click_n)) {\n\n\t\t\t// We failed to get a click_n value. But if we are in Animate or Final Position mode,\n\t\t\t// it should still work even if the user isn't hovering over a move exactly; we can\n\t\t\t// just pass any valid click_n from the line... this is a pretty dumb hack.\n\n\t\t\tif (config.hover_method !== 0 && config.hover_method !== 2) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tfor (let item of div.childNodes) {\n\t\t\t\tif (typeof item.id === \"string\" && item.id.startsWith(\"infobox_\")) {\n\t\t\t\t\tclick_n = parseInt(item.id.slice(\"infobox_\".length), 10);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (typeof click_n !== \"number\" || Number.isNaN(click_n)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\t//\n\n\t\tif (config.hover_method === 0) {\n\t\t\treturn this.hoverdraw_animate(div_index, click_n);\t\t// Sets this.hoverdraw_div\n\t\t} else if (config.hover_method === 1) {\n\t\t\treturn this.hoverdraw_single(div_index, click_n);\t\t// Sets this.hoverdraw_div\n\t\t} else if (config.hover_method === 2) {\n\t\t\treturn this.hoverdraw_final(div_index, click_n);\t\t// Sets this.hoverdraw_div\n\t\t} else {\n\t\t\treturn false;\t\t\t\t\t\t\t\t\t\t\t// Caller must set this.hoverdraw_div to -1\n\t\t}\n\t},\n\n\thoverdraw_animate: function(div_index, click_n) {\n\n\t\t// If the user is hovering over an unexpected div index in the infobox, reset depth...\n\n\t\tif (div_index !== this.hoverdraw_div) {\n\t\t\tthis.hoverdraw_div = div_index;\n\t\t\tthis.hoverdraw_depth = 0;\n\t\t}\n\n\t\t// Sometimes increase depth...\n\n\t\tif (this.tick % config.animate_delay_multiplier === 0) {\n\t\t\tthis.hoverdraw_depth++;\n\t\t}\n\n\t\tlet moves = this.info_handler.moves_from_click_n(click_n, this.hoverdraw_depth);\n\n\t\tif (Array.isArray(moves) === false || moves.length === 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn this.draw_fantasy_from_moves(moves);\n\t},\n\n\thoverdraw_single: function(div_index, click_n) {\n\n\t\tthis.hoverdraw_div = div_index;\n\n\t\tlet moves = this.info_handler.moves_from_click_n(click_n);\n\n\t\tif (Array.isArray(moves) === false || moves.length === 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn this.draw_fantasy_from_moves(moves);\n\t},\n\n\thoverdraw_final: function(div_index, click_n) {\n\n\t\tthis.hoverdraw_div = div_index;\n\n\t\tlet moves = this.info_handler.moves_from_click_n(click_n, 999);\n\n\t\tif (Array.isArray(moves) === false || moves.length === 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn this.draw_fantasy_from_moves(moves);\n\t},\n\n\tdraw_fantasy_from_moves: function(moves) {\n\n\t\t// We don't assume moves is an array of legal moves, or even an array.\n\t\t// This is probably paranoid at this point but meh.\n\n\t\tif (Array.isArray(moves) === false) {\n\t\t\treturn false;\n\t\t}\n\n\t\tlet board = this.tree.node.board;\n\n\t\tfor (let move of moves) {\n\t\t\tlet illegal_reason = board.illegal(move);\n\t\t\tif (illegal_reason) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tboard = board.move(move);\n\t\t}\n\n\t\tlet move = moves[moves.length - 1];\t\t// Possibly undefined...\n\n\t\tthis.draw_fantasy(board, move);\n\t\treturn true;\n\t},\n\n\tdraw_fantasy: function(board, move) {\n\t\tthis.draw_move_and_active_squares(move, null);\n\t\tthis.draw_enemies_in_table(board);\n\t\tboardctx.clearRect(0, 0, canvas.width, canvas.height);\t\t// Clearing the canvas arrows.\n\t\tthis.draw_friendlies_in_table(board);\n\t},\n\n\tdraw_canvas_arrows: function() {\n\t\tboardctx.clearRect(0, 0, canvas.width, canvas.height);\n\t\tif (config.book_explorer) {\n\t\t\tthis.draw_explorer_arrows();\n\t\t} else if (config.lichess_explorer) {\n\t\t\tthis.draw_lichess_arrows();\n\t\t} else {\n\t\t\tlet arrow_spotlight_square = config.click_spotlight ? this.active_square : null;\n\t\t\tlet next_move = (config.next_move_arrow && this.tree.node.children.length > 0) ? this.tree.node.children[0].move : null;\n\t\t\tthis.info_handler.draw_arrows(this.tree.node, arrow_spotlight_square, next_move);\n\t\t}\n\t},\n\n\tdraw_explorer_arrows: function() {\n\n\t\t// This is all pretty isolated from everything else. Keep it that way.\n\n\t\tif (!this.book) {\n\t\t\tthis.explorer_objects_cache = null;\n\t\t\tthis.explorer_cache_node_id = null;\n\t\t\tthis.info_handler.draw_explorer_arrows(this.tree.node, [], null);\t\t// Needs to happen, to update the one_click_moves.\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.explorer_objects_cache || this.explorer_cache_node_id !== this.tree.node.id) {\n\t\t\tlet objects = BookProbe(KeyFromBoard(this.tree.node.board), this.book);\n\t\t\tlet total_weight = 0;\n\t\t\tif (Array.isArray(objects)) {\n\t\t\t\tfor (let o of objects) {\n\t\t\t\t\ttotal_weight += o.weight;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (total_weight <= 0) {\n\t\t\t\ttotal_weight = 1;\t\t// Avoid div by zero.\n\t\t\t}\n\t\t\tlet tmp = {};\n\t\t\tfor (let o of objects) {\n\t\t\t\tif (!this.tree.node.board.illegal(o.move)) {\n\t\t\t\t\tif (tmp[o.move] === undefined) {\n\t\t\t\t\t\ttmp[o.move] = {move: o.move, weight: o.weight / total_weight};\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.explorer_cache_node_id = this.tree.node.id;\n\t\t\tthis.explorer_objects_cache = Object.values(tmp);\n\t\t\tthis.explorer_objects_cache.sort((a, b) => b.weight - a.weight);\n\t\t}\n\n\t\tlet arrow_spotlight_square = config.click_spotlight ? this.active_square : null;\n\t\tthis.info_handler.draw_explorer_arrows(this.tree.node, this.explorer_objects_cache, arrow_spotlight_square);\n\t},\n\n\tdraw_lichess_arrows: function() {\n\n\t\t// Modified version of the above.\n\n\t\tlet ok = true;\n\n\t\tif (config.looker_api !== \"lichess_masters\" && config.looker_api !== \"lichess_plebs\") {\n\t\t\tok = false;\n\t\t}\n\n\t\tlet entry = this.looker.lookup(config.looker_api, this.tree.node.board);\n\n\t\tif (!entry) {\n\t\t\tok = false;\n\t\t}\n\n\t\tif (!ok) {\n\t\t\tthis.explorer_objects_cache = null;\n\t\t\tthis.explorer_cache_node_id = null;\n\t\t\tthis.info_handler.draw_explorer_arrows(this.tree.node, [], null);\t\t// Needs to happen, to update the one_click_moves.\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.explorer_objects_cache || this.explorer_cache_node_id !== this.tree.node.id) {\n\t\t\tlet total_weight = 0;\n\t\t\tfor (let o of Object.values(entry.moves)) {\n\t\t\t\ttotal_weight += o.total;\n\t\t\t}\n\t\t\tif (total_weight <= 0) {\n\t\t\t\ttotal_weight = 1;\t\t// Avoid div by zero.\n\t\t\t}\n\t\t\tlet tmp = {};\n\t\t\tfor (let move of Object.keys(entry.moves)) {\n\t\t\t\tif (!this.tree.node.board.illegal(move)) {\n\t\t\t\t\tif (tmp[move] === undefined) {\n\t\t\t\t\t\ttmp[move] = {move: move, weight: entry.moves[move].total / total_weight};\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.explorer_cache_node_id = this.tree.node.id;\n\t\t\tthis.explorer_objects_cache = Object.values(tmp);\n\t\t\tthis.explorer_objects_cache.sort((a, b) => b.weight - a.weight);\n\t\t}\n\n\t\tlet arrow_spotlight_square = config.click_spotlight ? this.active_square : null;\n\t\tthis.info_handler.draw_explorer_arrows(this.tree.node, this.explorer_objects_cache, arrow_spotlight_square);\n\t},\n\n\tdraw_statusbox: function() {\n\n\t\tlet analysing_other = null;\n\n\t\tif (config.behaviour === \"analysis_locked\" && this.leela_lock_node && this.leela_lock_node !== this.tree.node) {\n\t\t\tif (!this.leela_lock_node.parent) {\n\t\t\t\tanalysing_other = \"root\";\n\t\t\t} else {\n\t\t\t\tanalysing_other = \"position after \" + this.leela_lock_node.token(false, true);\n\t\t\t}\n\t\t}\n\n\t\tlet loading_message = null;\n\n\t\tfor (let loader of this.loaders) {\n\t\t\tif (loader.callback) {\t\t\t\t// By our rules, can only exist if the load is still pending...\n\t\t\t\tif (performance.now() - loader.starttime > 100) {\n\t\t\t\t\tloading_message = loader.msg;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.status_handler.draw_statusbox(\n\t\t\tthis.tree.node,\n\t\t\tthis.engine,\n\t\t\tanalysing_other,\n\t\t\tloading_message,\n\t\t\tthis.book ? true : false\n\t\t);\n\t},\n\n\tdraw_infobox: function() {\n\t\tthis.info_handler.draw_infobox(\n\t\t\tthis.tree.node,\n\t\t\tthis.mouse_point(),\n\t\t\tthis.active_square,\n\t\t\tthis.tree.node.board.active,\n\t\t\tthis.hoverdraw_div,\n\t\t\tconfig.behaviour === \"halt\" || config.never_suppress_searchmoves,\n\t\t\tconfig.looker_api ? this.looker.lookup(config.looker_api, this.tree.node.board) : null);\n\t},\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\t// Fundamental engine methods... not to be called directly, except by behave() and handle_search_params_change()...\n\n\t__halt: function() {\n\t\tthis.engine.set_search_desired(null);\n\t},\n\n\t__go: function(node) {\n\t\tthis.hide_fullbox();\n\t\tif (!node || node.destroyed || node.terminal_reason()) {\n\t\t\tthis.engine.set_search_desired(null);\n\t\t\treturn;\n\t\t}\n\t\tthis.engine.set_search_desired(node, this.node_limit(), engineconfig[this.engine.filepath].limit_by_time, node.searchmoves);\n\t},\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\t// Info receivers...\n\n\treceive_bestmove: function(s, relevant_node) {\n\n\t\tlet ok;\t\t// Could be used by 2 different parts of the switch (but not at time of writing...)\n\n\t\tswitch (config.behaviour) {\n\n\t\tcase \"self_play\":\n\t\tcase \"play_white\":\n\t\tcase \"play_black\":\n\n\t\t\tif (relevant_node !== this.tree.node) {\n\t\t\t\tLogBoth(`(ignored bestmove, relevant_node !== hub.tree.node, config.behaviour was \"${config.behaviour}\")`);\n\t\t\t\tthis.set_behaviour(\"halt\");\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tlet tokens = s.split(\" \").filter(z => z !== \"\");\n\t\t\tok = this.move(tokens[1]);\n\n\t\t\tif (!ok) {\n\t\t\t\tLogBoth(`BAD BESTMOVE (${tokens[1]}) IN POSITION ${this.tree.node.board.fen(true)}`);\n\t\t\t\tthis.set_special_message(`WARNING! Bad bestmove (${tokens[1]}) received!`, \"yellow\", 10000);\n\t\t\t} else {\n\t\t\t\tif (this.tree.node.terminal_reason()) {\n\t\t\t\t\tthis.set_behaviour(\"halt\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tbreak;\n\n\t\tcase \"auto_analysis\":\n\t\tcase \"back_analysis\":\n\n\t\t\tif (relevant_node !== this.tree.node) {\n\t\t\t\tLogBoth(`(ignored bestmove, relevant_node !== hub.tree.node, config.behaviour was \"${config.behaviour}\")`);\n\t\t\t\tthis.set_behaviour(\"halt\");\n\t\t\t} else {\n\t\t\t\tthis.continue_auto_analysis();\n\t\t\t}\n\n\t\t\tbreak;\n\n\t\tcase \"analysis_free\":\t\t\t// We hit the node limit.\n\n\t\t\tif (!config.allow_stopped_analysis) {\n\t\t\t\tthis.set_behaviour(\"halt\");\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase \"analysis_locked\":\n\n\t\t\t// We hit the node limit. If the node we're looking at isn't the locked node, don't\n\t\t\t// change behaviour. (It will get changed when we enter the locked node.)\n\n\t\t\tif (this.tree.node === this.leela_lock_node) {\n\t\t\t\tthis.set_behaviour(\"halt\");\n\t\t\t}\n\t\t\tbreak;\n\n\t\t}\n\t},\n\n\treceive_misc: function(s) {\n\n\t\tif (s.startsWith(\"id name\")) {\n\n\t\t\t// Note that we do need to set the leelaish flag on the engine here (rather than relying on the\n\t\t\t// autodetection in info.js) so that correct options can be sent.\n\n\t\t\tthis.engine.leelaish = false;\n\n\t\t\tfor (let name of config.leelaish_names) {\n\t\t\t\tif (s.includes(name)) {\n\t\t\t\t\tthis.engine.leelaish = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Our defaults in engineconfig_io.newentry() are appropriate for Leelaish engines.\n\t\t\t// But if this is the first time we see an A/B engine, we must adjust them...\n\n\t\t\tif (!this.engine.leelaish && !engineconfig[this.engine.filepath].options[\"MultiPV\"]) {\n\t\t\t\t// This likely indicates the engine is new to the config.\n\t\t\t\tengineconfig[this.engine.filepath].options[\"MultiPV\"] = 3;\t\t\t\t// Will get ack'd when engine_send_all_options() happens\n\t\t\t\tengineconfig[this.engine.filepath].search_nodes_special = 10000000;\n\t\t\t\tthis.send_ack_node_limit(true);\n\t\t\t}\n\n\t\t\t// Pass unknown engines to the error handler to be displayed...\n\n\t\t\tif (!s.includes(\"Lc0\") && !s.includes(\"Ceres\") && !s.includes(\"Stockfish\")) {\n\t\t\t\tthis.info_handler.err_receive(s.slice(\"id name\".length).trim());\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tif (s.startsWith(\"uciok\")) {\n\n\t\t\t// Until we receive uciok and readyok, set_behaviour() does nothing and set_search_desired() ignores calls, so \"go\" cannot have been sent.\n\n\t\t\tthis.engine_send_all_options();\n\t\t\tthis.engine.send(\"isready\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (s.startsWith(\"readyok\")) {\n\n\t\t\t// Until we receive uciok and readyok, set_behaviour() does nothing and set_search_desired() ignores calls, so \"go\" cannot have been sent.\n\n\t\t\tthis.set_behaviour(\"halt\");\t\t\t\t\t// Likely redundant (should be \"halt\" anyway), but ensures the hub is in a sane state.\n\t\t\tthis.engine.send_ucinewgame();\t\t\t\t// Relies on the engine not running.\n\t\t\treturn;\n\t\t}\n\n\t\t// Misc messages. Treat ones that aren't valid UCI as errors to be passed along...\n\n\t\tif (!s.startsWith(\"id\") &&\n\t\t\t!s.startsWith(\"option\") &&\n\t\t\t!s.startsWith(\"bestmove\") &&\t\t\t\t// These messages shouldn't reach this function\n\t\t\t!s.startsWith(\"info\")\t\t\t\t\t\t// These messages shouldn't reach this function\n\t\t) {\n\t\t\tthis.info_handler.err_receive(s);\n\t\t}\n\t},\n\n\terr_receive: function(s) {\n\n\t\t// Some highlights... this is obviously super-fragile based on the precise strings Leela sends.\n\n\t\tif (s.startsWith(\"Found configuration file: \")) {\n\t\t\tthis.info_handler.err_receive(HighlightString(s, \"Found configuration file: \", \"blue\"));\n\t\t\treturn;\n\t\t}\n\n\t\tif (s.startsWith(\"Loading Syzygy tablebases from \")) {\n\t\t\tthis.info_handler.err_receive(HighlightString(s, \"Loading Syzygy tablebases from \", \"blue\"));\n\t\t\treturn;\n\t\t}\n\n\t\tif (s.startsWith(\"Loading weights file from: \")) {\n\t\t\tthis.info_handler.err_receive(HighlightString(s, \"Loading weights file from: \", \"blue\"));\n\t\t\treturn;\n\t\t}\n\n\t\tif (s.startsWith(\"Found pb network file: \")) {\n\t\t\tthis.info_handler.err_receive(HighlightString(s, \"Found pb network file: \", \"blue\"));\n\t\t\treturn;\n\t\t}\n\n\t\tthis.info_handler.err_receive(s);\n\t},\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\t// Node limits...\n\n\tnode_limit: function() {\n\n\t\t// Given the current state of the config, what is the node limit?\n\t\t// Note that this value is used as a time limit instead, if engineconfig[this.engine.filepath].limit_by_time is set.\n\n\t\tlet cfg_value;\n\n\t\tswitch (config.behaviour) {\n\n\t\tcase \"play_white\":\n\t\tcase \"play_black\":\n\t\tcase \"self_play\":\n\t\tcase \"auto_analysis\":\n\t\tcase \"back_analysis\":\n\n\t\t\tcfg_value = engineconfig[this.engine.filepath].search_nodes_special;\n\t\t\tbreak;\n\n\t\tdefault:\n\n\t\t\tcfg_value = engineconfig[this.engine.filepath].search_nodes;\n\t\t\tbreak;\n\n\t\t}\n\n\t\t// Should match the system in engine.js.\n\n\t\tif (typeof cfg_value === \"number\" && cfg_value >= 1) {\n\t\t\treturn cfg_value;\n\t\t} else {\n\t\t\treturn null;\n\t\t}\n\t},\n\n\tadjust_node_limit: function(direction, special_flag) {\n\n\t\tlet cfg_value = special_flag ? engineconfig[this.engine.filepath].search_nodes_special : engineconfig[this.engine.filepath].search_nodes;\n\n\t\tif (direction > 0) {\n\n\t\t\tif (typeof cfg_value !== \"number\" || cfg_value <= 0) {\t\t\t\t// Already unlimited\n\t\t\t\tthis.set_node_limit_generic(null, special_flag);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tfor (let i = 0; i < limit_options.length; i++) {\n\t\t\t\tif (limit_options[i] > cfg_value) {\n\t\t\t\t\tthis.set_node_limit_generic(limit_options[i], special_flag);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.set_node_limit_generic(null, special_flag);\n\n\t\t} else {\n\n\t\t\tif (typeof cfg_value !== \"number\" || cfg_value <= 0) {\t\t\t\t// Unlimited; reduce to highest finite option\n\t\t\t\tthis.set_node_limit_generic(limit_options[limit_options.length - 1], special_flag);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tfor (let i = limit_options.length - 1; i >= 0; i--) {\n\t\t\t\tif (limit_options[i] < cfg_value) {\n\t\t\t\t\tthis.set_node_limit_generic(limit_options[i], special_flag);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.set_node_limit_generic(1, special_flag);\n\t\t}\n\t},\n\n\tset_node_limit: function(val) {\n\t\tthis.set_node_limit_generic(val, false);\n\t},\n\n\tset_node_limit_special: function(val) {\n\t\tthis.set_node_limit_generic(val, true);\n\t},\n\n\tset_node_limit_generic: function(val, special_flag) {\n\n\t\tif (typeof val !== \"number\" || val <= 0) {\n\t\t\tval = null;\n\t\t}\n\n\t\tlet msg_start;\n\t\tlet by_time = engineconfig[this.engine.filepath].limit_by_time;\n\n\t\tif (by_time) {\n\t\t\tmsg_start = special_flag ? \"Special time limit\" : \"Time limit\";\n\t\t} else {\n\t\t\tmsg_start = special_flag ? \"Special node limit\" : \"Node limit\";\n\t\t}\n\n\t\tif (val) {\n\t\t\tthis.set_special_message(`${msg_start} now ${CommaNum(val)} ${by_time ? \"ms\" : \"\"}`, \"blue\");\n\t\t} else {\n\t\t\tthis.set_special_message(`${msg_start} removed!`, \"blue\");\n\t\t}\n\n\t\tif (special_flag) {\n\t\t\tengineconfig[this.engine.filepath].search_nodes_special = val;\n\t\t} else {\n\t\t\tengineconfig[this.engine.filepath].search_nodes = val;\n\t\t}\n\n\t\tthis.send_ack_node_limit(special_flag);\n\n\t\tthis.handle_search_params_change();\n\t},\n\n\tsend_ack_node_limit: function(special_flag) {\n\n\t\tlet ack_type = special_flag ? \"ack_special_node_limit\" : \"ack_node_limit\";\n\t\tlet val;\n\n\t\tif (special_flag) {\n\t\t\tval = engineconfig[this.engine.filepath].search_nodes_special;\n\t\t} else {\n\t\t\tval = engineconfig[this.engine.filepath].search_nodes;\n\t\t}\n\n\t\tif (val) {\n\t\t\tipcRenderer.send(ack_type, CommaNum(val));\n\t\t} else {\n\t\t\tipcRenderer.send(ack_type, \"Unlimited\");\n\t\t}\n\t},\n\n\ttoggle_limit_by_time: function() {\n\t\tengineconfig[this.engine.filepath].limit_by_time = !engineconfig[this.engine.filepath].limit_by_time;\n\t\tthis.send_ack_limit_by_time();\n\t\tthis.handle_search_params_change();\n\t},\n\n\tsend_ack_limit_by_time: function() {\n\t\tipcRenderer.send(\"ack_limit_by_time\", engineconfig[this.engine.filepath].limit_by_time);\n\t},\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\t// Engine-related acks...\n\n\tsend_ack_engine: function() {\n\t\tthis.engine.send_ack_engine();\n\t},\n\n\tsend_ack_setoption: function(name) {\n\t\tthis.engine.send_ack_setoption(name);\n\t},\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\t// Misc engine methods...\n\n\tsoft_engine_reset: function() {\n\t\tthis.set_behaviour(\"halt\");\t\t\t\t\t// Will cause \"stop\" to be sent.\n\t\tthis.engine.send_ucinewgame();\t\t\t\t// Must happen after \"stop\" is sent.\n\t},\n\n\tforget_analysis: function() {\n\t\tCleanTree(this.tree.root);\n\t\tthis.tree.node.table.autopopulate(this.tree.node);\n\t\tthis.set_behaviour(\"halt\");\t\t\t\t\t// Will cause \"stop\" to be sent.\n\t\tthis.engine.send_ucinewgame();\t\t\t\t// Must happen after \"stop\" is sent.\n\t\tthis.engine.suppress_cycle_info = this.info_handler.engine_cycle;\t\t// Ignore further info updates from this cycle.\n\t},\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\t// UCI options...\n\n\tset_uci_option: function(name, val, save_to_cfg = false, blue_text = true) {\n\n\t\t// Note that all early returns from this function need to send an ack\n\t\t// of the prevailing value to fix checkmarks in the main process.\n\n\t\tif (!this.engine.ever_received_uciok) {\t\t\t\t\t\t\t\t\t// Correct leelaish flag not yet known.\n\t\t\talert(messages.too_soon_to_set_options);\n\t\t\tthis.engine.send_ack_setoption(name);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.engine.leelaish && name.toLowerCase() === \"multipv\") {\n\t\t\tthis.set_special_message(\"MultiPV should be 500 for this engine\", \"blue\");\n\t\t\tthis.engine.send_ack_setoption(name);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.engine.known(name)) {\n\t\t\tthis.set_special_message(`${name} not known by this engine`, \"blue\");\n\t\t\tthis.engine.send_ack_setoption(name);\n\t\t\treturn;\n\t\t}\n\n\t\tif (save_to_cfg) {\n\t\t\tif (val === null || val === undefined) {\n\t\t\t\tdelete engineconfig[this.engine.filepath].options[name];\n\t\t\t} else {\n\t\t\t\tengineconfig[this.engine.filepath].options[name] = val;\n\t\t\t}\n\t\t}\n\n\t\tif (val === null || val === undefined) {\n\t\t\tval = \"\";\n\t\t}\n\n\t\tthis.set_behaviour(\"halt\");\n\t\tlet sent = this.engine.setoption(name, val);\t\t\t\t\t\t\t// Will ack the new value.\n\t\tif (blue_text) {\n\t\t\tthis.set_special_message(sent, \"blue\");\n\t\t}\n\t},\n\n\tset_uci_option_permanent: function(name, val) {\n\t\tthis.set_uci_option(name, val, true);\n\t},\n\n\tset_uci_option_permanent_and_cleartree: function(name, val) {\n\t\tthis.set_uci_option(name, val, true);\n\t\tif (this.engine.leelaish) {\n\t\t\tthis.set_uci_option(\"ClearTree\", true, false, false);\n\t\t}\n\t},\n\n\tdisable_syzygy: function() {\n\t\tdelete engineconfig[this.engine.filepath].options[\"SyzygyPath\"];\n\t\tthis.restart_engine();\t\t// Causes the correct ack to be sent.\n\t},\n\n\tauto_weights: function() {\n\t\tdelete engineconfig[this.engine.filepath].options[\"EvalFile\"];\n\t\tdelete engineconfig[this.engine.filepath].options[\"WeightsFile\"];\n\t\tthis.restart_engine();\t\t// Causes the correct acks to be sent.\n\t},\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\t// Engine startup...\n\n\treload_engineconfig: function() {\n\t\t[load_err2, engineconfig] = engineconfig_io.load();\n\t\tif (load_err2) {\n\t\t\talert(load_err2);\n\t\t}\n\t\tthis.restart_engine();\n\t},\n\n\tswitch_engine: function(filename) {\n\t\tthis.set_behaviour(\"halt\");\n\t\tif (this.engine_start(filename)) {\n\t\t\tconfig.path = filename;\n\t\t} else {\n\t\t\talert(\"Failed to start this engine.\");\n\t\t\tthis.engine.send_ack_engine();\n\t\t}\n\t},\n\n\trestart_engine: function() {\n\t\tthis.engine.warn_send_fail = false;\t\t\t// Don't want \"send failed\" warnings from old engine any more.\n\t\tthis.set_behaviour(\"halt\");\n\t\tif (this.engine_start(config.path)) {\n\t\t\t// pass\n\t\t} else {\n\t\t\talert(\"Failed to restart the engine.\");\n\t\t\tthis.engine.send_ack_engine();\n\t\t}\n\t},\n\n\tengine_start: function(filepath, blue_fail) {\n\n\t\tif (!filepath || typeof filepath !== \"string\" || fs.existsSync(filepath) === false) {\n\t\t\tif (blue_fail && !load_err1 && !load_err2) {\n\t\t\t\tthis.err_receive(`<span class=\"blue\">${messages.engine_not_present}</span>`);\n\t\t\t\tthis.err_receive(\"\");\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\n\t\tlet args = engineconfig[filepath] ? engineconfig[filepath].args : [];\n\n\t\tlet new_engine = NewEngine(this);\n\t\tlet success = new_engine.setup(filepath, args, this);\n\n\t\tif (success === false) {\n\t\t\tif (blue_fail && !load_err1 && !load_err2) {\n\t\t\t\tthis.err_receive(`<span class=\"blue\">${messages.engine_failed_to_start}</span>`);\n\t\t\t\tthis.err_receive(\"\");\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\n\t\tthis.engine.shutdown();\n\t\tthis.engine = new_engine;\t\t\t\t\t// Don't reuse engine objects, not even the dummy object. There are sync issues due to fake \"go\"s.\n\n\t\tif (!engineconfig[this.engine.filepath]) {\n\t\t\tengineconfig[this.engine.filepath] = engineconfig_io.newentry();\n\t\t\tconsole.log(`Creating new entry in engineconfig for ${filepath}`);\n\t\t}\n\n\t\tthis.engine.send(\"uci\");\n\n\t\tthis.send_ack_node_limit(false);\t\t\t// Ack the node limits that are set in engineconfig[this.engine.filepath]\n\t\tthis.send_ack_node_limit(true);\n\n\t\tthis.send_ack_limit_by_time();\t\t\t\t// Also ack the limit_by_time boolean for that menu item.\n\n\t\tthis.info_handler.reset_engine_info();\n\t\tthis.info_handler.must_draw_infobox();\t\t// To display the new stderr log that appears.\n\n\t\treturn true;\n\t},\n\n\tengine_send_all_options: function() {\t\t\t// The engine should never have been given a \"go\" before this.\n\n\t\t// Options that are sent regardless of whether the engine seems to know about them...\n\n\t\tlet forced_engine_options = this.engine.leelaish ? forced_lc0_options : forced_ab_options;\n\t\tfor (let [key, value] of Object.entries(forced_engine_options)) {\n\t\t\tthis.engine.setoption(key, value);\n\t\t}\n\n\t\t// Standard options... only sent if the engine has said it knows them...\n\n\t\tlet standard_engine_options = this.engine.leelaish ? standard_lc0_options : standard_ab_options;\n\t\tfor (let [key, value] of Object.entries(standard_engine_options)) {\n\t\t\tif (this.engine.known(key)) {\n\t\t\t\tthis.engine.setoption(key, value);\n\t\t\t}\n\t\t}\n\n\t\t// Now send user-selected options. Thus, the user can override anything above.\n\n\t\tlet options = engineconfig[this.engine.filepath].options;\n\t\tlet keys = Object.keys(options);\n\n\t\tkeys.sort((a, b) => {\t\t// \"It is recommended to set Hash after setting Threads.\"\n\t\t\tif (a.toLowerCase() === \"hash\" && b.toLowerCase() !== \"hash\") return 1;\n\t\t\tif (a.toLowerCase() !== \"hash\" && b.toLowerCase() === \"hash\") return -1;\n\t\t\treturn 0;\n\t\t});\n\n\t\tfor (let key of keys) {\n\t\t\tthis.engine.setoption(key, options[key]);\n\t\t}\n\t},\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\t// Tree manipulation methods...\n\n\tmove: function(s) {\t\t\t\t\t\t\t// It is safe to call this with illegal moves.\n\n\t\tif (typeof s !== \"string\") {\n\t\t\tconsole.log(`hub.move(${s}) - bad argument`);\n\t\t\treturn false;\n\t\t}\n\n\t\tlet board = this.tree.node.board;\n\t\tlet source = Point(s.slice(0, 2));\n\n\t\tif (!source) {\n\t\t\tconsole.log(`hub.move(${s}) - invalid source`);\n\t\t\treturn false;\n\t\t}\n\n\t\t// First deal with old-school castling in Standard Chess...\n\n\t\ts = board.c960_castling_converter(s);\n\n\t\t// If a promotion character is required and not present, show the promotion chooser and return\n\t\t// without committing to anything.\n\n\t\tif (s.length === 4) {\n\t\t\tif ((board.piece(source) === \"P\" && source.y === 1) || (board.piece(source) === \"p\" && source.y === 6)) {\n\t\t\t\tlet illegal_reason = board.illegal(s + \"q\");\n\t\t\t\tif (illegal_reason) {\n\t\t\t\t\tconsole.log(`hub.move(${s}) - ${illegal_reason}`);\n\t\t\t\t} else {\n\t\t\t\t\tthis.show_promotiontable(s);\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\t// The promised legality check...\n\n\t\tlet illegal_reason = board.illegal(s);\n\t\tif (illegal_reason) {\n\t\t\tconsole.log(`hub.move(${s}) - ${illegal_reason}`);\n\t\t\treturn false;\n\t\t}\n\n\t\tthis.tree.make_move(s);\n\t\tthis.position_changed();\n\t\treturn true;\n\t},\n\n\trandom_move: function() {\n\t\tlet legals = this.tree.node.board.movegen();\n\t\tif (legals.length > 0) {\n\t\t\tthis.move(RandChoice(legals));\n\t\t}\n\t},\n\n\tplay_info_index: function(n) {\n\n\t\tlet line_starts = this.info_handler.info_clickers.filter(o => o.is_start);\n\n\t\tif (n < line_starts.length) {\n\n\t\t\tlet move = line_starts[n].move;\n\n\t\t\tlet table_move = this.tree.node.table.moveinfo[move];\n\n\t\t\tif (table_move && table_move.__touched) {\t\t// Allow this to happen if the move is touched\n\t\t\t\tthis.move(move);\n\t\t\t} else if (config.looker_api) {\t\t\t\t\t// Allow this to happen if the move is in the selected API database\n\t\t\t\tlet db_entry = this.looker.lookup(config.looker_api, this.tree.node.board);\n\t\t\t\tif (db_entry && db_entry.moves[move]) {\n\t\t\t\t\tthis.move(move);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\t// Note that the various tree.methods() return whether or not the current node changed.\n\n\treturn_to_lock: function() {\n\t\tif (config.behaviour === \"analysis_locked\") {\n\t\t\tif (this.tree.set_node(this.leela_lock_node)) {\t\t// Fool-proof against null / destroyed.\n\t\t\t\tthis.position_changed(false, true);\n\t\t\t}\n\t\t}\n\t},\n\n\tprev: function() {\n\t\tif (this.tree.prev()) {\n\t\t\tthis.position_changed(false, true);\n\t\t}\n\t},\n\n\tnext: function() {\n\t\tif (this.tree.next()) {\n\t\t\tthis.position_changed(false, true);\n\t\t}\n\t},\n\n\tgoto_root: function() {\n\t\tif (this.tree.goto_root()) {\n\t\t\tthis.position_changed(false, true);\n\t\t}\n\t},\n\n\tgoto_end: function() {\n\t\tif (this.tree.goto_end()) {\n\t\t\tthis.position_changed(false, true);\n\t\t}\n\t},\n\n\tprevious_sibling: function() {\n\t\tif (this.tree.previous_sibling()) {\n\t\t\tthis.position_changed(false, true);\n\t\t}\n\t},\n\n\tnext_sibling: function() {\n\t\tif (this.tree.next_sibling()) {\n\t\t\tthis.position_changed(false, true);\n\t\t}\n\t},\n\n\treturn_to_main_line: function() {\n\t\tif (this.tree.return_to_main_line()) {\n\t\t\tthis.position_changed(false, true);\n\t\t}\n\t},\n\n\tdelete_node: function() {\n\t\tif (this.tree.delete_node()) {\n\t\t\tthis.position_changed(false, true);\n\t\t}\n\t},\n\n\tpromote_to_main_line: function() {\n\t\tthis.tree.promote_to_main_line();\n\t},\n\n\tpromote: function() {\n\t\tthis.tree.promote();\n\t},\n\n\tdelete_other_lines: function() {\n\t\tthis.tree.delete_other_lines();\n\t},\n\n\tdelete_children: function() {\n\t\tthis.tree.delete_children();\n\t},\n\n\tdelete_siblings: function() {\n\t\tthis.tree.delete_siblings();\n\t},\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\n\tnew_game: function() {\n\t\tthis.load_fen(\"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\");\n\t},\n\n\tnew_960: function(n) {\n\t\tif (n === undefined) {\n\t\t\tn = RandInt(0, 960);\n\t\t}\n\t\tthis.load_fen(c960_fen(n), true);\n\t},\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\n\tpgn_to_clipboard: function() {\n\t\tPGNToClipboard(this.tree.node);\n\t},\n\n\tsave: function(filename) {\n\t\tSavePGN(filename, this.tree.node);\n\t},\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\t// Loading PGN...\n\n\topen: function(filename) {\n\n\t\tif (filename === __dirname || filename === \".\") {\t\t// Can happen when extra args are passed to main process. Silently return.\n\t\t\treturn;\n\t\t}\n\t\tif (fs.existsSync(filename) === false) {\t\t\t\t// Can happen when extra args are passed to main process. Silently return.\n\t\t\treturn;\n\t\t}\n\t\tif (!config.ignore_filesize_limits && FileExceedsGigabyte(filename, 2)) {\n\t\t\talert(messages.file_too_big);\n\t\t\treturn;\n\t\t}\n\n\t\tfor (let loader of this.loaders) {\n\t\t\tif (loader.type === \"pgn\") {\n\t\t\t\tloader.shutdown();\n\t\t\t}\n\t\t}\n\n\t\tconsole.log(`Loading PGN: ${filename}`);\n\n\t\tlet loader = NewFastPGNLoader(filename, (err, pgndata) => {\n\t\t\tif (!err) {\n\t\t\t\tpgndata.source = path.basename(filename);\n\t\t\t\tthis.handle_loaded_pgndata(pgndata);\n\t\t\t} else {\n\t\t\t\tconsole.log(err);\n\t\t\t}\n\t\t});\n\n\t\tthis.loaders.push(loader);\n\t},\n\n\thandle_loaded_pgndata: function(pgndata) {\n\t\tif (!pgndata || pgndata.count() === 0) {\n\t\t\talert(\"No data found.\");\n\t\t\treturn;\n\t\t}\n\t\tif (pgndata.count() === 1) {\n\t\t\tlet success = this.load_pgn_object(pgndata.getrecord(0));\n\t\t\tif (success) {\n\t\t\t\tthis.pgndata = pgndata;\n\t\t\t\tthis.pgn_choices_start = 0;\n\t\t\t}\n\t\t} else {\n\t\t\tthis.pgndata = pgndata;\n\t\t\tthis.pgn_choices_start = 0;\n\t\t\tthis.show_pgn_chooser();\n\t\t}\n\t},\n\n\tload_pgn_object: function(o) {\t\t\t\t// Returns true or false - whether this actually succeeded.\n\n\t\tlet root_node;\n\n\t\ttry {\n\t\t\troot_node = LoadPGNRecord(o);\n\t\t} catch (err) {\n\t\t\talert(err);\n\t\t\treturn false;\n\t\t}\n\n\t\tthis.tree.replace_tree(root_node);\n\t\tthis.position_changed(true, true);\n\n\t\treturn true;\n\t},\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\t// Books...\n\n\tunload_book: function() {\n\t\tthis.book = null;\n\t\tfor (let loader of this.loaders) {\n\t\t\tif (loader.type === \"book\") {\n\t\t\t\tloader.shutdown();\n\t\t\t}\n\t\t}\n\t\tthis.send_ack_book();\n\t},\n\n\tload_polyglot_book: function(filename) {\n\n\t\tif (!config.ignore_filesize_limits && FileExceedsGigabyte(filename, 2)) {\n\t\t\talert(messages.file_too_big);\n\t\t\tthis.send_ack_book();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.book = null;\n\t\tthis.send_ack_book();\n\n\t\tfor (let loader of this.loaders) {\n\t\t\tif (loader.type === \"book\") {\n\t\t\t\tloader.shutdown();\n\t\t\t}\n\t\t}\n\n\t\tconsole.log(`Loading Polyglot book: ${filename}`);\n\n\t\tlet loader = NewPolyglotBookLoader(filename, (err, data) => {\n\t\t\tif (!err) {\n\t\t\t\tif (BookSortedTest(data)) {\n\t\t\t\t\tthis.book = data;\n\t\t\t\t\tthis.explorer_objects_cache = null;\n\t\t\t\t\tthis.send_ack_book();\n\t\t\t\t\tthis.set_special_message(`Finished loading book (moves: ${Math.floor(data.length / 16)})`, \"green\");\n\t\t\t\t} else {\n\t\t\t\t\talert(messages.bad_bin_book);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconsole.log(err);\n\t\t\t}\n\t\t});\n\n\t\tthis.loaders.push(loader);\n\t},\n\n\tload_pgn_book: function(filename) {\n\n\t\tif (!config.ignore_filesize_limits && FileExceedsGigabyte(filename, 0.02)) {\n\t\t\talert(messages.pgn_book_too_big);\n\t\t\tthis.send_ack_book();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.book = null;\n\t\tthis.send_ack_book();\n\n\t\tfor (let loader of this.loaders) {\n\t\t\tif (loader.type === \"book\") {\n\t\t\t\tloader.shutdown();\n\t\t\t}\n\t\t}\n\n\t\tconsole.log(`Loading PGN book: ${filename}`);\n\n\t\tlet loader = NewPGNBookLoader(filename, (err, data) => {\n\t\t\tif (!err) {\n\t\t\t\tthis.book = data;\n\t\t\t\tthis.explorer_objects_cache = null;\n\t\t\t\tthis.send_ack_book();\n\t\t\t\tthis.set_special_message(`Finished loading book (moves: ${data.length})`, \"green\");\n\t\t\t} else {\n\t\t\t\tconsole.log(err);\n\t\t\t}\n\t\t});\n\n\t\tthis.loaders.push(loader);\n\t},\n\n\tsend_ack_book: function() {\n\t\tlet msg = false;\n\t\tif (this.book) {\n\t\t\tmsg = this.book instanceof Buffer ? \"polyglot\" : \"pgn\";\n\t\t}\n\t\tipcRenderer.send(\"ack_book\", msg);\n\t},\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\t// Loading from clipboard or fenbox...\n\n\tload_fen_or_pgn_from_string: function(s) {\n\t\tif (typeof s !== \"string\") return;\n\t\ts = s.trim();\n\t\ttry {\n\t\t\tLoadFEN(s);\t\t\t// Used as a test. Throws on any error.\n\t\t\tthis.load_fen(s);\n\t\t} catch (err) {\n\t\t\tthis.load_pgn_from_string(s);\n\t\t}\n\t},\n\n\tload_pgn_from_string: function(s) {\n\n\t\tif (typeof s !== \"string\") {\n\t\t\treturn;\n\t\t}\n\n\t\tlet buf = Buffer.from(s);\n\t\tconsole.log(`Loading PGN from string...`);\n\n\t\tfor (let loader of this.loaders) {\n\t\t\tif (loader.type === \"pgn\") {\n\t\t\t\tloader.shutdown();\n\t\t\t}\n\t\t}\n\n\t\tlet loader = NewFastPGNLoader(buf, (err, pgndata) => {\n\t\t\tif (!err) {\n\t\t\t\tpgndata.source = \"From clipboard\";\n\t\t\t\tthis.handle_loaded_pgndata(pgndata);\n\t\t\t} else {\n\t\t\t\tconsole.log(err);\n\t\t\t}\n\t\t});\n\n\t\tthis.loaders.push(loader);\n\t},\n\n\tload_fen: function(s, abnormal) {\n\n\t\tlet board;\n\n\t\ttry {\n\n\t\t\tboard = LoadFEN(s);\n\n\t\t\t// If the FEN loader thought it looked like normal chess, we must\n\t\t\t// override it if the caller passed the abnormal flag. Note that\n\t\t\t// it is never permissible to go in the opposite direction... if\n\t\t\t// the loader thought it was abnormal, we never say it's normal.\n\n\t\t\tif (abnormal) {\n\t\t\t\tboard.normalchess = false;\n\t\t\t}\n\n\t\t} catch (err) {\n\t\t\talert(err);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.tree.replace_tree(NewRoot(board));\n\t\tthis.position_changed(true, true);\n\t},\n\n\tload_from_fenbox: function(s) {\n\n\t\ts = s.trim();\n\n\t\tif (s === this.tree.node.board.fen(true)) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet abnormal = false;\n\n\t\t// Allow loading a Chess 960 position by giving its ID:\n\n\t\tif (s.length <= 3) {\n\t\t\tlet n = parseInt(s, 10);\n\t\t\tif (Number.isNaN(n) === false && n < 960) {\n\t\t\t\ts = c960_fen(n);\n\t\t\t\tabnormal = true;\n\t\t\t}\n\t\t}\n\n\t\t// Allow loading a fruity start position by giving the pieces:\n\n\t\tif (s.length === 8) {\n\t\t\tlet ok = true;\n\t\t\tfor (let c of s) {\n\t\t\t\tif ([\"K\", \"k\", \"Q\", \"q\", \"R\", \"r\", \"B\", \"b\", \"N\", \"n\"].includes(c) === false) {\n\t\t\t\t\tok = false;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (ok) {\n\t\t\t\ts = `${s.toLowerCase()}/pppppppp/8/8/8/8/PPPPPPPP/${s.toUpperCase()} w KQkq - 0 1`;\n\t\t\t\tabnormal = true;\n\t\t\t}\n\t\t}\n\n\t\tthis.load_fen(s, abnormal);\n\t},\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\t// Mouse and mouseclicks...\n\n\tset_active_square: function(new_point) {\n\n\t\t// We do this immediately so it's snappy and responsive, rather than waiting for the next draw cycle. But we don't\n\t\t// want to actually call draw() here since whatever called this may well end up triggering a draw anyway.\n\n\t\tlet old_point = this.active_square;\n\n\t\tif (old_point) {\n\t\t\tlet td = document.getElementById(\"underlay_\" + old_point.s);\n\t\t\ttd.style[\"background-color\"] = \"transparent\";\n\t\t\tthis.dirty_squares[old_point.x][old_point.y] = 0;\t\t// Lame. This is the constant for EMPTY.\n\t\t}\n\n\t\tif (new_point) {\n\t\t\tlet td = document.getElementById(\"underlay_\" + new_point.s);\n\t\t\ttd.style[\"background-color\"] = config.active_square;\n\t\t\tthis.dirty_squares[new_point.x][new_point.y] = 2;\t\t// Lame. This is the constant for ACTIVE.\n\t\t}\n\n\t\tthis.active_square = new_point ? new_point : null;\n\t},\n\n\tboardfriends_click: function(event) {\n\n\t\tlet s = EventPathString(event, \"overlay_\");\n\t\tlet p = Point(s);\n\n\t\tif (!p) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.hide_promotiontable();\t\t// Just in case it's up.\n\n\t\tlet ocm = this.info_handler.one_click_moves[p.x][p.y];\n\t\tlet board = this.tree.node.board;\n\n\t\tif (!this.active_square && ocm && board.colour(p) !== board.active) {\t\t// Note that we test colour difference\n\t\t\tthis.set_active_square(null);\t\t\t\t\t\t\t\t\t\t\t// to disallow castling moves from OCM\n\t\t\tthis.move(ocm);\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// since the dest is the rook (which\n\t\t\treturn;\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// the user might want to click on.)\n\t\t}\n\n\t\tif (this.active_square) {\n\t\t\tlet move = this.active_square.s + p.s;\t\t// e.g. \"e2e4\" - note promotion char is handled by hub.move()\n\t\t\tthis.set_active_square(null);\n\t\t\tlet ok = this.move(move);\n\t\t\tif (!ok && config.click_spotlight) {\t\t// No need to worry about spotlight arrows if the move actually happened\n\t\t\t\tthis.draw_canvas_arrows();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// So there is no active_square... create one?\n\n\t\tif (board.active === \"w\" && board.is_white(p)) {\n\t\t\tthis.set_active_square(p);\n\t\t\tif (config.click_spotlight) {\n\t\t\t\tthis.draw_canvas_arrows();\n\t\t\t}\n\t\t}\n\t\tif (board.active === \"b\" && board.is_black(p)) {\n\t\t\tthis.set_active_square(p);\n\t\t\tif (config.click_spotlight) {\n\t\t\t\tthis.draw_canvas_arrows();\n\t\t\t}\n\t\t}\n\t},\n\n\tinfobox_click: function(event) {\n\n\t\tif (this.info_handler.clickers_are_valid_for_node(this.tree.node) === false) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet n = EventPathN(event, \"infobox_\");\n\t\tlet moves = this.info_handler.moves_from_click_n(n);\n\n\t\tif (!moves || moves.length === 0) {\t\t\t\t// We do assume length > 0 below.\n\t\t\tthis.maybe_searchmove_click(event);\n\t\t\treturn;\n\t\t}\n\n\t\t// So it appears to be a real click in the infobox.........................................\n\t\t// I doubt moves can be an illegal sequence now but this check is not too expensive here...\n\n\t\tlet illegal_reason = this.tree.node.board.sequence_illegal(moves);\n\t\tif (illegal_reason) {\n\t\t\tconsole.log(\"infobox_click(): \" + illegal_reason);\n\t\t\treturn;\n\t\t}\n\n\t\tswitch (config.pv_click_event) {\n\n\t\tcase 0:\n\t\t\treturn;\n\n\t\tcase 1:\n\t\t\tthis.tree.make_move_sequence(moves);\n\t\t\tthis.position_changed(false, true);\n\t\t\treturn;\n\n\t\tcase 2:\n\t\t\tthis.tree.add_move_sequence(moves);\n\t\t\treturn;\n\t\t}\n\t},\n\n\tmaybe_searchmove_click: function(event) {\n\n\t\tlet sm = EventPathString(event, \"searchmove_\");\n\t\tif (typeof sm !== \"string\" || (sm.length < 4 || sm.length > 5)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.tree.node.searchmoves.includes(sm)) {\n\t\t\tthis.tree.node.searchmoves = this.tree.node.searchmoves.filter(move => move !== sm);\n\t\t} else {\n\t\t\tthis.tree.node.searchmoves.push(sm);\n\t\t}\n\n\t\tthis.tree.node.searchmoves.sort();\n\t\tthis.handle_search_params_change();\n\t},\n\n\tmovelist_click: function(event) {\n\t\tif (this.tree.handle_click(event)) {\n\t\t\tthis.position_changed(false, true);\n\t\t}\n\t},\n\n\twinrate_click: function(event) {\n\n\t\tlet node = this.grapher.node_from_click(this.tree.node, event);\n\n\t\tif (!node) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.tree.set_node(node)) {\n\t\t\tthis.position_changed(false, true);\n\t\t}\n\t},\n\n\tstatusbox_click: function(event) {\n\n\t\tif (EventPathString(event, \"gobutton\")) {\n\t\t\tthis.set_behaviour(\"analysis_free\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (EventPathString(event, \"haltbutton\")) {\n\t\t\tthis.set_behaviour(\"halt\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (EventPathString(event, \"lock_return\")) {\n\t\t\tthis.return_to_lock();\n\t\t\treturn;\n\t\t}\n\n\t\tif (EventPathString(event, \"loadabort\")) {\n\t\t\tfor (let loader of this.loaders) {\n\t\t\t\tloader.shutdown();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t},\n\n\tfullbox_click: function(event) {\n\n\t\tlet n;\n\n\t\t// Config item editor...\n\n\t\tif (EventPathString(event, \"config_item_save\") !== null) {\n\t\t\tif (event.button !== 2) {\n\t\t\t\tthis.apply_fullbox_config_item_edit();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (EventPathString(event, \"config_item_cancel\") !== null) {\n\t\t\tthis.hide_fullbox();\n\t\t\treturn;\n\t\t}\n\n\t\tif (EventPathString(event, \"config_item_web_link\") !== null) {\n\t\t\tipcRenderer.send(\"web_link\", this.fullbox_web_link);\n\t\t\treturn;\n\t\t}\n\n\t\t// PGN chooser...\n\n\t\tn = EventPathN(event, \"pgn_chooser_\");\n\t\tif (typeof n === \"number\") {\n\t\t\tif (this.pgndata && n >= 0 && n < this.pgndata.count()) {\n\t\t\t\tthis.load_pgn_object(this.pgndata.getrecord(n));\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// PGN chooser, prev / next page buttons...\n\n\t\tn = EventPathN(event, \"pgn_index_chooser_\");\n\t\tif (typeof n !== \"number\") {\n\t\t\tn = EventPathN(event, \"pgn_index_b_chooser_\");\n\t\t}\n\t\tif (typeof n === \"number\") {\n\t\t\tthis.pgn_choices_start = n;\n\t\t\tthis.show_pgn_chooser();\n\t\t\treturn;\n\t\t}\n\n\t\t// Engine chooser...\n\n\t\tn = EventPathN(event, \"engine_chooser_\");\n\t\tif (typeof n === \"number\") {\n\t\t\tlet filepath = this.engine_choices[n];\t\t\t// The array is remade every time the fast engine chooser is displayed\n\t\t\tif (filepath) {\n\t\t\t\tif (event.button === 2) {\t\t\t\t\t// Right-click\n\t\t\t\t\tif (this.engine.filepath !== filepath) {\n\t\t\t\t\t\tdelete engineconfig[filepath];\n\t\t\t\t\t\tthis.show_fast_engine_chooser();\n\t\t\t\t\t}\n\t\t\t\t} else {\t\t\t\t\t\t\t\t\t// Any other click\n\t\t\t\t\tthis.switch_engine(filepath);\n\t\t\t\t\tthis.hide_fullbox();\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t},\n\n\tpromotiontable_click: function(event) {\n\t\tlet s = EventPathString(event, \"promotion_chooser_\");\n\t\tthis.hide_promotiontable();\n\t\tthis.move(s);\n\t},\n\n\thandle_file_drop: function(event) {\n\t\tif (event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0] && get_path_for_file(event.dataTransfer.files[0])) {\n\t\t\tthis.open(get_path_for_file(event.dataTransfer.files[0]));\n\t\t\treturn;\n\t\t}\n\t},\n\n\tmouse_point: function() {\n\t\tlet overlist = document.querySelectorAll(\":hover\");\n\t\tfor (let item of overlist) {\n\t\t\tif (typeof item.id === \"string\" && item.id.startsWith(\"overlay_\")) {\n\t\t\t\treturn Point(item.id.slice(8));\t\t// Possibly null\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t},\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\t// Settings (but NOT including UCI options)...\n\n\ttoggle: function(option) {\n\n\t\t// Cases with their own handler...\n\n\t\tif (option === \"flip\") {\n\t\t\tthis.toggle_flip();\n\t\t\treturn;\n\t\t}\n\n\t\t// Normal cases...\n\n\t\tconfig[option] = !config[option];\n\n\t\t// Cases that have additional actions after...\n\n\t\tif (option === \"book_explorer\") {\n\t\t\tconfig.lichess_explorer = false;\n\t\t\tthis.explorer_objects_cache = null;\n\t\t}\n\t\tif (option === \"lichess_explorer\") {\n\t\t\tconfig.book_explorer = false;\n\t\t\tthis.explorer_objects_cache = null;\n\t\t}\n\t\tif (option === \"look_past_25\") {\n\t\t\tif (config.look_past_25 && this.tree.node.board.fullmove > 25) {\n\t\t\t\tthis.looker.add_to_queue(this.tree.node.board);\n\t\t\t}\n\t\t}\n\t\tif (option === \"searchmoves_buttons\") {\n\t\t\tthis.tree.node.searchmoves = [];\t\t// This is reasonable regardless of which way the toggle went.\n\t\t\tthis.handle_search_params_change();\n\t\t}\n\n\t\tthis.info_handler.must_draw_infobox();\n\t\tthis.draw();\n\t},\n\n\ttoggle_flip: function() {\t\t\t\t\t\t// config.flip should not be directly set, call this function instead.\n\n\t\tconfig.flip = !config.flip;\n\n\t\tfor (let x = 0; x < 8; x++) {\n\t\t\tfor (let y = 0; y < 4; y++) {\n\n\t\t\t\tlet first = document.getElementById(`overlay_${S(x, y)}`);\n\t\t\t\tlet second = document.getElementById(`overlay_${S(7 - x, 7 - y)}`);\n\t\t\t\tSwapElements(first, second);\n\n\t\t\t\tfirst = document.getElementById(`underlay_${S(x, y)}`);\n\t\t\t\tsecond = document.getElementById(`underlay_${S(7 - x, 7 - y)}`);\n\t\t\t\tSwapElements(first, second);\n\t\t\t}\n\t\t}\n\n\t\tthis.draw();\t\t\t\t\t\t\t\t// For the canvas stuff.\n\t},\n\n\tset_arrow_filter: function(type, value) {\n\t\tconfig.arrow_filter_type = type;\n\t\tconfig.arrow_filter_value = value;\n\t\tthis.draw();\n\t},\n\n\tset_looker_api: function(value) {\n\n\t\tif (config.looker_api === value) {\n\t\t\treturn;\n\t\t}\n\n\t\tconfig.looker_api = value;\n\n\t\tif (value && value.includes(\"lichess\") && !config.lichess_token) {\n\t\t\talert(messages.lichess_token_needed);\n\t\t}\n\n\t\tthis.looker.clear_queue();\n\n\t\tif (value) {\n\t\t\tthis.looker.add_to_queue(this.tree.node.board);\n\t\t}\n\n\t\tthis.explorer_objects_cache = null;\n\t},\n\n\tinvert_searchmoves: function() {\n\n\t\tif (!config.searchmoves_buttons || Array.isArray(this.tree.node.searchmoves) === false) {\n\t\t\treturn;\n\t\t}\n\n\t\t// It's no disaster if the result is wrong somehow, because\n\t\t// searchmoves are validated before being sent to Leela.\n\n\t\tlet moveset = Object.create(null);\n\n\t\tfor (let move of Object.keys(this.tree.node.table.moveinfo)) {\n\t\t\tmoveset[move] = true;\n\t\t}\n\n\t\tfor (let move of this.tree.node.searchmoves) {\n\t\t\tdelete moveset[move];\n\t\t}\n\n\t\tthis.tree.node.searchmoves = Object.keys(moveset);\n\t\tthis.tree.node.searchmoves.sort();\n\t\tthis.handle_search_params_change();\n\t},\n\n\tclear_searchmoves: function() {\n\t\tthis.tree.node.searchmoves = [];\n\t\tthis.handle_search_params_change();\n\t},\n\n\tset_pgn_font_size: function(n) {\n\t\tmovelist.style[\"font-size\"] = n.toString() + \"px\";\n\t\tfenbox.style[\"font-size\"] = n.toString() + \"px\";\n\t\tconfig.pgn_font_size = n;\n\t\tconfig.fen_font_size = n;\n\t},\n\n\tset_arrow_size: function(width, radius, fontsize) {\n\t\tconfig.arrow_width = width;\n\t\tconfig.arrowhead_radius = radius;\n\t\tconfig.board_font = `${fontsize}px Arial`;\n\t},\n\n\tset_info_font_size: function(n) {\n\t\tinfobox.style[\"font-size\"] = n.toString() + \"px\";\n\t\tstatusbox.style[\"font-size\"] = n.toString() + \"px\";\n\t\tfullbox.style[\"font-size\"] = n.toString() + \"px\";\n\t\tconfig.info_font_size = n;\n\t\tthis.rebuild_sizes();\n\t},\n\n\tset_graph_height: function(sz) {\n\t\tconfig.graph_height = sz;\n\t\tthis.rebuild_sizes();\n\t\tthis.grapher.draw(this.tree.node, true);\n\t},\n\n\tset_board_size: function(sz) {\n\t\tconfig.square_size = Math.floor(sz / 8);\n\t\tconfig.board_size = config.square_size * 8;\n\t\tthis.rebuild_sizes();\n\t},\n\n\tchange_piece_set: function(directory) {\n\t\tif (directory) {\n\t\t\tif (images.validate_folder(directory) === false) {\n\t\t\t\talert(messages.invalid_pieces_directory);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\timages.load_from(directory);\n\t\t} else {\n\t\t\tdirectory = null;\n\t\t\timages.load_from(path.join(__dirname, \"pieces\"));\n\t\t}\n\t\tthis.friendly_draws = New2DArray(8, 8, null);\n\t\tthis.enemy_draws = New2DArray(8, 8, null);\n\t\tconfig[\"override_piece_directory\"] = directory;\n\t},\n\n\tchange_background: function(file, config_save = true) {\n\t\tif (file && fs.existsSync(file)) {\n\t\t\tlet img = new Image();\n\t\t\timg.src = file;\t\t\t// Automagically gets converted to \"file:///C:/foo/bar/whatever.png\"\n\t\t\tboardsquares.style[\"background-image\"] = `url(\"${img.src}\")`;\n\t\t} else {\n\t\t\tboardsquares.style[\"background-image\"] = background(config.light_square, config.dark_square, config.square_size);\n\t\t}\n\t\tif (config_save) {\n\t\t\tconfig.override_board = file;\n\t\t}\n\t},\n\n\trebuild_sizes: function() {\n\n\t\t// This assumes everything already exists.\n\t\t// Derived from the longer version in start.js, which it does not replace.\n\n\t\tboardfriends.width = canvas.width = boardsquares.width = config.board_size;\n\t\tboardfriends.height = canvas.height = boardsquares.height = config.board_size;\n\n\t\trightgridder.style[\"height\"] = `${canvas.height}px`;\n\n\t\tfor (let y = 0; y < 8; y++) {\n\t\t\tfor (let x = 0; x < 8; x++) {\n\t\t\t\tlet td1 = document.getElementById(\"underlay_\" + S(x, y));\n\t\t\t\tlet td2 = document.getElementById(\"overlay_\" + S(x, y));\n\t\t\t\ttd1.width = td2.width = config.square_size;\n\t\t\t\ttd1.height = td2.height = config.square_size;\n\t\t\t}\n\t\t}\n\n\t\tif (config.graph_height <= 0) {\n\t\t\tgraph.style.display = \"none\";\n\t\t} else {\n\t\t\tgraph.style.height = config.graph_height.toString() + \"px\";\n\t\t\tgraph.style.display = \"\";\n\t\t}\n\n\t\tpromotiontable.style.left = (boardsquares.offsetLeft + config.square_size * 2).toString() + \"px\";\n\t\tpromotiontable.style.top = (boardsquares.offsetTop + config.square_size * 3.5).toString() + \"px\";\n\t\tpromotiontable.style[\"background-color\"] = config.active_square;\n\n\t\tthis.draw();\n\t},\n\n\tsave_window_size: function() {\n\t\tlet zoomfactor = parseFloat(querystring.parse(global.location.search.slice(1))[\"zoomfactor\"]);\n\t\tconfig.width = Math.floor(window.innerWidth * zoomfactor);\n\t\tconfig.height = Math.floor(window.innerHeight * zoomfactor);\n\t},\n\n\tset_logfile: function(filename) {\t\t\t\t// Arg can be null to stop logging.\n\t\tconfig.logfile = null;\n\t\tLog(\"Stopping log.\");\t\t\t\t\t\t// This will do nothing, but calling Log() forces it to close any open file.\n\t\tconfig.logfile = filename;\n\t\tthis.send_ack_logfile();\n\t},\n\n\tset_language: function(s) {\n\t\tconfig.language = s;\n\t\talert(translate.t(\"RESTART_REQUIRED\", s));\n\t},\n\n\tsend_ack_logfile: function() {\n\t\tipcRenderer.send(\"ack_logfile\", config.logfile);\n\t},\n\n\tsave_config: function() {\n\t\tif (!load_err1) {\t\t\t\t\t\t\t// If the config file was broken, never save to it, let the user fix it.\n\t\t\tconfig_io.save(config);\n\t\t}\n\t},\n\n\tsave_engineconfig: function() {\n\t\tif (!load_err2) {\t\t\t\t\t\t\t// If the config file was broken, never save to it, let the user fix it.\n\t\t\tengineconfig_io.save(engineconfig);\n\t\t}\n\t},\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\t// Misc...\n\n\tquit: function() {\n\t\tthis.engine.shutdown();\n\t\tthis.save_config();\n\t\tthis.save_engineconfig();\n\t\tipcRenderer.send(\"terminate\");\n\t},\n\n\tset_special_message: function(s, css_class, duration) {\n\t\tthis.status_handler.set_special_message(s, css_class, duration);\n\t\tthis.draw_statusbox();\n\t},\n\n\tinfobox_to_clipboard: function() {\n\t\tlet s = infobox.innerText;\n\t\ts = ReplaceAll(s, `${config.focus_on_text} `, \"\");\n\t\ts = ReplaceAll(s, `${config.focus_off_text} `, \"\");\n\t\tclipboard.writeText(this.tree.node.board.fen(true) + \"\\n\" + statusbox.innerText + \"\\n\\n\" + s);\n\t},\n\n\tsend_title: function() {\n\t\tlet title = \"Nibbler\";\n\t\tlet root = this.tree.root;\n\t\tif (root.tags && root.tags.White && root.tags.White !== \"White\" && root.tags.Black && root.tags.Black !== \"Black\") {\n\t\t\ttitle += `: ${root.tags.White} - ${root.tags.Black}`;\n\t\t}\n\t\tipcRenderer.send(\"set_title\", UnsafeStringHTML(title));\t\t// Fix any &amp; and that sort of thing in the names.\n\t},\n\n\tgenerate_simple_book: function() {\t\t// For https://github.com/rooklift/lc0_lichess\n\t\tlet histories = this.tree.root.end_nodes().map(end => end.history_old_format());\n\t\tlet text_lines = histories.map(h => \"\\t\\\"\" + h.join(\" \") + \"\\\"\");\n\t\tconsole.log(\"[\\n\" + text_lines.join(\",\\n\") + \"\\n]\");\n\t},\n\n\trun_script: function(filename) {\n\n\t\tconst disallowed = [\"position\", \"go\", \"stop\", \"ponderhit\", \"quit\"];\n\n\t\tlet buf;\n\t\ttry {\n\t\t\tbuf = fs.readFileSync(filename);\n\t\t} catch (err) {\n\t\t\talert(err);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.set_behaviour(\"halt\");\n\n\t\tlet s = buf.toString();\n\t\tlet lines = s.split(\"\\n\").map(z => z.trim()).filter(z => z !== \"\");\n\n\t\tif (!config.allow_arbitrary_scripts) {\n\t\t\tfor (let line of lines) {\n\t\t\t\tfor (let d of disallowed) {\n\t\t\t\t\tif (line.startsWith(d)) {\n\t\t\t\t\t\tthis.set_special_message(`${messages.invalid_script}`, \"yellow\");\n\t\t\t\t\t\tconsole.log(`Refused to run script: ${filename}`);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconsole.log(`Running script: ${filename}`);\n\n\t\tfor (let line of lines) {\n\t\t\tif (config.allow_arbitrary_scripts) {\n\t\t\t\tthis.engine.send(line, true);\t\t\t// Force mode, so setoptions don't get held back\n\t\t\t} else {\n\t\t\t\tthis.engine.send(line);\n\t\t\t}\n\t\t\tconsole.log(line);\n\t\t}\n\t\tthis.set_special_message(`${path.basename(filename)}: Sent ${lines.length} lines`, \"blue\");\n\t},\n\n\tfire_gc: function() {\n\t\tif (!global || !global.gc) {\n\t\t\talert(\"Unable.\");\n\t\t} else {\n\t\t\tglobal.gc();\n\t\t}\n\t},\n\n\tlog_ram: function() {\n\t\tconsole.log(`RAM after ${Math.floor(performance.now() / 1000)} seconds:`);\n\t\tfor (let foo of Object.entries(process.memoryUsage())) {\n\t\t\tlet type = foo[0] + \" \".repeat(12 - foo[0].length);\n\t\t\tlet mb = foo[1] / (1024 * 1024);\n\t\t\tlet mb_rounded = Math.floor(mb * 1000) / 1000;\t\t\t// 3 d.p.\n\t\t\tconsole.log(type, \"(MB)\", mb_rounded);\n\t\t}\n\t},\n\n\tconsole: function(...args) {\n\t\tconsole.log(...args);\n\t},\n\n\ttoggle_debug_css: function() {\n\t\tlet ss = document.styleSheets[0];\n\t\tlet i = 0;\n\t\tfor (let rule of Object.values(ss.cssRules)) {\n\t\t\tif (rule.selectorText && rule.selectorText === \"*\") {\n\t\t\t\tss.deleteRule(i);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ti++;\n\t\t}\n\t\tss.insertRule(\"* {outline: 1px dotted red;}\");\n\t},\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\t// Fullbox (our full size info div)...\n\n\tshow_pgn_chooser: function() {\n\n\t\tconst interval = 100;\n\n\t\tif (!this.pgndata || this.pgndata.count() === 0) {\n\t\t\tfullbox_content.innerHTML = `<span class=\"green\">No PGN loaded</span>`;\n\t\t\tthis.show_fullbox();\n\t\t\treturn;\n\t\t}\n\n\t\tlet count = this.pgndata.count();\n\n\t\tif (this.pgn_choices_start >= count) {\n\t\t\tthis.pgn_choices_start = Math.floor((count - 1) / interval) * interval;\n\t\t}\n\t\tif (this.pgn_choices_start < 0) {\t\t// The most important thing, values < 0 will crash.\n\t\t\tthis.pgn_choices_start = 0;\n\t\t}\n\n\t\tlet lines = [];\n\n\t\tlet max_ordinal_length = count.toString().length;\n\n\t\tlet prevnextfoo = (count > interval) ?\t\t\t// All these values get fixed on function entry if they're out-of-bounds. ids should be unique.\n\t\t\t\t`<span id=\"pgn_index_chooser_-99999999\">Start </span>|` +\n\t\t\t\t`<span id=\"pgn_index_chooser_${this.pgn_choices_start - 10000}\"> <<<< </span>|` +\n\t\t\t\t`<span id=\"pgn_index_chooser_${this.pgn_choices_start - 1000}\"> <<< </span>|` +\n\t\t\t\t`<span id=\"pgn_index_chooser_${this.pgn_choices_start - 100}\"> << </span>|` +\n\t\t\t\t`<span id=\"pgn_index_chooser_${this.pgn_choices_start + 100}\"> >> </span>|` +\n\t\t\t\t`<span id=\"pgn_index_chooser_${this.pgn_choices_start + 1000}\"> >>> </span>|` +\n\t\t\t\t`<span id=\"pgn_index_chooser_${this.pgn_choices_start + 10000}\"> >>>> </span>|` +\n\t\t\t\t`<span id=\"pgn_index_chooser_99999999\"> End (${count}) </span>` +\n\t\t\t\t`&mdash; <span class=\"green\">${this.pgndata.source}</span>`\n\t\t\t:\n\t\t\t\t`<span class=\"green\">${this.pgndata.source}</span>`;\n\n\t\tlines.push(prevnextfoo);\n\t\tlines.push(\"<ul>\");\n\t\tfor (let n = this.pgn_choices_start; n < this.pgn_choices_start + interval; n++) {\n\n\t\t\tif (n < count) {\n\n\t\t\t\tlet pad = n < 10 ? \" \" : \"\";\n\n\t\t\t\tlet p = this.pgndata.getrecord(n);\n\n\t\t\t\tlet s;\n\n\t\t\t\tif (p.tags.Result === \"1-0\") {\n\t\t\t\t\ts = `${pad}${n}. <span class=\"blue\">${p.tags.White || \"Unknown\"}</span> - ${p.tags.Black || \"Unknown\"}`;\n\t\t\t\t} else if (p.tags.Result === \"0-1\") {\n\t\t\t\t\ts = `${pad}${n}. ${p.tags.White || \"Unknown\"} - <span class=\"blue\">${p.tags.Black || \"Unknown\"}</span>`;\n\t\t\t\t} else {\n\t\t\t\t\ts = `${pad}${n}. ${p.tags.White || \"Unknown\"} - ${p.tags.Black || \"Unknown\"}`;\n\t\t\t\t}\n\n\t\t\t\tif (p.tags.Opening && p.tags.Opening !== \"?\") {\n\t\t\t\t\ts += `  <span class=\"gray\">(${p.tags.Opening})</span>`;\n\t\t\t\t} else if (p.tags.Variant && p.tags.Variant.toLowerCase() !== \"standard\" && p.tags.Variant.toLowerCase() !== \"from position\") {\n\t\t\t\t\ts += `  <span class=\"gray\">(${p.tags.Variant})</span>`;\n\t\t\t\t}\n\n\t\t\t\tlines.push(`<li class=\"pgnchooser\" id=\"pgn_chooser_${n}\">${s}</li>`);\n\n\t\t\t} else if (count > interval) {\t\t// Pad the chooser with blank lines so the buttons at the bottom behave nicely. This is stupid though.\n\n\t\t\t\tlines.push(`<li><span class=\"darkgray\">${n}.${n === count ? \" [end]\" : \"\"}</li>`);\n\n\t\t\t}\n\t\t}\n\t\tlines.push(\"</ul>\");\n\t\tif (count > interval) {\n\t\t\tprevnextfoo = ReplaceAll(prevnextfoo, `span id=\"pgn_index_chooser_`, `span id=\"pgn_index_b_chooser_`);\t\t// id should be unique per element.\n\t\t\tlines.push(prevnextfoo);\n\t\t}\n\n\t\tfullbox_content.innerHTML = lines.join(\"\");\n\t\tthis.show_fullbox();\n\t},\n\n\tshow_sent_options: function() {\n\n\t\tlet lines = [];\n\n\t\tlines.push(`<span class=\"yellow\">${this.engine.filepath || \"No engine loaded\"}</span>`);\n\t\tlines.push(\"\");\n\n\t\tfor (let name of Object.keys(this.engine.sent_options)) {\n\t\t\tlines.push(`${name}<br>    <span class=\"green\">${this.engine.sent_options[name]}</span>`);\n\t\t}\n\n\t\tfullbox_content.innerHTML = lines.join(\"<br>\");\n\t\tthis.show_fullbox();\n\t},\n\n\tshow_error_log: function() {\n\t\tfullbox_content.innerHTML = this.info_handler.error_log;\n\t\tthis.show_fullbox();\n\t},\n\n\tparse_fullbox_config_item_value: function(item_name, raw) {\n\n\t\traw = raw.trim();\n\n\t\tlet defaults_has_item = Object.prototype.hasOwnProperty.call(config_io.defaults, item_name);\n\t\tlet expected = defaults_has_item ? config_io.defaults[item_name] : config[item_name];\n\n\t\tif (Array.isArray(expected)) {\n\t\t\ttry {\n\t\t\t\tlet parsed = JSON.parse(raw);\n\t\t\t\tif (Array.isArray(parsed)) {\n\t\t\t\t\treturn [parsed, null];\n\t\t\t\t}\n\t\t\t\treturn [null, \"Expected JSON array\"];\n\t\t\t} catch (err) {\n\t\t\t\treturn [null, \"Expected JSON array\"];\n\t\t\t}\n\t\t}\n\n\t\tif (typeof expected === \"string\") {\n\t\t\treturn [raw, null];\n\t\t}\n\n\t\tif (typeof expected === \"number\") {\n\t\t\tlet n = Number(raw);\n\t\t\tif (Number.isNaN(n)) {\n\t\t\t\treturn [null, \"Expected number\"];\n\t\t\t}\n\t\t\treturn [n, null];\n\t\t}\n\n\t\tif (typeof expected === \"boolean\") {\n\t\t\tlet s = raw.toLowerCase();\n\t\t\tif (s === \"true\" || s === \"1\" || s === \"yes\" || s === \"on\") {\n\t\t\t\treturn [true, null];\n\t\t\t}\n\t\t\tif (s === \"false\" || s === \"0\" || s === \"no\" || s === \"off\") {\n\t\t\t\treturn [false, null];\n\t\t\t}\n\t\t\treturn [null, `Expected boolean (true / false)`];\n\t\t}\n\n\t\tif (expected === null) {\t\t// Null defaults are usually nullable strings.\n\t\t\tif (raw.toLowerCase() === \"null\") {\n\t\t\t\treturn [null, null];\n\t\t\t}\n\t\t\treturn [raw, null];\n\t\t}\n\n\t\tif (typeof expected === \"object\") {\n\t\t\ttry {\n\t\t\t\tlet parsed = JSON.parse(raw);\n\t\t\t\tif (parsed !== null && typeof parsed === \"object\" && Array.isArray(parsed) === false) {\n\t\t\t\t\treturn [parsed, null];\n\t\t\t\t}\n\t\t\t\treturn [null, \"Expected JSON object\"];\n\t\t\t} catch (err) {\n\t\t\t\treturn [null, \"Expected JSON object\"];\n\t\t\t}\n\t\t}\n\n\t\treturn [raw, null];\n\t},\n\n\tapply_fullbox_config_item_edit: function() {\n\n\t\tif (typeof this.fullbox_config_item !== \"string\") {\n\t\t\treturn;\n\t\t}\n\n\t\tlet textarea = document.getElementById(\"config_item_input\");\n\t\tif (!textarea) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet [value, err] = this.parse_fullbox_config_item_value(this.fullbox_config_item, textarea.value);\n\n\t\tif (err) {\n\t\t\tlet errdiv = document.getElementById(\"config_item_error\");\n\t\t\tif (errdiv) {\n\t\t\t\terrdiv.innerHTML = `<span class=\"red\">${SafeStringHTML(err)}</span>`;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconfig[this.fullbox_config_item] = value;\n\t\tthis.info_handler.must_draw_infobox();\n\t\tthis.draw();\n\t\tthis.hide_fullbox();\n\t},\n\n\tshow_config_item_editor: function(item_name, web_link = null, web_text = \"See web\") {\n\n\t\tif (typeof item_name !== \"string\" || item_name === \"\") {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!Object.prototype.hasOwnProperty.call(config, item_name)) {\n\t\t\tfullbox_content.innerHTML =\n\t\t\t\t`<span class=\"red\">Unknown config item: ${SafeStringHTML(item_name)}</span><br><br>` +\n\t\t\t\t`<span id=\"config_item_cancel\" class=\"blue\">Cancel</span>`;\n\t\t\tthis.show_fullbox();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.fullbox_config_item = item_name;\n\t\tthis.fullbox_web_link = web_link;\n\n\t\tlet current = config[item_name];\n\t\tlet expected = Object.prototype.hasOwnProperty.call(config_io.defaults, item_name) ? config_io.defaults[item_name] : current;\n\n\t\tlet expected_type = Array.isArray(expected) ? \"array\" : (expected === null ? \"string or null\" : typeof expected);\n\t\tlet current_text = SafeStringHTML(stringify(current));\n\n\t\tlet initial_input;\n\t\tif (typeof current === \"string\") {\n\t\t\tinitial_input = current;\n\t\t} else if (current !== null && typeof current === \"object\") {\n\t\t\ttry {\n\t\t\t\tinitial_input = JSON.stringify(current, null, \"\\t\");\n\t\t\t} catch (err) {\n\t\t\t\tinitial_input = stringify(current);\n\t\t\t}\n\t\t} else {\n\t\t\tinitial_input = stringify(current);\n\t\t}\n\n\t\tlet lines = [];\n\t\tlines.push(`<div class=\"infoline\">Editing: <span class=\"green\">config.${SafeStringHTML(item_name)}</span></div>`);\n\t\tlines.push(`<div class=\"infoline\">Current: <span class=\"green\">${current_text}</span></div>`);\n\t\tif (web_link) {\n\t\t\tlines.push(`<div>${SafeStringHTML(web_text)}:</div>`);\n\t\t\tlines.push(`<div class=\"infoline\"><span id=\"config_item_web_link\" class=\"blue\">${SafeStringHTML(web_link)}</span></div>`);\n\t\t}\n\t\tlines.push(`<textarea id=\"config_item_input\" placeholder=\"Set the value here, then click 'Save'\" rows=\"6\"></textarea>`);\n\t\tlines.push(`<div class=\"infoline\"><span id=\"config_item_save\" class=\"blue\">Save</span> | <span id=\"config_item_cancel\" class=\"red\">Cancel</span></div>`);\n\t\tlines.push(`<div id=\"config_item_error\" class=\"infoline\">&nbsp;</div>`);\n\n\t\tfullbox_content.innerHTML = lines.join(\"\");\n\t\tthis.show_fullbox();\n\n\t\tlet textarea = document.getElementById(\"config_item_input\");\n\t\tif (textarea) {\n\t\t\ttextarea.value = initial_input;\n\t\t\ttextarea.focus();\n\t\t\ttextarea.select();\n\t\t}\n\t},\n\n\tshow_fast_engine_chooser: function() {\n\n\t\tthis.engine_choices = [];\n\n\t\tlet divs = [];\n\n\t\tfor (let filepath of Object.keys(engineconfig)) {\n\n\t\t\tif (filepath === \"\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tlet ac = (this.engine.filepath === filepath) ? ` <span class=\"blue\">(active)</span>` : \"\";\n\n\t\t\tdivs.push(`<div class=\"enginechooser\" id=\"engine_chooser_${this.engine_choices.length}\"><span class=\"gray\">${path.dirname(filepath)}</span>` +\n\t\t\t\t\t  `<br>    ${path.basename(filepath)}${ac}</div>`);\n\n\t\t\tthis.engine_choices.push(filepath);\t\t\t\t\t// After the above calc using length\n\t\t}\n\n\t\tif (divs.length === 0) {\n\t\t\tdivs.push(`<div>No engines known yet.</div>`);\n\t\t} else {\n\t\t\tdivs.unshift(`<div class=\"infoline green\">Click to load.<br>Right-click to remove from ${engineconfig_io.filename}.</div>`);\n\t\t}\n\n\t\tfullbox_content.innerHTML = divs.join(\"\");\n\t\tthis.show_fullbox();\n\t},\n\n\t// ---------------------------------------------------------------------------------------------------------------------\n\t// Showing and hiding things...\n\n\tshow_promotiontable: function(partial_move) {\n\n\t\tlet pieces = this.tree.node.board.active === \"w\" ? [\"Q\", \"R\", \"B\", \"N\"] : [\"q\", \"r\", \"b\", \"n\"];\n\n\t\tfor (let piece of pieces) {\n\t\t\tlet td = document.getElementsByClassName(\"promotion_\" + piece.toLowerCase())[0];\t\t// Our 4 TDs each have a unique class.\n\t\t\ttd.id = \"promotion_chooser_\" + partial_move + piece.toLowerCase();\t\t\t\t\t\t// We store the actual move in the id.\n\t\t\ttd.width = config.square_size;\n\t\t\ttd.height = config.square_size;\n\t\t\ttd.style[\"background-image\"] = images[piece].string_for_bg_style;\n\t\t}\n\n\t\tpromotiontable.style.display = \"block\";\n\t},\n\n\thide_promotiontable: function() {\n\t\tpromotiontable.style.display = \"none\";\n\t},\n\n\tshow_fullbox: function() {\n\t\tthis.set_behaviour(\"halt\");\n\t\tthis.hide_promotiontable();\n\t\tfullbox.style.display = \"block\";\n\t},\n\n\thide_fullbox: function() {\n\t\tthis.fullbox_config_item = null;\n\t\tthis.fullbox_web_link = null;\n\t\tfullbox.style.display = \"none\";\n\t},\n\n\tescape: function() {\t\t\t\t\t// Set things into a clean state.\n\t\tthis.hide_fullbox();\n\t\tthis.hide_promotiontable();\n\t\tif (this.active_square) {\n\t\t\tthis.set_active_square(null);\n\t\t\tif (config.click_spotlight) {\n\t\t\t\tthis.draw_canvas_arrows();\n\t\t\t}\n\t\t}\n\t},\n};\n"
  },
  {
    "path": "files/src/renderer/97_drag.js",
    "content": "\"use strict\"\n\n// Drag improvements submitted by ObnubiladO in PR #291\n// Back in the day, something like this would be referenced in hub, but nowadays I leave such things in global space.\n\nconst drag_handler = {\n\n\tdrag_state: null,\n\n\tcancel_drag: function() {\t\t\t\t\t\t\t// Must also be called after a successful drag. (Maybe misnamed, hmm?)\n\n\t\tif (!this.drag_state) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.drag_state.floating) {\t\t\t\t\t// Drag is in progress...\n\t\t\thub.set_active_square(null);\n\t\t\tthis.drag_state.floating.remove();\n\t\t\tthis.drag_state.floating = null;\t\t\t// Not strictly needed.\n\t\t}\n\n\t\tthis.drag_state.from_element.style.opacity = \"\";\n\n\t\tthis.drag_state = null;\n\t\tdocument.body.classList.remove(\"dragging-piece\");\n\n\t\tif (config.click_spotlight) {\n\t\t\thub.draw_canvas_arrows();\t\t\t\t\t// Might need to clear spotlight arrows.\n\t\t}\n\t},\n\n\tmousedown_event_on_board_td: function(overlay_td, event) {\n\n\t\tif (event.button !== 0 || this.drag_state) {\n\t\t\treturn;\n\t\t}\n\n\t\tevent.preventDefault();\t\t\t\t\t\t\t// I forget why?\n\n\t\tlet piece_style = overlay_td.style.backgroundImage;\n\t\tif (!piece_style || piece_style === \"none\") {\n\t\t\treturn;\n\t\t}\n\n\t\tlet rect = overlay_td.getBoundingClientRect();\n\n\t\tthis.drag_state = {\n\t\t\tfrom_element: overlay_td,\n\t\t\tfrom_square: overlay_td.id.slice(8),\t\t// e.g. \"e4\" or similar.\n\t\t\tpiece_style: piece_style,\n\t\t\trect: rect,\n\n\t\t\tstartX: event.clientX,\n\t\t\tstartY: event.clientY,\n\n\t\t\toffsetX: rect.width / 2,\n\t\t\toffsetY: rect.height / 2,\n\n\t\t\tfloating: null,\t\t\t\t\t\t\t\t// The actual element - not created until we're sure we're really dragging.\n\t\t};\n\t},\n\n\tmousemove_handler: function(event) {\n\n\t\tif (!this.drag_state) {\n\t\t\treturn;\n\t\t}\n\n\t\t// I dunno if this can happen but for safety...\n\n\t\tif (!(event.buttons & 1)) {\t\t\t\t\t\t// Bitmask: right-most bit means left click is down.\n\t\t\tconsole.log(\"drag_handler: mousemove handler saw active drag state while button 1 up!\")\n\t\t\tthis.cancel_drag();\n\t\t\treturn;\n\t\t}\n\n\t\tlet dx = event.clientX - this.drag_state.startX;\n\t\tlet dy = event.clientY - this.drag_state.startY;\n\t\tlet dist = Math.hypot(dx, dy);\n\n\t\tif (!this.drag_state.floating) {\n\n\t\t\t// Treat small mouse movement as a normal click so boardfriends_click keeps its select/move behavior.\n\n\t\t\tif (dist < 5) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Drag starting now!\n\n\t\t\thub.set_active_square(Point(this.drag_state.from_square));\n\t\t\tif (config.click_spotlight) {\n\t\t\t\thub.draw_canvas_arrows();\n\t\t\t}\n\n\t\t\tlet floating = document.createElement(\"div\");\t\t\t// A custom ghost piece instead of HTML5 drag-and-drop.\n\n\t\t\tfloating.style.position = \"fixed\";\n\t\t\tfloating.style.pointerEvents = \"none\";\n\t\t\tfloating.style.width = this.drag_state.rect.width + \"px\";\n\t\t\tfloating.style.height = this.drag_state.rect.height + \"px\";\n\t\t\tfloating.style.backgroundImage = this.drag_state.piece_style;\n\t\t\tfloating.style.backgroundSize = \"contain\";\n\t\t\tfloating.style.backgroundRepeat = \"no-repeat\";\n\t\t\tfloating.style.zIndex = 1000;\n\n\t\t\tdocument.body.appendChild(floating);\n\n\t\t\tdocument.body.classList.add(\"dragging-piece\");\t\t\t// This is just a css change.\n\t\t\tthis.drag_state.from_element.style.opacity = \"0.35\";\n\n\t\t\tthis.drag_state.floating = floating;\n\t\t}\n\n\t\tthis.drag_state.floating.style.left = (event.clientX - this.drag_state.offsetX) + \"px\";\n\t\tthis.drag_state.floating.style.top = (event.clientY - this.drag_state.offsetY) + \"px\";\n\t},\n\n\tmouseup_handler: function(event) {\n\n\t\tif (hub.grapher.dragging) {\n\t\t\thub.grapher.dragging = false;\t\t\t\t// Always stop graph dragging.\n\t\t}\n\n\t\tif (!this.drag_state) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (event.button !== 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.drag_state.floating) {\t\t\t\t\t// Real drag was in progress...\n\n\t\t\tlet e = document.elementFromPoint(event.clientX, event.clientY);\n\t\t\tlet target_element = null;\n\n\t\t\twhile (e && e !== document.body) {\n\t\t\t\tif (e.id && e.id.startsWith(\"overlay_\")) {\n\t\t\t\t\ttarget_element = e;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\te = e.parentElement;\n\t\t\t}\n\n\t\t\tif (target_element) {\n\t\t\t\tlet move = this.drag_state.from_square + target_element.id.slice(8);\n\t\t\t\tlet ok = hub.move(move);\n\t\t\t\tif (!ok && config.click_spotlight) {\t// The spotlight needs to be cleared.\n\t\t\t\t\thub.draw_canvas_arrows();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.cancel_drag();\t\t\t\t\t\t\t\t// Final cleanup needed in all cases.\n\t}\n};\n\n// Setup drag-and-drop...\n\nwindow.addEventListener(\"mousemove\", (event) => {\n\tdrag_handler.mousemove_handler(event);\n});\n\nwindow.addEventListener(\"mouseup\", (event) => {\n\tdrag_handler.mouseup_handler(event);\n});\n\n\nwindow.addEventListener(\"drop\", (event) => {\n\tevent.preventDefault();\n\tif (drag_handler.drag_state) {\t\t\t\t\t\t// Ignore if handler is in the middle of internal piece drag.\n\t\treturn;\n\t}\n\tlet dt = event.dataTransfer;\n\tif (dt && dt.files && dt.files.length > 0) {\n\t\thub.handle_file_drop(event);\n\t}\n});\n\nwindow.addEventListener(\"blur\", () => {\n\tdrag_handler.cancel_drag();\n});\n\nwindow.addEventListener(\"mouseleave\", () => {\n\tdrag_handler.cancel_drag();\n});\n\n// Native dragenter / dragover prevention is required so external file drops are accepted by the window...\n\nwindow.addEventListener(\"dragenter\", (event) => {\n\tevent.preventDefault();\n});\n\nwindow.addEventListener(\"dragover\", (event) => {\n\tevent.preventDefault();\n});\n"
  },
  {
    "path": "files/src/renderer/99_start.js",
    "content": "\"use strict\";\n\n// Upon first run, hopefully the prefs directory exists by now\n// (I think the main process makes it...)\n\nconfig_io.create_if_needed(config);\nengineconfig_io.create_if_needed(engineconfig);\ncustom_uci.create_if_needed();\n\nLog(\"\");\nLog(\"======================================================================================================================================\");\nLog(`Nibbler startup at ${new Date().toUTCString()}`);\n\nlet hub = NewHub();\nhub.engine_start(config.path, true);\n\nif (load_err1) {\n\thub.err_receive(`<span class=\"blue\">While loading config.json: ${load_err1}</span>`);\n\thub.err_receive(\"\");\n} else if (load_err2) {\n\thub.err_receive(`<span class=\"blue\">While loading engines.json: ${load_err2}</span>`);\n\thub.err_receive(\"\");\n} else if (config.options) {\n\talert(messages.engine_options_reset);\n\tconfig.args_unused = config.args;\n\tconfig.options_unused = config.options;\n\thub.save_config();\t\t\t\t// Ensure the options object is deleted from the file.\n}\n\nfenbox.value = \"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\";\n\n// We have 3 main things that get drawn to:\n//\n//\t\t- boardsquares, lowest z-level table with enemy pieces and coloured squares.\n//\t\t- canvas, which gets arrows drawn on it.\n//\t\t- boardfriends, a table with friendly pieces.\n//\n// boardsquares has its natural position, while the other three get\n// fixed position that is set to be on top of it.\n\nboardfriends.width = canvas.width = boardsquares.width = config.board_size;\nboardfriends.height = canvas.height = boardsquares.height = config.board_size;\n\nrightgridder.style[\"height\"] = `${canvas.height}px`;\n\n// Set up the squares in both tables. Note that, upon flips, the elements\n// themselves are moved to their new position, so everything works, e.g.\n// the x and y values are still correct for the flipped view.\n\nhub.change_background(config.override_board, false);\n\nfor (let y = 0; y < 8; y++) {\n\tlet tr1 = document.createElement(\"tr\");\n\tlet tr2 = document.createElement(\"tr\");\n\tboardsquares.appendChild(tr1);\n\tboardfriends.appendChild(tr2);\n\tfor (let x = 0; x < 8; x++) {\n\t\tlet td1 = document.createElement(\"td\");\n\t\tlet td2 = document.createElement(\"td\");\n\t\ttd1.id = \"underlay_\" + S(x, y);\n\t\ttd2.id = \"overlay_\" + S(x, y);\n\t\ttd1.width = td2.width = config.square_size;\n\t\ttd1.height = td2.height = config.square_size;\n\t\ttr1.appendChild(td1);\n\t\ttr2.appendChild(td2);\n\t\ttd2.addEventListener(\"mousedown\", (event) => {\n\t\t\tdrag_handler.mousedown_event_on_board_td(td2, event);\n\t\t});\n\t}\n}\n\nstatusbox.style[\"font-size\"] = config.info_font_size.toString() + \"px\";\ninfobox.style[\"font-size\"] = config.info_font_size.toString() + \"px\";\nfullbox.style[\"font-size\"] = config.info_font_size.toString() + \"px\";\nmovelist.style[\"font-size\"] = config.pgn_font_size.toString() + \"px\";\nfenbox.style[\"font-size\"] = config.fen_font_size.toString() + \"px\";\n\nif (config.graph_height <= 0) {\n\tgraph.style.display = \"none\";\n} else {\n\tgraph.style.height = config.graph_height.toString() + \"px\";\n\tgraph.style.display = \"\";\n}\n\n// The promotion table pops up when needed...\n\npromotiontable.style.left = (boardsquares.offsetLeft + config.square_size * 2).toString() + \"px\";\npromotiontable.style.top = (boardsquares.offsetTop + config.square_size * 3.5).toString() + \"px\";\npromotiontable.style[\"background-color\"] = config.active_square;\n\n// --------------------------------------------------------------------------------------------\n// In bad cases of super-large trees, the UI can become unresponsive. To mitigate this, we\n// put user input in a queue, and drop certain user actions if needed...\n\nlet input_queue = [];\n\nipcRenderer.on(\"set\", (event, msg) => {\t\t// Should only be for things that don't need any action except redraw.\n\tfor (let [key, value] of Object.entries(msg)) {\n\t\tconfig[key] = value;\n\t}\n\thub.info_handler.must_draw_infobox();\n\thub.draw();\n});\n\nlet droppables = [\t\t\t\t\t\t\t// If the UI is already lagging, dropping one of these won't make it feel any worse.\n\t\"goto_root\", \"goto_end\", \"prev\", \"next\", \"previous_sibling\", \"next_sibling\", \"return_to_main_line\", \"promote_to_main_line\",\n\t\"promote\", \"delete_node\", \"delete_children\", \"delete_siblings\", \"delete_other_lines\", \"return_to_lock\", \"play_info_index\",\n\t\"clear_searchmoves\", \"invert_searchmoves\",\n];\n\nipcRenderer.on(\"call\", (event, msg) => {\t// Adds stuff to the queue, or drops some stuff.\n\n\tlet fn;\n\n\tif (typeof msg === \"string\") {\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// msg is function name\n\t\tif (input_queue.length > 0 && droppables.includes(msg)) {\n\t\t\treturn;\n\t\t}\n\t\tfn = hub[msg].bind(hub);\n\t} else if (typeof msg === \"object\" && typeof msg.fn === \"string\" && Array.isArray(msg.args)) {\t\t// msg is object with fn and args\n\t\tif (input_queue.length > 0 && droppables.includes(msg.fn)) {\n\t\t\treturn;\n\t\t}\n\t\tfn = hub[msg.fn].bind(hub, ...msg.args);\n\t} else {\n\t\tconsole.log(\"Bad call, msg was...\");\n\t\tconsole.log(msg);\n\t}\n\n\tif (fn) {\n\t\tinput_queue.push(fn);\n\t}\n});\n\n// The queue needs to be examined very regularly and acted upon.\n\nfunction input_loop() {\n\tif (input_queue.length > 0) {\n\t\tfor (let fn of input_queue) {\n\t\t\tfn();\n\t\t}\n\t\tinput_queue = [];\n\t}\n\tsetTimeout(input_loop, 10);\n}\n\ninput_loop();\n\n// --------------------------------------------------------------------------------------------\n// We had some problems with the various clickers: we used to destroy and create\n// clickable objects a lot. This seemed to lead to moments where clicks wouldn't\n// register.\n//\n// A better approach is to use event handlers on the outer elements, and examine\n// the event.path to see what was actually clicked on.\n\nfullbox.addEventListener(\"mousedown\", (event) => {\n\thub.fullbox_click(event);\n});\n\nboardfriends.addEventListener(\"mousedown\", (event) => {\n\thub.boardfriends_click(event);\n});\n\ninfobox.addEventListener(\"mousedown\", (event) => {\n\thub.infobox_click(event);\n});\n\nmovelist.addEventListener(\"mousedown\", (event) => {\n\thub.movelist_click(event);\n});\n\nstatusbox.addEventListener(\"mousedown\", (event) => {\n\thub.statusbox_click(event);\n});\n\npromotiontable.addEventListener(\"mousedown\", (event) => {\n\thub.promotiontable_click(event);\n});\n\n// Graph clicks and dragging, borrowed from Ogatak...\n\ngraph.addEventListener(\"mousedown\", (event) => {\n\thub.winrate_click(event);\n\thub.grapher.dragging = true;\n});\n\nfor (let s of [\"mousemove\", \"mouseleave\"]) {\n\n\tgraph.addEventListener(s, (event) => {\n\t\tif (!hub.grapher.dragging) {\n\t\t\treturn;\n\t\t}\n\t\tif (!event.buttons) {\n\t\t\thub.grapher.dragging = false;\n\t\t\treturn;\n\t\t}\n\t\thub.winrate_click(event);\n\t});\n}\n\n//\n\nwindow.addEventListener(\"wheel\", (event) => {\n\n\t// Only if the PGN chooser is closed, and the mouse is over the board or graph.\n\t// (Not over the moveslist or infobox, because those can have scroll bars, which\n\t// the mouse wheel should interact with.)\n\n\tif (fullbox.style.display !== \"none\") {\n\t\treturn;\n\t}\n\n\t// Not if the GUI has pending actions...\n\n\tif (input_queue.length > 0) {\n\t\treturn;\n\t}\n\n\tlet allow = false;\n\n\tlet path = event.path || (event.composedPath && event.composedPath());\n\n\tif (path) {\n\t\tfor (let item of path) {\n\t\t\tif (item.id === \"boardfriends\" || item.id === \"graph\") {\n\t\t\t\tallow = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (allow) {\n\t\tif (event.deltaY && event.deltaY < 0) input_queue.push(hub.prev.bind(hub));\n\t\tif (event.deltaY && event.deltaY > 0) input_queue.push(hub.next.bind(hub));\n\t}\n});\n\n// Setup return key on FEN box...\n\nfenbox.addEventListener(\"keydown\", (event) => {\n\tif (event.key === \"Enter\") {\n\t\thub.load_from_fenbox(fenbox.value);\n\t}\n});\n\n// Set space-bar to toggle go/halt, unless we're in the FEN box...\n\nwindow.addEventListener(\"keydown\", (event) => {\n\tif (event.key === \" \") {\n\t\tlet ae = document.activeElement;\n\t\tif (ae.tagName !== \"INPUT\" && ae.tagName !== \"TEXTAREA\" && !ae.isContentEditable) {\n\t\t\tevent.preventDefault();\t\t\t\t\t\t// Prevent scrolling e.g. when the moves area is big enough to have a scroll bar.\n\t\t\tif (!event.repeat) {\n\t\t\t\thub.toggle_go();\n\t\t\t}\n\t\t}\n\t}\n});\n\nwindow.addEventListener(\"resize\", (event) => {\n\thub.window_resize_time = performance.now();\n});\n\nwindow.addEventListener(\"error\", (event) => {\n\talert(messages.uncaught_exception);\n}, {once: true});\n\n// Forced garbage collection. For reasons I can't begin to fathom, Node isn't\n// garbage collecting everything, and the heaps seems to grow and grow. It's\n// not what you would call a memory leak, since manually triggering the GC\n// does clear everything... note --max-old-space-size is another option.\n\nfunction force_gc() {\n\tif (!global || !global.gc) {\n\t\tconsole.log(\"Triggered GC not enabled.\");\n\t\treturn;\n\t}\n\tglobal.gc();\n\tsetTimeout(force_gc, 300000);\t\t// Once every 5 minutes or so?\n}\n\nsetTimeout(force_gc, 300000);\n\n// Go...\n\nfunction enter_loop() {\n\tif (images.fully_loaded()) {\n\t\thub.spin();\n\t\tipcRenderer.send(\"renderer_ready\", null);\n\t} else {\n\t\tsetTimeout(enter_loop, 25);\n\t}\n}\n\nenter_loop();\n"
  }
]