From 49feff0e585a5efef9f4bfaae87566852c5a8435 Mon Sep 17 00:00:00 2001 From: John Coffey Date: Thu, 17 Sep 2026 23:13:56 -0700 Subject: [PATCH] World Conquest: the classic game of world domination, one against five Forty-two territories grouped from Natural Earth's public-domain outlines, the classic eighty-three links, exact dice odds, escalating card sets, and five bots that play every seeded game through to a winner. --- .gitignore | 28 + LICENSE | 661 ++++++++ NOTICE.md | 70 + README.md | 124 ++ index.html | 122 ++ package-lock.json | 2928 ++++++++++++++++++++++++++++++++++++ package.json | 38 + public/favicon.svg | 14 + src/App.css | 337 +++++ src/App.tsx | 402 +++++ src/audio/play.ts | 27 + src/audio/score.ts | 98 ++ src/audio/synth.ts | 528 +++++++ src/components/Board.tsx | 156 ++ src/components/Landing.tsx | 143 ++ src/components/Side.tsx | 473 ++++++ src/game/board.json | 1 + src/game/bot.ts | 268 ++++ src/game/cards.test.ts | 37 + src/game/cards.ts | 56 + src/game/describe.test.ts | 32 + src/game/describe.ts | 106 ++ src/game/dice.test.ts | 58 + src/game/dice.ts | 99 ++ src/game/game.test.ts | 128 ++ src/game/game.ts | 370 +++++ src/game/map.test.ts | 65 + src/game/map.ts | 201 +++ src/game/play.test.ts | 51 + src/game/rng.ts | 26 + src/index.css | 15 + src/main.tsx | 10 + tools/map.py | 350 +++++ tsconfig.app.json | 28 + tsconfig.json | 7 + tsconfig.node.json | 23 + vite.config.ts | 15 + web/Dockerfile | 24 + web/nginx.conf | 22 + 39 files changed, 8141 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 NOTICE.md create mode 100644 README.md create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/favicon.svg create mode 100644 src/App.css create mode 100644 src/App.tsx create mode 100644 src/audio/play.ts create mode 100644 src/audio/score.ts create mode 100644 src/audio/synth.ts create mode 100644 src/components/Board.tsx create mode 100644 src/components/Landing.tsx create mode 100644 src/components/Side.tsx create mode 100644 src/game/board.json create mode 100644 src/game/bot.ts create mode 100644 src/game/cards.test.ts create mode 100644 src/game/cards.ts create mode 100644 src/game/describe.test.ts create mode 100644 src/game/describe.ts create mode 100644 src/game/dice.test.ts create mode 100644 src/game/dice.ts create mode 100644 src/game/game.test.ts create mode 100644 src/game/game.ts create mode 100644 src/game/map.test.ts create mode 100644 src/game/map.ts create mode 100644 src/game/play.test.ts create mode 100644 src/game/rng.ts create mode 100644 src/index.css create mode 100644 src/main.tsx create mode 100644 tools/map.py create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts create mode 100644 web/Dockerfile create mode 100644 web/nginx.conf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ffdaf44 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Natural Earth downloads and the Python tools' environment, for tools/map.py. +natural-earth +.venv diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..be3f7b2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/NOTICE.md b/NOTICE.md new file mode 100644 index 0000000..4bdc617 --- /dev/null +++ b/NOTICE.md @@ -0,0 +1,70 @@ +# Notice, credits and provenance + +## What this is + +An independent reimplementation of the classic game of world domination, +written from scratch in TypeScript in 2026: one human against five computer +players, in one sitting. + +## The name + +It isn't called what you'd expect, and that's deliberate. The rules of a game +are ideas rather than expression, and nobody gets to fence them off, so they're +rebuilt here freely. The **name** is a live trademark belonging to a company +that enforces it, and this ships on a public site with somebody's name in the +footer. So the mechanics are exact and the vocabulary is ours. *World +Conquest* is what the game was called when it was first published, in French, +as *La Conquête du Monde*. + +None of that is a copyright question. Game mechanics are ideas. Names are a +trademark question, which is about whether a reader would think this came from +the trademark holder. Calling it *World Conquest* answers that, and costs the +game nothing. + +The same goes for the cards. The published game draws a soldier, a horseman and +a cannon. The rules only need three kinds, fourteen of each, so here they're +named for what they are and drawn as plain shapes. + +## The map + +**The outlines are from Natural Earth**, 1:50m cultural vectors, admin-0 +countries and admin-1 states and provinces: + +- , public domain + +Being public domain, they carry no license terms of their own. They're credited +here anyway, because somebody drew the world properly and that deserves saying. +`tools/map.py` reads them and writes `src/game/board.json`. + +**The grouping is ours**: which countries and provinces make up each of the +forty-two territories, where the board's line down the Urals falls, and which +pieces are left off the map. So are the projection, the simplification and +every color drawn on top. + +The **topology**, which territory borders which, is the classic board's. It's +a fact about a published game, typed out by hand in `src/game/map.ts` and +checked against itself. + +## What is ours + +Everything else: the rules engine, the odds, the bots and their reasoning, the +interface, the sound effects and the music, and every word on screen. + +## License + +Copyright (C) 2026 John Coffey. + +This program is free software: you can redistribute it and/or modify it under +the terms of the **GNU Affero General Public License** as published by the +Free Software Foundation, either version 3 of the License, or (at your option) +any later version. See [LICENSE](LICENSE). + +AGPL rather than plain GPL because this is served over a network. Section 13 +means anyone who runs a modified copy of this for other people over a network +has to offer them its source. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + +*None of the above is legal advice.* diff --git a/README.md b/README.md new file mode 100644 index 0000000..ca37d6f --- /dev/null +++ b/README.md @@ -0,0 +1,124 @@ +# World Conquest + +A browser rebuild of the classic game of world domination: forty-two +territories, six continents and the dice, with one human against five +computer players. + +It isn't called what you'd expect, and that's deliberate. The rules are free to +rebuild, but the trademark and the published artwork are not. *World Conquest* +is the name the game had when it first came out, in French, in 1957. See +[NOTICE.md](NOTICE.md). + +## The table + +**You against five, at the classic six-player table.** You get seven +territories and twenty armies, the same as everybody else. The world is dealt +at random, the armies go down a turn at a time, and play continues until one +player holds all forty-two territories. + +**The rules are the classic ones.** Choices that vary from table to table are +settled like this, and the landing page says so before anyone plays: + +- sets escalate for the whole table (4, 6, 8, 10, 12, 15, then five more each + time), not at a fixed value per set; +- a card showing a territory you hold adds two armies there when traded, + on one territory per set; +- five cards means you must trade, and taking a player's cards up to six or + more means you trade immediately, in the middle of your attack; +- fortifying moves once, along any chain of your own territories; +- the defender always rolls every die it can. + +## The map + +**Real geography, grouped into the classic forty-two.** The outlines come from +[Natural Earth](https://www.naturalearthdata.com/), which is public domain. +`tools/map.py` groups countries, and for Russia, Canada, the United States, +Australia, China and Indonesia their provinces, into the board's territories, +then writes `src/game/board.json`. The grouping is this project's own. Deciding +which country goes in which territory is the only part of the map anyone had to +decide. + +**The links are the classic board's, link for link.** They're typed out in +`src/game/map.ts`, and `npm test` holds them to eighty-three, symmetric and +connected, with each continent's entry points where every player expects them: +one into Australia, two into South America, and five into Asia. + +Where real geography and the classic links disagree, the rules win and the map +says so: + +- **A lane across the water** for every link the shapes don't show: + Brazil to North Africa, Greenland to Iceland, and so on. Alaska and Kamchatka + meet off the two edges of the board, across the date line. +- **A closed border** where the real map touches but the classic board has no + link. There's exactly one. The Altai Republic gives Siberia a short border with + Kazakhstan (Afghanistan, on this board), so it's drawn as a fence. Every other + accidental border was grouped away: North Korea goes with Manchuria rather + than China, and the steppe south of Siberia goes with the Ural. + +Owners are shown by the fill. Continents are shown by the coastline, with each +continent's shore drawn in its own color underneath the land. That way the six +read at a glance without taking the fill away from who holds what. + +To rebuild the map: + +```bash +python3 -m venv .venv && .venv/bin/pip install shapely pyshp +.venv/bin/python tools/map.py path/to/natural-earth +``` + +The directory needs the 1:50m admin-0 countries and admin-1 states and +provinces from naciscdn.org, unzipped. + +## The dice + +`src/game/dice.ts` computes the odds from the 36, 216, 1,296 and 7,776 equally +likely throws, rather than copying a table, and the tests hold it to the +published figures. A 20,000-battle run of the actual dice has to agree with it +too. The same numbers drive two things: the "Pressed to the end, 64% to take +it" line shown when you aim an attack, and every decision a bot makes. + +## The bots + +`src/game/bot.ts`. A bot keeps no plan between moves. Each time it's asked, it +looks at the board and answers with the one thing it would do next. A plan the +dice have already ruined can never linger, and a game can be paused anywhere. + +What a bot wants comes down to three things, in this order: a continent it can +hold, a card for the turn, and nobody else holding a continent in peace. It +prices each attack as what winning is worth, multiplied by the exact chance of +winning. It fights when the odds clear a line set by its nerve (roughly 55% to +75%), and goes a little lower for the first territory of a turn, because a +card is on the line. Reinforcements pile up at one staging point rather than +spreading thin. Idle stacks walk to the front. + +Each bot leans a little: braver or more careful, and slightly fonder of some +continents than others. The lean comes from the seed, so a seed still replays a +game exactly. + +**They finish.** `play.test.ts` plays thirty seeded six-bot games to the end, +and every one has to end with somebody holding the world. They take 20 rounds +on average, 9 at the fewest and 40 at the most, and more than three different +seats win. A game that can't be won isn't a hard game, it's a broken one, and +the sibling game on this site learned that the hard way. + +## The sound + +The synth is the one the site's other games share. What's written for this +game is the dice, which clatter as many dice as were actually thrown; a +conquest, which is three guns and a bugle going up; the card-trade fanfare, +which starts higher the more the set is worth; and one march in C minor. When +the bots are playing fast, a batch of moves makes at most one sound, the most +important thing in it, so a whole round doesn't arrive as one crash. + +## Where it lives + +, built straight from this repository's +`main`. This is AGPL, and section 13 asks that players be offered the source +of the version they're running. Building from the published ref is what makes +that offer true. + +## Still to build + +- On a phone the whole world is 390 pixels wide. It fits and plays, but the + small territories are small targets, and there's no zoom of its own beyond the + browser's pinch. diff --git a/index.html b/index.html new file mode 100644 index 0000000..09b384b --- /dev/null +++ b/index.html @@ -0,0 +1,122 @@ + + + + + + World Conquest — the classic game of world domination, in your browser + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..938b10c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2928 @@ +{ + "name": "world-conquest", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "world-conquest", + "version": "0.1.0", + "license": "AGPL-3.0-or-later", + "dependencies": { + "react": "^19.2.8", + "react-dom": "^19.2.8" + }, + "devDependencies": { + "@types/node": "^24.13.3", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", + "@vitejs/plugin-react": "^6.1.0", + "oxlint": "^1.79.0", + "typescript": "~6.0.2", + "vite": "^8.2.2", + "vitest": "^3.2.7" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.148.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.148.0.tgz", + "integrity": "sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.82.0.tgz", + "integrity": "sha512-a3LB+C5Dsj5b/qtmG/mv5WrzuiXEpg1KF5nXWcEvaoN5TYAqkIvxPOwTPp3Jy/FoGpRo8zsTFhMElMXfeoOEzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.82.0.tgz", + "integrity": "sha512-OBlhRgNqFblGpGenno/aqOfJLOkQ2B8Ig3iDAalfn0H8hJGZKXPeexCRTDm6uwv6YUjSA9Xnwt1y/Bgj5ZH8uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.82.0.tgz", + "integrity": "sha512-dsopxqtY5ZdyT9uLHyGt1SyiLop6hi7hWI3PKpePodkRQOkLaCm+OE4fR9CAz9qdfjiFO8531tX/QDyP/psjFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.82.0.tgz", + "integrity": "sha512-94Lu0SgTClKColU66g1VDuigV3HkcbkJBnTtZjGYfE8UPugaWDgKrm2icjC6HJVUYler2OXaHP/X0TBy8+CowQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.82.0.tgz", + "integrity": "sha512-hne/V06ewhh1i0w8+l7GDNROAGCGPmyFuOwiP7YTRu0JycyStJ4785dmF8xU5p0uUwt2emvIF9vc7Xjis+cJ0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.82.0.tgz", + "integrity": "sha512-aWY2xtbZf1LneW9Qsv/n2Sp8gOu74JrlQzEtj4coHX2SHFrCfhmAumaU+sI/A5nr+yoTRTSmI/pL2s6ADlNSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.82.0.tgz", + "integrity": "sha512-Fe+TtXCXMh/5f7kWlZ2VAwsMumZWtraFlKVk1NJlL52/beGwfDE7ov+/8gVirHzWokzGu7X65hSPq0ucPDskWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.82.0.tgz", + "integrity": "sha512-6azCZ6OJudlvipNttXCCQcyeFfcJ/NvUZdSN1z8elo73kCHtyQC7WTiUcSjWYvJ1jaq9KDUyMAoAS/vNzhBomA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.82.0.tgz", + "integrity": "sha512-PLEaSD8IAIIlwW4dwOd9YaxuxeOpwiXL4J24rcnE4iNtyM5j9Q9/3+gti08oXpx0u2ygNjRDx9xjWWpQonuJEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.82.0.tgz", + "integrity": "sha512-D94em/BwknNTn4vqxjHh5wb2oL566eFhArabqKIr0cNZMHOJuiraFp1A8tXpH05bbE5tqwEfLXTI0MWEGtn3Dw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.82.0.tgz", + "integrity": "sha512-MOprxBaoYU2D4VgxXCl3ghydThWtx7Um1lL51kGYNeQ5Al7WzsH7/tqGdNtbLrIWnjq3bsm13+nz/gRIxjrOXw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.82.0.tgz", + "integrity": "sha512-5h55QsfJ/luDXZzC20k6SNOY1Az+dCP9WvntKtcUWh2JhckAdwApY2ZusaBTwLENnReXU+A2fJtSrYvZJNKNPg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.82.0.tgz", + "integrity": "sha512-IE8NJNLlHr0CaXyGJPGVn0eTkUyoj1I2UfA8x7I4PSOYKsQ/6btVC7Pywrj5onk0cMH25r6Z38SoN3AvE5Zuog==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.82.0.tgz", + "integrity": "sha512-XUUUxaBo9XKl+J1B9EmP1cTGQPddzeURvoGkfwh/94PGnbW+hBprDljneoI2M1jzC1bzrIV3ihc7iM9UXl8+tg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.82.0.tgz", + "integrity": "sha512-SWLSFulX9TDuH6yvbPYp4+VNn6jkkIvvI+KiujDM5rWBRHEfkesCC/pCneIIUr6ovkxZ5fRtpi2v5Cz5FrMJZg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.82.0.tgz", + "integrity": "sha512-BQy35f6ZUdNr9a6c7B7orxQTcLjByGT2z3WAgmRovpRwmPYAaJ+NTplmMzhdjdJ4qSchfMNZy/Ukg+qRg6zseQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.82.0.tgz", + "integrity": "sha512-V4QhSTg5gctZue8RJjsGi7NpQPThr/p1/HfmiMC5kfe1KFEup9SQRVub4A6kijQjdHfxj7bLL1KO3QO7/5bwMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.82.0.tgz", + "integrity": "sha512-TUSCLaKB2yktpFAJ/r3HAUYsaV/3DT7JS4iNKyoh3a9YNwD0UG7Ezh4D8m23654vQcU6P/RQrCAjRPKe4peP/A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.82.0.tgz", + "integrity": "sha512-VTVoRIWJTb+wvUX8EYoPArfFH02whuR10goFXE/LHRRX33ajRrFgqbcONXZMiF4C5rnattfkm87HqYn8jb8hmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.7.tgz", + "integrity": "sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.7.tgz", + "integrity": "sha512-l17HE9EweWaqJZhuUuNBN/FzM62xw+DECVnJyvMsxn8vJFAGLy5QfLDoYAcronkAN8VxKZHezDpulHDPx95vFw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.7.tgz", + "integrity": "sha512-8ED8ELFvHXc6OCETIn4gXObPiaR6bckM/ipXtbzlPVDRMBfEGjCKgO90F9YtfdpDatVx/ZQw7aZ1vUMf/+T3Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.7.tgz", + "integrity": "sha512-/WPripjtiAIZ2tWY7ddijORT0Ujg87wxWW/qcoFVCKAWVDPhtY0xr7Dj0M3GyNGz60jGwTElhro/mkF9dT7dDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.7.tgz", + "integrity": "sha512-14DI4NcqpvbICxSnGLx3PmtDaWqRP/KGSGb6C+JLLVPeZRl6dKdHba3pGsqT3vpdTqhEYIPG0MMQ8c0xYqoJxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.7.tgz", + "integrity": "sha512-bxrWIRvHWQvbJwi+VIie/kDJmQxcNE6xxWwZdqF/ExVAigtHkv54WTLQPb+QsZdnFy18fg7JPfWGL0RH6vwIlQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.7.tgz", + "integrity": "sha512-toOY2BChBZyuxU7OYX6Tn389di4IzAqPTycVcci0O7FSfBqzRB3RZn+K5Is6ANf4tmgRd/K1yZTsNTXbkXsnLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.7.tgz", + "integrity": "sha512-lAIXTH/aiLRLxsTgQvfhjo4K1ydWIp00+V0voOr9beb/9ZmkUFrSIb03dXNFRgMNvkE6oGsF10ioQ6UsI+vS5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.7.tgz", + "integrity": "sha512-kdnwS28Pkenp/mZMRwjXXXwxQ7pIsm+bF919LUK93BOyhcLsrVKdP2p9fxpiPNPAbNuch8ypQt0pm2P2LYCAGg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.7.tgz", + "integrity": "sha512-516OdsyLdr5E65paF3yBF55t8mfm9+gmtCsK3xI7XKXIT7EfRlHhxL8K/NR6Hu8BWSgF5+1w74lTL0+nxcc8Qw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.7.tgz", + "integrity": "sha512-r8/z8n7GFaYRln3xmP1Cxy0HH/HLM0uBUPkEuSVEfKGDA89M0FsZRZJRSwe/tJjRx+fpH/gjorfhB8tmEbSFLA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.7.tgz", + "integrity": "sha512-pAsE8iiDxUg1xBqdhrTfg45AVDVpirjz00sblEYClGNNcMnDb+e8beQgqIAw6LvauX/APvgxUnwrgun/YYGBhw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.7.tgz", + "integrity": "sha512-lTcIYmmnQQA8Or/2DatS6oSqcdLHvendjS+zLu+FwgToynWMRSmQdpM65fTANJgIS4mjbMOo5KT2lnT9SAb96w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.7.tgz", + "integrity": "sha512-e3Gu3WxbNk/UqQhxqU7YIYO+9ZBvWNz3U+h/qRFosscMFzdRPbXYSaSWgSnklv2fz1TgzBTcti2z35c/7irsHw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.7.tgz", + "integrity": "sha512-W/jg5qoRSqjsEv0+dZi4e687mcHqmVuU0P4fK6qS/xjetW2Gmc1W8j//z5nAeNcC8Ttm0hV46IjcYeuVwYhuiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz", + "integrity": "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz", + "integrity": "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz", + "integrity": "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz", + "integrity": "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz", + "integrity": "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz", + "integrity": "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz", + "integrity": "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz", + "integrity": "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz", + "integrity": "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz", + "integrity": "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz", + "integrity": "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz", + "integrity": "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz", + "integrity": "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz", + "integrity": "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz", + "integrity": "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz", + "integrity": "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz", + "integrity": "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz", + "integrity": "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz", + "integrity": "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz", + "integrity": "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz", + "integrity": "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz", + "integrity": "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz", + "integrity": "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz", + "integrity": "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz", + "integrity": "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-I8bPpDLcHBv1qiIiXDCy71Rt8eQDKJP0sMSWJphDdAcdqiJ1sGpZamavoEIRZmYzjia9LuEb2HlYdDpmoENpvQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz", + "integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "oxc-transform-react": "^0.145.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "oxc-transform-react": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz", + "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz", + "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz", + "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.7", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz", + "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz", + "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz", + "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/oxlint": { + "version": "1.82.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.82.0.tgz", + "integrity": "sha512-+iFM1BGw1ntYJt3QngbJmjbrGxPaKMUADOXOijpWGnYcBPq8YZnQftSS1C+pVcDYy9YxqDVJKQqQkTazTQMboQ==", + "dev": true, + "license": "MIT", + "bin": { + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/oxc-project" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.82.0", + "@oxlint/binding-android-arm64": "1.82.0", + "@oxlint/binding-darwin-arm64": "1.82.0", + "@oxlint/binding-darwin-x64": "1.82.0", + "@oxlint/binding-freebsd-x64": "1.82.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.82.0", + "@oxlint/binding-linux-arm-musleabihf": "1.82.0", + "@oxlint/binding-linux-arm64-gnu": "1.82.0", + "@oxlint/binding-linux-arm64-musl": "1.82.0", + "@oxlint/binding-linux-ppc64-gnu": "1.82.0", + "@oxlint/binding-linux-riscv64-gnu": "1.82.0", + "@oxlint/binding-linux-riscv64-musl": "1.82.0", + "@oxlint/binding-linux-s390x-gnu": "1.82.0", + "@oxlint/binding-linux-x64-gnu": "1.82.0", + "@oxlint/binding-linux-x64-musl": "1.82.0", + "@oxlint/binding-openharmony-arm64": "1.82.0", + "@oxlint/binding-win32-arm64-msvc": "1.82.0", + "@oxlint/binding-win32-ia32-msvc": "1.82.0", + "@oxlint/binding-win32-x64-msvc": "1.82.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=7.0.2001", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/rolldown": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.7.tgz", + "integrity": "sha512-g0EtLvBjTUB7jhyV0S/TCup3v/XSVl45vUIGbOGU4QPiyjTenCe4mKuFvW9fEgYmS2Fo42AUssRmNuMziXdrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.148.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.7", + "@rolldown/binding-android-arm64": "1.2.7", + "@rolldown/binding-darwin-arm64": "1.2.7", + "@rolldown/binding-darwin-x64": "1.2.7", + "@rolldown/binding-freebsd-x64": "1.2.7", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.7", + "@rolldown/binding-linux-arm64-gnu": "1.2.7", + "@rolldown/binding-linux-arm64-musl": "1.2.7", + "@rolldown/binding-linux-ppc64-gnu": "1.2.7", + "@rolldown/binding-linux-s390x-gnu": "1.2.7", + "@rolldown/binding-linux-x64-gnu": "1.2.7", + "@rolldown/binding-linux-x64-musl": "1.2.7", + "@rolldown/binding-openharmony-arm64": "1.2.7", + "@rolldown/binding-win32-arm64-msvc": "1.2.7", + "@rolldown/binding-win32-x64-msvc": "1.2.7" + } + }, + "node_modules/rollup": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz", + "integrity": "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.1", + "@rollup/rollup-android-arm64": "4.63.1", + "@rollup/rollup-darwin-arm64": "4.63.1", + "@rollup/rollup-darwin-x64": "4.63.1", + "@rollup/rollup-freebsd-arm64": "4.63.1", + "@rollup/rollup-freebsd-x64": "4.63.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", + "@rollup/rollup-linux-arm-musleabihf": "4.63.1", + "@rollup/rollup-linux-arm64-gnu": "4.63.1", + "@rollup/rollup-linux-arm64-musl": "4.63.1", + "@rollup/rollup-linux-loong64-gnu": "4.63.1", + "@rollup/rollup-linux-loong64-musl": "4.63.1", + "@rollup/rollup-linux-ppc64-gnu": "4.63.1", + "@rollup/rollup-linux-ppc64-musl": "4.63.1", + "@rollup/rollup-linux-riscv64-gnu": "4.63.1", + "@rollup/rollup-linux-riscv64-musl": "4.63.1", + "@rollup/rollup-linux-s390x-gnu": "4.63.1", + "@rollup/rollup-linux-x64-gnu": "4.63.1", + "@rollup/rollup-linux-x64-musl": "4.63.1", + "@rollup/rollup-openbsd-x64": "4.63.1", + "@rollup/rollup-openharmony-arm64": "4.63.1", + "@rollup/rollup-win32-arm64-msvc": "4.63.1", + "@rollup/rollup-win32-ia32-msvc": "4.63.1", + "@rollup/rollup-win32-x64-gnu": "4.63.1", + "@rollup/rollup-win32-x64-msvc": "4.63.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.6.tgz", + "integrity": "sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", + "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.26", + "rolldown": "~1.2.4", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.4.0 || ^0.5.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz", + "integrity": "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.7", + "@vitest/mocker": "3.2.7", + "@vitest/pretty-format": "^3.2.7", + "@vitest/runner": "3.2.7", + "@vitest/snapshot": "3.2.7", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.7", + "@vitest/ui": "3.2.7", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz", + "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..e0b265c --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "world-conquest", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "oxlint", + "test": "vitest run", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.2.8", + "react-dom": "^19.2.8" + }, + "devDependencies": { + "@types/node": "^24.13.3", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", + "@vitejs/plugin-react": "^6.1.0", + "oxlint": "^1.79.0", + "typescript": "~6.0.2", + "vite": "^8.2.2", + "vitest": "^3.2.7" + }, + "description": "A browser rebuild of the classic world-conquest board game, one human against five computer players.", + "license": "AGPL-3.0-or-later", + "author": "John Coffey", + "repository": { + "type": "git", + "url": "git+https://github.com/jcoffey-dev/world-conquest.git" + }, + "homepage": "https://games.jcoffey.dev/conquest/", + "bugs": { + "url": "https://github.com/jcoffey-dev/world-conquest/issues" + } +} diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..4611f4c --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..6f8b916 --- /dev/null +++ b/src/App.css @@ -0,0 +1,337 @@ +/* ===================================================================== + World Conquest. + + The site's cel look: flat fills, one heavy outline on everything, no + gradients anywhere near the board. And, like the seven-power game, it + takes the whole window -- forty-two territories do not fit a cabinet. + ===================================================================== */ + +.app { + height: 100%; + display: grid; + grid-template-columns: minmax(0, 1fr) 300px; + grid-template-rows: auto minmax(0, 1fr); + grid-template-areas: 'head head' 'map side'; + gap: 10px 18px; + padding: clamp(10px, 2vw, 22px); +} + +header { + grid-area: head; + display: flex; + flex-wrap: wrap; + align-items: baseline; + justify-content: space-between; + gap: 6px 12px; +} + +header .ways { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + align-items: baseline; + gap: 6px 10px; + font-size: 0.85rem; + font-weight: 700; +} + +h1 { + margin: 0; + font-size: clamp(1.4rem, 3.2vw, 2.2rem); + font-weight: 900; + letter-spacing: -0.02em; + color: #ffd479; + -webkit-text-stroke: 3px #241a10; + paint-order: stroke fill; +} + +button { + font: inherit; + font-weight: 700; + font-size: 0.85rem; + color: #f2e9d8; + background: #22304a; + border: 2px solid #2b2318; + border-radius: 7px; + padding: 4px 11px; + cursor: pointer; +} + +button:disabled { opacity: 0.4; cursor: default; } +button:focus-visible { outline: 3px solid #ffd479; outline-offset: 1px; } + +button.go { + color: #241a10; + background: #ffd479; + font-weight: 800; +} + +button.quiet { background: none; color: #a3b3c9; border-color: #2f3b52; } + +.sound[aria-pressed='false'] { color: #8c98ab; background: #1b2437; } + +/* Red, because this one cannot be taken back. */ +.leave { color: #ffb3a7; background: #2c3b57; } + +header select { + font: inherit; + font-weight: 700; + color: #f2e9d8; + background: #22304a; + border: 2px solid #2b2318; + border-radius: 6px; + padding: 1px 6px; +} + +.dim { color: #a3b3c9; } +.small { font-size: 0.78rem; } + +.row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; } + +/* ------------------------------------------------------------ the map */ + +.map-wrap { + grid-area: map; + position: relative; + min-width: 0; + min-height: 0; + display: flex; + align-items: flex-start; + justify-content: center; +} + +.board { + /* Sized by its width and the map's own proportions, never letterboxed: + empty ocean above and below the world is not a place anybody can go. */ + width: 100%; + height: auto; + max-height: 100%; + border: 2px solid #2b2318; + border-radius: 8px; + background: #cfe3ef; + user-select: none; +} + +.ocean { fill: #cfe3ef; } + +/* Each continent's shore in its own color, drawn under the land so only the + coast shows. */ +.shores path { + fill: none; + stroke-width: 7; + stroke-linejoin: round; + stroke-opacity: 0.85; +} + +.lanes line { + stroke: #41596b; + stroke-width: 2; + stroke-dasharray: 6 5; + stroke-linecap: round; +} + +.land { + stroke: #2b2318; + stroke-width: 0.9; + stroke-linejoin: round; + cursor: pointer; + transition: filter 0.2s; +} + +.land:hover { filter: brightness(1.12); } + +.land.picked { stroke: #fffaf0; stroke-width: 3.5; filter: brightness(1.18); } +.land.open { stroke: #241a10; stroke-width: 2.6; stroke-dasharray: 5 4; } +.land.aimed { stroke: #fffaf0; stroke-width: 3.5; stroke-dasharray: none; } + +@keyframes taken { + from { filter: brightness(1.8) saturate(0.4); } + to { filter: none; } +} +.land.flash { animation: taken 0.9s ease-out; } + +.borders path { fill: none; pointer-events: none; stroke-linejoin: round; stroke-linecap: round; } +.borders .inner { stroke: none; } +.borders .continental { stroke: #241a10; stroke-width: 2.4; } +/* A fence: the real map touches here and the rules do not cross. */ +.borders .closed { stroke: #241a10; stroke-width: 4.5; stroke-dasharray: 1 5; } + +.badges g { cursor: pointer; } +.badges circle { stroke: #fffaf0; stroke-width: 1.6; } +.badges text { + fill: #fffaf0; + font-size: 11px; + font-weight: 900; + text-anchor: middle; + pointer-events: none; + font-variant-numeric: tabular-nums; +} + +/* ------------------------------------------------------------ the end */ + +.ending { + position: absolute; + inset: auto 50% 50% auto; + transform: translate(50%, 50%); + max-width: min(440px, 90%); + padding: 18px 20px; + border: 2px solid #2b2318; + border-radius: 10px; + background: #1b2437; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.45); +} + +.ending h2 { margin: 0 0 6px; color: #ffd479; font-size: 1.3rem; } +.ending p { margin: 0 0 12px; color: #cbd6e6; line-height: 1.45; } + +/* ------------------------------------------------------------ the side */ + +.side { + grid-area: side; + min-height: 0; + display: flex; + flex-direction: column; + gap: 14px; + overflow-y: auto; + padding-right: 4px; + font-size: 0.86rem; +} + +.side section { display: flex; flex-direction: column; gap: 6px; } +.side p { margin: 0; line-height: 1.45; } +.side h2 { margin: 0; font-size: 0.95rem; color: #ffd479; } +.side strong { color: #fffaf0; } +.side section > button { align-self: flex-start; } + +.now { + padding: 10px; + border: 2px solid #2b2318; + border-radius: 8px; + background: #22304a; +} + +.turn { font-weight: 800; font-size: 0.95rem; display: flex; align-items: center; } + +.toggle[aria-pressed='true'] { background: #3c527a; box-shadow: inset 0 0 0 2px #ffd479; } + +.odds { color: #ffd479; font-weight: 700; } + +input[type='range'] { width: 100%; accent-color: #ffd479; } + +.chip { + display: inline-block; + width: 11px; + height: 11px; + border: 2px solid #241a10; + border-radius: 3px; + margin-right: 6px; + vertical-align: -1px; + flex: none; +} + +.throw .dice { display: flex; align-items: center; gap: 10px; } +.throw .dice div { display: flex; gap: 4px; } +.throw .vs { color: #a3b3c9; font-size: 0.75rem; } + +.die { width: 26px; height: 26px; } +.die rect { stroke: #241a10; stroke-width: 0.25; } +.die.attack rect { fill: #c8452f; } +.die.attack circle { fill: #fffaf0; } +.die.defend rect { fill: #fffaf0; } +.die.defend circle { fill: #241a10; } + +.cards { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 5px; } + +.card { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 1px; + min-width: 80px; + padding: 5px 8px; + font-size: 0.76rem; + text-align: left; + background: #2a3a58; +} +.card:disabled { opacity: 1; cursor: default; } +.card[aria-pressed='true'] { box-shadow: inset 0 0 0 2px #ffd479; background: #3c527a; } +.card .glyph { font-size: 0.95rem; color: #ffd479; } +.card.wild .glyph { color: #ff9a8b; } +.card .own { font-size: 0.66rem; color: #9fe0b9; } + +.table { border-collapse: collapse; width: 100%; font-variant-numeric: tabular-nums; } +.table th, .table td { padding: 2px 4px; text-align: right; } +.table thead th { font-size: 0.72rem; color: #a3b3c9; font-weight: 700; } +.table tbody th { text-align: left; white-space: nowrap; } +.table tr.now th { color: #ffd479; } +.table tr.gone { opacity: 0.4; text-decoration: line-through; } + +.continents { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; } +.continents li { display: flex; align-items: center; gap: 6px; } +.continents .shore { width: 16px; height: 0; border-top: 5px solid; border-radius: 3px; } +.continents .held { margin-left: auto; display: flex; align-items: center; font-weight: 700; } + +.log ol { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; } +.log li { font-size: 0.78rem; line-height: 1.35; color: #cbd6e6; } +.log li.heading { margin-top: 5px; color: #ffd479; font-weight: 700; } + +@media (max-width: 860px) { + .app { + height: auto; + grid-template-columns: 1fr; + grid-template-rows: auto auto auto; + grid-template-areas: 'head' 'map' 'side'; + } + .side { overflow: visible; } +} + +/* ---------------------------------------------------------- the landing */ + +.landing { + max-width: 1100px; + margin: 0 auto; + padding: clamp(16px, 4vw, 48px); + line-height: 1.55; +} + +.landing header { display: block; margin-bottom: 22px; } +.landing h1 { font-size: clamp(2rem, 6vw, 3.4rem); } +.landing .tagline { font-size: 1.1rem; color: #a3b3c9; margin: 6px 0 0; } + +.landing .cols { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 4px 36px; +} + +.landing h2 { font-size: 1.05rem; color: #ffd479; margin: 22px 0 6px; letter-spacing: 0.02em; } +.landing p { margin: 0 0 10px; } +.landing ol { margin: 0 0 10px; padding-left: 1.3em; } +.landing ol li { margin-bottom: 6px; } +.landing strong { color: #f2e9d8; } +.landing a { color: #7fc4e8; } + +.landing .choose { margin-top: 26px; } +.landing .choose ul { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 16px; padding: 0; } +.landing .choose li button { display: flex; align-items: center; gap: 8px; padding: 6px 12px; font-size: 0.95rem; } +.landing .choose button.on { background: #3c527a; box-shadow: inset 0 0 0 2px #ffd479; } + +.swatch { width: 15px; height: 15px; border: 2px solid #241a10; border-radius: 4px; } + +.landing .choose .start { + font-size: 1.05rem; + font-weight: 800; + color: #241a10; + background: #ffd479; + padding: 10px 26px; +} + +.landing footer { + margin-top: 34px; + padding-top: 16px; + border-top: 1px solid #2f3b52; + font-size: 0.82rem; + color: #8c98ab; +} +.landing footer p { margin: 0 0 8px; } +.landing footer .ways { font-size: 0.95rem; color: #cbd6e6; margin-bottom: 12px; } diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..638ee50 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,402 @@ +import { useCallback, useEffect, useRef, useState } from 'react' +import './App.css' +import { playFor } from './audio/play' +import { MARCH } from './audio/score' +import { synth } from './audio/synth' +import { Board } from './components/Board' +import { Landing } from './components/Landing' +import { Side } from './components/Side' +import { apply, decide, leanings, type Action, type Leaning } from './game/bot' +import { setsIn } from './game/cards' +import { PLAYER_NAMES } from './game/describe' +import { Game, PLAYER_COUNT } from './game/game' +import type { Territory } from './game/map' + +export type Speed = 'steady' | 'quick' | 'instant' + +/** + * How long a bot takes over each kind of move, in milliseconds, at steady. + * + * Slow enough to follow, which is the whole reason to pace it at all: a bot + * that finishes its turn before you have looked up has told you nothing + * about what it did. The dice get the least, because there are the most of + * them, and a trade the most, because it is the one move everybody at a + * table stops to watch. + */ +const PACE: Record = { + place: 320, + trade: 900, + beginAttack: 250, + attack: 380, + occupy: 320, + endAttack: 200, + fortify: 600, + endTurn: 350, +} + +const pace = (a: Action, speed: Speed, setup: boolean) => + setup ? (speed === 'steady' ? 70 : 25) : PACE[a.type] * (speed === 'steady' ? 1 : 0.35) + +export default function App() { + const [screen, setScreen] = useState<'landing' | 'game'>('landing') + const [color, setColor] = useState(0) + const [, setVersion] = useState(0) + const bump = useCallback(() => setVersion((v) => v + 1), []) + const gameRef = useRef(null) + const leanRef = useRef([]) + const seen = useRef(0) + + const [speed, setSpeed] = useState('steady') + const [music, setMusic] = useState(true) + const [sfx, setSfx] = useState(true) + const [sel, setSel] = useState(null) + const [target, setTarget] = useState(null) + const [flash, setFlash] = useState>(new Set()) + const [autoSetup, setAutoSetup] = useState(false) + const [watching, setWatching] = useState(false) + const [leaving, setLeaving] = useState(false) + /** How many armies a click puts down while reinforcing. */ + const [step, setStep] = useState<1 | 5 | 'all'>(1) + + const g = gameRef.current + + /** Everything that happens goes through here: sound, the flash, and a redraw. */ + const settle = useCallback(() => { + const game = gameRef.current + if (!game) return + const fresh = game.events.slice(seen.current) + seen.current = game.events.length + const taken = new Set() + for (const e of fresh) if (e.kind === 'conquer') taken.add(e.to) + if (taken.size) setFlash(taken) + playFor(fresh, color) + if (game.phase === 'over') synth.stopTune() + bump() + }, [bump, color]) + + // ---------------------------------------------------------- the bots + + const humanOut = g ? !g.players[color].alive : false + + useEffect(() => { + const game = gameRef.current + if (!game || screen !== 'game' || game.phase === 'over') return + const human = game.players[game.current].human + const autopilot = human && game.phase === 'setup' && autoSetup + if (human && !autopilot) return + if (humanOut && !watching) return + + const act = () => { + const a = decide(game, leanRef.current[game.current]) + apply(game, a) + return a + } + + if (speed === 'instant' && !autopilot) { + // Straight through to the next thing that needs a person, with the + // screen drawn once at the end. There is a ceiling in case that never + // comes -- a whole game of bots is a few thousand moves. + const id = window.setTimeout(() => { + for (let i = 0; i < 20000; i++) { + if (game.phase === 'over' || game.players[game.current].human) break + act() + } + settle() + }, 60) + return () => window.clearTimeout(id) + } + + const next = decide(game, leanRef.current[game.current]) + const id = window.setTimeout(() => { + apply(game, next) + settle() + }, pace(next, speed, game.phase === 'setup')) + return () => window.clearTimeout(id) + }) + + // The flash fades on its own. + useEffect(() => { + if (flash.size === 0) return + const id = window.setTimeout(() => setFlash(new Set()), 900) + return () => window.clearTimeout(id) + }, [flash]) + + // ------------------------------------------------------------- music + + useEffect(() => { + synth.setMusic(music) + }, [music]) + useEffect(() => { + synth.setSfx(sfx) + }, [sfx]) + + // --------------------------------------------------------- starting + + const start = () => { + synth.ensure() + const seed = (Date.now() ^ (Math.random() * 0x7fffffff)) >>> 0 + const game = new Game(seed, color) + gameRef.current = game + leanRef.current = leanings(seed, PLAYER_COUNT) + seen.current = 0 + setSel(null) + setTarget(null) + setAutoSetup(false) + setWatching(false) + setLeaving(false) + setScreen('game') + synth.dice(4) + if (music) synth.playTune(MARCH) + settle() + } + + const leave = () => { + synth.stopTune() + gameRef.current = null + setScreen('landing') + } + + // ------------------------------------------------ the player's moves + + const mine = g ? g.current === color && g.phase !== 'over' : false + + /** The one move a click on the map can mean, given the phase. */ + const pick = (t: Territory) => { + if (!g || !mine) return + const own = g.owner[t] === color + + switch (g.phase) { + case 'setup': + if (!own) return synth.reject() + g.place(t) + synth.tap() + return settle() + + case 'reinforce': + if (!own || g.mustTrade || g.reinforcements === 0) return synth.reject() + return act.place(t, step === 'all' ? g.reinforcements : Math.min(step, g.reinforcements)) + case 'attack': + if (own) { + if (t === sel) { + setSel(null) + setTarget(null) + } else if (g.canAttackFrom(t)) { + setSel(t) + setTarget(null) + synth.tap() + } else synth.reject() + return + } + if (sel && g.attackTargets(sel).includes(t)) { + setTarget(t) + synth.written() + } else synth.reject() + return + + case 'fortify': + if (!own) return synth.reject() + if (sel && t !== sel && g.fortifyTargets(sel).includes(t)) { + setTarget(t) + synth.written() + } else if (t === sel) { + setSel(null) + setTarget(null) + } else if (g.fortifyTargets(t).length) { + setSel(t) + setTarget(null) + synth.tap() + } else synth.reject() + return + } + } + + const act = { + place: (t: Territory, n: number) => { + if (!g) return + g.place(t, n) + synth.muster() + // Nothing left to place and no set worth trading: straight on. + if (g.reinforcements === 0 && setsIn(g.hand).length === 0) g.beginAttack() + settle() + }, + trade: (cards: [number, number, number]) => { + g?.trade(cards) + settle() + }, + beginAttack: () => { + g?.beginAttack() + settle() + }, + roll: (dice: number) => { + if (!g || !sel || !target) return + g.attack(sel, target, dice) + afterRoll() + }, + blitz: () => { + if (!g || !sel || !target) return + while (g.phase === 'attack' && g.armies[sel] > 1) g.attack(sel, target) + afterRoll() + }, + occupy: (n: number) => { + if (!g || !g.occupation) return + const to = g.occupation.to + g.occupy(n) + // Stay on the new ground, ready to push on from it. + setSel(g.canAttackFrom(to) ? to : null) + setTarget(null) + settle() + }, + endAttack: () => { + g?.endAttack() + setSel(null) + setTarget(null) + settle() + }, + fortify: (n: number) => { + if (!g || !sel || !target) return + g.fortify(sel, target, n) + setSel(null) + setTarget(null) + settle() + }, + endTurn: () => { + g?.endTurn() + setSel(null) + setTarget(null) + settle() + }, + autoSetup: () => { + setAutoSetup(true) + }, + } + + const afterRoll = () => { + if (!g || !sel) return + if (g.phase === 'attack' && !g.canAttackFrom(sel)) { + setSel(null) + setTarget(null) + } + settle() + } + + // Escape lets go of whatever is picked. + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + setSel(null) + setTarget(null) + } + } + window.addEventListener('keydown', onKey) + return () => window.removeEventListener('keydown', onKey) + }, []) + + const targets: Territory[] = + !g || !sel || !mine + ? [] + : g.phase === 'attack' + ? g.attackTargets(sel) + : g.phase === 'fortify' + ? g.fortifyTargets(sel) + : [] + + if (screen === 'landing' || !g) { + return + } + + const ended = g.phase === 'over' + const won = ended && g.winner === color + + return ( +
+
+

World Conquest

+ +
+ +
+ + {(ended || (humanOut && !watching)) && ( +
+ {ended ? ( + <> +

{won ? 'The world is yours.' : `${PLAYER_NAMES[g.winner!]} holds the world.`}

+

+ {won + ? `Round ${g.round}. Forty-two territories, and nobody left to take them back.` + : humanOut + ? 'You were out of it before the end, but the end came all the same.' + : `Round ${g.round}.`} +

+ + ) : ( + <> +

You are out.

+

Your last territory has gone. The war goes on without you.

+ + )} +
+ {!ended && ( + + )} + +
+
+ )} +
+ + +
+ ) +} diff --git a/src/audio/play.ts b/src/audio/play.ts new file mode 100644 index 0000000..15e48fe --- /dev/null +++ b/src/audio/play.ts @@ -0,0 +1,27 @@ +import type { GameEvent } from '../game/game' +import { synth } from './synth' + +/** + * What a batch of events sounds like. + * + * At a steady pace a batch is one move and gets its sound. At speed it can + * be a whole round, and forty dice and six conquests at once are one + * indistinct crash -- so a batch makes at most one noise, the most important + * thing in it, in this order: the end, somebody going out, a trade, a + * territory taken, a throw of the dice. + */ +export function playFor(events: readonly GameEvent[], human: number) { + if (!synth.sfxOn || events.length === 0) return + const find = (kind: K) => + events.findLast((e): e is Extract => e.kind === kind) + + const win = find('win') + if (win) return win.player === human ? synth.victory() : synth.defeat() + const out = find('eliminate') + if (out) return out.loser === human ? synth.eliminated() : synth.fallen() + const trade = find('trade') + if (trade) return synth.fanfare(trade.armies) + if (find('conquer')) return synth.conquest() + const battle = find('battle') + if (battle) return synth.dice(battle.roll.attack.length + battle.roll.defend.length) +} diff --git a/src/audio/score.ts b/src/audio/score.ts new file mode 100644 index 0000000..410f340 --- /dev/null +++ b/src/audio/score.ts @@ -0,0 +1,98 @@ +import type { Tune } from './synth' + +/** + * The score: one march, and what it is. + * + * Not a quotation of anything. The idiom belongs to nobody -- a minor key, a + * dotted figure, brass moving over a band that holds its chords a bar at a + * time, and a side drum that never lets up -- and it is the same idiom the + * seven-power game uses, so the two sound like the same site without either + * borrowing a bar from the other. + * + * C minor, where that game is in D, and a little quicker: this is a game of + * dice and it moves faster than a game of letters. It loops for as long as + * the war does, and stops when somebody has the world. + * + * Eight bars. The first four climb the dotted figure twice and fall back; + * the second four answer them from a fourth higher and come home on the + * dominant borrowed from the harmonic minor -- the B natural in bar seven is + * the only note that is not in the key, and it is the one that makes the + * loop want to go round again. + */ + +const bar = (...steps: string[]) => steps +const held = (chord: string) => [chord, '=', '=', '=', '=', '=', '=', '='] + +export const MARCH: Tune = { + bpm: 108, + stepsPerBeat: 2, + tracks: [ + { + wave: 'saw', + gain: 0.11, + gate: 0.92, + detune: 6, + filter: { from: 1700, to: 900, q: 1.3 }, + notes: [ + ...bar('C4', '=', '=', 'D4', 'D#4', '=', '=', '='), + ...bar('D4', '=', '=', 'D#4', 'F4', '=', '=', '='), + ...bar('G4', '=', '=', '=', 'F4', '=', 'D#4', '='), + ...bar('D4', '=', '=', '=', '=', '=', '.', '.'), + ...bar('F4', '=', '=', 'G4', 'G#4', '=', '=', '='), + ...bar('G4', '=', '=', 'F4', 'D#4', '=', '=', '='), + ...bar('D4', '=', '=', '=', 'B3', '=', 'D4', '='), + ...bar('C4', '=', '=', '=', '=', '=', '=', '.'), + ], + }, + { + wave: 'pulse50', + gain: 0.04, + gate: 1, + notes: [ + ...held('C3/D#3/G3'), + ...held('G#2/C3/D#3'), + ...held('C3/D#3/G3'), + ...held('G2/B2/D3'), + ...held('F2/G#2/C3'), + ...held('C3/D#3/G3'), + ...held('G2/B2/D3'), + ...held('C3/D#3/G3'), + ], + }, + { + wave: 'triangle', + gain: 0.22, + gate: 1, + notes: [ + ...held('C2'), + ...held('G#1'), + ...held('C2'), + ...held('G1'), + ...held('F1'), + ...held('C2'), + ...held('G1'), + ...held('C2'), + ], + }, + { + // The side drum, marking the end of each half rather than vamping. + wave: 'noise', + gain: 0.14, + notes: [ + ...bar('S', '.', 'S', 'S', '.', 'S', '.', 'S'), + ...bar('S', '.', 'S', 'S', '.', 'S', '.', '.'), + ...bar('S', '.', 'S', 'S', '.', 'S', '.', 'S'), + ...bar('S', 'S', 'S', 'S', 'S', '.', 'S', '.'), + ...bar('S', '.', 'S', 'S', '.', 'S', '.', 'S'), + ...bar('S', '.', 'S', 'S', '.', 'S', '.', '.'), + ...bar('S', '.', 'S', 'S', '.', 'S', '.', 'S'), + ...bar('S', 'S', 'S', 'S', 'S', 'S', 'S', 'S'), + ], + }, + { + wave: 'noise', + gain: 0.28, + notes: Array.from({ length: 64 }, (_, i) => (i % 4 === 0 ? 'K' : '.')), + }, + ], +} diff --git a/src/audio/synth.ts b/src/audio/synth.ts new file mode 100644 index 0000000..4e2c1b7 --- /dev/null +++ b/src/audio/synth.ts @@ -0,0 +1,528 @@ +/** + * The synth, shared with its siblings. + * + * The engine below -- the scheduler, the six voices and the small kit -- is + * the one written for the lemonade stand and carried through the cave game, + * the starship and the seven powers. Same author, same license, and + * deliberately not forked: a look-ahead scheduler is not the part of a game + * worth writing five times. + * + * What is written *for this game* is everything under "the war", and the + * score in `score.ts`. This is a game of dice, so the dice get the most care: + * a throw is heard as the number of dice actually thrown. + */ + +const NOTE_INDEX: Record = { + C: 0, 'C#': 1, D: 2, 'D#': 3, E: 4, F: 5, + 'F#': 6, G: 7, 'G#': 8, A: 9, 'A#': 10, B: 11, +} + +/** "C#4" -> Hz. A4 = 440. */ +export function noteToFreq(note: string): number { + const m = /^([A-G]#?)(-?\d)$/.exec(note) + if (!m) return 0 + const semis = NOTE_INDEX[m[1]] + (Number(m[2]) + 1) * 12 + return 440 * Math.pow(2, (semis - 69) / 12) +} + +export type Wave = 'pulse12' | 'pulse25' | 'pulse50' | 'triangle' | 'saw' | 'noise' + +export interface Track { + wave: Wave + gain: number + /** + * One entry per step. '.' rest, '=' sustain previous, otherwise a note. + * Slashes stack notes into a chord: "F4/A4/C5". On a noise track the + * letter picks the drum: K kick, S snare, C clap, H closed hat, O open hat. + */ + notes: string[] + /** Sweeping lowpass, the whole point of a funk bass. */ + filter?: { from: number; to: number; q?: number } + /** Fraction of the note's length actually sounded; low values are stabs. */ + gate?: number + /** Cents, for a fatter unison. */ + detune?: number +} + +export interface Tune { + bpm: number + stepsPerBeat: number + tracks: Track[] + /** 0 is straight, ~0.15 is a light funk shuffle. Delays every other step. */ + swing?: number +} + +/** Fourier series for a pulse wave of the given duty cycle. */ +function pulseWave(ctx: AudioContext, duty: number, harmonics = 24): PeriodicWave { + const real = new Float32Array(harmonics + 1) + const imag = new Float32Array(harmonics + 1) + for (let n = 1; n <= harmonics; n++) { + imag[n] = (2 / (n * Math.PI)) * Math.sin(n * Math.PI * duty) + } + return ctx.createPeriodicWave(real, imag, { disableNormalization: false }) +} + +export class Synth { + private ctx: AudioContext | null = null + private master!: GainNode + private musicBus!: GainNode + private sfxBus!: GainNode + private waves: Partial> = {} + private noiseBuffer!: AudioBuffer + + private tune: Tune | null = null + private step = 0 + private nextStepTime = 0 + private timer: number | null = null + + musicOn = true + sfxOn = true + + /** Must be called from a user gesture the first time. */ + ensure(): AudioContext { + if (this.ctx) { + if (this.ctx.state === 'suspended') void this.ctx.resume() + return this.ctx + } + const ctx = new AudioContext() + this.ctx = ctx + this.master = ctx.createGain() + this.master.gain.value = 0.5 + this.master.connect(ctx.destination) + + /* + * The buses open at whatever the toggles already say, not at full. + * A one-shot calls `ensure` itself, so a game started with the sound + * turned off would otherwise make exactly one noise -- the first one -- + * before anything got round to muting it. + */ + this.musicBus = ctx.createGain() + this.musicBus.gain.value = this.musicOn ? 0.55 : 0 + this.musicBus.connect(this.master) + + this.sfxBus = ctx.createGain() + this.sfxBus.gain.value = this.sfxOn ? 0.9 : 0 + this.sfxBus.connect(this.master) + + this.waves.pulse12 = pulseWave(ctx, 0.125) + this.waves.pulse25 = pulseWave(ctx, 0.25) + this.waves.pulse50 = pulseWave(ctx, 0.5) + + const len = Math.floor(ctx.sampleRate * 1.5) + const buf = ctx.createBuffer(1, len, ctx.sampleRate) + const data = buf.getChannelData(0) + for (let i = 0; i < len; i++) data[i] = Math.random() * 2 - 1 + this.noiseBuffer = buf + + return ctx + } + + setMusic(on: boolean) { + this.musicOn = on + if (!this.ctx) return + this.musicBus.gain.setTargetAtTime(on ? 0.55 : 0, this.ctx.currentTime, 0.05) + } + + setSfx(on: boolean) { + this.sfxOn = on + if (!this.ctx) return + this.sfxBus.gain.setTargetAtTime(on ? 0.9 : 0, this.ctx.currentTime, 0.02) + } + + // ---------------------------------------------------------------- voices + + private voice( + dest: AudioNode, + wave: Wave, + freq: number, + at: number, + dur: number, + gain: number, + opts: { filter?: Track['filter']; detune?: number } = {}, + ) { + const ctx = this.ensure() + const osc = ctx.createOscillator() + if (wave === 'triangle') osc.type = 'triangle' + else if (wave === 'saw') osc.type = 'sawtooth' + else osc.setPeriodicWave(this.waves[wave] ?? this.waves.pulse50!) + osc.frequency.setValueAtTime(freq, at) + if (opts.detune) osc.detune.setValueAtTime(opts.detune, at) + + const env = ctx.createGain() + const peak = Math.max(0.0001, gain) + env.gain.setValueAtTime(0.0001, at) + env.gain.exponentialRampToValueAtTime(peak, at + 0.008) + env.gain.setValueAtTime(peak, at + Math.max(0.02, dur * 0.6)) + env.gain.exponentialRampToValueAtTime(0.0001, at + dur) + + let node: AudioNode = osc + if (opts.filter) { + const lp = ctx.createBiquadFilter() + lp.type = 'lowpass' + lp.Q.value = opts.filter.q ?? 6 + lp.frequency.setValueAtTime(opts.filter.from, at) + lp.frequency.exponentialRampToValueAtTime( + Math.max(60, opts.filter.to), + at + Math.max(0.05, dur), + ) + osc.connect(lp) + node = lp + } + + node.connect(env).connect(dest) + osc.start(at) + osc.stop(at + dur + 0.02) + } + + /** A small kit: pitched-sine kick, noise-and-tone snare, clap, two hats. */ + private drum(dest: AudioNode, kind: string, at: number, gain: number) { + const ctx = this.ensure() + + if (kind === 'K') { + const osc = ctx.createOscillator() + osc.type = 'sine' + osc.frequency.setValueAtTime(130, at) + osc.frequency.exponentialRampToValueAtTime(42, at + 0.11) + const env = ctx.createGain() + env.gain.setValueAtTime(gain * 1.5, at) + env.gain.exponentialRampToValueAtTime(0.0001, at + 0.24) + osc.connect(env).connect(dest) + osc.start(at) + osc.stop(at + 0.26) + return + } + + const noise = ctx.createBufferSource() + noise.buffer = this.noiseBuffer + const filter = ctx.createBiquadFilter() + const env = ctx.createGain() + let dur = 0.05 + + if (kind === 'S' || kind === 'C') { + filter.type = 'bandpass' + filter.frequency.value = kind === 'S' ? 1900 : 1300 + filter.Q.value = kind === 'S' ? 0.9 : 2.4 + dur = kind === 'S' ? 0.16 : 0.1 + if (kind === 'S') { + // A little body under the crack. + const tone = ctx.createOscillator() + tone.type = 'triangle' + tone.frequency.setValueAtTime(210, at) + tone.frequency.exponentialRampToValueAtTime(150, at + 0.09) + const tenv = ctx.createGain() + tenv.gain.setValueAtTime(gain * 0.6, at) + tenv.gain.exponentialRampToValueAtTime(0.0001, at + 0.1) + tone.connect(tenv).connect(dest) + tone.start(at) + tone.stop(at + 0.12) + } + } else { + filter.type = 'highpass' + filter.frequency.value = 7200 + dur = kind === 'O' ? 0.22 : 0.035 + } + + env.gain.setValueAtTime(gain, at) + env.gain.exponentialRampToValueAtTime(0.0001, at + dur) + noise.connect(filter).connect(env).connect(dest) + noise.start(at) + noise.stop(at + dur + 0.02) + } + + // ------------------------------------------------------------- sequencer + + playTune(tune: Tune, restart = true) { + this.ensure() + if (this.tune === tune && this.timer !== null && !restart) return + this.stopTune() + this.tune = tune + this.step = 0 + this.nextStepTime = this.ctx!.currentTime + 0.08 + this.timer = window.setInterval(() => this.schedule(), 25) + } + + stopTune() { + if (this.timer !== null) window.clearInterval(this.timer) + this.timer = null + this.tune = null + } + + get playing() { + return this.timer !== null + } + + private schedule() { + const ctx = this.ctx + const tune = this.tune + if (!ctx || !tune) return + const stepDur = 60 / tune.bpm / tune.stepsPerBeat + const length = Math.max(...tune.tracks.map((t) => t.notes.length)) + const swing = tune.swing ?? 0 + + while (this.nextStepTime < ctx.currentTime + 0.2) { + // A shuffle pushes every other step late without moving the downbeats. + const at = this.nextStepTime + (this.step % 2 === 1 ? swing * stepDur : 0) + + for (const track of tune.tracks) { + const note = track.notes[this.step % track.notes.length] + if (!note || note === '.' || note === '=') continue + + // A note runs until the next step that is not a sustain marker. + let held = 1 + for (let i = 1; i < length; i++) { + if (track.notes[(this.step + i) % track.notes.length] === '=') held++ + else break + } + const dur = held * stepDur * (track.gate ?? 0.95) + + if (track.wave === 'noise') { + this.drum(this.musicBus, note, at, track.gain) + continue + } + + for (const part of note.split('/')) { + const f = noteToFreq(part) + if (!f) continue + this.voice(this.musicBus, track.wave, f, at, dur, track.gain, { + filter: track.filter, + detune: track.detune, + }) + } + } + this.nextStepTime += stepDur + this.step = (this.step + 1) % length + } + } + + // ------------------------------------------------------------------ sfx + + private seq(notes: [string, number][], wave: Wave = 'pulse25', gain = 0.22) { + const ctx = this.ensure() + let t = ctx.currentTime + 0.01 + for (const [note, dur] of notes) { + if (note !== '.') this.voice(this.sfxBus, wave, noteToFreq(note), t, dur, gain) + t += dur + } + } + + /** A click on the map. Small, dry, and not a musical note. */ + tap() { + const ctx = this.ensure() + const at = ctx.currentTime + 0.01 + const src = ctx.createBufferSource() + src.buffer = this.noiseBuffer + const bp = ctx.createBiquadFilter() + bp.type = 'bandpass' + bp.frequency.value = 2600 + bp.Q.value = 1.4 + const env = ctx.createGain() + env.gain.setValueAtTime(0.1, at) + env.gain.exponentialRampToValueAtTime(0.0001, at + 0.035) + src.connect(bp).connect(env).connect(this.sfxBus) + src.start(at) + src.stop(at + 0.05) + } + + /** A choice made: a territory picked, a number settled. */ + written() { + this.seq([['B4', 0.045], ['E5', 0.08]], 'triangle', 0.14) + } + + /** Something the rules will not take. */ + reject() { + this.seq([['A3', 0.09], ['D#3', 0.18]], 'saw', 0.16) + } + + // -------------------------------------------------------------- the war + + /** + * Dice on a table. + * + * A die is a handful of short bright knocks at uneven spacing, slowing as + * it loses energy, and then it stops -- so the gaps grow and the knocks + * quieten, which between them is the whole illusion. As many as were + * thrown, up to five, each a hair behind the last so they do not merge. + */ + dice(count = 2) { + const ctx = this.ensure() + const at = ctx.currentTime + 0.01 + + for (let die = 0; die < Math.min(5, count); die++) { + let t = at + die * 0.035 + let gap = 0.035 + for (let i = 0; i < 8; i++) { + const knock = ctx.createBufferSource() + knock.buffer = this.noiseBuffer + knock.playbackRate.value = 1.3 + die * 0.15 + const bp = ctx.createBiquadFilter() + bp.type = 'bandpass' + bp.frequency.value = 1500 + ((i * 7 + die * 11) % 5) * 260 + bp.Q.value = 3 + const env = ctx.createGain() + const gain = 0.1 * (1 - i / 9) + env.gain.setValueAtTime(gain, t) + env.gain.exponentialRampToValueAtTime(0.0001, t + 0.03) + knock.connect(bp).connect(env).connect(this.sfxBus) + knock.start(t) + knock.stop(t + 0.04) + t += gap + gap *= 1.2 + } + } + } + + /** + * A field gun, and the shell landing. + * + * A crack with almost no body, then a lowpassed thump a beat later with + * plenty. Artillery at a distance is mostly the second one, so the first is + * kept quiet and quick -- turn it up and the whole thing becomes a drum. + */ + private gun(at: number, gain: number, pitch: number) { + const ctx = this.ensure() + + const crack = ctx.createBufferSource() + crack.buffer = this.noiseBuffer + crack.playbackRate.value = 1.2 + const hp = ctx.createBiquadFilter() + hp.type = 'highpass' + hp.frequency.value = 1100 + const cenv = ctx.createGain() + cenv.gain.setValueAtTime(gain * 0.5, at) + cenv.gain.exponentialRampToValueAtTime(0.0001, at + 0.09) + crack.connect(hp).connect(cenv).connect(this.sfxBus) + crack.start(at) + crack.stop(at + 0.11) + + const body = ctx.createBufferSource() + body.buffer = this.noiseBuffer + body.playbackRate.value = 0.45 + const lp = ctx.createBiquadFilter() + lp.type = 'lowpass' + lp.Q.value = 1.2 + lp.frequency.setValueAtTime(900, at + 0.02) + lp.frequency.exponentialRampToValueAtTime(110, at + 0.5) + const benv = ctx.createGain() + benv.gain.setValueAtTime(0.0001, at + 0.02) + benv.gain.exponentialRampToValueAtTime(gain, at + 0.05) + benv.gain.exponentialRampToValueAtTime(0.0001, at + 0.55) + body.connect(lp).connect(benv).connect(this.sfxBus) + body.start(at + 0.02) + body.stop(at + 0.6) + + const boom = ctx.createOscillator() + boom.type = 'sine' + boom.frequency.setValueAtTime(pitch, at + 0.03) + boom.frequency.exponentialRampToValueAtTime(pitch * 0.35, at + 0.4) + const oenv = ctx.createGain() + oenv.gain.setValueAtTime(gain * 0.8, at + 0.03) + oenv.gain.exponentialRampToValueAtTime(0.0001, at + 0.45) + boom.connect(oenv).connect(this.sfxBus) + boom.start(at + 0.03) + boom.stop(at + 0.5) + } + + /** + * A territory changing hands. + * + * Three guns and a bugle going up. The dice are the fight; this is the + * moment it is over, so it is shorter than a barrage and ends on the + * rising interval that has meant "forward" since there were bugles. + */ + conquest() { + const ctx = this.ensure() + const at = ctx.currentTime + 0.01 + for (const [i, offset] of [0, 0.14, 0.31].entries()) this.gun(at + offset, 0.2 - i * 0.03, 88 - i * 8) + const t = 0.42 + for (const [i, note] of ['C4', 'G4'].entries()) { + this.voice(this.sfxBus, 'saw', noteToFreq(note), at + t + i * 0.13, i ? 0.32 : 0.12, 0.1, { + filter: { from: 2400, to: 1200, q: 1 }, + }) + } + } + + /** Armies raised: a drum and a short rising figure, quieter the more often it plays. */ + muster() { + const ctx = this.ensure() + const at = ctx.currentTime + 0.01 + this.drum(this.sfxBus, 'K', at, 0.22) + this.seq([['G3', 0.06], ['C4', 0.1]], 'triangle', 0.1) + } + + /** + * Cards traded in. + * + * A fanfare, because it is the one moment in the game when armies appear + * out of nowhere and everybody at the table notices. Three notes up and a + * held fourth; the later the set, the more it is worth, and the higher it + * starts. + */ + fanfare(worth: number) { + const lift = Math.min(7, Math.floor(worth / 5)) + const root = ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5'][lift] + const f = noteToFreq(root) + const ctx = this.ensure() + const at = ctx.currentTime + 0.01 + for (const [i, [ratio, dur]] of [[1, 0.12], [1.25, 0.12], [1.5, 0.12], [2, 0.5]].entries()) { + this.voice(this.sfxBus, 'saw', f * ratio, at + i * 0.14, dur, 0.09, { + filter: { from: 2600, to: 1400, q: 1 }, + detune: 5, + }) + } + } + + /** A player gone from the board, and not you: the drum stopping. */ + fallen() { + const ctx = this.ensure() + const at = ctx.currentTime + 0.01 + this.drum(this.sfxBus, 'K', at, 0.3) + this.drum(this.sfxBus, 'K', at + 0.3, 0.2) + this.seq([['.', 0.3], ['E4', 0.18], ['D#4', 0.6]], 'triangle', 0.12) + } + + // ---------------------------------------------------------- the endings + + private bell(at: number, note: string, partials: readonly (readonly [number, number, number])[]) { + const ctx = this.ensure() + const f = noteToFreq(note) + for (const [mult, gain, dur] of partials) { + const osc = ctx.createOscillator() + osc.type = 'sine' + osc.frequency.value = f * mult + const env = ctx.createGain() + env.gain.setValueAtTime(0.0001, at) + env.gain.exponentialRampToValueAtTime(gain, at + 0.01) + env.gain.exponentialRampToValueAtTime(0.0001, at + dur) + osc.connect(env).connect(this.sfxBus) + osc.start(at) + osc.stop(at + dur + 0.05) + } + } + + /** The world. Bells, and guns firing at nothing. */ + victory() { + const ctx = this.ensure() + const at = ctx.currentTime + 0.01 + for (const [i, note] of ['C5', 'G4', 'E4', 'C4', 'G4', 'C5'].entries()) { + this.bell(at + i * 0.42, note, [[1, 0.13, 2.6], [2.76, 0.05, 1.1], [5.4, 0.025, 0.5]]) + } + for (const offset of [0.6, 1.4, 2.1]) this.gun(at + offset, 0.12, 80) + } + + /** Somebody else's world: the same bells, in a minor key, for a flag that is not yours. */ + defeat() { + const ctx = this.ensure() + const at = ctx.currentTime + 0.01 + for (const [i, note] of ['C4', 'D#4', 'G3', 'C3'].entries()) { + this.bell(at + i * 0.5, note, [[1, 0.13, 3], [2.76, 0.04, 1.2]]) + } + } + + /** Your last territory. A single low bell and nothing after it. */ + eliminated() { + const ctx = this.ensure() + this.bell(ctx.currentTime + 0.01, 'C2', [[1, 0.16, 4], [2.76, 0.05, 1.6]]) + } +} + +export const synth = new Synth() diff --git a/src/components/Board.tsx b/src/components/Board.tsx new file mode 100644 index 0000000..69cfce6 --- /dev/null +++ b/src/components/Board.tsx @@ -0,0 +1,156 @@ +import { memo } from 'react' +import board from '../game/board.json' +import type { Game } from '../game/game' +import { LINK_LIST, TERRITORIES, TERRITORY_IDS, type Continent, type Territory } from '../game/map' +import { PLAYER_NAMES } from '../game/describe' + +/** + * The map. + * + * The shapes are real geography, grouped into the forty-two territories by + * `tools/map.py`; the links are the classic board's, from `map.ts`. Where + * the two disagree the rules win and the drawing says so: a dashed lane + * across water that the rules cross, and a closed border -- drawn as a + * fence -- where two territories happen to touch and the rules never let + * anybody across. + * + * Owners are the fill. Continents are the coastline: each one's shore is + * drawn in its own color, under everything, so the six read at a glance + * without taking the fill away from the question that matters, which is + * whose it is. + */ + +export const COLORS = ['#d8543f', '#3f7fd8', '#e8b53a', '#3fae7a', '#9a62c9', '#7c8a99'] as const +/** A darker tone of each, for the army badges, so a number is legible on its own color. */ +export const DARK = ['#8e2a1b', '#1d4a8e', '#94690f', '#1d6b48', '#5c3182', '#434e5a'] as const + +export const SHORE: Record = { + 'north-america': '#e3a33b', + 'south-america': '#d4543b', + europe: '#4a79c9', + africa: '#8a5a33', + asia: '#3e9b5a', + australia: '#a052b8', +} + +interface Drawn { + d: string + label: [number, number] +} + +const DRAWN = board.territories as unknown as Record +const NEAR = board.near as unknown as Record +const BORDERS = board.borders as Record +const TOUCHING = new Set(board.touching.map(([a, b]) => `${a}|${b}`)) +const LINKED = new Set(LINK_LIST.map(([a, b]) => [a, b].sort().join('|'))) +const WRAP = board.wrap as unknown as { alaska: [number, number]; kamchatka: [number, number] } + +/** Links drawn as lanes: every one the shapes do not already show. */ +const LANES = LINK_LIST.map(([a, b]) => [a, b].sort().join('|')) + .filter((k) => !TOUCHING.has(k) && k !== 'alaska|kamchatka') + .map((k) => ({ key: k, xy: NEAR[k] })) + .filter((l) => l.xy) + +const edgeKind = (key: string) => { + const [a, b] = key.split('|') as [Territory, Territory] + if (!LINKED.has(key)) return 'closed' + return TERRITORIES[a].continent === TERRITORIES[b].continent ? 'inner' : 'continental' +} + +/** Drawn once: nothing about the land or the sea changes during a game. */ +const Ground = memo(function Ground() { + return ( + <> + + + {TERRITORY_IDS.map((t) => ( + + ))} + + + {LANES.map((l) => ( + + ))} + {/* Across the date line, which is the edge of the board. */} + + + + + ) +}) + +export function Board({ + game, + selected, + targets, + target, + flash, + onPick, +}: { + game: Game + selected: Territory | null + targets: readonly Territory[] + target: Territory | null + /** Territories that just changed hands, briefly marked. */ + flash: ReadonlySet + onPick: (t: Territory) => void +}) { + return ( + + + + {TERRITORY_IDS.map((t) => { + const owner = game.owner[t] + const cls = [ + 'land', + t === selected && 'picked', + t === target && 'aimed', + targets.includes(t) && 'open', + flash.has(t) && 'flash', + ] + .filter(Boolean) + .join(' ') + return ( + onPick(t)} + > + + {TERRITORIES[t].name}: {PLAYER_NAMES[owner]}, {game.armies[t]}{' '} + {game.armies[t] === 1 ? 'army' : 'armies'} + + + ) + })} + + + {Object.entries(BORDERS).map(([key, d]) => ( + + {edgeKind(key) === 'closed' && A closed border: no link on the classic board} + + ))} + + + + ) +} diff --git a/src/components/Landing.tsx b/src/components/Landing.tsx new file mode 100644 index 0000000..24c8c12 --- /dev/null +++ b/src/components/Landing.tsx @@ -0,0 +1,143 @@ +import { PLAYER_NAMES } from '../game/describe' +import { PLAYER_COUNT, STARTING_ARMIES } from '../game/game' +import { CONTINENTS, CONTINENT_IDS } from '../game/map' +import { COLORS, SHORE } from './Board' + +/** + * The page before the map. + * + * Everybody thinks they know the rules to this one, and most people learned + * them from somebody who had changed three. So they are stated here in full + * -- they are short -- along with the two or three places where tables + * differ, and which way this one goes. + * + * It is also the honest place for the license and for where the map came + * from, and for saying plainly whose game this is not. + */ +export function Landing({ + color, + onPick, + onStart, +}: { + color: number + onPick: (c: number) => void + onStart: () => void +}) { + return ( +
+
+

World Conquest

+

+ Forty-two territories, six armies, and the dice. You against five, until one of you holds + the world. +

+
+ +
+
+

The whole game

+

+ The world is dealt out at random, seven territories each, and everybody puts down{' '} + {STARTING_ARMIES} armies on their own ground, a turn at a time. Then each turn goes the + same way: +

+
    +
  1. + Reinforce. A third of your territories, never fewer than three, + plus a bonus for every whole continent you hold. +
  2. +
  3. + Attack as often as you like, from any territory with an army to + spare into any enemy territory next to it. Up to three dice attack and up to two + defend; highest against highest, and a tie goes to the defender. Empty a territory + and you march in. +
  4. +
  5. + Fortify once: move armies between two of your territories joined + by your own ground. Then the turn passes. +
  6. +
+

+ Take at least one territory in a turn and you draw a card. Three alike, or one of + each, trade in for armies: 4, then 6, 8, 10, 12, 15, and five more every time after. + The count is shared by the whole table, so every set traded makes the next one + dearer for everybody. Hold five cards and you must trade. Finish a player off and + their cards are yours. +

+
+ +
+

The continents

+
    + {CONTINENT_IDS.map((c) => ( +
  • + + {CONTINENTS[c].name} +{CONTINENTS[c].bonus} a turn +
  • + ))} +
+

+ The shapes on the map are the real ones; the links are the classic board's. Dashed + lines cross the water where the rules do, and Alaska meets Kamchatka off the edge of + the world. The one border drawn as a fence, between Siberia and the steppe, is a place + the real map touches and the rules never let anybody across. +

+

Where tables differ

+

+ Fortifying goes anywhere along a chain of your own territories, not only next door. + The defender always rolls every die they can. Sets escalate for the whole table rather + than staying fixed. A card showing a territory you hold puts two more armies there + when you trade it. +

+
+
+ +
+

Take a color

+
    + {Array.from({ length: PLAYER_COUNT }, (_, c) => ( +
  • + +
  • + ))} +
+ +
+ +
+

+ {/* + Absolute rather than "/", because this game is served from a + subdirectory in production and from the root in development. + */} + The rest of the games + {' · '} + Source +

+

+ World Conquest is free software under the{' '} + + GNU Affero General Public License, version 3 or later + + . It comes with no warranty whatsoever. You may use, study, change and share it; if you run + a changed version where other people can reach it, they are entitled to its source. +

+

+ The coastlines and borders are from Natural Earth, + which is in the public domain. Which country belongs to which territory is this + project's own choice. +

+

+ This is not the board game it is modeled on, and is not connected to the people who + publish it. No artwork, text or trademark of theirs is used here: every mark on the screen + and every note of the music is generated in code in this repository. +

+
+
+ ) +} diff --git a/src/components/Side.tsx b/src/components/Side.tsx new file mode 100644 index 0000000..e1b5bed --- /dev/null +++ b/src/components/Side.tsx @@ -0,0 +1,473 @@ +import { useState } from 'react' +import { isSet, setsIn, type Card } from '../game/cards' +import { winChance } from '../game/dice' +import { PLAYER_NAMES, describe } from '../game/describe' +import type { Game } from '../game/game' +import { CONTINENTS, CONTINENT_IDS, TERRITORIES, inContinent, type Territory } from '../game/map' +import { COLORS, SHORE } from './Board' + +/** + * Everything beside the map: whose turn it is and what they can do, the + * dice, your cards, the table and the log. + * + * The map is where you point; this is where you are told what pointing + * will do. Every control here is only ever offered when the rules would + * take it, which is why so much of it is conditional. + */ + +interface Acts { + place: (t: Territory, n: number) => void + trade: (cards: [number, number, number]) => void + beginAttack: () => void + roll: (dice: number) => void + blitz: () => void + occupy: (n: number) => void + endAttack: () => void + fortify: (n: number) => void + endTurn: () => void + autoSetup: () => void +} + +const Chip = ({ p }: { p: number }) => + +const name = (t: Territory) => TERRITORIES[t].name + +/** A chance as a player would say it: never a flat 0% or 100% while the dice can still disagree. */ +function percent(p: number) { + if (p >= 0.995) return 'better than 99%' + if (p > 0 && p < 0.005) return 'less than 1%' + return `${Math.round(p * 100)}%` +} + +export function Side({ + game: g, + human, + sel, + target, + autoSetup, + step, + onStep, + act, +}: { + game: Game + human: number + sel: Territory | null + target: Territory | null + autoSetup: boolean + step: 1 | 5 | 'all' + onStep: (s: 1 | 5 | 'all') => void + act: Acts +}) { + const mine = g.current === human && g.phase !== 'over' + const who = (p: number) => (p === human ? 'You' : PLAYER_NAMES[p]) + + return ( + + ) +} + +// ------------------------------------------------------------ controls + +function Controls({ + g, + sel, + target, + autoSetup, + step, + onStep, + act, +}: { + g: Game + sel: Territory | null + target: Territory | null + autoSetup: boolean + step: 1 | 5 | 'all' + onStep: (s: 1 | 5 | 'all') => void + act: Acts +}) { + switch (g.phase) { + case 'setup': + return ( + <> +

+ {g.toPlace[g.current]} armies to put down, one at a time, taking turns. + Click one of your territories. +

+ {!autoSetup && ( + + )} + + ) + + case 'reinforce': + if (g.mustTrade) + return ( +

+ {g.hand.length} cards in hand: trade a set before anything else. Your cards + are below. +

+ ) + if (g.reinforcements > 0) + return ( + <> +

+ {g.reinforcements} to place. Click your territories. + {g.resume && ' Then back to the fighting.'} +

+
+ {([1, 5, 'all'] as const).map((s) => ( + + ))} +
+ + ) + return ( + <> +

All placed. Trade another set now, or go to war.

+ + + ) + + case 'attack': + return + + case 'occupy': + return + + case 'fortify': + return + + default: + return null + } +} + +function Attack({ g, sel, target, act }: { g: Game; sel: Territory | null; target: Territory | null; act: Acts }) { + const most = sel ? Math.min(3, g.armies[sel] - 1) : 0 + return ( + <> + {!sel &&

Pick one of your territories with an army to spare and an enemy next door.

} + {sel && !target && ( +

+ From {name(sel)} ({g.armies[sel]}). Now pick who to attack. +

+ )} + {sel && target && ( + <> +

+ {name(sel)} ({g.armies[sel]}) against {name(target)} ( + {g.armies[target]}, {PLAYER_NAMES[g.owner[target]]}) +

+

Pressed to the end, {percent(winChance(g.armies[sel] - 1, g.armies[target]))} to take it.

+
+ {[3, 2, 1] + .filter((d) => d <= most) + .map((d) => ( + + ))} + +
+ + )} + + + ) +} + +function Occupy({ g, act }: { g: Game; act: Acts }) { + const o = g.occupation! + const [n, setN] = useState(o.max) + return ( + <> +

+ {name(o.to)} is yours. How many march in from {name(o.from)}? +

+ {o.max > o.min && ( + setN(Number(e.target.value))} + aria-label="Armies to move in" + /> + )} +
+ + {o.max > o.min && ( + + leaving {g.armies[o.from] - n} behind + + )} +
+ + ) +} + +function Fortify({ g, sel, target, act }: { g: Game; sel: Territory | null; target: Territory | null; act: Acts }) { + const max = sel ? g.armies[sel] - 1 : 0 + const [n, setN] = useState(max) + return ( + <> +

+ {!sel + ? 'One move to finish: pick a territory to move armies from, or end the turn.' + : !target + ? `From ${name(sel)}. Pick where to, anywhere joined to it by your own territories.` + : `${name(sel)} to ${name(target)}.`} +

+ {sel && target && ( + <> + {max > 1 && ( + setN(Number(e.target.value))} + aria-label="Armies to move" + /> + )} + + + )} + + + ) +} + +// ------------------------------------------------------------ the dice + +function Die({ face, side }: { face: number; side: 'attack' | 'defend' }) { + const pips: Record = { + 1: [[2, 2]], + 2: [[1, 1], [3, 3]], + 3: [[1, 1], [2, 2], [3, 3]], + 4: [[1, 1], [3, 1], [1, 3], [3, 3]], + 5: [[1, 1], [3, 1], [2, 2], [1, 3], [3, 3]], + 6: [[1, 1], [3, 1], [1, 2], [3, 2], [1, 3], [3, 3]], + } + return ( + + + {pips[face].map(([x, y], i) => ( + + ))} + + ) +} + +function LastThrow({ g, human }: { g: Game; human: number }) { + const last = g.events.findLast((e) => e.kind === 'battle') + if (!last || last.kind !== 'battle') return null + const { roll } = last + const who = (p: number) => (p === human ? 'You' : PLAYER_NAMES[p]) + return ( +
+

+ {who(last.player)}, {name(last.from)} → {name(last.to)} +

+
+
+ {roll.attack.map((f, i) => ( + + ))} +
+ vs +
+ {roll.defend.map((f, i) => ( + + ))} +
+
+

+ Attacker lost {roll.attackerLoss}, defender lost {roll.defenderLoss}. +

+
+ ) +} + +// ------------------------------------------------------------ the cards + +const MARK_GLYPH: Record = { + infantry: '▲', + cavalry: '●', + artillery: '■', + wild: '★', +} + +function Hand({ g, human, act }: { g: Game; human: number; act: Acts }) { + const hand = g.players[human].cards + const [chosen, setChosen] = useState([]) + const canTrade = g.current === human && g.phase === 'reinforce' + const picked = chosen.filter((i) => i < hand.length) + const cards = picked.map((i) => hand[i]) + const ready = picked.length === 3 && isSet(cards) + const sets = setsIn(hand) + + const toggle = (i: number) => + setChosen((c) => (c.includes(i) ? c.filter((x) => x !== i) : c.length < 3 ? [...c, i] : c)) + + return ( +
+

+ Your cards · next set pays {g.nextSetValue} +

+ {hand.length === 0 ? ( +

None yet. Take a territory in a turn and you draw one at the end of it.

+ ) : ( +
    + {hand.map((c, i) => ( +
  • + +
  • + ))} +
+ )} + {canTrade && sets.length > 0 && ( +
+ + {!ready && ( + + )} +
+ )} + {hand.length > 0 && ( +

Three alike, one of each, or any two with a wild.

+ )} +
+ ) +} + +// -------------------------------------------------------------- the log + +function Log({ g, human }: { g: Game; human: number }) { + const lines = describe(g.events, human).slice(-60).reverse() + return ( +
+

What happened

+
    + {lines.map((l, i) => ( +
  1. + + {l.text} +
  2. + ))} +
+
+ ) +} diff --git a/src/game/board.json b/src/game/board.json new file mode 100644 index 0000000..b47db45 --- /dev/null +++ b/src/game/board.json @@ -0,0 +1 @@ +{"source":"Natural Earth 1:50m, public domain; grouping by tools/map.py","width":1448,"height":710,"territories":{"afghanistan":{"d":"M866.4,247.6L868.1,244.6L867.2,243.5L867.6,241.8L869.7,238.3L870.8,238.2L873.4,240.9L875.0,240.6L874.5,237.0L881.2,233.2L883.2,231.1L885.4,232.4L888.9,231.2L890.3,232.4L893.4,232.4L896.6,234.7L898.2,237.4L898.6,234.9L902.7,237.1L906.0,234.8L908.0,234.6L909.8,235.5L911.4,234.4L913.4,234.6L918.1,237.6L920.2,235.7L922.0,236.7L924.9,236.1L926.2,233.2L920.1,230.0L923.9,227.8L923.1,226.0L924.2,224.4L928.3,224.2L924.8,222.7L926.1,221.4L923.9,220.8L924.9,218.7L927.7,219.1L937.8,216.7L940.4,216.9L941.9,215.3L952.6,213.4L952.8,212.4L955.9,211.1L960.7,212.3L963.0,211.6L964.7,215.5L964.4,217.6L968.7,217.0L969.8,219.1L970.5,218.0L972.9,219.0L974.8,218.5L973.2,220.4L973.6,221.8L975.4,220.9L977.4,221.6L981.7,218.3L987.3,216.3L986.6,218.0L985.7,218.0L985.9,218.6L991.4,222.8L1000.0,236.1L1002.9,233.4L1004.5,233.9L1004.3,235.1L1005.6,235.2L1005.9,236.3L1010.0,236.4L1013.4,235.0L1015.8,236.1L1017.3,238.9L1020.0,239.9L1021.0,242.2L1024.7,242.8L1026.7,241.3L1026.5,242.4L1029.3,244.9L1027.2,245.1L1026.2,247.8L1023.0,248.6L1022.1,250.9L1022.6,254.3L1021.9,255.3L1019.1,256.4L1012.1,254.6L1009.3,262.6L1010.4,263.5L1010.1,265.0L1006.8,263.9L999.5,266.1L1001.9,267.0L1001.4,270.0L1003.1,274.6L1001.6,275.2L1002.2,276.0L1000.7,277.0L1000.9,280.0L995.0,282.3L992.5,284.6L987.6,284.9L985.3,288.0L982.7,288.3L982.1,286.7L976.0,289.5L975.6,291.7L974.5,292.4L975.0,295.8L979.2,296.9L979.6,300.9L980.5,301.9L977.6,302.9L978.2,303.6L976.2,304.5L970.5,304.5L966.5,306.3L964.8,308.0L966.5,312.0L963.9,314.9L964.4,316.8L962.6,317.5L959.6,317.3L961.0,320.5L958.0,321.7L957.0,324.3L957.1,326.5L956.3,327.4L952.7,327.1L950.4,328.4L951.0,329.2L950.4,329.4L947.3,329.5L945.4,331.5L944.9,335.7L936.4,337.7L929.9,337.7L923.4,335.7L927.2,331.0L926.6,329.0L923.3,328.5L923.3,325.1L922.2,321.5L923.7,319.5L922.0,318.9L921.9,316.9L923.5,315.8L922.9,315.0L924.3,313.5L925.1,310.5L924.5,305.3L921.4,305.3L917.2,301.3L913.0,300.6L909.4,299.2L908.8,298.1L901.5,298.8L898.8,301.5L895.7,302.1L895.5,294.6L894.8,293.4L892.6,293.2L892.9,291.5L894.4,291.8L893.9,290.1L892.0,289.8L892.1,290.8L890.9,287.8L891.8,284.8L895.5,286.6L897.4,286.4L897.5,285.6L898.8,285.5L898.8,284.6L896.4,282.5L895.0,279.6L891.9,280.3L891.4,284.0L889.9,280.7L890.4,276.5L885.2,274.5L885.2,273.0L883.3,269.5L881.0,268.5L881.6,267.4L886.0,267.8L884.0,266.0L885.7,263.8L890.5,263.7L890.4,263.0L890.7,263.8L892.6,264.0L891.1,262.7L892.5,259.6L892.3,256.3L889.9,255.6L888.6,256.4L884.7,255.0L876.7,258.9L874.2,257.6L874.2,256.8L875.8,256.7L872.7,252.0L869.2,251.8L868.3,249.3Z","label":[941.8,270.9]},"alaska":{"d":"M7.6,149.0L14.4,145.8L17.5,145.9L16.7,144.7L22.2,142.8L25.1,142.6L25.4,142.9L24.0,145.3L25.2,146.2L32.3,146.5L34.2,144.9L35.8,145.2L32.3,144.0L29.6,140.8L30.6,140.4L33.6,143.7L39.0,144.0L38.6,142.7L34.4,143.1L32.6,142.1L33.1,139.9L25.9,139.4L23.5,136.0L12.9,130.8L14.5,130.3L15.2,127.1L22.8,126.8L25.9,125.1L28.2,121.0L32.5,116.9L32.5,117.9L36.0,117.1L39.5,115.0L40.1,115.2L39.7,116.8L40.5,117.1L41.6,115.5L39.7,114.7L41.3,113.6L48.4,113.0L54.1,109.0L57.7,111.1L55.7,112.5L56.1,113.2L60.2,111.4L63.2,113.5L70.8,113.1L70.2,114.6L72.9,115.2L72.5,116.0L82.9,115.6L101.5,119.4L107.1,118.4L116.0,121.7L116.0,182.6L120.1,183.3L123.7,182.4L123.3,184.0L129.6,189.0L130.2,190.9L133.7,189.5L134.7,186.8L138.1,185.7L139.8,187.0L140.2,188.7L146.4,193.8L151.1,200.8L150.7,201.5L151.9,201.8L151.6,202.8L152.7,204.3L159.6,207.0L159.9,211.6L157.7,214.5L156.6,214.3L155.8,212.3L156.9,211.5L155.6,207.3L152.9,208.3L152.2,210.2L151.5,210.1L151.2,209.0L153.4,206.5L150.9,204.8L151.7,206.3L151.5,208.0L149.4,207.2L150.6,204.2L148.7,201.7L146.1,201.0L145.4,198.3L146.9,198.4L145.9,197.5L146.3,196.7L145.1,197.1L143.9,195.8L144.2,193.5L143.2,194.9L140.9,193.6L138.5,188.1L138.1,189.3L139.6,194.8L136.4,193.9L135.8,191.6L136.4,191.0L135.5,190.2L135.1,191.8L132.1,190.2L132.0,191.1L135.8,193.6L133.6,194.8L127.0,190.1L120.8,187.1L121.9,186.3L122.1,184.7L122.9,186.4L123.3,185.2L122.3,184.4L118.3,186.2L114.4,185.0L114.4,183.8L113.3,184.6L103.4,184.3L100.4,182.4L100.9,180.6L99.4,182.6L96.4,181.6L97.2,180.5L93.7,180.1L94.3,179.6L93.5,178.5L94.5,177.6L87.5,179.4L88.6,177.3L85.8,179.6L86.9,180.2L86.6,181.2L88.1,181.3L87.1,183.6L88.3,183.9L85.0,184.8L82.4,183.8L81.6,185.8L81.1,185.0L76.3,188.9L72.2,188.8L75.7,185.9L74.0,186.5L72.6,185.7L74.4,182.8L74.7,180.0L78.2,178.3L83.6,179.1L79.8,177.4L82.2,175.4L73.6,178.5L70.5,182.4L68.5,182.8L69.4,184.5L67.4,185.3L67.6,186.2L65.5,186.5L63.3,189.4L66.3,190.6L66.2,191.8L63.9,193.3L63.0,195.3L60.0,196.1L54.3,199.9L53.5,201.9L46.3,205.2L45.9,205.8L46.9,206.5L46.1,207.2L45.6,206.6L41.4,209.8L40.9,208.5L35.3,211.1L34.1,211.1L34.8,210.0L33.9,209.8L31.7,212.5L30.5,213.0L29.5,211.9L29.3,213.3L27.6,212.9L26.7,214.1L26.9,212.6L33.2,208.2L35.1,207.5L36.4,207.7L36.0,208.3L36.8,209.0L38.8,208.7L37.8,207.6L38.8,205.9L44.3,202.7L45.3,203.2L45.4,201.8L46.7,200.3L49.9,199.2L49.2,198.1L49.6,195.9L50.9,195.0L49.9,193.8L51.9,192.0L52.4,190.3L47.2,192.6L46.0,191.3L47.3,190.3L46.0,189.9L44.9,190.7L44.8,193.7L44.2,193.9L41.3,190.9L40.3,191.4L38.5,190.1L34.6,192.3L31.4,192.5L33.1,191.6L33.2,189.9L32.1,189.5L32.7,186.9L30.3,182.7L31.9,180.3L29.5,182.7L29.7,184.5L25.3,185.6L18.6,181.2L20.8,179.0L22.7,179.8L22.7,180.8L26.2,179.9L24.4,179.3L25.5,179.1L25.0,178.6L19.5,178.8L20.4,177.8L18.8,176.8L17.7,177.8L16.5,176.4L16.6,175.2L15.4,175.1L16.6,174.2L15.7,173.5L17.4,173.1L17.2,171.7L19.2,169.4L20.8,169.2L20.8,166.6L22.3,165.8L22.4,164.7L26.9,165.8L30.9,162.7L35.6,162.5L36.9,160.8L36.0,158.1L34.0,157.0L36.3,156.0L36.6,154.9L35.5,153.8L33.0,154.5L28.8,157.3L27.2,155.6L27.4,157.0L25.1,156.0L20.1,156.8L15.4,156.0L12.3,152.3L15.0,151.3ZM85.9,181.3L86.4,181.0L85.9,181.2ZM21.2,168.5L21.4,168.0L20.9,168.5Z M61.2,199.9L63.5,198.2L64.9,200.0L65.1,198.1L64.4,197.3L66.8,197.3L66.1,196.4L67.0,197.4L67.4,196.4L68.5,197.5L70.0,197.1L71.1,198.5L68.2,199.1L69.3,200.0L66.2,201.0L64.1,203.3L64.7,202.0L62.9,201.2L63.3,201.9L62.6,202.4Z M145.0,207.7L145.8,206.9L145.7,205.7L147.2,205.8L147.7,207.2L149.6,208.2L151.3,210.6L149.9,209.9L149.7,210.6L152.1,212.1L151.9,214.8L147.5,211.4L148.1,211.1L147.8,209.9L145.3,208.9L146.7,208.5L146.8,207.6Z M1432.7,163.1L1433.4,161.5L1435.9,162.4L1438.3,161.7L1441.8,163.7L1445.1,164.1L1441.3,166.3L1437.9,163.7L1433.9,164.0Z M133.7,196.4L134.7,194.9L135.4,195.6L136.9,194.7L137.5,196.3L140.0,195.9L140.1,197.3L138.8,197.6L140.5,198.6L137.7,198.2L137.2,199.6L136.4,199.4Z M10.3,183.2L16.6,182.4L17.6,185.0L15.4,185.9Z M136.7,200.4L138.2,198.9L140.1,200.0L141.5,203.6L141.4,206.4L140.2,205.1L140.3,203.9L138.7,203.0L139.2,201.8L138.5,200.9L136.8,201.9Z M140.3,194.3L143.7,196.2L144.7,198.4L142.8,196.1L144.3,200.0L141.6,201.5L142.0,199.2Z M20.4,215.8L22.1,213.8L24.8,213.0L27.7,215.1L23.7,215.4L21.6,216.6Z M152.6,210.9L153.5,208.6L154.9,207.9L156.1,209.5L155.2,212.1L154.2,211.1L152.9,212.3Z M144.1,201.9L147.7,202.1L148.2,203.8L146.8,203.1L147.4,204.9L145.6,205.0L145.3,203.1Z M8.8,222.5L12.7,220.7L11.6,219.8L11.8,219.1L15.1,219.1L13.8,220.3L14.8,220.4L12.9,221.6Z","label":[70.1,146.9]},"alberta":{"d":"M123.8,184.4L272.0,184.4L272.0,208.7L274.5,245.4L187.7,245.4L187.1,244.0L187.8,243.7L186.8,243.5L187.1,242.0L185.9,243.3L183.9,242.2L184.0,241.6L184.7,242.3L185.3,242.1L184.4,240.1L182.9,241.4L180.9,240.1L179.8,238.0L180.6,236.8L180.5,235.8L179.2,237.7L177.8,237.0L177.4,237.7L174.4,237.4L175.7,236.6L173.9,236.6L174.4,235.8L174.0,234.8L171.8,235.6L169.2,234.1L170.3,231.7L172.3,231.2L170.6,231.2L169.1,232.3L168.5,230.8L168.8,228.6L171.0,227.5L172.3,228.0L171.1,226.6L168.8,228.1L167.6,230.7L166.6,228.8L167.9,227.9L167.9,226.7L166.9,227.7L167.6,224.8L166.2,224.9L165.9,223.5L163.3,221.3L164.3,220.3L164.5,221.2L166.4,221.8L165.0,220.6L165.7,219.4L164.1,219.7L161.7,222.9L158.7,220.3L159.8,218.0L161.2,217.6L159.7,217.8L158.3,216.5L161.5,210.8L160.8,210.3L160.1,212.6L157.7,214.5L159.8,211.6L159.8,207.3L152.7,204.3L146.4,193.8L140.2,188.7L139.8,186.9L138.1,185.7L134.9,186.8L133.7,189.5L130.2,190.9L129.6,189.0ZM184.9,239.7L184.4,239.4L184.6,240.0ZM172.0,225.4L172.2,225.7L172.1,225.3ZM167.6,222.1L167.9,222.2L167.6,222.0ZM167.3,221.7L167.0,221.8L167.3,221.9Z M166.6,236.5L168.3,235.7L173.2,237.5L177.9,238.4L180.7,242.6L184.0,244.2L186.9,248.2L185.7,248.9L179.9,246.9L179.5,246.3L180.4,245.3L178.0,245.7L176.7,244.9L177.3,244.5L176.2,243.4L173.9,243.3L173.8,242.4L174.6,241.9L173.0,240.7L171.5,240.8L171.0,239.5L168.5,239.5L168.5,237.6ZM180.7,244.7L180.4,245.2L180.4,245.3ZM170.0,237.1L168.7,237.6L170.0,237.8Z M147.7,218.0L151.0,218.4L151.4,219.5L149.9,220.7L151.2,220.5L151.5,219.5L151.5,218.6L153.3,218.0L152.2,222.6L150.6,223.2L149.0,222.5L149.9,222.1L147.8,220.0Z M149.8,223.5L153.0,223.2L153.5,224.6L152.3,224.9L154.2,225.8L153.7,226.5L155.5,228.4L154.3,228.3L151.3,225.4L151.4,224.2Z","label":[203.0,215.2]},"argentina":{"d":"M377.2,660.8L380.2,657.7L379.7,656.2L383.4,655.7L384.2,653.9L384.7,654.1L383.9,657.2L382.6,658.0L384.1,657.7L385.1,655.9L384.6,659.7L385.6,656.4L385.1,654.3L388.2,654.1L386.2,653.1L386.4,652.4L389.3,649.9L389.3,649.1L386.9,647.8L388.0,645.2L387.7,643.7L389.0,642.2L388.9,638.5L390.3,639.2L390.0,637.2L388.7,636.8L390.5,635.6L390.7,635.1L389.8,635.8L388.2,635.0L387.0,636.2L385.1,636.1L385.5,635.3L384.6,634.6L384.1,632.4L387.1,624.1L385.9,620.8L385.4,615.4L387.1,614.6L389.5,607.3L391.1,605.1L392.0,600.9L393.3,598.6L393.0,596.1L394.2,594.1L393.2,585.2L394.7,580.9L393.9,577.7L395.3,575.3L397.4,566.5L398.4,554.8L397.6,553.4L398.7,551.7L399.7,546.0L398.3,532.9L400.3,532.4L400.6,530.3L402.0,529.5L403.6,531.7L404.1,535.5L406.1,537.4L405.2,538.6L405.8,539.7L405.0,540.2L405.0,541.5L407.2,545.2L408.8,551.9L411.2,551.6L415.1,547.3L416.9,548.6L421.6,549.1L422.7,551.6L424.3,548.3L428.6,548.1L429.4,549.1L430.9,542.1L433.0,538.3L443.6,536.8L447.3,539.0L448.7,544.0L448.2,548.6L452.3,549.3L454.2,548.5L456.8,549.6L458.3,556.4L461.5,555.8L463.0,556.8L461.5,563.2L464.0,563.3L465.3,566.1L464.6,569.9L460.7,571.8L457.1,574.6L457.3,575.4L456.4,575.3L449.6,583.3L451.1,583.6L452.7,582.9L455.8,585.8L456.0,587.1L457.6,586.1L459.3,588.0L465.0,591.5L467.5,594.5L465.9,596.2L465.9,598.6L466.5,599.0L465.9,600.2L463.3,603.2L460.4,604.4L457.3,603.7L455.0,604.2L451.3,602.2L448.7,602.3L446.2,598.9L446.5,596.5L447.6,595.5L447.2,593.5L447.3,595.5L446.3,596.2L445.8,598.6L446.1,602.2L450.8,605.5L451.4,607.0L450.7,609.3L453.2,611.2L453.3,613.1L449.8,618.8L447.3,620.4L440.7,622.3L433.6,623.1L430.7,622.1L430.8,624.2L431.8,624.8L430.4,628.7L431.0,631.0L430.4,632.0L425.5,633.3L420.5,631.3L419.5,631.9L420.1,637.8L422.3,639.4L424.8,637.8L425.5,640.6L423.9,641.5L422.1,639.8L420.1,640.5L422.7,642.0L419.0,644.9L418.9,648.4L417.4,650.3L417.4,651.9L415.2,651.7L412.2,653.2L409.6,657.1L410.0,659.1L412.9,661.9L416.6,662.7L416.9,664.8L415.2,666.1L416.8,666.7L410.1,671.9L408.3,677.2L407.0,677.8L405.4,676.2L404.9,677.1L406.3,678.1L403.8,679.9L402.9,682.4L403.9,685.1L402.3,685.7L404.3,686.3L406.2,689.8L403.0,689.0L396.8,692.0L396.0,697.5L394.8,698.1L391.3,696.7L390.3,695.1L392.2,694.5L392.8,695.9L395.1,692.2L390.9,694.0L389.8,695.7L387.8,694.6L389.1,692.0L393.8,691.1L389.2,690.7L387.5,693.6L385.4,692.4L387.0,691.7L387.5,690.5L383.9,691.3L382.8,688.5L384.7,689.1L388.8,687.6L389.7,688.9L389.2,689.8L389.9,689.2L389.8,686.3L387.3,684.9L389.6,686.8L385.9,688.1L383.2,686.2L384.3,685.7L384.2,684.0L380.7,682.9L379.6,680.9L381.3,680.8L381.4,679.2L383.4,681.6L384.7,682.2L385.3,680.0L384.1,681.6L383.2,680.5L383.7,679.7L381.5,678.3L382.7,677.2L384.1,677.3L382.7,676.2L382.8,675.2L384.6,675.2L383.6,674.3L384.0,672.2L383.1,674.7L382.5,674.2L382.6,670.0L383.8,669.4L382.1,669.3L381.7,667.0L386.4,667.8L385.1,665.2L383.1,666.8L381.4,665.5L383.4,665.0L382.1,664.1L383.3,662.8L382.8,660.8L381.2,661.2L380.0,660.6L379.9,659.5L377.8,660.4L378.3,661.6Z M392.3,701.6L396.8,700.5L398.7,701.6L396.5,699.3L396.5,698.1L397.9,696.7L398.5,698.6L397.6,698.9L399.1,700.6L401.0,700.4L402.8,701.8L403.8,701.1L400.0,699.3L399.4,698.1L402.4,695.3L399.1,695.5L398.2,694.4L399.5,692.9L398.5,691.9L400.3,692.3L402.3,690.5L405.7,691.6L407.0,693.7L406.3,693.5L406.0,694.6L407.4,694.9L410.8,699.0L415.1,701.6L419.3,702.4L418.1,703.7L414.0,704.4L407.1,703.2L403.7,703.7L401.1,702.6L398.0,703.2Z M382.5,643.2L383.7,636.4L385.9,636.8L386.1,639.5L384.9,640.2L386.1,642.1L384.6,643.9Z M441.4,688.3L443.7,686.2L444.3,684.0L446.3,684.3L446.0,685.1L446.9,685.6L447.2,684.7L448.8,685.3L448.2,685.6L448.6,686.3L445.4,687.5L445.4,688.4L443.3,687.9L443.7,688.8L442.4,689.5Z M377.7,675.7L378.7,675.3L378.3,673.8L379.6,673.5L379.2,672.9L380.1,671.0L381.8,670.9L381.7,677.2L380.8,676.9L380.9,674.4L379.7,676.5Z M400.0,704.9L400.5,703.6L406.2,704.0L405.6,705.1L406.9,705.6L407.8,707.8L404.5,706.7L404.4,705.6L402.8,705.2L403.0,706.9Z M436.2,688.2L439.0,686.6L437.7,686.3L439.0,685.9L437.7,684.4L442.9,684.8L438.5,688.7Z M381.5,650.2L383.6,648.9L384.0,647.7L385.2,648.3L384.0,649.8L385.0,653.2L383.6,653.5Z M384.6,696.2L387.8,695.4L391.2,697.6L388.5,699.4L388.9,698.0L387.2,698.6L386.7,697.2Z","label":[422.0,582.5]},"brazil":{"d":"M384.0,489.0L385.1,488.0L385.0,486.4L387.4,484.6L387.1,483.2L388.5,479.2L392.6,476.8L396.8,475.4L400.1,475.9L402.4,463.5L401.5,460.8L399.7,459.3L399.8,456.4L403.4,456.1L402.8,454.5L400.6,454.5L400.6,451.9L407.2,451.9L407.2,450.8L408.3,451.7L410.4,450.3L411.5,451.9L411.7,454.0L412.5,453.8L414.8,455.7L417.3,454.8L418.1,456.0L419.6,454.3L423.2,452.6L424.0,451.0L426.4,449.8L426.4,449.1L423.8,448.7L423.1,444.4L420.8,441.6L423.9,443.0L426.6,443.0L428.6,444.4L429.2,442.7L436.0,440.6L437.6,438.9L437.0,437.9L439.0,437.7L440.0,438.4L439.4,440.6L441.2,441.2L441.8,443.0L440.6,444.4L440.0,447.7L441.0,451.1L444.6,453.9L446.0,453.6L446.6,452.4L447.9,452.7L451.5,450.7L456.1,451.3L455.4,449.7L456.2,448.7L457.1,449.1L460.1,448.3L463.5,450.2L464.9,449.3L468.2,450.0L474.2,441.5L475.1,442.4L477.2,450.2L478.2,451.4L480.2,452.1L480.4,454.1L477.0,457.8L474.9,459.1L472.1,464.2L469.8,464.9L472.2,465.1L476.4,462.5L477.3,465.8L478.4,466.8L482.7,465.7L481.6,469.3L483.2,466.4L485.2,464.7L486.2,465.1L486.2,463.3L487.5,461.7L490.4,461.2L498.2,464.2L498.7,465.6L499.7,464.6L501.4,465.7L501.8,466.9L501.0,467.9L502.3,467.4L502.5,468.2L501.2,471.5L503.1,468.6L503.3,469.9L506.5,468.2L514.1,470.4L520.1,470.2L526.1,473.6L531.3,478.4L538.1,479.4L540.8,487.9L540.7,490.7L538.6,495.8L530.6,504.5L530.6,503.9L529.2,507.4L527.0,510.4L526.0,510.8L525.3,509.5L524.7,510.2L523.8,512.7L524.4,513.0L523.7,513.3L524.5,522.7L523.2,528.1L523.4,530.3L521.4,532.6L520.9,538.0L516.2,545.0L516.0,548.2L513.2,549.5L511.8,552.1L508.2,552.2L507.4,551.2L507.1,552.3L501.5,552.6L501.7,553.5L498.1,555.7L496.1,555.7L492.5,557.6L488.1,560.9L487.2,562.6L486.4,562.0L486.1,562.7L485.2,562.6L486.4,563.4L485.0,566.2L485.8,570.2L484.8,576.2L481.0,579.6L476.3,587.9L471.8,591.7L471.7,590.5L474.9,588.9L477.7,584.3L477.7,583.5L475.9,584.0L474.8,582.6L475.4,584.0L474.9,585.7L472.1,588.5L471.2,590.7L471.5,592.0L469.4,596.3L466.5,599.0L465.9,598.6L465.9,596.4L467.5,594.5L464.9,591.5L459.3,588.0L457.6,586.2L456.0,587.1L455.8,585.8L452.7,582.9L451.1,583.7L449.6,583.2L456.4,575.3L457.3,575.3L457.1,574.6L460.7,571.8L464.6,569.9L465.3,566.1L464.0,563.3L461.5,563.3L463.0,556.8L461.5,555.8L458.3,556.4L456.8,549.6L454.2,548.5L452.3,549.3L448.1,548.4L448.7,544.0L447.4,540.5L448.5,539.7L447.5,538.7L450.0,532.4L448.7,529.5L446.4,528.3L446.6,524.5L439.3,524.4L439.0,521.2L437.7,519.6L438.9,519.5L438.0,514.2L435.7,513.0L432.8,513.2L431.5,511.7L428.9,511.0L427.7,509.7L422.3,508.7L420.0,506.9L418.4,503.9L418.4,497.7L413.7,498.5L405.5,503.4L403.1,502.7L397.4,503.0L397.7,496.7L395.0,498.7L391.3,498.9L390.5,496.9L387.2,496.5L388.1,494.8Z M476.9,462.8L477.2,460.2L478.2,459.4L486.4,459.9L484.7,464.3L483.1,465.1L482.0,464.8L480.8,465.9L478.0,465.9L477.0,463.7L477.7,463.1Z","label":[483.8,509.5]},"central-america":{"d":"M211.5,323.9L221.1,323.1L220.8,324.0L235.8,329.2L247.1,329.2L247.2,327.2L254.2,327.3L260.3,332.5L262.4,336.9L267.0,339.4L270.6,335.7L274.5,336.2L282.0,345.7L282.2,347.8L283.6,350.5L290.5,352.9L291.4,352.5L289.3,359.2L288.9,368.9L290.7,371.1L290.3,372.5L291.3,374.7L294.2,378.2L296.5,382.7L299.3,383.1L301.8,385.2L310.2,383.2L312.1,383.0L312.4,383.8L313.9,384.1L314.9,383.4L314.3,382.3L317.0,380.4L318.6,373.5L326.1,371.1L331.0,371.6L330.5,371.1L331.9,371.0L332.7,371.7L332.7,373.5L329.3,379.2L330.3,379.4L329.4,380.4L330.0,380.6L328.6,384.9L327.8,382.5L326.6,384.4L327.7,385.0L326.7,391.6L324.4,394.6L325.6,395.1L325.6,394.4L327.5,395.4L329.2,394.6L332.4,395.2L334.6,395.1L336.1,394.1L340.1,394.2L343.0,394.9L344.9,396.6L344.1,396.2L343.6,396.6L345.0,397.4L345.9,397.3L345.4,396.8L346.5,397.3L347.4,398.3L346.4,399.0L347.2,401.0L346.0,403.4L346.0,408.8L345.6,408.0L345.0,408.5L345.3,410.4L344.7,411.1L345.4,412.0L344.5,413.3L346.6,417.3L350.5,420.9L351.1,422.5L352.8,422.8L352.4,422.1L354.6,423.5L359.5,421.8L361.7,420.2L367.7,421.7L370.5,424.0L370.1,424.7L371.2,426.8L370.6,427.9L369.7,428.5L369.0,427.9L368.4,429.8L366.3,426.4L367.4,425.1L368.7,425.9L367.6,424.7L366.4,425.2L364.2,422.9L362.2,422.6L361.0,424.3L358.2,425.8L360.0,428.7L356.6,429.8L355.7,427.1L354.9,428.2L353.1,426.1L349.3,425.4L348.5,426.4L346.9,424.0L346.2,423.9L346.8,425.0L345.6,424.7L345.1,422.0L341.7,420.3L339.9,418.1L339.0,417.7L340.5,419.3L339.5,420.3L337.5,419.0L336.6,417.4L337.3,416.0L336.4,415.0L337.0,414.2L329.3,406.8L329.7,406.3L330.6,406.6L330.0,405.0L326.0,405.7L318.1,402.8L314.5,402.4L304.3,394.0L301.4,393.4L301.5,392.8L300.4,392.5L299.9,393.0L300.8,393.3L294.0,395.6L289.0,394.3L284.9,392.0L276.6,389.3L272.3,386.1L266.2,384.6L264.3,382.5L260.2,380.5L258.1,377.8L257.3,376.1L259.0,375.2L258.0,374.3L259.0,373.0L259.2,371.5L257.4,369.4L256.8,366.7L252.3,361.4L248.9,358.9L249.9,359.4L249.9,358.8L248.2,358.3L246.9,356.3L247.8,356.3L244.8,354.3L243.9,354.6L244.3,353.7L242.5,353.5L242.9,350.1L238.5,347.1L237.6,345.7L237.8,344.4L235.5,343.9L231.4,339.3L227.8,332.2L227.8,329.9L224.3,327.9L223.4,328.4L220.7,327.4L220.5,330.0L221.8,335.0L228.5,341.9L229.1,344.5L230.7,345.8L232.6,349.5L233.2,349.9L232.8,348.6L233.7,349.3L234.8,353.3L237.3,357.2L237.4,359.4L238.5,360.4L238.9,359.4L242.3,363.1L242.0,364.4L240.7,365.4L240.0,365.6L238.5,362.6L232.8,358.6L231.4,356.4L231.8,358.6L230.8,357.5L231.8,354.6L230.5,351.5L227.9,349.9L227.4,348.5L225.6,349.3L220.0,344.9L222.8,344.3L223.6,345.2L222.9,344.1L223.8,341.9L220.2,337.9L217.3,336.1L215.8,331.5ZM302.5,393.0L302.8,393.5L303.1,393.5ZM289.2,369.7L290.2,372.2L290.5,371.1ZM243.4,351.2L243.3,351.1L243.3,351.2ZM220.6,326.9L220.8,327.4L220.9,327.1Z M340.5,369.9L342.7,369.0L342.6,367.7L343.5,366.7L347.0,365.3L351.6,364.3L357.4,364.7L360.7,365.6L362.9,367.6L365.3,367.8L369.4,370.2L370.0,369.9L371.4,370.8L371.0,371.5L371.6,371.0L372.6,372.1L377.1,373.0L377.6,374.2L377.1,374.7L380.5,375.0L383.5,376.7L379.5,378.1L369.1,378.3L371.6,376.0L371.1,375.0L367.5,374.5L366.0,373.3L365.1,371.0L362.9,371.2L355.3,368.3L354.6,368.9L352.6,368.4L351.7,367.7L353.2,367.2L352.6,366.5L349.0,366.4L346.5,368.4L344.4,368.6L343.9,369.6Z M382.1,384.1L383.1,383.2L388.8,384.2L390.5,383.6L388.8,381.5L389.1,380.0L386.2,378.8L388.5,378.0L392.7,378.9L393.8,378.1L396.2,378.0L400.2,379.0L401.0,380.6L403.1,380.7L401.5,381.3L405.3,382.2L406.6,383.4L405.3,385.1L402.9,384.2L395.9,384.8L394.2,387.5L391.8,385.0L385.9,385.0L384.5,385.8Z M366.6,384.8L368.5,383.8L371.0,384.1L374.6,385.3L375.2,386.3L372.6,386.1L371.2,387.1Z M410.9,384.5L411.8,383.8L415.7,384.0L417.5,384.4L417.5,385.0L416.1,386.1L412.4,386.2L411.2,386.0Z","label":[272.9,363.0]},"china":{"d":"M975.2,296.2L974.5,292.3L975.6,291.7L976.0,289.5L979.3,288.1L979.3,287.4L982.2,286.7L982.7,288.3L985.3,288.0L987.6,284.9L992.5,284.6L995.0,282.3L1000.9,280.0L1000.7,277.0L1002.2,275.9L1001.6,275.2L1003.1,274.6L1001.4,270.0L1001.9,266.9L999.5,266.1L1006.8,263.8L1010.1,264.9L1010.5,263.5L1009.3,262.6L1012.1,254.6L1019.2,256.4L1021.9,255.3L1022.6,254.3L1022.1,250.9L1023.0,248.5L1026.2,247.8L1027.5,244.9L1031.3,244.5L1031.0,246.0L1032.2,246.9L1031.9,247.7L1036.2,250.5L1037.9,250.4L1041.3,252.3L1044.1,257.8L1044.0,260.4L1042.7,262.9L1043.5,264.6L1054.1,265.8L1058.9,268.8L1061.4,269.1L1061.3,270.3L1065.4,276.6L1068.8,276.4L1071.4,282.0L1070.8,283.3L1073.3,285.7L1073.0,287.1L1080.0,285.5L1080.8,286.8L1077.8,290.2L1083.2,292.5L1083.6,293.9L1086.3,295.9L1088.1,295.1L1087.3,294.1L1089.6,293.3L1091.9,293.8L1096.1,292.3L1096.7,294.6L1093.9,297.0L1093.6,299.1L1097.0,301.9L1103.1,300.0L1103.6,295.6L1105.1,293.1L1106.5,292.7L1107.9,294.2L1106.0,297.7L1108.7,298.4L1112.2,300.8L1115.0,300.5L1115.2,299.3L1116.0,299.1L1115.9,297.5L1120.7,293.1L1120.4,292.4L1122.7,293.2L1124.1,291.8L1124.5,292.6L1127.7,291.2L1129.6,288.3L1130.9,288.9L1132.3,287.8L1136.2,287.4L1136.6,286.2L1135.5,284.5L1135.7,283.0L1138.4,279.5L1139.2,279.3L1139.6,282.0L1140.9,282.2L1143.4,280.5L1144.2,281.1L1147.4,280.1L1147.5,278.3L1151.2,277.4L1152.5,280.0L1153.1,279.8L1152.7,281.6L1153.5,283.4L1156.9,283.7L1155.4,285.8L1158.3,287.2L1159.4,289.8L1157.6,290.9L1155.9,293.5L1151.2,293.8L1150.2,295.8L1150.6,297.1L1152.1,298.2L1155.8,298.9L1156.4,300.4L1155.8,302.2L1157.2,303.0L1159.0,303.0L1161.0,300.5L1163.0,299.8L1166.6,301.6L1170.7,301.8L1169.4,304.4L1167.7,303.9L1163.2,305.4L1163.6,306.2L1162.8,306.4L1162.6,307.7L1160.7,307.3L1161.1,308.3L1157.7,311.4L1156.7,313.5L1161.1,316.1L1163.5,321.7L1163.4,323.3L1165.4,324.4L1167.4,327.1L1166.7,327.5L1162.1,325.8L1160.3,326.4L1162.9,326.3L1166.6,329.3L1167.5,331.0L1163.3,333.5L1161.1,333.9L1162.6,334.5L1165.0,333.7L1168.3,335.6L1166.2,337.2L1167.6,336.7L1167.9,337.3L1167.7,338.8L1165.8,338.8L1166.7,339.6L1165.9,341.0L1166.4,342.5L1164.6,342.4L1163.1,343.7L1162.4,346.3L1160.3,349.5L1158.4,349.1L1159.5,350.9L1157.9,352.1L1156.7,351.9L1158.5,352.4L1158.5,355.0L1156.7,354.7L1157.1,355.7L1155.9,355.8L1154.6,358.3L1151.4,358.9L1152.2,359.8L1149.5,362.6L1147.6,362.4L1145.9,365.3L1142.0,366.3L1140.8,365.9L1139.4,366.7L1138.6,366.2L1138.2,367.1L1137.1,367.0L1136.8,368.5L1135.6,367.7L1136.0,367.2L1134.1,364.7L1134.3,366.1L1133.3,365.6L1134.2,368.4L1132.3,368.5L1131.8,369.7L1130.3,370.3L1129.5,369.4L1129.2,370.4L1127.8,370.0L1126.4,371.2L1121.6,372.2L1121.2,373.1L1122.0,373.6L1121.1,373.1L1120.6,373.7L1122.0,375.5L1121.4,376.4L1119.8,376.4L1118.7,373.9L1119.7,371.5L1118.2,370.6L1118.2,371.3L1116.3,371.7L1116.4,371.0L1115.1,370.9L1114.3,369.7L1114.0,370.9L1113.0,371.2L1109.4,371.0L1106.7,369.4L1106.1,367.7L1107.1,366.0L1103.4,365.4L1101.1,363.7L1098.7,365.9L1096.6,366.0L1095.8,367.1L1093.3,366.1L1091.9,367.4L1089.9,366.2L1088.5,367.7L1086.8,367.3L1086.1,368.3L1087.2,372.6L1085.1,372.7L1084.5,370.4L1080.9,371.6L1079.7,369.2L1076.8,368.8L1078.0,365.2L1075.5,364.3L1074.7,361.3L1075.3,360.4L1070.3,361.3L1070.8,359.9L1070.3,357.6L1071.3,355.6L1074.6,353.0L1074.9,349.9L1074.6,345.6L1073.2,345.7L1071.5,342.2L1070.4,341.5L1069.3,342.8L1065.3,342.1L1066.3,340.4L1065.7,339.2L1064.5,339.8L1065.4,338.4L1064.2,337.5L1061.6,339.3L1058.5,338.1L1055.0,340.6L1053.0,341.1L1049.9,344.4L1046.5,344.8L1046.5,344.1L1045.1,343.4L1041.4,343.4L1041.4,342.7L1039.9,342.4L1037.9,343.5L1035.6,346.7L1035.2,343.8L1034.5,343.4L1030.5,344.6L1026.5,343.3L1025.6,344.1L1024.5,343.3L1024.0,344.2L1022.7,342.6L1020.5,342.4L1020.6,341.2L1019.2,341.3L1016.9,339.8L1016.4,338.5L1014.3,338.6L1008.2,333.6L1005.7,333.6L1004.4,334.9L1000.8,332.6L1000.6,331.6L997.6,330.4L996.4,328.9L995.0,329.2L994.9,326.4L993.6,323.8L994.8,323.6L996.0,324.6L998.0,323.0L997.4,321.1L995.2,319.6L994.9,317.1L995.9,315.9L993.1,314.4L992.2,310.6L988.4,310.3L986.2,308.8L984.7,309.1L983.6,308.0L983.4,305.3L977.5,303.0L980.5,301.9L979.6,300.9L979.3,297.1L977.1,296.0Z M1160.3,364.5L1160.6,362.1L1164.2,356.5L1166.1,355.5L1167.6,356.4L1165.6,364.4L1163.8,367.2L1163.4,369.6Z M1114.6,380.7L1117.3,378.1L1122.3,377.7L1122.6,377.1L1123.9,378.2L1124.1,379.1L1122.6,380.6L1121.8,382.9L1118.7,384.9L1114.8,383.8Z","label":[1034.7,303.2]},"congo":{"d":"M714.1,440.3L716.0,435.0L719.1,431.7L722.4,430.4L724.9,432.9L727.4,430.2L727.1,429.6L728.9,425.5L731.1,423.4L734.8,415.1L738.3,412.4L738.5,409.9L736.8,409.0L736.3,406.1L737.8,406.4L739.4,408.5L740.3,411.1L740.5,416.0L742.6,418.6L737.0,418.7L735.9,419.9L740.5,424.4L742.2,427.5L742.0,428.6L743.8,428.7L746.2,427.2L747.2,428.5L750.6,426.7L754.3,426.5L756.4,424.1L755.8,422.9L761.4,422.1L766.7,417.4L767.1,416.0L770.0,414.6L771.4,414.9L774.6,419.3L773.9,422.4L774.3,423.6L776.8,424.0L777.2,425.5L779.4,426.1L780.8,427.4L780.8,428.6L785.4,432.2L786.1,434.4L788.6,435.8L789.0,437.6L792.3,441.0L796.9,441.2L798.7,440.4L803.6,444.9L802.9,448.9L805.1,450.2L799.8,455.5L798.3,464.3L796.8,465.6L796.5,467.5L795.4,468.5L796.9,471.0L797.3,478.4L798.4,481.7L798.2,484.0L800.9,486.9L803.0,491.6L795.7,492.7L795.5,494.0L793.6,495.8L794.6,501.1L793.5,505.2L796.3,508.4L797.9,508.7L798.0,507.9L799.2,507.6L799.1,512.8L798.2,512.0L796.1,512.5L793.7,509.1L790.3,507.9L788.7,505.3L787.3,506.8L784.1,506.5L781.4,505.4L781.1,503.8L777.5,504.6L777.5,503.4L775.9,502.4L775.9,511.0L767.9,511.1L768.2,524.4L768.6,525.7L773.5,530.0L763.0,531.6L755.8,530.7L753.6,529.0L736.1,529.1L732.4,527.3L730.2,528.3L727.0,528.4L727.0,522.6L728.1,521.3L730.2,512.8L734.7,507.5L735.3,502.6L732.0,495.0L733.4,493.6L733.5,492.3L731.3,486.6L729.1,483.3L732.2,482.2L729.8,482.8L729.0,482.0L727.1,477.0L718.9,468.6L720.2,468.9L718.5,468.2L717.2,466.4L717.9,466.3L717.1,466.0L714.8,461.1L715.8,461.5L717.2,460.2L717.4,457.4L719.1,458.5L719.9,458.0L717.3,456.5L718.5,456.4L718.4,454.5L717.5,454.2L719.2,451.0L719.8,446.4L718.2,443.5L718.9,443.3L718.6,442.5L717.5,443.0L716.0,442.4L715.6,440.5ZM718.0,465.1L716.3,464.0L717.3,465.3Z","label":[760.8,461.3]},"east-africa":{"d":"M767.3,407.3L768.9,405.1L768.4,403.2L770.1,401.7L769.5,400.1L771.7,397.6L771.7,396.1L772.4,395.4L775.9,395.3L775.9,377.7L779.9,377.7L779.9,369.3L804.8,369.3L805.6,368.5L805.9,369.3L827.5,369.3L829.0,373.1L828.8,377.2L829.9,382.6L834.3,385.7L838.0,396.1L839.1,397.7L839.5,396.4L840.8,398.2L844.7,399.8L846.6,402.4L849.0,404.1L849.6,405.6L852.3,407.2L853.5,410.1L850.2,412.5L852.6,412.3L857.1,416.7L859.8,416.8L863.3,415.2L866.3,415.6L869.6,413.9L877.5,413.2L880.4,412.4L883.2,410.6L885.0,411.2L884.2,416.8L884.8,416.4L885.5,417.0L883.7,417.3L883.3,420.9L871.9,440.7L864.2,448.8L854.9,455.3L847.9,462.6L845.5,466.2L844.0,466.6L843.3,468.3L841.1,469.3L838.0,476.7L836.9,477.4L835.2,483.0L838.2,486.9L837.2,488.9L837.8,490.8L837.2,492.6L838.6,495.6L839.0,498.9L841.9,500.7L837.3,503.4L834.0,504.6L831.7,504.1L829.5,505.8L827.9,505.2L825.2,505.8L823.6,504.8L819.8,505.3L818.4,503.2L818.1,499.0L816.4,497.0L813.7,497.3L803.6,492.7L800.9,486.9L798.2,484.0L798.4,481.6L797.3,478.3L796.9,471.0L795.4,468.5L796.5,467.5L796.8,465.6L798.3,464.3L799.8,455.5L805.0,450.6L804.7,449.6L802.9,448.9L803.6,444.9L798.2,440.2L796.9,441.2L792.3,441.0L789.0,437.6L788.6,435.8L786.1,434.4L785.4,432.1L780.8,428.6L780.8,427.4L779.4,426.1L777.2,425.5L776.8,424.0L774.3,423.6L773.9,422.4L774.6,419.3L771.4,414.9L771.7,413.2L770.4,412.2L769.4,407.8Z","label":[806.8,406.8]},"eastern-australia":{"d":"M1232.0,525.6L1236.0,527.0L1237.0,528.7L1240.1,530.3L1243.3,529.1L1245.2,525.2L1246.5,519.4L1245.9,514.3L1246.5,510.8L1247.7,510.0L1246.8,508.4L1247.9,507.1L1248.7,502.7L1250.2,501.7L1251.5,506.2L1252.7,506.8L1252.4,508.2L1253.6,509.6L1255.0,516.5L1255.9,517.0L1257.9,516.0L1261.2,518.9L1261.7,525.0L1263.6,527.0L1264.5,530.0L1264.1,532.6L1265.3,533.8L1265.5,535.5L1269.7,537.2L1271.0,538.8L1275.0,541.0L1275.5,541.8L1274.8,542.2L1277.8,546.4L1278.8,550.0L1279.9,550.4L1280.3,548.8L1282.2,550.5L1282.5,549.7L1283.4,554.3L1287.3,557.1L1291.7,562.7L1292.7,564.9L1292.5,570.2L1294.3,574.7L1294.4,577.4L1293.4,579.3L1291.8,588.7L1289.9,593.2L1287.3,595.2L1285.2,598.3L1283.2,604.7L1280.8,608.5L1279.7,616.2L1277.9,617.4L1271.5,618.1L1267.4,621.5L1264.9,621.8L1265.9,622.3L1265.6,623.8L1261.6,620.9L1261.9,619.5L1259.8,620.7L1258.9,620.0L1260.5,618.9L1259.6,618.0L1257.7,619.0L1258.7,619.4L1254.2,622.2L1249.8,620.2L1245.7,620.1L1243.9,618.7L1244.0,565.2L1232.0,565.1Z M1345.9,656.4L1347.8,655.6L1347.0,654.2L1348.6,653.3L1347.6,653.1L1353.5,647.4L1356.6,646.5L1364.1,640.9L1365.9,636.3L1367.8,635.1L1368.6,632.3L1369.9,630.7L1370.8,630.1L1371.8,630.2L1370.8,630.9L1372.0,631.8L1372.5,633.8L1375.8,632.1L1375.4,633.7L1376.0,632.5L1377.2,632.6L1376.5,633.7L1377.5,633.0L1376.3,634.6L1376.9,636.6L1372.9,642.0L1370.6,643.5L1372.4,646.2L1368.4,645.7L1368.7,646.5L1365.8,647.7L1362.8,655.3L1363.1,656.2L1361.4,656.8L1358.7,659.6L1353.5,659.9L1352.8,658.7L1350.2,657.6L1346.9,657.9L1346.9,656.4Z M1370.8,602.2L1372.2,602.1L1373.1,604.5L1373.8,604.0L1376.6,606.0L1377.3,605.8L1378.3,608.2L1377.6,608.3L1379.2,610.6L1378.9,613.0L1382.2,614.7L1381.8,611.4L1383.1,612.6L1384.4,616.8L1389.1,618.4L1392.0,616.4L1394.1,617.0L1393.1,621.0L1391.9,621.8L1391.6,624.2L1390.1,623.4L1388.3,624.1L1387.8,625.7L1388.4,626.2L1387.4,628.5L1383.9,633.5L1381.2,635.4L1379.5,634.5L1379.5,633.6L1378.5,633.9L1380.7,630.7L1380.6,628.3L1375.1,624.6L1378.4,622.1L1379.7,617.5L1378.3,614.2L1379.6,614.1L1377.9,613.5L1376.8,611.5L1377.7,611.8L1377.6,610.3L1375.7,609.1L1376.2,610.9L1373.6,607.1L1374.5,606.1L1373.3,606.7Z M1258.6,632.4L1258.9,630.9L1265.3,633.3L1272.9,631.8L1273.4,638.3L1272.8,637.2L1272.1,638.5L1271.9,642.9L1271.1,643.2L1270.6,642.2L1271.2,641.8L1270.3,641.4L1269.8,642.3L1269.3,641.3L1269.0,643.2L1268.0,642.9L1267.5,645.1L1264.2,644.8L1264.0,643.9L1264.8,643.7L1263.5,643.5L1262.0,641.8L1260.8,638.4L1262.1,639.0L1260.9,638.2Z","label":[1268.7,577.3]},"eastern-us":{"d":"M253.3,326.2L267.7,326.2L268.0,303.7L301.5,303.6L301.5,293.8L299.8,292.0L300.3,290.4L298.6,289.7L297.4,288.0L295.7,281.1L293.5,276.7L294.2,274.9L293.6,272.9L294.2,272.8L294.2,264.1L292.6,262.6L293.8,259.9L291.1,245.4L299.3,245.4L299.4,243.5L300.6,243.8L301.5,246.7L305.2,247.8L308.0,247.4L313.9,250.2L316.3,249.4L322.2,250.5L326.5,249.0L340.5,256.1L341.7,258.3L343.4,257.9L344.1,260.2L345.5,260.0L346.1,260.6L345.7,261.6L349.8,263.9L351.4,272.6L349.8,277.2L347.4,279.5L347.9,281.0L349.2,281.7L363.9,276.3L364.3,275.8L363.3,273.1L364.6,272.5L372.7,272.3L375.4,269.0L380.0,265.7L393.9,265.5L394.7,264.1L396.5,264.2L398.8,261.1L400.0,257.0L403.0,253.2L404.3,254.5L407.1,253.8L408.8,255.2L408.8,262.0L410.3,262.6L410.1,264.2L411.5,264.7L412.0,266.3L407.8,268.6L406.9,268.0L406.7,269.3L406.2,268.1L405.9,269.2L404.8,268.8L404.8,267.8L403.4,270.3L399.0,272.0L396.7,276.2L397.5,277.2L395.9,278.6L397.0,279.1L398.3,281.4L400.0,281.0L399.0,279.7L399.6,279.8L400.2,281.7L397.4,282.4L397.2,281.6L393.9,283.2L388.3,283.6L385.7,284.9L384.7,285.8L390.9,284.2L389.8,285.3L392.4,284.7L387.2,286.6L383.9,287.0L383.9,286.1L383.0,287.3L384.1,287.8L383.7,290.7L383.6,289.9L383.0,291.9L380.3,294.7L380.4,293.7L377.9,292.1L377.9,291.1L377.7,292.2L379.8,297.1L376.3,303.0L377.3,299.3L376.6,299.2L376.5,297.5L374.9,297.0L375.7,296.1L374.6,295.8L376.1,291.7L373.8,293.3L374.4,297.3L373.8,297.3L374.6,298.7L372.0,297.0L371.1,297.2L371.5,295.9L371.0,296.3L370.9,297.4L374.9,299.6L374.6,300.6L373.7,300.4L374.9,301.4L373.9,302.4L374.9,303.4L374.4,303.7L373.5,302.7L371.6,302.3L373.3,302.9L374.1,304.2L376.0,304.1L377.9,309.1L376.4,305.7L376.7,307.7L375.5,307.0L374.1,308.1L373.1,307.8L373.2,308.5L375.7,308.4L375.7,309.7L376.6,308.4L377.0,309.0L375.3,311.2L373.7,310.4L372.6,310.8L373.9,311.6L372.9,312.9L372.0,312.6L373.0,313.1L374.2,312.9L374.2,313.5L370.4,314.3L368.3,317.6L365.7,317.9L360.2,323.3L357.7,323.9L357.7,325.0L356.8,324.5L357.2,325.3L355.6,326.8L354.2,330.1L355.0,336.0L357.9,341.7L357.9,344.1L359.8,348.9L359.5,353.1L358.1,355.7L355.6,356.1L354.5,353.1L352.8,351.7L352.4,349.7L351.8,350.0L351.9,348.3L350.8,348.6L349.1,345.9L350.4,344.5L349.5,343.9L349.4,345.0L348.6,344.4L349.4,339.9L343.8,334.6L338.7,336.5L337.4,333.8L336.8,334.3L334.2,333.3L335.4,333.2L335.0,332.9L331.2,333.6L332.0,332.6L330.9,333.6L328.0,334.1L328.8,333.7L327.9,332.0L327.5,333.5L324.4,333.2L321.2,334.3L321.0,335.4L322.4,334.9L322.6,335.9L321.3,337.1L323.9,338.5L322.5,339.5L322.2,338.6L319.5,337.1L319.1,339.0L318.5,338.1L317.0,338.9L314.8,338.2L315.4,338.0L315.0,337.0L312.4,335.8L311.0,337.0L307.3,336.0L304.7,336.3L304.7,335.2L304.4,336.4L301.0,337.8L301.7,337.0L300.9,337.0L301.0,336.2L300.0,336.4L300.4,337.8L298.9,339.6L295.1,341.7L295.7,341.1L293.5,340.7L294.3,341.8L292.9,342.0L292.5,343.1L291.4,343.2L291.9,343.4L291.3,344.3L290.3,344.4L290.8,345.2L290.2,346.7L289.1,346.3L290.1,347.1L290.3,350.3L291.4,352.2L290.5,352.9L283.6,350.5L282.2,347.8L282.0,345.7L274.5,336.2L270.6,335.7L269.1,336.6L267.6,339.2L266.3,339.1L262.4,336.9L260.3,332.5ZM394.5,281.3L394.7,282.6L395.3,281.5ZM384.4,284.8L384.5,284.5L384.4,284.5ZM384.4,284.4L384.4,284.0L384.3,284.1ZM373.6,296.9L373.4,296.8L373.6,297.0ZM373.4,299.9L372.8,299.6L373.4,300.0ZM372.2,298.8L372.1,298.6L372.1,298.7ZM357.5,341.7L357.2,342.4L357.5,343.0ZM357.2,341.2L356.7,340.5L357.0,341.8ZM357.2,342.2L357.0,342.0L357.0,342.2ZM356.1,355.3L355.7,355.3L356.2,355.6ZM321.1,334.3L319.1,333.4L318.4,334.4L319.3,334.9Z","label":[330.5,303.8]},"egypt":{"d":"M717.2,334.6L719.6,333.4L720.9,331.6L720.5,328.6L721.1,327.6L726.0,324.4L726.0,321.0L729.1,322.4L733.1,322.2L740.7,324.5L742.8,328.8L751.3,331.0L756.5,333.9L758.8,332.9L760.4,330.8L759.7,327.1L760.5,325.3L766.5,322.1L772.4,323.5L773.1,325.3L779.5,326.3L780.9,328.3L787.1,328.6L796.3,331.4L804.3,328.0L807.6,328.3L808.5,329.2L807.6,328.6L807.1,329.4L808.4,330.3L808.9,329.4L810.4,330.4L816.8,329.3L819.6,337.4L816.9,344.8L813.0,341.3L810.3,335.2L809.4,336.7L811.6,341.3L814.2,344.2L815.8,349.6L820.8,358.9L823.1,361.2L822.0,361.8L822.8,365.3L827.5,369.3L805.7,369.3L805.6,368.5L804.8,369.3L780.0,369.3L779.9,377.7L776.0,377.7L775.9,379.8L744.0,363.2L736.9,366.7L733.9,364.4L727.9,362.9L726.0,359.6L721.3,358.6L717.7,351.7L719.5,349.7L719.0,346.7L719.7,344.7L719.2,338.7Z","label":[774.1,348.7]},"great-britain":{"d":"M654.7,205.8L655.4,205.0L654.9,204.2L656.6,204.6L655.5,203.6L657.6,201.4L656.7,199.5L657.4,198.9L657.0,198.1L657.6,196.9L659.2,196.8L658.3,195.8L659.9,194.7L659.9,192.9L667.8,192.5L667.2,194.3L664.0,196.5L664.6,197.3L663.5,198.6L671.7,197.9L672.9,199.1L669.6,204.5L667.4,205.4L669.3,206.2L666.5,207.7L669.6,207.5L673.4,210.1L675.1,214.9L679.7,218.1L679.2,218.7L680.5,220.9L678.7,220.4L681.4,223.4L680.3,224.8L684.2,224.5L686.6,225.6L686.4,229.0L682.2,232.3L685.7,233.0L685.6,234.0L680.8,236.2L674.6,235.6L671.9,237.0L668.0,236.4L666.4,236.9L665.3,238.9L663.2,238.1L659.5,240.0L657.5,239.9L663.2,233.9L667.5,233.8L669.8,231.4L666.8,232.9L662.4,231.0L659.3,231.0L659.3,229.9L663.1,228.1L664.0,226.8L663.5,224.7L661.3,225.3L662.8,223.5L661.7,222.1L667.5,222.7L668.6,218.0L667.3,218.1L665.7,216.2L667.3,213.5L664.2,214.5L660.7,214.1L660.4,215.0L659.5,214.0L661.3,210.5L660.4,209.3L661.3,207.9L660.6,207.1L659.1,208.3L659.2,207.0L658.5,207.5L657.8,211.1L656.9,211.2L659.0,203.5L656.8,204.6L656.9,205.7ZM668.3,222.3L668.6,222.6L668.7,222.5Z M638.4,228.9L642.9,226.4L640.3,226.6L644.2,223.2L639.6,222.0L640.4,220.3L641.7,219.7L640.3,219.5L640.6,218.3L639.6,217.6L645.7,217.5L647.3,215.3L644.9,215.0L646.9,212.4L649.3,211.8L649.7,213.2L650.8,211.2L652.2,211.9L651.3,212.9L655.5,212.0L657.1,214.3L656.5,215.2L658.0,215.7L657.4,215.9L657.6,217.3L654.6,218.9L655.9,224.6L654.2,227.7L654.7,228.3L650.2,229.0L644.7,231.8L641.1,232.4L641.7,231.3L639.5,231.7L640.9,230.5L638.5,230.3L640.3,228.9ZM644.6,226.0L644.0,225.6L643.4,226.3Z","label":[674.0,226.3]},"greenland":{"d":"M388.7,54.9L390.1,52.3L393.4,51.1L416.7,46.2L418.8,43.9L420.8,38.8L422.9,37.7L412.6,38.1L411.2,36.2L421.9,29.5L427.6,30.4L427.1,28.7L428.0,27.6L434.2,28.3L435.6,25.8L435.2,22.4L438.3,20.8L442.9,21.1L453.2,26.1L442.9,20.2L461.8,16.6L465.3,18.4L465.8,23.3L467.8,21.2L467.9,16.9L476.4,21.0L481.6,20.9L476.0,15.5L479.9,15.4L501.1,22.1L501.9,21.4L501.5,19.0L503.0,16.5L502.7,15.4L497.8,12.7L514.5,13.0L494.1,10.6L507.2,7.6L514.8,9.1L518.6,6.8L527.4,10.2L528.2,8.5L525.0,6.4L557.2,4.2L576.8,7.5L552.1,9.3L551.9,10.2L579.5,8.6L582.1,11.4L593.9,14.2L587.5,16.8L560.5,19.5L579.4,20.0L582.8,22.9L588.2,19.7L594.6,19.3L595.5,20.6L595.4,22.9L594.0,25.4L587.7,30.2L587.5,31.6L601.5,23.5L610.2,25.8L615.5,22.1L623.0,21.8L634.3,25.0L622.3,29.6L623.1,30.7L622.0,31.7L613.2,33.4L616.3,34.9L614.0,36.5L602.3,36.4L599.4,38.7L599.7,40.8L602.6,41.2L602.3,44.6L604.0,46.2L595.6,50.7L596.2,51.7L593.1,59.0L596.5,57.3L602.0,59.0L602.8,60.1L597.4,59.9L602.8,63.2L606.2,62.9L606.7,65.7L605.6,67.0L598.1,65.7L589.6,67.6L592.5,68.6L594.0,71.1L600.5,72.4L600.2,73.5L602.0,75.3L602.3,79.6L598.1,78.9L592.2,81.2L596.1,80.9L596.9,82.3L596.0,84.1L596.5,84.4L598.3,81.7L600.1,81.7L602.9,85.1L602.9,86.7L595.5,88.5L592.2,87.4L592.9,85.6L592.2,84.9L592.1,86.3L590.9,87.0L590.7,88.9L591.5,89.4L598.5,90.5L597.5,93.5L590.6,95.0L583.4,91.2L581.3,92.5L578.4,90.7L580.8,93.2L575.8,95.1L572.7,94.4L573.8,95.2L569.8,96.1L583.5,93.9L591.9,97.7L591.7,101.6L590.8,103.7L583.7,100.9L581.4,96.8L573.7,99.3L580.7,97.8L581.4,100.3L579.3,101.8L581.7,101.5L592.2,106.5L590.1,109.2L593.0,108.5L593.9,115.5L590.5,115.9L590.2,113.1L589.2,116.1L586.7,116.0L584.1,114.6L581.7,110.4L576.5,107.8L571.6,107.4L576.6,108.5L577.3,110.1L573.1,112.4L566.7,112.0L568.0,112.8L567.7,114.2L564.8,115.9L574.0,116.4L569.5,118.9L570.4,119.3L571.9,117.8L577.9,116.7L591.2,118.5L584.8,121.1L585.0,122.2L583.0,122.2L574.1,128.6L557.3,131.6L557.6,132.5L556.1,132.8L550.8,130.3L551.3,133.3L547.6,135.6L541.5,143.9L539.2,145.1L536.7,144.0L537.5,145.8L529.0,149.4L528.0,148.7L530.8,144.7L527.7,144.1L529.0,145.0L527.4,147.4L526.1,146.8L527.2,148.7L519.3,149.7L521.7,151.1L519.0,153.0L515.6,153.0L519.3,156.7L516.9,158.3L514.2,158.0L517.5,158.9L517.8,161.5L515.8,162.9L514.4,165.7L511.4,164.8L513.5,166.2L512.4,167.7L508.5,167.9L511.4,168.8L510.7,171.3L511.6,173.2L509.8,173.9L510.6,174.6L509.1,179.8L507.8,181.3L504.3,180.9L507.1,182.1L507.5,184.1L504.5,184.4L505.4,185.3L504.4,185.5L502.3,184.9L503.1,182.8L501.5,184.3L498.5,183.2L498.5,182.2L500.8,180.6L498.5,181.8L495.8,180.7L495.4,179.8L496.5,177.1L492.5,179.5L487.0,179.6L488.7,178.4L486.5,178.4L486.3,177.3L484.3,176.7L482.8,174.8L482.5,173.0L484.3,171.7L481.5,172.3L481.7,170.8L478.7,169.4L478.8,167.8L480.2,166.1L478.4,167.2L473.8,161.3L473.8,159.7L478.3,158.6L478.1,157.8L473.3,158.7L474.4,156.8L478.0,155.3L479.9,156.8L479.5,155.2L477.9,154.8L476.3,152.1L477.1,154.6L475.1,155.7L475.0,154.9L472.3,158.3L471.0,152.3L469.8,151.2L473.4,148.6L470.6,150.3L467.2,149.4L467.5,147.0L466.4,146.4L467.7,145.6L465.5,145.7L465.5,144.0L467.8,141.2L470.3,140.9L465.3,140.2L464.5,139.2L464.8,137.2L469.9,134.9L477.5,136.4L475.3,135.4L476.1,134.6L470.6,134.4L465.1,136.4L467.4,131.8L473.6,132.9L475.3,130.6L471.2,131.7L466.5,131.2L469.6,128.4L473.5,129.6L476.2,128.5L476.7,127.7L475.0,128.1L475.7,125.4L478.7,125.2L475.7,124.9L476.8,121.6L478.6,120.7L478.0,119.7L478.7,119.1L471.0,118.8L462.0,114.5L463.3,113.3L467.6,113.7L477.2,116.3L474.7,115.0L474.9,113.1L472.9,111.9L475.7,112.1L474.5,111.2L468.0,110.7L467.5,109.7L468.4,108.7L472.6,107.2L466.2,107.8L467.4,106.2L466.7,106.2L464.8,102.0L464.8,103.6L466.1,105.4L464.1,107.2L464.1,108.6L461.2,109.3L457.6,107.9L457.3,106.9L458.7,103.8L460.6,101.9L457.7,103.3L457.5,102.4L458.7,102.0L457.6,101.2L460.3,100.3L461.0,98.0L457.3,97.0L458.8,94.5L455.6,92.8L456.6,91.1L455.1,88.3L451.1,88.4L453.2,87.6L453.1,86.0L454.8,85.1L445.7,78.6L447.0,77.4L445.9,75.9L435.2,72.1L426.8,70.5L418.5,72.3L411.8,71.8L413.3,73.5L412.7,73.6L404.9,71.8L402.1,70.1L407.3,67.9L401.3,67.3L400.4,66.5L400.9,65.3L397.5,66.5L395.4,64.4L407.5,61.8L414.3,62.6L414.9,60.6L412.7,59.2L402.6,61.1L397.8,59.0L399.0,57.9L394.9,58.1ZM571.7,94.0L571.3,94.2L571.7,94.2ZM468.5,145.1L473.9,141.9L475.0,140.9ZM474.2,148.5L474.8,148.2L474.1,148.4Z M460.3,121.3L460.8,119.6L461.8,119.5L460.7,117.9L462.5,117.0L466.8,117.8L471.8,120.6L472.4,122.1L465.0,124.5L463.3,123.5L465.1,123.1L464.7,122.5Z M567.9,115.5L569.2,112.8L573.5,112.9L576.7,111.7L578.2,112.4L578.6,114.2L575.1,116.0Z M490.6,14.1L494.4,13.2L501.0,16.1L500.5,19.2L499.7,19.3Z M603.4,40.1L608.1,38.3L610.4,39.3L607.9,41.4Z M604.4,80.9L604.6,78.9L608.0,78.2L610.4,81.2Z","label":[518.5,75.0]},"iceland":{"d":"M582.1,149.9L584.2,149.7L583.6,148.2L586.4,148.6L584.7,147.7L585.6,147.5L584.9,146.5L586.2,145.5L590.2,147.3L590.2,146.3L588.2,145.3L589.6,144.8L587.5,144.5L588.4,143.8L594.4,146.6L594.5,148.4L593.6,148.8L595.5,151.5L596.8,149.1L598.2,149.5L598.8,146.2L602.0,148.3L602.5,146.2L604.6,145.5L607.4,148.5L606.8,145.7L609.8,147.0L611.4,145.4L614.0,145.5L613.8,143.8L615.9,143.3L617.8,145.2L621.6,144.2L619.6,146.0L621.2,146.6L620.8,148.3L625.5,149.9L624.8,151.0L625.8,152.7L623.8,155.0L622.5,155.0L621.5,157.1L605.4,163.5L599.2,162.6L598.0,161.7L598.4,161.0L595.3,160.1L589.4,160.8L589.2,159.2L591.8,159.3L593.5,157.3L591.8,157.7L592.3,156.2L590.1,154.6L583.9,154.2L592.4,153.0L592.9,152.1L590.0,152.0L592.6,150.4ZM593.4,155.6L593.2,155.9L593.5,155.8Z","label":[604.3,155.8]},"india":{"d":"M923.4,335.7L929.9,337.6L936.4,337.7L944.7,335.8L945.4,331.5L947.3,329.5L949.8,329.6L950.9,329.1L950.3,328.4L952.6,327.1L955.5,327.9L957.1,326.6L957.0,324.3L958.0,321.7L961.1,320.5L959.6,317.3L964.2,317.1L963.9,314.9L966.5,312.0L964.9,307.7L969.0,304.9L976.1,304.5L978.8,303.5L983.4,305.3L983.6,308.0L984.7,309.1L986.2,308.8L988.4,310.3L992.2,310.6L993.1,314.4L995.7,315.7L994.9,317.1L995.2,319.5L996.5,321.0L996.9,324.0L995.7,324.7L994.8,323.6L993.6,323.9L994.9,326.4L995.0,329.3L996.4,328.9L997.6,330.4L1000.6,331.6L1000.8,332.6L1004.5,334.9L1005.7,333.6L1008.2,333.6L1014.3,338.6L1016.4,338.5L1016.9,339.8L1019.2,341.3L1020.6,341.2L1020.5,342.4L1022.7,342.6L1023.9,344.1L1024.5,343.3L1025.6,344.1L1026.5,343.3L1030.5,344.6L1034.5,343.4L1035.2,343.7L1035.5,346.7L1037.9,343.5L1039.9,342.4L1041.4,342.7L1041.4,343.4L1045.1,343.4L1047.9,344.9L1050.6,344.0L1053.0,341.0L1055.0,340.6L1058.5,338.1L1061.5,339.3L1064.1,337.5L1065.4,338.3L1064.6,339.8L1065.7,339.2L1066.3,340.5L1065.1,342.0L1068.3,342.2L1069.4,343.8L1067.5,345.5L1068.4,347.6L1066.9,346.7L1064.8,347.0L1060.5,349.8L1060.5,352.2L1058.3,355.3L1058.8,356.4L1056.5,361.4L1053.3,360.6L1053.6,363.7L1052.3,366.3L1052.6,368.4L1051.9,369.3L1050.8,368.8L1050.3,369.4L1050.4,372.4L1048.8,372.0L1049.3,374.4L1047.6,369.8L1047.3,370.4L1047.4,368.0L1045.9,365.8L1043.8,366.8L1042.6,365.0L1042.3,362.7L1041.3,363.3L1042.4,364.0L1041.7,366.1L1042.5,367.8L1040.9,370.0L1040.3,369.8L1040.7,368.8L1039.8,369.2L1039.5,368.6L1038.5,370.1L1038.1,369.2L1037.1,370.5L1036.2,369.2L1036.2,370.8L1035.0,371.1L1034.6,369.0L1034.3,370.7L1032.5,370.8L1032.7,368.8L1031.3,370.5L1028.4,371.4L1027.4,372.5L1027.9,374.8L1027.0,376.4L1024.9,378.1L1021.0,378.7L1020.7,379.3L1021.8,379.2L1016.4,384.8L1009.4,389.7L1009.0,391.9L1005.2,392.8L1003.9,395.2L1002.6,394.6L1001.2,395.4L1000.2,398.0L1001.2,404.5L1000.7,403.6L1000.3,404.0L1001.4,405.0L999.1,411.8L999.4,417.3L997.3,417.6L995.8,420.4L996.1,421.3L997.6,421.9L993.7,422.2L992.2,425.1L990.1,426.4L986.2,423.0L985.3,420.8L985.4,418.9L982.9,413.1L979.8,408.2L977.5,400.4L973.4,392.3L971.5,383.3L972.0,381.7L971.3,381.9L970.7,378.4L971.6,374.9L970.5,371.6L971.7,370.7L970.2,370.6L970.1,369.4L971.0,368.3L969.0,368.2L968.2,370.0L969.0,371.3L968.1,372.9L962.9,374.6L960.5,373.1L955.9,368.1L956.2,367.5L957.1,368.1L960.7,366.9L962.0,364.8L958.7,366.1L955.3,364.9L953.7,362.7L954.6,361.7L952.9,362.6L952.7,361.7L950.3,361.4L948.7,357.7L946.8,357.2L946.8,355.7L945.7,354.2L944.6,354.5L945.9,354.7L938.6,355.9L936.3,354.9L934.0,355.7L926.3,355.8L927.4,351.4L929.8,350.0L932.6,349.6L933.2,347.5L931.0,346.9L931.0,342.7L927.6,341.4ZM1039.8,367.7L1039.5,367.9L1039.6,368.0ZM1039.5,368.0L1039.5,368.2L1039.6,368.2ZM985.5,419.8L985.8,420.4L985.6,419.8Z M999.0,425.5L999.1,426.6L1000.3,420.3L1001.6,420.6L999.9,419.4L1001.0,419.4L1005.5,424.9L1007.3,429.0L1007.4,431.1L1006.5,433.0L1002.9,434.8L1000.4,434.1L999.4,431.4Z","label":[998.4,362.4]},"indonesia":{"d":"M1115.8,457.3L1116.3,452.8L1118.5,450.6L1118.6,452.3L1122.0,455.3L1127.2,454.7L1129.9,452.5L1131.8,452.5L1134.5,453.8L1135.6,453.0L1138.2,452.9L1139.3,450.8L1139.1,449.7L1140.7,448.6L1140.5,447.2L1141.8,446.6L1142.3,443.0L1143.4,441.3L1150.3,442.0L1149.9,442.4L1151.1,444.0L1148.6,444.3L1152.3,449.5L1151.3,450.7L1155.9,454.8L1154.1,455.5L1151.8,454.5L1150.1,457.8L1150.2,461.8L1148.0,463.5L1146.9,463.2L1147.0,464.0L1145.2,465.8L1145.8,466.4L1145.3,467.3L1146.3,467.9L1144.6,470.6L1145.0,471.2L1143.8,473.1L1138.8,475.4L1138.1,472.4L1136.3,471.9L1134.8,472.6L1134.4,471.5L1133.4,471.7L1132.2,470.6L1131.0,472.0L1129.1,472.0L1127.6,473.0L1126.8,470.3L1123.7,471.0L1121.0,470.6L1119.9,463.8L1117.0,462.0L1117.5,461.3L1116.5,460.3L1117.0,458.6Z M1060.8,437.6L1061.6,436.2L1065.0,437.6L1070.2,437.9L1073.0,441.1L1073.2,442.3L1078.9,446.0L1082.1,450.0L1083.5,450.9L1083.3,449.8L1084.2,449.7L1085.9,452.0L1088.1,453.0L1089.6,455.4L1091.8,456.1L1092.3,456.8L1090.9,457.8L1093.4,456.7L1094.7,457.6L1095.1,458.5L1093.7,459.5L1093.8,461.0L1094.9,462.3L1097.4,462.9L1098.1,466.0L1099.4,467.1L1098.6,469.1L1099.9,468.2L1101.6,468.3L1104.2,471.2L1103.4,473.2L1103.0,482.0L1101.4,481.0L1100.3,481.7L1098.6,480.9L1098.4,482.4L1086.6,471.7L1083.6,467.7L1081.2,461.9L1078.7,458.6L1076.6,457.3L1074.4,451.3L1070.8,449.3L1070.4,447.4L1067.9,444.4L1065.8,443.5Z M1155.1,469.6L1156.4,468.7L1157.3,466.4L1158.0,462.4L1158.8,461.5L1159.4,461.9L1158.9,459.1L1161.1,454.9L1162.4,455.3L1164.3,453.4L1171.4,455.4L1175.4,455.4L1180.0,451.9L1180.7,452.2L1179.9,454.4L1177.1,457.1L1174.1,457.5L1172.0,456.8L1162.3,456.6L1160.3,458.6L1160.4,461.3L1162.7,464.2L1164.6,464.1L1166.3,462.1L1167.9,462.5L1172.7,461.0L1173.7,461.6L1173.5,462.8L1171.6,462.3L1169.0,465.0L1165.4,466.3L1169.2,470.4L1169.6,471.5L1169.0,473.2L1171.4,475.0L1171.5,476.3L1168.5,476.9L1168.2,478.1L1166.4,477.8L1166.5,475.1L1163.6,472.8L1164.2,469.7L1162.6,469.4L1161.0,470.6L1161.7,473.6L1161.1,479.3L1161.7,481.1L1158.9,481.5L1157.4,480.0L1158.4,476.4L1158.0,473.0L1155.7,472.7Z M1101.0,485.9L1102.0,485.9L1104.3,482.4L1107.3,483.2L1108.2,482.4L1109.3,482.7L1110.7,483.6L1113.3,483.9L1114.7,485.9L1117.2,486.2L1121.7,486.6L1122.9,484.7L1123.9,484.5L1124.7,485.5L1130.2,486.5L1131.2,489.0L1132.1,489.4L1137.5,489.9L1137.5,492.4L1138.4,493.7L1133.0,492.0L1126.0,492.0L1117.1,489.6L1111.7,489.7L1105.8,488.3L1106.0,487.0L1104.8,486.5Z M1160.3,398.9L1159.3,392.9L1160.6,394.0L1161.6,393.5L1161.4,387.5L1162.4,383.9L1164.4,383.4L1167.8,384.8L1168.9,383.9L1168.6,387.4L1170.1,389.6L1168.5,393.4L1166.4,394.5L1165.6,397.2L1167.6,402.3L1168.8,402.7L1168.8,401.8L1170.5,401.1L1171.7,401.6L1172.4,403.4L1173.3,402.1L1175.4,403.2L1174.2,403.9L1175.2,406.0L1176.6,406.3L1176.2,408.2L1175.5,407.7L1175.7,406.8L1173.2,406.3L1172.7,404.7L1170.4,402.7L1169.9,402.8L1170.4,405.7L1167.1,402.6L1165.4,403.8L1163.3,402.8L1162.5,403.2L1162.5,401.6L1163.8,399.7L1162.3,398.9L1162.4,400.4L1161.8,400.5Z M1167.6,430.4L1169.0,426.9L1171.7,426.0L1173.7,423.8L1175.4,424.9L1175.2,426.5L1176.8,425.8L1177.6,424.3L1178.9,424.4L1179.5,422.8L1182.0,422.6L1181.9,419.6L1184.8,421.5L1185.3,423.3L1184.6,424.3L1185.8,425.9L1186.3,429.7L1184.8,431.3L1184.8,433.5L1183.3,429.4L1182.8,429.6L1181.5,431.9L1182.7,434.8L1181.4,436.3L1180.9,434.4L1179.7,435.2L1176.3,433.1L1175.9,430.7L1176.8,429.1L1174.7,427.4L1174.0,427.5L1173.6,429.1L1172.4,428.0L1171.4,428.6L1170.5,427.6L1168.6,430.9Z M1174.4,498.7L1175.5,496.7L1179.6,494.7L1180.7,493.4L1187.9,492.1L1189.2,492.5L1181.6,495.9L1177.7,499.5L1175.0,500.3Z M1189.7,453.7L1190.5,451.4L1192.1,449.9L1191.5,451.4L1192.0,453.4L1190.7,455.2L1191.5,455.4L1192.9,453.1L1194.8,452.4L1194.8,454.3L1193.0,455.8L1195.6,457.9L1192.0,456.9L1191.6,457.5L1191.9,459.7L1193.7,462.3L1190.8,459.7Z M1191.5,471.6L1192.8,470.2L1196.5,470.5L1198.2,469.9L1201.5,470.7L1203.4,473.0L1203.2,474.2L1199.4,472.1L1197.9,472.6L1195.5,471.7L1194.1,472.5L1192.5,471.4L1191.7,472.8Z M1159.6,494.3L1159.5,492.5L1161.7,491.8L1165.8,493.1L1167.9,492.6L1169.3,493.3L1171.4,492.0L1171.4,491.2L1172.0,492.1L1171.1,493.3L1166.6,494.4Z M1147.1,494.7L1147.3,492.9L1148.7,492.3L1150.3,492.5L1151.2,493.7L1152.8,493.4L1151.1,491.9L1151.7,491.2L1153.2,492.2L1155.7,492.0L1156.5,493.5L1153.9,494.3L1153.5,493.6L1148.2,495.2Z M1169.6,419.3L1171.4,418.2L1171.9,415.0L1174.0,414.9L1172.6,419.2L1172.9,422.2L1172.0,422.4Z M1177.2,408.2L1181.0,408.4L1182.1,409.7L1182.0,412.2L1182.9,414.4L1180.1,413.2L1180.0,411.5L1177.8,409.9Z M1100.5,466.9L1101.8,465.0L1102.8,465.6L1102.9,464.9L1103.9,464.9L1105.5,468.6L1107.3,469.0L1106.7,471.0L1104.0,470.0L1103.1,467.4Z M1167.6,411.4L1168.1,410.9L1171.7,412.7L1172.6,412.4L1171.1,415.3L1167.8,416.8L1168.4,412.0Z M1148.9,425.2L1149.4,423.8L1156.9,416.7L1158.2,413.3L1158.7,416.6L1155.1,418.9L1153.7,421.6Z M1155.8,496.9L1159.8,496.0L1163.3,499.0L1161.8,500.1Z M1161.4,404.8L1163.9,404.5L1166.1,405.9L1165.6,409.3L1164.5,409.5L1162.7,405.9Z M1184.1,471.4L1187.4,471.1L1188.9,472.3L1188.9,473.3L1186.2,473.8L1184.7,473.1Z M1177.2,412.6L1180.1,413.7L1180.1,415.4L1181.1,417.4L1180.6,417.8L1180.0,417.3L1180.1,418.5L1179.1,417.9L1179.1,415.4L1177.8,414.9Z M1136.3,440.4L1140.2,438.7L1141.3,441.2L1140.4,441.2L1140.0,439.2L1139.0,439.8L1139.4,441.2L1138.4,442.6Z M1137.9,491.5L1140.8,491.1L1142.8,492.4L1140.6,494.2L1140.2,493.1Z M1143.4,494.0L1144.3,493.8L1144.2,492.6L1145.2,491.8L1146.9,492.2L1146.3,494.4Z M1173.3,420.7L1173.5,418.7L1176.2,413.5L1176.0,417.0Z M1110.3,470.4L1111.4,468.9L1113.2,470.1L1112.2,471.6L1111.4,471.1L1110.5,471.6Z M1216.6,482.8L1217.4,482.1L1216.8,481.6L1218.5,480.5L1218.9,483.9L1217.8,484.1Z M1130.9,487.1L1132.3,486.3L1136.3,486.7L1133.9,487.7Z M1170.3,480.9L1171.4,477.2L1172.3,476.3L1172.8,478.0L1172.2,477.8L1171.9,479.3L1172.7,480.1L1171.2,481.4Z","label":[1136.8,462.5]},"irkutsk":{"d":"M1062.6,217.2L1064.2,215.7L1066.0,215.9L1066.8,213.7L1066.3,213.1L1067.6,211.4L1067.0,209.1L1071.5,205.3L1071.0,203.2L1069.9,203.0L1069.4,201.6L1071.7,197.3L1075.9,197.8L1080.1,195.7L1081.7,198.9L1083.6,199.4L1083.4,197.1L1090.1,192.0L1089.8,189.5L1092.0,188.5L1097.8,192.7L1099.4,192.1L1099.0,191.4L1101.4,189.7L1099.2,186.3L1101.5,185.5L1101.9,183.0L1098.7,182.0L1097.8,179.4L1100.0,177.2L1099.6,176.2L1103.3,174.5L1103.6,173.3L1105.6,172.1L1105.6,170.4L1106.9,168.7L1104.5,165.8L1106.8,163.8L1106.1,163.0L1107.1,160.8L1106.7,160.2L1113.0,159.8L1112.9,158.5L1112.1,158.3L1113.9,157.9L1115.1,160.6L1113.2,161.1L1112.5,162.3L1116.9,163.3L1118.5,166.9L1117.3,169.3L1120.0,170.6L1118.4,173.1L1119.4,175.0L1119.2,176.4L1122.1,177.9L1119.0,183.0L1117.1,188.5L1118.6,190.0L1121.1,190.5L1122.9,188.8L1126.5,189.1L1128.9,187.4L1130.5,188.5L1129.9,190.8L1133.8,188.8L1134.4,187.0L1140.5,182.1L1142.7,181.3L1146.3,182.3L1149.2,184.3L1148.3,185.0L1148.9,185.6L1148.5,187.0L1151.6,187.7L1153.3,186.9L1154.9,187.9L1155.3,192.8L1156.5,193.5L1156.6,198.8L1157.9,198.6L1158.6,201.9L1159.6,202.6L1158.8,203.3L1159.2,204.2L1162.1,206.0L1160.2,207.2L1161.8,208.4L1164.9,207.6L1165.7,210.6L1167.5,209.9L1167.8,213.2L1166.6,214.6L1168.5,216.2L1166.7,216.8L1166.6,218.7L1167.7,219.7L1168.1,221.8L1163.9,222.7L1160.2,225.7L1162.6,226.6L1163.0,229.1L1160.3,231.8L1156.7,238.0L1157.4,238.7L1156.6,240.1L1151.3,242.7L1144.9,240.1L1141.7,240.7L1137.2,238.7L1131.2,242.7L1122.5,244.7L1114.1,243.6L1111.9,242.0L1111.7,240.5L1108.9,240.2L1106.8,238.5L1101.5,237.7L1094.5,239.5L1090.7,238.2L1089.2,237.1L1088.4,233.1L1079.7,230.9L1074.6,228.0L1075.7,224.7L1076.9,224.9L1076.1,223.6L1073.2,223.7L1069.3,221.0L1066.0,220.5Z","label":[1122.6,216.6]},"japan":{"d":"M1203.6,316.1L1211.7,310.5L1220.7,309.4L1221.3,310.4L1223.2,310.3L1224.4,309.4L1224.3,307.7L1226.8,304.9L1227.4,301.9L1229.3,301.3L1227.6,303.1L1229.0,304.8L1233.3,302.7L1237.5,298.6L1239.2,295.0L1240.3,291.5L1240.0,290.4L1239.0,290.1L1240.0,288.5L1239.7,286.9L1241.1,285.7L1241.4,284.0L1242.5,284.0L1243.0,285.8L1244.5,285.5L1245.0,284.0L1243.2,284.3L1243.6,282.7L1245.8,283.0L1245.7,286.3L1247.2,288.3L1247.9,292.4L1245.9,297.2L1243.8,298.4L1244.0,303.1L1242.3,307.2L1243.5,309.5L1241.8,310.5L1241.4,312.0L1239.4,313.2L1239.3,311.5L1240.4,310.2L1239.3,309.8L1238.7,312.1L1237.0,311.6L1235.4,314.5L1235.5,312.7L1234.9,312.2L1232.8,314.6L1228.2,314.7L1229.1,313.9L1227.5,314.0L1227.2,312.6L1226.1,314.2L1227.4,315.9L1225.3,316.5L1222.8,319.6L1220.5,317.3L1220.4,316.0L1221.6,314.5L1219.0,313.9L1212.6,316.0L1209.3,315.9L1208.6,318.0L1207.0,317.1L1203.7,317.4Z M1239.3,278.8L1239.6,277.1L1241.9,275.2L1241.6,273.9L1245.5,274.0L1247.0,268.1L1246.4,264.3L1247.8,263.0L1255.0,269.8L1258.9,270.8L1261.5,268.9L1260.6,272.1L1261.4,273.9L1263.3,273.5L1259.7,275.4L1255.9,275.9L1252.9,280.2L1247.4,277.4L1243.9,278.5L1241.9,277.5L1241.3,278.8L1244.6,281.1L1242.6,281.1L1240.6,282.9L1240.0,282.2L1240.2,279.9Z M1198.3,320.7L1202.9,317.6L1204.7,319.1L1206.8,319.1L1206.2,320.5L1207.6,320.7L1208.0,322.2L1206.6,324.2L1205.3,328.9L1204.3,328.8L1204.4,329.5L1202.7,330.6L1203.2,327.7L1202.7,327.5L1202.4,329.9L1200.8,329.4L1201.3,328.0L1200.8,325.8L1202.5,323.5L1200.9,321.0L1200.7,322.4L1201.4,323.1L1200.2,322.8L1199.1,323.7L1198.7,321.5L1199.9,322.4Z M1208.1,320.3L1210.6,318.7L1211.7,316.9L1212.6,317.6L1214.3,317.2L1214.4,316.2L1215.8,315.8L1218.5,316.3L1219.0,318.1L1216.7,320.7L1214.5,319.5L1213.1,320.2L1211.5,322.9L1210.0,322.2L1209.6,319.9Z","label":[1237.9,305.0]},"kamchatka":{"d":"M1158.7,203.3L1161.2,202.0L1165.6,202.1L1166.5,203.4L1175.7,205.5L1177.8,208.2L1183.0,208.8L1184.3,210.0L1190.7,209.4L1195.9,210.7L1196.2,209.6L1198.6,209.1L1203.6,209.4L1204.7,205.0L1207.2,203.6L1204.7,200.6L1206.3,200.3L1206.8,198.8L1208.1,198.1L1205.9,195.2L1208.1,196.0L1208.5,193.4L1210.2,192.5L1210.4,191.0L1212.5,189.2L1220.8,189.6L1223.2,187.2L1227.0,188.3L1230.5,186.0L1232.9,186.2L1233.0,181.5L1233.7,180.3L1232.8,178.7L1234.9,176.4L1238.2,175.5L1242.8,168.7L1249.7,173.1L1251.7,172.9L1252.5,171.7L1257.0,173.8L1260.7,170.5L1260.5,169.3L1261.8,168.9L1261.0,164.9L1262.5,161.9L1262.3,160.8L1264.4,160.1L1265.5,158.5L1270.4,158.7L1271.8,160.1L1273.0,158.2L1272.1,157.0L1273.2,156.2L1274.5,157.1L1279.3,156.1L1280.9,158.4L1282.5,158.6L1282.5,157.6L1288.1,156.5L1288.7,157.4L1290.9,155.4L1290.0,153.7L1290.8,151.8L1294.0,150.7L1294.0,147.8L1297.0,147.8L1297.6,145.4L1305.4,146.3L1306.6,145.5L1308.5,147.3L1313.8,145.9L1313.5,143.7L1315.6,141.3L1314.8,139.7L1311.2,137.6L1311.2,135.3L1313.2,135.0L1312.4,134.4L1313.1,132.7L1314.5,132.3L1325.6,130.4L1329.7,131.1L1330.8,129.1L1330.4,127.8L1331.3,127.3L1330.2,126.4L1331.0,124.8L1329.3,122.5L1329.6,121.7L1335.8,121.1L1347.5,122.8L1351.4,121.2L1353.2,124.4L1357.2,125.8L1358.5,127.8L1362.1,127.5L1364.0,126.0L1362.3,122.2L1360.6,121.9L1362.0,120.3L1361.9,118.5L1373.1,120.5L1383.7,120.0L1395.4,123.6L1418.6,135.5L1418.8,137.6L1420.6,137.7L1420.2,139.4L1420.9,141.5L1420.4,142.6L1422.4,144.5L1423.7,143.6L1423.8,145.2L1424.9,143.8L1423.1,142.5L1424.0,141.6L1423.7,140.5L1421.8,139.5L1427.4,139.6L1426.7,140.4L1427.0,141.1L1427.2,140.1L1429.8,140.4L1428.0,139.6L1432.8,140.5L1441.1,146.4L1437.9,147.6L1437.3,149.2L1434.3,148.0L1435.8,149.7L1435.3,150.0L1429.2,148.8L1431.1,150.3L1430.0,151.7L1431.1,153.0L1427.9,154.3L1428.8,154.7L1428.5,155.7L1430.5,157.0L1428.4,156.4L1427.4,158.0L1426.5,157.5L1426.7,156.4L1425.1,157.4L1421.7,155.1L1418.2,154.5L1416.6,153.2L1415.6,150.2L1411.8,149.3L1406.4,150.1L1404.2,146.5L1405.0,146.6L1405.9,144.2L1404.3,145.6L1403.4,144.3L1400.9,146.6L1402.6,149.9L1397.8,154.5L1394.1,155.9L1387.5,152.8L1386.7,153.2L1388.9,154.2L1384.2,153.6L1383.2,154.3L1384.7,154.1L1385.2,155.7L1389.5,154.8L1389.7,156.9L1390.7,157.8L1393.3,157.7L1394.9,161.9L1393.8,162.4L1397.3,164.9L1397.1,166.1L1398.3,167.5L1396.5,170.3L1388.1,167.5L1388.6,168.8L1374.5,174.1L1372.5,175.9L1371.4,175.6L1371.6,176.5L1369.6,178.0L1362.5,181.8L1361.4,184.6L1356.9,180.9L1348.9,182.0L1345.1,185.3L1344.5,184.6L1345.3,181.5L1340.4,183.8L1339.8,185.4L1338.1,184.1L1336.5,185.1L1335.0,184.3L1333.5,185.7L1333.1,188.6L1328.6,193.6L1327.8,195.8L1329.7,197.7L1330.6,196.5L1332.9,197.4L1331.0,200.6L1331.2,203.1L1333.0,203.8L1333.3,206.3L1331.4,207.3L1330.5,206.3L1332.0,204.8L1330.7,204.9L1328.3,207.1L1326.9,210.5L1328.4,214.6L1326.5,215.9L1323.1,215.8L1320.3,217.7L1319.4,220.0L1320.1,223.5L1318.3,222.9L1315.0,224.7L1313.9,224.1L1314.4,224.9L1314.0,227.6L1312.4,230.6L1307.0,235.1L1302.3,212.1L1303.9,203.7L1307.4,200.3L1307.9,199.3L1307.3,197.5L1313.1,196.1L1328.3,181.6L1334.7,179.0L1334.2,178.3L1336.0,176.3L1335.2,175.6L1336.8,170.5L1341.6,169.3L1337.7,167.9L1333.3,168.9L1332.1,173.0L1333.0,174.1L1332.3,174.9L1329.6,174.4L1323.1,179.9L1320.7,180.6L1321.4,178.2L1319.2,178.7L1321.2,172.9L1318.2,174.0L1316.7,172.7L1308.3,174.3L1297.2,185.5L1296.7,187.3L1299.9,187.7L1300.6,189.3L1296.0,189.9L1293.5,189.1L1291.3,190.8L1289.3,190.2L1285.3,191.1L1284.5,189.9L1288.9,189.0L1285.4,187.1L1281.9,187.5L1282.3,187.0L1278.6,185.8L1276.3,186.6L1276.8,187.5L1275.2,187.3L1275.0,188.1L1275.9,188.2L1274.9,188.9L1266.1,187.7L1264.2,189.4L1262.2,187.9L1255.5,187.9L1249.3,189.5L1243.2,194.5L1241.8,197.3L1234.6,202.2L1230.8,206.9L1220.9,214.1L1223.4,215.6L1227.2,215.4L1227.2,220.0L1228.6,219.7L1228.6,217.8L1230.7,217.2L1229.5,218.3L1231.3,219.1L1229.0,221.3L1231.8,220.9L1234.1,219.0L1233.1,221.3L1233.8,221.3L1234.8,219.5L1234.6,217.1L1238.8,217.3L1245.5,222.6L1244.7,224.1L1243.4,223.8L1245.0,225.1L1244.6,227.3L1245.9,228.7L1243.7,231.7L1242.1,236.0L1242.5,239.7L1240.5,248.3L1234.3,255.3L1230.7,261.5L1223.5,268.7L1220.5,272.8L1212.6,276.8L1209.2,275.9L1209.2,273.9L1207.5,274.9L1207.7,273.9L1204.6,277.2L1202.9,277.0L1202.7,278.7L1201.7,276.7L1204.3,275.8L1205.0,273.5L1205.0,270.1L1203.9,266.3L1207.4,264.0L1211.7,265.4L1212.4,264.9L1212.7,263.1L1215.4,259.4L1216.7,254.1L1219.0,252.0L1218.3,250.4L1218.7,249.2L1217.2,248.6L1212.6,250.0L1209.9,252.0L1203.8,252.0L1202.2,246.1L1200.8,245.9L1198.0,243.4L1192.0,242.4L1190.2,241.2L1190.3,239.1L1189.3,238.3L1189.2,236.5L1187.7,234.4L1185.3,227.6L1182.6,224.0L1174.4,221.3L1168.1,221.8L1167.7,219.7L1166.6,218.7L1166.9,216.4L1168.5,216.5L1167.6,214.7L1166.6,214.8L1167.8,213.2L1167.9,210.6L1165.5,210.4L1164.8,207.6L1161.0,208.1L1160.2,206.9L1162.1,206.1ZM1407.7,130.2L1407.9,131.4L1408.7,130.9ZM1382.3,154.8L1383.0,154.4L1382.3,154.6Z M1246.7,230.0L1247.3,222.4L1250.1,221.8L1250.8,219.3L1249.3,217.2L1251.0,216.6L1253.3,224.4L1252.6,229.1L1253.2,231.6L1257.1,243.8L1258.8,246.3L1258.9,247.2L1255.3,243.8L1252.1,244.8L1250.2,251.8L1252.8,256.6L1253.9,256.8L1254.3,258.8L1253.7,260.5L1253.1,257.8L1250.6,257.0L1248.3,261.0L1247.7,260.2L1247.9,252.6L1248.7,250.4L1247.5,246.7L1248.6,242.4L1248.8,233.8Z M1394.5,111.7L1401.2,107.8L1406.6,108.1L1410.0,110.4L1395.2,113.3Z M1351.2,120.4L1353.4,119.1L1357.7,120.4L1357.0,122.1L1355.7,122.3Z","label":[1357.8,151.7]},"madagascar":{"d":"M853.4,547.3L857.6,539.5L857.8,537.4L855.8,529.8L857.7,526.2L857.8,524.3L864.6,522.2L865.6,523.0L865.9,521.3L867.8,520.1L868.4,521.0L869.4,518.2L869.9,518.0L869.9,519.2L871.6,517.7L871.1,516.6L872.0,515.0L871.6,514.0L872.2,513.5L873.0,513.9L875.2,512.1L875.7,510.4L875.1,508.9L877.1,507.3L879.7,511.3L881.9,520.8L880.8,523.1L879.6,521.0L878.7,521.3L879.4,525.3L877.8,528.4L877.5,532.9L870.2,556.1L868.1,560.7L860.8,563.3L856.1,560.8L854.7,557.9L854.9,554.6L853.1,549.8Z","label":[866.8,532.9]},"middle-east":{"d":"M784.4,292.0L784.7,289.8L787.0,287.8L796.0,287.9L795.2,287.2L799.0,286.2L796.5,285.3L796.6,283.9L805.0,284.5L809.2,281.5L813.5,280.1L820.0,279.9L821.2,281.5L824.2,281.7L825.6,283.7L827.1,283.2L833.5,285.3L837.7,284.5L841.1,285.2L845.7,282.9L847.1,280.3L845.7,276.6L839.9,273.3L840.6,272.6L846.3,274.3L851.0,274.5L855.1,276.6L855.3,277.4L858.0,276.6L860.6,276.9L862.8,277.8L862.6,279.2L867.0,281.1L870.1,283.9L871.4,283.9L874.3,280.9L877.8,285.9L881.5,288.4L879.7,288.2L877.9,289.3L877.5,292.8L875.4,295.2L875.8,299.6L876.7,300.9L880.5,301.8L883.7,304.6L887.1,305.4L895.1,304.0L895.9,304.5L895.7,302.1L898.8,301.5L901.5,298.8L906.7,297.9L908.8,298.1L909.4,299.2L913.3,300.7L917.1,301.3L921.4,305.3L924.5,305.3L925.1,310.5L924.3,313.5L922.9,315.0L923.6,315.9L922.0,316.9L922.0,318.9L923.7,319.5L922.2,321.5L923.3,325.1L923.3,328.5L926.6,329.0L927.2,331.1L923.4,335.7L927.5,341.4L931.0,342.7L931.0,347.0L933.0,347.2L933.0,348.6L932.6,349.6L929.8,350.0L927.4,351.4L926.4,355.8L925.6,356.2L922.1,354.8L921.6,355.3L916.2,354.9L911.2,353.9L909.3,353.3L908.1,348.9L906.9,347.5L904.5,347.5L898.6,350.2L894.8,349.3L889.9,345.4L886.4,344.4L880.3,334.2L878.2,334.9L876.2,333.7L877.0,333.2L876.1,332.8L875.3,334.9L872.0,335.0L872.3,336.3L872.7,335.2L873.4,336.4L870.9,337.7L872.2,337.9L875.2,345.0L880.6,349.5L880.0,349.5L880.9,351.1L880.3,352.5L883.2,357.5L883.1,354.7L884.0,352.5L885.0,351.7L886.2,352.8L886.4,356.4L885.2,358.3L885.3,359.5L888.5,361.0L890.6,360.2L896.6,360.2L905.7,350.9L905.5,356.6L906.6,358.9L908.9,361.2L915.1,362.9L917.7,366.5L919.3,367.2L919.3,368.0L913.9,376.0L912.8,375.9L912.7,375.2L911.5,376.7L910.9,379.0L911.2,381.8L907.3,382.8L905.5,386.0L901.9,386.6L900.3,389.9L896.3,390.1L890.3,392.3L889.0,393.5L888.9,395.6L877.4,399.8L874.7,402.2L872.0,402.2L867.2,404.6L862.6,405.1L860.2,407.2L856.0,408.0L854.0,407.7L851.7,398.5L850.6,397.3L851.4,394.1L850.8,391.2L849.2,388.3L846.6,385.9L843.0,378.7L838.9,376.1L837.1,373.6L836.0,369.8L836.2,366.8L833.9,362.1L830.2,359.7L828.7,357.4L828.6,355.4L820.7,343.6L818.5,343.5L819.9,337.2L816.8,329.3L818.7,326.7L822.6,316.2L823.9,314.9L823.2,306.8L824.7,304.6L824.2,304.1L821.6,305.6L818.8,304.5L814.8,307.4L811.2,308.1L805.0,304.5L802.6,304.3L801.8,307.0L798.8,307.5L796.9,306.8L795.9,305.0L790.2,305.1L792.9,303.6L789.1,303.8L790.1,302.8L788.3,300.6L788.9,299.2L785.2,297.8L785.8,296.1L786.6,297.5L788.3,297.1L787.1,295.8L788.0,295.0L786.7,293.0L787.6,291.9Z M784.2,286.3L786.5,283.1L785.3,281.3L788.0,279.9L792.1,280.3L792.8,282.3L796.2,283.9L795.1,285.1L790.0,285.1L784.8,289.3L785.0,288.2L787.1,286.8Z M809.2,312.4L811.5,312.0L811.8,311.0L814.4,311.2L818.2,309.8L815.8,311.5L816.2,312.9L812.0,314.8L809.8,314.0Z","label":[852.0,315.1]},"mongolia":{"d":"M1031.0,246.0L1032.8,243.0L1036.4,242.8L1040.2,239.7L1049.4,235.6L1052.4,237.0L1057.0,237.3L1058.5,240.0L1064.3,240.2L1069.4,241.5L1073.0,238.6L1073.1,237.4L1071.3,234.7L1072.2,232.5L1075.6,229.0L1088.4,233.1L1089.2,237.1L1093.2,239.1L1096.3,239.4L1101.5,237.7L1106.8,238.5L1108.9,240.2L1111.7,240.5L1111.9,242.0L1114.5,243.7L1122.8,244.6L1131.2,242.7L1137.2,238.7L1141.7,240.7L1144.9,240.1L1151.5,242.7L1157.0,239.8L1157.4,238.7L1156.7,238.1L1160.3,231.7L1162.7,229.7L1162.6,226.6L1160.3,226.2L1160.4,225.4L1163.9,222.7L1173.2,221.2L1182.6,224.0L1184.2,225.6L1189.2,236.5L1189.4,238.4L1190.4,239.1L1190.2,241.2L1192.0,242.4L1198.0,243.4L1200.8,245.9L1202.2,246.1L1203.9,252.0L1209.9,252.0L1212.1,250.2L1217.2,248.6L1218.7,249.2L1218.3,250.4L1219.0,252.0L1216.7,254.0L1215.4,259.4L1212.7,263.1L1212.5,264.9L1211.7,265.4L1207.4,264.0L1203.9,266.3L1205.0,270.1L1205.0,273.5L1204.3,275.8L1201.7,276.7L1202.7,278.7L1201.0,279.3L1199.0,281.6L1198.8,285.6L1190.3,290.8L1189.6,293.4L1193.3,295.9L1197.3,302.4L1197.6,308.0L1198.2,308.5L1196.9,312.0L1194.0,312.4L1193.8,313.4L1190.9,313.0L1190.9,314.1L1189.6,313.6L1189.3,315.2L1188.9,314.0L1186.1,315.9L1185.5,314.8L1184.4,315.5L1186.3,313.7L1185.2,312.1L1187.0,308.9L1186.0,305.2L1184.6,304.7L1185.9,303.6L1187.9,303.9L1186.5,300.0L1185.8,299.8L1185.8,300.9L1185.7,299.7L1184.5,300.2L1183.1,299.1L1181.4,300.3L1180.0,299.4L1180.8,298.7L1178.8,298.5L1180.3,296.5L1182.1,295.9L1180.7,295.3L1181.4,291.9L1179.1,290.9L1178.6,291.5L1177.4,289.7L1174.6,290.3L1164.7,295.7L1164.4,294.8L1166.7,293.9L1167.2,292.6L1165.1,292.6L1165.1,291.8L1169.1,287.1L1167.3,285.1L1164.7,285.4L1161.9,288.6L1159.4,289.8L1158.3,287.2L1155.4,285.8L1156.9,283.6L1153.5,283.5L1152.6,281.5L1153.1,279.9L1152.5,280.1L1151.0,277.3L1147.6,278.3L1147.4,280.1L1144.2,281.1L1143.4,280.5L1140.9,282.2L1139.6,282.1L1139.3,279.4L1138.4,279.5L1135.7,283.0L1135.5,284.6L1136.6,286.2L1136.2,287.4L1132.3,287.8L1130.9,288.9L1129.6,288.4L1127.7,291.2L1124.4,292.6L1124.0,291.8L1122.7,293.2L1120.5,292.4L1120.7,293.1L1116.0,297.5L1116.0,299.1L1115.2,299.3L1115.1,300.5L1112.2,300.8L1108.7,298.4L1105.9,297.7L1107.9,294.3L1106.5,292.7L1105.1,293.1L1103.6,295.6L1103.2,300.0L1097.0,301.9L1093.5,299.1L1093.9,297.0L1096.7,294.6L1096.1,292.4L1091.9,293.8L1089.6,293.3L1087.4,294.1L1088.1,295.1L1086.3,295.9L1083.6,293.9L1083.2,292.5L1077.7,290.3L1080.8,286.8L1080.1,285.5L1072.9,287.1L1073.3,285.6L1070.8,283.3L1071.4,282.0L1068.8,276.4L1065.4,276.6L1061.3,270.3L1061.4,269.2L1058.9,268.8L1054.1,265.8L1043.5,264.6L1042.6,263.0L1044.0,260.4L1044.1,257.8L1041.3,252.2L1034.3,249.4Z","label":[1099.5,265.8]},"new-guinea":{"d":"M1202.7,462.7L1204.1,462.4L1204.2,463.9L1205.0,462.2L1210.0,460.1L1216.1,461.8L1217.0,464.2L1216.4,465.6L1216.8,468.0L1217.8,469.9L1218.5,468.9L1218.8,470.4L1220.4,472.1L1222.0,472.1L1225.6,467.8L1228.3,467.2L1228.5,466.1L1231.6,464.7L1239.2,468.1L1242.5,468.5L1257.9,474.0L1263.1,478.0L1263.0,480.4L1269.7,482.6L1271.2,484.0L1271.2,485.6L1268.5,485.7L1267.8,486.5L1269.0,488.6L1272.5,491.2L1274.3,495.0L1276.8,495.0L1277.1,496.8L1280.0,497.6L1279.1,498.1L1279.5,498.9L1283.4,499.8L1281.8,500.1L1282.6,501.0L1281.3,501.5L1279.0,500.3L1271.1,499.2L1266.5,494.6L1264.1,491.1L1258.1,489.1L1256.9,489.9L1255.1,489.1L1255.8,490.6L1254.2,490.8L1254.5,491.6L1250.3,492.0L1252.4,492.7L1253.6,494.0L1250.6,496.2L1248.9,495.5L1244.5,495.7L1240.0,491.6L1240.2,490.9L1237.5,491.6L1236.9,490.9L1235.7,491.9L1236.3,489.1L1235.0,487.8L1235.9,487.5L1234.4,486.5L1235.4,486.1L1232.2,480.6L1229.1,478.6L1220.8,476.6L1219.0,475.5L1218.8,474.5L1215.9,474.0L1214.7,472.2L1213.0,475.0L1211.9,475.1L1211.0,471.9L1207.9,469.9L1210.9,469.9L1212.8,468.5L1214.8,469.2L1215.7,467.2L1209.2,467.7L1208.1,466.7L1207.7,465.0L1204.0,464.4ZM1249.9,492.3L1250.0,492.1L1249.8,492.1ZM1249.5,492.0L1249.7,492.0L1249.6,491.9ZM1214.8,471.7L1215.3,471.1L1215.3,471.0L1214.7,471.3Z M1273.3,481.4L1273.7,480.6L1279.3,480.9L1280.4,478.8L1280.7,480.8L1282.9,480.8L1285.3,478.6L1286.7,478.3L1286.4,475.5L1289.6,476.1L1289.4,478.0L1287.9,479.0L1288.3,480.6L1286.1,481.0L1284.9,482.4L1281.9,483.8L1278.6,483.9Z M1230.6,492.4L1232.7,488.8L1235.1,488.4L1235.9,489.1L1234.1,491.9Z M1298.8,481.8L1298.9,480.5L1300.4,481.2L1303.3,484.3L1303.7,485.9L1301.4,485.7Z M1283.0,469.7L1283.3,469.0L1288.1,471.7L1292.1,475.2L1292.5,476.2L1291.9,477.8L1291.0,477.3L1290.7,475.3L1289.1,473.1Z M1318.4,496.3L1321.4,496.5L1323.3,498.3L1319.4,498.0Z","label":[1245.2,481.1]},"north-africa":{"d":"M609.9,399.3L611.4,398.6L613.7,395.3L614.1,391.7L615.7,387.8L615.7,383.8L613.9,380.3L614.7,379.9L615.1,376.7L612.5,373.1L611.6,374.1L612.3,369.7L614.6,366.8L616.8,361.8L616.1,362.3L616.4,361.5L620.4,357.8L622.1,351.7L625.7,349.2L628.2,344.1L634.3,342.1L638.1,339.1L640.6,335.9L641.5,333.5L640.5,331.9L640.8,328.8L642.6,325.9L643.0,323.7L646.0,320.7L652.4,317.5L656.3,309.2L658.4,308.6L659.6,310.7L661.5,311.9L666.4,311.8L668.1,311.0L668.7,312.2L671.9,312.4L678.3,308.9L679.8,309.0L683.9,306.2L691.9,304.7L699.0,304.2L701.2,305.3L705.9,303.3L707.7,304.0L709.7,303.4L711.6,304.3L718.8,302.1L719.3,303.0L720.8,302.7L721.6,304.9L724.2,303.3L724.5,304.3L721.9,307.5L724.0,309.9L724.5,311.7L720.2,316.7L721.2,318.5L722.9,318.7L722.9,319.5L723.8,319.0L724.9,320.7L726.0,321.0L726.1,324.1L721.1,327.7L720.5,328.6L720.9,331.6L719.6,333.4L717.3,334.6L719.2,338.7L719.7,344.7L719.0,346.7L719.5,349.7L717.7,351.7L720.0,355.2L721.0,358.4L726.0,359.6L727.9,362.9L733.9,364.4L736.9,366.7L743.9,363.2L775.9,379.8L775.9,395.3L772.4,395.4L771.8,396.1L771.7,397.6L769.5,400.1L770.2,401.7L768.4,403.2L768.9,405.1L767.3,407.3L769.4,407.8L770.4,412.2L771.7,413.2L771.4,414.9L770.0,414.6L767.1,416.0L766.7,417.4L761.4,422.1L755.8,422.9L756.4,424.0L754.3,426.5L750.6,426.7L747.1,428.5L746.1,427.2L743.8,428.7L741.9,428.6L742.2,427.6L740.5,424.4L735.9,419.9L737.0,418.7L742.5,418.6L740.5,416.0L740.3,411.1L739.4,408.5L737.9,406.4L736.3,406.1L736.8,408.9L738.5,409.9L738.2,412.6L734.8,415.1L732.8,420.4L731.7,420.9L730.9,423.7L729.6,424.3L726.1,432.1L724.6,433.0L722.5,430.5L721.7,431.2L720.5,430.7L716.2,434.7L714.2,439.7L713.0,439.1L713.2,440.5L708.7,440.2L708.1,441.1L707.2,440.3L707.0,441.4L705.0,441.1L704.7,441.6L703.2,440.9L701.5,438.2L702.1,436.9L700.8,436.6L701.7,436.2L700.4,436.1L697.7,433.3L693.8,433.0L694.6,432.4L693.3,433.1L685.2,434.1L683.8,435.5L681.0,435.7L672.0,439.7L667.0,438.3L667.7,438.1L667.2,437.3L666.6,438.2L663.5,437.5L658.9,437.9L659.8,438.2L656.3,438.7L649.8,441.3L647.0,440.4L643.5,438.5L633.1,430.3L630.1,429.1L630.0,427.7L628.6,427.4L626.9,424.9L628.4,424.2L627.3,424.3L627.6,423.1L625.2,420.5L625.2,419.0L622.3,417.6L620.9,414.8L620.0,415.4L619.7,414.0L619.1,414.4L618.4,413.7L618.3,412.5L619.6,412.1L618.0,411.4L619.7,410.7L616.2,411.4L616.3,410.8L614.7,410.3L615.0,409.6L613.0,408.9L614.2,408.1L612.9,408.0L612.7,405.1L613.3,404.5L613.8,405.2L615.2,405.3L616.6,404.6L614.2,405.0L612.1,400.7ZM617.8,404.5L617.6,404.5L617.7,404.6ZM616.7,404.7L616.9,404.6L616.7,404.6Z","label":[671.2,381.9]},"northern-europe":{"d":"M690.1,234.4L693.7,232.8L696.6,233.0L693.8,232.1L697.0,232.4L696.0,231.8L696.6,231.6L696.1,230.0L697.9,228.0L699.1,224.5L702.1,222.8L707.3,221.8L708.6,222.7L708.3,221.6L709.1,220.5L712.0,220.5L712.8,221.9L713.3,220.9L714.1,221.4L714.5,219.5L716.7,219.6L715.7,219.0L715.6,217.3L714.6,217.2L715.8,215.9L714.7,214.4L714.5,210.9L712.5,209.9L712.7,204.2L714.7,204.8L715.6,203.5L716.8,203.6L717.0,202.0L716.5,201.7L715.1,203.5L713.1,203.0L714.5,201.3L717.7,201.0L719.8,198.6L722.4,197.7L721.1,204.1L723.4,204.7L723.6,205.6L721.3,206.4L720.7,208.4L718.4,210.5L718.7,211.8L717.9,212.9L720.1,215.1L719.8,216.1L724.1,216.7L723.4,218.7L725.6,219.1L730.3,216.2L737.5,219.2L751.4,214.3L753.3,214.2L755.0,215.0L753.8,214.8L755.3,216.7L757.6,216.7L759.4,215.3L759.9,213.7L762.1,213.3L763.6,211.7L762.5,213.4L764.7,213.6L764.9,211.8L765.6,211.7L770.3,212.9L771.3,214.0L771.0,216.7L773.8,218.0L775.4,223.6L775.6,225.8L772.7,228.1L774.6,229.4L774.2,231.7L776.4,235.7L775.9,238.0L770.6,242.5L771.4,245.0L770.2,245.0L768.4,248.5L762.6,247.7L759.6,249.8L755.6,250.3L754.9,251.6L751.0,251.7L748.3,250.3L747.5,246.9L740.0,245.4L738.8,247.4L735.3,246.6L734.9,247.7L733.9,247.5L733.5,248.7L731.3,249.7L732.1,253.2L728.9,252.0L724.2,253.6L721.8,252.8L720.7,254.2L719.4,252.8L714.3,251.6L713.7,252.6L710.3,252.5L710.5,249.7L712.5,245.5L706.9,244.5L705.0,242.8L702.0,242.7L699.5,241.3L699.3,239.4L696.7,240.4L696.7,238.9L694.8,238.6L692.4,236.1L691.0,236.2ZM735.3,218.7L735.8,219.9L738.3,220.6L738.2,219.7Z M723.9,209.2L726.5,207.9L727.3,209.3L727.5,207.8L729.3,207.0L730.4,207.5L730.7,209.9L729.0,210.2L729.7,211.7L728.4,212.2L728.2,214.3L727.4,214.5L726.6,212.2L724.7,211.4Z M719.4,211.3L719.4,210.4L722.6,209.8L723.2,212.1L721.8,213.0Z","label":[755.9,233.0]},"northwest-territory":{"d":"M116.0,121.7L123.3,122.7L128.5,125.7L138.9,128.5L136.4,126.8L137.3,126.2L136.4,125.5L139.4,123.0L142.1,123.0L142.4,121.5L144.5,122.7L143.5,124.5L157.3,118.3L161.8,118.5L149.2,124.5L146.4,127.3L147.1,128.1L146.7,127.5L149.7,127.4L149.1,125.8L152.8,123.3L155.7,123.2L156.2,124.7L159.5,121.2L164.4,119.5L163.4,120.7L164.6,121.0L169.0,117.6L167.3,116.2L168.0,115.1L177.1,123.7L179.3,123.3L178.6,121.7L180.8,119.4L180.0,119.1L181.8,118.2L182.4,120.9L183.5,121.5L182.1,123.3L182.6,123.8L185.9,123.6L187.9,120.6L193.0,120.7L211.1,126.9L218.2,126.7L224.1,130.5L220.9,131.4L218.4,133.8L219.5,134.5L230.0,135.4L239.7,133.3L241.5,135.1L243.8,135.4L244.6,137.2L245.5,136.0L248.0,138.3L248.3,139.2L246.4,139.8L250.6,143.9L249.0,140.3L251.2,140.6L248.2,134.5L248.8,133.0L254.2,131.9L257.0,129.1L249.5,131.0L249.1,132.1L245.1,131.2L246.7,129.0L255.3,126.9L257.3,127.5L258.5,130.4L262.1,132.8L266.1,132.5L270.7,135.1L286.3,134.6L285.2,133.5L285.5,132.8L290.2,135.9L291.4,134.6L287.2,133.8L285.4,130.8L288.6,130.6L288.7,129.6L292.1,131.5L294.3,131.1L293.2,133.0L296.1,131.5L294.5,136.6L295.4,138.2L298.3,139.0L298.2,140.1L297.0,140.4L297.5,140.6L298.6,140.2L299.0,138.3L297.4,135.1L298.2,133.1L301.0,132.8L305.9,129.1L304.7,126.4L303.7,127.8L301.6,127.7L303.5,125.6L303.0,124.1L305.5,123.4L305.0,124.7L306.1,123.9L305.8,122.9L302.9,123.1L296.1,120.7L293.8,117.7L296.3,114.5L293.8,113.4L294.2,110.3L295.8,109.0L297.7,109.6L298.3,108.3L296.5,107.8L301.6,104.7L307.9,109.6L308.2,113.2L313.7,118.0L310.7,117.6L310.1,118.5L312.1,119.0L308.7,121.7L315.2,121.8L314.7,122.5L318.3,123.1L317.0,123.6L318.0,124.8L315.2,124.4L318.1,127.2L317.7,130.0L319.0,131.4L322.9,124.5L327.8,127.6L328.7,131.2L326.6,131.3L326.7,133.6L330.6,138.9L333.8,136.8L336.2,132.8L337.2,128.6L340.3,127.9L339.6,127.2L340.5,125.8L338.3,124.1L338.3,120.3L349.5,121.4L350.5,121.8L349.2,123.0L351.1,124.6L354.5,125.0L352.3,127.0L354.5,127.4L354.9,128.7L349.8,130.2L351.1,132.3L351.9,131.9L351.8,133.8L354.9,137.0L354.1,139.6L346.4,144.3L341.8,140.2L339.6,140.6L343.1,142.2L344.5,145.3L341.5,145.4L337.6,143.0L333.2,143.3L333.3,144.4L336.2,146.0L330.8,151.0L328.1,151.0L321.6,147.3L317.5,147.2L324.1,151.0L332.0,152.6L327.6,158.6L323.5,159.9L320.0,158.8L320.6,160.0L319.5,159.9L319.9,161.0L319.0,162.0L316.2,162.4L305.5,159.1L305.8,160.6L311.4,161.7L310.5,162.5L312.6,161.7L316.1,163.3L317.2,164.2L317.2,165.7L314.2,167.3L310.5,167.2L312.3,168.6L309.8,168.9L308.9,170.0L309.9,171.3L307.9,170.2L308.4,171.0L307.9,171.7L305.8,172.7L306.8,173.7L304.4,175.5L301.1,181.4L300.9,184.4L123.8,184.4L123.6,182.4L120.1,183.3L116.0,182.6ZM314.5,147.1L316.1,147.3L317.4,147.3ZM296.5,140.2L294.3,139.7L296.9,142.4L295.8,140.6Z M319.9,105.0L321.2,103.5L320.7,101.1L325.2,94.0L330.1,91.2L339.6,90.9L340.2,91.5L336.0,94.7L333.3,98.9L334.7,101.2L335.1,105.4L339.9,109.4L333.9,111.9L339.6,110.9L340.5,112.0L341.2,107.4L336.4,104.7L338.7,102.9L342.6,104.2L340.6,102.3L341.3,101.8L338.6,101.5L337.4,99.2L338.5,97.5L342.5,98.3L338.2,96.3L346.4,92.2L353.6,91.7L358.9,98.8L355.5,102.0L357.6,101.3L356.3,103.0L357.2,103.9L356.3,104.1L356.3,105.3L360.2,103.2L359.6,102.1L360.7,101.3L364.0,102.6L364.0,104.3L365.5,105.5L364.5,103.9L365.2,102.0L369.9,103.2L366.8,101.9L366.3,100.3L372.4,99.2L379.7,102.2L377.4,105.5L380.4,103.9L382.9,104.4L382.7,105.8L379.7,106.8L381.2,107.0L380.1,110.3L382.0,107.2L384.5,106.3L385.1,106.7L383.6,108.9L385.1,107.7L387.2,109.8L386.9,108.1L388.4,107.0L393.4,108.2L395.3,110.0L394.0,111.2L392.2,111.3L389.9,113.1L397.3,111.6L397.0,113.5L392.7,116.1L394.9,115.6L394.4,118.1L397.8,113.9L403.3,113.7L406.5,115.8L399.8,118.9L404.9,117.8L404.1,119.4L405.0,119.7L407.5,117.0L408.6,117.2L411.2,121.0L403.1,122.6L411.1,123.1L413.3,124.5L405.5,124.9L404.4,125.6L406.8,125.2L409.0,126.8L408.5,127.8L403.2,127.5L413.1,130.2L411.9,130.9L412.4,132.8L415.2,131.3L414.2,134.4L416.2,132.8L416.5,133.8L418.0,133.5L418.3,135.3L418.3,134.1L420.3,133.0L419.9,134.7L421.4,134.4L424.6,136.2L423.7,136.7L424.0,137.7L422.2,137.8L422.6,138.3L425.6,137.5L427.8,138.5L425.7,141.2L431.5,139.8L434.8,142.4L431.7,142.4L433.7,144.0L432.6,144.7L430.0,144.0L430.0,145.3L432.0,146.5L429.8,146.6L430.5,147.8L429.4,149.1L426.3,148.0L425.7,149.0L426.7,149.3L426.5,151.8L425.6,153.8L422.6,152.2L422.8,150.9L421.8,152.5L418.4,148.3L421.7,144.7L416.7,146.8L416.8,145.8L414.3,145.1L412.2,142.6L410.8,143.0L411.1,144.7L408.5,143.6L411.3,146.5L410.6,147.2L405.5,145.2L408.5,148.2L408.2,149.6L409.7,149.1L411.5,150.4L410.8,151.1L413.2,154.4L413.5,153.3L415.1,155.1L416.2,154.0L418.3,155.1L418.9,155.7L418.2,156.4L419.7,156.9L417.9,157.9L419.4,159.6L421.3,159.6L422.4,161.6L421.8,161.8L421.7,164.5L419.5,161.6L419.7,164.5L421.3,166.6L419.4,166.4L419.6,168.4L413.5,164.6L413.2,165.6L408.7,161.6L409.1,163.4L404.6,161.3L407.4,165.0L416.1,171.0L415.8,172.8L415.0,173.1L405.9,170.7L401.6,167.5L399.1,167.6L394.0,165.3L392.0,163.5L394.4,162.4L390.8,161.5L391.2,160.4L390.0,160.9L386.2,157.2L386.8,156.0L383.7,157.0L381.3,154.4L380.4,154.8L381.2,156.6L377.2,156.4L372.6,158.2L368.8,157.4L367.3,155.8L367.8,153.4L370.5,152.1L370.7,150.3L376.7,151.8L378.2,154.3L378.5,153.2L376.8,151.4L385.8,150.1L382.3,145.6L391.1,138.3L386.7,130.8L384.7,130.8L384.6,128.4L382.3,129.3L380.4,127.6L381.0,127.0L380.2,126.6L380.6,125.9L373.7,128.4L373.8,126.3L377.4,124.8L374.1,123.0L374.6,121.6L371.6,121.8L372.5,120.8L369.6,120.3L368.9,117.5L366.9,117.6L363.7,114.9L362.4,116.3L364.8,118.0L363.6,119.8L353.6,118.5L356.3,121.2L353.7,119.7L350.8,120.4L347.4,119.2L336.9,119.0L334.7,118.2L334.1,116.1L328.4,117.4L324.6,115.5L322.2,111.6L331.2,111.9L321.2,108.9ZM421.8,137.8L421.6,137.8L421.7,137.9Z M313.3,23.5L327.7,19.1L339.3,20.2L333.5,17.9L340.4,15.6L349.5,19.2L350.4,19.2L349.0,18.1L349.9,17.6L361.7,21.3L350.2,15.8L352.8,15.0L351.5,13.8L356.5,14.4L355.3,12.7L356.0,12.4L364.0,13.2L359.4,11.0L360.5,10.8L370.1,11.3L376.0,14.8L377.6,13.9L371.5,10.1L377.0,9.7L388.3,12.7L386.2,10.7L400.1,9.0L414.3,10.9L407.3,13.7L420.4,11.1L426.0,12.2L425.6,13.1L427.7,14.5L427.3,15.5L435.2,16.7L431.3,19.6L405.4,26.7L420.9,24.9L401.8,35.3L397.5,34.1L398.9,36.6L392.0,37.7L397.8,38.1L394.5,41.0L382.4,39.9L381.8,40.3L387.2,42.4L373.6,43.4L377.6,45.6L382.1,45.7L381.9,47.3L366.5,47.1L382.3,50.2L380.5,51.8L375.6,52.0L379.2,53.7L376.5,56.4L368.0,57.0L367.7,60.7L366.0,61.9L357.7,62.4L353.6,60.7L354.9,62.0L352.0,62.6L362.0,63.4L363.1,65.6L368.0,66.3L366.9,68.6L356.8,71.9L356.0,71.6L356.7,70.2L356.1,69.5L350.7,67.9L351.1,69.5L346.4,69.7L343.2,67.9L342.9,70.4L339.4,70.9L334.2,68.5L333.3,70.3L330.1,68.7L330.0,70.2L326.4,70.0L325.9,67.2L325.8,69.9L321.7,69.3L322.0,66.5L325.8,64.4L332.5,63.5L329.3,61.4L327.9,58.4L333.0,57.7L337.6,61.2L342.1,61.9L345.6,61.3L349.5,56.9L344.9,60.5L340.6,60.8L338.8,60.3L337.8,57.2L342.1,54.8L340.4,54.5L340.7,52.4L337.7,55.6L335.1,55.8L336.0,53.8L329.8,55.0L330.0,52.9L332.8,49.8L340.0,48.6L346.9,49.8L352.7,48.2L342.4,47.8L341.7,47.1L345.3,47.1L338.9,41.8L334.3,40.2L333.5,37.7L334.0,36.4L345.1,36.7L353.2,41.6L358.1,42.4L359.0,41.9L355.3,41.2L348.1,35.8L371.8,30.9L365.1,30.0L370.9,25.8L356.0,32.8L348.5,33.5L350.6,31.7L339.4,34.0L333.5,33.0L339.0,29.6L346.7,28.3L336.9,29.2L329.2,32.7L322.9,30.3L334.1,29.2L339.8,26.9L321.5,29.2L320.1,28.4L322.4,27.3L321.3,26.4L322.3,25.9L327.7,24.4L318.8,25.7L317.6,25.5L320.3,23.6Z M204.0,106.4L204.2,104.7L207.1,102.7L206.1,101.4L207.5,99.9L221.4,94.2L223.2,94.8L223.8,96.5L222.0,100.2L225.7,99.7L227.7,97.1L234.9,99.3L232.7,101.8L233.3,102.4L239.2,99.6L237.4,97.0L240.0,97.2L245.0,100.5L247.2,106.7L248.8,107.4L250.6,105.3L249.2,103.5L247.1,95.9L247.9,94.4L252.7,95.0L258.3,98.7L262.5,107.8L261.9,111.5L265.7,114.7L267.7,115.6L267.8,114.5L273.8,118.3L275.6,118.3L276.4,120.6L275.8,121.6L271.1,120.4L269.5,122.4L266.3,121.6L267.8,123.0L267.6,124.8L271.4,122.9L272.8,125.1L272.6,126.2L268.4,127.6L259.6,126.9L259.9,125.8L255.4,125.2L253.4,123.2L250.2,126.3L242.1,128.6L227.9,129.9L225.8,127.9L225.2,125.0L213.9,123.3L211.6,120.6L211.2,118.9L221.6,117.0L233.3,116.9L225.0,114.3L209.7,114.7L206.5,112.3L218.7,108.4L208.3,109.1L207.1,108.6L208.8,107.2Z M176.6,104.8L180.0,100.3L180.1,98.3L181.7,97.5L180.8,96.1L184.8,91.2L181.2,86.6L194.0,85.1L201.7,87.5L201.2,88.4L203.2,87.8L203.5,89.2L205.5,87.5L209.2,87.4L218.4,93.2L204.1,99.5L201.9,102.4L199.3,103.0L197.5,108.3L193.0,108.8L188.6,111.3L186.7,110.8L184.0,107.0Z M292.9,37.5L301.5,38.3L302.2,37.0L294.4,35.9L297.4,35.4L295.4,33.7L296.3,32.1L303.5,33.6L297.9,31.0L300.1,29.1L307.1,28.2L303.3,26.3L307.9,26.3L312.0,27.8L317.5,33.3L323.1,34.1L323.2,36.4L324.6,37.3L327.0,37.6L325.4,36.1L325.9,34.9L329.3,35.4L329.5,37.1L328.5,37.9L332.1,39.7L331.0,42.7L334.7,42.1L336.0,43.5L337.4,42.3L339.8,45.2L332.2,48.0L329.5,50.6L328.3,50.0L328.6,47.5L327.8,47.8L327.1,50.7L328.1,51.9L325.1,51.4L325.6,53.1L324.7,54.9L319.9,51.3L322.0,55.0L318.2,53.7L318.7,54.9L314.4,54.9L308.6,52.5L311.6,51.6L306.9,51.2L305.5,50.0L306.5,49.4L303.3,48.0L313.0,44.5L299.6,45.2L297.1,44.1L302.2,41.3L296.6,41.7Z M292.4,67.1L294.2,66.9L293.0,65.3L297.4,64.5L307.1,67.0L306.2,69.5L315.5,67.9L317.8,69.3L314.3,69.4L322.9,70.9L314.6,71.7L322.9,75.0L321.9,76.8L324.3,77.8L325.1,76.1L336.2,78.3L335.7,77.4L344.3,74.8L354.9,75.4L361.4,77.8L362.0,79.4L358.7,81.2L362.4,82.1L358.8,82.2L359.4,83.1L358.9,84.8L352.2,85.6L348.3,84.9L347.6,82.9L346.0,82.3L346.6,83.3L345.9,84.8L342.3,85.4L326.3,85.5L325.8,84.9L326.6,83.2L325.9,82.8L321.8,85.0L316.2,83.1L314.0,84.2L312.2,83.1L311.7,80.5L310.4,79.3L311.7,76.2L311.3,74.6L307.6,70.4L298.9,71.2Z M209.6,79.3L211.0,77.8L219.2,76.1L211.3,76.3L218.8,74.5L213.3,73.7L215.2,71.8L220.1,72.0L216.7,70.9L220.9,69.2L223.2,69.6L224.7,71.7L229.2,71.7L235.8,77.0L244.0,77.3L244.2,75.8L240.5,73.9L242.3,72.4L238.7,70.3L243.6,66.6L246.1,67.3L245.5,68.5L247.5,71.4L246.4,72.8L249.1,73.4L248.1,75.0L251.1,74.2L252.3,75.7L253.0,74.9L252.6,73.8L254.4,72.9L257.5,73.8L258.1,75.4L255.6,80.7L251.4,82.0L244.7,80.9L229.9,86.1L225.9,86.0L222.5,84.1L235.8,79.6L225.2,80.9L224.5,79.8L225.7,78.2L223.9,78.0L223.3,79.5L222.0,79.2L222.2,80.8L217.1,81.7Z M269.1,98.7L271.2,96.5L274.9,99.2L279.5,97.8L277.8,95.8L279.9,95.2L273.9,93.3L276.1,92.5L277.9,93.5L276.3,90.9L289.7,90.2L292.0,91.9L289.6,93.0L291.1,93.9L286.3,97.5L290.1,97.1L291.7,98.9L291.5,99.9L293.6,99.3L294.2,101.0L293.1,102.3L293.5,105.9L289.7,107.4L287.3,107.1L286.7,105.8L286.2,106.4L287.2,108.8L285.3,109.8L283.1,109.2L277.6,103.5L273.1,102.3Z M331.2,162.1L335.0,158.9L334.5,156.1L335.7,149.8L337.8,147.3L339.3,148.4L339.1,149.9L340.3,151.6L342.0,150.3L351.8,155.6L353.3,158.4L352.6,159.6L356.7,159.2L359.0,161.0L355.8,163.2L350.5,161.6L350.1,160.2L346.8,158.8L341.5,164.1L338.4,165.3L336.9,161.7Z M297.4,91.9L300.5,91.9L299.4,90.1L300.1,89.0L306.4,88.0L318.6,90.4L311.5,99.0L303.6,98.9L305.9,100.9L303.4,104.6L299.2,104.4L299.0,100.9L297.6,98.0Z M188.4,72.3L203.6,62.5L212.6,62.2L211.8,61.1L214.0,60.4L218.1,62.4L214.8,63.9L216.8,65.6L215.1,65.9L216.1,67.7L212.0,69.0L211.1,71.0L208.0,70.0L208.5,66.7L204.8,69.1L205.5,70.3L204.0,72.2L201.7,70.7L201.1,72.5L201.9,73.4L200.3,74.5L198.4,74.8L196.6,71.9L195.1,73.5L190.4,73.8L189.4,73.3L189.5,72.0Z M268.8,76.6L271.4,74.4L274.7,75.2L272.5,72.7L273.6,71.4L271.4,71.0L272.6,69.7L280.9,74.0L279.5,72.4L280.6,71.9L278.3,71.4L279.9,70.7L276.4,69.4L279.7,68.1L284.2,69.5L283.9,68.3L285.2,67.6L289.1,69.3L289.9,72.2L289.6,74.6L288.6,75.4L290.4,76.0L290.7,78.1L288.5,78.1L289.4,80.2L287.7,79.9L288.0,81.1L281.5,81.6L279.1,81.4L278.1,79.9L279.0,79.3L277.1,78.7L281.4,75.9Z M257.7,46.8L258.4,44.9L265.2,44.6L268.3,45.7L269.7,48.8L273.2,47.1L275.9,48.3L275.5,49.5L281.6,51.5L280.9,53.8L284.0,56.6L280.2,58.3L275.7,54.9L268.8,53.3L262.0,54.0L260.0,52.0L265.7,51.8L263.9,51.0L266.3,50.0L263.3,49.7L264.4,48.6L263.4,47.9L260.4,49.5L261.0,47.5Z M356.6,92.5L357.5,91.2L367.7,92.1L371.2,93.2L375.6,98.0L362.0,98.9L360.3,98.1L359.2,95.4L356.9,94.5Z M281.7,126.1L286.0,124.2L285.8,122.3L287.4,123.0L286.9,121.0L288.8,120.2L298.9,127.5L293.6,130.1Z M293.6,81.2L296.2,77.9L301.4,76.4L305.0,78.7L306.1,82.6L305.7,84.1L301.2,84.3Z M286.6,50.0L290.5,49.7L300.5,53.5L298.8,54.4L300.3,55.6L299.7,56.6L292.0,58.2L289.4,55.8L291.9,55.2L287.8,53.7L286.7,52.4L287.6,51.6Z M370.8,135.3L373.6,131.3L377.3,131.0L379.7,132.3L379.5,136.4L376.9,138.2L372.0,138.3Z M226.9,58.1L235.2,55.8L241.5,56.0L236.7,58.0L239.4,58.6L239.2,60.6L232.2,62.1L227.3,60.6Z M252.2,93.2L253.5,91.7L258.7,91.2L261.8,93.5L258.6,97.7Z M226.8,53.6L237.5,49.9L242.0,51.5L242.7,53.0Z M344.4,169.7L346.5,166.7L352.2,166.8L347.9,171.0L345.2,171.3Z M294.9,59.7L298.1,58.3L307.5,59.5L304.7,61.2L296.1,61.0Z M262.4,72.5L269.1,70.8L272.0,73.9L266.7,75.3L267.7,74.0L264.9,74.2Z M358.9,173.4L359.1,171.4L360.3,169.9L362.9,170.8L361.1,174.7Z M202.4,76.5L205.5,74.1L210.0,72.7L206.7,76.8Z M315.3,60.3L319.3,60.0L321.2,62.2L319.1,63.3L316.0,62.3Z M255.7,58.9L258.2,59.1L262.2,63.2L259.1,63.5Z M351.8,224.0L355.5,223.1L357.2,225.4L356.0,225.5Z M279.5,38.8L283.9,37.8L284.9,40.1L284.2,41.3Z M260.5,80.3L262.6,78.0L265.4,80.1L262.8,81.2Z","label":[167.8,153.4]},"ontario":{"d":"M272.0,184.4L300.9,184.4L300.5,190.0L302.9,192.0L302.8,194.2L303.8,191.8L307.3,192.0L310.2,199.8L309.3,202.2L317.6,200.7L324.7,203.0L330.1,207.5L337.3,209.9L339.1,211.3L339.0,212.1L344.4,211.5L350.4,212.9L351.1,214.3L350.3,217.4L351.4,219.8L350.8,224.1L353.6,227.3L353.0,228.4L357.4,230.9L358.2,232.9L356.9,234.1L358.9,233.3L361.8,235.2L361.1,233.9L361.9,232.0L361.8,253.2L362.5,255.5L364.9,258.6L369.3,259.7L371.9,261.6L372.7,261.2L373.8,262.9L376.4,263.5L381.7,262.4L382.4,262.8L382.6,264.5L375.4,269.0L372.7,272.3L364.6,272.5L363.4,273.1L364.3,275.8L363.9,276.3L349.2,281.7L347.9,281.0L347.4,279.5L349.8,277.2L351.4,272.6L349.8,263.9L345.6,261.5L346.1,260.6L345.5,260.0L344.1,260.2L343.4,257.9L341.8,258.3L340.5,256.1L327.4,249.4L322.2,250.5L316.3,249.4L313.9,250.2L308.0,247.4L305.2,247.8L301.5,246.7L300.6,243.8L299.5,243.5L299.4,245.4L274.5,245.4L272.0,208.7Z","label":[314.6,225.1]},"peru":{"d":"M354.7,477.4L355.1,475.7L359.9,471.6L361.1,469.1L360.6,467.2L360.3,468.9L359.8,468.3L358.9,469.6L356.3,467.8L357.0,466.5L356.4,463.1L357.8,462.1L358.4,461.1L358.1,460.2L359.8,458.1L359.6,455.6L364.4,453.9L364.6,452.9L369.2,455.4L370.4,457.2L374.0,457.8L374.9,457.0L376.9,458.4L380.8,459.5L383.0,462.6L385.3,463.7L388.2,468.3L391.1,468.3L393.0,467.3L394.4,468.1L396.1,467.6L399.6,469.4L397.1,473.8L399.0,474.3L400.1,476.0L396.8,475.4L392.6,476.8L388.5,479.2L387.1,483.1L387.5,484.6L385.0,486.4L385.1,488.0L384.0,489.0L388.1,494.8L387.3,496.5L390.5,496.9L391.4,498.9L395.0,498.7L397.8,496.6L397.4,502.8L398.6,503.2L400.2,502.6L405.5,503.4L413.7,498.5L418.4,497.7L418.4,503.9L420.0,506.9L422.3,508.8L427.7,509.7L428.9,511.0L431.5,511.7L432.8,513.2L435.7,513.0L438.0,514.2L438.9,519.5L437.8,519.7L439.0,521.1L439.3,524.4L446.6,524.4L446.4,528.3L448.7,529.5L450.0,532.4L447.5,538.7L448.6,539.7L447.4,540.5L447.3,539.0L443.6,536.8L433.0,538.3L430.9,542.1L429.4,549.1L428.6,548.1L424.3,548.3L422.7,551.6L421.6,549.1L416.9,548.6L415.1,547.3L411.2,551.6L408.8,551.9L407.2,545.2L405.0,541.5L405.0,540.2L405.8,539.6L405.2,538.6L406.1,537.4L404.1,535.5L403.6,531.7L401.9,529.6L400.6,530.3L400.3,532.4L398.3,532.9L394.7,530.2L393.9,528.6L379.6,520.9L376.3,517.7L374.5,514.5L375.1,512.5L371.4,507.2L371.1,505.6L369.5,504.1L364.9,493.3L360.0,485.8L355.4,483.0L356.5,481.3Z","label":[420.1,526.3]},"quebec":{"d":"M361.1,215.2L368.9,211.6L373.0,207.7L373.8,205.6L373.7,200.9L372.4,197.6L365.9,192.2L368.9,188.2L369.0,186.5L370.2,186.7L370.8,184.3L369.7,183.9L370.1,183.5L369.3,181.9L369.9,181.1L368.8,180.6L369.3,179.6L367.3,179.5L369.8,175.1L367.9,173.3L367.6,170.3L370.5,168.7L381.2,171.4L385.2,169.4L389.3,171.5L389.1,173.3L390.0,172.8L391.7,174.7L393.4,174.7L392.6,175.7L394.3,177.4L398.9,178.0L400.0,179.3L402.0,178.2L402.4,179.3L401.0,181.5L401.3,184.0L397.7,184.5L401.1,184.9L401.7,186.4L401.2,188.3L402.6,188.6L401.9,191.1L400.8,190.6L399.5,191.8L405.2,190.9L406.3,191.7L407.1,193.9L405.7,196.2L407.6,193.5L408.5,194.7L409.0,193.9L409.3,196.1L414.6,191.6L416.0,193.6L415.8,191.4L418.3,190.1L417.2,189.1L418.6,187.5L418.3,185.8L419.4,185.6L420.7,182.5L422.0,182.8L421.7,184.2L422.9,184.1L425.0,187.3L424.2,188.1L426.3,189.2L425.4,190.1L427.1,190.0L427.0,191.1L428.5,192.3L426.3,194.1L429.6,193.5L427.2,196.1L430.1,195.3L432.4,197.0L432.0,198.7L433.4,199.6L431.9,198.6L430.6,199.4L432.3,199.6L432.2,200.6L434.7,201.9L434.5,203.8L430.1,203.2L433.0,204.7L432.2,205.3L434.3,205.6L433.2,206.3L434.5,206.4L434.3,207.7L438.6,208.9L439.2,210.6L438.0,212.6L441.0,211.5L442.2,212.3L441.0,213.8L443.7,212.2L444.9,214.2L448.2,213.9L450.4,215.5L446.3,217.5L445.5,218.5L439.9,220.1L438.9,221.0L439.6,221.6L438.9,222.7L447.7,218.2L446.8,217.7L450.3,217.9L451.5,219.9L450.0,220.8L450.7,221.6L453.9,220.1L456.1,221.7L456.8,226.2L455.3,226.6L457.0,227.1L456.4,227.8L457.3,228.6L452.1,232.5L445.9,233.4L439.7,238.8L433.1,239.6L429.1,238.6L414.0,239.1L412.2,240.2L410.5,243.6L407.0,244.3L401.8,249.2L398.2,248.6L400.9,250.0L396.0,255.6L396.7,255.6L396.4,256.1L398.4,255.0L402.1,250.7L407.0,247.3L415.3,244.3L420.7,244.4L423.1,246.0L422.3,246.3L423.0,247.0L422.6,248.3L419.0,250.4L416.3,249.5L413.5,250.3L416.6,251.0L417.6,252.2L420.0,251.3L421.2,251.9L418.8,255.1L420.7,255.3L420.5,256.5L421.9,259.4L424.3,259.8L424.0,260.6L426.8,261.8L430.1,262.5L432.2,261.3L432.9,262.3L434.2,262.3L434.4,259.8L438.0,255.5L438.7,256.8L438.0,259.5L440.6,259.9L440.6,260.9L438.5,262.3L436.0,263.2L434.6,262.6L434.4,263.7L435.6,264.4L425.8,267.3L425.6,268.1L422.9,267.8L422.9,268.9L420.6,271.1L417.4,272.8L415.5,271.4L415.2,269.8L416.4,267.8L415.4,268.4L415.9,267.7L422.2,263.9L423.3,265.2L426.3,263.8L420.5,263.8L422.7,261.6L421.5,261.0L420.9,262.4L416.5,264.5L415.6,263.8L414.2,265.1L410.5,264.7L410.3,262.6L408.8,262.0L408.8,255.2L407.1,253.8L404.5,254.6L403.0,253.3L400.0,257.0L398.8,261.1L397.2,263.4L394.7,264.2L393.9,265.5L381.2,265.6L385.4,263.4L383.3,263.0L384.1,263.9L382.6,264.5L382.4,262.8L381.1,262.3L376.4,263.5L374.3,263.1L372.7,261.2L371.9,261.6L369.3,259.7L364.4,258.2L361.8,253.1L361.9,232.0L362.8,231.6L364.4,233.6L365.1,232.2L364.1,230.8L366.1,228.2L363.6,220.7L364.0,218.7ZM436.1,260.5L435.7,262.0L438.0,261.0L437.1,260.8L437.7,259.8ZM397.7,248.5L396.7,248.6L397.8,248.6ZM395.0,256.7L395.5,256.0L394.9,256.7ZM392.5,257.2L392.4,257.4L392.8,257.3ZM391.2,257.8L390.8,258.4L391.5,257.8ZM388.0,260.1L389.7,259.0L387.9,260.0ZM387.5,260.2L387.5,260.5L387.6,260.3ZM387.0,260.9L386.1,262.4L387.1,260.9ZM386.0,262.7L385.5,263.5L385.9,263.3ZM385.7,262.0L385.8,262.2L385.9,262.0Z M442.6,251.2L446.4,248.0L443.3,247.6L444.6,246.7L445.2,247.4L446.4,244.9L447.7,245.2L447.9,244.2L447.1,243.4L448.8,242.8L448.3,241.7L451.9,234.9L453.5,233.0L457.9,231.8L457.7,232.9L455.9,233.2L456.8,234.8L453.1,240.1L452.8,242.0L455.3,239.6L455.5,240.4L458.0,240.3L455.8,241.9L456.5,241.9L456.0,242.9L458.7,243.4L459.1,242.7L458.7,244.9L462.0,242.6L462.2,243.6L465.5,243.7L465.7,244.6L463.8,246.3L465.2,247.0L464.4,248.2L467.9,247.6L465.7,249.4L464.6,251.9L465.6,252.2L468.3,249.8L467.3,253.0L468.9,251.7L469.4,252.8L467.7,257.2L465.7,257.5L465.6,254.9L463.3,256.2L464.6,253.4L463.3,251.2L462.0,253.5L460.6,253.6L458.7,256.1L456.8,256.2L456.3,255.5L460.7,252.4L455.5,253.0L456.5,252.6L456.5,251.5L452.9,252.7L446.6,251.9L443.5,252.7Z M422.1,240.8L427.6,241.4L433.2,244.7L427.8,244.2Z M422.4,257.1L424.0,255.3L423.7,256.7L424.8,258.3L431.9,258.5L429.9,259.6L430.1,260.6L428.8,260.7L427.7,259.2L425.4,259.4Z","label":[395.6,220.1]},"scandinavia":{"d":"M699.6,173.5L706.3,173.3L700.4,172.1L702.9,170.0L706.5,169.6L704.5,169.8L705.4,168.5L710.5,168.8L710.1,168.3L710.7,167.7L706.9,167.8L710.3,165.4L714.2,166.8L712.6,165.1L714.5,163.9L713.4,162.9L714.3,162.3L716.5,162.3L716.6,163.2L718.8,162.1L720.1,163.6L722.9,163.2L722.9,162.1L725.4,161.0L724.7,160.4L725.7,159.7L725.2,159.4L720.2,162.8L718.3,161.6L722.3,157.1L726.3,154.7L725.2,154.9L726.0,153.5L728.6,152.1L730.0,152.7L731.3,151.5L729.7,152.1L728.5,150.9L730.8,147.4L732.1,147.0L731.1,146.1L736.0,144.8L732.5,145.2L732.8,142.5L735.5,141.5L734.6,140.7L736.4,139.3L741.7,138.7L737.8,138.2L739.8,136.2L742.2,137.7L742.7,136.6L741.0,136.0L741.2,134.9L739.1,135.5L739.2,134.6L742.4,133.6L741.2,133.0L744.0,131.7L745.1,134.1L745.5,132.7L744.8,131.1L750.0,130.3L746.1,129.6L752.4,125.2L753.0,123.0L755.4,124.1L754.5,122.9L758.7,120.6L758.7,123.2L759.8,120.5L761.3,119.6L761.4,122.0L760.4,123.9L762.8,122.6L762.1,121.4L762.5,119.9L765.7,119.2L767.9,120.4L765.4,117.6L770.7,116.6L773.4,119.4L773.5,117.5L779.1,112.0L783.1,113.3L781.1,115.3L780.2,118.5L786.0,112.7L786.9,113.1L786.4,116.2L787.9,115.6L790.1,113.4L788.9,112.4L790.4,111.3L793.6,112.2L791.7,114.2L793.1,114.4L792.7,117.3L795.3,113.0L800.3,114.2L803.8,116.8L801.1,118.4L795.2,118.2L798.4,119.4L798.8,121.1L801.0,120.2L803.5,120.8L803.7,122.1L800.8,121.8L800.0,123.6L793.8,127.1L795.1,127.4L793.9,129.8L794.7,131.9L799.9,135.4L796.3,140.8L800.4,148.2L799.0,149.2L798.4,151.7L799.3,152.4L798.4,153.5L800.4,155.0L800.0,156.4L802.1,159.2L800.0,161.5L806.0,166.4L805.1,168.8L791.2,181.2L786.1,182.0L786.3,180.7L783.0,182.8L771.9,185.5L772.8,184.4L771.6,183.2L769.9,184.3L770.3,182.2L765.7,180.9L765.4,178.6L766.4,174.8L765.0,172.4L765.4,170.6L764.5,168.0L766.5,165.8L766.2,164.8L769.2,164.1L769.0,163.3L770.1,162.0L774.4,159.5L778.2,154.6L781.1,154.2L781.4,150.2L778.7,148.9L778.4,147.7L772.4,148.5L769.6,147.7L769.0,149.4L766.1,150.8L766.3,152.5L764.6,154.5L766.1,156.8L763.1,160.6L754.4,165.0L753.4,165.9L753.9,166.7L751.6,166.9L752.1,168.6L749.6,169.2L750.5,170.9L749.5,173.1L749.9,174.2L748.5,174.9L749.0,180.2L751.8,180.9L755.4,184.3L755.9,185.9L754.3,187.8L751.9,188.2L754.5,188.4L753.1,189.7L747.9,192.4L745.3,192.4L747.7,193.4L746.7,193.7L746.6,199.1L745.4,203.6L743.3,206.9L739.1,206.7L737.6,207.8L736.7,211.0L731.5,211.0L731.9,209.1L729.9,206.0L731.1,206.2L730.6,205.2L731.5,204.2L729.7,202.5L725.7,194.2L725.0,194.1L725.1,189.7L723.3,189.3L722.3,186.0L720.7,190.3L718.4,189.7L718.6,190.5L712.7,195.4L708.0,196.1L702.8,193.2L702.2,190.5L704.6,191.1L704.9,190.5L703.6,189.8L705.2,187.3L700.7,189.4L701.0,187.0L704.4,185.7L702.9,185.3L705.4,182.3L706.1,182.0L706.2,183.2L707.5,181.5L704.6,182.4L700.6,186.6L700.8,183.9L702.7,183.6L700.7,182.6L701.5,181.0L700.2,180.2L700.0,178.2L707.1,177.6L708.2,178.7L708.2,177.9L710.2,177.2L709.6,176.0L708.7,177.4L706.4,176.7L701.3,177.8L700.1,176.9L700.0,175.8L701.2,175.4ZM725.3,189.9L725.5,189.8L725.3,189.7ZM712.2,167.6L712.0,167.7L712.2,167.7ZM702.2,180.4L702.1,180.7L702.3,180.6Z M737.0,131.5L741.6,129.0L741.9,126.0L743.9,124.2L744.5,124.4L743.3,126.2L743.7,128.2L743.2,129.2L745.1,127.2L746.2,128.3L743.7,130.6Z M753.1,201.5L752.5,198.8L753.6,197.6L757.3,196.4L755.3,197.9L755.6,199.7L753.4,202.1L752.6,202.4Z M747.2,125.8L750.0,122.1L752.3,123.5L751.8,124.9Z M752.2,122.1L754.7,120.7L756.5,117.5L758.4,119.1L755.1,122.2Z","label":[733.1,174.7]},"siam":{"d":"M1048.7,372.3L1049.3,371.7L1050.4,372.4L1050.3,369.4L1050.8,368.8L1051.8,369.3L1052.6,368.3L1052.3,366.3L1053.6,363.7L1053.3,360.6L1056.5,361.4L1058.8,356.4L1058.3,355.3L1060.5,352.2L1060.5,349.8L1064.8,347.0L1066.9,346.7L1068.4,347.6L1067.5,345.6L1069.3,344.1L1069.3,342.8L1070.4,341.5L1072.4,343.2L1073.2,345.7L1074.6,345.6L1075.0,349.4L1074.6,353.0L1071.3,355.6L1070.3,357.6L1070.8,359.9L1070.3,361.2L1075.3,360.4L1074.7,361.3L1075.5,364.3L1078.0,365.3L1076.7,368.7L1079.7,369.2L1080.6,371.5L1082.4,371.5L1084.3,370.3L1085.0,372.7L1086.9,372.9L1086.9,370.1L1086.1,368.3L1086.8,367.3L1088.5,367.7L1089.9,366.2L1092.0,367.4L1093.2,366.1L1094.0,366.8L1094.5,366.0L1095.8,367.0L1096.6,366.0L1098.8,365.9L1101.1,363.7L1103.4,365.4L1107.1,366.0L1106.2,367.2L1106.7,369.4L1111.9,371.4L1109.7,372.3L1108.7,373.7L1106.7,373.5L1107.0,374.6L1106.1,376.5L1103.9,377.9L1102.5,382.0L1106.0,386.2L1105.9,387.2L1106.9,388.6L1111.3,392.9L1112.1,392.8L1115.3,396.7L1117.2,403.0L1117.8,408.1L1116.9,407.9L1117.0,410.7L1115.9,413.2L1109.0,417.0L1108.3,417.2L1108.7,416.7L1108.0,416.0L1107.8,417.0L1106.6,416.6L1107.0,417.4L1106.1,417.5L1107.1,418.1L1106.4,419.2L1105.5,418.7L1106.2,420.2L1105.5,420.4L1103.6,418.7L1104.7,421.0L1102.0,422.3L1100.5,424.1L1099.1,424.3L1100.1,418.3L1097.0,416.4L1094.3,416.4L1094.9,415.0L1094.2,414.0L1092.6,414.9L1092.5,412.7L1090.4,409.8L1086.9,407.7L1083.6,407.9L1083.8,404.7L1082.4,404.2L1080.1,405.0L1080.0,409.8L1076.7,417.3L1077.0,421.6L1079.3,421.5L1080.0,424.3L1081.1,425.6L1082.2,429.8L1081.1,427.6L1080.6,428.3L1081.7,429.9L1084.1,431.2L1086.0,431.2L1092.8,437.7L1093.8,440.0L1093.8,447.0L1095.2,448.4L1097.0,453.2L1096.0,452.4L1095.9,453.4L1093.9,453.4L1085.2,447.2L1085.2,445.7L1082.9,442.9L1080.5,432.9L1078.8,431.2L1078.3,429.3L1077.4,429.2L1074.8,425.6L1073.2,425.8L1073.0,422.3L1074.8,418.0L1074.3,418.5L1073.9,415.9L1075.4,411.6L1074.5,411.6L1074.3,405.8L1072.9,402.6L1072.4,403.5L1071.2,398.9L1070.5,392.5L1070.1,393.2L1068.8,389.7L1067.6,388.6L1067.1,391.3L1066.0,392.1L1065.0,391.6L1065.2,392.5L1063.1,393.5L1061.6,395.3L1061.4,394.1L1060.7,394.9L1059.8,394.9L1059.5,393.9L1058.6,394.6L1058.7,392.8L1056.9,394.1L1058.3,388.8L1057.7,385.1L1056.1,381.1L1055.7,382.2L1054.0,380.3L1055.3,380.8L1055.9,380.4L1054.6,379.8L1055.5,379.8L1054.7,378.1L1053.0,377.4L1052.5,378.3L1052.2,376.3L1051.2,376.8L1051.4,376.0L1050.9,375.5L1050.9,376.4L1049.5,374.7ZM1070.2,392.0L1070.5,392.4L1070.6,392.0ZM1065.0,391.4L1064.8,391.1L1065.0,391.4ZM1058.7,392.4L1058.8,392.7L1058.8,392.4ZM1055.6,379.9L1056.0,380.0L1055.6,379.8Z M1118.2,451.2L1118.5,450.6L1119.5,451.7L1124.7,453.2L1124.1,452.5L1125.2,447.8L1125.6,449.3L1126.5,447.5L1132.0,446.1L1136.2,440.4L1138.6,442.6L1139.4,441.2L1139.0,439.9L1140.1,439.1L1140.4,441.1L1141.3,441.3L1140.6,439.1L1142.2,438.3L1141.7,437.1L1143.5,436.3L1147.1,430.7L1147.2,432.2L1148.5,430.8L1150.6,432.8L1150.5,435.1L1152.0,434.5L1152.5,435.3L1151.9,435.9L1153.4,435.5L1157.0,437.3L1156.5,438.3L1153.0,438.8L1152.8,439.4L1154.4,440.9L1154.0,441.3L1150.8,441.4L1150.3,442.0L1148.4,441.4L1143.4,441.4L1142.3,443.0L1141.8,446.6L1140.5,447.2L1140.7,448.6L1139.1,449.8L1139.3,450.8L1138.0,452.9L1135.6,453.0L1134.5,453.8L1131.8,452.5L1129.9,452.5L1127.2,454.7L1122.0,455.3Z","label":[1087.8,389.0]},"siberia":{"d":"M980.3,194.2L981.2,193.5L980.6,192.4L982.7,190.2L982.5,189.0L986.6,186.9L987.1,181.7L988.1,181.3L988.5,179.3L996.7,179.4L997.5,180.4L1002.9,179.7L1008.6,181.2L1012.6,178.2L1017.0,179.3L1023.8,175.5L1018.7,173.4L1017.7,171.2L1022.1,166.5L1022.5,163.8L1020.4,162.7L1023.8,159.4L1023.4,154.9L1017.1,153.7L1018.2,150.5L1016.1,148.1L1014.1,147.9L1013.2,146.4L1014.0,145.5L1012.4,143.7L1013.1,142.3L1012.7,141.3L1008.5,138.4L1009.5,136.5L1006.9,133.9L1009.4,133.6L1009.6,132.0L1010.7,131.3L1010.1,129.0L1011.9,128.5L1010.2,127.4L1009.7,125.4L1007.1,124.7L1007.6,123.9L1007.1,123.3L1003.9,124.9L999.4,123.8L995.7,120.2L1003.0,114.7L1002.2,111.7L996.6,108.9L997.5,107.5L1000.4,107.1L1000.1,105.9L1001.1,105.3L994.8,103.3L993.9,101.7L997.7,101.8L1006.6,106.7L1012.4,106.7L1012.9,107.1L1011.9,108.7L1009.3,110.1L1008.8,115.8L1011.5,112.3L1012.2,114.2L1011.1,118.1L1012.3,118.6L1012.3,117.3L1014.8,115.9L1012.6,111.3L1014.1,107.0L1008.4,102.6L1003.3,101.0L1002.6,99.3L1003.3,97.4L1001.7,95.3L1002.3,92.7L1028.1,90.7L1023.2,93.6L1023.3,94.6L1026.3,96.1L1023.8,93.6L1030.1,90.8L1024.0,86.9L1028.6,86.3L1023.2,84.3L1024.8,82.9L1027.4,83.7L1029.8,81.5L1027.8,80.9L1028.0,80.1L1056.3,73.9L1051.4,73.5L1053.0,72.5L1064.3,72.7L1063.0,74.4L1074.6,71.4L1079.1,73.1L1078.4,74.9L1079.4,73.9L1079.3,72.2L1075.2,69.4L1086.4,69.7L1083.7,68.8L1084.4,67.5L1083.6,65.9L1090.4,60.7L1096.1,58.9L1104.2,61.8L1097.7,64.2L1109.7,65.7L1105.7,69.1L1110.9,69.0L1112.7,67.3L1124.5,67.4L1130.5,70.2L1131.0,72.9L1133.1,71.3L1134.3,74.2L1135.4,74.0L1134.3,76.9L1130.1,74.7L1134.9,77.8L1131.7,81.3L1119.5,86.8L1119.2,88.0L1112.8,91.7L1109.1,92.3L1104.7,94.7L1101.3,98.5L1105.9,96.0L1111.0,95.8L1123.3,91.6L1124.4,92.2L1122.1,94.0L1123.5,96.9L1122.5,98.8L1124.8,100.5L1124.5,101.9L1128.0,103.6L1128.0,108.9L1130.3,111.2L1120.5,114.7L1120.5,115.8L1117.2,117.8L1118.0,119.0L1116.9,120.8L1113.2,120.3L1104.7,123.5L1107.5,127.3L1107.2,137.7L1102.4,140.0L1105.0,142.1L1105.3,143.6L1104.4,144.3L1105.9,146.7L1105.8,148.9L1107.8,150.0L1104.0,153.2L1104.5,153.9L1102.9,155.7L1104.8,157.2L1112.9,158.4L1113.3,159.5L1110.2,160.7L1106.8,160.1L1107.2,160.7L1106.1,163.0L1106.9,163.7L1104.6,165.8L1106.9,168.7L1105.6,170.4L1105.6,172.1L1103.7,173.3L1103.3,174.6L1099.7,176.1L1100.0,177.2L1097.9,179.4L1098.7,182.1L1101.9,182.9L1101.5,185.5L1099.2,186.3L1100.9,187.8L1101.4,189.7L1097.8,192.7L1092.0,188.5L1089.8,189.5L1090.1,192.0L1085.2,195.0L1083.5,197.1L1083.6,199.4L1081.7,198.9L1080.3,195.9L1078.8,195.7L1077.6,197.4L1075.8,197.8L1071.7,197.3L1069.5,201.5L1069.9,203.0L1071.1,203.2L1071.5,205.2L1067.1,209.1L1067.7,211.4L1066.9,211.8L1066.0,216.0L1064.2,215.7L1062.6,217.2L1066.0,220.5L1067.5,220.3L1073.3,223.7L1077.0,224.5L1075.7,224.7L1075.7,226.0L1074.5,227.4L1075.8,229.0L1071.8,233.1L1071.3,235.0L1073.2,237.6L1072.0,240.1L1068.8,241.6L1064.3,240.2L1060.4,240.5L1058.5,240.1L1057.0,237.3L1052.4,237.0L1049.4,235.7L1040.2,239.7L1036.4,242.8L1032.8,243.0L1031.7,244.5L1029.3,244.9L1026.5,242.5L1026.7,241.3L1024.7,242.8L1020.9,242.2L1019.9,239.8L1017.0,238.7L1016.3,236.9L1018.1,236.6L1018.4,235.7L1016.1,234.2L1021.7,231.9L1023.8,229.2L1027.0,229.0L1027.2,228.1L1026.5,227.6L1027.4,226.1L1028.7,225.9L1027.8,225.0L1028.3,223.8L1026.8,223.2L1027.8,221.8L1019.8,215.4L1020.3,214.0L1018.1,207.4L1015.8,207.6L1013.3,209.4L1013.6,208.2L1012.5,206.9L1012.9,205.1L1012.4,204.6L1006.2,206.2L1004.5,204.6L1001.0,205.1L998.4,202.4L993.7,201.1L984.4,200.5L982.3,198.1L982.2,196.6L980.4,195.6L980.8,194.9Z M1052.3,43.4L1059.9,37.9L1070.4,37.2L1072.1,38.6L1070.6,41.0L1074.4,38.3L1080.2,40.8L1078.7,44.9L1076.5,45.2L1079.7,48.1L1073.6,49.7L1060.1,47.3L1056.9,44.2Z M1046.1,35.5L1051.3,33.1L1052.5,31.6L1050.4,31.3L1052.3,29.6L1060.6,27.0L1063.2,26.9L1071.5,31.7L1068.4,33.1L1069.7,35.8L1068.7,36.6L1058.3,37.6L1055.5,38.7Z M1077.1,56.2L1080.1,53.6L1081.7,50.0L1083.6,49.7L1084.8,46.0L1087.0,44.5L1088.8,45.5L1089.1,43.9L1092.4,45.1L1089.9,49.1L1095.2,46.5L1100.6,49.4L1101.4,51.4L1099.0,53.6Z M1044.3,39.0L1048.7,38.4L1055.2,39.7L1049.8,41.7Z M1118.7,90.9L1121.0,89.2L1126.2,89.1L1123.2,91.2Z","label":[1059.3,127.2]},"south-africa":{"d":"M727.0,528.4L730.2,528.3L732.4,527.3L736.1,529.1L753.6,529.0L755.8,530.7L763.0,531.6L773.4,530.0L768.6,525.7L768.2,524.4L767.9,511.0L775.8,511.0L775.9,502.4L777.5,503.4L777.5,504.6L781.2,503.8L781.4,505.4L784.1,506.5L787.3,506.8L788.6,505.3L790.3,507.9L793.7,509.1L796.0,512.5L798.2,512.0L799.1,512.8L799.2,507.6L798.0,507.9L797.9,508.7L796.3,508.4L793.4,504.9L794.6,501.1L793.6,495.7L795.9,492.7L803.0,491.6L803.9,493.0L805.8,493.4L807.8,495.1L813.9,497.3L816.4,497.0L818.1,499.0L818.4,503.2L819.9,505.3L823.6,504.8L825.2,505.8L827.9,505.2L829.5,505.8L831.7,504.1L834.0,504.6L837.3,503.4L841.9,500.7L842.4,501.5L841.6,504.2L842.3,509.5L841.7,510.9L842.3,511.0L842.6,517.1L843.3,517.0L843.4,518.0L842.2,521.1L839.2,524.5L839.4,525.1L836.3,527.3L829.0,530.4L825.6,534.7L823.4,535.6L819.8,539.0L818.7,538.6L818.8,541.8L821.1,546.7L821.3,549.8L821.8,548.6L822.2,549.4L821.8,557.3L820.0,559.3L813.4,561.9L811.2,563.6L810.4,565.1L811.3,566.2L811.8,565.5L811.6,568.7L810.1,574.6L809.1,576.4L805.3,579.7L799.9,588.2L789.5,597.3L785.7,599.1L783.2,599.0L782.3,600.3L780.0,600.0L778.4,600.9L770.2,600.2L767.2,601.8L762.1,602.2L760.1,603.7L755.3,601.8L755.0,600.5L753.6,601.5L753.7,598.9L751.4,594.9L753.3,593.5L752.8,590.1L749.4,584.4L747.0,578.1L741.2,570.6L738.0,557.4L737.9,550.0L727.1,531.5Z","label":[776.5,563.6]},"southern-europe":{"d":"M703.9,259.5L708.2,253.1L713.8,252.6L714.3,251.6L719.4,252.8L720.8,254.2L721.8,252.8L724.2,253.6L728.8,251.9L732.0,253.2L731.3,249.7L733.5,248.7L733.9,247.5L734.9,247.7L735.3,246.6L738.7,247.4L740.0,245.4L747.5,246.9L748.4,250.3L751.1,251.7L754.9,251.6L755.7,250.2L759.6,249.8L762.6,247.7L768.9,248.4L771.5,250.8L772.6,250.1L777.9,250.8L779.6,251.9L787.1,249.2L792.3,255.7L793.0,257.4L792.4,262.6L792.9,263.3L795.0,264.4L797.6,263.5L798.8,264.3L798.2,266.3L796.2,266.8L796.3,265.7L795.6,265.9L794.2,272.9L792.5,273.5L791.6,276.6L790.0,277.9L792.1,280.3L790.1,280.6L789.0,279.7L786.3,280.4L785.3,281.4L786.5,283.1L784.2,286.3L780.4,285.0L775.1,286.2L775.5,287.7L776.9,288.2L777.4,289.0L774.9,288.1L775.8,289.9L773.7,288.5L774.7,289.9L773.6,289.8L771.6,286.9L770.5,287.4L770.4,289.5L773.3,293.6L772.6,293.9L771.7,293.0L772.3,294.2L770.4,295.1L774.5,297.0L773.0,295.3L771.5,295.0L773.3,294.2L776.4,295.9L777.1,298.0L778.4,298.5L778.0,299.2L776.1,297.3L774.6,297.0L776.0,298.0L776.2,300.4L774.1,298.9L774.0,299.5L772.2,299.5L774.0,301.5L772.6,302.1L770.9,301.2L772.6,306.2L770.9,304.6L770.0,306.2L768.3,303.6L767.6,304.9L767.0,304.3L766.3,303.3L766.7,301.9L764.5,299.6L767.3,297.5L771.6,299.2L772.6,298.3L769.7,297.1L764.5,297.3L763.1,295.0L764.5,294.3L763.1,294.4L761.2,292.9L759.4,289.5L757.3,287.8L757.8,287.8L757.4,286.6L758.3,281.2L754.5,278.1L748.2,275.3L750.3,275.7L747.6,273.5L743.9,272.8L741.5,270.9L740.5,269.2L741.6,268.9L739.6,267.0L739.4,265.2L738.6,264.5L739.2,265.7L737.8,265.2L738.2,264.1L737.2,263.9L735.6,266.4L734.1,263.2L735.1,262.5L734.5,261.8L729.8,262.9L728.9,264.3L730.1,265.7L729.1,266.5L729.2,268.4L730.8,270.5L734.3,272.6L736.0,276.9L738.2,279.0L740.7,280.5L744.7,280.7L743.6,282.6L751.8,286.6L753.9,289.2L753.4,290.5L751.4,288.4L747.7,287.5L746.1,290.9L748.5,292.6L748.7,294.4L746.5,295.3L746.2,297.2L744.2,299.3L742.6,298.9L744.8,294.7L742.8,289.8L739.8,288.6L739.1,286.6L737.4,286.9L737.8,286.2L736.2,285.9L734.9,283.8L732.4,283.8L726.5,278.8L724.4,278.2L724.7,277.6L722.1,275.5L720.2,270.4L715.1,268.4L712.0,271.1L710.0,271.6L710.6,269.7L707.6,268.8L708.0,266.4L706.5,265.2L708.6,263.6L707.2,261.9L708.1,260.9L707.1,258.6Z M729.7,299.9L730.9,298.2L735.2,299.1L742.5,297.8L740.4,301.6L741.2,303.4L740.5,305.1L738.0,304.6Z M712.7,286.1L716.9,283.7L718.5,284.9L719.2,287.3L718.2,293.6L716.3,293.3L715.5,294.8L714.0,294.0L713.9,288.3Z M774.2,310.9L774.9,309.8L776.7,310.1L777.3,311.2L782.9,311.2L783.2,312.2L785.3,311.4L784.7,312.7L779.2,313.1L777.9,312.1L774.6,311.7Z","label":[771.2,268.6]},"ukraine":{"d":"M764.9,211.8L764.3,203.0L766.9,198.7L770.2,197.8L774.6,202.1L777.5,200.5L777.2,197.5L778.1,194.2L775.1,194.1L774.0,192.4L774.5,191.8L773.7,190.8L774.0,189.2L782.0,186.6L791.6,187.9L792.2,185.7L793.3,186.3L796.6,184.4L800.6,185.0L798.9,183.3L794.6,182.2L794.1,180.4L791.2,181.2L804.7,169.3L806.1,166.7L800.0,161.4L802.1,159.2L799.9,156.4L800.4,155.0L798.4,153.5L799.3,152.4L798.4,151.7L798.9,149.2L800.4,148.2L796.3,140.8L799.9,135.5L797.4,132.8L794.7,132.0L793.9,129.9L795.1,127.5L793.7,127.0L800.0,123.6L800.6,121.9L803.4,122.5L803.5,120.8L806.2,121.4L808.0,120.6L807.9,119.6L812.0,121.2L808.5,121.8L809.5,122.9L813.8,123.3L812.8,125.8L814.7,124.1L823.4,125.0L833.7,130.8L839.2,132.2L843.9,135.2L844.6,138.3L845.4,138.7L844.8,141.2L842.1,143.8L833.6,146.3L817.9,143.1L812.6,141.1L811.5,139.4L808.5,139.1L814.6,143.8L814.0,144.6L818.8,147.1L819.1,148.3L817.7,150.8L819.8,154.9L819.5,156.2L825.5,159.7L829.8,160.9L832.3,159.6L831.8,157.7L828.7,157.1L826.3,154.7L826.1,153.7L827.5,152.2L838.3,156.1L841.6,154.9L839.2,151.0L839.3,149.4L848.8,143.3L852.9,144.0L856.4,146.6L856.9,144.0L868.4,147.9L870.7,148.0L872.6,145.9L876.0,146.0L876.0,151.6L878.7,151.6L881.9,154.7L881.4,156.3L875.8,156.6L873.6,158.3L866.5,157.7L861.9,158.7L867.3,161.4L868.1,162.7L867.8,163.9L870.2,165.1L868.4,167.1L869.7,171.1L873.1,170.3L873.5,167.8L878.4,167.5L876.2,174.1L877.8,174.8L877.2,177.3L873.8,178.5L874.1,181.6L875.0,182.3L873.8,183.7L874.1,186.2L876.3,186.5L876.2,187.4L878.7,189.1L880.0,185.8L883.3,185.2L884.3,184.1L886.1,184.8L888.2,182.5L894.4,183.6L894.6,185.5L892.9,188.3L895.5,189.8L894.9,190.8L895.7,191.7L894.8,192.7L895.1,193.7L891.7,193.1L887.2,194.1L886.8,195.2L887.7,197.3L886.4,199.4L884.7,199.5L884.9,201.6L886.2,202.6L884.6,204.7L885.7,206.1L885.8,208.0L888.3,208.2L888.8,207.2L891.2,207.1L890.2,206.2L892.3,204.7L891.9,206.3L893.1,207.1L893.0,206.2L894.2,206.3L893.2,208.4L896.7,209.3L894.4,212.1L892.6,212.5L894.4,214.8L893.4,216.7L893.5,218.9L891.1,216.9L889.1,217.0L889.9,218.4L888.4,221.5L888.2,224.2L886.2,226.0L885.7,229.1L881.2,233.2L874.6,237.0L875.0,240.6L873.3,240.9L870.0,238.1L867.6,241.8L867.2,243.5L868.1,244.6L866.8,246.4L866.6,248.4L868.3,249.3L869.1,251.8L872.7,252.0L875.8,256.7L874.2,256.8L874.2,257.6L877.0,259.1L874.8,260.2L874.9,261.1L870.0,262.3L868.3,266.5L866.9,267.7L869.2,270.0L869.8,272.3L870.6,271.1L869.9,275.2L874.3,280.9L871.2,284.0L869.3,283.6L867.0,281.1L862.6,279.2L862.6,277.7L859.5,276.5L855.3,277.4L855.1,276.6L851.0,274.5L846.3,274.3L842.6,272.8L839.9,273.3L834.9,269.1L826.5,264.8L827.6,264.2L826.9,263.7L827.5,263.5L828.8,264.2L830.6,263.7L831.7,260.6L833.9,260.1L831.1,257.4L833.9,257.3L833.8,256.5L837.1,255.3L836.8,254.2L823.3,257.5L820.8,259.8L820.1,260.1L821.1,259.2L820.9,258.5L819.4,259.7L820.1,262.1L821.8,264.0L826.3,263.6L825.6,265.2L821.9,265.1L815.6,268.6L813.8,267.8L814.2,265.1L810.0,263.6L814.6,260.9L814.4,260.2L809.9,260.2L807.3,259.2L808.0,258.5L806.2,257.8L809.4,258.2L810.0,257.7L808.2,257.4L807.5,255.2L807.5,257.3L806.1,256.8L803.2,257.8L800.9,261.3L798.5,262.0L798.8,264.3L797.6,263.5L795.0,264.4L792.3,262.6L792.9,257.4L787.6,249.4L785.3,249.5L784.7,250.5L779.6,251.9L777.9,250.8L772.6,250.1L771.5,250.8L768.5,248.4L768.6,247.6L770.2,245.0L771.4,245.1L770.8,242.2L776.4,237.4L776.4,235.6L774.2,231.6L774.6,229.4L772.7,228.1L775.6,225.8L775.5,224.1L773.8,218.0L770.9,216.5L771.3,214.0Z M895.1,91.2L898.6,89.7L901.4,86.1L904.2,85.6L902.3,84.4L905.7,81.7L903.2,80.5L910.4,78.7L912.2,76.1L915.5,74.5L923.8,72.8L924.8,71.0L937.8,70.2L950.1,65.0L953.9,65.6L955.8,67.5L952.7,71.0L925.4,78.9L921.1,81.4L922.0,82.2L918.7,84.6L917.0,83.9L916.2,85.5L914.1,85.5L914.5,87.6L911.1,89.2L911.0,91.2L909.3,90.7L910.2,92.0L907.9,94.3L900.0,93.6L897.2,94.4Z M885.7,105.9L886.6,103.9L890.5,102.4L891.5,99.7L890.2,98.8L893.2,97.8L892.8,96.3L893.4,95.4L899.2,94.1L905.4,95.4L904.5,98.6L901.6,100.5L902.1,103.0L901.3,105.1L905.8,111.2L910.5,114.0L908.6,115.0L905.2,114.1L906.2,115.0L905.7,115.2L893.5,112.9L896.2,111.0L893.6,109.5L893.5,108.0L887.2,108.4Z M866.6,36.0L876.4,34.0L877.0,31.2L880.5,30.2L886.8,32.4L875.6,35.4L874.9,36.0L875.9,37.3L873.5,37.9Z M896.2,30.7L898.9,28.4L910.8,31.5L903.5,33.0Z M917.2,33.5L918.9,31.1L928.9,31.5L928.3,33.1L924.2,34.9Z M930.1,31.2L938.3,27.7L940.7,28.2L941.7,30.2L933.5,32.3Z M901.9,26.6L910.9,24.2L914.3,25.6L909.8,28.3Z M859.6,33.2L869.4,30.9L874.7,32.2L870.8,31.7L864.6,34.7Z M767.4,194.5L767.7,193.2L772.7,192.3L773.4,192.9L772.3,192.7L773.3,193.6L769.5,195.0L768.3,196.6L768.7,195.3Z M908.0,34.5L913.9,34.5L917.0,35.6L911.2,37.8L909.3,37.3Z M908.8,29.4L912.4,28.5L915.7,31.1L911.8,31.5Z","label":[836.6,209.8]},"ural":{"d":"M853.3,128.6L863.6,130.0L866.8,134.3L862.1,134.9L859.6,137.3L863.6,140.8L866.0,141.4L870.6,140.2L871.5,136.1L875.3,135.4L875.0,134.0L888.0,129.5L889.6,130.8L890.8,130.0L889.4,129.1L895.2,126.4L898.0,126.4L895.3,126.9L895.7,130.3L893.1,131.4L899.4,131.9L901.7,129.3L908.5,129.4L916.2,126.3L917.5,128.2L916.4,130.2L918.9,130.8L919.7,130.0L919.6,128.4L922.0,128.2L923.7,126.5L920.7,122.2L923.6,120.3L936.8,122.6L948.0,127.2L953.5,131.1L956.5,126.7L954.2,126.6L952.0,122.9L948.3,121.4L947.6,122.4L947.3,120.4L948.9,118.5L949.1,114.0L946.8,113.3L947.3,111.7L946.6,111.4L953.1,107.0L957.6,97.4L966.0,97.7L971.2,99.4L970.3,104.5L967.5,108.6L970.8,112.3L970.8,116.0L969.9,117.3L970.3,126.5L974.4,129.9L972.6,132.0L972.3,134.9L967.4,140.0L965.6,140.4L966.2,142.2L962.9,143.3L959.5,141.1L956.2,141.5L956.8,142.9L959.9,144.1L968.3,145.1L969.7,143.0L975.2,140.1L976.3,137.3L979.1,134.9L979.0,132.8L977.6,130.4L978.3,128.1L980.5,127.3L985.8,126.5L989.0,130.0L988.8,134.8L991.1,136.2L995.5,136.0L990.4,135.0L990.7,132.0L992.0,131.4L990.6,127.0L984.0,124.7L979.3,125.7L975.1,124.9L975.5,123.4L974.3,120.7L977.4,115.0L972.3,108.8L974.7,105.8L980.0,103.5L980.2,100.5L979.1,98.5L981.5,98.6L982.4,100.3L983.0,102.4L981.1,105.0L982.0,107.2L981.1,109.0L982.9,110.1L995.8,112.5L992.8,110.1L985.7,108.0L984.1,105.3L987.5,104.4L990.2,105.8L992.7,105.3L992.1,103.9L989.6,103.8L993.9,101.7L994.8,103.3L1000.9,105.1L1001.4,105.5L1000.2,105.9L1000.5,107.1L997.6,107.5L996.6,109.0L1002.2,111.7L1003.0,114.7L995.7,120.3L999.4,123.8L1003.9,124.9L1007.1,123.3L1007.7,123.9L1007.2,124.8L1010.0,125.6L1009.5,125.9L1010.2,127.4L1012.1,128.5L1010.3,129.1L1010.7,131.3L1009.6,132.1L1009.4,133.7L1007.1,134.0L1009.5,136.5L1008.6,138.5L1012.6,141.3L1013.2,142.3L1012.4,143.6L1014.1,145.6L1013.2,146.3L1014.1,147.9L1017.3,148.8L1018.2,150.5L1017.3,153.8L1023.4,154.9L1023.8,159.4L1020.5,162.6L1022.6,163.7L1022.1,166.5L1017.8,171.2L1018.7,173.4L1023.8,175.6L1017.0,179.3L1012.6,178.3L1008.6,181.2L1002.9,179.7L997.4,180.5L996.7,179.4L988.6,179.3L988.1,181.3L987.1,181.8L986.6,186.9L982.6,189.0L982.6,190.5L980.5,192.7L981.2,193.6L980.4,194.1L980.4,195.6L982.2,196.6L982.3,198.1L984.5,200.5L993.8,201.1L998.4,202.4L1001.0,205.1L1004.5,204.6L1006.2,206.2L1012.4,204.5L1013.0,205.1L1012.6,206.9L1013.6,208.3L1013.3,209.4L1015.8,207.6L1018.1,207.4L1019.5,213.3L1020.3,214.0L1019.9,215.5L1027.7,221.8L1026.9,223.2L1028.3,223.8L1027.8,225.0L1028.7,226.3L1027.3,226.2L1026.5,227.6L1027.2,228.0L1027.0,229.0L1023.8,229.2L1021.7,232.0L1016.1,234.2L1017.9,235.1L1018.2,236.6L1016.3,236.9L1013.4,235.0L1010.0,236.4L1005.9,236.3L1005.5,235.2L1004.3,235.1L1004.5,233.9L1002.9,233.4L1000.3,236.2L991.4,222.8L985.9,218.7L985.7,217.9L986.6,218.0L987.3,216.4L981.7,218.3L977.4,221.6L975.4,220.9L973.6,221.8L973.2,220.4L974.8,218.5L972.9,219.0L970.5,218.0L969.8,219.1L968.7,217.0L964.4,217.6L964.7,215.4L963.0,211.6L960.7,212.3L955.9,211.1L952.9,212.3L952.6,213.4L941.9,215.3L940.4,216.9L937.8,216.7L927.7,219.1L924.9,218.7L923.9,220.8L926.1,221.4L924.8,222.6L928.3,224.2L924.2,224.4L923.1,226.0L924.0,227.8L920.1,230.0L926.2,233.2L925.6,235.7L923.8,236.5L920.2,235.7L918.1,237.6L913.4,234.6L911.4,234.4L909.8,235.5L906.0,234.8L902.7,237.1L898.6,234.9L898.2,237.4L896.6,234.7L893.4,232.4L890.3,232.4L888.9,231.2L885.4,232.4L883.2,231.1L885.6,229.1L886.2,226.0L888.2,224.2L888.3,221.5L889.9,218.4L889.1,217.0L891.1,216.9L893.6,218.9L893.4,216.7L894.4,214.7L892.6,212.4L894.4,212.1L896.7,209.4L893.2,208.5L894.2,206.3L893.2,206.2L893.1,207.2L891.8,206.2L892.2,204.7L890.4,206.1L891.4,206.7L890.6,207.6L885.8,208.1L885.7,206.1L884.6,204.7L886.1,202.7L884.5,200.5L884.7,199.4L887.4,198.3L887.5,196.3L886.7,195.3L887.9,193.5L895.1,193.6L895.3,189.8L892.8,188.2L894.8,184.4L893.8,183.2L888.2,182.6L886.1,184.8L884.3,184.1L880.1,185.8L878.7,189.1L876.2,187.4L876.2,186.5L874.0,186.1L873.8,183.8L874.9,182.3L874.1,181.6L873.8,178.5L877.2,177.3L877.8,174.8L876.2,174.2L878.4,167.6L873.5,167.8L873.2,170.3L869.7,171.1L868.3,167.1L870.2,165.2L867.7,163.9L868.1,162.7L867.3,161.4L861.8,158.5L872.5,157.5L873.6,158.3L875.8,156.6L881.1,156.4L881.8,154.7L878.3,151.4L876.1,151.8L876.0,146.0L872.6,145.9L870.6,148.0L868.4,147.9L856.9,144.0L857.9,142.3L857.6,140.1L855.1,138.4L856.8,131.5ZM962.0,142.0L962.5,141.9L962.5,141.8Z M873.1,126.0L873.7,123.9L875.8,122.7L880.7,124.5L881.1,125.7L874.7,128.2Z M913.9,117.3L916.2,115.9L921.9,120.7L918.5,121.2L915.8,120.0Z M959.7,96.4L960.0,94.3L961.4,93.4L964.1,93.2L966.5,95.4Z","label":[934.5,176.1]},"venezuela":{"d":"M363.9,452.2L364.8,451.3L365.7,451.6L365.6,449.3L368.7,447.8L371.6,443.2L369.9,441.9L370.9,439.8L369.9,436.6L371.0,435.6L370.1,434.0L370.5,432.4L368.4,429.8L369.0,427.9L369.7,428.5L371.2,426.8L370.2,424.4L370.6,424.1L372.8,426.9L372.3,424.4L375.9,421.2L377.4,420.8L377.8,417.7L377.2,418.0L379.0,415.4L380.6,414.1L382.7,414.6L381.9,415.1L382.4,415.5L383.4,413.3L386.8,413.4L393.6,408.7L395.0,409.2L395.5,410.3L392.2,412.3L393.6,415.9L391.6,419.4L393.3,422.4L395.7,421.2L395.7,419.3L393.9,416.1L394.1,414.7L399.6,412.5L400.8,412.6L400.7,411.9L399.2,412.0L398.9,411.0L400.0,409.8L401.5,412.6L404.7,412.8L406.4,413.9L407.5,416.6L415.0,416.1L416.6,417.6L419.5,418.3L425.0,416.6L422.8,416.0L432.5,415.6L428.6,416.9L430.7,419.5L431.5,418.7L433.0,419.9L433.6,419.0L436.8,421.2L435.7,422.6L436.6,422.8L433.8,424.4L434.8,425.0L439.3,424.2L443.2,426.4L446.1,429.4L445.6,432.2L446.3,431.3L448.1,431.6L451.1,434.0L451.2,436.4L452.1,434.7L456.3,435.6L456.7,434.9L460.7,434.8L463.4,435.3L463.9,436.2L464.6,435.6L468.4,437.0L471.8,439.9L472.1,441.1L472.7,440.2L473.4,442.2L469.7,448.6L468.1,450.0L464.9,449.3L463.5,450.3L460.0,448.4L457.1,449.1L456.2,448.7L455.5,449.6L456.1,451.3L451.5,450.7L447.9,452.7L446.6,452.4L446.0,453.6L444.7,453.9L441.0,451.1L440.0,447.7L440.6,444.4L441.8,443.0L441.2,441.2L439.4,440.6L440.0,438.4L439.4,437.8L437.2,437.9L437.5,439.2L435.9,440.6L429.2,442.6L428.6,444.4L426.7,443.0L423.9,443.0L420.9,441.6L423.1,444.4L423.8,448.7L426.4,449.1L426.4,449.8L424.0,451.0L423.2,452.6L419.6,454.3L418.1,456.0L417.3,454.8L414.8,455.7L412.5,453.9L411.7,454.0L411.5,451.9L410.4,450.3L408.3,451.7L407.2,450.8L407.3,451.9L400.6,451.9L400.6,454.4L402.8,454.5L403.4,456.1L399.8,456.5L399.7,459.3L401.5,460.8L402.4,463.5L400.1,475.7L398.7,474.0L397.1,473.9L399.6,469.4L396.1,467.6L394.4,468.1L393.0,467.4L391.1,468.3L388.2,468.3L385.4,463.7L383.0,462.6L380.8,459.6L376.9,458.4L374.9,457.0L374.0,457.8L370.4,457.2L369.2,455.4L366.8,454.6Z","label":[388.5,439.0]},"western-australia":{"d":"M1132.7,565.9L1133.4,565.4L1134.3,567.5L1135.3,567.2L1133.8,563.4L1134.9,564.4L1134.9,565.7L1135.4,565.2L1136.0,566.5L1136.9,566.3L1136.9,564.5L1133.7,558.4L1135.0,554.1L1134.7,550.8L1135.8,547.9L1136.5,547.4L1136.7,550.1L1138.8,547.4L1146.8,542.5L1149.6,542.8L1156.4,539.8L1158.3,540.0L1164.0,538.1L1167.3,533.5L1169.4,532.0L1168.6,529.6L1169.0,527.9L1171.9,525.1L1174.3,529.4L1174.4,527.5L1175.3,527.9L1175.5,527.0L1174.0,525.3L1174.4,524.2L1178.8,524.8L1177.6,524.5L1178.6,522.7L1178.0,523.2L1177.5,522.3L1177.8,521.2L1178.8,520.3L1180.2,521.0L1179.4,519.8L1180.2,519.2L1181.4,519.6L1180.7,518.0L1182.3,517.1L1182.5,516.1L1183.4,517.6L1184.1,517.1L1184.2,515.0L1186.3,515.7L1187.6,514.1L1189.8,515.2L1192.7,518.0L1192.3,520.5L1194.3,518.3L1196.2,518.7L1196.8,519.7L1197.1,518.7L1198.5,519.7L1198.5,518.6L1199.3,518.4L1197.5,516.7L1199.4,513.4L1201.0,512.3L1200.7,510.9L1202.4,509.6L1202.5,508.7L1203.5,509.2L1205.2,507.2L1205.8,508.1L1210.8,507.4L1210.7,505.5L1208.3,504.8L1207.3,504.1L1207.8,503.6L1220.1,507.8L1223.7,506.3L1222.8,507.8L1224.0,507.7L1224.4,508.7L1225.1,508.7L1225.1,507.5L1226.2,506.8L1227.8,508.4L1226.1,510.2L1225.8,511.9L1223.7,512.3L1223.5,515.7L1221.6,518.2L1225.2,521.5L1230.8,524.2L1232.0,525.6L1232.0,565.0L1244.0,565.1L1243.9,618.7L1241.6,617.9L1239.1,614.9L1238.9,610.9L1235.9,607.3L1237.1,607.4L1237.1,606.4L1232.7,607.4L1234.0,604.8L1232.4,601.0L1230.8,605.3L1227.5,605.8L1228.1,604.3L1229.6,604.2L1230.0,600.9L1231.7,598.3L1231.3,594.0L1228.9,598.5L1225.7,600.3L1223.6,603.1L1223.9,604.6L1222.6,604.4L1220.5,602.8L1221.8,602.8L1220.9,600.0L1218.9,596.8L1217.2,596.4L1216.4,594.6L1216.9,593.6L1211.0,591.0L1208.9,591.3L1204.6,589.0L1195.8,589.9L1189.3,592.4L1183.7,592.5L1177.0,595.7L1174.6,599.4L1172.8,600.1L1165.6,599.4L1159.4,600.0L1157.8,601.8L1150.3,605.1L1143.9,603.7L1140.0,601.3L1140.0,598.0L1141.4,598.5L1142.7,596.5L1142.8,589.9L1140.3,584.9L1139.4,578.7L1136.7,574.1L1136.1,570.9Z M1201.5,503.8L1203.0,504.5L1205.1,503.7L1206.2,504.7L1203.8,506.7L1202.0,505.4Z M1226.2,608.7L1229.3,607.4L1232.5,608.5L1229.8,609.6Z","label":[1197.1,555.1]},"western-europe":{"d":"M642.6,295.8L642.5,292.8L644.6,289.2L645.4,284.8L644.4,281.3L645.0,278.2L644.7,277.1L643.9,277.3L644.3,276.4L643.1,275.5L643.3,274.5L649.2,271.6L651.7,272.7L656.6,272.2L661.9,273.3L665.6,272.8L672.0,273.7L674.1,272.6L675.6,267.1L675.0,267.2L675.7,262.9L677.4,265.1L676.8,263.3L675.2,262.0L675.8,261.7L675.4,259.1L672.9,258.0L671.6,256.3L671.6,254.1L670.0,254.0L670.2,253.1L668.9,252.4L662.8,251.4L661.3,250.3L662.6,249.7L661.7,249.0L662.9,248.8L661.1,248.6L661.1,247.7L667.1,246.2L669.2,247.7L674.2,247.2L672.6,241.8L675.0,241.8L675.5,243.4L680.0,243.7L681.2,243.1L680.4,242.5L680.7,241.7L685.6,239.7L686.7,235.5L690.1,234.4L691.0,236.2L692.4,236.1L694.7,238.5L696.7,238.9L696.7,240.4L699.3,239.4L699.5,241.3L702.0,242.7L705.0,242.8L707.0,244.5L712.5,245.5L710.5,249.7L710.5,252.6L707.9,253.3L703.9,259.6L707.1,258.6L708.1,261.0L707.2,261.9L708.6,263.6L706.5,265.2L708.0,266.4L707.6,268.9L710.5,269.7L710.0,271.6L704.5,275.0L696.2,272.5L693.0,274.4L692.2,276.1L693.2,278.8L692.6,279.4L693.0,280.5L688.3,283.6L683.0,285.7L683.6,286.3L682.4,286.8L678.7,292.0L679.2,294.1L680.8,295.5L677.9,297.6L676.7,300.1L677.1,300.8L673.4,301.9L671.6,304.7L662.5,305.0L659.3,306.3L657.5,308.1L655.8,307.4L654.5,305.3L654.6,304.3L652.4,302.6L648.7,303.7L644.0,303.5L644.7,301.7L644.5,297.0L645.2,296.9L643.1,297.0L644.0,294.8ZM672.0,254.2L672.2,254.1L672.0,254.0ZM654.7,304.3L654.8,304.1L654.6,304.2Z M714.3,278.5L715.3,277.3L717.2,276.8L717.9,275.5L718.2,279.4L716.7,283.1L715.2,282.2Z M689.5,291.7L692.6,289.8L692.6,290.7L693.8,291.1L692.3,293.0Z","label":[660.8,289.1]},"western-us":{"d":"M181.2,248.6L188.9,249.8L189.1,251.6L189.9,250.9L189.7,254.1L188.0,254.8L189.2,255.0L190.4,253.9L190.1,253.1L190.6,253.7L191.0,250.4L189.9,249.7L190.4,249.0L189.3,248.1L189.9,247.1L188.8,245.4L291.1,245.4L293.8,259.9L292.6,262.6L294.2,264.1L294.2,272.9L293.6,273.0L294.1,274.9L293.5,276.6L295.7,281.0L297.4,288.0L298.6,289.7L300.3,290.4L299.8,292.0L301.5,293.8L301.5,303.7L268.0,303.7L267.7,326.2L253.5,326.2L254.2,327.3L247.2,327.2L247.1,329.2L235.8,329.2L220.7,324.0L221.0,323.1L211.5,323.9L210.1,320.5L207.7,318.6L206.4,318.5L206.0,317.2L197.4,314.7L197.4,312.3L192.5,306.7L192.8,304.4L190.4,302.7L189.9,300.1L191.6,301.4L190.5,299.2L191.2,298.7L190.1,298.5L189.9,299.9L188.0,299.1L188.1,297.9L185.2,294.8L184.7,290.8L182.6,288.0L183.7,282.8L181.8,276.3L183.4,272.0L184.0,259.5L186.7,259.8L183.7,259.2L184.4,257.3L183.5,256.3L184.4,255.8L183.4,255.8ZM192.7,298.9L193.2,298.7L192.7,298.8ZM192.4,298.8L191.9,298.5L191.5,298.8ZM187.7,253.6L188.1,252.7L189.0,251.8L187.8,252.8Z","label":[232.6,285.2]},"yakutsk":{"d":"M1102.2,139.9L1107.2,137.7L1107.5,127.3L1104.6,123.6L1105.7,122.4L1113.2,120.3L1117.0,120.8L1118.0,119.1L1117.1,117.8L1120.4,115.9L1120.4,114.7L1130.3,111.2L1128.0,109.0L1128.0,103.7L1122.8,99.7L1122.5,97.9L1123.5,96.9L1122.1,94.0L1124.4,92.3L1123.2,91.2L1125.2,90.3L1129.6,91.6L1131.4,91.1L1131.3,89.7L1133.7,92.1L1132.7,93.5L1134.0,94.4L1133.6,97.6L1135.3,94.5L1134.0,93.2L1141.4,91.7L1153.8,92.6L1155.7,93.4L1153.8,93.5L1153.7,95.2L1155.8,96.2L1169.0,98.0L1172.6,97.4L1174.3,95.7L1173.3,93.8L1174.0,92.0L1178.2,91.3L1182.5,93.6L1185.0,92.9L1186.2,94.5L1188.1,92.9L1191.8,93.7L1196.4,96.2L1194.7,97.9L1197.0,99.3L1194.0,100.7L1197.1,101.3L1197.6,103.4L1195.7,104.0L1191.0,101.5L1191.4,102.3L1195.6,106.4L1196.8,105.3L1197.8,106.6L1195.5,107.3L1199.1,111.1L1203.0,112.3L1204.1,113.9L1206.2,112.7L1210.6,105.2L1214.8,108.8L1218.8,109.2L1224.4,107.5L1231.8,111.0L1231.4,110.4L1232.6,109.6L1231.7,108.9L1232.9,107.6L1239.8,108.4L1238.8,105.5L1237.4,105.0L1240.3,103.1L1236.7,103.4L1236.6,102.2L1244.1,100.3L1242.8,97.9L1265.0,101.3L1256.9,102.8L1257.9,103.3L1267.3,102.4L1264.5,105.0L1264.8,103.6L1263.0,103.0L1263.0,105.1L1260.2,105.2L1260.0,106.5L1260.7,106.9L1264.3,106.1L1269.0,102.2L1273.6,102.3L1278.0,103.4L1280.1,105.4L1275.9,106.9L1282.7,108.7L1281.4,109.9L1286.3,109.9L1288.4,111.8L1287.6,112.3L1290.0,113.2L1306.7,111.3L1317.4,113.5L1319.8,116.2L1319.3,120.8L1323.6,122.0L1324.1,125.6L1325.4,127.0L1323.8,129.5L1326.2,127.0L1326.1,123.7L1329.5,121.7L1331.1,124.9L1330.2,126.4L1331.4,127.2L1330.4,127.8L1330.9,129.1L1329.7,131.1L1325.6,130.4L1313.1,132.8L1312.4,134.3L1313.3,135.0L1310.8,136.4L1315.6,141.4L1313.6,143.7L1313.9,146.0L1308.5,147.3L1306.6,145.5L1305.4,146.3L1297.6,145.4L1297.0,147.9L1294.0,147.8L1294.1,150.6L1290.7,151.8L1290.1,153.7L1291.0,155.3L1288.7,157.5L1288.0,156.5L1282.6,157.6L1282.5,158.7L1280.9,158.4L1279.3,156.1L1274.5,157.1L1273.2,156.2L1272.1,157.0L1273.0,158.1L1271.8,160.1L1270.4,158.7L1265.5,158.5L1262.3,160.8L1262.5,161.9L1261.0,165.0L1261.9,168.9L1260.6,169.2L1260.7,170.5L1257.0,173.8L1252.5,171.7L1251.7,172.9L1249.8,173.1L1242.8,168.8L1238.2,175.5L1234.9,176.4L1232.8,178.7L1233.7,180.2L1232.8,183.1L1233.0,186.2L1230.5,186.0L1227.0,188.3L1223.1,187.2L1220.8,189.7L1212.5,189.2L1210.4,191.1L1210.3,192.5L1208.6,193.4L1208.2,196.1L1206.0,195.3L1208.1,198.0L1206.4,200.3L1204.7,200.7L1207.2,203.6L1204.7,205.0L1203.7,209.5L1198.6,209.1L1196.3,209.6L1195.9,210.7L1190.7,209.5L1184.2,210.0L1183.1,208.8L1177.8,208.2L1175.6,205.5L1166.4,203.5L1165.6,202.1L1159.6,202.6L1158.6,202.0L1157.9,198.6L1156.6,198.9L1156.2,197.8L1156.5,193.5L1155.3,192.9L1155.0,187.9L1153.3,186.9L1151.6,187.8L1148.4,186.9L1148.9,185.7L1148.3,185.0L1148.9,184.4L1146.3,182.3L1142.7,181.3L1140.5,182.1L1134.4,187.0L1133.9,188.8L1129.8,190.8L1130.4,188.6L1128.9,187.5L1126.5,189.2L1122.9,188.9L1121.1,190.5L1118.5,190.0L1117.1,187.7L1122.1,177.9L1119.2,176.4L1119.4,175.0L1118.5,174.0L1120.0,170.6L1117.2,169.3L1118.4,166.9L1116.9,163.3L1112.4,162.2L1115.0,160.6L1113.9,158.0L1111.4,158.6L1103.2,156.3L1102.8,155.6L1104.4,153.9L1103.9,153.2L1107.7,150.0L1105.8,149.0L1105.9,146.7L1104.4,144.2L1105.2,143.6L1105.0,142.1ZM1132.6,98.8L1133.3,99.7L1134.6,99.7L1133.2,99.1L1133.2,97.7Z M1227.8,78.8L1229.1,78.6L1229.1,75.4L1230.5,75.2L1230.0,74.1L1230.5,73.5L1235.3,71.7L1242.6,76.3L1245.9,72.2L1261.4,77.2L1256.1,81.1L1251.3,79.3L1250.8,77.8L1251.8,75.7L1249.2,75.9L1248.4,76.2L1248.8,78.3L1252.5,81.7L1249.9,82.9L1247.9,81.5L1241.1,82.7L1238.7,81.7L1236.4,84.2L1232.4,83.1Z M1264.6,79.9L1266.1,76.8L1267.2,78.5L1273.7,78.1L1274.4,79.6L1283.3,80.2L1282.3,82.1L1276.2,83.3Z M1238.7,93.8L1241.5,93.4L1244.3,90.4L1247.7,90.0L1253.4,92.7L1254.0,95.2Z M1126.0,86.6L1128.3,85.0L1133.5,86.2L1131.1,88.6Z","label":[1167.3,142.8]}},"touching":[["afghanistan","china"],["afghanistan","india"],["afghanistan","middle-east"],["afghanistan","siberia"],["afghanistan","ukraine"],["afghanistan","ural"],["alaska","alberta"],["alaska","northwest-territory"],["alberta","northwest-territory"],["alberta","ontario"],["alberta","western-us"],["argentina","brazil"],["argentina","peru"],["brazil","peru"],["brazil","venezuela"],["central-america","eastern-us"],["central-america","venezuela"],["central-america","western-us"],["china","india"],["china","mongolia"],["china","siam"],["china","siberia"],["congo","east-africa"],["congo","north-africa"],["congo","south-africa"],["east-africa","egypt"],["east-africa","north-africa"],["east-africa","south-africa"],["eastern-australia","western-australia"],["eastern-us","ontario"],["eastern-us","quebec"],["eastern-us","western-us"],["egypt","middle-east"],["egypt","north-africa"],["india","middle-east"],["india","siam"],["indonesia","siam"],["irkutsk","kamchatka"],["irkutsk","mongolia"],["irkutsk","siberia"],["irkutsk","yakutsk"],["kamchatka","mongolia"],["kamchatka","yakutsk"],["middle-east","southern-europe"],["middle-east","ukraine"],["mongolia","siberia"],["north-africa","western-europe"],["northern-europe","scandinavia"],["northern-europe","southern-europe"],["northern-europe","ukraine"],["northern-europe","western-europe"],["northwest-territory","ontario"],["ontario","quebec"],["ontario","western-us"],["peru","venezuela"],["scandinavia","ukraine"],["siberia","ural"],["siberia","yakutsk"],["southern-europe","ukraine"],["southern-europe","western-europe"],["ukraine","ural"]],"near":{"afghanistan|congo":[923.4,335.7,803.6,444.9],"afghanistan|east-africa":[923.4,335.7,885.0,411.2],"afghanistan|egypt":[895.7,302.1,816.9,329.6],"afghanistan|indonesia":[956.3,327.4,1061.6,436.2],"afghanistan|irkutsk":[1026.7,241.3,1062.6,217.2],"afghanistan|kamchatka":[1029.3,244.9,1158.7,203.3],"afghanistan|madagascar":[928.3,337.2,877.1,507.3],"afghanistan|mongolia":[1029.3,244.9,1031.0,246.0],"afghanistan|north-africa":[895.7,302.1,775.9,379.8],"afghanistan|northern-europe":[866.4,247.6,776.4,235.7],"afghanistan|scandinavia":[869.7,238.3,805.1,168.8],"afghanistan|siam":[1000.9,280.0,1060.5,349.8],"afghanistan|southern-europe":[866.4,247.6,798.8,264.3],"afghanistan|western-europe":[866.4,247.6,717.9,275.5],"afghanistan|yakutsk":[1029.3,244.9,1117.2,187.9],"alaska|central-america":[157.7,214.5,211.5,323.9],"alaska|eastern-us":[159.9,211.6,291.1,245.4],"alaska|kamchatka":[1433.4,161.5,1430.5,157.0],"alaska|ontario":[159.9,211.6,272.0,208.7],"alaska|western-us":[157.7,214.5,181.2,248.6],"alaska|yakutsk":[1432.7,163.1,1330.9,129.1],"alberta|central-america":[221.1,245.4,221.1,323.1],"alberta|eastern-us":[274.5,245.4,291.1,245.4],"alberta|greenland":[272.0,184.4,395.4,64.4],"alberta|quebec":[274.5,245.4,361.8,246.0],"argentina|central-america":[402.0,529.5,368.4,429.8],"argentina|eastern-us":[402.0,529.5,358.1,355.7],"argentina|venezuela":[402.0,529.5,400.1,475.7],"brazil|central-america":[400.6,451.9,370.6,427.9],"brazil|congo":[540.8,487.9,714.8,461.1],"brazil|eastern-us":[400.6,451.9,358.1,355.7],"brazil|north-africa":[538.1,479.4,613.0,408.9],"brazil|ontario":[420.8,441.6,360.0,277.7],"brazil|quebec":[437.0,437.9,417.4,272.8],"brazil|western-us":[400.6,451.9,301.5,303.7],"central-america|northwest-territory":[274.5,336.2,351.8,224.0],"central-america|ontario":[351.6,364.3,349.2,281.7],"central-america|peru":[368.4,429.8,364.6,452.9],"central-america|quebec":[357.4,364.7,381.2,265.6],"china|east-africa":[995.0,329.2,885.0,411.2],"china|eastern-australia":[1163.4,369.6,1248.8,502.6],"china|egypt":[975.2,296.2,819.6,337.4],"china|indonesia":[1163.4,369.6,1164.4,383.4],"china|irkutsk":[1151.2,277.4,1151.3,242.7],"china|japan":[1167.4,327.1,1198.3,320.7],"china|kamchatka":[1170.7,301.8,1202.7,278.7],"china|middle-east":[975.2,296.2,924.5,305.3],"china|new-guinea":[1163.4,369.6,1202.7,462.7],"china|southern-europe":[974.5,292.3,798.8,264.3],"china|ukraine":[974.5,292.3,874.3,280.9],"china|ural":[1027.5,244.9,1018.2,236.6],"china|western-australia":[1163.4,369.6,1201.5,503.8],"china|yakutsk":[1151.2,277.4,1177.8,208.2],"congo|egypt":[770.0,414.6,775.9,379.8],"congo|great-britain":[736.3,406.1,680.8,236.2],"congo|india":[803.6,444.9,926.3,355.8],"congo|madagascar":[799.1,512.8,855.8,529.8],"congo|middle-east":[803.6,444.9,854.0,407.7],"congo|northern-europe":[736.3,406.1,720.7,254.2],"congo|southern-europe":[736.3,406.1,740.5,305.1],"congo|ukraine":[770.0,414.6,795.0,264.4],"congo|western-europe":[736.3,406.1,671.6,304.7],"east-africa|great-britain":[779.9,369.3,685.6,234.0],"east-africa|india":[885.0,411.2,926.3,355.8],"east-africa|indonesia":[885.5,417.0,1060.8,437.6],"east-africa|madagascar":[841.9,500.7,857.8,524.3],"east-africa|middle-east":[852.7,408.2,854.0,407.7],"east-africa|northern-europe":[779.9,369.3,754.9,251.6],"east-africa|scandinavia":[779.9,369.3,736.7,211.0],"east-africa|siam":[885.0,411.2,1048.7,372.3],"east-africa|southern-europe":[779.9,369.3,779.2,313.1],"east-africa|ukraine":[827.5,369.3,869.3,283.6],"east-africa|ural":[827.5,369.3,885.4,232.4],"east-africa|western-europe":[779.9,369.3,716.7,283.1],"eastern-australia|indonesia":[1248.7,502.7,1218.9,483.9],"eastern-australia|japan":[1250.2,501.7,1202.7,330.6],"eastern-australia|new-guinea":[1250.2,501.7,1250.6,496.2],"eastern-australia|siam":[1248.7,502.7,1156.5,438.3],"eastern-us|greenland":[408.8,255.2,487.0,179.6],"eastern-us|northwest-territory":[336.5,254.1,351.8,224.0],"eastern-us|peru":[355.6,356.1,364.6,452.9],"eastern-us|venezuela":[358.1,355.7,380.6,414.1],"egypt|great-britain":[726.0,321.0,685.6,234.0],"egypt|india":[827.5,369.3,926.3,355.8],"egypt|madagascar":[827.5,369.3,877.1,507.3],"egypt|northern-europe":[726.0,321.0,720.7,254.2],"egypt|scandinavia":[726.0,321.0,731.5,211.0],"egypt|south-africa":[775.9,379.8,795.9,492.7],"egypt|southern-europe":[771.7,323.3,774.6,311.7],"egypt|ukraine":[807.6,328.3,815.6,268.6],"egypt|ural":[816.8,329.3,883.2,231.1],"egypt|western-europe":[726.0,321.0,716.7,283.1],"great-britain|greenland":[659.9,192.9,591.2,118.5],"great-britain|iceland":[659.9,192.9,623.8,155.0],"great-britain|middle-east":[685.6,234.0,785.3,281.3],"great-britain|north-africa":[659.5,240.0,658.4,308.6],"great-britain|northern-europe":[685.6,234.0,690.1,234.4],"great-britain|quebec":[638.5,230.3,469.4,252.8],"great-britain|scandinavia":[672.9,199.1,700.7,189.4],"great-britain|southern-europe":[685.6,234.0,708.2,253.1],"great-britain|ukraine":[686.6,225.6,764.9,211.8],"great-britain|western-europe":[685.6,234.0,686.7,235.5],"greenland|iceland":[576.4,126.9,588.4,143.8],"greenland|northern-europe":[593.9,115.5,713.1,203.0],"greenland|northwest-territory":[421.9,29.5,419.7,25.5],"greenland|ontario":[473.8,161.3,361.9,232.0],"greenland|quebec":[487.0,179.6,450.4,215.5],"greenland|scandinavia":[593.9,115.5,699.6,173.5],"greenland|southern-europe":[591.2,118.5,708.2,253.1],"greenland|western-europe":[585.0,122.2,661.1,247.7],"iceland|north-africa":[605.4,163.5,656.3,309.2],"iceland|northern-europe":[625.8,152.7,713.1,203.0],"iceland|northwest-territory":[582.1,149.9,434.8,142.4],"iceland|quebec":[583.9,154.2,456.1,221.7],"iceland|scandinavia":[625.8,152.7,699.6,173.5],"iceland|southern-europe":[621.5,157.1,708.2,253.1],"iceland|ukraine":[625.8,152.7,764.3,203.0],"iceland|western-europe":[621.5,157.1,661.1,247.7],"india|indonesia":[1007.4,431.1,1060.8,437.6],"india|irkutsk":[1064.1,337.5,1094.5,239.5],"india|japan":[1069.4,343.8,1198.3,320.7],"india|kamchatka":[1069.4,343.8,1201.7,276.7],"india|madagascar":[986.2,423.0,877.1,507.3],"india|mongolia":[1064.1,337.5,1086.3,295.9],"india|new-guinea":[1068.4,347.6,1202.7,462.7],"india|north-africa":[926.3,355.8,775.9,379.8],"india|northern-europe":[923.4,335.7,775.9,238.0],"india|siberia":[983.4,305.3,1020.9,242.2],"india|south-africa":[986.2,423.0,842.4,501.5],"india|southern-europe":[923.4,335.7,785.3,311.4],"india|ukraine":[923.4,335.7,871.8,283.4],"india|ural":[978.8,303.5,1000.3,236.2],"india|western-australia":[1006.5,433.0,1135.8,547.9],"india|yakutsk":[1064.1,337.5,1118.5,190.0],"indonesia|irkutsk":[1164.4,383.4,1151.3,242.7],"indonesia|japan":[1168.9,383.9,1202.7,330.6],"indonesia|kamchatka":[1168.9,383.9,1202.7,278.7],"indonesia|middle-east":[1061.6,436.2,932.6,349.6],"indonesia|mongolia":[1168.9,383.9,1184.4,315.5],"indonesia|new-guinea":[1218.5,480.5,1220.8,476.5],"indonesia|siberia":[1162.4,383.9,1072.0,240.1],"indonesia|western-australia":[1189.2,492.5,1201.5,503.8],"indonesia|yakutsk":[1164.4,383.4,1184.2,210.0],"irkutsk|japan":[1168.1,221.8,1246.4,264.3],"irkutsk|middle-east":[1062.6,217.2,924.5,305.3],"irkutsk|siam":[1094.5,239.5,1070.4,341.5],"irkutsk|ukraine":[1062.6,217.2,896.7,209.3],"irkutsk|ural":[1062.6,217.2,1028.3,223.8],"japan|kamchatka":[1247.8,263.0,1248.3,261.0],"japan|mongolia":[1203.6,316.1,1196.9,312.0],"japan|new-guinea":[1202.7,330.6,1210.0,460.1],"japan|siam":[1200.8,329.4,1111.9,371.4],"japan|siberia":[1198.3,320.7,1072.0,240.1],"japan|western-australia":[1202.7,330.6,1205.1,503.7],"japan|yakutsk":[1247.8,263.0,1203.7,209.5],"kamchatka|siam":[1202.7,278.7,1107.1,366.0],"kamchatka|siberia":[1158.7,203.3,1101.4,189.7],"kamchatka|ural":[1158.7,203.3,1028.3,223.8],"madagascar|middle-east":[877.1,507.3,856.7,407.9],"madagascar|north-africa":[857.8,524.3,771.4,414.9],"madagascar|south-africa":[857.8,524.3,843.1,518.8],"middle-east|mongolia":[924.5,305.3,1031.0,246.0],"middle-east|north-africa":[818.5,343.5,775.9,379.8],"middle-east|northern-europe":[785.3,281.3,768.4,248.5],"middle-east|scandinavia":[788.0,279.9,753.4,202.1],"middle-east|siam":[933.0,348.6,1048.7,372.3],"middle-east|siberia":[924.5,305.3,1017.0,238.7],"middle-east|south-africa":[856.0,408.0,841.9,500.7],"middle-east|ural":[874.3,280.9,898.2,237.4],"middle-east|western-europe":[784.2,286.3,718.2,279.4],"mongolia|new-guinea":[1186.1,315.9,1210.0,460.1],"mongolia|siam":[1097.0,301.9,1070.4,341.5],"mongolia|ukraine":[1031.0,246.0,896.7,209.3],"mongolia|ural":[1032.8,243.0,1027.0,229.0],"mongolia|yakutsk":[1173.9,221.4,1177.8,208.2],"new-guinea|siam":[1202.7,462.7,1156.5,438.3],"new-guinea|western-australia":[1230.6,492.4,1226.2,506.8],"north-africa|northern-europe":[718.8,302.1,720.7,254.2],"north-africa|scandinavia":[718.8,302.1,731.5,211.0],"north-africa|south-africa":[754.3,426.5,795.9,492.7],"north-africa|southern-europe":[724.2,303.3,729.7,299.9],"north-africa|ukraine":[724.2,303.3,768.5,248.4],"north-africa|ural":[724.2,303.3,883.2,231.1],"north-africa|venezuela":[609.9,399.3,473.4,442.2],"northern-europe|ural":[773.8,218.0,874.0,186.1],"northwest-territory|quebec":[362.9,170.8,367.6,170.3],"northwest-territory|western-us":[291.1,184.4,291.1,245.4],"ontario|peru":[349.2,281.7,364.6,452.9],"ontario|venezuela":[349.2,281.7,393.6,408.7],"peru|western-us":[359.6,455.6,267.7,326.2],"quebec|venezuela":[417.4,272.8,393.6,408.7],"quebec|western-europe":[469.4,252.8,643.1,275.5],"quebec|western-us":[361.8,253.1,293.8,259.9],"scandinavia|siberia":[806.0,166.4,980.3,194.2],"scandinavia|southern-europe":[736.7,211.0,740.0,245.4],"scandinavia|ural":[803.7,122.1,853.3,128.6],"scandinavia|western-europe":[731.5,211.0,712.5,245.5],"siam|siberia":[1070.4,341.5,1068.8,241.6],"siam|ural":[1070.4,341.5,1018.2,236.6],"siam|western-australia":[1138.0,452.9,1182.5,516.1],"siam|yakutsk":[1070.4,341.5,1118.5,190.0],"siberia|ukraine":[993.9,101.7,955.6,67.7],"south-africa|southern-europe":[803.0,491.6,784.7,312.7],"southern-europe|ural":[798.8,264.3,883.2,231.1],"ukraine|western-europe":[768.5,248.4,712.5,245.5],"ukraine|yakutsk":[955.8,67.5,1104.6,123.6],"ural|western-europe":[883.2,231.1,717.9,275.5],"ural|yakutsk":[1023.8,159.4,1102.8,155.6],"venezuela|western-us":[380.6,414.1,301.5,303.7]},"borders":{"afghanistan|china":"M1028.1,243.8L1029.3,244.9L1027.2,245.1L1026.2,247.8L1023.0,248.6L1022.1,250.9L1022.6,254.3L1021.9,255.3L1019.1,256.4L1012.1,254.6L1009.3,262.6L1010.4,263.5L1010.1,265.0L1006.8,263.9L999.5,266.1L1001.9,267.0L1001.4,270.0L1003.1,274.6L1001.6,275.2L1002.2,276.0L1000.7,277.0L1000.9,280.0L995.0,282.3L992.5,284.6L987.6,284.9L985.3,288.0L982.7,288.3L982.1,286.7L976.0,289.5L975.6,291.7L974.5,292.4L975.0,295.8L979.2,296.9L979.6,300.9L980.5,301.9L977.6,302.9L978.2,303.6L977.2,304.0","afghanistan|india":"M978.7,302.5L977.6,302.9L978.2,303.6L976.2,304.5L970.5,304.5L966.5,306.3L964.8,308.0L966.5,312.0L963.9,314.9L964.4,316.8L962.6,317.5L959.6,317.3L961.0,320.5L958.0,321.7L957.0,324.3L957.1,326.5L956.3,327.4L952.7,327.1L950.4,328.4L951.0,329.2L950.4,329.4L947.3,329.5L945.4,331.5L944.9,335.7L936.4,337.7L929.9,337.7L923.4,335.7L924.1,334.8","afghanistan|middle-east":"M925.0,336.2L923.4,335.7L927.2,331.0L926.6,329.0L923.3,328.5L923.3,325.1L922.2,321.5L923.7,319.5L922.0,318.9L921.9,316.9L923.5,315.8L922.9,315.0L924.3,313.5L925.1,310.5L924.5,305.3L921.4,305.3L917.2,301.3L913.0,300.6L909.4,299.2L908.8,298.1L901.5,298.8L898.8,301.5L895.7,302.1L895.6,301.1","afghanistan|siberia":"M1015.7,236.1L1015.8,236.1L1017.3,238.9L1020.0,239.9L1021.0,242.2L1024.7,242.8L1026.7,241.3L1026.5,242.4L1029.3,244.9L1027.9,245.1","afghanistan|ukraine":"M866.4,247.6L868.1,244.6L867.2,243.5L867.6,241.8L869.7,238.3L870.8,238.2L873.4,240.9L875.0,240.6L874.5,237.0L881.2,233.2L883.2,231.1L884.3,231.7M877.7,258.4L876.7,258.9L874.2,257.6L874.2,256.8L875.8,256.7L872.7,252.0L869.2,251.8L868.3,249.3L866.4,247.6","afghanistan|ural":"M882.5,231.8L883.2,231.1L885.4,232.4L888.9,231.2L890.3,232.4L893.4,232.4L896.6,234.7L898.2,237.4L898.6,234.9L902.7,237.1L906.0,234.8L908.0,234.6L909.8,235.5L911.4,234.4L913.4,234.6L918.1,237.6L920.2,235.7L922.0,236.7L924.9,236.1L926.2,233.2L920.1,230.0L923.9,227.8L923.1,226.0L924.2,224.4L928.3,224.2L924.8,222.7L926.1,221.4L923.9,220.8L924.9,218.7L927.7,219.1L937.8,216.7L940.4,216.9L941.9,215.3L952.6,213.4L952.8,212.4L955.9,211.1L960.7,212.3L963.0,211.6L964.7,215.5L964.4,217.6L968.7,217.0L969.8,219.1L970.5,218.0L972.9,219.0L974.8,218.5L973.2,220.4L973.6,221.8L975.4,220.9L977.4,221.6L981.7,218.3L987.3,216.3L986.6,218.0L985.7,218.0L985.9,218.6L991.4,222.8L1000.0,236.1L1002.9,233.4L1004.5,233.9L1004.3,235.1L1005.6,235.2L1005.9,236.3L1010.0,236.4L1013.4,235.0L1015.8,236.1L1016.7,237.8","alaska|alberta":"M123.4,183.5L123.3,184.0L129.6,189.0L130.2,190.9L133.7,189.5L134.7,186.8L138.1,185.7L139.8,187.0L140.2,188.7L146.4,193.8L151.1,200.8L150.7,201.5L151.9,201.8L151.6,202.8L152.7,204.3L159.6,207.0L159.9,211.6L157.7,214.5L156.7,214.3M133.2,190.8L132.1,190.2L132.0,191.1L132.2,191.2","alaska|northwest-territory":"M115.1,121.4L116.0,121.7L116.0,182.6L120.1,183.3L123.7,182.4L123.3,184.0L125.1,185.4","alberta|northwest-territory":"M123.8,184.4L272.0,184.4L272.0,185.4","alberta|ontario":"M271.0,184.4L272.0,184.4L272.0,208.7L274.5,245.4L273.5,245.4","alberta|western-us":"M274.5,244.4L274.5,245.4L187.8,245.4M186.5,248.4L185.7,248.9L182.6,247.8","argentina|brazil":"M446.5,538.6L447.3,539.0L448.7,544.0L448.2,548.6L452.3,549.3L454.2,548.5L456.8,549.6L458.3,556.4L461.5,555.8L463.0,556.8L461.5,563.2L464.0,563.3L465.3,566.1L464.6,569.9L460.7,571.8L457.1,574.6L457.3,575.4L456.4,575.3L449.6,583.3L451.1,583.6L452.7,582.9L455.8,585.8L456.0,587.1L457.6,586.1L459.3,588.0L465.0,591.5L467.5,594.5L465.9,596.2L465.9,598.6L466.5,599.0L466.0,599.9","argentina|peru":"M398.4,533.9L398.3,532.9L400.3,532.4L400.6,530.3L402.0,529.5L403.6,531.7L404.1,535.5L406.1,537.4L405.2,538.6L405.8,539.7L405.0,540.2L405.0,541.5L407.2,545.2L408.8,551.9L411.2,551.6L415.1,547.3L416.9,548.6L421.6,549.1L422.7,551.6L424.3,548.3L428.6,548.1L429.4,549.1L430.9,542.1L433.0,538.3L443.6,536.8L447.3,539.0L447.9,541.3","brazil|peru":"M384.0,489.0L385.1,488.0L385.0,486.4L387.4,484.6L387.1,483.2L388.5,479.2L392.6,476.8L396.8,475.4L400.1,475.9L400.4,474.5M447.7,541.4L447.4,540.5L448.5,539.7L447.5,538.7L450.0,532.4L448.7,529.5L446.4,528.3L446.6,524.5L439.3,524.4L439.0,521.2L437.7,519.6L438.9,519.5L438.0,514.2L435.7,513.0L432.8,513.2L431.5,511.7L428.9,511.0L427.7,509.7L422.3,508.7L420.0,506.9L418.4,503.9L418.4,497.7L413.7,498.5L405.5,503.4L403.1,502.7L397.4,503.0L397.7,496.7L395.0,498.7L391.3,498.9L390.5,496.9L387.2,496.5L388.1,494.8L384.0,489.0","brazil|venezuela":"M398.9,475.8L400.1,475.9L402.4,463.5L401.5,460.8L399.7,459.3L399.8,456.4L403.4,456.1L402.8,454.5L400.6,454.5L400.6,451.9L407.2,451.9L407.2,450.8L408.3,451.7L410.4,450.3L411.5,451.9L411.7,454.0L412.5,453.8L414.8,455.7L417.3,454.8L418.1,456.0L419.6,454.3L423.2,452.6L424.0,451.0L426.4,449.8L426.4,449.1L423.8,448.7L423.1,444.4L420.8,441.6L423.9,443.0L426.6,443.0L428.6,444.4L429.2,442.7L436.0,440.6L437.6,438.9L437.0,437.9L439.0,437.7L440.0,438.4L439.4,440.6L441.2,441.2L441.8,443.0L440.6,444.4L440.0,447.7L441.0,451.1L444.6,453.9L446.0,453.6L446.6,452.4L447.9,452.7L451.5,450.7L456.1,451.3L455.4,449.7L456.2,448.7L457.1,449.1L460.1,448.3L463.5,450.2L464.9,449.3L468.2,450.0L474.2,441.5L474.2,441.5","central-america|eastern-us":"M253.0,327.3L254.2,327.3L260.3,332.5L262.4,336.9L267.0,339.4L270.6,335.7L274.5,336.2L282.0,345.7L282.2,347.8L283.6,350.5L290.5,352.9L291.4,352.5L291.0,353.8","central-america|venezuela":"M369.8,423.5L370.5,424.0L370.1,424.7L371.2,426.8L370.6,427.9L369.7,428.5L369.0,427.9L368.4,429.8L367.7,428.6","central-america|western-us":"M211.5,323.9L221.1,323.1L220.8,324.0L235.8,329.2L247.1,329.2L247.2,327.2L254.2,327.3L255.0,327.9","china|india":"M1069.6,342.4L1069.3,342.8L1065.3,342.1L1066.3,340.4L1065.7,339.2L1064.5,339.8L1065.4,338.4L1064.2,337.5L1061.6,339.3L1058.5,338.1L1055.0,340.6L1053.0,341.1L1049.9,344.4L1046.5,344.8L1046.5,344.1L1045.1,343.4L1041.4,343.4L1041.4,342.7L1039.9,342.4L1037.9,343.5L1035.6,346.7L1035.2,343.8L1034.5,343.4L1030.5,344.6L1026.5,343.3L1025.6,344.1L1024.5,343.3L1024.0,344.2L1022.7,342.6L1020.5,342.4L1020.6,341.2L1019.2,341.3L1016.9,339.8L1016.4,338.5L1014.3,338.6L1008.2,333.6L1005.7,333.6L1004.4,334.9L1000.8,332.6L1000.6,331.6L997.6,330.4L996.4,328.9L995.0,329.2L994.9,326.4L993.6,323.8L994.8,323.6L996.0,324.6L997.8,323.2M997.6,321.7L997.4,321.1L995.2,319.6L994.9,317.1L995.9,315.9L993.1,314.4L992.2,310.6L988.4,310.3L986.2,308.8L984.7,309.1L983.6,308.0L983.4,305.3L977.5,303.0L978.9,302.5","china|mongolia":"M1030.6,244.6L1031.3,244.5L1031.0,246.0L1032.2,246.9L1031.9,247.7L1036.2,250.5L1037.9,250.4L1041.3,252.3L1044.1,257.8L1044.0,260.4L1042.7,262.9L1043.5,264.6L1054.1,265.8L1058.9,268.8L1061.4,269.1L1061.3,270.3L1065.4,276.6L1068.8,276.4L1071.4,282.0L1070.8,283.3L1073.3,285.7L1073.0,287.1L1080.0,285.5L1080.8,286.8L1077.8,290.2L1083.2,292.5L1083.6,293.9L1086.3,295.9L1088.1,295.1L1087.3,294.1L1089.6,293.3L1091.9,293.8L1096.1,292.3L1096.7,294.6L1093.9,297.0L1093.6,299.1L1097.0,301.9L1103.1,300.0L1103.6,295.6L1105.1,293.1L1106.5,292.7L1107.9,294.2L1106.0,297.7L1108.7,298.4L1112.2,300.8L1115.0,300.5L1115.2,299.3L1116.0,299.1L1115.9,297.5L1120.7,293.1L1120.4,292.4L1122.7,293.2L1124.1,291.8L1124.5,292.6L1127.7,291.2L1129.6,288.3L1130.9,288.9L1132.3,287.8L1136.2,287.4L1136.6,286.2L1135.5,284.5L1135.7,283.0L1138.4,279.5L1139.2,279.3L1139.6,282.0L1140.9,282.2L1143.4,280.5L1144.2,281.1L1147.4,280.1L1147.5,278.3L1151.2,277.4L1152.5,280.0L1153.1,279.8L1152.7,281.6L1153.5,283.4L1156.9,283.7L1155.4,285.8L1158.3,287.2L1159.4,289.8L1158.5,290.3","china|siam":"M1112.9,371.2L1109.4,371.0L1106.7,369.4L1106.1,367.7L1107.1,366.0L1103.4,365.4L1101.1,363.7L1098.7,365.9L1096.6,366.0L1095.8,367.1L1093.3,366.1L1091.9,367.4L1089.9,366.2L1088.5,367.7L1086.8,367.3L1086.1,368.3L1087.2,372.6L1085.1,372.7L1084.5,370.4L1080.9,371.6L1079.7,369.2L1076.8,368.8L1078.0,365.2L1075.5,364.3L1074.7,361.3L1075.3,360.4L1070.3,361.3L1070.8,359.9L1070.3,357.6L1071.3,355.6L1074.6,353.0L1074.9,349.9L1074.6,345.6L1073.2,345.7L1071.5,342.2L1070.4,341.5L1069.3,342.8L1068.3,342.6","china|siberia":"M1027.7,244.9L1031.3,244.5L1031.0,245.6","congo|east-africa":"M770.5,414.7L771.4,414.9L774.6,419.3L773.9,422.4L774.3,423.6L776.8,424.0L777.2,425.5L779.4,426.1L780.8,427.4L780.8,428.6L785.4,432.2L786.1,434.4L788.6,435.8L789.0,437.6L792.3,441.0L796.9,441.2L798.7,440.4L803.6,444.9L802.9,448.9L805.1,450.2L799.8,455.5L798.3,464.3L796.8,465.6L796.5,467.5L795.4,468.5L796.9,471.0L797.3,478.4L798.4,481.7L798.2,484.0L800.9,486.9L803.0,491.6L802.0,491.7","congo|north-africa":"M714.1,440.3L716.0,435.0L719.1,431.7L722.4,430.4L724.9,432.9L727.4,430.2L727.1,429.6L728.9,425.5L731.1,423.4L734.8,415.1L738.3,412.4L738.5,409.9L736.8,409.0L736.3,406.1L737.8,406.4L739.4,408.5L740.3,411.1L740.5,416.0L742.6,418.6L737.0,418.7L735.9,419.9L740.5,424.4L742.2,427.5L742.0,428.6L743.8,428.7L746.2,427.2L747.2,428.5L750.6,426.7L754.3,426.5L756.4,424.1L755.8,422.9L761.4,422.1L766.7,417.4L767.1,416.0L770.0,414.6L771.4,414.9L772.0,415.7","congo|south-africa":"M802.6,490.6L803.0,491.6L795.7,492.7L795.5,494.0L793.6,495.8L794.6,501.1L793.5,505.2L796.3,508.4L797.9,508.7L798.0,507.9L799.2,507.6L799.1,512.8L798.2,512.0L796.1,512.5L793.7,509.1L790.3,507.9L788.7,505.3L787.3,506.8L784.1,506.5L781.4,505.4L781.1,503.8L777.5,504.6L777.5,503.4L775.9,502.4L775.9,511.0L767.9,511.1L768.2,524.4L768.6,525.7L773.5,530.0L763.0,531.6L755.8,530.7L753.6,529.0L736.1,529.1L732.4,527.3L730.2,528.3L727.0,528.4L727.0,527.4","east-africa|egypt":"M775.9,380.8L775.9,377.7L779.9,377.7L779.9,369.3L804.8,369.3L805.6,368.5L805.9,369.3L827.5,369.3L827.9,370.3","east-africa|north-africa":"M767.3,407.3L768.9,405.1L768.4,403.2L770.1,401.7L769.5,400.1L771.7,397.6L771.7,396.1L772.4,395.4L775.9,395.3L775.9,378.6M772.0,415.7L771.4,414.9L771.7,413.2L770.4,412.2L769.4,407.8L767.3,407.3","east-africa|south-africa":"M840.9,500.1L841.9,500.7L837.3,503.4L834.0,504.6L831.7,504.1L829.5,505.8L827.9,505.2L825.2,505.8L823.6,504.8L819.8,505.3L818.4,503.2L818.1,499.0L816.4,497.0L813.7,497.3L803.6,492.7L802.6,490.6","eastern-australia|western-australia":"M1244.7,619.3L1243.9,618.7L1244.0,565.2L1232.0,565.1L1232.0,525.6","eastern-us|ontario":"M291.3,246.4L291.1,245.4L299.3,245.4L299.4,243.5L300.6,243.8L301.5,246.7L305.2,247.8L308.0,247.4L313.9,250.2L316.3,249.4L322.2,250.5L326.5,249.0L340.5,256.1L341.7,258.3L343.4,257.9L344.1,260.2L345.5,260.0L346.1,260.6L345.7,261.6L349.8,263.9L351.4,272.6L349.8,277.2L347.4,279.5L347.9,281.0L349.2,281.7L363.9,276.3L364.3,275.8L363.3,273.1L364.6,272.5L372.7,272.3L375.4,269.0L380.0,265.7L382.6,265.7","eastern-us|quebec":"M380.2,265.7L393.9,265.5L394.7,264.1L396.5,264.2L398.8,261.1L400.0,257.0L403.0,253.2L404.3,254.5L407.1,253.8L408.8,255.2L408.8,262.0L410.3,262.6L410.1,264.2L411.5,264.7L411.9,265.9","eastern-us|western-us":"M253.3,326.2L267.7,326.2L268.0,303.7L301.5,303.6L301.5,293.8L299.8,292.0L300.3,290.4L298.6,289.7L297.4,288.0L295.7,281.1L293.5,276.7L294.2,274.9L293.6,272.9L294.2,272.8L294.2,264.1L292.6,262.6L293.8,259.9L291.1,245.4L292.1,245.4M255.1,327.8L253.3,326.2","egypt|middle-east":"M815.8,329.4L816.8,329.3L819.6,337.4L817.7,342.7","egypt|north-africa":"M717.2,334.6L719.6,333.4L720.9,331.6L720.5,328.6L721.1,327.6L726.0,324.4L726.0,321.0L727.0,321.5M775.9,378.7L775.9,379.8L744.0,363.2L736.9,366.7L733.9,364.4L727.9,362.9L726.0,359.6L721.3,358.6L717.7,351.7L719.5,349.7L719.0,346.7L719.7,344.7L719.2,338.7L717.2,334.6","india|middle-east":"M927.4,355.8L926.3,355.8L927.4,351.4L929.8,350.0L932.6,349.6L933.2,347.5L931.0,346.9L931.0,342.7L927.6,341.4L923.4,335.7","india|siam":"M1068.4,342.3L1069.4,343.8L1067.5,345.5L1068.4,347.6L1066.9,346.7L1064.8,347.0L1060.5,349.8L1060.5,352.2L1058.3,355.3L1058.8,356.4L1056.5,361.4L1053.3,360.6L1053.6,363.7L1052.3,366.3L1052.6,368.4L1051.9,369.3L1050.8,368.8L1050.3,369.4L1050.4,372.4L1048.8,372.0L1049.3,374.4L1048.2,371.4","indonesia|siam":"M1117.4,451.7L1118.5,450.6L1118.6,452.3L1122.0,455.3L1127.2,454.7L1129.9,452.5L1131.8,452.5L1134.5,453.8L1135.6,453.0L1138.2,452.9L1139.3,450.8L1139.1,449.7L1140.7,448.6L1140.5,447.2L1141.8,446.6L1142.3,443.0L1143.4,441.3L1150.3,442.0L1149.9,442.4L1150.4,443.0M1138.0,439.6L1140.2,438.7L1141.3,441.2L1140.4,441.2L1140.0,439.2L1139.0,439.8L1139.4,441.2L1138.4,442.6L1136.3,440.4","irkutsk|kamchatka":"M1158.8,202.1L1159.6,202.6L1158.8,203.3L1159.2,204.2L1162.1,206.0L1160.2,207.2L1161.8,208.4L1164.9,207.6L1165.7,210.6L1167.5,209.9L1167.8,213.2L1166.6,214.6L1168.5,216.2L1166.7,216.8L1166.6,218.7L1167.7,219.7L1168.1,221.8L1167.1,222.0","irkutsk|mongolia":"M1167.9,221.0L1168.1,221.8L1163.9,222.7L1160.2,225.7L1162.6,226.6L1163.0,229.1L1160.3,231.8L1156.7,238.0L1157.4,238.7L1156.6,240.1L1151.3,242.7L1144.9,240.1L1141.7,240.7L1137.2,238.7L1131.2,242.7L1122.5,244.7L1114.1,243.6L1111.9,242.0L1111.7,240.5L1108.9,240.2L1106.8,238.5L1101.5,237.7L1094.5,239.5L1090.7,238.2L1089.2,237.1L1088.4,233.1L1079.7,230.9L1075.0,228.2","irkutsk|siberia":"M1062.6,217.2L1064.2,215.7L1066.0,215.9L1066.8,213.7L1066.3,213.1L1067.6,211.4L1067.0,209.1L1071.5,205.3L1071.0,203.2L1069.9,203.0L1069.4,201.6L1071.7,197.3L1075.9,197.8L1080.1,195.7L1081.7,198.9L1083.6,199.4L1083.4,197.1L1090.1,192.0L1089.8,189.5L1092.0,188.5L1097.8,192.7L1099.4,192.1L1099.0,191.4L1101.4,189.7L1099.2,186.3L1101.5,185.5L1101.9,183.0L1098.7,182.0L1097.8,179.4L1100.0,177.2L1099.6,176.2L1103.3,174.5L1103.6,173.3L1105.6,172.1L1105.6,170.4L1106.9,168.7L1104.5,165.8L1106.8,163.8L1106.1,163.0L1107.1,160.8L1106.7,160.2L1113.0,159.8L1112.9,158.5L1112.1,158.3L1113.8,158.0M1076.7,229.2L1074.6,228.0L1075.7,224.7L1076.9,224.9L1076.1,223.6L1073.2,223.7L1069.3,221.0L1066.0,220.5L1062.6,217.2","irkutsk|yakutsk":"M1113.0,159.2L1112.9,158.5L1112.1,158.3L1113.9,157.9L1115.1,160.6L1113.2,161.1L1112.5,162.3L1116.9,163.3L1118.5,166.9L1117.3,169.3L1120.0,170.6L1118.4,173.1L1119.4,175.0L1119.2,176.4L1122.1,177.9L1119.0,183.0L1117.1,188.5L1118.6,190.0L1121.1,190.5L1122.9,188.8L1126.5,189.1L1128.9,187.4L1130.5,188.5L1129.9,190.8L1133.8,188.8L1134.4,187.0L1140.5,182.1L1142.7,181.3L1146.3,182.3L1149.2,184.3L1148.3,185.0L1148.9,185.6L1148.5,187.0L1151.6,187.7L1153.3,186.9L1154.9,187.9L1155.3,192.8L1156.5,193.5L1156.6,198.8L1157.9,198.6L1158.6,201.9L1159.6,202.6L1158.8,203.3L1158.8,203.3","kamchatka|mongolia":"M1203.7,277.1L1202.9,277.0L1202.7,278.7L1201.7,276.7L1204.3,275.8L1205.0,273.5L1205.0,270.1L1203.9,266.3L1207.4,264.0L1211.7,265.4L1212.4,264.9L1212.7,263.1L1215.4,259.4L1216.7,254.1L1219.0,252.0L1218.3,250.4L1218.7,249.2L1217.2,248.6L1212.6,250.0L1209.9,252.0L1203.8,252.0L1202.2,246.1L1200.8,245.9L1198.0,243.4L1192.0,242.4L1190.2,241.2L1190.3,239.1L1189.3,238.3L1189.2,236.5L1187.7,234.4L1185.3,227.6L1182.6,224.0L1174.4,221.3L1168.1,221.8L1167.9,221.0","kamchatka|yakutsk":"M1158.7,203.3L1161.2,202.0L1165.6,202.1L1166.5,203.4L1175.7,205.5L1177.8,208.2L1183.0,208.8L1184.3,210.0L1190.7,209.4L1195.9,210.7L1196.2,209.6L1198.6,209.1L1203.6,209.4L1204.7,205.0L1207.2,203.6L1204.7,200.6L1206.3,200.3L1206.8,198.8L1208.1,198.1L1205.9,195.2L1208.1,196.0L1208.5,193.4L1210.2,192.5L1210.4,191.0L1212.5,189.2L1220.8,189.6L1223.2,187.2L1227.0,188.3L1230.5,186.0L1232.9,186.2L1233.0,181.5L1233.7,180.3L1232.8,178.7L1234.9,176.4L1238.2,175.5L1242.8,168.7L1249.7,173.1L1251.7,172.9L1252.5,171.7L1257.0,173.8L1260.7,170.5L1260.5,169.3L1261.8,168.9L1261.0,164.9L1262.5,161.9L1262.3,160.8L1264.4,160.1L1265.5,158.5L1270.4,158.7L1271.8,160.1L1273.0,158.2L1272.1,157.0L1273.2,156.2L1274.5,157.1L1279.3,156.1L1280.9,158.4L1282.5,158.6L1282.5,157.6L1288.1,156.5L1288.7,157.4L1290.9,155.4L1290.0,153.7L1290.8,151.8L1294.0,150.7L1294.0,147.8L1297.0,147.8L1297.6,145.4L1305.4,146.3L1306.6,145.5L1308.5,147.3L1313.8,145.9L1313.5,143.7L1315.6,141.3L1314.8,139.7L1311.2,137.6L1311.2,135.3L1313.2,135.0L1312.4,134.4L1313.1,132.7L1314.5,132.3L1325.6,130.4L1329.7,131.1L1330.8,129.1L1330.4,127.8L1331.3,127.3L1330.2,126.4L1331.0,124.8L1329.3,122.5L1329.6,121.7L1330.6,121.6","middle-east|southern-europe":"M784.2,286.3L786.5,283.1L785.3,281.3L788.0,279.9L792.1,280.3L792.4,281.3","middle-east|ukraine":"M841.2,274.1L839.9,273.3L840.6,272.6L846.3,274.3L851.0,274.5L855.1,276.6L855.3,277.4L858.0,276.6L860.6,276.9L862.8,277.8L862.6,279.2L867.0,281.1L870.1,283.9L871.4,283.9L874.3,280.9L874.9,281.8","mongolia|siberia":"M1031.2,245.6L1032.8,243.0L1036.4,242.8L1040.2,239.7L1049.4,235.6L1052.4,237.0L1057.0,237.3L1058.5,240.0L1064.3,240.2L1069.4,241.5L1073.0,238.6L1073.1,237.4L1071.3,234.7L1072.2,232.5L1075.6,229.0L1076.7,229.3","northern-europe|scandinavia":"M729.3,207.0L730.4,207.5L730.6,208.9","northern-europe|southern-europe":"M769.0,247.4L768.4,248.5L762.6,247.7L759.6,249.8L755.6,250.3L754.9,251.6L751.0,251.7L748.3,250.3L747.5,246.9L740.0,245.4L738.8,247.4L735.3,246.6L734.9,247.7L733.9,247.5L733.5,248.7L731.3,249.7L732.1,253.2L728.9,252.0L724.2,253.6L721.8,252.8L720.7,254.2L719.4,252.8L714.3,251.6L713.7,252.6L710.3,252.5L710.3,251.9","northern-europe|ukraine":"M764.8,212.8L764.9,211.8L765.6,211.7L770.3,212.9L771.3,214.0L771.0,216.7L773.8,218.0L775.4,223.6L775.6,225.8L772.7,228.1L774.6,229.4L774.2,231.7L776.4,235.7L775.9,238.0L770.6,242.5L771.4,245.0L770.2,245.0L768.4,248.5L767.5,248.4","northern-europe|western-europe":"M711.5,252.5L710.3,252.5L710.5,249.7L712.5,245.5L706.9,244.5L705.0,242.8L702.0,242.7L699.5,241.3L699.3,239.4L696.7,240.4L696.7,238.9L694.8,238.6L692.4,236.1L691.0,236.2L690.1,234.4","northwest-territory|ontario":"M301.0,183.4L300.9,184.4L271.0,184.4","ontario|quebec":"M360.9,234.6L361.8,235.2L361.1,233.9L361.9,232.0L361.8,253.2L362.5,255.5L364.9,258.6L369.3,259.7L371.9,261.6L372.7,261.2L373.8,262.9L376.4,263.5L381.7,262.4L382.4,262.8L382.6,264.5L380.3,266.0","ontario|western-us":"M292.1,245.4L274.5,245.4L274.5,244.4","peru|venezuela":"M364.4,453.9L364.4,453.9L364.6,452.9L369.2,455.4L370.4,457.2L374.0,457.8L374.9,457.0L376.9,458.4L380.8,459.5L383.0,462.6L385.3,463.7L388.2,468.3L391.1,468.3L393.0,467.3L394.4,468.1L396.1,467.6L399.6,469.4L397.1,473.8L399.0,474.3L400.1,476.0L398.9,475.8","scandinavia|ukraine":"M802.5,120.6L803.5,120.8L803.7,122.1L800.8,121.8L800.0,123.6L793.8,127.1L795.1,127.4L793.9,129.8L794.7,131.9L799.9,135.4L796.3,140.8L800.4,148.2L799.0,149.2L798.4,151.7L799.3,152.4L798.4,153.5L800.4,155.0L800.0,156.4L802.1,159.2L800.0,161.5L806.0,166.4L805.1,168.8L791.2,181.2L790.2,181.4","siberia|ural":"M980.3,194.2L981.2,193.5L980.6,192.4L982.7,190.2L982.5,189.0L986.6,186.9L987.1,181.7L988.1,181.3L988.5,179.3L996.7,179.4L997.5,180.4L1002.9,179.7L1008.6,181.2L1012.6,178.2L1017.0,179.3L1023.8,175.5L1018.7,173.4L1017.7,171.2L1022.1,166.5L1022.5,163.8L1020.4,162.7L1023.8,159.4L1023.4,154.9L1017.1,153.7L1018.2,150.5L1016.1,148.1L1014.1,147.9L1013.2,146.4L1014.0,145.5L1012.4,143.7L1013.1,142.3L1012.7,141.3L1008.5,138.4L1009.5,136.5L1006.9,133.9L1009.4,133.6L1009.6,132.0L1010.7,131.3L1010.1,129.0L1011.9,128.5L1010.2,127.4L1009.7,125.4L1007.1,124.7L1007.6,123.9L1007.1,123.3L1003.9,124.9L999.4,123.8L995.7,120.2L1003.0,114.7L1002.2,111.7L996.6,108.9L997.5,107.5L1000.4,107.1L1000.1,105.9L1001.1,105.3L994.8,103.3L993.9,101.7L995.1,101.7M1016.7,237.8L1016.3,236.9L1018.1,236.6L1018.4,235.7L1016.1,234.2L1021.7,231.9L1023.8,229.2L1027.0,229.0L1027.2,228.1L1026.5,227.6L1027.4,226.1L1028.7,225.9L1027.8,225.0L1028.3,223.8L1026.8,223.2L1027.8,221.8L1019.8,215.4L1020.3,214.0L1018.1,207.4L1015.8,207.6L1013.3,209.4L1013.6,208.2L1012.5,206.9L1012.9,205.1L1012.4,204.6L1006.2,206.2L1004.5,204.6L1001.0,205.1L998.4,202.4L993.7,201.1L984.4,200.5L982.3,198.1L982.2,196.6L980.4,195.6L980.8,194.9L980.3,194.2","siberia|yakutsk":"M1122.4,91.9L1123.3,91.6L1124.4,92.2L1122.1,94.0L1123.5,96.9L1122.5,98.8L1124.8,100.5L1124.5,101.9L1128.0,103.6L1128.0,108.9L1130.3,111.2L1120.5,114.7L1120.5,115.8L1117.2,117.8L1118.0,119.0L1116.9,120.8L1113.2,120.3L1104.7,123.5L1107.5,127.3L1107.2,137.7L1102.4,140.0L1105.0,142.1L1105.3,143.6L1104.4,144.3L1105.9,146.7L1105.8,148.9L1107.8,150.0L1104.0,153.2L1104.5,153.9L1102.9,155.7L1104.8,157.2L1112.9,158.4L1113.2,159.2M1125.8,89.4L1123.2,91.2L1122.2,91.2","southern-europe|ukraine":"M767.5,248.2L768.9,248.4L771.5,250.8L772.6,250.1L777.9,250.8L779.6,251.9L787.1,249.2L792.3,255.7L793.0,257.4L792.4,262.6L792.9,263.3L795.0,264.4L797.6,263.5L798.8,264.3L798.5,265.2","southern-europe|western-europe":"M703.9,259.5L708.2,253.1L711.4,252.8M711.1,271.3L710.0,271.6L710.6,269.7L707.6,268.8L708.0,266.4L706.5,265.2L708.6,263.6L707.2,261.9L708.1,260.9L707.1,258.6L703.9,259.5","ukraine|ural":"M856.7,145.0L856.9,144.0L868.4,147.9L870.7,148.0L872.6,145.9L876.0,146.0L876.0,151.6L878.7,151.6L881.9,154.7L881.4,156.3L875.8,156.6L873.6,158.3L866.5,157.7L861.9,158.7L867.3,161.4L868.1,162.7L867.8,163.9L870.2,165.1L868.4,167.1L869.7,171.1L873.1,170.3L873.5,167.8L878.4,167.5L876.2,174.1L877.8,174.8L877.2,177.3L873.8,178.5L874.1,181.6L875.0,182.3L873.8,183.7L874.1,186.2L876.3,186.5L876.2,187.4L878.7,189.1L880.0,185.8L883.3,185.2L884.3,184.1L886.1,184.8L888.2,182.5L894.4,183.6L894.6,185.5L892.9,188.3L895.5,189.8L894.9,190.8L895.7,191.7L894.8,192.7L895.1,193.7L891.7,193.1L887.2,194.1L886.8,195.2L887.7,197.3L886.4,199.4L884.7,199.5L884.9,201.6L886.2,202.6L884.6,204.7L885.7,206.1L885.8,208.0L888.3,208.2L888.8,207.2L891.2,207.1L890.2,206.2L892.3,204.7L891.9,206.3L893.1,207.1L893.0,206.2L894.2,206.3L893.2,208.4L896.7,209.3L894.4,212.1L892.6,212.5L894.4,214.8L893.4,216.7L893.5,218.9L891.1,216.9L889.1,217.0L889.9,218.4L888.4,221.5L888.2,224.2L886.2,226.0L885.7,229.1L882.6,231.9"},"wrap":{"alaska":[7.6,149.0],"kamchatka":[1441.1,146.4]}} diff --git a/src/game/bot.ts b/src/game/bot.ts new file mode 100644 index 0000000..1088a89 --- /dev/null +++ b/src/game/bot.ts @@ -0,0 +1,268 @@ +import { setsIn, type Card } from './cards' +import { winChance } from './dice' +import type { Game } from './game' +import { + ADJACENT, + BORDERS, + CONTINENTS, + CONTINENT_IDS, + TERRITORIES, + inContinent, + type Continent, + type Territory, +} from './map' +import { makeRng } from './rng' + +/** + * The five opponents. + * + * A bot keeps no plan between moves. Every time it is asked, it looks at the + * board and answers with the one thing it would do next -- trade, place, + * throw the dice, march in, move up, or stop -- so it can never be caught + * holding a plan the dice have already made nonsense of, and a game can be + * stopped and resumed anywhere without anybody's intentions going stale. + * + * What it wants comes down to three things, in this order: a continent it + * can hold, a card for the turn, and nobody else holding a continent in + * peace. Every attack is priced as what winning is worth times the exact + * chance of winning it, from `dice.ts` -- not a rule of thumb about armies. + * A player watching one of these turn on them is owed a reason, and "it had + * a 71% chance at Brazil and Brazil finished South America" is one. + */ + +export type Action = + | { type: 'place'; territory: Territory; count: number } + | { type: 'trade'; cards: [number, number, number] } + | { type: 'beginAttack' } + | { type: 'attack'; from: Territory; to: Territory } + | { type: 'occupy'; count: number } + | { type: 'endAttack' } + | { type: 'fortify'; from: Territory; to: Territory; count: number } + | { type: 'endTurn' } + +export interface Leaning { + /** 0 is careful, 1 is reckless. Moves the odds a bot will fight at. */ + nerve: number + /** A small private taste for some continents over others. */ + taste: Record +} + +/** + * Six minds reasoning identically play the same game every time. Each one + * leans a little -- braver or more careful, fonder of one continent -- and + * the lean comes from the seed, so a seed still replays a game exactly. + */ +export function leanings(seed: number, players: number): Leaning[] { + return Array.from({ length: players }, (_, id) => { + const rng = makeRng(seed * 31 + id * 7919 + 1) + return { + nerve: rng(), + taste: Object.fromEntries(CONTINENT_IDS.map((c) => [c, 0.85 + rng() * 0.3])) as Record< + Continent, + number + >, + } + }) +} + +// ------------------------------------------------------------ appraisal + +/** How hard each continent would be to take from here, and what it pays. */ +export function goal(g: Game, p: number, lean: Leaning): Continent | null { + let best: Continent | null = null + let bestScore = -Infinity + for (const c of CONTINENT_IDS) { + const land = inContinent(c) + const mine = land.filter((t) => g.owner[t] === p) + if (mine.length === land.length) continue + if (mine.length === 0 && !land.some((t) => ADJACENT[t].some((n) => g.owner[n] === p))) continue + const theirs = land.filter((t) => g.owner[t] !== p).reduce((n, t) => n + g.armies[t], 0) + const ours = mine.reduce((n, t) => n + g.armies[t], 0) + const held = mine.length / land.length + // What it pays, made cheaper by what is already there and dearer by the + // opposition and by every door that will need watching afterwards. + const score = + (CONTINENTS[c].bonus * (1 + held) ** 2 * lean.taste[c] * (1 + ours / 10)) / + ((theirs + 1) * Math.sqrt(BORDERS[c].length)) + if (score > bestScore) { + bestScore = score + best = c + } + } + return best +} + +const enemies = (g: Game, t: Territory) => ADJACENT[t].filter((n) => g.owner[n] !== g.owner[t]) + +const threat = (g: Game, t: Territory) => enemies(g, t).reduce((n, e) => n + g.armies[e], 0) + +/** What taking `t` would be worth to `p`, before the odds. */ +export function worth(g: Game, p: number, t: Territory, target: Continent | null): number { + const c = TERRITORIES[t].continent + const holder = g.owner[t] + const land = inContinent(c) + let v = 1 + if (c === target) v += 3 + // The last one in a continent pays out every turn. + if (land.every((x) => x === t || g.owner[x] === p)) v += CONTINENTS[c].bonus * 2 + // Somebody else's continent stops paying the moment one territory goes. + if (land.every((x) => g.owner[x] === holder)) v += CONTINENTS[c].bonus * 1.5 + // Finishing a player off hands over their cards. + const left = g.territoriesOf(holder).length + if (left <= 2) v += 2 + g.players[holder].cards.length * 2 + // A territory taken this turn is a card at the end of it; the first one + // is worth more than any after. + if (!g.conquered) v += 2 + return v +} + +interface Plan { + from: Territory + to: Territory + odds: number + value: number +} + +function bestAttack(g: Game, p: number, lean: Leaning): Plan | null { + const target = goal(g, p, lean) + // The odds a bot will fight at: between about 55% and 75%, by nerve. For + // the first territory of the turn it will go lower, because the card is + // on the line and a card is armies later. + const floor = 0.75 - lean.nerve * 0.2 + let best: Plan | null = null + for (const from of g.territoriesOf(p)) { + if (g.armies[from] < 2) continue + for (const to of enemies(g, from)) { + const odds = winChance(g.armies[from] - 1, g.armies[to]) + const needed = g.conquered ? floor : floor - 0.1 + if (odds < needed) continue + const value = worth(g, p, to, target) * odds + if (!best || value > best.value) best = { from, to, odds, value } + } + } + return best +} + +/** Where the next army does most good: somewhere to attack from, or a door to hold. */ +function stagingPoint(g: Game, p: number, lean: Leaning): Territory { + const target = goal(g, p, lean) + const held = g.continentsOf(p) + let best: Territory | null = null + let bestScore = -Infinity + for (const t of g.territoriesOf(p)) { + const foes = enemies(g, t) + if (foes.length === 0) continue + let score = 0 + // Attack: the best thing next door, cheapest to take. + for (const n of foes) { + const s = worth(g, p, n, target) / (1 + g.armies[n]) + if (s > score) score = s + } + // Defence: a door of a continent we hold, outnumbered. + const door = held.some((c) => BORDERS[c].includes(t)) + if (door) score += Math.max(0, threat(g, t) - g.armies[t]) * 0.4 + 1 + // Pile up rather than spread thin: a stack attacks, a scatter does not. + score += Math.min(g.armies[t], 12) * 0.05 + if (score > bestScore) { + bestScore = score + best = t + } + } + return best ?? g.territoriesOf(p)[0] +} + +/** The set that pays most: one showing a territory we hold, and wild cards kept if possible. */ +function bestSet(g: Game, p: number, hand: readonly Card[]): [number, number, number] | null { + const sets = setsIn(hand) + if (sets.length === 0) return null + const score = (s: [number, number, number]) => + s.reduce((n, i) => { + const c = hand[i] + if (c.mark === 'wild') return n - 1 + return n + (c.territory && g.owner[c.territory] === p ? 2 : 0) + }, 0) + return sets.reduce((a, b) => (score(b) > score(a) ? b : a)) +} + +// ------------------------------------------------------------- deciding + +export function decide(g: Game, lean: Leaning): Action { + const p = g.current + + switch (g.phase) { + case 'setup': + return { type: 'place', territory: stagingPoint(g, p, lean), count: 1 } + + case 'reinforce': { + // Trading now rather than saving up: the value rises for everybody + // whether or not we wait, and armies now are armies on the board. + const set = bestSet(g, p, g.hand) + if (set && (g.mustTrade || g.reinforcements > 0 || g.resume)) return { type: 'trade', cards: set } + if (g.reinforcements > 0) { + // Most on the best point, and the rest reconsidered, so a big + // income can cover a threatened door as well as a push. + const count = g.reinforcements <= 3 ? g.reinforcements : Math.ceil(g.reinforcements * 0.7) + return { type: 'place', territory: stagingPoint(g, p, lean), count } + } + return { type: 'beginAttack' } + } + + case 'attack': { + const plan = bestAttack(g, p, lean) + return plan ? { type: 'attack', from: plan.from, to: plan.to } : { type: 'endAttack' } + } + + case 'occupy': { + const o = g.occupation! + const behind = threat(g, o.from) + const ahead = threat(g, o.to) + let count: number + if (behind === 0) count = o.max + else if (ahead === 0) count = o.min + else count = Math.round(o.max * (ahead / (ahead + behind * 0.6))) + return { type: 'occupy', count: Math.max(o.min, Math.min(o.max, count)) } + } + + case 'fortify': { + // The biggest idle stack -- one with no enemy beside it -- walks to the + // front that needs it most. + const idle = g + .territoriesOf(p) + .filter((t) => g.armies[t] > 1 && enemies(g, t).length === 0) + .sort((a, b) => g.armies[b] - g.armies[a]) + for (const from of idle) { + const fronts = g.connected(from).filter((t) => enemies(g, t).length > 0) + if (fronts.length === 0) continue + const to = fronts.reduce((a, b) => + threat(g, b) - g.armies[b] > threat(g, a) - g.armies[a] ? b : a, + ) + return { type: 'fortify', from, to, count: g.armies[from] - 1 } + } + return { type: 'endTurn' } + } + + case 'over': + throw new Error('the game is over') + } +} + +export function apply(g: Game, a: Action) { + switch (a.type) { + case 'place': + return g.place(a.territory, a.count) + case 'trade': + return g.trade(a.cards) + case 'beginAttack': + return g.beginAttack() + case 'attack': + return g.attack(a.from, a.to) + case 'occupy': + return g.occupy(a.count) + case 'endAttack': + return g.endAttack() + case 'fortify': + return g.fortify(a.from, a.to, a.count) + case 'endTurn': + return g.endTurn() + } +} diff --git a/src/game/cards.test.ts b/src/game/cards.test.ts new file mode 100644 index 0000000..148d102 --- /dev/null +++ b/src/game/cards.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest' +import { fullDeck, isSet, setValue, setsIn, type Card } from './cards' + +const c = (mark: Card['mark']): Card => ({ territory: null, mark }) + +describe('the deck', () => { + it('is forty-two territories, fourteen of each mark, and two wild', () => { + const deck = fullDeck() + expect(deck).toHaveLength(44) + for (const m of ['infantry', 'cavalry', 'artillery'] as const) + expect(deck.filter((x) => x.mark === m)).toHaveLength(14) + expect(deck.filter((x) => x.mark === 'wild')).toHaveLength(2) + }) +}) + +describe('sets', () => { + it('are three alike, one of each, or anything with a wild', () => { + expect(isSet([c('infantry'), c('infantry'), c('infantry')])).toBe(true) + expect(isSet([c('infantry'), c('cavalry'), c('artillery')])).toBe(true) + expect(isSet([c('infantry'), c('infantry'), c('wild')])).toBe(true) + expect(isSet([c('infantry'), c('infantry'), c('cavalry')])).toBe(false) + }) + + it('five cards always hold one', () => { + const marks = ['infantry', 'cavalry', 'artillery'] as const + for (let a = 0; a < 3; a++) + for (let b = 0; b < 3; b++) + for (let d = 0; d < 3; d++) + for (let e = 0; e < 3; e++) + for (let f = 0; f < 3; f++) + expect(setsIn([a, b, d, e, f].map((i) => c(marks[i]))).length).toBeGreaterThan(0) + }) + + it('are worth more each time anybody trades one', () => { + expect([0, 1, 2, 3, 4, 5, 6, 7, 8].map(setValue)).toEqual([4, 6, 8, 10, 12, 15, 20, 25, 30]) + }) +}) diff --git a/src/game/cards.ts b/src/game/cards.ts new file mode 100644 index 0000000..33f2672 --- /dev/null +++ b/src/game/cards.ts @@ -0,0 +1,56 @@ +import { TERRITORY_IDS, type Territory } from './map' + +/** + * The deck: one card per territory, each carrying one of three marks, and two + * wild cards that stand for any of them. + * + * The marks are ours. The published game draws a soldier, a horseman and a + * cannon; what matters to the rules is only that there are three kinds, + * fourteen of each, so they are named for what they are and drawn as plain + * shapes. + */ + +export type Mark = 'infantry' | 'cavalry' | 'artillery' + +export interface Card { + /** Null for a wild card. */ + territory: Territory | null + mark: Mark | 'wild' +} + +const MARKS: Mark[] = ['infantry', 'cavalry', 'artillery'] + +export function fullDeck(): Card[] { + const cards: Card[] = TERRITORY_IDS.map((territory, i) => ({ territory, mark: MARKS[i % 3] })) + cards.push({ territory: null, mark: 'wild' }, { territory: null, mark: 'wild' }) + return cards +} + +/** Three of a kind, one of each, or anything with a wild card in it. */ +export function isSet(cards: readonly Card[]): boolean { + if (cards.length !== 3) return false + if (cards.some((c) => c.mark === 'wild')) return true + const marks = new Set(cards.map((c) => c.mark)) + return marks.size === 1 || marks.size === 3 +} + +/** + * What the nth set traded in by anybody is worth: 4, 6, 8, 10, 12, 15, and + * five more each time after that. The count is shared by the whole table, + * which is why the value of holding cards rises while nobody else trades. + */ +export function setValue(tradesSoFar: number): number { + const early = [4, 6, 8, 10, 12, 15] + if (tradesSoFar < early.length) return early[tradesSoFar] + return 15 + 5 * (tradesSoFar - early.length + 1) +} + +/** Every set that can be made from a hand, as index triples. */ +export function setsIn(hand: readonly Card[]): [number, number, number][] { + const out: [number, number, number][] = [] + for (let i = 0; i < hand.length; i++) + for (let j = i + 1; j < hand.length; j++) + for (let k = j + 1; k < hand.length; k++) + if (isSet([hand[i], hand[j], hand[k]])) out.push([i, j, k]) + return out +} diff --git a/src/game/describe.test.ts b/src/game/describe.test.ts new file mode 100644 index 0000000..26e9b1a --- /dev/null +++ b/src/game/describe.test.ts @@ -0,0 +1,32 @@ +import { describe as suite, expect, it } from 'vitest' +import { describe } from './describe' +import type { GameEvent } from './game' + +const throwOf = (attackerLoss: number, defenderLoss: number): GameEvent => ({ + kind: 'battle', + player: 1, + from: 'peru', + to: 'brazil', + defender: 0, + roll: { attack: [], defend: [], attackerLoss, defenderLoss }, +}) + +suite('the log', () => { + it('folds a run of throws into one line', () => { + const lines = describe([throwOf(1, 1), throwOf(2, 0), { kind: 'card', player: 1 }], 0) + expect(lines.map((l) => l.text)).toEqual(['Cobalt attacked Brazil from Peru: lost 3, killed 1.']) + }) + + it('lets a conquest speak for the fight that won it', () => { + const lines = describe( + [throwOf(0, 2), throwOf(1, 1), { kind: 'conquer', player: 1, from: 'peru', to: 'brazil', loser: 0 }], + 0, + ) + expect(lines.map((l) => l.text)).toEqual(['Cobalt took Brazil from you (lost 1, killed 3).']) + }) + + it('speaks to the player in the second person', () => { + const lines = describe([{ kind: 'eliminate', player: 3, loser: 0, cards: 2 }], 0) + expect(lines[0].text).toBe('You are out, finished by Jade, who takes 2 cards.') + }) +}) diff --git a/src/game/describe.ts b/src/game/describe.ts new file mode 100644 index 0000000..13f3c04 --- /dev/null +++ b/src/game/describe.ts @@ -0,0 +1,106 @@ +import type { GameEvent } from './game' +import { TERRITORIES, type Territory } from './map' + +/** + * The log, in words. + * + * A game of this throws the dice thousands of times and nobody wants to read + * a line for each throw. So consecutive throws between the same two + * territories fold into one line saying how the fight went, and the log + * reads like an account of the war rather than a transcript of the dice. + */ + +export const PLAYER_NAMES = ['Crimson', 'Cobalt', 'Saffron', 'Jade', 'Violet', 'Ash'] as const + +export interface Line { + /** Whose line it is, for the color chip. */ + player: number + text: string + /** A new turn starts a new paragraph. */ + heading?: boolean +} + +const name = (t: Territory) => TERRITORIES[t].name + +export function describe(events: readonly GameEvent[], human: number | null): Line[] { + const who = (p: number) => (p === human ? 'You' : PLAYER_NAMES[p]) + const whom = (p: number) => (p === human ? 'you' : PLAYER_NAMES[p]) + const lines: Line[] = [] + let fight: { player: number; from: Territory; to: Territory; lost: number; killed: number; defender: number } | null = + null + + const closeFight = (taken: boolean) => { + if (!fight) return + const f = fight + fight = null + if (taken) return // the conquest line says it better + lines.push({ + player: f.player, + text: `${who(f.player)} attacked ${name(f.to)} from ${name(f.from)}: lost ${f.lost}, killed ${f.killed}.`, + }) + } + + for (const e of events) { + if (e.kind !== 'battle' && e.kind !== 'conquer') closeFight(false) + switch (e.kind) { + case 'turn': + lines.push({ + player: e.player, + heading: true, + text: `Round ${e.round}: ${who(e.player)} ${e.player === human ? 'have' : 'has'} ${ + e.reinforcements + } to place.`, + }) + break + case 'battle': + if (fight && (fight.from !== e.from || fight.to !== e.to)) closeFight(false) + fight ??= { player: e.player, from: e.from, to: e.to, lost: 0, killed: 0, defender: e.defender } + fight.lost += e.roll.attackerLoss + fight.killed += e.roll.defenderLoss + break + case 'conquer': { + const f = fight + closeFight(true) + const cost = f ? ` (lost ${f.lost}, killed ${f.killed})` : '' + lines.push({ + player: e.player, + text: `${who(e.player)} took ${name(e.to)} from ${whom(e.loser)}${cost}.`, + }) + break + } + case 'trade': + lines.push({ + player: e.player, + text: `${who(e.player)} traded a set for ${e.armies}${ + e.bonus.length ? `, and 2 more on ${name(e.bonus[0])}` : '' + }.`, + }) + break + case 'eliminate': + lines.push({ + player: e.player, + text: `${who(e.loser)} ${e.loser === human ? 'are' : 'is'} out, finished by ${whom(e.player)}${ + e.cards ? `, who takes ${e.cards} card${e.cards === 1 ? '' : 's'}` : '' + }.`, + }) + break + case 'fortify': + lines.push({ + player: e.player, + text: `${who(e.player)} moved ${e.count} from ${name(e.from)} to ${name(e.to)}.`, + }) + break + case 'win': + lines.push({ + player: e.player, + heading: true, + text: `${who(e.player)} ${e.player === human ? 'hold' : 'holds'} the world.`, + }) + break + default: + break + } + } + closeFight(false) + return lines +} diff --git a/src/game/dice.test.ts b/src/game/dice.test.ts new file mode 100644 index 0000000..1d85094 --- /dev/null +++ b/src/game/dice.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from 'vitest' +import { OUTCOMES, compare, roll, winChance } from './dice' +import { makeRng } from './rng' + +describe('one throw', () => { + it('matches highest against highest, and a tie goes to the defender', () => { + expect(compare([6, 1, 3], [6, 2])).toMatchObject({ attackerLoss: 1, defenderLoss: 1 }) + expect(compare([5, 5, 5], [4, 4])).toMatchObject({ attackerLoss: 0, defenderLoss: 2 }) + expect(compare([2], [2, 1])).toMatchObject({ attackerLoss: 1, defenderLoss: 0 }) + }) + + it('rolls the dice it was asked for', () => { + const r = roll(makeRng(1), 3, 2) + expect(r.attack).toHaveLength(3) + expect(r.defend).toHaveLength(2) + expect(r.attackerLoss + r.defenderLoss).toBe(2) + }) +}) + +describe('the odds', () => { + // The figures everybody quotes, to four places. + it('agrees with the published exchange tables', () => { + const three = OUTCOMES['3v2'] + expect(three.get(0)).toBeCloseTo(2890 / 7776, 10) + expect(three.get(1)).toBeCloseTo(2611 / 7776, 10) + expect(three.get(2)).toBeCloseTo(2275 / 7776, 10) + expect(OUTCOMES['1v1'].get(0)).toBeCloseTo(15 / 36, 10) + expect(OUTCOMES['3v1'].get(0)).toBeCloseTo(855 / 1296, 10) + expect(OUTCOMES['2v2'].get(0)).toBeCloseTo(295 / 1296, 10) + }) + + it('knows what a whole battle is worth', () => { + expect(winChance(1, 1)).toBeCloseTo(0.4167, 3) + expect(winChance(3, 2)).toBeCloseTo(0.6560, 3) + // Two against one: win the first exchange, or lose it and win one-on-one. + expect(winChance(2, 1)).toBeCloseTo(125 / 216 + (91 / 216) * (15 / 36), 12) + expect(winChance(10, 10)).toBeGreaterThan(0.5) + expect(winChance(5, 0)).toBe(1) + expect(winChance(0, 3)).toBe(0) + }) + + it('agrees with the dice when they are actually thrown', () => { + const rng = makeRng(99) + let wins = 0 + const games = 20000 + for (let i = 0; i < games; i++) { + let a = 5 + let d = 4 + while (a > 0 && d > 0) { + const r = roll(rng, Math.min(3, a), Math.min(2, d)) + a -= r.attackerLoss + d -= r.defenderLoss + } + if (d === 0) wins++ + } + expect(wins / games).toBeCloseTo(winChance(5, 4), 1) + }) +}) diff --git a/src/game/dice.ts b/src/game/dice.ts new file mode 100644 index 0000000..9d1c7e4 --- /dev/null +++ b/src/game/dice.ts @@ -0,0 +1,99 @@ +import type { Rng } from './rng' + +/** + * The dice, and the arithmetic of them. + * + * An attacker rolls up to three, never more than the armies it has beyond + * the one that must stay home. A defender rolls up to two, never more than + * it has. Highest against highest, second against second; a tie goes to the + * defender, and each comparison costs the loser one army. + * + * The odds are worked out here rather than looked up. A table copied from + * somewhere is a table nobody can check; one computed from thirty-six and + * two hundred and sixteen and seven thousand seven hundred and seventy-six + * equally likely throws is its own proof, and the tests hold it to the + * figures every Risk player's cousin has quoted at them. + */ + +export interface Roll { + attack: number[] + defend: number[] + attackerLoss: number + defenderLoss: number +} + +const die = (rng: Rng) => 1 + Math.floor(rng() * 6) + +export function compare(attack: number[], defend: number[]) { + const a = [...attack].sort((x, y) => y - x) + const d = [...defend].sort((x, y) => y - x) + let attackerLoss = 0 + let defenderLoss = 0 + for (let i = 0; i < Math.min(a.length, d.length); i++) { + if (a[i] > d[i]) defenderLoss++ + else attackerLoss++ + } + return { attack: a, defend: d, attackerLoss, defenderLoss } +} + +export function roll(rng: Rng, attackDice: number, defendDice: number): Roll { + const attack = Array.from({ length: attackDice }, () => die(rng)) + const defend = Array.from({ length: defendDice }, () => die(rng)) + return compare(attack, defend) +} + +/** Every throw of `n` dice, each equally likely. */ +function throws(n: number): number[][] { + if (n === 0) return [[]] + const out: number[][] = [] + for (const rest of throws(n - 1)) for (let f = 1; f <= 6; f++) out.push([f, ...rest]) + return out +} + +/** + * For one exchange of `a` attacking and `d` defending dice: the chance of + * each outcome, keyed by how many armies the attacker loses. + */ +export const OUTCOMES: Record> = (() => { + const table: Record> = {} + for (let a = 1; a <= 3; a++) { + for (let d = 1; d <= 2; d++) { + const counts = new Map() + let total = 0 + for (const at of throws(a)) { + for (const de of throws(d)) { + const { attackerLoss } = compare(at, de) + counts.set(attackerLoss, (counts.get(attackerLoss) ?? 0) + 1) + total++ + } + } + for (const [k, v] of counts) counts.set(k, v / total) + table[`${a}v${d}`] = counts + } + } + return table +})() + +const memo = new Map() + +/** + * The chance that `attackers` armies -- the ones free to attack, not counting + * the one left behind -- take a territory held by `defenders`, if the attack + * is pressed with every die available until one side is gone. + */ +export function winChance(attackers: number, defenders: number): number { + if (defenders <= 0) return 1 + if (attackers <= 0) return 0 + const key = `${attackers},${defenders}` + const hit = memo.get(key) + if (hit !== undefined) return hit + const a = Math.min(3, attackers) + const d = Math.min(2, defenders) + const fights = Math.min(a, d) + let p = 0 + for (const [attackerLoss, chance] of OUTCOMES[`${a}v${d}`]) { + p += chance * winChance(attackers - attackerLoss, defenders - (fights - attackerLoss)) + } + memo.set(key, p) + return p +} diff --git a/src/game/game.test.ts b/src/game/game.test.ts new file mode 100644 index 0000000..2898cdd --- /dev/null +++ b/src/game/game.test.ts @@ -0,0 +1,128 @@ +import { describe, expect, it } from 'vitest' +import { Game, STARTING_ARMIES } from './game' +import { TERRITORY_IDS, type Territory } from './map' + +/** Place every setup army wherever it is legal, so a test can start at turn one. */ +function pastSetup(g: Game) { + while (g.phase === 'setup') g.place(g.territoriesOf(g.current)[0]) +} + +describe('the deal', () => { + it('shares the world out seven each, with twenty armies apiece', () => { + const g = new Game(1) + for (const p of g.players) expect(g.territoriesOf(p.id)).toHaveLength(7) + pastSetup(g) + for (const p of g.players) expect(g.armiesOf(p.id)).toBe(STARTING_ARMIES) + expect(g.phase).toBe('reinforce') + expect(g.current).toBe(g.first) + expect(g.round).toBe(1) + }) + + it('is the same deal for the same seed', () => { + const a = new Game(42) + const b = new Game(42) + expect(TERRITORY_IDS.map((t) => a.owner[t])).toEqual(TERRITORY_IDS.map((t) => b.owner[t])) + }) +}) + +describe('reinforcements', () => { + it('are a third of your territories, at least three, plus whole continents', () => { + const g = new Game(1) + const p = 0 + for (const t of TERRITORY_IDS) g.owner[t] = 1 + expect(g.income(p)).toBe(3) + for (const t of ['indonesia', 'new-guinea', 'western-australia', 'eastern-australia'] as Territory[]) + g.owner[t] = p + expect(g.income(p)).toBe(3 + 2) + for (const t of TERRITORY_IDS.slice(0, 15)) g.owner[t] = p + // 19 territories, North and South America and Australia. + expect(g.territoriesOf(p)).toHaveLength(19) + expect(g.income(p)).toBe(6 + 5 + 2 + 2) + }) +}) + +describe('a battle', () => { + function staged(attackers: number, defenders: number) { + const g = new Game(3) + pastSetup(g) + for (const t of TERRITORY_IDS) { + g.owner[t] = 1 + g.armies[t] = 1 + } + g.current = 0 + g.owner.brazil = 0 + g.armies.brazil = attackers + g.armies['north-africa'] = defenders + g.reinforcements = 0 + g.beginAttack() + return g + } + + it('refuses what the rules refuse', () => { + const g = staged(5, 2) + expect(() => g.attack('brazil', 'china')).toThrow(/border/) + expect(() => g.attack('peru', 'brazil')).toThrow(/not yours/) + g.armies.brazil = 1 + expect(() => g.attack('brazil', 'north-africa')).toThrow(/spare/) + }) + + it('takes a territory, marches in, and earns a card', () => { + const g = staged(40, 1) + while (g.phase === 'attack') g.attack('brazil', 'north-africa') + expect(g.phase).toBe('occupy') + expect(g.owner['north-africa']).toBe(0) + const o = g.occupation! + expect(o.min).toBe(3) + expect(() => g.occupy(o.min - 1)).toThrow() + g.occupy(o.max) + expect(g.armies.brazil).toBe(1) + g.endAttack() + g.endTurn() + expect(g.players[0].cards).toHaveLength(1) + }) + + it('hands over the loser\'s cards and forces a trade at six', () => { + const g = staged(40, 1) + for (const t of TERRITORY_IDS) if (t !== 'north-africa' && t !== 'brazil') g.owner[t] = 2 + g.players[0].cards = [ + { territory: 'china', mark: 'infantry' }, + { territory: 'peru', mark: 'cavalry' }, + { territory: 'egypt', mark: 'infantry' }, + ] + g.players[1].cards = [ + { territory: 'japan', mark: 'cavalry' }, + { territory: 'siam', mark: 'cavalry' }, + { territory: 'ural', mark: 'artillery' }, + ] + while (g.phase === 'attack') g.attack('brazil', 'north-africa') + expect(g.players[1].alive).toBe(false) + g.occupy(3) + expect(g.phase).toBe('reinforce') + expect(g.mustTrade).toBe(true) + expect(() => g.beginAttack()).toThrow(/trade/) + g.trade([0, 1, 5]) + expect(g.hand).toHaveLength(3) + g.place('brazil', g.reinforcements) + g.beginAttack() + expect(g.phase).toBe('attack') + }) +}) + +describe('fortifying', () => { + it('moves once, along a chain of your own, and ends the turn', () => { + const g = new Game(5) + pastSetup(g) + for (const t of TERRITORY_IDS) g.owner[t] = 1 + const me = g.current + for (const t of ['alaska', 'alberta', 'western-us'] as Territory[]) g.owner[t] = me + g.armies.alaska = 6 + g.place('alaska', g.reinforcements) + g.beginAttack() + g.endAttack() + expect(g.fortifyTargets('alaska').sort()).toEqual(['alberta', 'western-us']) + expect(() => g.fortify('alaska', 'ontario', 1)).toThrow() + const before = g.current + g.fortify('alaska', 'western-us', 3) + expect(g.current).not.toBe(before) + }) +}) diff --git a/src/game/game.ts b/src/game/game.ts new file mode 100644 index 0000000..a785428 --- /dev/null +++ b/src/game/game.ts @@ -0,0 +1,370 @@ +import { fullDeck, isSet, setValue, type Card } from './cards' +import { roll, type Roll } from './dice' +import { + ADJACENT, + CONTINENTS, + CONTINENT_IDS, + TERRITORY_IDS, + adjacent, + inContinent, + type Continent, + type Territory, +} from './map' +import { makeRng, shuffle, type Rng } from './rng' + +/** + * The game, with no interface attached. + * + * Every rule lives here and nowhere else, and every move is checked here + * whether a person or a bot is making it -- so the board can only ever offer + * what this will accept, and a whole game can be played out in a test from + * the deal to the last territory. + * + * It is a plain mutable object rather than a reducer. A game of this runs to + * thousands of dice rolls, and copying forty-two territories on every one of + * them buys nothing the event log does not already give: everything that has + * ever happened is in `events`, in order, and the interface reads that. + */ + +export const PLAYER_COUNT = 6 +/** The classic allowance for a six-player table. */ +export const STARTING_ARMIES = 20 + +export type Phase = 'setup' | 'reinforce' | 'attack' | 'occupy' | 'fortify' | 'over' + +export interface Player { + id: number + human: boolean + alive: boolean + cards: Card[] +} + +export type GameEvent = + | { kind: 'deal'; first: number } + | { kind: 'turn'; player: number; round: number; reinforcements: number } + | { kind: 'place'; player: number; territory: Territory; count: number } + | { kind: 'trade'; player: number; cards: Card[]; armies: number; bonus: Territory[] } + | { kind: 'battle'; player: number; from: Territory; to: Territory; defender: number; roll: Roll } + | { kind: 'conquer'; player: number; from: Territory; to: Territory; loser: number } + | { kind: 'occupy'; player: number; from: Territory; to: Territory; count: number } + | { kind: 'eliminate'; player: number; loser: number; cards: number } + | { kind: 'fortify'; player: number; from: Territory; to: Territory; count: number } + | { kind: 'card'; player: number } + | { kind: 'win'; player: number } + +export interface Occupation { + from: Territory + to: Territory + min: number + max: number +} + +export class Game { + readonly seed: number + readonly players: Player[] + readonly owner = {} as Record + readonly armies = {} as Record + readonly events: GameEvent[] = [] + + phase: Phase = 'setup' + current: number + first: number + round = 0 + /** Armies waiting to be placed, in setup or at the start of a turn. */ + reinforcements = 0 + /** In setup, what each player has still to place. */ + readonly toPlace: number[] + /** Sets traded by anybody, which is what prices the next one. */ + trades = 0 + conquered = false + occupation: Occupation | null = null + winner: number | null = null + /** + * Set when an elimination handed over enough cards to force a trade in the + * middle of an attack: the trade, and placing what it pays, then back to + * attacking. + */ + resume: 'attack' | null = null + + private deck: Card[] + private discard: Card[] = [] + private readonly rng: Rng + + constructor(seed: number, human: number | null = 0) { + this.seed = seed + this.rng = makeRng(seed) + this.players = Array.from({ length: PLAYER_COUNT }, (_, id) => ({ + id, + human: id === human, + alive: true, + cards: [], + })) + + // Deal the territories round the table, one army on each. + this.first = Math.floor(this.rng() * PLAYER_COUNT) + const dealt = shuffle(this.rng, TERRITORY_IDS) + dealt.forEach((t, i) => { + this.owner[t] = (this.first + i) % PLAYER_COUNT + this.armies[t] = 1 + }) + this.toPlace = this.players.map((p) => STARTING_ARMIES - this.territoriesOf(p.id).length) + this.current = this.first + this.deck = shuffle(this.rng, fullDeck()) + this.events.push({ kind: 'deal', first: this.first }) + } + + // ------------------------------------------------------------- queries + + territoriesOf(player: number): Territory[] { + return TERRITORY_IDS.filter((t) => this.owner[t] === player) + } + + armiesOf(player: number): number { + return this.territoriesOf(player).reduce((n, t) => n + this.armies[t], 0) + } + + continentsOf(player: number): Continent[] { + return CONTINENT_IDS.filter((c) => inContinent(c).every((t) => this.owner[t] === player)) + } + + /** A third of your territories, never less than three, and your continents. */ + income(player: number): number { + const base = Math.max(3, Math.floor(this.territoriesOf(player).length / 3)) + return base + this.continentsOf(player).reduce((n, c) => n + CONTINENTS[c].bonus, 0) + } + + get alive(): Player[] { + return this.players.filter((p) => p.alive) + } + + get hand(): Card[] { + return this.players[this.current].cards + } + + /** Five cards in hand at the start of a turn, or after taking a loser's, and you must trade. */ + get mustTrade(): boolean { + return this.phase === 'reinforce' && this.hand.length >= 5 + } + + get nextSetValue(): number { + return setValue(this.trades) + } + + canAttackFrom(t: Territory): boolean { + return ( + this.phase === 'attack' && + this.owner[t] === this.current && + this.armies[t] >= 2 && + ADJACENT[t].some((n) => this.owner[n] !== this.current) + ) + } + + attackTargets(t: Territory): Territory[] { + if (!this.canAttackFrom(t)) return [] + return ADJACENT[t].filter((n) => this.owner[n] !== this.current) + } + + /** Everywhere reachable from `t` through territories of the same owner. */ + connected(t: Territory): Territory[] { + const who = this.owner[t] + const seen = new Set([t]) + const queue = [t] + while (queue.length) { + const at = queue.shift()! + for (const n of ADJACENT[at]) { + if (this.owner[n] === who && !seen.has(n)) { + seen.add(n) + queue.push(n) + } + } + } + seen.delete(t) + return [...seen] + } + + fortifyTargets(t: Territory): Territory[] { + if (this.phase !== 'fortify' || this.owner[t] !== this.current || this.armies[t] < 2) return [] + return this.connected(t) + } + + // ------------------------------------------------------------ actions + + place(t: Territory, count = 1) { + if (this.owner[t] !== this.current) throw new Error(`${t} is not yours to reinforce`) + if (count < 1 || !Number.isInteger(count)) throw new Error(`cannot place ${count}`) + + if (this.phase === 'setup') { + if (count !== 1) throw new Error('setup places one army at a time') + this.armies[t] += 1 + this.toPlace[this.current]-- + this.events.push({ kind: 'place', player: this.current, territory: t, count: 1 }) + this.advanceSetup() + return + } + + if (this.phase !== 'reinforce') throw new Error(`cannot place during ${this.phase}`) + if (this.mustTrade) throw new Error('trade cards first') + if (count > this.reinforcements) throw new Error(`only ${this.reinforcements} to place`) + this.armies[t] += count + this.reinforcements -= count + this.events.push({ kind: 'place', player: this.current, territory: t, count }) + } + + /** Hand in three cards, by their positions in the hand. */ + trade(indices: readonly number[]) { + if (this.phase !== 'reinforce') throw new Error('cards are traded while reinforcing') + const hand = this.hand + const unique = [...new Set(indices)] + if (unique.length !== 3 || unique.some((i) => i < 0 || i >= hand.length)) + throw new Error('a trade is three cards from the hand') + const cards = unique.map((i) => hand[i]) + if (!isSet(cards)) throw new Error('those three are not a set') + + const armies = setValue(this.trades) + this.trades++ + this.reinforcements += armies + + // Two extra on one territory you hold that is pictured on the set. + // Only one of them, however many match. + const bonus: Territory[] = [] + const pictured = cards.find((c) => c.territory && this.owner[c.territory] === this.current) + if (pictured?.territory) { + this.armies[pictured.territory] += 2 + bonus.push(pictured.territory) + } + + this.players[this.current].cards = hand.filter((_, i) => !unique.includes(i)) + this.discard.push(...cards) + this.events.push({ kind: 'trade', player: this.current, cards, armies, bonus }) + } + + /** Done placing; on to the fighting (or back to it). */ + beginAttack() { + if (this.phase !== 'reinforce') throw new Error(`not reinforcing`) + if (this.mustTrade) throw new Error('trade cards first') + if (this.reinforcements > 0) throw new Error(`${this.reinforcements} armies still to place`) + this.phase = 'attack' + this.resume = null + } + + /** One throw of the dice. Returns it, and the territory may change hands. */ + attack(from: Territory, to: Territory, dice = 3): Roll { + if (this.phase !== 'attack') throw new Error(`cannot attack during ${this.phase}`) + if (this.owner[from] !== this.current) throw new Error(`${from} is not yours`) + if (this.owner[to] === this.current) throw new Error(`${to} is already yours`) + if (!adjacent(from, to)) throw new Error(`${from} does not border ${to}`) + if (this.armies[from] < 2) throw new Error(`${from} has nobody to spare`) + + const attackDice = Math.max(1, Math.min(3, dice, this.armies[from] - 1)) + const defendDice = Math.min(2, this.armies[to]) + const defender = this.owner[to] + const r = roll(this.rng, attackDice, defendDice) + this.armies[from] -= r.attackerLoss + this.armies[to] -= r.defenderLoss + this.events.push({ kind: 'battle', player: this.current, from, to, defender, roll: r }) + + if (this.armies[to] === 0) { + this.owner[to] = this.current + this.conquered = true + this.events.push({ kind: 'conquer', player: this.current, from, to, loser: defender }) + this.occupation = { from, to, min: attackDice, max: this.armies[from] - 1 } + this.phase = 'occupy' + + if (this.territoriesOf(defender).length === 0) { + const loser = this.players[defender] + loser.alive = false + const taken = loser.cards.length + this.players[this.current].cards.push(...loser.cards) + loser.cards = [] + this.events.push({ kind: 'eliminate', player: this.current, loser: defender, cards: taken }) + } + } + return r + } + + /** March into the territory just taken: at least as many as rolled, at most all but one. */ + occupy(count: number) { + const o = this.occupation + if (this.phase !== 'occupy' || !o) throw new Error('nothing to occupy') + if (count < o.min || count > o.max) throw new Error(`move between ${o.min} and ${o.max}`) + this.armies[o.from] -= count + this.armies[o.to] = count + this.events.push({ kind: 'occupy', player: this.current, from: o.from, to: o.to, count }) + this.occupation = null + this.phase = 'attack' + + if (this.territoriesOf(this.current).length === TERRITORY_IDS.length) { + this.winner = this.current + this.phase = 'over' + this.events.push({ kind: 'win', player: this.current }) + return + } + if (this.hand.length >= 6) { + this.phase = 'reinforce' + this.resume = 'attack' + this.reinforcements = 0 + } + } + + endAttack() { + if (this.phase !== 'attack') throw new Error(`not attacking`) + this.phase = 'fortify' + } + + /** One move between two of your territories joined by your own, and the turn ends. */ + fortify(from: Territory, to: Territory, count: number) { + if (this.phase !== 'fortify') throw new Error(`not fortifying`) + if (!this.fortifyTargets(from).includes(to)) throw new Error(`${from} cannot reach ${to}`) + if (count < 1 || count > this.armies[from] - 1) throw new Error(`cannot move ${count}`) + this.armies[from] -= count + this.armies[to] += count + this.events.push({ kind: 'fortify', player: this.current, from, to, count }) + this.endTurn() + } + + endTurn() { + if (this.phase !== 'attack' && this.phase !== 'fortify') throw new Error(`cannot end turn during ${this.phase}`) + if (this.conquered) { + if (this.deck.length === 0) { + this.deck = shuffle(this.rng, this.discard) + this.discard = [] + } + const card = this.deck.pop() + if (card) { + this.players[this.current].cards.push(card) + this.events.push({ kind: 'card', player: this.current }) + } + } + // A round is counted each time play comes back past the seat that went + // first, whether or not that player is still at the table. + let next = this.current + do { + next = (next + 1) % PLAYER_COUNT + if (next === this.first) this.round++ + } while (!this.players[next].alive) + this.startTurn(next) + } + + // ------------------------------------------------------------ internals + + private advanceSetup() { + if (this.toPlace.every((n) => n === 0)) { + this.round = 1 + this.startTurn(this.first) + return + } + let next = this.current + do next = (next + 1) % PLAYER_COUNT + while (this.toPlace[next] === 0) + this.current = next + } + + private startTurn(player: number) { + this.current = player + this.phase = 'reinforce' + this.resume = null + this.conquered = false + this.reinforcements = this.income(player) + this.events.push({ kind: 'turn', player, round: this.round, reinforcements: this.reinforcements }) + } +} diff --git a/src/game/map.test.ts b/src/game/map.test.ts new file mode 100644 index 0000000..148b757 --- /dev/null +++ b/src/game/map.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it } from 'vitest' +import board from './board.json' +import { ADJACENT, BORDERS, CONTINENTS, CONTINENT_IDS, LINK_LIST, TERRITORY_IDS, inContinent } from './map' + +describe('the board', () => { + it('has the classic forty-two territories in six continents', () => { + expect(TERRITORY_IDS).toHaveLength(42) + expect(CONTINENT_IDS.map((c) => inContinent(c).length)).toEqual([9, 4, 7, 6, 12, 4]) + expect(CONTINENT_IDS.map((c) => CONTINENTS[c].bonus)).toEqual([5, 2, 5, 3, 7, 2]) + }) + + it('has the classic eighty-three links, none twice', () => { + expect(LINK_LIST).toHaveLength(83) + const keys = LINK_LIST.map(([a, b]) => [a, b].sort().join('|')) + expect(new Set(keys).size).toBe(83) + }) + + it('is symmetric, and nothing borders itself', () => { + for (const t of TERRITORY_IDS) { + expect(ADJACENT[t]).not.toContain(t) + for (const n of ADJACENT[t]) expect(ADJACENT[n]).toContain(t) + } + }) + + it('is one connected world', () => { + const seen = new Set([TERRITORY_IDS[0]]) + const queue = [TERRITORY_IDS[0]] + while (queue.length) { + for (const n of ADJACENT[queue.shift()!]) { + if (seen.has(n)) continue + seen.add(n) + queue.push(n) + } + } + expect(seen.size).toBe(42) + }) + + // The doors are the strategy. If one of these moves, the game has changed. + it('keeps the doors where every player expects them', () => { + expect([...BORDERS['australia']]).toEqual(['indonesia']) + expect([...BORDERS['south-america']].sort()).toEqual(['brazil', 'venezuela']) + expect([...BORDERS['north-america']].sort()).toEqual(['alaska', 'central-america', 'greenland']) + expect([...BORDERS['africa']].sort()).toEqual(['east-africa', 'egypt', 'north-africa']) + expect([...BORDERS['europe']].sort()).toEqual(['iceland', 'southern-europe', 'ukraine', 'western-europe']) + expect([...BORDERS['asia']].sort()).toEqual(['afghanistan', 'kamchatka', 'middle-east', 'siam', 'ural']) + }) +}) + +describe('the drawing', () => { + const drawn = board.territories as Record + + it('draws every territory and nothing else', () => { + expect(Object.keys(drawn).sort()).toEqual([...TERRITORY_IDS].sort()) + }) + + it('can draw a lane for every link that is not a shared border', () => { + const touching = new Set(board.touching.map((p) => [...p].sort().join('|'))) + const near = board.near as Record + for (const [a, b] of LINK_LIST) { + const key = [a, b].sort().join('|') + if (touching.has(key) || key === 'alaska|kamchatka') continue + expect(near[key], key).toBeDefined() + } + }) +}) diff --git a/src/game/map.ts b/src/game/map.ts new file mode 100644 index 0000000..65521fb --- /dev/null +++ b/src/game/map.ts @@ -0,0 +1,201 @@ +/** + * The board as the rules see it: forty-two territories in six continents, and + * which of them border which. + * + * The topology is the classic one, link for link. It is a fact about a + * published game and free to use, and it is also the part players know by + * heart: that Australia has one door, that South America has two, that Asia + * has nowhere to hide. The shapes the board draws come from real geography in + * `board.json` and only approximate this -- where they disagree, the rules + * here are the ones that count, and the board says so by drawing a lane across + * the water or closing a border that happens to touch. + */ + +export const CONTINENTS = { + 'north-america': { name: 'North America', bonus: 5 }, + 'south-america': { name: 'South America', bonus: 2 }, + europe: { name: 'Europe', bonus: 5 }, + africa: { name: 'Africa', bonus: 3 }, + asia: { name: 'Asia', bonus: 7 }, + australia: { name: 'Australia', bonus: 2 }, +} as const + +export type Continent = keyof typeof CONTINENTS + +export const TERRITORIES = { + alaska: { name: 'Alaska', continent: 'north-america' }, + 'northwest-territory': { name: 'Northwest Territory', continent: 'north-america' }, + greenland: { name: 'Greenland', continent: 'north-america' }, + alberta: { name: 'Alberta', continent: 'north-america' }, + ontario: { name: 'Ontario', continent: 'north-america' }, + quebec: { name: 'Quebec', continent: 'north-america' }, + 'western-us': { name: 'Western United States', continent: 'north-america' }, + 'eastern-us': { name: 'Eastern United States', continent: 'north-america' }, + 'central-america': { name: 'Central America', continent: 'north-america' }, + + venezuela: { name: 'Venezuela', continent: 'south-america' }, + peru: { name: 'Peru', continent: 'south-america' }, + brazil: { name: 'Brazil', continent: 'south-america' }, + argentina: { name: 'Argentina', continent: 'south-america' }, + + iceland: { name: 'Iceland', continent: 'europe' }, + 'great-britain': { name: 'Great Britain', continent: 'europe' }, + scandinavia: { name: 'Scandinavia', continent: 'europe' }, + 'northern-europe': { name: 'Northern Europe', continent: 'europe' }, + 'western-europe': { name: 'Western Europe', continent: 'europe' }, + 'southern-europe': { name: 'Southern Europe', continent: 'europe' }, + ukraine: { name: 'Ukraine', continent: 'europe' }, + + 'north-africa': { name: 'North Africa', continent: 'africa' }, + egypt: { name: 'Egypt', continent: 'africa' }, + 'east-africa': { name: 'East Africa', continent: 'africa' }, + congo: { name: 'Congo', continent: 'africa' }, + 'south-africa': { name: 'South Africa', continent: 'africa' }, + madagascar: { name: 'Madagascar', continent: 'africa' }, + + ural: { name: 'Ural', continent: 'asia' }, + siberia: { name: 'Siberia', continent: 'asia' }, + yakutsk: { name: 'Yakutsk', continent: 'asia' }, + kamchatka: { name: 'Kamchatka', continent: 'asia' }, + irkutsk: { name: 'Irkutsk', continent: 'asia' }, + mongolia: { name: 'Mongolia', continent: 'asia' }, + japan: { name: 'Japan', continent: 'asia' }, + afghanistan: { name: 'Afghanistan', continent: 'asia' }, + china: { name: 'China', continent: 'asia' }, + 'middle-east': { name: 'Middle East', continent: 'asia' }, + india: { name: 'India', continent: 'asia' }, + siam: { name: 'Siam', continent: 'asia' }, + + indonesia: { name: 'Indonesia', continent: 'australia' }, + 'new-guinea': { name: 'New Guinea', continent: 'australia' }, + 'western-australia': { name: 'Western Australia', continent: 'australia' }, + 'eastern-australia': { name: 'Eastern Australia', continent: 'australia' }, +} as const satisfies Record + +export type Territory = keyof typeof TERRITORIES + +export const TERRITORY_IDS = Object.keys(TERRITORIES) as Territory[] + +/** Each link once, in the order a player would read the board. */ +const LINKS: readonly (readonly [Territory, Territory])[] = [ + ['alaska', 'northwest-territory'], + ['alaska', 'alberta'], + ['alaska', 'kamchatka'], + ['northwest-territory', 'alberta'], + ['northwest-territory', 'ontario'], + ['northwest-territory', 'greenland'], + ['greenland', 'ontario'], + ['greenland', 'quebec'], + ['greenland', 'iceland'], + ['alberta', 'ontario'], + ['alberta', 'western-us'], + ['ontario', 'western-us'], + ['ontario', 'eastern-us'], + ['ontario', 'quebec'], + ['quebec', 'eastern-us'], + ['western-us', 'eastern-us'], + ['western-us', 'central-america'], + ['eastern-us', 'central-america'], + ['central-america', 'venezuela'], + + ['venezuela', 'peru'], + ['venezuela', 'brazil'], + ['peru', 'brazil'], + ['peru', 'argentina'], + ['brazil', 'argentina'], + ['brazil', 'north-africa'], + + ['iceland', 'great-britain'], + ['iceland', 'scandinavia'], + ['great-britain', 'scandinavia'], + ['great-britain', 'northern-europe'], + ['great-britain', 'western-europe'], + ['scandinavia', 'northern-europe'], + ['scandinavia', 'ukraine'], + ['northern-europe', 'ukraine'], + ['northern-europe', 'southern-europe'], + ['northern-europe', 'western-europe'], + ['western-europe', 'southern-europe'], + ['western-europe', 'north-africa'], + ['southern-europe', 'ukraine'], + ['southern-europe', 'middle-east'], + ['southern-europe', 'egypt'], + ['southern-europe', 'north-africa'], + ['ukraine', 'middle-east'], + ['ukraine', 'afghanistan'], + ['ukraine', 'ural'], + + ['north-africa', 'egypt'], + ['north-africa', 'east-africa'], + ['north-africa', 'congo'], + ['egypt', 'middle-east'], + ['egypt', 'east-africa'], + ['east-africa', 'congo'], + ['east-africa', 'south-africa'], + ['east-africa', 'madagascar'], + ['east-africa', 'middle-east'], + ['congo', 'south-africa'], + ['south-africa', 'madagascar'], + + ['middle-east', 'afghanistan'], + ['middle-east', 'india'], + ['afghanistan', 'ural'], + ['afghanistan', 'china'], + ['afghanistan', 'india'], + ['ural', 'siberia'], + ['ural', 'china'], + ['siberia', 'yakutsk'], + ['siberia', 'irkutsk'], + ['siberia', 'mongolia'], + ['siberia', 'china'], + ['yakutsk', 'kamchatka'], + ['yakutsk', 'irkutsk'], + ['kamchatka', 'irkutsk'], + ['kamchatka', 'mongolia'], + ['kamchatka', 'japan'], + ['irkutsk', 'mongolia'], + ['mongolia', 'japan'], + ['mongolia', 'china'], + ['china', 'siam'], + ['china', 'india'], + ['india', 'siam'], + ['siam', 'indonesia'], + + ['indonesia', 'new-guinea'], + ['indonesia', 'western-australia'], + ['new-guinea', 'western-australia'], + ['new-guinea', 'eastern-australia'], + ['western-australia', 'eastern-australia'], +] + +export const LINK_LIST = LINKS + +export const ADJACENT: Record = (() => { + const adj = Object.fromEntries(TERRITORY_IDS.map((t) => [t, [] as Territory[]])) as Record< + Territory, + Territory[] + > + for (const [a, b] of LINKS) { + adj[a].push(b) + adj[b].push(a) + } + return adj +})() + +export const adjacent = (a: Territory, b: Territory) => ADJACENT[a].includes(b) + +export const inContinent = (c: Continent) => + TERRITORY_IDS.filter((t) => TERRITORIES[t].continent === c) + +export const CONTINENT_IDS = Object.keys(CONTINENTS) as Continent[] + +/** + * The territories through which a continent can be entered: those with a + * neighbor outside it. Holding a continent means holding these. + */ +export const BORDERS: Record = Object.fromEntries( + CONTINENT_IDS.map((c) => [ + c, + inContinent(c).filter((t) => ADJACENT[t].some((n) => TERRITORIES[n].continent !== c)), + ]), +) as Record diff --git a/src/game/play.test.ts b/src/game/play.test.ts new file mode 100644 index 0000000..60d65f6 --- /dev/null +++ b/src/game/play.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from 'vitest' +import { apply, decide, leanings } from './bot' +import { Game, PLAYER_COUNT } from './game' + +/** + * Six bots left alone, to the end. + * + * The question that matters about the bots is not whether each move looks + * sensible but whether a table of them ever finishes. A game that cannot be + * won is not a hard game, it is a broken one -- so these play out in full, + * on thirty seeds, and every one of them has to end in a winner. + */ +export function playOut(seed: number, limit = 20000) { + const g = new Game(seed, null) + const lean = leanings(seed, PLAYER_COUNT) + let actions = 0 + while (g.phase !== 'over' && actions < limit) { + apply(g, decide(g, lean[g.current])) + actions++ + } + return { g, actions } +} + +describe('a table of bots', () => { + it('plays every seed through to a winner', () => { + const rounds: number[] = [] + for (let seed = 1; seed <= 30; seed++) { + const { g } = playOut(seed) + expect(g.phase, `seed ${seed}`).toBe('over') + expect(g.winner).not.toBeNull() + expect(g.territoriesOf(g.winner!)).toHaveLength(42) + rounds.push(g.round) + } + const mean = rounds.reduce((a, b) => a + b, 0) / rounds.length + console.log(`rounds: mean ${mean.toFixed(1)}, min ${Math.min(...rounds)}, max ${Math.max(...rounds)}`) + expect(Math.max(...rounds)).toBeLessThan(80) + }) + + it('does not always hand it to the same seat', () => { + const winners = new Set() + for (let seed = 1; seed <= 30; seed++) winners.add(playOut(seed).g.winner!) + expect(winners.size).toBeGreaterThan(3) + }) + + it('replays exactly from the same seed', () => { + const a = playOut(7).g + const b = playOut(7).g + expect(a.events.length).toBe(b.events.length) + expect(a.winner).toBe(b.winner) + }) +}) diff --git a/src/game/rng.ts b/src/game/rng.ts new file mode 100644 index 0000000..b1d42d2 --- /dev/null +++ b/src/game/rng.ts @@ -0,0 +1,26 @@ +/** + * mulberry32 — small, fast, seedable PRNG. + * The dice and the deal come from it, and so do the bots' leanings, so a seed + * replays a whole game exactly and a test can throw the same dice twice. + */ +export function makeRng(seed: number) { + let a = seed >>> 0 + return function rng(): number { + a = (a + 0x6d2b79f5) >>> 0 + let t = a + t = Math.imul(t ^ (t >>> 15), t | 1) + t ^= t + Math.imul(t ^ (t >>> 7), t | 61) + return ((t ^ (t >>> 14)) >>> 0) / 4294967296 + } +} + +export type Rng = ReturnType + +export function shuffle(rng: Rng, items: readonly T[]): T[] { + const out = [...items] + for (let i = out.length - 1; i > 0; i--) { + const j = Math.floor(rng() * (i + 1)) + ;[out[i], out[j]] = [out[j], out[i]] + } + return out +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..bf6321c --- /dev/null +++ b/src/index.css @@ -0,0 +1,15 @@ +:root { + font-synthesis: none; + -webkit-font-smoothing: antialiased; + color-scheme: dark; +} + +* { box-sizing: border-box; } + +html, body, #root { height: 100%; margin: 0; } + +body { + background: radial-gradient(120% 90% at 50% 0%, #23324a 0%, #16202f 55%, #0d141d 100%); + color: #f2e9d8; + font-family: ui-rounded, "Nunito", "Avenir Next", "Segoe UI", system-ui, sans-serif; +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..bef5202 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/tools/map.py b/tools/map.py new file mode 100644 index 0000000..5aa4230 --- /dev/null +++ b/tools/map.py @@ -0,0 +1,350 @@ +#!/usr/bin/env python3 +""" +Build src/game/board.json from Natural Earth. + +The forty-two territories are groups of real countries and, for the five +countries that the board cuts through (Russia, Canada, the United States, +Australia, China) and the one it cuts across (Indonesia, whose half of New +Guinea belongs with the other half), their first-level provinces. + +Natural Earth is public domain, so the outlines carry no license of their own. +The grouping below is ours: which country goes in which territory is the one +part of this map anybody had to decide. + + python3 -m venv .venv && .venv/bin/pip install shapely pyshp + .venv/bin/python tools/map.py path/to/natural-earth + +The directory needs the 1:50m admin-0 countries and admin-1 states and +provinces, unzipped where naciscdn.org puts them: + + ne_50m_admin_0_countries/ne_50m_admin_0_countries.shp + ne_50m_admin_1_states_provinces/ne_50m_admin_1_states_provinces.shp +""" + +import json +import math +import sys +from pathlib import Path + +import shapefile +from shapely.geometry import MultiPolygon, Polygon, shape +from shapely.ops import polylabel, unary_union, nearest_points +from shapely import affinity + +# ---------------------------------------------------------------- grouping + +COUNTRIES = { + # North America. Alaska, Canada and the United States come from admin-1. + 'greenland': ['GRL'], + 'central-america': [ + 'MEX', 'GTM', 'BLZ', 'SLV', 'HND', 'NIC', 'CRI', 'PAN', + 'CUB', 'JAM', 'HTI', 'DOM', 'BHS', 'PRI', 'TCA', 'CYM', + ], + # South America. + 'venezuela': ['VEN', 'COL', 'GUY', 'SUR'], # plus French Guiana, below + 'peru': ['PER', 'ECU', 'BOL'], + 'brazil': ['BRA'], + 'argentina': ['ARG', 'CHL', 'URY', 'PRY', 'FLK'], + # Europe. + 'iceland': ['ISL', 'FRO'], + 'great-britain': ['GBR', 'IRL', 'IMN', 'GGY', 'JEY'], + 'scandinavia': ['NOR', 'SWE', 'FIN', 'ALD'], + 'northern-europe': ['DEU', 'POL', 'DNK', 'NLD', 'BEL', 'LUX', 'CZE', 'SVK'], + 'western-europe': ['FRA', 'ESP', 'PRT', 'AND', 'MCO'], # metropolitan France only + 'southern-europe': [ + 'ITA', 'CHE', 'LIE', 'AUT', 'SVN', 'HRV', 'BIH', 'SRB', 'MNE', 'KOS', + 'ALB', 'MKD', 'GRC', 'BGR', 'ROU', 'HUN', 'MLT', 'SMR', 'VAT', + ], + 'ukraine': ['UKR', 'BLR', 'MDA', 'EST', 'LVA', 'LTU'], # plus European Russia + # Africa. + 'north-africa': [ + 'MAR', 'SAH', 'DZA', 'TUN', 'MRT', 'MLI', 'NER', 'TCD', 'SEN', 'GMB', + 'GNB', 'GIN', 'SLE', 'LBR', 'CIV', 'BFA', 'GHA', 'TGO', 'BEN', 'NGA', + ], + 'egypt': ['EGY', 'LBY'], + 'east-africa': [ + 'SDN', 'SDS', 'ETH', 'ERI', 'DJI', 'SOM', 'SOL', 'KEN', 'UGA', 'TZA', + 'RWA', 'BDI', + ], + 'congo': ['COD', 'COG', 'CAF', 'CMR', 'GAB', 'GNQ', 'AGO'], + 'south-africa': ['ZAF', 'NAM', 'BWA', 'ZWE', 'ZMB', 'MOZ', 'MWI', 'LSO', 'SWZ'], + 'madagascar': ['MDG', 'COM'], + # Asia. Russia and China come from admin-1. + 'middle-east': [ + 'TUR', 'SYR', 'LBN', 'ISR', 'PSX', 'JOR', 'IRQ', 'SAU', 'KWT', 'BHR', + 'QAT', 'ARE', 'OMN', 'YEM', 'IRN', 'CYP', 'CYN', 'GEO', 'ARM', 'AZE', + ], + 'afghanistan': ['AFG', 'KAZ', 'UZB', 'TKM', 'TJK', 'KGZ'], + 'india': ['IND', 'PAK', 'NPL', 'BTN', 'BGD', 'LKA', 'KAS'], + 'siam': ['THA', 'MMR', 'LAO', 'KHM', 'VNM', 'MYS', 'SGP'], + 'china': ['TWN', 'HKG', 'MAC'], + # Korea goes with Manchuria rather than China: on the board China does not + # reach the Pacific coast of Russia, and North Korea would take it there. + 'mongolia': ['MNG', 'PRK', 'KOR'], + 'japan': ['JPN'], + # Australia. Indonesia and Australia come from admin-1. + 'indonesia': ['PHL', 'BRN', 'TLS'], + 'new-guinea': ['PNG', 'SLB'], + 'eastern-australia': ['NZL'], +} + +# Countries taken apart by province instead. Each maps a province to its +# territory; anything not named goes to the default. +PROVINCES = { + 'USA': ('eastern-us', { + 'Alaska': 'alaska', + **{s: 'western-us' for s in [ + 'Washington', 'Oregon', 'California', 'Nevada', 'Idaho', 'Montana', + 'Wyoming', 'Utah', 'Colorado', 'Arizona', 'New Mexico', + 'North Dakota', 'South Dakota', 'Nebraska', 'Kansas', + ]}, + 'Hawaii': None, + }), + 'CAN': ('quebec', { + 'Yukon': 'northwest-territory', + 'Northwest Territories': 'northwest-territory', + 'Nunavut': 'northwest-territory', + 'British Columbia': 'alberta', + 'Alberta': 'alberta', + 'Saskatchewan': 'alberta', + 'Manitoba': 'ontario', + 'Ontario': 'ontario', + }), + 'AUS': ('eastern-australia', { + 'Western Australia': 'western-australia', + 'Northern Territory': 'western-australia', + 'South Australia': 'western-australia', + }), + 'CHN': ('china', { + 'Inner Mongol': 'mongolia', + 'Heilongjiang': 'mongolia', + 'Jilin': 'mongolia', + 'Liaoning': 'mongolia', + }), + 'IDN': ('indonesia', { + 'Papua': 'new-guinea', + 'Papua Barat': 'new-guinea', + }), +} + +# Russia is grouped by federal district first, then by name, because the +# board's line down the Urals is not quite the administrative one. +RUSSIA_BY_NAME = { + 'Komi': 'ural', 'Nenets': 'ural', "Perm'": 'ural', 'Bashkortostan': 'ural', + 'Orenburg': 'ural', 'Udmurt': 'ural', + # The steppe south of the Siberian plain, so Siberia meets Kazakhstan only + # where the Altai Republic does, and the board's Siberia never borders it. + 'Omsk': 'ural', 'Novosibirsk': 'ural', 'Altay': 'ural', + 'Irkutsk': 'irkutsk', 'Buryat': 'irkutsk', 'Chita': 'irkutsk', + 'Sakha (Yakutia)': 'yakutsk', + 'Kaliningrad': 'northern-europe', +} +RUSSIA_BY_REGION = { + 'Urals': 'ural', + 'Siberian': 'siberia', + 'Far Eastern': 'kamchatka', +} + + +def russia(rec): + return RUSSIA_BY_NAME.get(rec['name']) or RUSSIA_BY_REGION.get(rec['region']) or 'ukraine' + + +# ---------------------------------------------------------------- geometry + +LON0 = -170.0 # the board's left edge; Alaska's Aleutians wrap to it +LON1 = 192.0 # and the right edge, past the date line for Chukotka +LAT_MIN = -56.0 +LAT_MAX = 84.0 +SCALE = 4.0 # board units per degree of longitude +SIMPLIFY = 0.12 # degrees +MIN_ISLAND = 0.35 # square degrees; smaller islands are dropped + + +def miller(lat): + lat = max(LAT_MIN, min(LAT_MAX, lat)) + return math.degrees(1.25 * math.log(math.tan(math.pi / 4 + 0.4 * math.radians(lat)))) + + +Y_TOP = miller(LAT_MAX) + + +def project(geom): + def xy(x, y, z=None): + return ((x - LON0) * SCALE, (Y_TOP - miller(y)) * SCALE) + from shapely.ops import transform + return transform(lambda xs, ys, zs=None: tuple(zip(*[xy(x, y) for x, y in zip(xs, ys)])), geom) + + +def polygons(geom): + if isinstance(geom, Polygon): + return [geom] + if isinstance(geom, MultiPolygon): + return list(geom.geoms) + return [g for g in getattr(geom, 'geoms', []) if isinstance(g, Polygon)] + + +def wrap(tid, poly): + """Move a polygon across the date line to the side of the board it lives on.""" + c = poly.centroid.x + if tid == 'alaska' and c > 0: + return affinity.translate(poly, -360) + if c < LON0: + return affinity.translate(poly, 360) + return poly + + +def keep(tid, code, poly): + """Drop the overseas pieces a country's outline carries with it.""" + x, y = poly.centroid.x, poly.centroid.y + if code == 'FRA': + if -55 < x < -50 and 2 < y < 6: + return 'venezuela' # French Guiana + return tid if (-6 < x < 10 and 41 < y < 52) else None + if code == 'NOR' and y > 74: + return None # Svalbard + if code == 'ESP' and (x < -12 or y < 36): + return None # the Canaries, Ceuta and Melilla + if code == 'PRT' and x < -12: + return None # the Azores and Madeira + if code == 'CHL' and x < -100: + return None # Easter Island + if code == 'ECU' and x < -85: + return None # the Galápagos + if code == 'GBR' and not (-9 < x < 3 and 49 < y < 61): + return None + if code == 'NLD' and y < 40: + return None # the Caribbean Netherlands + if code == 'DNK' and x < 0: + return None + if y < LAT_MIN: + return None + return tid + + +def main(src): + src = Path(src) + parts = {} + + def add(tid, geom, code): + for p in polygons(geom): + if not p.is_valid: + p = p.buffer(0) + t = keep(tid, code, p) + if t: + parts.setdefault(t, []).append(wrap(t, p)) + + lookup = {code: tid for tid, codes in COUNTRIES.items() for code in codes} + unused = [] + r0 = shapefile.Reader(str(src / 'ne_50m_admin_0_countries/ne_50m_admin_0_countries.shp')) + for sr in r0.iterShapeRecords(): + code = sr.record['ADM0_A3'] + if code in PROVINCES or code == 'RUS': + continue + tid = lookup.get(code) + if tid is None: + unused.append(code) + continue + add(tid, shape(sr.shape.__geo_interface__), code) + + r1 = shapefile.Reader(str(src / 'ne_50m_admin_1_states_provinces/ne_50m_admin_1_states_provinces.shp')) + for sr in r1.iterShapeRecords(): + rec = sr.record + code = rec['adm0_a3'] + if code == 'RUS': + tid = russia(rec) + elif code in PROVINCES: + default, named = PROVINCES[code] + tid = named.get(rec['name'], default) + else: + continue + if tid: + add(tid, shape(sr.shape.__geo_interface__), code) + + out = {} + shapes = {} + for tid, ps in sorted(parts.items()): + merged = unary_union([p.buffer(0.02) for p in ps]).buffer(-0.02) + pieces = sorted(polygons(merged), key=lambda p: -p.area) + big = [p for p in pieces if p.area >= MIN_ISLAND] or pieces[:1] + geom = MultiPolygon(big).simplify(SIMPLIFY, preserve_topology=True) + shapes[tid] = geom + proj = project(geom) + main_piece = max(polygons(proj), key=lambda p: p.area) + label = polylabel(main_piece, tolerance=0.5) + out[tid] = { + 'd': ' '.join(path(p) for p in polygons(proj)), + 'label': [round(label.x, 1), round(label.y, 1)], + } + + touching = [] + ids = sorted(shapes) + grown = {t: shapes[t].buffer(0.25) for t in ids} + for i, a in enumerate(ids): + for b in ids[i + 1:]: + if grown[a].intersects(shapes[b]): + touching.append([a, b]) + + # Nearest points for every pair not touching but close enough that the + # rules might join them across water. The board draws a lane between + # these; which pairs are joined is the rules' business, not this file's. + near = {} + pshapes = {t: project(shapes[t]) for t in ids} + for i, a in enumerate(ids): + for b in ids[i + 1:]: + if [a, b] in touching: + continue + pa, pb = nearest_points(pshapes[a], pshapes[b]) + if pa.distance(pb) < 45 * SCALE: + near[f'{a}|{b}'] = [round(pa.x, 1), round(pa.y, 1), round(pb.x, 1), round(pb.y, 1)] + + # The line two touching territories share, so the board can say something + # about it: heavier where it is also a continent's edge, and closed where + # the classic board has no link across it. + borders = {} + for a, b in touching: + line = pshapes[a].boundary.intersection(pshapes[b].buffer(0.25 * SCALE)) + parts = [g for g in getattr(line, 'geoms', [line]) if g.geom_type == 'LineString' and g.length > 2] + if parts: + borders[f'{a}|{b}'] = ''.join( + 'M' + 'L'.join(f'{x:.1f},{y:.1f}' for x, y in g.coords) for g in parts + ) + + # Alaska and Kamchatka meet across the date line, which is the edge of + # the board: a lane off each side, at the height of each coast. + alaska = max(polygons(pshapes['alaska']), key=lambda p: p.area) + kamchatka = max(polygons(pshapes['kamchatka']), key=lambda p: p.area) + west = min(alaska.exterior.coords, key=lambda c: c[0]) + east = max(kamchatka.exterior.coords, key=lambda c: c[0]) + + width = round((LON1 - LON0) * SCALE) + height = round((Y_TOP - miller(LAT_MIN)) * SCALE) + board = { + 'source': 'Natural Earth 1:50m, public domain; grouping by tools/map.py', + 'width': width, + 'height': height, + 'territories': out, + 'touching': touching, + 'near': near, + 'borders': borders, + 'wrap': { + 'alaska': [round(west[0], 1), round(west[1], 1)], + 'kamchatka': [round(east[0], 1), round(east[1], 1)], + }, + } + dest = Path(__file__).resolve().parent.parent / 'src/game/board.json' + dest.write_text(json.dumps(board, separators=(',', ':')) + '\n') + print(f'{len(out)} territories, {len(touching)} land borders, {len(near)} crossings -> {dest}') + print(f'{dest.stat().st_size // 1024} KiB') + print('left out:', ' '.join(sorted(unused))) + + +def path(poly): + def ring(coords): + pts = [f'{x:.1f},{y:.1f}' for x, y in coords[:-1]] + return 'M' + 'L'.join(pts) + 'Z' + return ring(list(poly.exterior.coords)) + ''.join(ring(list(i.coords)) for i in poly.interiors) + + +if __name__ == '__main__': + main(sys.argv[1] if len(sys.argv) > 1 else 'natural-earth') diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..18d7d9c --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023", "DOM"], + "module": "esnext", + "types": ["vite/client"], + "allowArbitraryExtensions": true, + "resolveJsonModule": true, + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..8455dcb --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023"], + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "module": "nodenext", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..056061a --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,15 @@ +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' + +// Vitest is left on its defaults on purpose: it already picks up *.test.ts and +// nothing else, and importing `vitest/config` here drags in a second copy of +// vite whose plugin types disagree with this one's. +// +// There is no dev proxy here, unlike the arcade games on the site. They post +// scores to a shared service; this one has nothing to post. +export default defineConfig({ + // Asset URLs are baked in at build time, so this has to match the path the + // game is served from. Local development stays at the root. + base: process.env.BASE_PATH ?? '/', + plugins: [react()], +}) diff --git a/web/Dockerfile b/web/Dockerfile new file mode 100644 index 0000000..4fce01e --- /dev/null +++ b/web/Dockerfile @@ -0,0 +1,24 @@ +# The game is a static bundle. It is built here and served by nginx, in its own +# container - it shares nothing with the rest of the site but the domain. +# +# It talks to no service at all. There is no score to post: a game against +# five bots is won or lost, and a leaderboard of how many rounds it took would +# mostly measure the dice. +FROM node:26-alpine AS build +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci +COPY tsconfig*.json vite.config.ts index.html ./ +COPY public ./public +COPY src ./src +# Must match where the host proxies it, or the asset URLs will be wrong. +ARG BASE_PATH=/conquest/ +ENV BASE_PATH=${BASE_PATH} +RUN npm run build + +FROM nginx:alpine +COPY web/nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=build /app/dist /usr/share/nginx/html +EXPOSE 8080 +HEALTHCHECK --interval=30s --timeout=3s --retries=3 \ + CMD wget -qO- http://127.0.0.1:8080/healthz >/dev/null || exit 1 diff --git a/web/nginx.conf b/web/nginx.conf new file mode 100644 index 0000000..e368b82 --- /dev/null +++ b/web/nginx.conf @@ -0,0 +1,22 @@ +server { + listen 8080; + server_name _; + root /usr/share/nginx/html; + + # Hashed filenames, so they can be cached hard. + location /assets/ { + expires 1y; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + + location / { + try_files $uri $uri/ /index.html; + add_header Cache-Control "no-cache"; + } + + location = /healthz { + access_log off; + return 200 "ok\n"; + } +}