pax_global_header00006660000000000000000000000064122724071260014515gustar00rootroot0000000000000052 comment=f5e293a33f0bf469ef8ebed878281c60c506f37a pentobi-7.2/000077500000000000000000000000001227240712600130255ustar00rootroot00000000000000pentobi-7.2/CMakeLists.txt000066400000000000000000000113051227240712600155650ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.9) # Use all lower-case for project name because it is used in # CMAKE_INSTALL_DOCDIR of GNUInstallDirs project(pentobi) set(PENTOBI_VERSION 7.2) include(CheckIncludeFiles) include(GNUInstallDirs) option(PENTOBI_BUILD_TESTS "Build unit tests" OFF) option(PENTOBI_BUILD_GTP "Build GTP interface" OFF) option(PENTOBI_BUILD_GUI "Build Qt-based GUI" ON) option(PENTOBI_BUILD_KDE_THUMBNAILER "Build thumbnailer for KDE (also requires PENTOBI_BUILD_GUI)" OFF) option(USE_BOOST_THREAD "Use Boost.Thread instead of std::thread" OFF) option(USE_QT5 "Use Qt 5 instead of Qt 4" OFF) if(NOT CMAKE_BUILD_TYPE) message(STATUS "No build type selected, default to Release") set(CMAKE_BUILD_TYPE "Release") endif() if(CMAKE_BUILD_TYPE STREQUAL "Debug") add_definitions(-DLIBBOARDGAME_DEBUG) endif() if(CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Intel")) add_definitions(-std=c++11) endif() if(CMAKE_COMPILER_IS_GNUCXX OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) add_definitions(-ffast-math) endif() if(MSVC) add_definitions(-D_CRT_SECURE_NO_DEPRECATE) add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE) add_definitions(-D_SCL_SECURE_NO_WARNINGS) endif() if(MINGW) # -std=c++11 makes MinGW define __STRICT_ANSI__, which also hides # some functions that we use in some Windows-specific code, like fdopen() add_definitions(-U__STRICT_ANSI__) add_definitions(-DWINVER=0x0501) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--large-address-aware") endif() check_include_files(unistd.h HAVE_UNISTD_H) check_include_files(sys/times.h HAVE_SYS_TIMES_H) check_include_files(sys/sysctl.h HAVE_SYS_SYSCTL_H) # Set USE_BOOST_THREAD to use Boost.Thread instead of std::thread # (e.g. with MinGW GCC 4.7, std::thread is not functional) if(USE_BOOST_THREAD) set(PENTOBI_BOOST_COMPONENTS thread) # On MinGW, the thread library needs chrono (last tested with Boost 1.52) if (MINGW) set(PENTOBI_BOOST_COMPONENTS ${PENTOBI_BOOST_COMPONENTS} chrono) endif() # On MinGW or CLang 3.2/Linux, the thread library needs system (last tested # with Boost 1.52) if (MINGW OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) set(PENTOBI_BOOST_COMPONENTS ${PENTOBI_BOOST_COMPONENTS} system) endif() find_package(Boost 1.49 REQUIRED COMPONENTS ${PENTOBI_BOOST_COMPONENTS}) if(NOT Boost_FOUND) message(FATAL_ERROR "Boost not found") endif() if(MINGW AND Boost_USE_STATIC_LIBS) # Boost static libs generate link errors with MinGW if BOOST_THREAD_USE_LIB # is not explicetly defined (last tested with Boost 1.52) add_definitions(-DBOOST_THREAD_USE_LIB) endif() endif() # -lpthread is needed on Unix but not automatically added to the compiler # options if std::thread/GCC 4.7 or CMake/FindBoost on MinGW is used. We # solve this by adding CMAKE_THREAD_LIBS_INIT to the link libraries. find_package(Threads) if(NOT DEFINED LIBPENTOBI_MCTS_FLOAT_TYPE) set(LIBPENTOBI_MCTS_FLOAT_TYPE float) endif() # Don't set the Pentobi data dirs on Windows. This is currently needed for # building a version of Pentobi for the NSIS installer on Windows (see # directory windows_installer) such that Pentobi will look for data dirs # relative to the installation directory. (It breaks installing Pentobi on # Windows with "make install" but we don't support that on Windows anyway.) if(UNIX) if(NOT DEFINED PENTOBI_BOOKS_DIR) set(PENTOBI_BOOKS_DIR "${CMAKE_INSTALL_FULL_DATADIR}/pentobi/books") endif() if(NOT DEFINED PENTOBI_MANUAL_DIR) set(PENTOBI_MANUAL_DIR "${CMAKE_INSTALL_FULL_DOCDIR}/manual") endif() if(NOT DEFINED PENTOBI_TRANSLATIONS) set(PENTOBI_TRANSLATIONS "${CMAKE_INSTALL_FULL_DATADIR}/pentobi/translations") endif() endif(UNIX) configure_file(config.h.in config.h) add_definitions(-DHAVE_CONFIG_H) include_directories(${CMAKE_CURRENT_BINARY_DIR}) if(PENTOBI_BUILD_TESTS) enable_testing() endif() if(PENTOBI_BUILD_GUI) if (USE_QT5) find_package(Qt5Widgets REQUIRED) find_package(Qt5Concurrent REQUIRED) find_package(Qt5LinguistTools REQUIRED) else() find_package(Qt4 COMPONENTS QtCore QtGui REQUIRED) endif() endif() add_custom_target(dist COMMAND git archive --prefix=pentobi-${PENTOBI_VERSION}/ HEAD | gzip --best > ${CMAKE_BINARY_DIR}/pentobi-${PENTOBI_VERSION}.tar.gz WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) add_custom_target(post-install COMMAND update-mime-database ${CMAKE_INSTALL_FULL_DATAROOTDIR}/mime\; update-desktop-database ${CMAKE_INSTALL_FULL_DATAROOTDIR}/applications\; ) install(FILES README NEWS COPYING doc/blksgf/Pentobi-SGF.html DESTINATION ${CMAKE_INSTALL_DOCDIR}) add_subdirectory(doc) add_subdirectory(src) add_subdirectory(data) add_subdirectory(windows_installer) pentobi-7.2/COPYING000066400000000000000000001063151227240712600140660ustar00rootroot00000000000000Copyright (C) 2011-2014 Markus Enzenberger Pentobi is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Pentobi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. A copy of the GNU General Public License version 3 is appended below. Trademark disclaimer: The trademark Blokus and other trademarks referred to are property of their respective trademark holders. The trademark holders are not affiliated with the author of the program Pentobi. ------------------------------------------------------------------------------ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . pentobi-7.2/INSTALL000066400000000000000000000044041227240712600140600ustar00rootroot00000000000000This file explains how to compile and install Pentobi from the sources. == Requirements == Pentobi requires the Qt libraries (>= 4.8). The C++ compiler needs to support certain C++11 features (only features that are already implemented by GCC 4.7 and MSVC 2012). For creating cross-platform makefiles, CMake (>= 2.8.9) is required. Ubuntu 13.04 provides suitable versions of the required tools and libraries in its package repository. The packages can be installed with the shell command: sudo apt-get install cmake g++ libqt4-dev make qt4-default == Building == Pentobi can be compiled with cmake using the shell command: cmake . && make === Building with Qt 5 === To build Pentobi with Qt 5 instead of Qt 4, use the cmake option -DUSE_QT5=ON and make sure that the development packages for Qt 5 are installed (on Ubuntu 13.04: qt5-default, qttools5-dev, qttools5-dev-tools). The KDE thumbnailer plugin (see below) cannot be compiled with Qt 5. === Building with Boost.Thread === Because of a non-functional C++11 threads implementation on MinGW GCC 4.7 (and potentially other platforms), it is still possible to compile Pentobi using Boost.Thread instead of C++11 threads by using the cmake option -DUSE_BOOST_THREAD=ON. In this case, the Boost libraries (>= 1.49) need to be installed. === Building the KDE thumbnailer plugin === A thumbnailer plugin for KDE can be built by using the cmake option -DPENTOBI_BUILD_KDE_THUMBNAILER=ON. In this case, the KDE development files need to be installed (package kdelibs5-dev on Ubuntu 13.04). Note that on Ubuntu 13.04, the plugin will not be found if the default installation prefix /usr/local is used. You need to add KDEDIRS=/usr/local to /etc/environment. After that, you can enable previews for Blokus game file in the Dolphin file manager in "Configure Dolphin/General/Previews". == Installing == On Linux, Pentobi can be installed after compilation with the shell command: sudo make install After installation, the system-wide databases should be updated to make Pentobi appear in the desktop menu and register it as handler for Blokus files (*.blksgf). The databases can be updated with the shell command: sudo make post-install This make target assumes that the commands update-desktop-database and update-mime-database exist. pentobi-7.2/NEWS000066400000000000000000000223611227240712600135300ustar00rootroot00000000000000Version 7.2 (30 Apr 2014) ========================= * Hyphens used as minus signs in manpage (bug #9) * Added keywords section to desktop entry to silence lintian warning (bug #10) * Fixed a compilation error with GCC 4.8.2 on PowerPC (and other big-endian systems) * Fixed wrong arguments to update-mime-database/update-desktop-database when runing "make post-install" * Improved a blurry menu item icon * Fixed a compilation warning about a missing translation * Reduced the sizes of the generated and installed translation files. * Fixed a compilation error on 64-bit Linux with X32 ABI * Fixed a compilation error with Cygwin Version 7.1 (13 Aug 2013) ========================= * Fixed the version string. The released file pentobi-7.0.tar.gz was erroneously built from git version c5247c56 just before the version tagged with v7.0 and contained the version string 6.UNKNOWN * The color played by the human in rated games is now randomly assigned * The mouse wheel is now disabled while the computer is thinking Version 7.0 (25 Jun 2013) ========================= * Support for compilation with version 5 of the Qt libraries (see INSTALL for details) * Slightly increased playing strength at higher levels (mainly in game variant Duo) * The default settings in game variants with more than two players are now that the human plays the first color and the computer all other colors * Fixed a crash that could occur if the window was put in fullscreen mode by a method of the window manager (e.g. title bar menu on KDE) and then returned to normal mode by a different method (e.g. pressing Escape) Version 6.0 (4 Mar 2013) ======================== * Increased playing strength at higher levels. The search algorithm used for move generation is now parallelized and can take advantage of multi-core CPUs (up to 4 cores). There is a new playing level 8, which has a 2 GHz dual-core CPU or faster as the recommended system requirement. * New menu item Toolbar Text to configure the toolbar button appearance independent of the system settings * More SGF game info properties (event, round, time) were added to the game info dialog * The source code now requires at least GCC 4.7 (because a larger subset of C++11 features is used) * The CMake module GNUInstallDirs is now used for setting the installation directories on Unix. Note that the defaults for bindir and datadir are now CMAKE_INSTALL_PREFIX/bin and CMAKE_INSTALL_PREFIX/share instead of CMAKE_INSTALL_PREFIX/games and CMAKE_INSTALL_PREFIX/share/games. They can be changed by setting CMAKE_INSTALL_BINDIR and CMAKE_INSTALL_DATADIR (bug #7) * The source code no longer depends on the Boost libraries. However, it is still possible to use Boost.Thread instead of std::thread by configuring with USE_BOOST_THREAD=ON (e.g. needed on MinGW GCC 4.7, which has no functional implementation of std::thread) * Thumbnailer registration for blksgf files is no longer supported for Gnome 2 Version 5.0 (10 Dec 2012) ========================= * Small increase in overall playing strength at higher levels in all game variants (especially Trigon) * The computer now knows about the possibility of rotational-symmetric tied games in game variant Trigon Two-Player (like it already knew in the variants Duo and Junior) and will prevent the second player from enforcing such a tie * If the move generation takes longer than 10 seconds, the maximum remaining time is now shown in the status bar * Removed less frequently used buttons (Open, Save) from the tool bar * Re-organized menu bar * The menu bar and tool bar are no longer shown in fullscreen mode * Avoided some window flickering at startup Version 4.0 (4 Oct 2012) ======================== * New menu item "Beginning of Branch" * The rating dialog now also shows the best previous rating and has a button to reset the rating * A thumbnail plugin for KDE can be built by using the CMake option -DPENTOBI_BUILD_KDE_THUMBNAILER=ON * Replaced the icons with less colorful ones. All icons are now licensed under the GPLv3+ and include SVG sources. No icons from the Tango icon set are used anymore. Version 3.0 (1 Aug 2012) ======================== * New functionality to compute a player rating for the user by playing rated games against the computer * Different options for speed of game analysis * New menu item "Play Single Move" to make the computer play a move without changing the colors played by the computer * The mouse wheel can now be used to navigate in the current variation if no piece is selected * Files written by older versions of Pentobi that use a deprecated format for move properties are now automatically converted to the current format on write Version 2.0 (22 May 2012) ========================= * No more popup messages if a color has no more moves; instead, score points of this color are underlined (feature request #3431031) * Newly supported game variant Junior * Improved playing strength. Number of levels increased to 7. Level 7 is about the same speed as the old level 6 but stronger. * New game analysis function that shows a graph with the estimated value of each position in a game (menu item "Computer/Analyze Game") * Support for setup properties in blksgf files (note that files with setup properties cannot be read by older versions of Pentobi). A new setup mode can be used to create files that start with a setup position including positions that cannot occur in real games (e.g. for puzzles or Blokus art) * New menu items for editing the game tree: "Delete All Variations", "Keep Only Position", "Keep Only Subtree", "Move Variation Up/Down", "Truncate Children" * Variations are now displayed by appending a letter to the move number instead of underlining * Added a toolbar button for fast selection of the computer colors without having to use the window menu. * User manual is no longer compiled into the resources of the executable but installed in the installation data directory * Open a console for stderr output on Windows if Pentobi is invoked with option --verbose * New option --memory to make Pentobi run on systems with low memory at the cost of reduced playing strength. * Use standard icons from theme Version 1.0 (1 Jan 2012) ======================== * Support for game variant Trigon Three-Player * Change directory for autosave file to use AppData (on Windows) or XDG_DATA_HOME (on other systems) * Changed Back to Main Variation to go to the last move in the main variation that had a variation, not to the last position in the main variation * Changed variation string in status bar to contain information about the move numbers at the branching points * Fixed small rendering errors * New menu item Find Next Comment * Added chapters about the main window and menu items to the user manual * Fix bug: computer color dialog did not set colors correctly in game variant Trigon * Show error message instead of crashing if the SGF file contains invalid move properties * Lowered the required version of the Boost libraries in CMakeLists.txt from 1.45 to 1.40 such that Pentobi can be compiled on Debian 6.0. Note: some versions of Boost cause compilation errors if used with certain versions of GCC and option -std=c++0x (e.g. the combinations GCC 4.4/Boost 1.40 in Ubuntu 10.04 and GCC 4.4/Boost 1.42 in Debian 6.0 work but the combination GCC 4.5/Boost 1.42 in Ubuntu 11.04 causes errors). * Changed installation directories according to Filesystem Hierarchy Standard (/usr/bin to /usr/games, /usr/share to /usr/share/games) * New CMake option PENTOBI_REGISTER_GNOME2_THUMBNAILER for disabling the installation of files for registering the Pentobi thumbnailer on Gnome 2 * Install man pages for pentobi and pentobi-thumbnailer on Unix systems Version 0.3 (2 Dec 2011) ======================== * Support for the game variants Trigon and Trigon Two-Player * Fixed saving/opening files if file name contained non-ASCII characters and the system used an encoding other than Latin1 * The score numbers now show the total player and color scores instead of on-board and bonus points separatedly (feature request #3431039) * New menu item "Edit/Select Next Color" that allows to enter moves independent of the color to play on the board (feature request #3441299) * Slightly changed file format to use single-valued move properties as used in other games supported by SGF. Files written by Pentobi 0.2 can still be read. Version 0.2 (17 Oct 2011) ========================= * German translation * Display sum score for both player colors in game variant Classic Two-Player * Slightly changed file format to conform to the proposed version 5 of SGF that requires digits for move properties in multi-player games. Files written by Pentobi 0.1 can still be read. * Support for move annotation symbols * Store and edit additional game information (player names, date) * New menu items Ten Moves Backward/Forward, Go to Move, Undo Move * Underline move numbers if there are alternative variations * Show move number, total number of moves and current variation in status bar * Faster play in higher levels, especially of opening moves * Make thumbnailer for Blokus files work under Gnome 3 * Fix broken compilation with GCC 4.6.1 (bug #3420555) Version 0.1 (15 Jul 2011) ========================= Initial release. pentobi-7.2/README000066400000000000000000000005671227240712600137150ustar00rootroot00000000000000Pentobi is a computer opponent for the board game Blokus. Copyright (C) 2011-2014 Markus Enzenberger See the file COPYING for license information. See the file INSTALL for instructions about how to build and install the program from the sources. See the file NEWS for release notes. The homepage of Pentobi is at http://pentobi.sourceforge.net/ pentobi-7.2/config.h.in000066400000000000000000000015361227240712600150550ustar00rootroot00000000000000/* Define to 1 if you have the header file. */ #cmakedefine01 HAVE_UNISTD_H /* Define to 1 if you have the header file. */ #cmakedefine01 HAVE_SYS_TIMES_H /* Define to 1 if you have the header file. */ #cmakedefine01 HAVE_SYS_SYSCTL_H /* Version number of package */ #define VERSION "@PENTOBI_VERSION@" /* Floating type for Monte-Carlo tree search values (float|double) */ #define LIBPENTOBI_MCTS_FLOAT_TYPE @LIBPENTOBI_MCTS_FLOAT_TYPE@ /* Directory containing opening books. */ #cmakedefine PENTOBI_BOOKS_DIR "@PENTOBI_BOOKS_DIR@" /** Location of the Pentobi user manual. */ #cmakedefine PENTOBI_MANUAL_DIR "@PENTOBI_MANUAL_DIR@" /** Location of the translations directory. */ #cmakedefine PENTOBI_TRANSLATIONS "@PENTOBI_TRANSLATIONS@" /** Use Boost.Thread instead of std::thread */ #cmakedefine USE_BOOST_THREAD pentobi-7.2/data/000077500000000000000000000000001227240712600137365ustar00rootroot00000000000000pentobi-7.2/data/CMakeLists.txt000066400000000000000000000025131227240712600164770ustar00rootroot00000000000000configure_file(pentobi.desktop.in pentobi.desktop @ONLY) configure_file(pentobi.thumbnailer.in pentobi.thumbnailer @ONLY) install(FILES ../src/pentobi/icons/pentobi.png DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48/apps) install(FILES ../src/pentobi/icons/pentobi-16.png DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps RENAME pentobi.png) install(FILES ../src/pentobi/icons/pentobi-32.png DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/32x32/apps RENAME pentobi.png) install(FILES ../src/pentobi/icons/pentobi.svg DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/apps) install(FILES application-x-blokus-sgf.png DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/48x48/mimetypes) install(FILES application-x-blokus-sgf-16.png DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/mimetypes RENAME application-x-blokus-sgf.png) install(FILES application-x-blokus-sgf.svg DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/mimetypes) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pentobi.desktop DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pentobi.thumbnailer DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/thumbnailers) install(FILES pentobi-mime.xml DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/mime/packages) pentobi-7.2/data/application-x-blokus-sgf-16.png000066400000000000000000000011761227240712600215170ustar00rootroot00000000000000‰PNG  IHDRѓџasBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<tEXtAuthorJakub Steinerцћї/!tEXtSourcehttp://jimmac.musichall.czifу^ItEXtCopyrightPublic Domain http://creativecommons.org/licenses/publicdomain/YУўЪYIDAT8•“?KQХїБA! a%}RЄ“є›"Љї Xлi-V ƒ "Bђ,э ЄM 1љ ЎЅ…љу;8›™wOŠŒГћєtїОѓ~œч™$ііwПLЇХ[ ЅЅх“lАѓIdУbŒКЏbŒЪ†IЂsC !№чъС УРъCrсrVКЯ›$љXЭe ˜е@& 8СCђ”`f<:x?[T'[нфBoyЃ№їјxЖмZчѓх‡f|ѓb3ЄyЬаdBЋЬ’1рgg‰a”cб3ЏЮТЬгЃа@Њј§БЯГњЌŠ/[Њђ /~р^сБњ|њ:j<лkЋ- $ђёwŠ|„{IŒ%“ы’EЯн $ђё)Х46Лѓ‹1‹žVРуW?y2WЄУзu %$GоpеM 2tG•oИЇo“ћ вџ_sИ›=ј;ќ”_Ю’ЋЛfGIENDЎB`‚pentobi-7.2/data/application-x-blokus-sgf-16.svg000066400000000000000000001077661227240712600215460ustar00rootroot00000000000000 image/svg+xml text plaintext regular script shell bash python perl php ruby Jakub Steiner http://jimmac.musichall.cz pentobi-7.2/data/application-x-blokus-sgf.png000066400000000000000000000026141227240712600212710ustar00rootroot00000000000000‰PNG  IHDR00Wљ‡sBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<tEXtAuthorJakub Steinerцћї/!tEXtSourcehttp://jimmac.musichall.czifу^ItEXtCopyrightPublic Domain http://creativecommons.org/licenses/publicdomain/YУўЪgIDAThэZA‹Eў^л„ьъd Š(Ц%9xђ ž§АFЬiQDљё xO"x№Aᛇ ž=ŠЃˆЦeГгН3нѕŠ}'f€Eš–ѓ$НЬBМ”ЄПЌтZbYhzTa^LР %I/ВPЛєЗѓБДЦ` " }w§— ‰k?<ђt{oXшЯony§Œžп€3ђ§)ˆІQ—B,ДўФ‡ƒБSJ]"n;i ‰ФBЋБ“ž>‘(z4%=2 ‘…†b'vІOLy%š…L$ŠRFˆ\MщKYh(v2ФaЪƒАЖы_уЬХnQj `mд­~yžїW›ЪЯГ6ГNр)’0…œuр9Д1sгСм;GЋ7С3‡m9Оb$-z*љЕL§РaжЏСцЛS‚Нcg љuк‚АUe@_У*–lox›ю9EVкpыљЭG“ ˜‡? O †7њюNЭ}нр0‰Ј €с,‰Єo1ХШЪ\Ч.ЛЩЧЮЛ‡{&p#"€<Я‹Ђ,ж6ЮФб*B”GGШѓ\ќdэМіњіM'Кб  Ш2к‘щџі8a9ѕўiІFІKUЬIENDЎB`‚pentobi-7.2/data/application-x-blokus-sgf.svg000066400000000000000000001474321227240712600213140ustar00rootroot00000000000000 image/svg+xml text plaintext regular script shell bash python perl php ruby Jakub Steiner http://jimmac.musichall.cz pentobi-7.2/data/pentobi-mime.xml000066400000000000000000000014421227240712600170460ustar00rootroot00000000000000 Blokus game Blokus-Partie pentobi-7.2/data/pentobi.desktop.in000077500000000000000000000006351227240712600174050ustar00rootroot00000000000000[Desktop Entry] Name=Pentobi GenericName=Computer Opponent for Blokus GenericName[de]=Computer-Gegner fУМr Blokus Comment=Computer opponent for the board game Blokus Comment[de]=Computer-Gegner fУМr das Brettspiel Blokus Keywords=Blokus;Blokus Duo;Blokus Trigon;Blokus Junior; Exec=@CMAKE_INSTALL_FULL_BINDIR@/pentobi %f Icon=pentobi Type=Application Categories=Game;BoardGame; MimeType=application/x-blokus-sgf; pentobi-7.2/data/pentobi.thumbnailer.in000066400000000000000000000001741227240712600202410ustar00rootroot00000000000000[Thumbnailer Entry] Exec=@CMAKE_INSTALL_FULL_BINDIR@/pentobi-thumbnailer --size %s %i %o MimeType=application/x-blokus-sgf; pentobi-7.2/doc/000077500000000000000000000000001227240712600135725ustar00rootroot00000000000000pentobi-7.2/doc/CMakeLists.txt000066400000000000000000000000261227240712600163300ustar00rootroot00000000000000add_subdirectory(man) pentobi-7.2/doc/blksgf/000077500000000000000000000000001227240712600150425ustar00rootroot00000000000000pentobi-7.2/doc/blksgf/Pentobi-SGF.html000066400000000000000000000177071227240712600177610ustar00rootroot00000000000000 Pentobi SGF Files

Pentobi SGF Files

Author: Markus Enzenberger

This document describes the file format for Blokus game records as used by the program Pentobi. The most recent version of this document can be found in the source code distribution of Pentobi in the folder pentobi/doc/blksgf.

Introduction

The file format is a derivative of the Smart Game Format (SGF). The current SGF version 4 does not define standard properties for Blokus. Therefore, a number of game-specific properties and value types had to be defined. The definitions follow the recommendations of SGF 4 and the proposals for multi-player games from the discussions about the future SGF version 5.

File Extension and MIME Type

The file extension .blksgf and the MIME type application/x-blokus-sgf are used for Blokus SGF files.

Note
Since this is a non-standard MIME type, links to Blokus SGF files on web servers will not automatically open the file with Pentobi even if Pentobi is installed locally and registered as a handler for Blokus SGF files. To make this work, you can put a file named .htaccess on the web server in the same directory that contains the .blksgf files or in one of its parent directories. This file needs to contain the line:

AddType application/x-blokus-sgf blksgf

Character Set

Although not specific to Blokus, it is recommended to use UTF-8 as the character set. Pentobi always writes files in UTF-8 and indicates that with the CA property. Pentobi can read SGF files encoded in UTF-8 or ISO-8859-1 (Latin1). Other character sets are currently not supported. As specified by the SGF standard, ISO-8859-1 is assumed for files without CA property.

Game Property

Since there is no number for Blokus defined in SGF 4, a string instead of a number is used as the value for the GM property. Currently, the following strings are used:

  • Blokus
  • Blokus Two-Player
  • Blokus Duo
  • Blokus Trigon
  • Blokus Trigon Two-Player
  • Blokus Trigon Three-Player
  • Blokus Junior
The strings are case-sensitive, words are separated by exactly one space and no additional whitespace at the beginning or end of the string is allowed.

Color and Player Properties

In game variants with two players and two colors, B denotes the first player or color, W the second player or color. In game variants with three or four players and one color per player, 1, 2, 3, 4 denote the first, second, third, and fourth player or color. In game variants with two players and four colors, B denotes the first player, W the second player, and 1, 2, 3, 4 denote the first, second, third, and fourth color. This applies to move properties and properties related to a player or a color.

Example 1: in the game variant Blokus Two-Player PW is the name of the first player, and 1 is a move of the first color.

Example 2: in the game variant Blokus Two-Player, one could either use the BL, WL properties to indicate the time left for a player, if the game is played with a time limit for each player, or one could use the 1L, 2L, 3L, 4L properties to indicate the time left for a color, if the game is played with a time limit for each color. (This is only an example how the properties should be interpreted. Pentobi currently has no support for game clocks.)

Note
Old versions of Pentobi (before version 0.2) used the properties BLUE, YELLOW, RED, GREEN in the four-color game variants, which did not reflect the current state of discussion for SGF 5. Current versions of Pentobi can still read games containing the old properties but they are deprecated and should no longer be used.

Coordinate System

Fields on the board (called points in SGF) are identified by a case-insensitive string with a letter for the column followed by a number for the row. The letters start with 'a', the numbers start with '1'. The lower left corner of the board is 'a1'. The strings are not allowed to contain whitespaces. Note that, unlike the common convention in the game of Go, the letter 'i' is used.

If there are more than 26 columns, the columns continue with 'aa', 'ab', ..., 'ba', 'bb', ... More than 26 columns are presently required for Trigon and could also be required for future game variants on rectangular boards larger than 26×26.

For Trigon, hexagonal boards are mapped to rectangular coordinates as in the following example of a hexagon with edge size 3:

       6     / \ / \ / \ / \
       5   / \ / \ / \ / \ / \
       4 / \ / \ / \ / \ / \ / \
       3 \ / \ / \ / \ / \ / \ /
       2   \ / \ / \ / \ / \ /
       1     \ / \ / \ / \ /
          a b c d e f g h i j k
      

Move Properties

The value of a move property is a string with the coordinates of the played piece on the board separated by commas. No whitespace characters are allowed before, after, or in-between the coordinates.

Example: B[f9,e10,f10,g10,f11]

Note
Old versions of Pentobi (before version 0.3) used to represent moves by a list of points, which did not follow the convention used by other games in SGF to use single-value properties for moves. Current versions of Pentobi can still read games containing the old move property values but they are deprecated and should no longer be used.

Setup Properties

The setup properties AB, AW, A1, A2, A3, A4 can be used to place pieces in one step on the board. The setup property AE can be used to remove a piece from the board. All these properties can have multiple values, each value represents a piece by its coordinates as in the move properties. The PL can be used to set the color to play in a setup position.

Example:
AB[e8,e9,f9,d10,e10][g6,f7,g7,h7,g8]
AW[i4,h5,i5,j5,i6][j7,j8,j9,k9,j10]
PL[B]

Note
Older versions of Pentobi (before version 2.0) did not support setup properties, you need a newer version of Pentobi to read such files.

pentobi-7.2/doc/doxygen/000077500000000000000000000000001227240712600152475ustar00rootroot00000000000000pentobi-7.2/doc/doxygen/Doxyfile000066400000000000000000001557071227240712600167740ustar00rootroot00000000000000# Doxyfile 1.5.5 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = Pentobi # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, # Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, # Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, # and Ukrainian. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = YES # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = YES # If the QT_AUTOBRIEF tag is set to YES then Doxygen will # interpret the first line (until the first dot) of a Qt-style # comment as the brief description. If set to NO, the comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for # Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. # Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES # When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum # is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically # be useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. TYPEDEF_HIDES_STRUCT = NO #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base # name of the file that contains the anonymous namespace. By default # anonymous namespace are hidden. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the # hierarchy of group names into alphabetical order. If set to NO (the default) # the group names will appear in their defined order. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or define consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # If the sources in your project are distributed over multiple directories # then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy # in the documentation. The default is NO. SHOW_DIRECTORIES = NO # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be abled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = ../../src # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 FILE_PATTERNS = *.h *.cpp # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER # is applied to all files. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES (the default) # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES (the default) # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. Otherwise they will link to the documentstion. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = NO # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = footer.html # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, # files or namespaces will be aligned in HTML using tables. If set to # NO a bullet list will be used. HTML_ALIGN_MEMBERS = YES # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_DOCSET tag is set to YES, additional index files # will be generated that can be used as input for Apple's Xcode 3 # integrated development environment, introduced with OSX 10.5 (Leopard). # To create a documentation set, doxygen will generate a Makefile in the # HTML output directory. Running make will produce the docset in that # directory and running "make install" will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find # it at startup. GENERATE_DOCSET = NO # When GENERATE_DOCSET tag is set to YES, this tag determines the name of the # feed. A documentation feed provides an umbrella under which multiple # documentation sets from a single provider (such as a company or product suite) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, this tag specifies a string that # should uniquely identify the documentation set bundle. This should be a # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.doxygen.Project # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. For this to work a browser that supports # JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox # Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). HTML_DYNAMIC_SECTIONS = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO # This tag can be used to set the number of enum values (range [1..20]) # that doxygen will group on one line in the generated HTML documentation. ENUM_VALUES_PER_LINE = 4 # If the GENERATE_TREEVIEW tag is set to YES, a side panel will be # generated containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, # Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are # probably better off using the HTML help feature. GENERATE_TREEVIEW = NO # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4wide # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = YES # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = YES # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. This is useful # if you want to understand what is going on. On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = HAVE_BOOST_THREAD # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such # function macros are typically used for boiler-plate code, and will confuse # the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option is superseded by the HAVE_DOT option below. This is only a # fallback. It is recommended to install and use dot, since it yields more # powerful graphs. CLASS_DIAGRAMS = YES # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see # http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = NO # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT options are set to YES then # doxygen will generate a call dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = NO # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The MAX_DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of # nodes that will be shown in the graph. If the number of nodes in a graph # becomes larger than this value, doxygen will truncate the graph, which is # visualized by representing a node as a red box. Note that doxygen if the # number of direct children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is enabled by default, which results in a transparent # background. Warning: Depending on the platform used, enabling this option # may lead to badly anti-aliased labels on the edges of a graph (i.e. they # become hard to read). DOT_TRANSPARENT = YES # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Configuration::additions related to the search engine #--------------------------------------------------------------------------- # The SEARCHENGINE tag specifies whether or not a search engine should be # used. If set to NO the values of all tags below this one will be ignored. SEARCHENGINE = NO pentobi-7.2/doc/doxygen/footer.html000066400000000000000000000002111227240712600174250ustar00rootroot00000000000000


$date Doxygen $doxygenversion

pentobi-7.2/doc/man/000077500000000000000000000000001227240712600143455ustar00rootroot00000000000000pentobi-7.2/doc/man/CMakeLists.txt000066400000000000000000000004101227240712600171000ustar00rootroot00000000000000configure_file(pentobi.6.in pentobi.6 @ONLY) configure_file(pentobi-thumbnailer.6.in pentobi-thumbnailer.6 @ONLY) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pentobi.6 ${CMAKE_CURRENT_BINARY_DIR}/pentobi-thumbnailer.6 DESTINATION ${CMAKE_INSTALL_MANDIR}/man6) pentobi-7.2/doc/man/pentobi-thumbnailer.6.in000066400000000000000000000016271227240712600210170ustar00rootroot00000000000000.TH PENTOBI-THUMBNAILER 6 "2011-12-08" "Pentobi @PENTOBI_VERSION@" "Pentobi command reference" .SH NAME pentobi-thumbnailer \- thumbnailer for game records for the board game Blokus as used by the program Pentobi .SH SYNOPSIS .B pentobi-thumbnailer .RI [ options ] " input-file output-file" .br .SH DESCRIPTION .B pentobi-thumbnailer is part of the program Pentobi and intended to be used as a thumbnailer for the Gnome desktop environment to generate previews of game files written by Pentobi. The input file is a game file in Pentobi's SGF format as documented in doc/blksgf/Pentobi-SGF.html in the Pentobi source package. The output file is a thumbnail in PNG format. .SH OPTIONS .TP .B \-s, \-\-size The size of the thumbnail. The default is 128. .SH EXIT STATUS .TP 0 if the thumbnail generation succeeds, 1 on error. .SH SEE ALSO .BR pentobi (6) .SH AUTHOR Markus Enzenberger pentobi-7.2/doc/man/pentobi.6.in000066400000000000000000000034401227240712600165020ustar00rootroot00000000000000.TH PENTOBI 6 "2012-12-02" "Pentobi @PENTOBI_VERSION@" "Pentobi command reference" .SH NAME pentobi \- computer opponent for the board game Blokus .SH SYNOPSIS .B pentobi .RI [ options ] " [file]" .br .SH DESCRIPTION .B pentobi is the command to invoke the program Pentobi, which is a graphical user interface and computer opponent to play the board game Blokus. The command can take the name of a game file to open at startup as an optional argument. The game file is expected to be in Pentobi's SGF format as documented in doc/blksgf/Pentobi-SGF.html in the Pentobi source package. .SH OPTIONS .TP .B \-\-memory The amount of memory in bytes to be used by the search. If this option is not used, it depends on the amount of physical memory available on the system how much memory Pentobi will allocate (currently up to 768 MB). If the memory for the search is too low, the playing strength at higher playing levels will be impaired. This can be checked by running Pentobi with option \-\-verbose in a game variant with a high number of Monte-Carlo simulations per second (e.g. Duo). If a message that the search tree was pruned occurs more than a few times during a move generation, the playing strength is likely to be affected. .TP .B \-\-threads The number of threads to use in the search. By default, up to 4 threads are used in the search depending on the number of hardware threads supported by the current system. Using more threads will speed up the move generation but using a very high number of threads (e.g. more than 8) can degrade the playing strength in higher playing levels. .TP .B \-\-verbose Print internal information about the move generation and other debugging information to standard error. .SH SEE ALSO .BR pentobi-thumbnailer (6) .SH AUTHOR Markus Enzenberger pentobi-7.2/src/000077500000000000000000000000001227240712600136145ustar00rootroot00000000000000pentobi-7.2/src/CMakeLists.txt000066400000000000000000000014341227240712600163560ustar00rootroot00000000000000add_subdirectory(books) add_subdirectory(libboardgame_sys) add_subdirectory(libboardgame_util) add_subdirectory(libboardgame_sgf) add_subdirectory(libboardgame_base) add_subdirectory(libpentobi_base) add_subdirectory(libpentobi_mcts) if (PENTOBI_BUILD_GTP) add_subdirectory(libboardgame_gtp) add_subdirectory(pentobi_gtp) endif() if (PENTOBI_BUILD_TESTS) add_subdirectory(libboardgame_test) add_subdirectory(libboardgame_test_main) add_subdirectory(unittest) endif() if (PENTOBI_BUILD_GUI) add_subdirectory(libpentobi_gui) add_subdirectory(libpentobi_thumbnail) add_subdirectory(pentobi_thumbnailer) add_subdirectory(pentobi) if(PENTOBI_BUILD_KDE_THUMBNAILER) add_subdirectory(libpentobi_kde_thumbnailer) add_subdirectory(pentobi_kde_thumbnailer) endif() endif() pentobi-7.2/src/books/000077500000000000000000000000001227240712600147315ustar00rootroot00000000000000pentobi-7.2/src/books/CMakeLists.txt000066400000000000000000000005571227240712600175000ustar00rootroot00000000000000# Install the opening book files. If you change the destination, you need to # update the default for PENTOBI_BOOKS_DIR in the main CMakeLists.txt install(FILES book_classic.blksgf book_classic_2.blksgf book_duo.blksgf book_junior.blksgf book_trigon.blksgf book_trigon_2.blksgf book_trigon_3.blksgf DESTINATION ${CMAKE_INSTALL_DATADIR}/pentobi/books) pentobi-7.2/src/books/book_classic.blksgf000066400000000000000000000003071227240712600205560ustar00rootroot00000000000000( ;GM[Blokus]CA[UTF-8] ( ;BLUE[a17][b17][a18][a19][a20]TE[1] ;YELLOW[s17][t17][t18][t19][t20]TE[1] ;RED[t1][t2][t3][s4][t4]TE[1] ) ( ;BLUE[b17][a18][b18][a19][a20]TE[1] ) ) pentobi-7.2/src/books/book_classic_2.blksgf000066400000000000000000000715261227240712600210120ustar00rootroot00000000000000( ;GM[Blokus Two-Player] CA[UTF-8] ( ;1[a17,b17,a18,a19,a20] TE[1] ( ;2[s17,t17,t18,t19,t20] TE[1] ( ;3[t1,t2,s3,t3,s4] TE[1] ( ;4[a1,b1,c1,d1,d2] TE[1] ( ;1[d14,e14,d15,c16,d16] TE[1] ( ;2[p14,q14,q15,q16,r16] TE[1] ( ;3[r5,p6,q6,r6,p7] TE[1] ;4[e3,e4,f4,g4,g5] TE[1] ;1[h11,g12,h12,f13,g13] TE[1] ;2[o11,p11,n12,o12,o13] TE[2] ;3[o8,m9,n9,o9,p9] TE[1] ;4[h6,h7,i7,i8,j8] TE[1] ;1[j9,k9,l9,i10,j10] TE[2] ;2[k10,l10,m10,n10,m11] TE[1] ;3[q10,q11,q12,p13,q13] TE[1] ) ( ;3[r5,q6,r6,p7,q7] TE[1] ;4[e3,f3,f4,g4,g5] TE[1] ;1[g11,f12,g12,h12,f13] TE[1] ;2[o11,p11,n12,o12,o13] TE[1] ;3[n8,o8,n9,m10,n10] TE[1] ;4[h6,h7,i7,j7,i8] TE[1] ;1[k10,l10,i11,j11,k11] TE[1] ;2[l12,k13,l13,m13,l14] TE[1] ;3[p9,q9,q10,r10,q11] TE[1] ;4[l7,k8,l8,m8,l9] TE[1] ) ) ( ;2[p14,p15,q15,q16,r16] TE[1] ;3[r5,p6,q6,r6,p7] TE[1] ( ;4[e3,e4,f4,g4,g5] TE[1] ;1[h11,g12,h12,f13,g13] TE[1] ;2[o11,p11,n12,o12,o13] TE[1] ;3[o8,m9,n9,o9,p9] TE[1] ;4[h6,h7,i7,i8,j8] ;1[j9,k9,l9,i10,j10] TE[2] ;2[k10,l10,m10,n10,m11] TE[1] ;3[q10,q11,q12,p13,q13] TE[2] ) ( ;4[e3,f3,f4,g4,g5] TE[1] ) ) ) ( ;1[e14,d15,e15,c16,d16] TE[1] ) ) ( ;4[a1,a2,b2,c2,c3] TE[1] ;1[d14,e14,d15,c16,d16] TE[1] ;2[p14,q14,q15,q16,r16] TE[1] ( ;3[r5,p6,q6,r6,p7] TE[1] ;4[d4,d5,e5,e6,f6] TE[1] ( ;1[h11,g12,h12,f13,g13] TE[1] ;2[o11,p11,n12,o12,o13] TE[2] ;3[o8,m9,n9,o9,p9] TE[1] ;4[h6,g7,h7,h8,i8] TE[1] ( ;1[j9,k9,l9,i10,j10] TE[2] ) ( ;1[k9,l9,i10,j10,k10] TE[2] ) ) ( ;1[g11,h11,f12,g12,f13] TE[1] ;2[o11,p11,n12,o12,o13] TE[1] ;3[o8,o9,n10,o10,p10] TE[1] ;4[g7,h7,h8,i8,j8] TE[1] ;1[i10,j10,k10,l10,m10] TE[1] ) ) ( ;3[r5,q6,r6,p7,q7] TE[1] ;4[d4,d5,e5,e6,f6] TE[1] ;1[g11,f12,g12,h12,f13] TE[1] ;2[m11,n11,n12,o12,o13] TE[1] ;3[o8,o9,m10,n10,o10] TE[1] ;4[g7,h7,h8,i8,j8] TE[1] ;1[k10,l10,i11,j11,k11] TE[1] ;2[i12,j12,k12,l12,j13] TE[1] ;3[p11,p12,q12,r12,p13] TE[1] ;4[k7,l7,m7,n7,o7] TE[1] ) ) ( ;4[a1,b1,b2,b3,c3] TE[1] ;1[d14,e14,d15,c16,d16] TE[1] ;2[p14,q14,q15,q16,r16] TE[1] ;3[r5,p6,q6,r6,p7] TE[1] ) ( ;4[a1,a2,a3,b3,c3] TE[1] ;1[d14,e14,d15,c16,d16] TE[1] ;2[p14,q14,q15,q16,r16] TE[1] ;3[r5,p6,q6,r6,p7] TE[1] ;4[d4,e4,e5,e6,f6] TE[1] ;1[h11,g12,h12,f13,g13] TE[1] ;2[o11,p11,n12,o12,o13] TE[2] ;3[o8,m9,n9,o9,p9] TE[1] ;4[g7,h7,h8,i8,j8] TE[1] ( ;1[j9,k9,l9,i10,j10] TE[2] ;2[k10,l10,m10,n10,m11] TE[1] ;3[q10,q11,q12,p13,q13] TE[1] ) ( ;1[k9,l9,i10,j10,k10] TE[2] ;2[l10,m10,n10,m11] TE[1] ;3[q10,q11,q12,p13,q13] TE[1] ) ) ) ( ;3[t1,t2,t3,s4,t4] TE[1] ( ;4[a1,b1,c1,d1,d2] TE[1] ;1[d14,e14,d15,c16,d16] TE[1] ;2[p14,q14,q15,q16,r16] TE[1] ;3[q5,r5,q6,p7,q7] TE[1] ;4[e3,e4,f4,g4,g5] TE[1] ;1[g11,h11,f12,g12,f13] TE[1] ;2[m11,n11,n12,o12,o13] TE[1] ;3[o8,o9,p9,n10,o10] TE[1] ;4[h6,h7,i7,i8,j8] TE[1] ;1[i10,j10,k10,l10,m10] TE[1] ;2[r10,p11,q11,r11,q12] TE[1] ;3[k7,k8,l8,l9,m9] TE[1] ;4[l5,j6,k6,l6,m6] TE[1] ) ( ;4[a1,a2,b2,c2,c3] TE[1] ;1[e14,c15,d15,e15,c16] TE[1] ;2[p14,q14,q15,q16,r16] TE[1] ;3[r5,q6,r6,p7,q7] TE[1] ;4[d4,d5,e5,e6,f6] TE[1] ;1[g11,h11,f12,g12,f13] TE[1] ( ;2[o11,p11,n12,o12,o13] TE[1] ;3[o8,o9,n10,o10,p10] TE[1] ;4[g7,h7,h8,i8,j8] TE[1] ;1[i10,j10,k10,l10,m10] TE[1] ) ( ;2[n11,o11,p11,o12,o13] TE[1] ;3[n8,o8,n9,m10,n10] TE[1] ;4[g7,h7,h8,i8,j8] TE[1] ;1[i10,j10,k10,l10] TE[1] ;2[l11,k12,l12,m12,m13] TE[1] ;3[p9,q9,q10,r10,q11] TE[1] ) ) ( ;4[a1,a2,a3,b3,c3] TE[1] ) ( ;4[a1,b1,b2,b3,c3] TE[1] ;1[d14,e14,d15,c16,d16] TE[1] ;2[p14,q14,q15,q16,r16] TE[1] ;3[q5,r5,q6,p7,q7] TE[1] ;4[d4,e4,e5,f5,f6] TE[1] ;1[g12,h12,f13,g13,g14] TE[1] ;2[m11,n11,n12,o12,o13] TE[1] ;3[o8,o9,m10,n10,o10] TE[1] ;4[g7,h7,h8,i8,j8] TE[1] ;1[j10,k10,l10,i11,j11] TE[1] ;2[i12,j12,k12,l12,k13] TE[1] ;3[k7,k8,j9,k9,l9] TE[1] ) ( ;4[a1,a2,a3,a4,b4] TE[1] ;1[e14,c15,d15,e15,c16] TE[1] ;2[p14,q14,q15,r15,r16] TE[1] ;3[r5,q6,r6,p7,q7] TE[1] ;4[c5,d5,d6,d7,e7] TE[1] ;1[g11,h11,f12,g12,f13] TE[1] ;2[n11,m12,n12,n13,o13] TE[1] ;3[o8,o9,p9,n10,o10] TE[1] ;4[f8,g8,h8,h9,i9] TE[1] ;1[i10,j10,k10,l10,m10] TE[1] ;2[i11,j11,k11,l11,j12] TE[1] ) ) ) ( ;2[r18,r19,s19,t19,t20] TE[1] ;3[t1,t2,t3,s4,t4] TE[1] ( ;4[a1,b1,b2,b3,c3] TE[1] ;1[d14,e14,d15,c16,d16] TE[1] ;2[o15,p15,p16,q16,q17] TE[1] ;3[r5,q6,r6,p7,q7] TE[1] ;4[d4,e4,e5,f5,f6] TE[1] ;1[g11,h11,f12,g12,f13] TE[1] ;2[m13,l14,m14,n14,m15] TE[1] ;3[o8,m9,n9,o9,o10] TE[1] ;4[g7,h7,h8,i8,j8] TE[1] ;1[j9,k9,l9,i10,j10] TE[1] ;2[m11,n11,o11,p11,n12] TE[1] ;3[l5,k6,l6,l7,l8] TE[1] ;4[j4,j5,k5,i6,j6] TE[1] ) ( ;4[a1,b1,c1,d1,d2] TE[1] ;1[d14,e14,c15,d15,c16] TE[1] ;2[o15,p15,p16,q16,q17] TE[1] ;3[q5,r5,q6,p7,q7] TE[1] ;4[e3,e4,f4,f5,g5] TE[1] ;1[g12,h12,f13,g13,g14] TE[1] ;2[m13,l14,m14,n14,m15] TE[1] ;3[o8,o9,m10,n10,o10] TE[1] ;4[h6,i6,i7,j7,k7] TE[1] ;1[j10,k10,l10,i11,j11] TE[1] ;2[l11,m11,n11,o11,n12] TE[1] ;3[p11,q11,q12,r12,q13] TE[1] ;4[m7,l8,m8,n8,l9] TE[1] ) ) ( ;2[r18,s18,s19,s20,t20] TE[1] ;3[t1,t2,t3,s4,t4] TE[1] ;4[a1,a2,b2,c2,c3] TE[1] ;1[e14,c15,d15,e15,c16] TE[1] ;2[o15,o16,p16,p17,q17] TE[1] ;3[r5,q6,r6,p7,q7] TE[1] ;4[d4,d5,d6,e6,e7] TE[1] ;1[g11,h11,f12,g12,f13] TE[1] ;2[n12,o12,m13,n13,n14] TE[1] ;3[o8,o9,m10,n10,o10] TE[1] ;4[f8,f9,g9,h9,i9] TE[1] ;1[e8,d9,e9,e10,e11] TE[1] ;2[l9,m9,l10,l11,m11] TE[1] ;3[p11,p12,o13,p13,o14] TE[1] ;4[c7,b8,c8,c9,c10] TE[1] ) ) ( ;1[d19,a20,b20,c20,d20] TE[1] ;2[r18,s18,s19,s20,t20] TE[1] ( ;3[q1,r1,s1,t1,q2] TE[1] ;4[a1,b1,b2,b3,c3] TE[1] ;1[f16,g16,e17,f17,e18] TE[1] ;2[o15,o16,p16,p17,q17] TE[1] ;3[p3,o4,p4,n5,o5] TE[1] ;4[d4,e4,e5,f5,f6] TE[1] ;1[j13,h14,i14,j14,h15] TE[1] ;2[m11,m12,m13,n13,n14] TE[1] ;3[l6,m6,k7,l7,l8] TE[1] ;4[g7,f8,g8,h8,h9] TE[1] ;1[l9,l10,k11,l11,k12] TE[1] ;2[m8,m9,n9,o9,n10] TE[1] ;3[k9,j10,k10,j11,j12] TE[1] ;4[i10,i11,i12,h13,i13] TE[1] ) ( ;3[t1,t2,t3,s4,t4] TE[1] ;4[a1,b1,b2,b3,c3] TE[1] ;1[g16,e17,f17,g17,e18] TE[1] ;2[o15,o16,p16,p17,q17] TE[1] ;3[q5,r5,q6,p7,q7] TE[1] ;4[d4,e4,e5,f5,f6] TE[1] ;1[j13,i14,j14,h15,i15] TE[1] ;2[m11,m12,m13,n13,n14] TE[1] ) ) ( ;1[b18,c18,b19,a20,b20] TE[1] ( ;2[r18,s18,s19,s20,t20] TE[1] ( ;3[s1,t1,s2,r3,s3] TE[1] ( ;4[a1,a2,b2,c2,c3] TE[1] ;1[f16,g16,d17,e17,f17] TE[1] ;2[o15,o16,p16,p17,q17] TE[1] ( ;3[o4,p4,q4,n5,o5] TE[1] ;4[d4,d5,d6,e6,e7] TE[1] ( ;1[j13,i14,j14,h15,i15] TE[1] ( ;2[m11,m12,m13,n13,n14] TE[1] ( ;3[l6,m6,k7,l7,k8] TE[1] ;4[f8,f9,g9,h9,g10] TE[1] ;1[k9,k10,k11,k12] TE[1] ( ;2[l8,m8,n8,l9,l10] TE[1] ;3[j9,j10,j11,j12] TE[1] ;4[h11,g12,h12,h13,h14] TE[1] ) ( ;2[l8,l9,m9,l10] TE[1] ;3[i9,j9,j10,j11,j12] TE[1] ;4[h11,g12,h12,h13,h14] TE[1] ) ) ( ;3[l5,k6,l6,m6,k7] TE[1] ;4[f8,f9,g9,h9,g10] TE[1] ;1[k8,k9,k10,k11,k12] TE[1] ;2[l7,l8,m8,l9,l10] TE[1] ;3[j8,j9,j10,j11,j12] TE[1] ;4[h11,i11,h12,i12,i13] TE[1] ) ) ( ;2[k13,l13,m13,m14,n14] TE[1] ( ;3[l5,k6,l6,m6,k7] TE[1] ( ;4[f8,e9,f9,g9,g10] TE[1] ;1[k8,k9,k10,k11,k12] TE[1] ;2[o10,m11,n11,o11,n12] TE[1] ;3[j8,j9,j10,j11,j12] TE[1] ;4[h11,g12,h12,h13,h14] TE[1] ) ( ;4[h7,f8,g8,h8,h9] TE[1] ;1[k8,k9,k10,k11,k12] TE[1] ;2[n9,n10,o10,n11,n12] TE[1] ;3[j8,j9,j10,j11,j12] TE[1] ;4[i10,i11,h12,i12,i13] TE[1] ) ) ( ;3[l6,m6,k7,l7,k8] TE[1] ;4[f8,f9,g9,h9,g10] TE[1] ;1[k9,k10,k11,k12] TE[1] ;2[i9,j9,j10,j11,j12] TE[1] ;3[j4,i5,j5,k5,j6] TE[1] ;4[f4,f5,g5,g6,h6] TE[1] ) ) ) ( ;1[j14,h15,i15,j15,j16] TE[1] ;2[m11,m12,m13,n13,n14] TE[1] ;3[l6,m6,k7,l7,k8] TE[1] ;4[f8,f9,g9,h9,g10] TE[1] ;1[k9,k10,k11,k12,k13] TE[1] ;2[l8,m8,l9,m9,l10] TE[1] ;3[j9,j10,j11,j12,j13] TE[1] ;4[h11,h12,g13,h13,h14] TE[1] ) ( ;1[j14,h15,i15,j15,i16] TE[1] ;2[k13,l13,m13,m14,n14] TE[1] ;3[m6,l7,m7,n7,m8] TE[1] ;4[f8,f9,g9,h9,g10] TE[1] ;1[i11,h12,i12,j12,i13] TE[1] ;2[n10,m11,n11,o11,n12] TE[1] ;3[n9,o9,o10,p10,p11] TE[1] ;4[k9,i10,j10,k10,k11] TE[1] ) ) ( ;3[n4,o4,p4,q4,n5] TE[1] ;4[d4,d5,d6,e6,e7] TE[1] ;1[j13,i14,j14,h15,i15] TE[1] ;2[m11,m12,m13,n13,n14] TE[1] ;3[l5,k6,l6,m6,k7] TE[1] ;4[f8,f9,g9,h9,g10] TE[1] ;1[k8,k9,k10,k11,k12] TE[1] ;2[l7,l8,m8,l9,l10] TE[1] ;3[j8,j9,j10,j11,j12] TE[1] ;4[h11,h12,g13,h13,h14] TE[1] ) ) ( ;4[a1,b1,b2,b3,c3] TE[1] ;1[f16,g16,d17,e17,f17] TE[1] ;2[o15,o16,p16,p17,q17] TE[1] ;3[o4,p4,q4,n5,o5] TE[1] ;4[d4,e4,e5,f5,f6] TE[1] ( ;1[j14,h15,i15,j15,j16] TE[1] ;2[k13,l13,l14,m14,n14] TE[1] ;3[l6,m6,k7,l7,l8] TE[1] ;4[g7,h7,h8,i8,h9] TE[1] ;1[j11,i12,j12,h13,i13] TE[1] ;2[m10,l11,m11,m12,n12] TE[1] ;3[h5,g6,h6,i6,j6] TE[1] ;4[g10,e11,f11,g11,g12] TE[1] ) ( ;1[j14,h15,i15,j15,i16] TE[1] ;2[k13,l13,m13,m14,n14] TE[1] ;3[l6,m6,k7,l7,k8] TE[1] ;4[g7,f8,g8,h8,g9] TE[1] ;1[j11,i12,j12,h13,i13] TE[1] ;2[n9,n10,n11,o11,n12] TE[1] ;3[h5,g6,h6,i6,j6] TE[1] ;4[i9,j9,k9,l9,m9] TE[1] ) ( ;1[j13,i14,j14,h15,i15] TE[1] ;2[m11,m12,m13,n13,n14] TE[1] ;3[l6,m6,k7,l7,k8] TE[1] ( ;4[g7,g8,h8,h9,h10] TE[1] ;1[k9,k10,k11,k12] TE[1] ;2[l8,l9,m9,l10] TE[1] ;3[j9,j10,i11,j11,j12] TE[1] ;4[g11,f12,g12,h12,h13] TE[1] ) ( ;4[g7,f8,g8,h8,g9] TE[1] ;1[k9,k10,k11,k12] TE[1] ;2[l8,l9,m9,l10] TE[1] ;3[j9,j10,j11,j12] TE[1] ;4[h10,h11,g12,h12,h13] TE[1] ) ) ) ) ( ;3[t1,t2,t3,s4,t4] TE[1] ( ;4[a1,b1,b2,b3,c3] TE[1] ;1[f15,e16,f16,d17,e17] TE[1] ;2[o15,o16,p16,p17,q17] TE[1] ;3[q5,r5,q6,p7,q7] TE[1] ;4[d4,e4,e5,f5,f6] TE[1] ;1[g12,h12,f13,g13,g14] TE[1] ( ;2[n12,o12,m13,n13,n14] TE[1] ;3[o8,o9,m10,n10,o10] TE[1] ;4[g7,g8,h8,i8,h9] TE[1] ;1[j10,k10,l10,i11,j11] TE[1] ;2[i12,j12,k12,l12,i13] TE[1] ;3[p11,p12,o13,p13,o14] TE[1] ;4[m8,j9,k9,l9,m9] TE[1] ) ( ;2[o12,m13,n13,o13,n14] TE[1] ;3[o8,o9,m10,n10,o10] TE[1] ;4[g7,h7,h8,i8,j8] TE[1] ;1[j10,k10,l10,i11,j11] TE[1] ;2[i12,j12,k12,l12,i13] TE[1] ;3[p11,p12,q12,r12,q13] TE[1] ;4[k7,l7,m7,n7,o7] TE[1] ) ) ( ;4[a1,a2,b2,c2,c3] TE[1] ;1[f15,e16,f16,d17,e17] TE[1] ;2[o15,o16,p16,p17,q17] TE[1] ;3[q5,r5,p6,q6,p7] TE[1] ;4[d4,d5,d6,e6,e7] TE[1] ;1[g12,h12,f13,g13,g14] TE[1] ;2[n12,o12,m13,n13,n14] TE[1] ;3[o8,o9,m10,n10,o10] TE[1] ;4[f8,f9,g9,h9,f10] TE[1] ;1[j10,k10,l10,i11,j11] TE[1] ;2[p8,p9,q9,p10,p11] TE[1] ;3[k11,l11,i12,j12,k12] TE[1] ;4[e11,e12,e13,e14,f14] TE[1] ) ) ( ;3[q1,r1,s1,t1,q2] TE[1] ( ;4[a1,b1,b2,b3,c3] TE[1] ;1[f16,g16,d17,e17,f17] TE[1] ;2[o15,o16,p16,p17,q17] TE[1] ( ;3[p3,o4,p4,n5,o5] TE[1] ;4[d4,e4,e5,f5,f6] TE[1] ;1[j14,h15,i15,j15,i16] TE[1] ;2[n12,m13,n13,o13,n14] TE[1] ;3[m6,k7,l7,m7,k8] TE[1] ;4[g7,h7,h8,i8,j8] TE[1] ;1[k9,k10,k11,k12,k13] TE[1] ;2[m8,m9,m10,l11,m11] TE[1] ;3[j9,j10,j11,j12,j13] TE[1] ;4[j5,i6,j6,k6,l6] TE[1] ) ( ;3[o3,p3,o4,n5,o5] TE[1] ;4[d4,e4,e5,f5,f6] TE[1] ;1[j14,h15,i15,j15,i16] TE[1] ;2[k13,l13,l14,m14,n14] TE[1] ;3[m6,l7,m7,n7,m8] TE[1] ;4[g7,f8,g8,h8,g9] TE[1] ;1[i11,h12,i12,j12,i13] TE[1] ;2[m10,l11,m11,n11,m12] TE[1] ;3[n9,n10,o10,p10,o11] TE[1] ;4[f10,f11,g11,e12,f12] TE[1] ) ) ( ;4[a1,a2,b2,c2,c3] TE[1] ;1[f16,g16,d17,e17,f17] TE[1] ;2[o15,o16,p16,p17,q17] TE[1] ;3[o3,p3,o4,n5,o5] TE[1] ;4[d4,d5,d6,e6,e7] TE[1] ;1[j14,h15,i15,j15,i16] TE[1] ;2[k13,l13,m13,m14,n14] TE[1] ;3[m6,l7,m7,n7,m8] TE[1] ;4[f8,f9,g9,h9,g10] TE[1] ;1[j11,i12,j12,h13,i13] TE[1] ;2[n10,m11,n11,o11,n12] TE[1] ;3[i8,j8,k8,j9,j10] TE[1] ;4[g6,i6,g7,h7,i7] TE[1] ) ) ( ;3[t1,t2,s3,t3,s4] TE[1] ( ;4[a1,b1,b2,b3,c3] TE[1] ;1[f15,e16,f16,d17,e17] TE[1] ( ;2[o15,o16,p16,p17,q17] TE[1] ;3[r5,p6,q6,r6,p7] TE[1] ( ;4[d4,e4,e5,f5,f6] TE[1] ;1[g12,h12,f13,g13,g14] TE[1] ;2[n12,o12,m13,n13,n14] TE[1] ;3[o8,n9,o9,m10,n10] TE[1] ;4[g7,g8,h8,i8,h9] TE[1] ;1[j10,k10,l10,i11,j11] TE[1] ;2[i12,j12,k12,l12,i13] ;3[p10,p11,q11,p12,p13] TE[1] ;4[l8,j9,k9,l9,m9] TE[1] ) ( ;4[d4,d5,d6,e6,e7] TE[1] ;1[g12,h12,f13,g13,g14] TE[1] ;2[n12,o12,m13,n13,n14] TE[1] ;3[o8,o9,m10,n10,o10] TE[1] ) ) ( ;2[o15,p15,p16,q16,q17] TE[1] ;3[r5,p6,q6,r6,p7] TE[1] ;4[d4,e4,e5,f5,f6] TE[1] ;1[g12,h12,f13,g13,g14] TE[1] ;2[m13,l14,m14,n14,m15] TE[1] ;3[o8,n9,o9,m10,n10] TE[1] ;4[g7,f8,g8,h8,g9] TE[1] ;1[j10,k10,l10,i11,j11] TE[1] ;2[k11,l11,m11,n11,l12] TE[1] ;3[p10,o11,p11,q11,p12] TE[1] ;4[i9,j9,k9,l9,m9] TE[1] ) ) ( ;4[a1,b1,c1,d1,d2] TE[1] ;1[f15,e16,f16,d17,e17] TE[1] ;2[o15,o16,p16,p17,q17] TE[1] ;3[r5,p6,q6,r6,p7] TE[1] ;4[e3,e4,f4,g4,g5] TE[1] ;1[g12,h12,f13,g13,g14] TE[1] ;2[n12,o12,m13,n13,n14] TE[1] ;3[o8,o9,m10,n10,o10] TE[1] ;4[h6,h7,i7,i8,j8] TE[1] ;1[j10,k10,l10,i11,j11] TE[1] ;2[p8,q8,p9,p10,p11] TE[1] ;3[l11,l12,k13,l13,l14] TE[1] ;4[h9,h10,f11,g11,h11] TE[1] ) ) ) ( ;2[r18,r19,r20,s20,t20] TE[1] ;3[s1,t1,s2,r3,s3] TE[1] ;4[a1,b1,c1,c2,c3] TE[1] ;1[f16,g16,d17,e17,f17] TE[1] ;2[o15,o16,p16,q16,q17] TE[1] ;3[o4,p4,q4,n5,o5] TE[1] ;4[d4,d5,e5,f5,f6] TE[1] ;1[j13,i14,j14,h15,i15] TE[1] ;2[m11,m12,m13,n13,n14] TE[1] ;3[l6,m6,k7,l7,k8] TE[1] ;4[g7,f8,g8,h8,g9] TE[1] ;1[k9,k10,k11,k12] TE[1] ) ( ;2[s17,t17,t18,t19,t20] ;3[q1,r1,s1,t1,q2] TE[1] ;4[a1,a2,a3,a4,b4] ;1[f16,g16,d17,e17,f17] TE[1] ;2[p14,q14,q15,r15,r16] ;3[o3,p3,n4,o4,n5] TE[1] ;4[c5,d5,d6,d7,e7] ;1[j14,h15,i15,j15,i16] TE[1] ;2[m11,m12,n12,o12,o13] ;3[m6,k7,l7,m7,k8] TE[1] ;4[f8,g8,g9,h9,h10] ;1[k9,k10,k11,k12,k13] TE[1] ;2[l8,m8,n8,l9,l10] ;3[j9,j10,j11,j12,j13] TE[1] ) ( ;2[s17,s18,t18,t19,t20] TE[1] ;3[s1,t1,s2,r3,s3] TE[1] ;4[a1,b1,b2,b3,c3] TE[1] ;1[f16,g16,d17,e17,f17] TE[1] ;2[p14,p15,q15,q16,r16] TE[1] ;3[o4,p4,q4,n5,o5] TE[1] ;4[d4,e4,e5,f5,f6] TE[1] ;1[j14,h15,i15,j15,i16] TE[1] ;2[m11,m12,n12,o12,o13] TE[1] ;3[l6,m6,k7,l7,k8] TE[1] ;4[g7,g8,h8,h9,h10] TE[1] ;1[k9,k10,k11,k12,k13] TE[1] ;2[n6,n7,n8,n9,n10] TE[1] ;3[j9,j10,j11,j12,j13] TE[1] ;4[i11,i12,i13,h14,i14] TE[1] ) ) ( ;1[a20,b20,c20,d20,e20] TE[1] ( ;2[s17,t17,t18,t19,t20] TE[1] ;3[s1,t1,s2,r3,s3] TE[1] ;4[a1,a2,a3,b3,c3] TE[1] ;1[h17,g18,h18,f19,g19] TE[1] ;2[p14,q14,q15,q16,r16] TE[1] ;3[o4,p4,q4,n5,o5] TE[1] ;4[d4,e4,e5,f5,f6] TE[1] ;1[i13,i14,h15,i15,i16] TE[1] ;2[m11,m12,n12,n13,o13] TE[1] ;3[l6,m6,k7,l7,k8] TE[1] ;4[g7,g8,h8,h9,h10] TE[1] ;1[g10,g11,h11,i11,h12] TE[1] ;2[j10,k10,l10,k11,k12] TE[1] ;3[h5,g6,h6,i6,j6] TE[1] ;4[f9,e10,f10,f11,f12] TE[1] ) ( ;2[r18,s18,s19,s20,t20] TE[1] ;3[s1,t1,s2,r3,s3] TE[1] ( ;4[a1,a2,b2,c2,c3] TE[1] ;1[h17,g18,h18,f19,g19] TE[1] ;2[o15,o16,p16,p17,q17] TE[1] ;3[o4,p4,q4,n5,o5] TE[1] ;4[d4,d5,d6,e6,e7] TE[1] ;1[j13,j14,i15,j15,i16] TE[1] ;2[m11,m12,m13,n13,n14] TE[1] ;3[l6,m6,k7,l7,k8] TE[1] ;4[f8,f9,g9,h9,g10] TE[1] ;1[k9,k10,k11,l11,k12] TE[1] ;2[m8,m9,n9,o9,n10] TE[1] ;3[i9,j9,j10,j11,j12] TE[1] ;4[j5,j6,j7,i8,j8] TE[1] ) ( ;4[a1,b1,b2,b3,c3] TE[1] ;1[h17,g18,h18,f19,g19] TE[1] ;2[o15,o16,p16,p17,q17] TE[1] ;3[o4,p4,q4,n5,o5] TE[1] ;4[d4,e4,e5,f5,f6] TE[1] ;1[i13,i14,h15,i15,i16] TE[1] ;2[m11,m12,m13,n13,n14] TE[1] ;3[l6,m6,k7,l7,k8] TE[1] ( ;4[g7,f8,g8,h8,g9] TE[1] ;1[k9,j10,k10,j11,j12] TE[1] ;2[l8,m8,l9,m9,l10] TE[1] ;3[i9,j9,i10,i11,i12] TE[1] ;4[e9,e10,f10,f11,f12] TE[1] ) ( ;4[g7,g8,h8,h9,h10] TE[1] ;1[k9,j10,k10,j11,j12] TE[1] ;2[l8,l9,m9,l10] TE[1] ;3[i9,j9,i10,i11,i12] TE[1] ;4[g11,f12,g12,h12,h13] TE[1] ) ) ) ) ( ;1[a16,a17,a18,a19,a20] TE[1] ;2[s17,t17,t18,t19,t20] TE[1] ( ;3[t1,t2,t3,t4,t5] TE[1] ;4[a1,b1,c1,d1,d2] TE[1] ;1[c13,d13,b14,c14,b15] TE[1] ;2[p14,q14,q15,q16,r16] TE[1] ;3[s6,r7,s7,q8,r8] TE[1] ;4[e3,e4,f4,g4,g5] TE[1] ;1[f11,g11,h11,e12,f12] TE[1] ;2[m11,n11,n12,o12,o13] TE[1] ;3[o9,p9,m10,n10,o10] TE[1] ;4[h6,h7,i7,i8,j8] TE[1] ;1[k9,i10,j10,k10,l10] TE[1] ;2[i11,j11,k11,k12,l12] TE[1] ;3[p11,p12,p13,q13,r13] TE[1] ;4[k7,l7,l8,m8,n8] TE[1] ) ( ;3[p1,q1,r1,s1,t1] TE[1] ;4[a1,b1,b2,b3,c3] TE[1] ;1[c13,d13,b14,c14,b15] TE[1] ;2[p14,q14,q15,q16,r16] TE[1] ;3[n2,o2,n3,m4,n4] TE[1] ;4[d4,e4,e5,f5,f6] TE[1] ;1[f11,g11,h11,e12,f12] TE[1] ;2[m11,m12,n12,n13,o13] TE[1] ;3[k5,l5,j6,k6,k7] TE[1] ;4[g7,g8,f9,g9,h9] TE[1] ;1[k11,i12,j12,k12,k13] TE[1] ;2[i9,j9,k9,k10,l10] TE[1] ;3[l8,l9,m9,m10,n10] TE[1] ;4[e10,c11,d11,e11,d12] TE[1] ) ) ( ;1[a18,b18,c18,a19,a20] TE[1] ( ;2[s17,t17,t18,t19,t20] TE[1] ( ;3[q1,r1,s1,t1,q2] TE[1] ;4[a1,b1,b2,b3,c3] TE[1] ;1[f16,g16,d17,e17,f17] TE[1] ;2[p14,q14,q15,q16,r16] TE[1] ;3[p3,o4,p4,n5,o5] TE[1] ;4[d4,e4,e5,f5,f6] TE[1] ;1[j14,h15,i15,j15,j16] TE[1] ;2[n12,m13,n13,o13,n14] TE[1] ;3[k6,l6,m6,k7,k8] TE[1] ;4[g7,f8,g8,h8,g9] TE[1] ;1[k9,k10,k11,k12,k13] TE[1] ;2[l8,l9,l10,m10,m11] TE[1] ;3[j9,j10,j11,j12,j13] TE[1] ;4[h10,h11,h12,h13,h14] TE[1] ) ( ;3[s1,t1,s2,r3,s3] TE[1] ;4[a1,a2,b2,c2,c3] TE[1] ;1[e15,f15,e16,d17,e17] TE[1] ;2[p14,q14,q15,q16,r16] TE[1] ;3[o4,p4,q4,n5,o5] TE[1] ;4[d4,d5,d6,e6,e7] TE[1] ;1[i13,g14,h14,i14,h15] TE[1] ;2[m11,m12,n12,n13,o13] TE[1] ;3[l6,m6,l7,l8,l9] TE[1] ;4[f8,f9,g9,h9,g10] TE[1] ;1[l10,k11,l11,j12,k12] TE[1] ;2[m8,m9,n9,o9,n10] TE[1] ;3[j10,k10,i11,j11,i12] TE[1] ;4[f11,f12,e13,f13,f14] TE[1] ) ) ( ;2[r18,s18,s19,s20,t20] TE[1] ( ;3[q1,r1,s1,t1,q2] TE[1] ;4[a1,a2,a3,b3,c3] TE[1] ;1[e15,f15,e16,d17,e17] TE[1] ;2[o15,o16,p16,p17,q17] TE[1] ;3[p3,n4,o4,p4,n5] TE[1] ;4[d4,e4,e5,f5,f6] TE[1] ;1[j13,g14,h14,i14,j14] TE[1] ;2[n12,m13,n13,o13,n14] TE[1] ;3[l6,m6,k7,l7,k8] TE[1] ;4[g7,f8,g8,h8,g9] TE[1] ;1[k9,k10,k11,k12] TE[1] ;2[l8,l9,l10,m10,m11] TE[1] ;3[j9,i10,j10,j11,j12] TE[1] ;4[j6,k6,i7,j7,j8] TE[1] ) ( ;3[t1,t2,r3,s3,t3] TE[1] ;4[a1,b1,b2,b3,c3] TE[1] ;1[e15,f15,e16,d17,e17] TE[1] ;2[o15,o16,p16,p17,q17] TE[1] ;3[p4,q4,p5,o6,p6] TE[1] ;4[d4,e4,e5,f5,f6] TE[1] ;1[h12,i12,g13,h13,g14] TE[1] ;2[m11,m12,m13,n13,n14] TE[1] ;3[n7,n8,n9,m10,n10] TE[1] ;4[g7,g8,h8,i8,h9] TE[1] ;1[j10,k10,l10,j11,k11] TE[1] ) ) ) ( ;1[c18,c19,a20,b20,c20] TE[1] ;2[s17,t17,t18,t19,t20] TE[1] ;3[t1,t2,t3,s4,t4] TE[1] ;4[a1,b1,c1,d1,d2] TE[1] ;1[f15,d16,e16,f16,d17] TE[1] ;2[p14,q14,q15,q16,r16] TE[1] ;3[q5,r5,q6,p7,q7] TE[1] ;4[e3,e4,f4,g4,g5] TE[1] ;1[i13,g14,h14,i14,i15] TE[1] ;2[o11,p11,n12,o12,o13] TE[1] ;3[k8,l8,m8,n8,o8] TE[1] ;4[h6,i6,i7,j7,j8] TE[1] ;1[k11,j12,k12,l12,k13] TE[1] ;2[p8,p9,q9,q10,r10] TE[1] ;3[j9,i10,j10,i11,i12] TE[1] ;4[h8,g9,h9,h10,h11] TE[1] ) ( ;1[c18,a19,b19,c19,a20] TE[1] ;2[r18,s18,s19,s20,t20] TE[1] ;3[t1,r2,s2,t2,r3] TE[1] ;4[a1,b1,c1,d1,d2] TE[1] ;1[f16,g16,d17,e17,f17] TE[1] ;2[o15,o16,p16,p17,q17] TE[1] ;3[o4,p4,q4,n5,o5] TE[1] ;4[e3,f3,f4,g4,g5] TE[1] ;1[j14,h15,i15,j15,i16] TE[1] ;2[k13,l13,m13,m14,n14] TE[1] ;3[m6,l7,m7,n7,m8] TE[1] ;4[h6,g7,h7,i7,h8] TE[1] ;1[i11,h12,i12,j12,i13] TE[1] ;2[k15,l15,j16,k16,k17] TE[1] ;3[n9,n10,o10,p10,n11] TE[1] ;4[i9,i10,j10,k10,k11] TE[1] ) ) pentobi-7.2/src/books/book_duo.blksgf000066400000000000000000000071301227240712600177250ustar00rootroot00000000000000( ;GM[Blokus Duo] CA[UTF-8] ( ;B[f9,e10,f10,g10,f11] TE[1] ( ;W[i4,h5,i5,j5,i6] TE[1] ( ;B[h7,g8,h8,h9,i9] TE[1] ( ;W[f5,e6,f6,g6,e7] TE[1] ) ( ;W[f5,f6,g6,e7,f7] TE[1] ) ) ( ;B[g7,g8,h8,i8,h9] TE[1] ) ( ;B[e7,c8,d8,e8,d9] TE[1] ( ;W[h7,h8,i8,h9,h10] TE[1] ) ( ;W[e5,d6,e6,f6,g6] TE[1] ) ( ;W[h7,h8,h9,i9,h10] TE[1] ) ) ( ;B[e6,e7,d8,e8,d9] TE[1] ) ( ;B[g6,g7,h7,i7,g8] TE[1] ( ;W[k6,j7,k7,j8,j9] TE[1] ) ( ;W[f4,g4,e5,f5,e6] TE[1] ) ) ( ;B[h6,h7,i7,g8,h8] TE[1] ) ( ;B[g6,g7,g8,h8,h9] ;W[j7,j8,j9,k9,j10] TE[1] ;B[h3,f4,g4,h4,f5] TE[1] ;W[h10,h11,i11,g12,h12] TE[1] ) ( ;B[g6,g7,g8,h8,i8] ;W[f4,g4,e5,f5,e6] TE[1] ) ( ;B[i7,g8,h8,i8,h9] BM[1] ;W[g6,f7,g7,h7,f8] TE[1] ) ) ( ;W[j5,i6,j6,k6,j7] TE[1] ) ) ( ;B[e9,d10,e10,f10,e11] TE[1] ;W[j4,i5,j5,k5,j6] TE[1] ( ;B[h6,g7,h7,f8,g8] TE[1] ) ( ;B[f7,f8,g8,g9,h9] TE[1] ) ( ;B[h7,f8,g8,h8,g9] TE[1] ) ) ( ;B[f8,e9,f9,g9,e10] TE[1] ;W[i4,h5,i5,j5,i6] TE[1] ( ;B[h8,i8,j8,k8,j9] TE[1] ( ;W[k6,k7,l7,l8,l9] TE[1] ) ( ;W[f6,g6,f7,g7,g8] TE[1] ) ) ( ;B[g4,h4,g5,g6,g7] TE[1] ) ( ;B[g5,g6,g7,h7,i7] TE[1] ( ;W[k6,j7,k7,k8,l8] TE[1] ) ( ;W[k6,j7,k7,l7,j8] TE[1] ) ) ( ;B[g6,g7,h7,h8,i8] ;W[j7,j8,i9,j9,i10] TE[1] ) ( ;B[g4,g5,f6,g6,g7] ;W[f3,g3,h3,e4,f4] TE[1] ;B[i7,j7,k7,h8,i8] ;W[k6,l6,l7,k8,l8] TE[1] ) ) ( ;B[e8,e9,f9,d10,e10] TE[1] ;W[i4,h5,i5,j5,i6] TE[1] ;B[g6,f7,g7,h7,g8] TE[1] ;W[j7,j8,j9,k9,j10] TE[1] ) ( ;B[e8,f8,d9,e9,e10] TE[1] ( ;W[j5,j6,k6,i7,j7] TE[1] ( ;B[g6,g7,h7,h8,i8] TE[1] ( ;W[i3,h4,i4,g5,h5] TE[1] ) ( ;W[h4,i4,f5,g5,h5] TE[1] ) ) ( ;B[h5,h6,g7,h7,h8] TE[1] ;W[f3,f4,g4,h4,i4] TE[1] ) ) ( ;W[j5,i6,j6,k6,j7] TE[1] ;B[h5,i5,h6,g7,h7] ( ;W[g8,h8,i8,h9,i9] TE[1] ) ( ;W[i8,i9,h10,i10,h11] TE[1] ) ) ( ;W[i4,h5,i5,j5,i6] ;B[g4,g5,g6,g7,h7] TE[1] ;W[g2,f3,g3,h3,f4] ;B[k6,j7,k7,i8,j8] TE[1] ) ) ( ;B[f8,d9,e9,f9,e10] TE[1] ;W[j5,i6,j6,k6,i7] TE[1] ;B[h5,h6,g7,h7,h8] TE[1] ( ;W[g3,f4,g4,h4,i4] TE[1] ) ( ;W[g4,h4,i4,f5,g5] ;B[j8,k8,l8,i9,j9] TE[1] ) ) ( ;B[e8,d9,e9,e10,f10] TE[1] ( ;W[j5,i6,j6,k6,j7] TE[1] ;B[f4,e5,f5,f6,f7] TE[1] ( ;W[f3,g3,g4,g5,h5] TE[1] ) ( ;W[h7,h8,h9,i9,h10] TE[1] ) ( ;W[g7,h7,f8,g8,f9] TE[1] ) ) ( ;W[i4,h5,i5,j5,i6] TE[1] ;B[g4,g5,f6,g6,f7] TE[1] ( ;W[g2,f3,g3,h3,f4] TE[1] ) ( ;W[g7,h7,f8,g8,f9] TE[1] ) ) ) ( ;B[e7,e8,d9,e9,e10] ;W[j5,i6,j6,h7,i7] TE[1] ;B[h4,f5,g5,h5,f6] ;W[f7,f8,g8,f9,f10] TE[1] ) ( ;B[g9,e10,f10,g10,g11] ;W[i4,h5,i5,j5,i6] TE[1] ;B[j6,h7,i7,j7,h8] ;W[f6,g6,f7,g7,g8] TE[1] ) ( ;B[e10,f10,g10,h10,g11] ;W[i4,h5,i5,j5,i6] TE[1] ;B[j6,i7,j7,i8,i9] ;W[e5,e6,f6,g6,g7] TE[1] ) ) pentobi-7.2/src/books/book_junior.blksgf000066400000000000000000000001561227240712600204450ustar00rootroot00000000000000( ;GM[Blokus Junior] CA[UTF-8] ( ;B[f9,e10,f10,e11,f11] TE[1] ) ( ;B[g9,d10,e10,f10,g10] TE[1] ) ) pentobi-7.2/src/books/book_trigon.blksgf000066400000000000000000000001121227240712600204310ustar00rootroot00000000000000( ;GM[Blokus Trigon]CA[UTF-8] ;1[r12][r13][s13][r14][s14][r15]TE[1] ) pentobi-7.2/src/books/book_trigon_2.blksgf000066400000000000000000000002551227240712600206620ustar00rootroot00000000000000( ;GM[Blokus Trigon Two-Player] CA[UTF-8] ;1[r12,r13,s13,r14,s14,r15] TE[1] ;2[r4,q5,r5,q6,r6,r7] TE[1] ;3[j7,k7,l7,m7,m8,n8] TE[1] ;4[v11,w11,w12,x12,y12,z12] TE[1] ) pentobi-7.2/src/books/book_trigon_3.blksgf000066400000000000000000000001221227240712600206540ustar00rootroot00000000000000( ;GM[Blokus Trigon Three-Player]CA[UTF-8] ;1[p11,o12,p12,o13,p13,p14]TE[1] ) pentobi-7.2/src/doc_libboardgame.cpp000066400000000000000000000070331227240712600175600ustar00rootroot00000000000000/** @page libboardgame_doc_tags Tags used in documentation This page defines attributes of documentation elements that are in widespread use. For brevity, the documentation block contains only a reference to the section of this page. @section libboardgame_avoid_stack_allocation Class size is large The size of this class is large because it contains large members that are not allocated on the heap to avoid dereferencing pointers for speed reasons. It should be avoided to create instances of this class on the stack. @section libboardgame_doc_obj_ref_opt Object reference optimization This class uses a reference to a certain object several times but does not store the reference at construction time for memory and/or speed optimization. The reference is passed as an argument to the functions that need it. The class instance assumes (and might check with assertions) that the reference always refers to the same object . @section libboardgame_doc_storesref Stores a reference Used for parameters to indicate that the class will store a reference to the parameter. The lifetime of the parameter must exceed the lifetime of the constructed class. @section libboardgame_doc_takesownership Takes ownership Used for pointer-type parameters to indicate that the ownership of the object instance will be transfered to the callee. @section libboardgame_doc_threadsafe_after_construction Thread-safe after construction Used for classes that, that are thread-safe (w.r.t. different instances) after construction. The constructor (and potentially also the destructor) is not thread-safe, for example because it modifies non-const static class members. @page libboardgame_doc_glossary Glossary This page explains and defines terms used in the documentation. @section libboardgame_doc_gogui GoGui Graphical interface for Go engines using GTP. Defines several GTP extension commands. http://gogui.sf.net @section libboardgame_doc_gnugo GNU Go GNU Go program http://www.gnu.org/s/gnugo/ @section libboardgame_doc_gtp GTP Go Text Protocol http://www.lysator.liu.se/~gunnar/gtp/ @section libboardgame_doc_uct UCT Upper Confidence bounds applied to Tree: a Monte-Carlo tree search algorithm that applies bandit ideas to the move selection at tree nodes. See @ref libboardgame_doc_kocsis_szepesvari_2006 @section libboardgame_doc_rave RAVE Rapid Action Value Estimation: Keeps track of the value of a move averaged over all simulations in the subtree of a node in which the move was played by a player in a position following the node (inclusive). See @ref libboardgame_doc_gelly_silver_2007 @section libboardgame_doc_sgf SGF Smart Game Format http://www.red-bean.com/sgf/ @page libboardgame_doc_bibliography Bibliography List of publications. @section libboardgame_doc_enz_2009 A Lock-free Multithreaded Monte-Carlo Tree Search Algorithm. M. Enzenberger, M. Mueller. Advances in Computer Games 2009. (PDF) @section libboardgame_doc_gelly_silver_2007 Combining Online and Offline Knowledge in UCT. S. Gelly, D. Silver. Proceedings of the 24th international conference on Machine learning, pp. 273-280, 2007. (PDF) @section libboardgame_doc_kocsis_szepesvari_2006 Bandit Based Monte-Carlo Planning L. Kocsis, Cs. SzepesvУЁri. Proceedings of the 17th European Conference on Machine Learning, Springer-Verlag, Berlin, LNCS/LNAI 4212, September 18-22, pp. 282-293, 2006 (PDF) */ pentobi-7.2/src/doc_mainpage.cpp000066400000000000000000000034031227240712600167260ustar00rootroot00000000000000/** @mainpage notitle @section mainpage_libboardgame LibBoardGame Modules The LibBoardGame modules contain code that is not specific to the board game Blokus and could be reused for other projects: - libboardgame_gtp - Implementation of the Go Text Protocol (@ref libboardgame_doc_gtp) - libboardgame_sys - Platform-dependent functionality - libboardgame_util - General utilities not specific to board games - libboardgame_sgf - Implementation of the Smart Game Format (@ref libboardgame_doc_sgf) - libboardgame_base - Utility classes and functions specific to board games - libboardgame_mcts - Monte-Carlo tree search - libboardgame_test - Functionality for unit tests similar to Boost::Test @section mainpage_pentobi Pentobi Modules The Pentobi modules are specific to the board game Blokus: - libpentobi_base - General Blokus-specific functionality - libpentobi_mcts - Blokus player based on Monte-Carlo tree search - pentobi_gtp - @ref libboardgame_doc_gtp interface to the player in libpentobi_mcts @section mainpage_gui Pentobi GUI Modules The Pentobi GUI modules implement the user interface. They have a dependency on the following Qt libraries: QtCore4, QtGui4. - libpentobi_gui - GUI functionality that could be reused for other projects - pentobi - Main program that provides a GUI for the player in libpentobi_mcts - pentobi_thumbnailer - Generates file preview thumbnails for the Gnome desktop - pentobi_kde_thumbnailer - Plugin for file preview thumbnails for the KDE desktop */ pentobi-7.2/src/libboardgame_base/000077500000000000000000000000001227240712600172165ustar00rootroot00000000000000pentobi-7.2/src/libboardgame_base/CMakeLists.txt000066400000000000000000000004011227240712600217510ustar00rootroot00000000000000include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) set(boardgame_base_STAT_SRCS CoordPoint.cpp Engine.cpp Rating.cpp SpreadsheetStringRep.cpp Transform.cpp ) add_library(boardgame_base STATIC ${boardgame_base_STAT_SRCS}) pentobi-7.2/src/libboardgame_base/CoordPoint.cpp000066400000000000000000000013311227240712600220000ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_base/CoordPoint.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "CoordPoint.h" #include namespace libboardgame_base { using namespace std; //----------------------------------------------------------------------------- ostream& operator<<(ostream& out, const CoordPoint& p) { if (! p.is_null()) out << '(' << p.x << ',' << p.y << ')'; else out << "NULL"; return out; } //----------------------------------------------------------------------------- } // namespace libboardgame_base pentobi-7.2/src/libboardgame_base/CoordPoint.h000066400000000000000000000072561227240712600214610ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_base/CoordPoint.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_BASE_COORD_POINT_H #define LIBBOARDGAME_BASE_COORD_POINT_H #include #include #include namespace libboardgame_base { using namespace std; //----------------------------------------------------------------------------- /** Point stored as x,y coordinates. */ struct CoordPoint { int x; int y; static bool is_onboard(int x, int y, unsigned width, unsigned height); static CoordPoint null(); CoordPoint(); CoordPoint(int x, int y); bool operator==(const CoordPoint& p) const; bool operator!=(const CoordPoint& p) const; bool operator<(const CoordPoint& p) const; CoordPoint operator+(const CoordPoint& p) const; CoordPoint operator-(const CoordPoint& p) const; CoordPoint& operator+=(const CoordPoint& p); CoordPoint& operator-=(const CoordPoint& p); bool is_null() const; bool is_onboard(unsigned width, unsigned height) const; /** Find the width and height of the bounding box. */ template static void find_width_height(T begin, T end, unsigned& width, unsigned& height); }; inline CoordPoint::CoordPoint() { } inline CoordPoint::CoordPoint(int x, int y) { this->x = x; this->y = y; } inline bool CoordPoint::operator==(const CoordPoint& p) const { return x == p.x && y == p.y; } inline bool CoordPoint::operator<(const CoordPoint& p) const { if (y != p.y) return (y < p.y); return (x < p.x); } inline bool CoordPoint::operator!=(const CoordPoint& p) const { return ! operator==(p); } inline CoordPoint CoordPoint::operator+(const CoordPoint& p) const { return CoordPoint(x + p.x, y + p.y); } inline CoordPoint& CoordPoint::operator+=(const CoordPoint& p) { *this = *this + p; return *this; } inline CoordPoint CoordPoint::operator-(const CoordPoint& p) const { return CoordPoint(x - p.x, y - p.y); } inline CoordPoint& CoordPoint::operator-=(const CoordPoint& p) { *this = *this - p; return *this; } inline CoordPoint CoordPoint::null() { return CoordPoint(numeric_limits::max(), numeric_limits::max()); } template void CoordPoint::find_width_height(T begin, T end, unsigned& width, unsigned& height) { int min_x = numeric_limits::max(); int min_y = numeric_limits::max(); int max_x = numeric_limits::min(); int max_y = numeric_limits::min(); for (auto i = begin; i != end; ++i) { if (i->x < min_x) min_x = i->x; if (i->x > max_x) max_x = i->x; if (i->y < min_y) min_y = i->y; if (i->y > max_y) max_y = i->y; } width = max_x - min_x + 1; height = max_y - min_y + 1; } inline bool CoordPoint::is_onboard(int x, int y, unsigned width, unsigned height) { return (x >= 0 && x < static_cast(width) && y >= 0 && y < static_cast(height)); } inline bool CoordPoint::is_onboard(unsigned width, unsigned height) const { return is_onboard(x, y, width, height); } inline bool CoordPoint::is_null() const { return x == numeric_limits::max(); } //----------------------------------------------------------------------------- ostream& operator<<(ostream& out, const CoordPoint& p); //----------------------------------------------------------------------------- } // namespace libboardgame_base #endif // LIBBOARDGAME_BASE_COORD_POINT_H pentobi-7.2/src/libboardgame_base/Engine.cpp000066400000000000000000000042121227240712600211260ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_base/Engine.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Engine.h" #include "libboardgame_sys/CpuTime.h" #include "libboardgame_util/Abort.h" #include "libboardgame_util/Log.h" #include "libboardgame_util/RandomGenerator.h" namespace libboardgame_base { using libboardgame_gtp::Failure; using libboardgame_util::clear_abort; using libboardgame_util::log; using libboardgame_util::set_abort; using libboardgame_util::RandomGenerator; //----------------------------------------------------------------------------- Engine::Engine() { add("cputime", &Engine::cmd_cputime); add("cputime_diff", &Engine::cmd_cputime_diff); add("gogui-interrupt", &Engine::cmd_interrupt); add("set_random_seed", &Engine::cmd_set_random_seed); } Engine::~Engine() throw() { } void Engine::cmd_cputime(Response& response) { double time = libboardgame_sys::cpu_time(); if (time == -1) throw Failure("cannot determine cpu time"); response << time; } void Engine::cmd_cputime_diff(Response& response) { double time = libboardgame_sys::cpu_time(); if (time == -1) throw Failure("cannot determine cpu time"); double diff; if (m_cpu_time_last == -1) diff = time; else diff = time - m_cpu_time_last; m_cpu_time_last = time; response << diff; } /** Indicate that commands can be interrupted using the GoGui convention. See the documentation of GoGui, chapter "Interrupting commands" */ void Engine::cmd_interrupt() { } /** Set global random seed. Compatible with @ref libboardgame_doc_gnugo
Arguments: random seed */ void Engine::cmd_set_random_seed(const Arguments& args) { RandomGenerator::set_global_seed(args.parse()); } void Engine::interrupt() { set_abort(); } void Engine::on_handle_cmd_begin() { clear_abort(); log() << flush; } //----------------------------------------------------------------------------- } // namespace libboardgame_base pentobi-7.2/src/libboardgame_base/Engine.h000066400000000000000000000020371227240712600205760ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_base/Engine.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_BASE_ENGINE_H #define LIBBOARDGAME_BASE_ENGINE_H #include #include "libboardgame_gtp/Engine.h" namespace libboardgame_base { using namespace std; using libboardgame_gtp::Arguments; using libboardgame_gtp::Response; //----------------------------------------------------------------------------- class Engine : public libboardgame_gtp::Engine { public: void cmd_cputime(Response&); void cmd_cputime_diff(Response&); void cmd_interrupt(); void cmd_set_random_seed(const Arguments&); Engine(); ~Engine() throw(); void interrupt() override; protected: void on_handle_cmd_begin() override; private: double m_cpu_time_last; }; //----------------------------------------------------------------------------- } // namespace libboardgame_base #endif // LIBBOARDGAME_BASE_ENGINE_H pentobi-7.2/src/libboardgame_base/Geometry.h000066400000000000000000000212761227240712600211720ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_base/Geometry.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_BASE_GEOMETRY_H #define LIBBOARDGAME_BASE_GEOMETRY_H #include #include "CoordPoint.h" #include "Point.h" #include "libboardgame_util/NullTermList.h" namespace libboardgame_base { using namespace std; using libboardgame_util::NullTermList; //----------------------------------------------------------------------------- /** Geometry data of a board with a given size. This class is a base class that uses virtual functions in its constructor that allow to restrict the shape of the board to a subset of the rectangle and/or to define different definitions of adjacent and diagonal neighbors of a point for geometries that are not a regular rectangular grid. @tparam P An instantiation of libboardgame_base::Point (or compatible class) */ template class Geometry { public: typedef P Point; class Iterator { friend class Geometry; public: Iterator(const Geometry& g); const Point& operator*() const; const Point* operator->() const; operator bool() const; void operator++(); private: const Point* m_p; const Point* m_end; }; virtual ~Geometry(); /** Return the point type if the board has different types of points. For example, in the geometry used in Blokus Trigon, there are two point types (0=downside triangle, 1=upside trinagle); in a regular rectangle, there is only one point type. By convention, 0 is the type of the point at (0,0). @param x The x coordinate (may be negative and/or outside the board). @param y The y coordinate (may be negative and/or outside the board). */ virtual unsigned get_point_type(int x, int y) const = 0; /** Get repeat interval for point types along the x axis. If the board has different point types, the layout of the point types repeats in this x interval. If the board has only one point type, the function should return 1. */ virtual unsigned get_period_x() const = 0; /** Get repeat interval for point types along the y axis. @see get_period_x(). */ virtual unsigned get_period_y() const = 0; unsigned get_point_type(CoordPoint p) const; unsigned get_point_type(Point p) const; bool is_onboard(Point p) const; bool is_onboard(CoordPoint p) const; unsigned get_width() const; unsigned get_height() const; /** Get list of on-board adjacent points. */ const NullTermList& get_adj(Point p) const; /** Get list of on-board diagonal points. Currently supports up to nine diagonal points as used on boards for Blokus Trigon. */ const NullTermList& get_diag(Point p) const; /** Get list of on-board adjacent and diagonal points. The adjacent points are first, diagobal points later in the list. Currently supports up to twelve diagonal points as used on boards for Blokus Trigon. */ const NullTermList& get_adj_diag(Point p) const; /** Get closest distance to first line. */ unsigned get_dist_to_edge(Point p) const; /** Get second closest distance to first line. */ unsigned get_second_dist_to_edge(Point p) const; protected: Geometry(); /** Initialize. Subclasses must call this function in their constructors. */ void init(unsigned width, unsigned height); /** Initialize on-board points. This function is used in init() and allows the subclass to restrict the on-board points to a subset of the on-board points of a rectangle to support different board shapes. */ virtual void init_is_onboard(Point p, bool& is_onboard) const = 0; /** Initialize adjacent and diagonal neighbors of an on-board point. This function is used in init() and allows the subclass to define other neighborhood relationships than the one of a regular rectangular grid (e.g. triangles or hexagonal fields). This function is used after the on-board status of all points has been initialized. */ virtual void init_adj_diag(Point p, NullTermList& adj, NullTermList& diag) const = 0; private: unsigned m_width; unsigned m_height; bool m_is_onboard[Point::range]; unsigned m_dist_to_edge[Point::range]; unsigned m_second_dist_to_edge[Point::range]; const Point* m_all_points_begin; const Point* m_all_points_end; unique_ptr m_all_points; NullTermList m_adj[Point::range]; NullTermList m_diag[Point::range]; NullTermList m_adj_diag[Point::range]; }; template inline Geometry

::Iterator::Iterator(const Geometry& g) : m_p(g.m_all_points_begin), m_end(g.m_all_points_end) { } template inline const P& Geometry

::Iterator::operator*() const { LIBBOARDGAME_ASSERT(operator bool()); return *m_p; } template inline const P* Geometry

::Iterator::operator->() const { LIBBOARDGAME_ASSERT(operator bool()); return m_p; } template inline Geometry

::Iterator::operator bool() const { return m_p != m_end; } template inline void Geometry

::Iterator::operator++() { LIBBOARDGAME_ASSERT(operator bool()); ++m_p; } template Geometry

::Geometry() { } template Geometry

::~Geometry() { } template inline const NullTermList& Geometry

::get_adj(Point p) const { LIBBOARDGAME_ASSERT(is_onboard(p)); return m_adj[p.to_int()]; } template inline const NullTermList& Geometry

::get_adj_diag(Point p) const { LIBBOARDGAME_ASSERT(is_onboard(p)); return m_adj_diag[p.to_int()]; } template inline const NullTermList& Geometry

::get_diag(Point p) const { LIBBOARDGAME_ASSERT(is_onboard(p)); return m_diag[p.to_int()]; } template inline unsigned Geometry

::get_dist_to_edge(Point p) const { LIBBOARDGAME_ASSERT(is_onboard(p)); return m_dist_to_edge[p.to_int()]; } template inline unsigned Geometry

::get_height() const { return m_height; } template inline unsigned Geometry

::get_point_type(Point p) const { return get_point_type(p.get_x(), p.get_y()); } template inline unsigned Geometry

::get_point_type(CoordPoint p) const { return get_point_type(p.x, p.y); } template inline unsigned Geometry

::get_second_dist_to_edge(Point p) const { LIBBOARDGAME_ASSERT(is_onboard(p)); return m_second_dist_to_edge[p.to_int()]; } template inline unsigned Geometry

::get_width() const { return m_width; } template void Geometry

::init(unsigned width, unsigned height) { m_width = width; m_height = height; m_all_points.reset(new Point[width * height]); LIBBOARDGAME_ASSERT(width >= 1 && width <= Point::max_width); LIBBOARDGAME_ASSERT(height >= 1 && height <= Point::max_height); fill(m_is_onboard, m_is_onboard + Point::range, false); m_all_points_begin = m_all_points.get(); auto all_points_end = m_all_points.get(); for (unsigned y = 0; y < height; ++y) for (unsigned x = 0; x < width; ++x) { Point p(x, y); init_is_onboard(p, m_is_onboard[p.to_int()]); if (is_onboard(p)) *(all_points_end++) = p; } m_all_points_end = all_points_end; for (Iterator i(*this); i; ++i) { unsigned j = (*i).to_int(); init_adj_diag(*i, m_adj[j], m_diag[j]); typename NullTermList::Init adj_diag(m_adj_diag[j]); for (typename NullTermList::Iterator k(m_adj[j]); k; ++k) adj_diag.push_back(*k); for (typename NullTermList::Iterator k(m_diag[j]); k; ++k) adj_diag.push_back(*k); adj_diag.finish(); unsigned x = (*i).get_x(); unsigned y = (*i).get_y(); unsigned dist_to_edge_x = min(width - x - 1, x); unsigned dist_to_edge_y = min(height - y - 1, y); m_dist_to_edge[j] = min(dist_to_edge_x, dist_to_edge_y); m_second_dist_to_edge[j] = max(dist_to_edge_x, dist_to_edge_y); } } template inline bool Geometry

::is_onboard(Point p) const { return m_is_onboard[p.to_int()]; } template bool Geometry

::is_onboard(CoordPoint p) const { return p.is_onboard(m_width, m_height) && is_onboard(Point(p.x, p.y)); } //----------------------------------------------------------------------------- } // namespace libboardgame_base #endif // LIBBOARDGAME_BASE_GEOMETRY_H pentobi-7.2/src/libboardgame_base/GeometryUtil.h000066400000000000000000000061541227240712600220260ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_base/GeometryUtil.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_BASE_GEOMETRY_UTIL_H #define LIBBOARDGAME_BASE_GEOMETRY_UTIL_H #include "Geometry.h" #include "libboardgame_util/Log.h" namespace libboardgame_base { namespace geometry_util { using libboardgame_util::log; //----------------------------------------------------------------------------- /** Shift a list of points as close to the (0,0) point as possible. This will minimize the minumum x and y coordinates. The function also returns the width and height of the bounding box and the offset that was subtracted from the points for the shifting. @note This transformation does not preserve point types. If the original list was compatible with the point types on the board, the new point type of (0,0) will be Geometry::get_point_type(offset). @tparam T An iterator over a container containing CoordPoint element. */ template void normalize_offset(T begin, T end, unsigned& width, unsigned& height, CoordPoint& offset) { int min_x = numeric_limits::max(); int min_y = numeric_limits::max(); int max_x = numeric_limits::min(); int max_y = numeric_limits::min(); for (auto i = begin; i != end; ++i) { if (i->x < min_x) min_x = i->x; if (i->x > max_x) max_x = i->x; if (i->y < min_y) min_y = i->y; if (i->y > max_y) max_y = i->y; } width = max_x - min_x + 1; height = max_y - min_y + 1; offset = CoordPoint(min_x, min_y); for (auto i = begin; i != end; ++i) *i -= offset; } /** Shift a list of points that are not compatible with the point types used in the geometry such that they match it. The points are shifted in a minimal positive direction to match the types, x-direction is preferred. @tparam T An iterator over a container containing CoordPoint element. @param geometry The geometry. @param begin The beginning of the list of points. @param end The end of the list of points. @param point_type The point type of (0,0) in the list of points. */ template void type_match_shift(const Geometry

& geometry, T begin, T end, unsigned point_type) { CoordPoint type_match_shift(0, 0); // Init to avoid compiler warning bool found = false; for (unsigned y = 0; ! found && y < geometry.get_period_y(); ++y) for (unsigned x = 0; ! found && x < geometry.get_period_x(); ++x) if (geometry.get_point_type(x, y) == point_type) { type_match_shift = CoordPoint(x, y); found = true; } LIBBOARDGAME_ASSERT(found); for (auto i = begin; i != end; ++i) *i += type_match_shift; } //----------------------------------------------------------------------------- } // namespace geometry_util } // namespace libboardgame_base #endif // LIBBOARDGAME_BASE_GEOMETRY_UTIL_H pentobi-7.2/src/libboardgame_base/Grid.h000066400000000000000000000112411227240712600202530ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_base/Grid.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_BASE_GRID_H #define LIBBOARDGAME_BASE_GRID_H #include #include #include #include "Point.h" #include "Geometry.h" namespace libboardgame_base { using namespace std; //----------------------------------------------------------------------------- /** Elements assigned to on-board points. The elements must be default-constructible. @tparam P An instantiation of libboardgame_base::Point (or compatible class) @tparam T The element type. */ template class Grid { public: typedef P Point; Grid(); explicit Grid(const Geometry

& geometry); Grid(const Geometry

& geometry, const T& val); bool operator==(const Grid& grid) const; void init(const Geometry

& geometry); void init(const Geometry

& geometry, const T& val); const Geometry

& get_geometry() const; T& operator[](const Point& p); const T& operator[](const Point& p) const; /** Fill all on-board points for a given board size with a value. */ void fill(const T& val); ostream& write(ostream& out) const; private: const Geometry

* m_geometry; T m_a[Point::range_onboard_end]; bool is_initialized() const; }; template inline Grid::Grid() { #if LIBBOARDGAME_DEBUG m_geometry = nullptr; #endif } template inline Grid::Grid(const Geometry

& geometry) { init(geometry); } template inline Grid::Grid(const Geometry

& geometry, const T& val) { init(geometry, val); } template bool Grid::operator==(const Grid& grid) const { LIBBOARDGAME_ASSERT(is_initialized()); LIBBOARDGAME_ASSERT(grid.is_initialized()); LIBBOARDGAME_ASSERT(m_geometry->get_width() == grid.get_geometry().get_width()); LIBBOARDGAME_ASSERT(m_geometry->get_height() == grid.get_geometry().get_height()); for (typename Geometry

::Iterator i(*m_geometry); i; ++i) if (operator[](*i) != grid[*i]) return false; return true; } template inline T& Grid::operator[](const Point& p) { LIBBOARDGAME_ASSERT(is_initialized()); LIBBOARDGAME_ASSERT(m_geometry->is_onboard(p)); return m_a[p.to_int()]; } template inline const T& Grid::operator[](const Point& p) const { LIBBOARDGAME_ASSERT(is_initialized()); LIBBOARDGAME_ASSERT(m_geometry->is_onboard(p)); return m_a[p.to_int()]; } template inline void Grid::fill(const T& val) { LIBBOARDGAME_ASSERT(is_initialized()); for (typename Geometry

::Iterator i(*m_geometry); i; ++i) operator[](*i) = val; } template const Geometry

& Grid::get_geometry() const { LIBBOARDGAME_ASSERT(is_initialized()); return *m_geometry; } template void Grid::init(const Geometry

& geometry) { m_geometry = &geometry; } template inline void Grid::init(const Geometry

& geometry, const T& val) { init(geometry); fill(val); } template bool Grid::is_initialized() const { return m_geometry != nullptr; } template ostream& Grid::write(ostream& out) const { LIBBOARDGAME_ASSERT(is_initialized()); ostringstream buffer; size_t max_len = 0; for (typename Geometry

::Iterator i(*m_geometry); i; ++i) { buffer.str(""); buffer << operator[](*i); max_len = max(max_len, buffer.str().length()); } unsigned width = m_geometry->get_width(); unsigned height = m_geometry->get_height(); string empty(max_len, ' '); for (unsigned y = height - 1; ; --y) { for (unsigned x = 0; x < width; ++x) { Point p(x, y); if (m_geometry->is_onboard(p)) out << setw(int(max_len)) << operator[](p); else out << empty; if (x < width - 1) out << ' '; } out << '\n'; if (y == 0) break; } return out; } //----------------------------------------------------------------------------- template ostream& operator<<(ostream& out, const Grid& grid) { return grid.write(out); } //----------------------------------------------------------------------------- } // namespace libboardgame_base #endif // LIBBOARDGAME_BASE_GRID_H pentobi-7.2/src/libboardgame_base/Marker.h000066400000000000000000000046501227240712600206150ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_base/Marker.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_BASE_MARKER_H #define LIBBOARDGAME_BASE_MARKER_H #include #include #include "Point.h" namespace libboardgame_base { using namespace std; //----------------------------------------------------------------------------- /** Marker to mark points on board with fast operation to clear all marks. This marker is typically used in recursive fills or other loops to remember what points have already been visited. @tparam P An instantiation of libboardgame_base::Point */ template class Marker { public: typedef P Point; Marker(); bool operator[](Point p) const; void clear(); void set(Point p); bool toggle(Point p); /** Set up for overflow test (for testing purposes only). The function is equivalent to calling reset() and then clear() nu_clear times. It allows a faster implementation of a unit test case that tests if the overflow is handled correctly, if clear() is called more than numeric_limits::max() times. */ void setup_for_overflow_test(unsigned nu_clear); private: unsigned m_current; unsigned m_a[Point::range]; void reset(); }; template inline Marker

::Marker() { reset(); } template inline bool Marker

::operator[](Point p) const { return m_a[p.to_int()] == m_current; } template inline void Marker

::clear() { if (--m_current != 0) return; reset(); } template inline void Marker

::setup_for_overflow_test(unsigned nu_clear) { reset(); m_current -= nu_clear; } template inline void Marker

::reset() { m_current = numeric_limits::max() - 1; fill(m_a, m_a + Point::range, numeric_limits::max()); } template inline void Marker

::set(Point p) { m_a[p.to_int()] = m_current; } template inline bool Marker

::toggle(Point p) { auto& ref = m_a[p.to_int()]; if (ref == m_current) { ++ref; return false; } else { ref = m_current; return true; } } //----------------------------------------------------------------------------- } // namespace libboardgame_base #endif // LIBBOARDGAME_BASE_MARKER_H pentobi-7.2/src/libboardgame_base/MarkerWithCount.h000066400000000000000000000030501227240712600224530ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_base/MarkerWithCount.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_BASE_MARKER_WITH_COUNT_H #define LIBBOARDGAME_BASE_MARKER_WITH_COUNT_H #include "Marker.h" namespace libboardgame_base { //----------------------------------------------------------------------------- /** A point marker that also keeps track of the number of marked points. @tparam P An instantiation of libboardgame_base::Point */ template class MarkerWithCount : private Marker

{ public: typedef P Point; MarkerWithCount(); void clear(); void set(Point p); bool toggle(Point p); int count() const; private: int m_count; }; template inline MarkerWithCount

::MarkerWithCount() { m_count = 0; } template inline void MarkerWithCount

::clear() { Marker

::clear(); m_count = 0; } template inline int MarkerWithCount

::count() const { return m_count; } template inline void MarkerWithCount

::set(Point p) { Marker

::set(p); ++m_count; } template inline bool MarkerWithCount

::toggle(Point p) { if (Marker

::toggle(p)) { ++m_count; return true; } else { --m_count; return false; } } //----------------------------------------------------------------------------- } // namespace libboardgame_base #endif // LIBBOARDGAME_BASE_MARKER_WITH_COUNT_H pentobi-7.2/src/libboardgame_base/Point.h000066400000000000000000000444721227240712600204730ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_base/Point.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_BASE_POINT_H #define LIBBOARDGAME_BASE_POINT_H #include #include #include #include #include "libboardgame_util/Assert.h" #include "libboardgame_util/Exception.h" #include "libboardgame_util/NullElement.h" #include "libboardgame_sys/Compiler.h" namespace libboardgame_base { using namespace std; using namespace libboardgame_util; //----------------------------------------------------------------------------- /** @page libboardgame.boardgame.board_representation Board representation For efficiency, points are represented using a one-dimensional value. Class Point is a lightweight wrapper around an integer. Many games allow different board sizes. Point::max_size defines the maximum board size. For a given instantiation of Point with a given Point::max_size, the mapping of integers to coordinates is independent of the actual board size, which allows to read and write points without knowing the board size. The integer value of the points increases from left to right and bottom to top. The lower left corner of the board has the coordinates (0,0). Point::null() has the index 0. It can be used when a special marker point is needed (e.g. end of point list marker, or the special meaning "no point"). */ //----------------------------------------------------------------------------- /** Coordinate on the board. Depending on the game, a point represents a field or intersection (in Go) on the board. @tparam W The maximum board width. @tparam H The maximum board height. @tparam I An unsigned integer type to store the point value @tparam S A class with functions to convert points from and to strings depending on the string representation of points in the game. @tparam M The maximum integer value of a point on the board. This is usually W*H but in some cases it can be slightly reduced. For example, if only the rectangular geometries 10x20 and 20x10 are used, the W=H=20 but M can be set to 390 instead of 400, which reduces the size of arrays indexed by points by 2.5%. @see @ref libboardgame.boardgame.board_representation */ template class Point { public: typedef I IntType; typedef S StringRep; static const unsigned max_width = W; static const unsigned max_height = H; static const unsigned max_onboard = M; static_assert(W <= StringRep::max_width, ""); static_assert(H <= StringRep::max_height, ""); static_assert(numeric_limits::is_integer, ""); static_assert(! numeric_limits::is_signed, ""); friend class Direction; class Direction { public: friend class Point; Direction(); static Direction left(); static Direction right(); static Direction up(); static Direction down(); static Direction up_left(); static Direction up_right(); static Direction down_left(); static Direction down_right(); private: static const int value_uninitialized = max_width + 1 + 1; int m_i; explicit Direction(int i); bool is_initialized() const; int to_int() const; }; class InvalidString : public Exception { public: InvalidString(const string& s); }; /** Range of integer representation. Width times height on-board points and the null point. */ static const unsigned range = max_onboard + 1; /** Start of integer indices representing on-board points. All points with lower indices are off-board (but not all points between range_onboard_begin and range_onboard_end - 1 are on-board). */ static const unsigned range_onboard_begin = 1; /** End (exclusive) of integer indices representing on-board points. Users should not assume that range_onboard_end and range are equal to allow this class to be changed in the future or to replace this class by a different one in template classes that take the point class as a parameter. In different point representations (e.g. with an extra border of off-board points for loop unrolling in rectangular board geometries, range_onboard_end might be smaller than range. */ static const unsigned range_onboard_end = range; /** Special-purpose off-board point. This point is an off-board point with index 0. Among the use cases is an end marker for point lists or to initialize variables that carry a special meaning if no point has assigned to them yet. */ static Point null(); /** Parse a point from a string. @param s The string to parse @throws InvalidString */ static Point from_string(const string& s); static void read(istream& in, Point& p); static bool is_x_coord(unsigned i); static bool is_y_coord(unsigned i); LIBBOARDGAME_FORCE_INLINE Point(); Point(unsigned x, unsigned y); Point(const Point& p); explicit Point(unsigned i); explicit Point(const string& s); bool operator==(const Point& p) const; bool operator!=(const Point& p) const; bool operator<(const Point& p) const; unsigned get_x() const; unsigned get_y() const; /** Check if point is on board on the largest possible board size. Allows to check that a point is potentially an on-board point without knowing the size of the board. Mainly used for assertions that do not have the board size context. */ bool is_onboard() const; bool is_null() const; Point get_neighbor(Direction dir) const; Point get_left() const; Point get_right() const; Point get_up() const; Point get_down() const; Point get_up_left() const; Point get_up_right() const; Point get_down_left() const; Point get_down_right() const; /** Check if point is adjacent to another point. */ bool is_adj(Point p) const; /** Check if point is an adjacent or diagonal neighbor of another point. */ bool is_adj_diag(Point p) const; /** Return point as an integer between 0 and Point::range */ unsigned to_int() const; /** Return point as an integer between 0 and Point::range. @pre ! is_null() */ unsigned to_int_not_null() const; /** Convert to string. The string representation is compatible with @ref libboardgame_doc_gtp */ string to_string() const; void write(ostream& out) const; private: struct Precomputed { unsigned x[Point::range]; unsigned y[Point::range]; Precomputed(); }; static const Precomputed s_precomputed; static const IntType value_uninitialized = Point::range; static const IntType value_null = 0; IntType m_i; LIBBOARDGAME_FORCE_INLINE bool is_initialized() const; }; template Point::Direction::Direction() { m_i = value_uninitialized; } template Point::Direction::Direction(int i) { m_i = i; } template inline auto Point::Direction::down() -> Direction { return Direction(-static_cast(max_width)); } template inline auto Point::Direction::down_left() -> Direction { return Direction(-static_cast(max_width) - 1); } template inline auto Point::Direction::down_right() -> Direction { return Direction(-static_cast(max_width) + 1); } template inline bool Point::Direction::is_initialized() const { return (m_i < value_uninitialized); } template inline auto Point::Direction::left() -> Direction { return Direction(-1); } template inline auto Point::Direction::right() -> Direction { return Direction(1); } template inline int Point::Direction::to_int() const { LIBBOARDGAME_ASSERT(is_initialized()); return m_i; } template inline auto Point::Direction::up() -> Direction { return Direction(max_width); } template inline auto Point::Direction::up_left() -> Direction { return Direction(max_width - 1); } template inline auto Point::Direction::up_right() -> Direction { return Direction(max_width + 1); } template Point::InvalidString::InvalidString(const string& s) : Exception("Invalid point string '" + s + "'") { } template Point::Precomputed::Precomputed() { // Make this a static assertion once GCC and MSVC support using // numeric_limits::max() in compile-time const expressions LIBBOARDGAME_ASSERT(Point::range <= numeric_limits::max()); for (unsigned i = 1; i < Point::range; ++i) { y[i] = (i - 1) / max_width; x[i] = (i - 1) - y[i] * max_width; } } template const typename Point::Precomputed Point::s_precomputed; template inline Point::Point() { #if LIBBOARDGAME_DEBUG m_i = value_uninitialized; #endif } template inline Point::Point(unsigned i) { LIBBOARDGAME_ASSERT(i < range); m_i = static_cast(i); } template inline Point::Point(unsigned x, unsigned y) { LIBBOARDGAME_ASSERT(is_x_coord(x)); LIBBOARDGAME_ASSERT(is_y_coord(y)); m_i = static_cast(y * max_width + x + 1); } template inline Point::Point(const Point& p) { m_i = p.m_i; } template inline Point::Point(const string& s) { *this = from_string(s); } template inline bool Point::operator==(const Point& p) const { LIBBOARDGAME_ASSERT(is_initialized()); LIBBOARDGAME_ASSERT(p.is_initialized()); return (m_i == p.m_i); } template inline bool Point::operator!=(const Point& p) const { return ! operator==(p); } template inline bool Point::operator<(const Point& p) const { LIBBOARDGAME_ASSERT(is_initialized()); LIBBOARDGAME_ASSERT(p.is_initialized()); return (m_i < p.m_i); } template Point Point::from_string(const string& s) { istringstream in(s); Point p; read(in, p); if (! in) throw InvalidString(s); // Check that no extra non-whitespace characters follow string extra; in >> extra; if (in) throw InvalidString(s); return p; } template inline Point Point::get_down() const { LIBBOARDGAME_ASSERT(get_y() > 0); return get_neighbor(Direction::down()); } template inline Point Point::get_down_left() const { LIBBOARDGAME_ASSERT(get_y() > 0); LIBBOARDGAME_ASSERT(get_x() > 0); return get_neighbor(Direction::down_left()); } template inline Point Point::get_down_right() const { LIBBOARDGAME_ASSERT(get_y() > 0); LIBBOARDGAME_ASSERT(get_x() < max_width - 1); return get_neighbor(Direction::down_right()); } template inline Point Point::get_left() const { LIBBOARDGAME_ASSERT(get_x() > 0); return get_neighbor(Direction::left()); } template inline Point Point::get_neighbor(Direction dir) const { LIBBOARDGAME_ASSERT(! is_null()); return Point(m_i + dir.to_int()); } template inline Point Point::get_right() const { LIBBOARDGAME_ASSERT(get_x() < max_width - 1); return get_neighbor(Direction::right()); } template inline Point Point::get_up() const { LIBBOARDGAME_ASSERT(get_y() < max_height - 1); return get_neighbor(Direction::up()); } template inline Point Point::get_up_left() const { LIBBOARDGAME_ASSERT(get_x() > 0); LIBBOARDGAME_ASSERT(get_y() < max_height - 1); return get_neighbor(Direction::up_left()); } template inline Point Point::get_up_right() const { LIBBOARDGAME_ASSERT(get_x() < max_width - 1); LIBBOARDGAME_ASSERT(get_y() < max_height - 1); return get_neighbor(Direction::up_right()); } template inline unsigned Point::get_x() const { LIBBOARDGAME_ASSERT(! is_null()); return s_precomputed.x[m_i]; } template inline unsigned Point::get_y() const { LIBBOARDGAME_ASSERT(! is_null()); return s_precomputed.y[m_i]; } template inline bool Point::is_adj(Point p) const { int d = m_i - p.m_i; d = abs(d); return (d == max_width || d == 1); } template inline bool Point::is_adj_diag(Point p) const { int d = m_i - p.m_i; d = abs(d); return ((d >= static_cast(max_width - 1) && d <= static_cast(max_width + 1)) || d == 1); } template inline bool Point::is_initialized() const { return m_i < value_uninitialized; } template inline bool Point::is_onboard() const { LIBBOARDGAME_ASSERT(is_initialized()); return ! is_null(); } template inline bool Point::is_null() const { LIBBOARDGAME_ASSERT(is_initialized()); return m_i == value_null; } template inline bool Point::is_x_coord(unsigned i) { return i < max_width; } template inline bool Point::is_y_coord(unsigned i) { return i < max_height; } template inline Point Point::null() { return Point(value_null); } template void Point::read(istream& in, Point& p) { unsigned x; unsigned y; if (StringRep::read(in, max_width, max_height, x, y)) p = Point(x, y); else in.setstate(ios::failbit); } template inline unsigned Point::to_int() const { LIBBOARDGAME_ASSERT(is_initialized()); return m_i; } template inline unsigned Point::to_int_not_null() const { LIBBOARDGAME_ASSERT(! is_null()); return m_i; } template inline string Point::to_string() const { ostringstream s; write(s); return s.str(); } template void Point::write(ostream& out) const { if (is_null()) out << "NULL"; else StringRep::write(out, get_x(), get_y()); } //----------------------------------------------------------------------------- template inline ostream& operator<<(ostream& out, const Point& p) { p.write(out); return out; } /** Read point from input stream. The function guarantees to support the point representation used by @ref libboardgame_doc_gtp, so it can be used for parsing GTP arguments. */ template inline istream& operator>>(istream& in, Point& p) { Point::read(in, p); return in; } //----------------------------------------------------------------------------- } // namespace boardgame_libboardgame //----------------------------------------------------------------------------- namespace std { template struct hash> : public unary_function, size_t> { size_t operator()(const libboardgame_base::Point& p) const { return p.to_int(); } }; } // namespace std //----------------------------------------------------------------------------- namespace libboardgame_util { template inline bool is_null(const libboardgame_base::Point& p) { return p.is_null(); } template inline void set_null(libboardgame_base::Point& p) { p = libboardgame_base::Point::null(); } } // namespace libboardgame_util //----------------------------------------------------------------------------- #endif // LIBBOARDGAME_BASE_POINT_H pentobi-7.2/src/libboardgame_base/PointList.h000066400000000000000000000025501227240712600213160ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_base/PointList.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_BASE_POINT_LIST_H #define LIBBOARDGAME_BASE_POINT_LIST_H #include #include #include "Point.h" #include "libboardgame_util/ArrayList.h" namespace libboardgame_base { using namespace std; using libboardgame_util::ArrayList; //----------------------------------------------------------------------------- /** Array list that can contain all points on the board. @tparam P An instantiation of libboardgame_base::Point */ template class PointList : public ArrayList { public: typedef P Point; PointList(); explicit PointList(const P& p); explicit PointList(const string& s); }; template inline PointList

::PointList() { } template inline PointList

::PointList(const P& p) : ArrayList(p) { } template PointList

::PointList(const string& s) { istringstream in(s); string t; while (in >> t) ArrayList::push_back(P::from_string(t)); } //----------------------------------------------------------------------------- } // namespace libboardgame_base #endif // LIBBOARDGAME_BASE_POINT_LIST_H pentobi-7.2/src/libboardgame_base/PointTransform.h000066400000000000000000000261331227240712600223610ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_base/PointTransform.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_BASE_POINT_TRANSFORM_H #define LIBBOARDGAME_BASE_POINT_TRANSFORM_H #include "Point.h" #include "libboardgame_util/MathUtil.h" #include "libboardgame_util/Unused.h" namespace libboardgame_base { using libboardgame_util::math_util::round; //----------------------------------------------------------------------------- /** Transform a point. @tparam P An instance of class Point. */ template class PointTransform { public: typedef P Point; virtual ~PointTransform(); virtual Point get_transformed(const Point& p, unsigned width, unsigned height) const = 0; }; template PointTransform

::~PointTransform() { } //----------------------------------------------------------------------------- template class PointTransfIdent : public PointTransform

{ public: typedef P Point; Point get_transformed(const Point& p, unsigned width, unsigned height) const override; }; template P PointTransfIdent

::get_transformed(const Point& p, unsigned width, unsigned height) const { LIBBOARDGAME_UNUSED(width); LIBBOARDGAME_UNUSED(height); return p; } //----------------------------------------------------------------------------- /** Rotate point by 180 degrees. */ template class PointTransfRot180 : public PointTransform

{ public: typedef P Point; Point get_transformed(const Point& p, unsigned width, unsigned height) const override; }; template P PointTransfRot180

::get_transformed(const Point& p, unsigned width, unsigned height) const { unsigned x = width - p.get_x() - 1; unsigned y = height - p.get_y() - 1; return Point(x, y); } //----------------------------------------------------------------------------- /** Rotate point by 270 degrees and reflect on y axis. This is equivalent to a reflection on the y=-x line. */ template class PointTransfRot270Refl : public PointTransform

{ public: typedef P Point; Point get_transformed(const Point& p, unsigned width, unsigned height) const override; }; template P PointTransfRot270Refl

::get_transformed(const Point& p, unsigned width, unsigned height) const { unsigned x = height - p.get_y() - 1; unsigned y = width - p.get_x() - 1; return Point(x, y); } //----------------------------------------------------------------------------- /** Mirror along x axis. */ template class PointTransfRefl : public PointTransform

{ public: typedef P Point; Point get_transformed(const Point& p, unsigned width, unsigned height) const override; }; template P PointTransfRefl

::get_transformed(const Point& p, unsigned width, unsigned height) const { LIBBOARDGAME_UNUSED(height); unsigned x = width - p.get_x() - 1; unsigned y = p.get_y(); return Point(x, y); } //----------------------------------------------------------------------------- /** Mirror along y axis. */ template class PointTransfReflRot180 : public PointTransform

{ public: typedef P Point; Point get_transformed(const Point& p, unsigned width, unsigned height) const override; }; template P PointTransfReflRot180

::get_transformed(const Point& p, unsigned width, unsigned height) const { LIBBOARDGAME_UNUSED(width); unsigned x = p.get_x(); unsigned y = height - p.get_y() - 1; return Point(x, y); } //----------------------------------------------------------------------------- template class PointTransfTrigonRot60 : public PointTransform

{ public: typedef P Point; Point get_transformed(const Point& p, unsigned width, unsigned height) const override; }; template P PointTransfTrigonRot60

::get_transformed(const Point& p, unsigned width, unsigned height) const { float cx = 0.5f * static_cast(width - 1); float cy = 0.5f * static_cast(height - 1); float px = static_cast(p.get_x()) - cx; float py = static_cast(p.get_y()) - cy; unsigned x = static_cast(round(cx + 0.5f * px + 1.5f * py)); unsigned y = static_cast(round(cy - 0.5f * px + 0.5f * py)); return Point(x, y); } //----------------------------------------------------------------------------- template class PointTransfTrigonRot120 : public PointTransform

{ public: typedef P Point; Point get_transformed(const Point& p, unsigned width, unsigned height) const override; }; template P PointTransfTrigonRot120

::get_transformed(const Point& p, unsigned width, unsigned height) const { float cx = 0.5f * static_cast(width - 1); float cy = 0.5f * static_cast(height - 1); float px = static_cast(p.get_x()) - cx; float py = static_cast(p.get_y()) - cy; unsigned x = static_cast(round(cx - 0.5f * px + 1.5f * py)); unsigned y = static_cast(round(cy - 0.5f * px - 0.5f * py)); return Point(x, y); } //----------------------------------------------------------------------------- template class PointTransfTrigonRot240 : public PointTransform

{ public: typedef P Point; Point get_transformed(const Point& p, unsigned width, unsigned height) const override; }; template P PointTransfTrigonRot240

::get_transformed(const Point& p, unsigned width, unsigned height) const { float cx = 0.5f * static_cast(width - 1); float cy = 0.5f * static_cast(height - 1); float px = static_cast(p.get_x()) - cx; float py = static_cast(p.get_y()) - cy; unsigned x = static_cast(round(cx - 0.5f * px - 1.5f * py)); unsigned y = static_cast(round(cy + 0.5f * px - 0.5f * py)); return Point(x, y); } //----------------------------------------------------------------------------- template class PointTransfTrigonRot300 : public PointTransform

{ public: typedef P Point; Point get_transformed(const Point& p, unsigned width, unsigned height) const override; }; template P PointTransfTrigonRot300

::get_transformed(const Point& p, unsigned width, unsigned height) const { float cx = 0.5f * static_cast(width - 1); float cy = 0.5f * static_cast(height - 1); float px = static_cast(p.get_x()) - cx; float py = static_cast(p.get_y()) - cy; unsigned x = static_cast(round(cx + 0.5f * px - 1.5f * py)); unsigned y = static_cast(round(cy + 0.5f * px + 0.5f * py)); return Point(x, y); } //----------------------------------------------------------------------------- template class PointTransfTrigonReflRot60 : public PointTransform

{ public: typedef P Point; Point get_transformed(const Point& p, unsigned width, unsigned height) const override; }; template P PointTransfTrigonReflRot60

::get_transformed(const Point& p, unsigned width, unsigned height) const { float cx = 0.5f * static_cast(width - 1); float cy = 0.5f * static_cast(height - 1); float px = static_cast(p.get_x()) - cx; float py = static_cast(p.get_y()) - cy; unsigned x = static_cast(round(cx + 0.5f * (-px) + 1.5f * py)); unsigned y = static_cast(round(cy - 0.5f * (-px) + 0.5f * py)); return Point(x, y); } //----------------------------------------------------------------------------- template class PointTransfTrigonReflRot120 : public PointTransform

{ public: typedef P Point; Point get_transformed(const Point& p, unsigned width, unsigned height) const override; }; template P PointTransfTrigonReflRot120

::get_transformed(const Point& p, unsigned width, unsigned height) const { float cx = 0.5f * static_cast(width - 1); float cy = 0.5f * static_cast(height - 1); float px = static_cast(p.get_x()) - cx; float py = static_cast(p.get_y()) - cy; unsigned x = static_cast(round(cx - 0.5f * (-px) + 1.5f * py)); unsigned y = static_cast(round(cy - 0.5f * (-px) - 0.5f * py)); return Point(x, y); } //----------------------------------------------------------------------------- template class PointTransfTrigonReflRot240 : public PointTransform

{ public: typedef P Point; Point get_transformed(const Point& p, unsigned width, unsigned height) const override; }; template P PointTransfTrigonReflRot240

::get_transformed(const Point& p, unsigned width, unsigned height) const { float cx = 0.5f * static_cast(width - 1); float cy = 0.5f * static_cast(height - 1); float px = static_cast(p.get_x()) - cx; float py = static_cast(p.get_y()) - cy; unsigned x = static_cast(round(cx - 0.5f * (-px) - 1.5f * py)); unsigned y = static_cast(round(cy + 0.5f * (-px) - 0.5f * py)); return Point(x, y); } //----------------------------------------------------------------------------- template class PointTransfTrigonReflRot300 : public PointTransform

{ public: typedef P Point; Point get_transformed(const Point& p, unsigned width, unsigned height) const override; }; template P PointTransfTrigonReflRot300

::get_transformed(const Point& p, unsigned width, unsigned height) const { float cx = 0.5f * static_cast(width - 1); float cy = 0.5f * static_cast(height - 1); float px = static_cast(p.get_x()) - cx; float py = static_cast(p.get_y()) - cy; unsigned x = static_cast(round(cx + 0.5f * (-px) - 1.5f * py)); unsigned y = static_cast(round(cy + 0.5f * (-px) + 0.5f * py)); return Point(x, y); } //----------------------------------------------------------------------------- } // namespace libboardgame_base #endif // LIBBOARDGAME_BASE_POINT_TRANSFORM_H pentobi-7.2/src/libboardgame_base/Rating.cpp000066400000000000000000000024771227240712600211600ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_base/Rating.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Rating.h" #include #include #include "libboardgame_util/Assert.h" namespace libboardgame_base { using namespace std; //----------------------------------------------------------------------------- ostream& operator<<(ostream& out, const Rating& rating) { out << rating.m_elo; return out; } istream& operator>>(istream& in, Rating& rating) { in >> rating.m_elo; return in; } float Rating::get_expected_result(Rating elo_opponent, unsigned nu_opponents) const { float diff = elo_opponent.m_elo - m_elo; return 1.f / (1.f + static_cast(nu_opponents) * pow(10.f, diff / 400.f)); } void Rating::update_multiplayer(float game_result, Rating elo_opponent, unsigned nu_opponents, float k_value) { LIBBOARDGAME_ASSERT(k_value > 0); float diff = game_result - get_expected_result(elo_opponent, nu_opponents); m_elo += k_value * diff; } //----------------------------------------------------------------------------- } // namespace libboardgame_base pentobi-7.2/src/libboardgame_base/Rating.h000066400000000000000000000046301227240712600206160ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_base/Rating.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_BASE_RATING_H #define LIBBOARDGAME_BASE_RATING_H #include #include #include "libboardgame_util/MathUtil.h" namespace libboardgame_base { using namespace std; //----------------------------------------------------------------------------- /** Elo-rating of a player. */ class Rating { public: friend ostream& operator<<(ostream& out, const Rating& rating); friend istream& operator>>(istream& in, Rating& rating); explicit Rating(float elo = 0); /** Get the expected outcome of a game. @param elo_opponent Elo-rating of the opponent. @param nu_opponents The number of opponents (all with the same rating elo_opponent) */ float get_expected_result(Rating elo_opponent, unsigned nu_opponents = 1) const; /** Update a rating after a two-player game. @param game_result The outcome of the game (0=loss, 0.5=tie, 1=win) @param elo_opponent Elo-rating of the opponent. @param k_value The K-value */ void update(float game_result, Rating elo_opponent, float k_value = 32); /** Update a rating after a multi-player game. @param game_result The outcome of the game (0=loss, 0.5=tie, 1=win) @param elo_opponent Elo-rating of the opponent. @param k_value The K-value @param nu_opponents The number of opponents (all with the same rating elo_opponent) */ void update_multiplayer(float game_result, Rating elo_opponent, unsigned nu_opponents, float k_value = 32); float get() const; /** Get rating rounded to an interger. */ int toInt() const; private: float m_elo; }; inline Rating::Rating(float elo) : m_elo(elo) { } inline float Rating::get() const { return m_elo; } inline int Rating::toInt() const { return static_cast(libboardgame_util::math_util::round(m_elo)); } inline void Rating::update(float game_result, Rating elo_opponent, float k_value) { update_multiplayer(game_result, elo_opponent, 1, k_value); } //----------------------------------------------------------------------------- } // namespace libboardgame_base #endif // LIBBOARDGAME_BASE_RATING_H pentobi-7.2/src/libboardgame_base/RectGeometry.h000066400000000000000000000070741227240712600220100ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_base/RectGeometry.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_BASE_RECT_GEOMETRY_H #define LIBBOARDGAME_BASE_RECT_GEOMETRY_H #include #include "Geometry.h" #include "libboardgame_util/Unused.h" namespace libboardgame_base { using namespace std; //----------------------------------------------------------------------------- /** Geometry of a regular rectangular grid. @tparam P An instantiation of libboardgame_base::Point */ template class RectGeometry : public Geometry

{ public: typedef P Point; /** Create or reuse an already created geometry with a given size. */ static const RectGeometry* get(unsigned width, unsigned height); RectGeometry(unsigned width, unsigned height); unsigned get_point_type(int x, int y) const override; unsigned get_period_x() const override; unsigned get_period_y() const override; protected: void init_is_onboard(Point p, bool& is_onboard) const; void init_adj_diag(Point p, NullTermList& adj, NullTermList& diag) const; private: static unique_ptr s_geometry[Point::max_width + 1][Point::max_height + 1]; }; template unique_ptr> RectGeometry

::s_geometry[P::max_width + 1][P::max_height + 1]; template RectGeometry

::RectGeometry(unsigned width, unsigned height) { Geometry

::init(width, height); } template const RectGeometry

* RectGeometry

::get(unsigned width, unsigned height) { if (! s_geometry[width][height]) s_geometry[width][height].reset(new RectGeometry(width, height)); return s_geometry[width][height].get(); } template unsigned RectGeometry

::get_period_x() const { return 1; } template unsigned RectGeometry

::get_period_y() const { return 1; } template unsigned RectGeometry

::get_point_type(int x, int y) const { LIBBOARDGAME_UNUSED(x); LIBBOARDGAME_UNUSED(y); return 0; } template void RectGeometry

::init_is_onboard(Point p, bool& is_onboard) const { LIBBOARDGAME_UNUSED(p); is_onboard = true; } template void RectGeometry

::init_adj_diag(Point p, NullTermList& adj, NullTermList& diag) const { unsigned width = this->get_width(); unsigned height = this->get_height(); unsigned x = p.get_x(); unsigned y = p.get_y(); { typename NullTermList::Init init_adj(adj); if (x > 0) init_adj.push_back(p.get_left()); if (x < width - 1) init_adj.push_back(p.get_right()); if (y > 0) init_adj.push_back(p.get_down()); if (y < height - 1) init_adj.push_back(p.get_up()); init_adj.finish(); } { typename NullTermList::Init init_diag(diag); if (x > 0 && y < height - 1) init_diag.push_back(p.get_up_left()); if (x < width - 1 && y < height - 1) init_diag.push_back(p.get_up_right()); if (x > 0 && y > 0) init_diag.push_back(p.get_down_left()); if (x < width - 1 && y > 0) init_diag.push_back(p.get_down_right()); init_diag.finish(); } } //----------------------------------------------------------------------------- } // namespace libboardgame_base #endif // LIBBOARDGAME_BASE_RECT_GEOMETRY_H pentobi-7.2/src/libboardgame_base/SpreadsheetStringRep.cpp000066400000000000000000000034031227240712600240270ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_base/SpreadsheetStringRep.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "SpreadsheetStringRep.h" #include #include #include "libboardgame_util/Assert.h" #include "libboardgame_util/StringUtil.h" namespace libboardgame_base { using libboardgame_util::get_letter_coord; //----------------------------------------------------------------------------- bool SpreadsheetStringRep::read(istream& in, unsigned width, unsigned height, unsigned& x, unsigned& y) { int c; while (true) { c = in.peek(); if (c == EOF || ! isspace(c)) break; c = in.get(); } bool read_x = false; x = 0; while (true) { c = in.peek(); if (c == EOF || ! isalpha(c)) break; c = tolower(in.get()); if (c < 'a' || c > 'z') return false; x = 26 * x + (c - 'a' + 1); read_x = true; } if (! read_x) return false; --x; if (x >= width) return false; c = in.peek(); if (c < '0' || c > '9') return false; in >> y; if (! in) return false; --y; if (y >= height) return false; c = in.peek(); if (c == EOF) { in.clear(); return true; } if (isspace(c)) return true; return false; } void SpreadsheetStringRep::write(ostream& out, unsigned x, unsigned y) { out << get_letter_coord(x) << (y + 1); } //----------------------------------------------------------------------------- } // namespace libboardgame_base pentobi-7.2/src/libboardgame_base/SpreadsheetStringRep.h000066400000000000000000000026201227240712600234740ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_base/SpreadsheetStringRep.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_BASE_SPREADSHEET_STRING_REP_H #define LIBBOARDGAME_BASE_SPREADSHEET_STRING_REP_H #include #include namespace libboardgame_base { using namespace std; //----------------------------------------------------------------------------- /** Spreadsheet-style string representation of points. Can be used as a template argument for libboardgame_base::Point. Columns are represented as letters including the letter 'J'. After 'Z', multi-letter combinations are used: 'AA', 'AB', etc. Rows are represented by numbers starting with '1'. Note that unlike in spreadsheets, row number 1 is at the bottom to be compatible with the coordinate convention used in class Point. */ struct SpreadsheetStringRep { static const unsigned max_width = UINT_MAX; static const unsigned max_height = UINT_MAX; static bool read(istream& in, unsigned width, unsigned height, unsigned& x, unsigned& y); static void write(ostream& out, unsigned x, unsigned y); }; //----------------------------------------------------------------------------- } // namespace libboardgame_base #endif // LIBBOARDGAME_BASE_SPREADSHEET_STRING_REP_H pentobi-7.2/src/libboardgame_base/Transform.cpp000066400000000000000000000206721227240712600217040ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_base/Transform.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Transform.h" #include #include "libboardgame_util/Assert.h" namespace libboardgame_base { //----------------------------------------------------------------------------- Transform::~Transform() { } //----------------------------------------------------------------------------- CoordPoint TransfIdentity::get_transformed(const CoordPoint& p) const { return p; } unsigned TransfIdentity::get_point_type() const { return 0; } unsigned TransfIdentity::get_new_point_type() const { return 0; } //----------------------------------------------------------------------------- CoordPoint TransfRectRot90::get_transformed(const CoordPoint& p) const { return CoordPoint(p.y, -p.x); } unsigned TransfRectRot90::get_point_type() const { return 0; } unsigned TransfRectRot90::get_new_point_type() const { return 0; } //----------------------------------------------------------------------------- CoordPoint TransfRectRot180::get_transformed(const CoordPoint& p) const { return CoordPoint(-p.x, -p.y); } unsigned TransfRectRot180::get_point_type() const { return 0; } unsigned TransfRectRot180::get_new_point_type() const { return 0; } //----------------------------------------------------------------------------- CoordPoint TransfRectRot270::get_transformed(const CoordPoint& p) const { return CoordPoint(-p.y, p.x); } unsigned TransfRectRot270::get_point_type() const { return 0; } unsigned TransfRectRot270::get_new_point_type() const { return 0; } //----------------------------------------------------------------------------- CoordPoint TransfRectRefl::get_transformed(const CoordPoint& p) const { return CoordPoint(-p.x, p.y); } unsigned TransfRectRefl::get_point_type() const { return 0; } unsigned TransfRectRefl::get_new_point_type() const { return 0; } //----------------------------------------------------------------------------- CoordPoint TransfRectRot90Refl::get_transformed(const CoordPoint& p) const { return CoordPoint(p.y, p.x); } unsigned TransfRectRot90Refl::get_point_type() const { return 0; } unsigned TransfRectRot90Refl::get_new_point_type() const { return 0; } //----------------------------------------------------------------------------- CoordPoint TransfRectRot180Refl::get_transformed(const CoordPoint& p) const { return CoordPoint(p.x, -p.y); } unsigned TransfRectRot180Refl::get_point_type() const { return 0; } unsigned TransfRectRot180Refl::get_new_point_type() const { return 0; } //----------------------------------------------------------------------------- CoordPoint TransfRectRot270Refl::get_transformed(const CoordPoint& p) const { return CoordPoint(-p.y, -p.x); } unsigned TransfRectRot270Refl::get_point_type() const { return 0; } unsigned TransfRectRot270Refl::get_new_point_type() const { return 0; } //----------------------------------------------------------------------------- CoordPoint TransfTrigonRefl::get_transformed(const CoordPoint& p) const { return CoordPoint(-p.x, p.y); } unsigned TransfTrigonRefl::get_point_type() const { return 0; } unsigned TransfTrigonRefl::get_new_point_type() const { return 0; } //----------------------------------------------------------------------------- CoordPoint TransfTrigonRot60::get_transformed(const CoordPoint& p) const { float px = static_cast(p.x); float py = static_cast(p.y); int x = static_cast(floor(0.5f * px + 1.5f * py)); int y = static_cast(floor(-0.5f * px + 0.5f * py)); return CoordPoint(x, y); } unsigned TransfTrigonRot60::get_point_type() const { return 0; } unsigned TransfTrigonRot60::get_new_point_type() const { return 1; } //----------------------------------------------------------------------------- CoordPoint TransfTrigonRot120::get_transformed(const CoordPoint& p) const { float px = static_cast(p.x); float py = static_cast(p.y); int x = static_cast(floor(-0.5f * px + 1.5f * py)); int y = static_cast(ceil(-0.5f * px - 0.5f * py)); return CoordPoint(x, y); } unsigned TransfTrigonRot120::get_point_type() const { return 0; } unsigned TransfTrigonRot120::get_new_point_type() const { return 0; } //----------------------------------------------------------------------------- CoordPoint TransfTrigonRot180::get_transformed(const CoordPoint& p) const { return CoordPoint(-p.x, -p.y); } unsigned TransfTrigonRot180::get_point_type() const { return 0; } unsigned TransfTrigonRot180::get_new_point_type() const { return 1; } //----------------------------------------------------------------------------- CoordPoint TransfTrigonRot240::get_transformed(const CoordPoint& p) const { float px = static_cast(p.x); float py = static_cast(p.y); int x = static_cast(ceil(-0.5f * px - 1.5f * py)); int y = static_cast(ceil(0.5f * px - 0.5f * py)); return CoordPoint(x, y); } unsigned TransfTrigonRot240::get_point_type() const { return 0; } unsigned TransfTrigonRot240::get_new_point_type() const { return 0; } //----------------------------------------------------------------------------- CoordPoint TransfTrigonRot300::get_transformed(const CoordPoint& p) const { float px = static_cast(p.x); float py = static_cast(p.y); int x = static_cast(ceil(0.5f * px - 1.5f * py)); int y = static_cast(floor(0.5f * px + 0.5f * py)); return CoordPoint(x, y); } unsigned TransfTrigonRot300::get_point_type() const { return 0; } unsigned TransfTrigonRot300::get_new_point_type() const { return 1; } //----------------------------------------------------------------------------- CoordPoint TransfTrigonReflRot60::get_transformed(const CoordPoint& p) const { float px = static_cast(p.x); float py = static_cast(p.y); int x = static_cast(floor(0.5f * (-px) + 1.5f * py)); int y = static_cast(floor(-0.5f * (-px) + 0.5f * py)); return CoordPoint(x, y); } unsigned TransfTrigonReflRot60::get_point_type() const { return 0; } unsigned TransfTrigonReflRot60::get_new_point_type() const { return 1; } //----------------------------------------------------------------------------- CoordPoint TransfTrigonReflRot120::get_transformed(const CoordPoint& p) const { float px = static_cast(p.x); float py = static_cast(p.y); int x = static_cast(floor(-0.5f * (-px) + 1.5f * py)); int y = static_cast(ceil(-0.5f * (-px) - 0.5f * py)); return CoordPoint(x, y); } unsigned TransfTrigonReflRot120::get_point_type() const { return 0; } unsigned TransfTrigonReflRot120::get_new_point_type() const { return 0; } //----------------------------------------------------------------------------- CoordPoint TransfTrigonReflRot180::get_transformed(const CoordPoint& p) const { return CoordPoint(p.x, -p.y); } unsigned TransfTrigonReflRot180::get_point_type() const { return 0; } unsigned TransfTrigonReflRot180::get_new_point_type() const { return 1; } //----------------------------------------------------------------------------- CoordPoint TransfTrigonReflRot240::get_transformed(const CoordPoint& p) const { float px = static_cast(p.x); float py = static_cast(p.y); int x = static_cast(ceil(-0.5f * (-px) - 1.5f * py)); int y = static_cast(ceil(0.5f * (-px) - 0.5f * py)); return CoordPoint(x, y); } unsigned TransfTrigonReflRot240::get_point_type() const { return 0; } unsigned TransfTrigonReflRot240::get_new_point_type() const { return 0; } //----------------------------------------------------------------------------- CoordPoint TransfTrigonReflRot300::get_transformed(const CoordPoint& p) const { float px = static_cast(p.x); float py = static_cast(p.y); int x = static_cast(ceil(0.5f * (-px) - 1.5f * py)); int y = static_cast(floor(0.5f * (-px) + 0.5f * py)); return CoordPoint(x, y); } unsigned TransfTrigonReflRot300::get_point_type() const { return 0; } unsigned TransfTrigonReflRot300::get_new_point_type() const { return 1; } //----------------------------------------------------------------------------- } // namespace libboardgame_base pentobi-7.2/src/libboardgame_base/Transform.h000066400000000000000000000166001227240712600213450ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file Transform.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_BASE_TRANSFORM_H #define LIBBOARDGAME_BASE_TRANSFORM_H #include #include "CoordPoint.h" namespace libboardgame_base { using namespace std; //----------------------------------------------------------------------------- /** Rotation and/or reflection of local coordinates on the board. Used for local patterns. */ class Transform { public: virtual ~Transform(); virtual CoordPoint get_transformed(const CoordPoint& p) const = 0; /** Get the point type of the (0,0) coordinates. The transformation can only applied to this point type at (0,0). */ virtual unsigned get_point_type() const = 0; /** Get the new point type of the (0,0) coordinates. The transformation may change the point type of the (0,0) coordinates. For example, in the Blokus Trigon board, a reflection at the y axis changes the type from 0 (=downside triangle) to 1 (=upside triangle). @see Geometry::get_point_type() */ virtual unsigned get_new_point_type() const = 0; /** @tparam I An iterator of a container with elements of type CoordPoint */ template void transform(I begin, I end) const; }; template void Transform::transform(I begin, I end) const { for (I i = begin; i != end; ++i) *i = get_transformed(*i); } //----------------------------------------------------------------------------- class TransfIdentity : public Transform { public: CoordPoint get_transformed(const CoordPoint& p) const override; unsigned get_point_type() const override; unsigned get_new_point_type() const override; }; //----------------------------------------------------------------------------- class TransfRectRot90 : public Transform { public: CoordPoint get_transformed(const CoordPoint& p) const override; unsigned get_point_type() const override; unsigned get_new_point_type() const override; }; //----------------------------------------------------------------------------- class TransfRectRot180 : public Transform { public: CoordPoint get_transformed(const CoordPoint& p) const override; unsigned get_point_type() const override; unsigned get_new_point_type() const override; }; //----------------------------------------------------------------------------- class TransfRectRot270 : public Transform { public: CoordPoint get_transformed(const CoordPoint& p) const override; unsigned get_point_type() const override; unsigned get_new_point_type() const override; }; //----------------------------------------------------------------------------- class TransfRectRefl : public Transform { public: CoordPoint get_transformed(const CoordPoint& p) const override; unsigned get_point_type() const override; unsigned get_new_point_type() const override; }; //----------------------------------------------------------------------------- class TransfRectRot90Refl : public Transform { public: CoordPoint get_transformed(const CoordPoint& p) const override; unsigned get_point_type() const override; unsigned get_new_point_type() const override; }; //----------------------------------------------------------------------------- class TransfRectRot180Refl : public Transform { public: CoordPoint get_transformed(const CoordPoint& p) const override; unsigned get_point_type() const override; unsigned get_new_point_type() const override; }; //----------------------------------------------------------------------------- class TransfRectRot270Refl : public Transform { public: CoordPoint get_transformed(const CoordPoint& p) const override; unsigned get_point_type() const override; unsigned get_new_point_type() const override; }; //----------------------------------------------------------------------------- class TransfTrigonRot60 : public Transform { public: CoordPoint get_transformed(const CoordPoint& p) const override; unsigned get_point_type() const override; unsigned get_new_point_type() const override; }; //----------------------------------------------------------------------------- class TransfTrigonRot120 : public Transform { public: CoordPoint get_transformed(const CoordPoint& p) const override; unsigned get_point_type() const override; unsigned get_new_point_type() const override; }; //----------------------------------------------------------------------------- class TransfTrigonRot180 : public Transform { public: CoordPoint get_transformed(const CoordPoint& p) const override; unsigned get_point_type() const override; unsigned get_new_point_type() const override; }; //----------------------------------------------------------------------------- class TransfTrigonRot240 : public Transform { public: CoordPoint get_transformed(const CoordPoint& p) const override; unsigned get_point_type() const override; unsigned get_new_point_type() const override; }; //----------------------------------------------------------------------------- class TransfTrigonRot300 : public Transform { public: CoordPoint get_transformed(const CoordPoint& p) const override; unsigned get_point_type() const override; unsigned get_new_point_type() const override; }; //----------------------------------------------------------------------------- class TransfTrigonRefl : public Transform { public: CoordPoint get_transformed(const CoordPoint& p) const override; unsigned get_point_type() const override; unsigned get_new_point_type() const override; }; //----------------------------------------------------------------------------- class TransfTrigonReflRot60 : public Transform { public: CoordPoint get_transformed(const CoordPoint& p) const override; unsigned get_point_type() const override; unsigned get_new_point_type() const override; }; //----------------------------------------------------------------------------- class TransfTrigonReflRot120 : public Transform { public: CoordPoint get_transformed(const CoordPoint& p) const override; unsigned get_point_type() const override; unsigned get_new_point_type() const override; }; //----------------------------------------------------------------------------- class TransfTrigonReflRot180 : public Transform { public: CoordPoint get_transformed(const CoordPoint& p) const override; unsigned get_point_type() const override; unsigned get_new_point_type() const override; }; //----------------------------------------------------------------------------- class TransfTrigonReflRot240 : public Transform { public: CoordPoint get_transformed(const CoordPoint& p) const override; unsigned get_point_type() const override; unsigned get_new_point_type() const override; }; //----------------------------------------------------------------------------- class TransfTrigonReflRot300 : public Transform { public: CoordPoint get_transformed(const CoordPoint& p) const override; unsigned get_point_type() const override; unsigned get_new_point_type() const override; }; //----------------------------------------------------------------------------- } // namespace libboardgame_base #endif // LIBBOARDGAME_BASE_TRANSFORM_H pentobi-7.2/src/libboardgame_base/TrigonGeometry.h000066400000000000000000000167331227240712600223570ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_base/TrigonGeometry.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_BASE_TRIGON_GEOMETRY_H #define LIBBOARDGAME_BASE_TRIGON_GEOMETRY_H #include #include "Geometry.h" namespace libboardgame_base { using namespace std; //----------------------------------------------------------------------------- /** Geometry as used in the game Blokus Trigon. The board is a hexagon consisting of triangles. The coordinates are like in this example of a hexagon with edge size 3: 6 / \ / \ / \ / \ 5 / \ / \ / \ / \ / \ 4 / \ / \ / \ / \ / \ / \ 3 \ / \ / \ / \ / \ / \ / 2 \ / \ / \ / \ / \ / 1 \ / \ / \ / \ / A B C D E F G H I J K @tparam P An instantiation of libboardgame_base::Point */ template class TrigonGeometry : public Geometry

{ public: typedef P Point; static const unsigned max_size = (Point::max_width + 1) / 4; /** Create or reuse an already created geometry with a given size. @param sz The edge size of the hexagon. */ static const TrigonGeometry* get(unsigned sz); unsigned get_point_type(int x, int y) const override; unsigned get_period_x() const override; unsigned get_period_y() const override; protected: void init_is_onboard(Point p, bool& is_onboard) const; void init_adj_diag(Point p, NullTermList& adj, NullTermList& diag) const; private: static unique_ptr s_geometry[max_size + 1]; unsigned m_sz; TrigonGeometry(unsigned size); }; template unique_ptr> TrigonGeometry

::s_geometry[max_size + 1]; template TrigonGeometry

::TrigonGeometry(unsigned sz) { LIBBOARDGAME_ASSERT(sz <= max_size); m_sz = sz; Geometry

::init(sz * 4 - 1, sz * 2); } template const TrigonGeometry

* TrigonGeometry

::get(unsigned sz) { if (! s_geometry[sz]) s_geometry[sz].reset(new TrigonGeometry(sz)); return s_geometry[sz].get(); } template unsigned TrigonGeometry

::get_period_x() const { return 2; } template unsigned TrigonGeometry

::get_period_y() const { return 2; } template unsigned TrigonGeometry

::get_point_type(int x, int y) const { if (m_sz % 2 == 0) { if (x % 2 != 0) return (y % 2 == 0 ? 0 : 1); else return (y % 2 != 0 ? 0 : 1); } else { if (x % 2 == 0) return (y % 2 == 0 ? 0 : 1); else return (y % 2 != 0 ? 0 : 1); } } template void TrigonGeometry

::init_is_onboard(Point p, bool& is_onboard) const { unsigned dy = min(p.get_y(), Geometry

::get_height() - p.get_y() - 1); unsigned min_x = m_sz - dy - 1; unsigned max_x = Geometry

::get_width() - min_x - 1; is_onboard = (p.get_x() >= min_x && p.get_x() <= max_x); } template void TrigonGeometry

::init_adj_diag(Point p, NullTermList& adj, NullTermList& diag) const { unsigned width = this->get_width(); unsigned height = this->get_height(); unsigned x = p.get_x(); unsigned y = p.get_y(); unsigned type = Geometry

::get_point_type(p); { typename NullTermList::Init init_adj(adj); if (type == 0) { if (y < height - 1 && this->is_onboard(p.get_up())) init_adj.push_back(p.get_up()); if (x > 0 && this->is_onboard(p.get_left())) init_adj.push_back(p.get_left()); if (x < width - 1 && this->is_onboard(p.get_right())) init_adj.push_back(p.get_right()); } else { if (y > 0 && this->is_onboard(p.get_down())) init_adj.push_back(p.get_down()); if (x > 0 && this->is_onboard(p.get_left())) init_adj.push_back(p.get_left()); if (x < width - 1 && this->is_onboard(p.get_right())) init_adj.push_back(p.get_right()); } init_adj.finish(); } { typename NullTermList::Init init_diag(diag); if (type == 0) { // The order does not matter logically but it is better to put // far away 2nd order adjacent points first because it slightly // increases the efficiency of libpentobi_base::BoardConst, which // uses the forbidden status of the first few points from this list // during move generation and those points can reject more moves. if (x > 1 && this->is_onboard(p.get_left().get_left())) init_diag.push_back(p.get_left().get_left()); if (x < width - 2 && this->is_onboard(p.get_right().get_right())) init_diag.push_back(p.get_right().get_right()); if (x > 0 && y > 0 && this->is_onboard(p.get_down_left())) init_diag.push_back(p.get_down_left()); if (x < width - 1 && y > 0 && this->is_onboard(p.get_down_right())) init_diag.push_back(p.get_down_right()); if (x < width - 1 && y < height - 1 && this->is_onboard(p.get_up_right())) init_diag.push_back(p.get_up_right()); if (x > 0 && y < height - 1 && this->is_onboard(p.get_up_left())) init_diag.push_back(p.get_up_left()); if (y > 0 && this->is_onboard(p.get_down())) init_diag.push_back(p.get_down()); if (x > 1 && y < height - 1 && this->is_onboard(p.get_up_left().get_left())) init_diag.push_back(p.get_up_left().get_left()); if (x < width - 2 && y < height - 1 && this->is_onboard(p.get_up_right().get_right())) init_diag.push_back(p.get_up_right().get_right()); } else { // See comment at type == 0 for the order of moves. if (x > 1 && this->is_onboard(p.get_left().get_left())) init_diag.push_back(p.get_left().get_left()); if (x < width - 2 && this->is_onboard(p.get_right().get_right())) init_diag.push_back(p.get_right().get_right()); if (x > 0 && y < height - 1 && this->is_onboard(p.get_up_left())) init_diag.push_back(p.get_up_left()); if (x < width - 1 && y < height - 1 && this->is_onboard(p.get_up_right())) init_diag.push_back(p.get_up_right()); if (x < width - 1 && y > 0 && this->is_onboard(p.get_down_right())) init_diag.push_back(p.get_down_right()); if (x > 0 && y > 0 && this->is_onboard(p.get_down_left())) init_diag.push_back(p.get_down_left()); if (y < height - 1 && this->is_onboard(p.get_up())) init_diag.push_back(p.get_up()); if (x > 1 && y > 0 && this->is_onboard(p.get_down_left().get_left())) init_diag.push_back(p.get_down_left().get_left()); if (x < width - 2 && y > 0 && this->is_onboard(p.get_down_right().get_right())) init_diag.push_back(p.get_down_right().get_right()); } init_diag.finish(); } } //----------------------------------------------------------------------------- } // namespace libboardgame_base #endif // LIBBOARDGAME_BASE_TRIGON_GEOMETRY_H pentobi-7.2/src/libboardgame_gtp/000077500000000000000000000000001227240712600170765ustar00rootroot00000000000000pentobi-7.2/src/libboardgame_gtp/Arguments.cpp000066400000000000000000000043261227240712600215540ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_gtp/Arguments.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Arguments.h" #include namespace libboardgame_gtp { using namespace std; //----------------------------------------------------------------------------- void Arguments::check_size(size_t n) const { if (get_size() == n) return; if (n == 0) throw Failure("no arguments allowed"); else if (n == 1) throw Failure("command needs one argument"); else { ostringstream msg; msg << "command needs " << n << " arguments"; throw Failure(msg.str()); } } void Arguments::check_size_less_equal(size_t n) const { if (get_size() <= n) return; if (n == 1) throw Failure("command needs at most one argument"); else { ostringstream msg; msg << "command needs at most " << n << " arguments"; throw Failure(msg.str()); } } CmdLineRange Arguments::get(size_t i) const { if (i < get_size()) return m_line.get_element(m_line.get_idx_name() + i + 1); ostringstream msg; msg << "missing argument " << (i + 1); throw Failure(msg.str()); } string Arguments::get_tolower(size_t i) const { string value = get(i); for (auto& c : value) c = static_cast(tolower(c)); return value; } string Arguments::get_tolower() const { check_size(1); return get_tolower(0); } string Arguments::get_remaining_arg(size_t i) const { size_t size = get_size(); if (size == i + 1) return ""; else if (size == i + 2) return get(i + 1); else return get_remaining_line(i); } CmdLineRange Arguments::get_remaining_line(size_t i) const { if (i < get_size()) return m_line.get_trimmed_line_after_elem(m_line.get_idx_name() + i + 1); ostringstream msg; msg << "missing argument " << (i + 1); throw Failure(msg.str()); } //----------------------------------------------------------------------------- } // namespace libboardgame_gtp pentobi-7.2/src/libboardgame_gtp/Arguments.h000066400000000000000000000166561227240712600212320ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_gtp/Arguments.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_GTP_ARGUMENTS_H #define LIBBOARDGAME_GTP_ARGUMENTS_H #if defined __GNUC__ #include #endif #include "CmdLine.h" namespace libboardgame_gtp { using namespace std; //----------------------------------------------------------------------------- /** Access arguments of command line. */ class Arguments { public: /** Constructor. @param line The command line (@ref libboardgame_doc_storesref) */ Arguments(const CmdLine& line); ~Arguments() throw(); /** Get argument. @param i Argument index starting with 0 @return Argument value @throws Failure If no such argument */ CmdLineRange get(size_t i) const; /** Get single argument. @return Argument value @throws Failure If no such argument or command has more than one arguments */ CmdLineRange get() const; /** Get argument converted to lowercase. @param i Argument index starting with 0 @return Copy of argument value converted to lowercase @throws Failure If no such argument */ string get_tolower(size_t i) const; /** Get single argument converted to lowercase. */ string get_tolower() const; /** Get argument converted to a type. The type must implement operator<<(istream) @param i Argument index starting with 0 @return The converted argument @throws Failure If no such argument, or argument cannot be converted */ template T parse(size_t i) const; /** Get single argument converted to a type. The type must implement operator<<(istream) @return The converted argument @throws Failure If no such argument, or argument cannot be converted, or command has more than one arguments */ template T parse() const; /** Get argument converted to a type and check against a minum value. The type must implement operator<< and operator< @param i Argument index starting with 0 @param min Minimum allowed value @return Argument value @throws Failure If no such argument, argument cannot be converted or smaller than the mimimum value */ template T parse_min(size_t i, T min) const; /** Get argument converted to a type and check against a range. The type must implement operator<< and operator< @param i Argument index starting with 0 @param min Minimum allowed value @param max Maximum allowed value @return Argument value @throws Failure If no such argument, argument cannot be converted or not in range */ template T parse_min_max(size_t i, T min, T max) const; template T parse_min_max(T min, T max) const; /** Check that command has no arguments. @throws Failure If command has arguments */ void check_empty() const; /** Check number of arguments. @param n Expected number of arguments @throws Failure If command has a different number of arguments */ void check_size(size_t n) const; /** Check maximum number of arguments. @param n Expected maximum number of arguments @throws Failure If command has more arguments */ void check_size_less_equal(size_t n) const; /** Get argument line. Get all arguments as a line. No modfications to the line were made apart from trimmimg leading and trailing white spaces. */ CmdLineRange get_line() const; /** Get number of arguments. */ size_t get_size() const; /** Return remaining line after argument. @param i Argument index starting with 0 @return The remaining line after the given argument, unmodified apart from leading and trailing whitespaces, which are trimmed. Quotation marks are not handled. @throws Failure If no such argument */ CmdLineRange get_remaining_line(size_t i) const; /** Return remaining line after argument as an argument. This function can be used for parsing string parameter values as used by analyze commands of type 'param' by GoGui (http://gogui.sf.net). GoGui never quotes string parameter values, because it does not know if the parameter value is a single argument or a list that will be further parsed by the engine. This can cause problems if the argument value is an unquoted filename containing spaces. This function can be used to handle this situation. It uses the arguments that were detected with supports for quoting and returns en empty string if the number of arguments is i, the argument i + 1 if the number of arguments is i + 1, and get_remaining_line(i) otherwise. @param i Argument index starting with 0 @return The remaining argument. @throws Failure If the number of arguments is less than i */ string get_remaining_arg(size_t i) const; private: const CmdLine& m_line; template static string get_type_name(); }; inline Arguments::Arguments(const CmdLine& line) : m_line(line) { } inline Arguments::~Arguments() throw() { } inline void Arguments::check_empty() const { check_size(0); } inline CmdLineRange Arguments::get() const { check_size(1); return get(0); } inline CmdLineRange Arguments::get_line() const { return m_line.get_trimmed_line_after_elem(m_line.get_idx_name()); } inline size_t Arguments::get_size() const { return m_line.get_elements().size() - m_line.get_idx_name() - 1; } template string Arguments::get_type_name() { #ifdef __GNUC__ int status; auto name_ptr = abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, &status); if (status == 0) { string result(name_ptr); free(name_ptr); return result; } else return typeid(T).name(); #else return typeid(T).name(); #endif } template T Arguments::parse() const { check_size(1); return parse(0); } template T Arguments::parse(size_t i) const { string s = get(i); istringstream in(s); T result; in >> result; if (! in) { ostringstream msg; msg << "argument " << (i + 1) << " ('" << s << "') has invalid type (expected " << get_type_name() << ")"; throw Failure(msg.str()); } return result; } template T Arguments::parse_min(std::size_t i, T min) const { T result = parse(i); if (result < min) { ostringstream msg; msg << "argument " << (i + 1) << " must be greater or equal " << min; throw Failure(msg.str()); } return result; } template T Arguments::parse_min_max(T min, T max) const { check_size(1); return parse_min_max(0, min, max); } template T Arguments::parse_min_max(std::size_t i, T min, T max) const { T result = parse_min(i, min); if (max < result) { ostringstream msg; msg << "argument " << (i + 1) << " must be less or equal " << max; throw Failure(msg.str()); } return result; } //----------------------------------------------------------------------------- } // namespace libboardgame_gtp #endif // LIBBOARDGAME_GTP_ARGUMENTS_H pentobi-7.2/src/libboardgame_gtp/CMakeLists.txt000066400000000000000000000003651227240712600216420ustar00rootroot00000000000000include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) set(boardgame_gtp_STAT_SRCS Arguments.cpp CmdLine.cpp CmdLineRange.cpp Engine.cpp Response.cpp ) add_library(boardgame_gtp STATIC ${boardgame_gtp_STAT_SRCS}) pentobi-7.2/src/libboardgame_gtp/CmdLine.cpp000066400000000000000000000053611227240712600211220ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_gtp/CmdLine.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "CmdLine.h" #include namespace libboardgame_gtp { using namespace std; //----------------------------------------------------------------------------- CmdLine::~CmdLine() throw() { } /** Find elements (ID, command name, arguments). Arguments are words separated by whitespaces. Arguments with whitespaces can be quoted with quotation marks ('"'). Characters can be escaped with a backslash ('\'). */ void CmdLine::find_elem() { m_elem.clear(); bool escape = false; bool is_in_string = false; string::const_iterator begin = m_line.begin(); string::const_iterator i; for (i = begin; i < m_line.end(); ++i) { char c = *i; if (c == '"' && ! escape) { if (is_in_string) m_elem.push_back(CmdLineRange(begin, i)); begin = i + 1; is_in_string = ! is_in_string; } else if (isspace(static_cast(c)) && ! is_in_string) { if (i > begin) m_elem.push_back(CmdLineRange(begin, i)); begin = i + 1; } escape = (c == '\\' && ! escape); } if (i > begin) m_elem.push_back(CmdLineRange(begin, m_line.end())); } CmdLineRange CmdLine::get_trimmed_line_after_elem(size_t i) const { assert(i < m_elem.size()); auto& e = m_elem[i]; auto begin = e.end(); if (begin < m_line.end() && *begin == '"') ++begin; while (begin < m_line.end() && isspace(static_cast(*begin))) ++begin; auto end = m_line.end(); while (end > begin && isspace(static_cast(*(end - 1)))) --end; return CmdLineRange(begin, end); } void CmdLine::init(const string& line) { m_line = line; find_elem(); assert(! m_elem.empty()); parse_id(); assert(! m_elem.empty()); } void CmdLine::init(const CmdLine& c) { m_idx_name = c.m_idx_name; m_line = c.m_line; m_elem.clear(); for (auto i = c.m_elem.begin(); i != c.m_elem.end(); ++i) { auto begin = m_line.begin() + (i->begin() - c.m_line.begin()); auto end = m_line.begin() + (i->end() - c.m_line.begin()); m_elem.push_back(CmdLineRange(begin, end)); } } void CmdLine::parse_id() { m_idx_name = 0; if (m_elem.size() < 2) return; istringstream in(m_elem[0]); int id; in >> id; if (in) m_idx_name = 1; } //----------------------------------------------------------------------------- } // namespace libboardgame_gtp pentobi-7.2/src/libboardgame_gtp/CmdLine.h000066400000000000000000000050661227240712600205710ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_gtp/CmdLine.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_GTP_CMDLINE_H #define LIBBOARDGAME_GTP_CMDLINE_H #include #include #include #include #include #include #include #include "CmdLineRange.h" #include "Failure.h" namespace libboardgame_gtp { using namespace std; //----------------------------------------------------------------------------- /** Parsed GTP command line. Only used internally by libboardgame_gtp::Engine. GTP command handlers query arguments of the command line through the instance of class Arguments given as a function argument by class Engine to the command handler. */ class CmdLine { public: /** Construct empty command. @warning An empty command cannot be used, before init() was called. This constructor exists only to reuse instances. */ CmdLine(); /** Construct with a command line. @see init() */ CmdLine(const string& line); ~CmdLine() throw(); void init(const string& line); void init(const CmdLine& c); const string& get_line() const; /** Get command name. */ CmdLineRange get_name() const; void write_id(ostream& out) const; CmdLineRange get_trimmed_line_after_elem(size_t i) const; const vector& get_elements() const; const CmdLineRange& get_element(size_t i) const; int get_idx_name() const; private: int m_idx_name; /** Full command line. */ string m_line; vector m_elem; void find_elem(); void parse_id(); }; inline CmdLine::CmdLine() { } inline CmdLine::CmdLine(const string& line) { init(line); } inline const vector& CmdLine::get_elements() const { return m_elem; } inline const CmdLineRange& CmdLine::get_element(size_t i) const { assert(i < m_elem.size()); return m_elem[i]; } inline int CmdLine::get_idx_name() const { return m_idx_name; } inline const string& CmdLine::get_line() const { return m_line; } inline CmdLineRange CmdLine::get_name() const { return m_elem[m_idx_name]; } inline void CmdLine::write_id(ostream& out) const { if (m_idx_name == 0) return; auto& e = m_elem[0]; copy(e.begin(), e.end(), ostream_iterator(out)); } //----------------------------------------------------------------------------- } // namespace libboardgame_gtp #endif // LIBBOARDGAME_GTP_CMDLINE_H pentobi-7.2/src/libboardgame_gtp/CmdLineRange.cpp000066400000000000000000000012471227240712600220760ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_gtp/CmdLineRange.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "CmdLineRange.h" #include namespace libboardgame_gtp { //----------------------------------------------------------------------------- CmdLineRange::operator string() const { return string(m_begin, m_end); } void CmdLineRange::write(ostream& o) const { o << string(*this); } //----------------------------------------------------------------------------- } // namespace libboardgame_gtp pentobi-7.2/src/libboardgame_gtp/CmdLineRange.h000066400000000000000000000044061227240712600215430ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_gtp/CmdLineRange.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_GTP_CMDLINERANGE_H #define LIBBOARDGAME_GTP_CMDLINERANGE_H #include #include #include namespace libboardgame_gtp { using namespace std; //----------------------------------------------------------------------------- /** Subrange of the GTP command line. Avoids allocation of strings on the heap for each parsed command line. Instances of this class are valid only during the lifetime of the command line object. Command handlers, which access the command line through the instance of Arguments given as a function argument, should not store references to CmdLineRange objects. */ class CmdLineRange { public: CmdLineRange(string::const_iterator begin, string::const_iterator end); bool operator==(const string& s) const; bool operator!=(const string& s) const; operator string() const; string::const_iterator begin() const; string::const_iterator end() const; string::size_type size() const; void write(ostream& o) const; private: string::const_iterator m_begin; string::const_iterator m_end; }; inline CmdLineRange::CmdLineRange(string::const_iterator begin, string::const_iterator end) : m_begin(begin), m_end(end) { } inline bool CmdLineRange::operator==(const string& s) const { return size() == s.size() && equal(m_begin, m_end, s.begin()); } inline bool CmdLineRange::operator!=(const string& s) const { return ! operator==(s); } inline string::const_iterator CmdLineRange::begin() const { return m_begin; } inline string::const_iterator CmdLineRange::end() const { return m_end; } inline string::size_type CmdLineRange::size() const { return m_end - m_begin; } //----------------------------------------------------------------------------- inline ostream& operator<<(ostream& out, const CmdLineRange& r) { r.write(out); return out; } //----------------------------------------------------------------------------- } // namespace libboardgame_gtp #endif // LIBBOARDGAME_GTP_CMDLINERANGE_H pentobi-7.2/src/libboardgame_gtp/Engine.cpp000066400000000000000000000422011227240712600210060ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_gtp/Engine.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Engine.h" #include #include #include #include #ifdef USE_BOOST_THREAD #include #include #include #else #include #include #include #endif namespace libboardgame_gtp { using namespace std; #ifdef USE_BOOST_THREAD using boost::condition_variable; using boost::lock_guard; using boost::mutex; using boost::unique_lock; using boost::thread; #endif //----------------------------------------------------------------------------- /** Utility functions. */ namespace { /** Check, if line contains a command. */ bool is_cmd_line(const string& line) { for (char c : line) if (! isspace(static_cast(c))) return c != '#'; return false; } /** Read next command from stream. @param in The input stream. @param[out] c The command (reused for efficiency) @return @c false on end-of-stream or read error. */ bool read_cmd(CmdLine& c, istream& in) { string line; while (getline(in, line)) if (is_cmd_line(line)) break; if (! in.fail()) { c.init(line); return true; } else return false; } /** Copied from libboardgame_util because libboardgame_gtp should use only C++11 standard libraries and not depend on any other code. */ class Barrier { public: Barrier(unsigned count); void wait(); private: mutex m_mutex; condition_variable m_condition; unsigned m_threshold; unsigned m_count; unsigned m_current; }; Barrier::Barrier(unsigned count) : m_threshold(count), m_count(count), m_current(0) { assert(count > 0); } void Barrier::wait() { unique_lock lock(m_mutex); unsigned current = m_current; if (--m_count == 0) { ++m_current; m_count = m_threshold; m_condition.notify_one(); } else while (current == m_current) m_condition.wait(lock); } /** Ponder thread used by Engine::MainLoop(). This thread calls Engine::ponder() while the engine is waiting for the next command. @see Engine::ponder() */ class PonderThread { public: PonderThread(Engine& engine); ~PonderThread() throw(); void start_ponder(); void stop_ponder(); private: class Function { public: Function(PonderThread& t); void operator()(); private: PonderThread& m_thread; }; friend class PonderThread::Function; bool m_quit; bool m_start_ponder_flag; bool m_ponder_finished_flag; Engine& m_engine; Barrier m_ready; mutex m_start_ponder_mutex; mutex m_ponder_finished_mutex; condition_variable m_start_ponder_cond; condition_variable m_ponder_finished_cond; unique_lock m_ponder_finished_lock; /** The thread to run the ponder function. Order dependency: must be constructed as the last member, because the constructor starts the thread. */ thread m_thread; }; PonderThread::Function::Function(PonderThread& t) : m_thread(t) { } void PonderThread::Function::operator()() { unique_lock lock(m_thread.m_start_ponder_mutex); m_thread.m_ready.wait(); m_thread.m_quit = false; while (true) { while (! m_thread.m_start_ponder_flag) m_thread.m_start_ponder_cond.wait(lock); m_thread.m_start_ponder_flag = false; auto& engine = m_thread.m_engine; if (! m_thread.m_quit) { engine.ponder(); { lock_guard lock(m_thread.m_ponder_finished_mutex); m_thread.m_ponder_finished_flag = true; } m_thread.m_ponder_finished_cond.notify_one(); } else return; } } PonderThread::PonderThread(Engine& engine) : m_start_ponder_flag(false), m_ponder_finished_flag(false), m_engine(engine), m_ready(2), m_ponder_finished_lock(m_ponder_finished_mutex), m_thread(Function(*this)) { m_ready.wait(); } PonderThread::~PonderThread() throw() { m_quit = true; { lock_guard lock(m_start_ponder_mutex); m_start_ponder_flag = true; } m_start_ponder_cond.notify_one(); if (m_thread.joinable()) m_thread.join(); } void PonderThread::start_ponder() { m_engine.init_ponder(); { lock_guard lock(m_start_ponder_mutex); m_start_ponder_flag = true; } m_start_ponder_cond.notify_one(); } void PonderThread::stop_ponder() { m_engine.stop_ponder(); while (! m_ponder_finished_flag) m_ponder_finished_cond.wait(m_ponder_finished_lock); m_ponder_finished_flag = false; } /** Thread for reading the next command line. This thread is used instead of the simple function read_command(CmdLine&), if Engine is compiled with interrupt support. @see Engine::interrupt() */ class ReadThread { public: ReadThread(istream& in, Engine& engine); ~ReadThread() throw(); bool read_cmd(CmdLine& c); private: /** Data used by ReadThread and ReadThread::Function. The lifetime of this data must exceed the lifetime of both the ReadThread object and the thread function. There are use cases in which the user wants to destruct the ReadThread object before the EOF of the input stream is reached (e.g. if a command handler throws an exception other than Failure), and we cannot terminate the thread if it is doing a blocking read on the input stream. Therefore the Data object is allocated on the heap and uses a thread-safe reference counter to determine when it will be deleted. Note that it could be never deleted if the program exits before the EOF of the blocking input stream is used. Therefore, this struct should not contain any members whose destructor is important to run. */ struct Data { int ref_count; bool quit; bool is_stream_good; bool wait_cmd_flag; bool cmd_received_flag; mutex ref_count_mutex; istream& in; Engine& engine; Barrier ready; mutex wait_cmd_mutex; mutex cmd_received_mutex; condition_variable wait_cmd_cond; condition_variable cmd_received_cond; unique_lock cmd_received_lock; CmdLine cmd; /** Increment reference count. */ void inc_ref_count(); /** Decrement reference count. @return true, if reference count is zero after the decrement */ bool dec_ref_count(); Data(istream& in, Engine& engine); }; struct DataRef { Data& data; DataRef(Data& data); DataRef(const DataRef& data_ref); ~DataRef() throw(); }; class Function { public: Function(Data& data); void operator()(); private: DataRef m_data_ref; }; friend class ReadThread::Function; DataRef m_data_ref; /** The thread to run the read command function. Order dependency: must be constructed as the last member, because the constructor starts the thread. */ thread m_thread; }; ReadThread::Data::Data(istream& in, Engine& engine) : ref_count(0), quit(false), wait_cmd_flag(false), cmd_received_flag(false), in(in), engine(engine), ready(2), cmd_received_lock(cmd_received_mutex) { } void ReadThread::Data::inc_ref_count() { lock_guard lock(ref_count_mutex); ++ref_count; } bool ReadThread::Data::dec_ref_count() { bool is_last_ref; { lock_guard lock(ref_count_mutex); is_last_ref = ((--ref_count) == 0); } return is_last_ref; } ReadThread::DataRef::DataRef(Data& data) : data(data) { data.inc_ref_count(); } ReadThread::DataRef::DataRef(const DataRef& data_ref) : data(data_ref.data) { data.inc_ref_count(); } ReadThread::DataRef::~DataRef() throw() { if (data.dec_ref_count()) delete &data; } ReadThread::Function::Function(Data& data) : m_data_ref(data) { } void ReadThread::Function::operator()() { auto& data = m_data_ref.data; unique_lock lock(data.wait_cmd_mutex); data.ready.wait(); auto& engine = data.engine; auto& in = data.in; string line; data.is_stream_good = true; while (true) { while (getline(in, line)) { if (data.quit) return; if (line.find("# interrupt") == 0) engine.interrupt(); else if (is_cmd_line(line)) break; } while (! data.wait_cmd_flag) data.wait_cmd_cond.wait(lock); data.wait_cmd_flag = false; if (data.quit) return; if (! in.fail()) data.cmd.init(line); else data.is_stream_good = false; { lock_guard lock(data.cmd_received_mutex); data.cmd_received_flag = true; } data.cmd_received_cond.notify_one(); if (in.fail()) return; } } ReadThread::ReadThread(istream& in, Engine& engine) : m_data_ref(*(new Data(in, engine))), m_thread(Function(m_data_ref.data)) { m_data_ref.data.ready.wait(); m_thread.detach(); } ReadThread::~ReadThread() throw() { auto& data = m_data_ref.data; data.quit = true; if (data.is_stream_good) // User destructs ReadThread before EOF was reached, so we cannot wait // for the read thread to finish because it could do a blocking read. return; if (m_thread.joinable()) m_thread.join(); } bool ReadThread::read_cmd(CmdLine& c) { auto& data = m_data_ref.data; { lock_guard lock(data.wait_cmd_mutex); data.wait_cmd_flag = true; } data.wait_cmd_cond.notify_one(); while (! data.cmd_received_flag) data.cmd_received_cond.wait(data.cmd_received_lock); data.cmd_received_flag = false; if (! data.is_stream_good) return false; c.init(data.cmd); return true; } } // namespace //----------------------------------------------------------------------------- Engine::Engine() { add("known_command", &Engine::cmd_known_command); add("list_commands", &Engine::cmd_list_commands); add("name", &Engine::cmd_name); add("protocol_version", &Engine::cmd_protocol_version); add("quit", &Engine::cmd_quit); add("version", &Engine::cmd_version); } Engine::~Engine() throw() { } void Engine::add(const string& name, Handler f) { m_handlers[name] = f; } void Engine::add(const string& name, HandlerNoArgs f) { add(name, Handler(bind(no_args_wrapper, f, placeholders::_1, placeholders::_2))); } void Engine::add(const string& name, HandlerNoResponse f) { add(name, Handler(bind(no_response_wrapper, f, placeholders::_1, placeholders::_2))); } void Engine::add(const string& name, HandlerNoArgsNoResponse f) { add(name, Handler(bind(no_args_no_response_wrapper, f, placeholders::_1, placeholders::_2))); } void Engine::add(const string& name, void (*f)(const Arguments&, Response&)) { add(name, Handler(f)); } void Engine::add(const string& name, void (*f)(Response&)) { add(name, HandlerNoArgs(f)); } void Engine::add(const string& name, void (*f)(const Arguments&)) { add(name, HandlerNoResponse(f)); } void Engine::add(const string& name, void (*f)()) { add(name, HandlerNoArgsNoResponse(f)); } /** Return @c true if command is known, @c false otherwise. */ void Engine::cmd_known_command(const Arguments& args, Response& response) { bool is_known = (m_handlers.find(args.get()) != m_handlers.end()); response.set(is_known ? "true" : "false"); } /** List all known commands. */ void Engine::cmd_list_commands(Response& response) { for (HandlerIterator i = m_handlers.begin(); i != m_handlers.end(); ++i) response << i->first << '\n'; } /** Return name. */ void Engine::cmd_name(Response& response) { response.set("Unknown"); } /** Return protocol version. */ void Engine::cmd_protocol_version(Response& response) { response.set("2"); } /** Quit command loop. */ void Engine::cmd_quit() { m_quit = true; } /** Return empty version string. The GTP standard says to return empty string, if no meaningful reponse is available. */ void Engine::cmd_version(Response&) { } bool Engine::contains(const string& name) const { return (m_handlers.find(name) != m_handlers.end()); } void Engine::remove(const string& name) { auto i = m_handlers.find(name); if (i != m_handlers.end()) m_handlers.erase(i); } string Engine::exec(const string& line) { assert(is_cmd_line(line)); Response response; string buffer; CmdLine cmd; cmd.init(line); ofstream null_stream; bool status = handle_cmd(cmd, null_stream, response, buffer); if (! status) throw Failure(response.to_string()); return response.to_string(); } bool Engine::exec(istream& in, bool throw_on_fail, ostream& log) { string line; Response response; string buffer; CmdLine cmd; while (getline(in, line)) { if (! is_cmd_line(line)) continue; cmd.init(line); log << cmd.get_line() << '\n'; bool status = handle_cmd(cmd, log, response, buffer); if (! status && throw_on_fail) { ostringstream msg; msg << "executing '" << cmd.get_line() << "' failed"; throw Failure(msg.str()); } } return ! in.fail(); } void Engine::exec_main_loop(istream& in, ostream& out) { // Tying of input to output stream (like used by std::cin/cout) is not // needed by this class and potentially harmful if threads are enabled and // the standard library implementation does not support simultaneous writes // to output stream by multiple threads in.tie(nullptr); m_quit = false; PonderThread ponder_thread(*this); ReadThread read_thread(in, *this); CmdLine cmd; Response response; string buffer; while (! m_quit) { ponder_thread.start_ponder(); bool is_stream_good = read_thread.read_cmd(cmd); ponder_thread.stop_ponder(); if (is_stream_good) handle_cmd(cmd, out, response, buffer); else m_quit = true; } } void Engine::exec_main_loop_st(istream& in, ostream& out) { in.tie(nullptr); // See comment in exec_main_loop() m_quit = false; CmdLine cmd; Response response; string buffer; while (! m_quit) { if (read_cmd(cmd, in)) handle_cmd(cmd, out, response, buffer); else break; } } /** Call the handler of a command and write its response. @param line The command @param out The output stream for the response @param response A reusable response instance to avoid memory allocation in each function call @param buffer A reusable string instance to avoid memory allocation in each function call */ bool Engine::handle_cmd(CmdLine& line, ostream& out, Response& response, string& buffer) { on_handle_cmd_begin(); bool status = true; try { response.clear(); HandlerIterator pos = m_handlers.find(line.get_name()); if (pos != m_handlers.end()) { Arguments args(line); (pos->second)(args, response); } else { status = false; response << "unknown command (" << line.get_name() << ')'; } } catch (const Failure& failure) { status = false; response.set(failure.get_response()); } // Keep output to cerr in sync, because out will be explicitely flushed by // this function after the response was written, but cerr could be buffered cerr.flush(); out << (status ? '=' : '?'); line.write_id(out); out << ' '; response.write(out, buffer); out << flush; return status; } void Engine::init_ponder() { // Default implementation does nothing } void Engine::interrupt() { // Default implementation does nothing } void Engine::no_args_wrapper(HandlerNoArgs h, const Arguments& args, Response& response) { args.check_empty(); h(response); } void Engine::no_response_wrapper(HandlerNoResponse h, const Arguments& args, Response&) { h(args); } void Engine::no_args_no_response_wrapper(HandlerNoArgsNoResponse h, const Arguments& args, Response&) { args.check_empty(); h(); } void Engine::on_handle_cmd_begin() { // Default implementation does nothing } void Engine::ponder() { // Default implementation does nothing } void Engine::stop_ponder() { // Default implementation does nothing } //----------------------------------------------------------------------------- } // namespace libboardgame_gtp pentobi-7.2/src/libboardgame_gtp/Engine.h000066400000000000000000000236421227240712600204630ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_gtp/Engine.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_GTP_ENGINE_H #define LIBBOARDGAME_GTP_ENGINE_H #include #include #include #include #include "Arguments.h" #include "CmdLine.h" #include "Response.h" namespace libboardgame_gtp { using namespace std; //----------------------------------------------------------------------------- /** Base class for GTP engines. Commands can be added with Engine::add(). Existing commands can be overridden by registering a new handler for the command. @see @ref libboardgame_gtp_commands */ class Engine { public: typedef function Handler; typedef function HandlerNoArgs; typedef function HandlerNoResponse; typedef function HandlerNoArgsNoResponse; /** @page libboardgame_gtp_commands libboardgame_gtp::Engine GTP commands

@link cmd_known_command() @c known_command @endlink
@copydoc cmd_known_command()
@link cmd_list_commands() @c list_commands @endlink
@copydoc cmd_list_commands()
@link cmd_name() @c name @endlink
@copydoc cmd_name()
@link cmd_protocol_version() @c protocol_version @endlink
@copydoc cmd_protocol_version()
@link cmd_quit() @c quit @endlink
@copydoc cmd_quit()
@link cmd_version() @c version @endlink
@copydoc cmd_version()
*/ /** @name Command handlers */ // @{ void cmd_known_command(const Arguments&, Response&); void cmd_list_commands(Response&); void cmd_name(Response&); void cmd_protocol_version(Response&); void cmd_quit(); void cmd_version(Response&); // @} // @name Engine(); virtual ~Engine() throw(); /** Execute commands from an input stream. @param in The input stream @param throw_on_fail Whether to throw an exception if a command fails, or to continue executing the remainign commands @param log Stream for logging the commands and responses to. @return The stream state as a bool @throws Failure If a command fails, and @c throw_on_fail is @c true */ bool exec(istream& in, bool throw_on_fail, ostream& log); /** Execute a single command line. @param line The command line (must contain a valid command, comment lines or empty lines are not allowed) @return The response without status character or command ID @throw Failure If the command fails */ string exec(const string& line); /** Run the main command loop. Reads lines from input stream, calls the corresponding command handler and writes the response to the output stream. Empty lines in the command responses will be replaced by a line containing a single space, because empty lines are not allowed in GTP responses. @note If a command handler throws an exception other than Failure, the input stream can no longer be used, because it might not be possible to terminate the read thread if it is currently doing a blocking read on the input stream. */ void exec_main_loop(istream& in, ostream& out); /** Single-threaded version of exec_main_loop(). Does not start threads for supporting pondering or interruption. This function can be used if pondering or interruption is not needed. */ void exec_main_loop_st(istream& in, ostream& out); /** Register command handler. If a command was already registered with the same name, it will be replaced by the new command. */ void add(const string& name, Handler f); void add(const string& name, HandlerNoArgs f); void add(const string& name, HandlerNoResponse f); void add(const string& name, HandlerNoArgsNoResponse f); void add(const string& name, void (*f)(const Arguments&, Response&)); void add(const string& name, void (*f)(Response&)); void add(const string& name, void (*f)(const Arguments&)); void add(const string& name, void (*f)()); /** Register a member function as a command handler. If a command was already registered with the same name, it will be replaced by the new command. */ template void add(const string& name, void (T::*f)(const Arguments&, Response&), T* t); template void add(const string& name, void (T::*f)(const Arguments&), T* t); template void add(const string& name, void (T::*f)(Response&), T* t); template void add(const string& name, void (T::*f)(), T* t); /** Returns if command registered. */ bool contains(const string& name) const; /** Unregister a command. */ void remove(const string& name); /** Ponder. This function will be called in main_loop() while the engine is waiting for the next command. It will be called after init_ponder() from a different thread than the command thread, but only while waiting for the next command, so no concurrent execution of this function and other engine functions is possible. The function should return immediately when stop_ponder() is called. init_ponder() and stop_ponder() are called from the command thread. In a typical implementation, init_ponder() will clear an abort flag and stop_ponder() will set it. ponder() will poll the abort flag and return when it is set (or it has nothing to do; or some maximum time limit for pondering was exceeded). The default implementation does nothing and returns immediately. */ virtual void ponder(); /** Prepare for pondering. @see ponder() The default implementation does nothing. */ virtual void init_ponder(); /** Stop pondering. @see ponder() The default implementation does nothing. */ virtual void stop_ponder(); /** Interrupt the current command. This function implements interrupt functionality as used by @ref libboardgame_doc_gogui. It will be called from a different thread that the command thread when the special command line # interrupt is received. The default implementation does nothing. */ virtual void interrupt(); protected: /** Hook function to be executed before each command. The default implementation does nothing. */ virtual void on_handle_cmd_begin(); /** Register a member function of the current instance as a command handler. If a command was already registered with the same name, it will be replaced by the new command. */ template void add(const string& name, void (T::*f)(const Arguments&, Response&)); template void add(const string& name, void (T::*f)(const Arguments&)); template void add(const string& name, void (T::*f)(Response&)); template void add(const string& name, void (T::*f)()); private: /** Mapping of command name to command handler. They key is a string subrange, not a string, to allow looking up the command name using Command::name_as_subrange() without creating a temporary string for the command name. The value of type CmdInfo with the name string and callback function are stored in an object allocated on the heap to ensure that the range stays valid, if the value object is copied. */ typedef map Handlers; typedef Handlers::const_iterator HandlerIterator; /** Flag to quit main loop. */ bool m_quit; Handlers m_handlers; /** Not to be implemented. */ Engine(const Engine& engine); /** Not to be implemented. */ Engine& operator=(const Engine& engine) const; bool handle_cmd(CmdLine& line, ostream& out, Response& response, string& buffer); static void no_args_wrapper(HandlerNoArgs h, const Arguments& args, Response& response); static void no_response_wrapper(HandlerNoResponse h, const Arguments& args, Response&); static void no_args_no_response_wrapper(HandlerNoArgsNoResponse h, const Arguments& args, Response&); }; template void Engine::add(const string& name, void (T::*f)(const Arguments&, Response&)) { add(name, f, dynamic_cast(this)); } template void Engine::add(const string& name, void (T::*f)(Response&)) { add(name, f, dynamic_cast(this)); } template void Engine::add(const string& name, void (T::*f)(const Arguments&)) { add(name, f, dynamic_cast(this)); } template void Engine::add(const string& name, void (T::*f)()) { add(name, f, dynamic_cast(this)); } template void Engine::add(const string& name, void (T::*f)(const Arguments&, Response&), T* t) { assert(f != nullptr); add(name, static_cast(bind(f, t, placeholders::_1, placeholders::_2))); } template void Engine::add(const string& name, void (T::*f)(Response&), T* t) { assert(f != nullptr); add(name, static_cast(bind(f, t, placeholders::_1))); } template void Engine::add(const string& name, void (T::*f)(const Arguments&), T* t) { assert(f != nullptr); add(name, static_cast(bind(f, t, placeholders::_1))); } template void Engine::add(const string& name, void (T::*f)(), T* t) { assert(f != nullptr); add(name, static_cast(bind(f, t))); } //----------------------------------------------------------------------------- } // namespace libboardgame_gtp #endif // LIBBOARDGAME_GTP_ENGINE_H pentobi-7.2/src/libboardgame_gtp/Failure.h000066400000000000000000000020171227240712600206360ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_gtp/Failure.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_GTP_FAILURE_H #define LIBBOARDGAME_GTP_FAILURE_H #include namespace libboardgame_gtp { using namespace std; //----------------------------------------------------------------------------- /** GTP failure. Command handlers generate a GTP error response by throwing an instance of Failure. */ class Failure { public: Failure(const string& response = ""); ~Failure() throw(); string get_response() const; private: string m_response; }; inline Failure::Failure(const string& response) : m_response(response) { } inline Failure::~Failure() throw() { } inline string Failure::get_response() const { return m_response; } //----------------------------------------------------------------------------- } // namespace libboardgame_gtp #endif // LIBBOARDGAME_GTP_ENGINE_H pentobi-7.2/src/libboardgame_gtp/Response.cpp000066400000000000000000000017731227240712600214100ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_gtp/Response.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Response.h" #include namespace libboardgame_gtp { using namespace std; //----------------------------------------------------------------------------- ostringstream Response::s_dummy; Response::~Response() throw() { } void Response::write(ostream& out, string& buffer) const { buffer = m_stream.str(); bool was_newline = false; for (auto i = buffer.begin(); i != buffer.end(); ++i) { bool is_newline =(*i == '\n'); if (is_newline && was_newline) out << ' '; out << *i; was_newline = is_newline; } if (! was_newline) out << '\n'; out << '\n'; } //----------------------------------------------------------------------------- } // namespace libboardgame_gtp pentobi-7.2/src/libboardgame_gtp/Response.h000066400000000000000000000040351227240712600210470ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_gtp/Response.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_GTP_RESPONSE_H #define LIBBOARDGAME_GTP_RESPONSE_H #include #include #include namespace libboardgame_gtp { using namespace std; //----------------------------------------------------------------------------- class Response { public: Response(); ~Response() throw(); /** Conversion to output stream. Returns reference to response stream. */ operator ostream&(); /** Get response. @return A copy of the internal response string stream */ string to_string() const; /** Set response. */ void set(const string& response); void clear(); /** Write response to output stream. Also sanitizes reponses containing empty lines ("\n\n" cannot occur in a reponse, because it means end of response; it will be replaced by "\n \n") and adds "\n\n" add the end of the reponse. */ void write(ostream& out, string& buffer) const; private: /** Dummy stream for copying default formatting settings. */ static ostringstream s_dummy; /** Response stream */ ostringstream m_stream; }; inline Response::Response() { } inline Response::operator ostream&() { return m_stream; } inline void Response::clear() { m_stream.str(""); m_stream.copyfmt(s_dummy); } inline string Response::to_string() const { return m_stream.str(); } inline void Response::set(const string& response) { m_stream.str(response); } //----------------------------------------------------------------------------- /** @relates libboardgame_gtp::Response */ template inline Response& operator<<(Response& r, const TYPE& t) { static_cast(r) << t; return r; } //----------------------------------------------------------------------------- } // namespace libboardgame_gtp #endif // LIBBOARDGAME_GTP_RESPONSE_H pentobi-7.2/src/libboardgame_mcts/000077500000000000000000000000001227240712600172525ustar00rootroot00000000000000pentobi-7.2/src/libboardgame_mcts/BiasTerm.h000066400000000000000000000062611227240712600211360ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_mcts/BiasTerm.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_MCTS_BIAS_TERM_H #define LIBBOARDGAME_MCTS_BIAS_TERM_H #include #include "libboardgame_util/Assert.h" #include "libboardgame_util/FastLog.h" namespace libboardgame_mcts { using namespace std; using libboardgame_util::FastLog; //----------------------------------------------------------------------------- /** Computes the UCT bias term. Uses a lookup table for small counts. */ template class BiasTerm { public: typedef F Float; BiasTerm(Float bias_term_constant); void set_bias_term_constant(Float value); Float get_bias_term_constant() const; void start_iteration(Float parent_count); Float get(Float child_count) const; private: static const unsigned nu_precomp = 50; /** The part of the bias term that does not depend on the child count. */ Float m_parent_part; Float m_bias_term_constant; Float m_precomp_parent_part[nu_precomp]; Float m_precomp_child_part[nu_precomp]; FastLog m_fast_log; Float compute_parent_part(Float parent_count) const; Float compute_child_part(Float child_count) const; }; template BiasTerm::BiasTerm(Float bias_term_constant) : m_fast_log(10) { set_bias_term_constant(bias_term_constant); } template inline auto BiasTerm::get(Float child_count) const -> Float { LIBBOARDGAME_ASSERT(child_count >= 0); if (child_count <= 1) return m_parent_part; Float child_part; if (child_count < nu_precomp) child_part = m_precomp_child_part[static_cast(child_count)]; else child_part = compute_child_part(child_count); return m_parent_part * child_part; } template inline auto BiasTerm::compute_child_part(Float child_count) const -> Float { return sqrt(1 / max(child_count, Float(1))); } template inline auto BiasTerm::compute_parent_part(Float parent_count) const -> Float { if (m_bias_term_constant == 0 || parent_count == 0) return 0; return m_bias_term_constant * sqrt(m_fast_log.get_log(float(parent_count))); } template inline auto BiasTerm::get_bias_term_constant() const -> Float { return m_bias_term_constant; } template void BiasTerm::set_bias_term_constant(Float value) { m_bias_term_constant = value; for (unsigned i = 0; i < nu_precomp; ++i) { m_precomp_parent_part[i] = compute_parent_part(static_cast(i)); m_precomp_child_part[i] = compute_child_part(static_cast(i)); } } template inline void BiasTerm::start_iteration(Float parent_count) { LIBBOARDGAME_ASSERT(parent_count >= 0); if (parent_count < nu_precomp) m_parent_part = m_precomp_parent_part[static_cast(parent_count)]; else m_parent_part = compute_parent_part(parent_count); } //----------------------------------------------------------------------------- } // namespace libboardgame_mcts #endif // LIBBOARDGAME_MCTS_BIAS_TERM_H pentobi-7.2/src/libboardgame_mcts/ChildIterator.h000066400000000000000000000036011227240712600221600ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_mcts/ChildIterator.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_MCTS_CHILD_ITERATOR_H #define LIBBOARDGAME_MCTS_CHILD_ITERATOR_H #include "Tree.h" namespace libboardgame_mcts { using namespace std; //----------------------------------------------------------------------------- /** Iterate over all children of a node in the MCTS tree. @tparam M The type of the move stored in the nodes. */ template class ChildIterator { public: typedef N Node; typedef libboardgame_mcts::Tree Tree; ChildIterator(const Tree& tree, const Node& node); operator bool() const; ChildIterator& operator++(); const Node& operator*(); const Node* operator->(); private: const Node* m_current; const Node* m_end; }; template ChildIterator::ChildIterator(const Tree& tree, const Node& node) { auto nu_children = node.get_nu_children(); m_current = (nu_children != 0 ? &tree.get_node(node.get_first_child()) : nullptr); m_end = m_current + nu_children; } template inline ChildIterator::operator bool() const { return m_current != m_end; } template inline ChildIterator& ChildIterator::operator++() { LIBBOARDGAME_ASSERT(operator bool()); ++m_current; return *this; } template inline auto ChildIterator::operator*() -> const Node& { LIBBOARDGAME_ASSERT(operator bool()); return *m_current; } template inline auto ChildIterator::operator->() -> const Node* { LIBBOARDGAME_ASSERT(operator bool()); return m_current; } //----------------------------------------------------------------------------- } // namespace libboardgame_mcts #endif // LIBBOARDGAME_MCTS_CHILD_ITERATOR_H pentobi-7.2/src/libboardgame_mcts/LastGoodReply.h000066400000000000000000000151211227240712600221530ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_mcts/LastGoodReply.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_MCTS_LAST_GOOD_REPLY_H #define LIBBOARDGAME_MCTS_LAST_GOOD_REPLY_H #include #include #include #include "PlayerMove.h" #include "libboardgame_util/Assert.h" #include "libboardgame_util/RandomGenerator.h" namespace libboardgame_mcts { using namespace std; using libboardgame_util::RandomGenerator; //----------------------------------------------------------------------------- /** Storage for Last-Good-Reply heuristic. Uses LGRF-2 (Baier, Drake: The Power of Forgetting: Improving the Last-Good-Reply Policy in Monte-Carlo Go. 2010. http://webdisk.lclark.edu/drake/publications/baier-drake-ieee-2010.pdf) To save space, only the player of the reply move is considered when storing or receiving a reply, the players of the last and second last moves are ignored. In games without a fixed order of players (i.e. when move sequences with the same moves but not played by the same players occur), this can cause undetected collisions. If these collisions are not sufficiently rare, the last-good-reply heuristic should be disabled in the search. Undetected collisions can also occur because the replies are stored in a hash table without collision check. But since the replies have to be checked for legality in the current position anyway and the collisions are probably rare, no major negative effect is expected from these collisions. @see Search::set_last_good_reply() */ template class LastGoodReply { public: typedef M Move; static const unsigned max_players = P; LastGoodReply(); void init(PlayerInt nu_players); void store(PlayerInt player, Move last_mv, Move second_last_mv, Move reply); void store(PlayerInt player, Move last_mv, Move reply); void forget(PlayerInt player, Move last_mv, Move second_last_mv, Move reply); void forget(PlayerInt player, Move last_mv, Move reply); void get(PlayerInt player, Move last_mv, Move second_last_mv, Move& last_good_reply_1, Move& last_good_reply_2) const; private: static const size_t hash_table_size = (1 << 21); size_t m_hash[Move::range]; atomic m_reply_1[max_players][Move::range]; atomic m_reply_2[max_players][hash_table_size]; size_t get_index(Move last_mv, Move second_last_mv) const; }; template LastGoodReply::LastGoodReply() { RandomGenerator generator; for (unsigned i = 0; i < Move::range; ++i) m_hash[i] = generator.generate(); } template inline size_t LastGoodReply::get_index(Move last_mv, Move second_last_mv) const { size_t hash = (m_hash[last_mv.to_int()] ^ m_hash[second_last_mv.to_int()]); return hash % hash_table_size; } template inline void LastGoodReply::get(PlayerInt player, Move last_mv, Move second_last_mv, Move& last_good_reply_1, Move& last_good_reply_2) const { LIBBOARDGAME_ASSERT(! last_mv.is_null()); if (! second_last_mv.is_null()) { auto index = get_index(last_mv, second_last_mv); last_good_reply_2 = Move(m_reply_2[player][index].load(memory_order_relaxed)); } else last_good_reply_2 = Move::null(); last_good_reply_1 = Move(m_reply_1[player][last_mv.to_int()].load(memory_order_relaxed)); } template void LastGoodReply::init(PlayerInt nu_players) { for (PlayerInt i = 0; i < nu_players; ++i) { auto null_int = Move::null().to_int(); // Don't use memory_order_relaxed here. init() could be called after // the game variant changed (e.g. board size) and while this class // does not guarantee that a move is legal in the current position, // it should at least only return moves that belong to the same game // variant. fill(m_reply_1[i], m_reply_1[i] + Move::range, null_int); fill(m_reply_2[i], m_reply_2[i] + hash_table_size, null_int); } } template inline void LastGoodReply::forget(PlayerInt player, Move last_mv, Move second_last_mv, Move reply) { LIBBOARDGAME_ASSERT(! last_mv.is_null()); LIBBOARDGAME_ASSERT(! second_last_mv.is_null()); auto reply_int = reply.to_int(); auto null_int = Move::null().to_int(); { auto index = get_index(last_mv, second_last_mv); auto& stored_reply = m_reply_2[player][index]; if (stored_reply.load(memory_order_relaxed) == reply_int) stored_reply.store(null_int, memory_order_relaxed); } auto& stored_reply = m_reply_1[player][last_mv.to_int()]; if (stored_reply.load(memory_order_relaxed) == reply_int) stored_reply.store(null_int, memory_order_relaxed); } template inline void LastGoodReply::forget(PlayerInt player, Move last_mv, Move reply) { LIBBOARDGAME_ASSERT(! last_mv.is_null()); auto reply_int = reply.to_int(); auto null_int = Move::null().to_int(); auto& stored_reply = m_reply_1[player][last_mv.to_int()]; if (stored_reply.load(memory_order_relaxed) == reply_int) stored_reply.store(null_int, memory_order_relaxed); } template inline void LastGoodReply::store(PlayerInt player, Move last_mv, Move second_last_mv, Move reply) { LIBBOARDGAME_ASSERT(! last_mv.is_null()); LIBBOARDGAME_ASSERT(! second_last_mv.is_null()); auto reply_int = reply.to_int(); { auto index = get_index(last_mv, second_last_mv); m_reply_2[player][index].store(reply_int, memory_order_relaxed); } m_reply_1[player][last_mv.to_int()].store(reply_int, memory_order_relaxed); } template inline void LastGoodReply::store(PlayerInt player, Move last_mv, Move reply) { LIBBOARDGAME_ASSERT(! last_mv.is_null()); auto reply_int = reply.to_int(); m_reply_1[player][last_mv.to_int()].store(reply_int, memory_order_relaxed); } //----------------------------------------------------------------------------- } // namespace libboardgame_mcts #endif // LIBBOARDGAME_MCTS_LAST_GOOD_REPLY_H pentobi-7.2/src/libboardgame_mcts/Node.h000066400000000000000000000173701227240712600203200ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_mcts/Node.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_MCTS_NODE_H #define LIBBOARDGAME_MCTS_NODE_H #include #include #include #include "libboardgame_util/Assert.h" namespace libboardgame_mcts { using namespace std; //----------------------------------------------------------------------------- typedef uint_least32_t NodeIndex; //----------------------------------------------------------------------------- /** Node in a MCTS tree. For details about how the nodes are used in lock-free multi-threaded mode, see @ref libboardgame_doc_enz_2009. */ template class Node { public: typedef M Move; typedef F Float; Node(); /** Initialize the node. This function may not be called on a node that is already part of the tree in multi-threaded mode. The node may be initialized with values and counts greater zero (prior knowledge) but even if it is initialized with count zero, it must be initialized with a usable value (e.g. first play urgency for inner nodes or tie value for the root node). */ void init(const Move& mv, Float value, Float count); const Move& get_move() const; /** Number of simulations that went through this node. */ Float get_visit_count() const; /** Number of values that were added. This count is usually larger than the visit count because in addition to the terminal values of the simulations, prior knowledge values and weighted RAVE values could have been added added. */ Float get_value_count() const; /** Value of the node. For the root node, this is the value of the position from the point of view of the player at the root node; for all other nodes, this is the value of the move leading to the position at the node from the point of view of the player at the parent node. */ Float get_value() const; bool has_children() const; unsigned get_nu_children() const; /** Set a new value. This operation is needed when reusing a subtree from a previous search because the value of root nodes and inner nodes have a different meaning (position value vs. move values) so the root value cannot be reused but all other nodes in the tree can be reused without changes. It is not thread-safe and may not be called during the search. */ void init_value(Float value, Float count); /** Copy the value count from another node without changing the child information. This function is not thread-safe and may not be called during the search. */ void copy_data_from(const Node& node); void link_children(NodeIndex first_child, int nu_children); void unlink_children(); void add_value(Float v); void add_value(Float v, Float weight); void inc_visit_count(); /** Get node index of first child. @pre has_children() */ NodeIndex get_first_child() const; private: atomic m_value; atomic m_value_count; atomic m_visit_count; atomic m_nu_children; Move m_move; NodeIndex m_first_child; /** Not to be implemented */ Node(const Node&); /** Not to be implemented */ Node& operator=(const Node&); }; template inline Node::Node() { } template void Node::add_value(Float v) { // Intentionally uses no synchronization and does not care about // lost updates in multi-threaded mode Float count = m_value_count.load(memory_order_relaxed); Float value = m_value.load(memory_order_relaxed); ++count; value += (v - value) / count; m_value.store(value, memory_order_relaxed); m_value_count.store(count, memory_order_relaxed); } template void Node::add_value(Float v, Float weight) { // Intentionally uses no synchronization and does not care about // lost updates in multi-threaded mode Float count = m_value_count.load(memory_order_relaxed); Float value = m_value.load(memory_order_relaxed); count += weight; value += weight * (v - value) / count; m_value.store(value, memory_order_relaxed); m_value_count.store(count, memory_order_relaxed); } template void Node::copy_data_from(const Node& node) { // Reminder to update this function when the class gets additional members struct Dummy { atomic m_value; atomic m_value_count; atomic m_visit_count; atomic m_nu_children; Move m_move; NodeIndex m_first_child; }; static_assert(sizeof(Node) == sizeof(Dummy), ""); m_move = node.m_move; m_value_count.store(node.m_value_count); m_value.store(node.m_value); m_visit_count.store(node.m_visit_count); } template inline auto Node::get_value_count() const -> Float { return m_value_count.load(memory_order_relaxed); } template inline NodeIndex Node::get_first_child() const { LIBBOARDGAME_ASSERT(has_children()); return m_first_child; } template inline auto Node::get_move() const -> const Move& { return m_move; } template inline unsigned Node::get_nu_children() const { return m_nu_children.load(memory_order_acquire); } template inline auto Node::get_value() const -> Float { return m_value.load(memory_order_relaxed); } template inline auto Node::get_visit_count() const -> Float { return m_visit_count.load(memory_order_relaxed); } template inline bool Node::has_children() const { return get_nu_children() > 0; } template inline void Node::inc_visit_count() { // We don't care about the unlikely case that updates are lost because // incrementing is not atomic Float count = m_visit_count.load(memory_order_relaxed); ++count; m_visit_count.store(count, memory_order_relaxed); } template void Node::init(const Move& mv, Float value, Float count) { // The node is not yet visible to other threads because init() is called // before the children are linked to its parent with link_children() // (which does a memory_order_release on m_nu_children of the parent). // Therefore, the most efficient way here is to initialize all values with // memory_order_relaxed. m_move = mv; m_value_count.store(count, memory_order_relaxed); m_value.store(value, memory_order_relaxed); m_visit_count.store(0, memory_order_relaxed); m_nu_children.store(0, memory_order_relaxed); } template void Node::init_value(Float value, Float count) { m_value_count = count; m_value = value; } template inline void Node::link_children(NodeIndex first_child, int nu_children) { LIBBOARDGAME_ASSERT(nu_children <= numeric_limits::max()); // first_child cannot be 0 because 0 is always used for the root node LIBBOARDGAME_ASSERT(first_child != 0); m_first_child = first_child; m_nu_children.store(static_cast(nu_children), memory_order_release); } template inline void Node::unlink_children() { m_nu_children.store(0, memory_order_release); } //----------------------------------------------------------------------------- } // namespace libboardgame_mcts #endif // LIBBOARDGAME_MCTS_NODE_H pentobi-7.2/src/libboardgame_mcts/PlayerMove.h000066400000000000000000000017651227240712600215170ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_mcts/PlayerMove.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_MCTS_PLAYER_MOVE_H #define LIBBOARDGAME_MCTS_PLAYER_MOVE_H namespace libboardgame_mcts { //----------------------------------------------------------------------------- typedef uint_fast8_t PlayerInt; //----------------------------------------------------------------------------- template struct PlayerMove { PlayerInt player; MOVE move; static PlayerMove null() { PlayerMove m; m.move = MOVE::null(); return m; } PlayerMove() { } PlayerMove(PlayerInt player, MOVE move) { this->player = player; this->move = move; } }; //----------------------------------------------------------------------------- } // namespace libboardgame_mcts #endif // LIBBOARDGAME_MCTS_PLAYER_MOVE_H pentobi-7.2/src/libboardgame_mcts/Search.h000066400000000000000000001627141227240712600206430ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_mcts/Search.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_MCTS_SEARCH_H #define LIBBOARDGAME_MCTS_SEARCH_H #include #include #include #include "BiasTerm.h" #include "LastGoodReply.h" #include "PlayerMove.h" #include "Tree.h" #include "TreeUtil.h" #include "libboardgame_util/Abort.h" #include "libboardgame_util/Barrier.h" #include "libboardgame_util/BitMarker.h" #include "libboardgame_util/FmtSaver.h" #include "libboardgame_util/IntervalChecker.h" #include "libboardgame_util/Log.h" #include "libboardgame_util/Statistics.h" #include "libboardgame_util/StringUtil.h" #include "libboardgame_util/TimeIntervalChecker.h" #include "libboardgame_util/Timer.h" #include "libboardgame_util/Unused.h" #ifdef USE_BOOST_THREAD #include #include #include #else #include #include #include #endif namespace libboardgame_mcts { using namespace std; using libboardgame_mcts::tree_util::find_node; using libboardgame_util::get_abort; using libboardgame_util::log; using libboardgame_util::time_to_string; using libboardgame_util::to_string; using libboardgame_util::Barrier; using libboardgame_util::BitMarker; using libboardgame_util::FmtSaver; using libboardgame_util::IntervalChecker; using libboardgame_util::StatisticsBase; using libboardgame_util::StatisticsDirtyLockFree; using libboardgame_util::StatisticsExt; using libboardgame_util::Timer; using libboardgame_util::TimeIntervalChecker; using libboardgame_util::TimeSource; #ifdef USE_BOOST_THREAD using boost::condition_variable; using boost::lock_guard; using boost::mutex; using boost::thread; using boost::unique_lock; #endif //----------------------------------------------------------------------------- /** Default optional compile-time parameters for Search. See description of class Search for more information. */ struct SearchParamConstDefault { /** The floating type used for mean values and counts. The default type is @c float for a reduced node size and performance gains (especially on 32-bit systems). However, using @c float sets a practical limit on the number of simulations before the count and mean values go into saturation. This maximum is given by 2^d-1 with d being the digits in the mantissa (=23 for IEEE 754 float's). The search will terminate when this number is reached. For longer searches, the code should be compiled with floating type @c double. */ typedef float Float; /** The maximum number of players. */ static const PlayerInt max_players = 2; /** Use RAVE. */ static const bool rave = false; /** Do not update RAVE values if the same move was played first by the other player. This improves RAVE performance in Go, where moves at the same point indicate that something was captured there, so the position has changed so significantly that the RAVE value of this move should not be updated to earlier positions. Setting it to true causes a slight slow-down in the update of the RAVE values. */ static const bool rave_check_same = false; /** Enable distance weighting of RAVE updates. The weight decreases linearly from the start to the end of a simulation. The distance weight is applied in addition to the normal RAVE weight. */ static const bool rave_dist_weighting = false; /** Enable Last-Good-Reply heuristic. @see LastGoodReply */ static const bool use_last_good_reply = false; }; //----------------------------------------------------------------------------- /** Game-independent Monte-Carlo tree search. Game-dependent functionality is added by implementing some pure virtual functions and by template parameters. RAVE (see @ref libboardgame_doc_rave) is implemented differently from the algorithm described in the original paper: RAVE values are not stored separately in the nodes but added to the normal values with a certain (constant) weight and up to a maximum visit count of the parent node. This saves memory in the tree and speeds up move selection in the in-tree phase. It is weaker than the original RAVE at a low number of simulations but seems to be equally good or even better at a high number of simulations. @tparam S The game-dependent state of a simulation. The state provides functions for move generation, evaluation of terminal positions, etc. The state should be thread-safe to support multiple states if multi-threading is used. @tparam M The move type. The type must be convertible to an integer by providing M::to_int() and M::range. @tparam R Optional compile-time parameters, see SearchParamConstDefault */ template class Search { public: typedef S State; typedef M Move; typedef R SearchParamConst; typedef typename SearchParamConst::Float Float; typedef libboardgame_mcts::Node Node; typedef libboardgame_mcts::ChildIterator ChildIterator; typedef libboardgame_mcts::Tree Tree; typedef libboardgame_mcts::PlayerMove PlayerMove; static const PlayerInt max_players = SearchParamConst::max_players; typedef array, max_players> RootStat; /** Constructor. @param nu_threads @param memory The memory to be used for (all) the search trees. If zero, a default value will be used. */ Search(unsigned nu_threads, size_t memory); virtual ~Search() throw(); /** @name Pure virtual functions */ // @{ /** Create a new game-specific state to be used in a thread of the search. */ virtual unique_ptr create_state() = 0; /** Get string representation of a move. This function is not a member function of the move type M because the move type may be a lightweight class that needs a context to be converted to a string. */ virtual string get_move_string(Move mv) const = 0; /** Get the current number of players. */ virtual PlayerInt get_nu_players() const = 0; /** Get player to play at root node of the search. */ virtual PlayerInt get_player() const = 0; /** An evaluation value representing a 50% winning probability. */ virtual Float get_tie_value() const = 0; // @} // @name /** @name Virtual functions */ // @{ /** Check if the position at the root is a follow-up position of the last search. In this function, the subclass can store the game state at the root of the search, compare it to the the one of the last search, check if the current state is a follow-up position and return the move sequence leading from the last position to the current one, so that the search can check if a subtree of the last search can be reused. This function will be called exactly once at the beginning of each search. The default implementation returns false. The information is also used for deciding whether to clear other caches from the last search (e.g. Last-Good-Reply heuristic). */ virtual bool check_followup(vector& sequence); virtual void write_info(ostream& out) const; virtual void write_info_ext(ostream& out) const; /** Return the expected simulations per second. If the simulations per second vary a lot, it should return a value closer to the lower values. This value is used, for example, to determine an interval for checking expensive abort conditions in deterministic mode (in regular mode, the simulations per second will be measured and the interval will be adjusted automatically). That means that in deterministic mode, a pessimistic low value will cause more calls to the expensive function but an optimistic high value will delay aborting the search. The default implementation returns 100. @see set_deterministic() */ virtual double expected_sim_per_sec() const; // @} // @name /** @name Parameters */ // @{ /** Interval in which the bias term is used at high parent counts. Takes advantage of the fact that the bias term changes slowly with high parent counts and often the child with the highest value would be selected, so the bias terms of the children do not have to be used always, which speeds up child selection. The default value is 1, which means that the bias term is always used. @see set_skip_bias_term_min_count. */ void set_bias_term_interval(unsigned n); /** See set_bias_term_interval(). */ unsigned get_bias_term_interval() const; /** See set_bias_term_interval(). */ void set_skip_bias_term_min_count(Float n); /** See set_bias_term_interval(). */ Float get_skip_bias_term_min_count() const; /** Minimum count of a node to be expanded. */ void set_expand_threshold(Float n); Float get_expand_threshold() const; /** Increase of the expand threshold per in-tree move played. */ void set_expand_threshold_incr(Float n); Float get_expand_threshold_incr() const; /** Constant used in UCT bias term. */ void set_bias_term_constant(Float c); Float get_bias_term_constant() const; /** Reuse the subtree from the previous search if the current position is a follow-up position of the previous one. */ void set_reuse_subtree(bool enable); bool get_reuse_subtree() const; /** Reuse the tree from the previous search if the current position is the same position as the previous one. */ void set_reuse_tree(bool enable); bool get_reuse_tree() const; void set_prune_full_tree(bool enable); bool get_prune_full_tree() const; /** Maximum parent visit count for applying RAVE. */ void set_rave_max_parent_count(Float value); Float get_rave_max_parent_count() const; /** Maximum child value count for applying RAVE. */ void set_rave_max_child_count(Float value); Float get_rave_max_child_count() const; /** Weight used for adding RAVE values to the node value. */ void set_rave_weight(Float value); Float get_rave_weight() const; /** The RAVE distance weight at the end of a simulation. If RAVE distance weighting is used, the RAVE distance weight decreases linearly from 1 at the current position to this value at the end of a simulation. */ void set_rave_dist_final(Float value); Float get_rave_dist_final() const; /** Value to start the tree pruning with. This value should be above typical count initializations if prior knowledge initialization is used. */ void set_prune_count_start(Float n); Float get_prune_count_start() const; /** Total size of the trees in bytes. */ void set_tree_memory(size_t memory); size_t get_tree_memory() const; /** Set deterministic mode. Note that using a fixed number of simulations instead of a time limit is not enough to make the search fully deterministic because the interval in which expensive abort conditions are checked (e.g. if the best move cannot change anymore) is adjusted dynamically depending on the number of simulations per second. In deterministic mode, a fixed interval is used. */ void set_deterministic(); // @} // @name /** Run a search. @param[out] mv @param max_count Number of simulations to run. The search might return earlier if the best move cannot change anymore or if the count of the root node was initialized from an init tree @param min_simulations @param max_time Maximum search time. Only used if max_count is zero @param time_source Time source for time measurement @param always_search Always call the search, even if extracting the subtree to reuse was aborted due to max_time or util::get_abort(). If true, this will call a search with the partially extracted subtree, and the search will return immediately (because it also checks max_time and get_abort()). This flag should be true for regular searches because even a partially extracted subtree can be used for move generation, and false for pondering searches because here we don't need a search result but want to keep the full tree for reuse in a future search. @return @c false if no move could be generated because the position is a terminal position. */ bool search(Move& mv, Float max_count, Float min_simulations, double max_time, TimeSource& time_source, bool always_search = true); const Tree& get_tree() const; void dump(ostream& out) const; /** Number of simulations in the current search in all threads. */ size_t get_nu_simulations() const; /** Select the move to play. Uses select_child_final() on the root node. */ bool select_move(Move& mv, const vector* exclude_moves = nullptr) const; /** Select the best child of a node after the search. Selects child with highest visit count; the value is used as a tie-breaker for equal counts (important at very low number of simulations, e.g. all children have count 1 or 0). */ const Node* select_child_final(const Node& node, const vector* exclude_moves = nullptr) const; State& get_state(unsigned thread_id); const State& get_state(unsigned thread_id) const; /** Set a callback function that informs the caller about the estimated time left. The callback function will be called about every 0.1s. The arguments of the callback function are: elapsed time, estimated remaining time. */ void set_callback(function callback); /** Get mean evaluation for all players at root node. */ const RootStat& get_root_val() const; /** Create the threads used in the search. This cannot be done in the constructor because it uses the virtual function create_state(). This function will automatically be called before a search if the threads have not been constructed yet, but it is advisable to explicitely call it in the constructor of the subclass to save some time at the first move generation where the game clock might already be running. */ void create_threads(); protected: struct Simulation { vector nodes; array eval; }; virtual void on_start_search(); virtual void on_search_iteration(size_t n, const State& state, const Simulation& simulation); /** Time source for current search. Only valid during a search. */ TimeSource& get_time_source(); private: class AssertionHandler : public libboardgame_util::AssertionHandler { public: AssertionHandler(const Search& search); ~AssertionHandler() throw(); void run() override; private: const Search& m_search; }; /** Thread-specific search state. */ struct ThreadState { unsigned thread_id; unique_ptr state; vector skip_bias_term_counter; /** Was the search in this thread terminated because the search tree was full? */ bool is_out_of_mem; /** Number of simulations in the current search in this thread. */ size_t nu_simulations; Simulation simulation; StatisticsExt<> stat_len; StatisticsExt<> stat_in_tree_len; /** Local variable for update_rave_values(). Stores if a move was played for each player. Reused for efficiency. */ array, max_players> was_played; /** Local variable for update_rave_values(). Stores the first time a move was played for each player. Elements are only defined if was_played is true. Reused for efficiency. */ array, max_players> first_play; }; /** Thread in the parallel search. The thread waits for a call to start_search(), then runs the given search loop function (see Search::search_loop()) with the thread-specific search state. After start_search(), wait_search_finished() needs to called before calling start_search() again or destructing this object. */ class Thread { public: typedef function SearchFunc; ThreadState thread_state; Thread(SearchFunc& search_func); ~Thread(); void run(); void start_search(); void wait_search_finished(); private: SearchFunc m_search_func; bool m_quit; bool m_start_search_flag; bool m_search_finished_flag; Barrier m_thread_ready; mutex m_start_search_mutex; mutex m_search_finished_mutex; condition_variable m_start_search_cond; condition_variable m_search_finished_cond; unique_lock m_search_finished_lock; thread m_thread; void thread_main(); }; unsigned m_nu_threads; /** See set_bias_term_interval(). */ unsigned m_bias_term_interval; /** See set_bias_term_min_count(). */ Float m_skip_bias_term_min_count; Float m_expand_threshold; Float m_expand_threshold_incr; bool m_deterministic; bool m_reuse_subtree; bool m_reuse_tree; bool m_prune_full_tree; /** Player to play at the root node of the search. */ PlayerInt m_player; /** Cached return value of get_nu_players() that stays constant during a search. */ PlayerInt m_nu_players; /** Time of last search. */ double m_last_time; Float m_prune_count_start; Float m_rave_max_parent_count; Float m_rave_max_child_count; Float m_rave_weight; Float m_rave_dist_final; /** Minimum simulations to perform in the current search. This does not include the count of simulations reused from a subtree of a previous search. */ Float m_min_simulations; /** Maximum simulations of current search. This include the count of simulations reused from a subtree of a previous search. */ Float m_max_count; /** Maximum count that can be exactly expressed in floating point type. */ Float m_max_float_count; size_t m_tree_memory; size_t m_max_nodes; /** Maximum time of current search. */ double m_max_time; TimeSource* m_time_source; BiasTerm m_bias_term; Timer m_timer; vector> m_threads; Tree m_tmp_tree; AssertionHandler m_assertion_handler; /** @name Members that are used concurrently by all threads during the lock-free multi-threaded search */ // @{ Tree m_tree; /** See get_root_val(). */ RootStat m_root_val; /** Current position value estimate for prior knowledge initialization. Derived form root values of last search and updated with root values of current search. */ RootStat m_init_val; LastGoodReply m_last_good_reply; /** See get_nu_simulations(). */ atomic m_nu_simulations; // @} // @name function m_callback; vector m_followup_sequence; static size_t get_max_nodes(size_t memory); bool check_abort(const ThreadState& thread_state) const; bool check_abort_expensive(ThreadState& thread_state) const; void check_create_threads(); bool check_move_cannot_change(Float count, Float remaining) const; bool check_skip_bias_term(ThreadState& thread_state, Float node_count, unsigned depth) const; bool expand_node(unsigned thread_id, const Node& node, const Node*& best_child, Float init_val); void log_thread(const ThreadState& thread_state, const string& s) const; void playout(ThreadState& thread_state); void play_in_tree(ThreadState& thread_state, bool& is_terminal); bool prune(TimeSource& time_source, double time, double max_time, Float prune_min_count, Float& new_prune_min_count); void restore_root_from_children(Tree& tree, const Node& root); void search_loop(ThreadState& thread_state); const Node* select_child(ThreadState& thread_state, const Node& node, unsigned depth); void update_last_good_reply(ThreadState& thread_state); void update_rave_values(ThreadState& thread_state); void update_rave_values(ThreadState& thread_state, unsigned i, PlayerInt player); void update_values(ThreadState& thread_state); }; template Search::Thread::Thread(SearchFunc& search_func) : m_search_func(search_func), m_quit(false), m_start_search_flag(false), m_search_finished_flag(false), m_thread_ready(2), m_search_finished_lock(m_search_finished_mutex) { } template Search::Thread::~Thread() { if (! m_thread.joinable()) return; m_quit = true; { lock_guard lock(m_start_search_mutex); m_start_search_flag = true; } m_start_search_cond.notify_one(); m_thread.join(); } template void Search::Thread::run() { m_thread = thread(bind(&Thread::thread_main, this)); m_thread_ready.wait(); } template void Search::Thread::start_search() { LIBBOARDGAME_ASSERT(m_thread.joinable()); { lock_guard lock(m_start_search_mutex); m_start_search_flag = true; } m_start_search_cond.notify_one(); } template void Search::Thread::thread_main() { //log() << "Start thread " << thread_state.thread_id << '\n'; unique_lock lock(m_start_search_mutex); m_thread_ready.wait(); while (true) { while (! m_start_search_flag) m_start_search_cond.wait(lock); m_start_search_flag = false; if (m_quit) break; m_search_func(thread_state); { lock_guard lock(m_search_finished_mutex); m_search_finished_flag = true; } m_search_finished_cond.notify_one(); } //log() << "Finish thread " << thread_state.thread_id << '\n'; } template void Search::Thread::wait_search_finished() { LIBBOARDGAME_ASSERT(m_thread.joinable()); while (! m_search_finished_flag) m_search_finished_cond.wait(m_search_finished_lock); m_search_finished_flag = false; } template Search::AssertionHandler::AssertionHandler(const Search& search) : m_search(search) { } template Search::AssertionHandler::~AssertionHandler() throw() { } template void Search::AssertionHandler::run() { m_search.dump(log()); } template Search::Search(unsigned nu_threads, size_t memory) : m_nu_threads(nu_threads), m_bias_term_interval(1), m_skip_bias_term_min_count(numeric_limits::max()), m_expand_threshold(0), m_expand_threshold_incr(0), m_deterministic(false), m_reuse_subtree(true), m_reuse_tree(false), m_prune_full_tree(true), m_prune_count_start(16), m_rave_max_parent_count(50000), m_rave_max_child_count(2000), m_rave_weight(0.3f), m_rave_dist_final(0), m_tree_memory(memory == 0 ? 256000000 : memory), m_max_nodes(get_max_nodes(m_tree_memory)), m_bias_term(0), m_tmp_tree(m_max_nodes, m_nu_threads), m_assertion_handler(*this), m_tree(m_max_nodes, m_nu_threads) { static_assert(numeric_limits::radix == 2, ""); m_max_float_count = (size_t(1) << numeric_limits::digits) - 1; } template Search::~Search() throw() { } template bool Search::check_abort(const ThreadState& thread_state) const { if (m_max_count > 0 && m_tree.get_root().get_visit_count() >= m_max_count) { log_thread(thread_state, "Maximum count reached"); return true; } return false; } template bool Search::check_abort_expensive(ThreadState& thread_state) const { if (get_abort()) { log_thread(thread_state, "Search aborted"); return true; } auto& root = m_tree.get_root(); if (root.get_nu_children() == 1) { log_thread(thread_state, "Root has only one child"); return true; } auto count = root.get_visit_count(); if (count >= m_max_float_count) { log_thread(thread_state, "Maximum count supported by floating type reached"); return true; } auto time = m_timer(); if (! m_deterministic && time < 0.1) // Simulations per second might be inaccurate for very small times return false; double simulations_per_sec; if (time == 0) simulations_per_sec = expected_sim_per_sec(); else { size_t nu_simulations = m_nu_simulations.load(memory_order_relaxed); simulations_per_sec = double(nu_simulations) / time; } double remaining_time; Float remaining_simulations; if (m_max_count == 0) { // Search uses time limit if (time > m_max_time) { log_thread(thread_state, "Maximum time reached"); return true; } remaining_time = m_max_time - time; remaining_simulations = Float(remaining_time * simulations_per_sec); } else { // Search uses count limit remaining_simulations = m_max_count - count; remaining_time = remaining_simulations / simulations_per_sec; } if (thread_state.thread_id == 0 && m_callback) m_callback(time, remaining_time); if (count + remaining_simulations > m_max_float_count) remaining_simulations = m_max_float_count - count; if (check_move_cannot_change(count, remaining_simulations)) { log_thread(thread_state, "Move cannot change anymore"); return true; } return false; } template void Search::check_create_threads() { if (m_nu_threads != m_threads.size()) create_threads(); } template bool Search::check_followup(vector& sequence) { LIBBOARDGAME_UNUSED(sequence); return false; } template bool Search::check_move_cannot_change(Float count, Float remaining) const { if (remaining > count) return false; Float max_count = 0; Float second_max_count = 0; for (ChildIterator i(m_tree, m_tree.get_root()); i; ++i) { Float count = i->get_visit_count(); if (count > max_count) { second_max_count = max_count; max_count = count; } } return (max_count > second_max_count + remaining); } template inline bool Search::check_skip_bias_term(ThreadState& thread_state, Float node_count, unsigned depth) const { if (node_count <= m_skip_bias_term_min_count) return false; // A different counter is used for each depth for checking whether to skip // the bias term. A global counter could systemtically skip nodes at a // certain depth. Theoretically, there could also be a systematic effect // within nodes at the same depth but that is not an issue in practice. if (thread_state.skip_bias_term_counter.size() < depth + 1) thread_state.skip_bias_term_counter.resize(depth + 1, 0); auto& counter = thread_state.skip_bias_term_counter[depth]; if (counter > 0) { --counter; return true; } counter = m_bias_term_interval; return false; } template void Search::create_threads() { log() << "Creating " << m_nu_threads << " threads\n"; m_threads.clear(); auto search_func = static_cast( bind(&Search::search_loop, this, placeholders::_1)); for (unsigned i = 0; i < m_nu_threads; ++i) { unique_ptr t(new Thread(search_func)); auto& thread_state = t->thread_state; thread_state.thread_id = i; thread_state.state = create_state(); for (PlayerInt j = 0; j < max_players; ++j) thread_state.was_played[j].clear(); if (i > 0) t->run(); m_threads.push_back(move(t)); } } template void Search::dump(ostream& out) const { for (unsigned i = 0; i < m_nu_threads; ++i) { out << "Thread state " << i << ":\n"; get_state(i).dump(out); } } template bool Search::expand_node(unsigned thread_id, const Node& node, const Node*& best_child, Float init_val) { typename Tree::NodeExpander expander(thread_id, m_tree, node); get_state(thread_id).gen_children(expander, init_val); if (! expander.is_tree_full()) { expander.link_children(); best_child = expander.get_best_child(); return true; } return false; } template double Search::expected_sim_per_sec() const { return 100.0; } template inline auto Search::get_bias_term_constant() const -> Float { return m_bias_term.get_bias_term_constant(); } template inline unsigned Search::get_bias_term_interval() const { return m_bias_term_interval; } template inline auto Search::get_expand_threshold() const -> Float { return m_expand_threshold; } template inline auto Search::get_expand_threshold_incr() const -> Float { return m_expand_threshold_incr; } template size_t Search::get_max_nodes(size_t memory) { // Memory is used for 2 trees (m_tree and m_tmp_tree) size_t max_nodes = memory / sizeof(Node) / 2; // It doesn't make sense to set max_nodes higher than what can be accessed // with NodeIndex max_nodes = min(max_nodes, static_cast(numeric_limits::max())); log() << "Search tree size: 2 x " << max_nodes << " nodes\n"; return max_nodes; } template inline size_t Search::get_nu_simulations() const { return m_nu_simulations; } template inline auto Search::get_root_val() const -> const RootStat& { return m_root_val; } template inline auto Search::get_prune_count_start() const -> Float { return m_prune_count_start; } template inline bool Search::get_prune_full_tree() const { return m_prune_full_tree; } template inline auto Search::get_rave_dist_final() const -> Float { return m_rave_dist_final; } template inline auto Search::get_rave_max_parent_count() const -> Float { return m_rave_max_parent_count; } template inline auto Search::get_rave_max_child_count() const -> Float { return m_rave_max_child_count; } template inline auto Search::get_rave_weight() const -> Float { return m_rave_weight; } template inline bool Search::get_reuse_subtree() const { return m_reuse_subtree; } template inline bool Search::get_reuse_tree() const { return m_reuse_tree; } template inline auto Search::get_skip_bias_term_min_count() const -> Float { return m_skip_bias_term_min_count; } template inline S& Search::get_state(unsigned thread_id) { LIBBOARDGAME_ASSERT(thread_id < m_threads.size()); return *m_threads[thread_id]->thread_state.state; } template inline const S& Search::get_state(unsigned thread_id) const { LIBBOARDGAME_ASSERT(thread_id < m_threads.size()); return *m_threads[thread_id]->thread_state.state; } template inline TimeSource& Search::get_time_source() { LIBBOARDGAME_ASSERT(m_time_source != 0); return *m_time_source; } template size_t Search::get_tree_memory() const { return m_tree_memory; } template inline auto Search::get_tree() const -> const Tree& { return m_tree; } template void Search::log_thread(const ThreadState& thread_state, const string& s) const { ostringstream o; o << "[" << thread_state.thread_id << "] " << s; log(o.str()); } template void Search::on_search_iteration(size_t n, const State& state, const Simulation& simulation) { LIBBOARDGAME_UNUSED(n); LIBBOARDGAME_UNUSED(state); LIBBOARDGAME_UNUSED(simulation); // Default implementation does nothing } template void Search::on_start_search() { // Default implementation does nothing } template void Search::playout(ThreadState& thread_state) { auto& state = *thread_state.state; state.start_playout(); while (true) { Move last_good_reply_1 = Move::null(); Move last_good_reply_2 = Move::null(); if (SearchParamConst::use_last_good_reply) { unsigned nu_moves = state.get_nu_moves(); if (nu_moves > 0) { Move last_mv = state.get_move(nu_moves - 1).move; Move second_last_mv = Move::null(); if (nu_moves > 1) second_last_mv = state.get_move(nu_moves - 2).move; m_last_good_reply.get(state.get_to_play(), last_mv, second_last_mv, last_good_reply_1, last_good_reply_2); } } if (! state.gen_and_play_playout_move(last_good_reply_1, last_good_reply_2)) break; } } template void Search::play_in_tree(ThreadState& thread_state, bool& is_terminal) { auto& state = *thread_state.state; auto& root = m_tree.get_root(); auto node = &root; m_tree.inc_visit_count(*node); thread_state.simulation.nodes.push_back(node); is_terminal = false; Float expand_threshold = m_expand_threshold; unsigned depth = 0; while (node->has_children()) { node = select_child(thread_state, *node, depth); m_tree.inc_visit_count(*node); thread_state.simulation.nodes.push_back(node); state.play_in_tree(node->get_move()); ++depth; expand_threshold += m_expand_threshold_incr; } state.finish_in_tree(); if (node->get_visit_count() > expand_threshold || node == &root) { Float init_val = m_init_val[state.get_to_play()].get_mean(); if (! expand_node(thread_state.thread_id, *node, node, init_val)) thread_state.is_out_of_mem = true; else if (node == nullptr) is_terminal = true; else { thread_state.simulation.nodes.push_back(node); state.play_expanded_child(node->get_move()); } } } template void Search::write_info(ostream& out) const { auto& root = m_tree.get_root(); if (m_threads.empty()) return; auto& thread_state = m_threads[0]->thread_state; if (thread_state.nu_simulations == 0) { out << "No simulations in thread 0\n"; return; } FmtSaver saver(out); out << fixed << setprecision(2) << "Val: " << root.get_value() << setprecision(0) << ", ValCnt: " << root.get_value_count() << ", VstCnt: " << root.get_visit_count() << ", Sim: " << m_nu_simulations; auto child = select_child_final(root); if (child != nullptr && root.get_visit_count() > 0) out << setprecision(1) << ", Chld: " << (100 * child->get_visit_count() / root.get_visit_count()) << '%'; out << "\nNds: " << m_tree.get_nu_nodes() << ", Tm: " << time_to_string(m_last_time) << setprecision(0) << ", Sim/s: " << (double(m_nu_simulations) / m_last_time) << ", Len: " << thread_state.stat_len.to_string(true, 1, true) << "\nDp: " << thread_state.stat_in_tree_len.to_string(true, 1, true) << "\n"; } template void Search::write_info_ext(ostream& out) const { LIBBOARDGAME_UNUSED(out); } template bool Search::prune(TimeSource& time_source, double time, double max_time, Float prune_min_count, Float& new_prune_min_count) { Timer timer(time_source); TimeIntervalChecker interval_checker(time_source, max_time); if (m_deterministic) interval_checker.set_deterministic(1000000); log() << "Pruning count " << prune_min_count << " (at tm " << time << ")\n"; m_tmp_tree.clear(m_tree.get_root().get_value()); if (! m_tree.copy_subtree(m_tmp_tree, m_tmp_tree.get_root(), m_tree.get_root(), prune_min_count, true, &interval_checker)) { log("Pruning aborted"); return false; } int percent = int(m_tmp_tree.get_nu_nodes() * 100 / m_tree.get_nu_nodes()); log() << "Pruned size: " << m_tmp_tree.get_nu_nodes() << " (" << percent << "%, tm=" << timer() << ")\n"; m_tree.swap(m_tmp_tree); if (percent > 50) { if (prune_min_count >= 0.5 * numeric_limits::max()) return false; new_prune_min_count = prune_min_count * 2; return true; } else { new_prune_min_count = prune_min_count; return true; } } /** Restore the value and count of a root node from its children. The value of a root node has a different meaning than than values of inner nodes (position vs. move value) so after reusing a subtree, we need to restore it from the children. We use only the child with the highest count to avoid backing up many values of unvisited children that have only a value and count from prior knowledge initialization. */ template void Search::restore_root_from_children(Tree& tree, const Node& root) { const Node* best_child = nullptr; Float max_count = 0; for (ChildIterator i(tree, root); i; ++i) if (i->get_visit_count() > max_count) { best_child = &(*i); max_count = i->get_visit_count(); } if (best_child == nullptr) tree.init_root_value(get_tie_value(), 0); else tree.init_root_value(best_child->get_value(), best_child->get_value_count()); } template bool Search::search(Move& mv, Float max_count, Float min_simulations, double max_time, TimeSource& time_source, bool always_search) { check_create_threads(); if (max_count > 0) // A fixed number of simulations means that no time limit is used, but // max_time is still used at some places in the code, so we set it to // infinity max_time = numeric_limits::max(); m_nu_players = get_nu_players(); bool clear_tree = true; bool is_followup = check_followup(m_followup_sequence); bool is_same = false; if (is_followup && m_followup_sequence.empty()) { is_same = true; is_followup = false; } for (PlayerInt i = 0; i < m_nu_players; ++i) { m_init_val[i].clear(); m_init_val[i].add(get_tie_value()); } if (is_same || (is_followup && m_followup_sequence.size() <= m_nu_players)) for (PlayerInt i = 0; i < m_nu_players; ++i) if (m_root_val[i].get_count() > 0) m_init_val[i] = m_root_val[i]; if ((m_reuse_subtree && is_followup) || (m_reuse_tree && is_same)) { size_t tree_nodes = m_tree.get_nu_nodes(); if (m_followup_sequence.empty()) { if (tree_nodes > 1) log() << "Reusing all " << tree_nodes << "nodes (count=" << m_tree.get_root().get_visit_count() << ")\n"; } else { Timer timer(time_source); m_tmp_tree.clear(get_tie_value()); auto node = find_node(m_tree, m_followup_sequence); if (node != nullptr) { TimeIntervalChecker interval_checker(time_source, max_time); if (m_deterministic) interval_checker.set_deterministic(1000000); bool aborted = ! m_tree.extract_subtree(m_tmp_tree, *node, true, &interval_checker); auto& tmp_tree_root = m_tmp_tree.get_root(); if (! is_same) restore_root_from_children(m_tmp_tree, tmp_tree_root); if (aborted && ! always_search) return false; size_t tmp_tree_nodes = m_tmp_tree.get_nu_nodes(); if (tree_nodes > 1 && tmp_tree_nodes > 1) { double time = timer(); double reuse = double(tmp_tree_nodes) / double(tree_nodes); double percent = 100 * reuse; { FmtSaver saver(log()); log() << "Reusing " << tmp_tree_nodes << " nodes (" << fixed << setprecision(1) << percent << "% tm=" << setprecision(4) << time << ")\n"; } m_tree.swap(m_tmp_tree); clear_tree = false; max_time -= time; if (max_time < 0) max_time = 0; } } } } if (clear_tree) m_tree.clear(get_tie_value()); m_timer.reset(time_source); m_time_source = &time_source; on_start_search(); m_player = get_player(); for (PlayerInt i = 0; i < m_nu_players; ++i) m_root_val[i].clear(); if (SearchParamConst::use_last_good_reply && ! is_followup) m_last_good_reply.init(m_nu_players); for (unsigned i = 0; i < m_threads.size(); ++i) { auto& thread_state = m_threads[i]->thread_state; thread_state.nu_simulations = 0; thread_state.skip_bias_term_counter.clear(); thread_state.stat_len.clear(); thread_state.stat_in_tree_len.clear(); thread_state.state->start_search(); } m_max_count = max_count; m_min_simulations = min_simulations; m_max_time = max_time; m_nu_simulations = 0; Float prune_min_count = get_prune_count_start(); // Don't use multi-threading for very short searches (less than 0.5s). // There are too many lost updates at the beginning (e.g. if all threads // expand the root node and only the children of the last thread are used) auto reused_count = m_tree.get_root().get_visit_count(); unsigned nu_threads = m_nu_threads; if (max_time < 0.5 || (max_count > 0 && (max_count - reused_count) / expected_sim_per_sec() < 0.5)) { log("Using single-threading for very short search"); nu_threads = 1; } while (true) { for (unsigned i = 1; i < nu_threads; ++i) m_threads[i]->start_search(); search_loop(m_threads[0]->thread_state); for (unsigned i = 1; i < nu_threads; ++i) m_threads[i]->wait_search_finished(); bool is_out_of_mem = false; for (unsigned i = 0; i < nu_threads; ++i) if (m_threads[i]->thread_state.is_out_of_mem) { is_out_of_mem = true; break; } if (! is_out_of_mem) break; if (! m_prune_full_tree) { log("Maximum tree size reached"); break; } double time = m_timer(); if (! prune(time_source, time, max_time - time, prune_min_count, prune_min_count)) { log("Aborting search because pruning failed."); break; } } m_last_time = m_timer(); write_info(log()); bool result = select_move(mv); m_time_source = nullptr; return result; } template void Search::search_loop(ThreadState& thread_state) { auto& state = *thread_state.state; double time_interval = 0.1; if (m_max_count == 0 && m_max_time < 1) time_interval = 0.1 * m_max_time; IntervalChecker expensive_abort_checker(*m_time_source, time_interval, bind(&Search::check_abort_expensive, this, ref(thread_state))); if (m_deterministic) { unsigned interval = static_cast(max(1.0, expected_sim_per_sec() / 5.0)); expensive_abort_checker.set_deterministic(interval); } while (true) { thread_state.is_out_of_mem = false; auto root_count = m_tree.get_root().get_visit_count(); auto nu_simulations = m_nu_simulations.fetch_add(1); if (root_count > 0 && nu_simulations > m_min_simulations && (check_abort(thread_state) || expensive_abort_checker())) { m_nu_simulations.fetch_add(-1); break; } ++thread_state.nu_simulations; auto& simulation = thread_state.simulation; simulation.nodes.clear(); state.start_simulation(nu_simulations); bool is_terminal; play_in_tree(thread_state, is_terminal); if (thread_state.is_out_of_mem) return; thread_state.stat_in_tree_len.add(double(state.get_nu_moves())); if (! is_terminal) { playout(thread_state); simulation.eval = state.evaluate_playout(); } else simulation.eval = state.evaluate_terminal(); thread_state.stat_len.add(double(state.get_nu_moves())); update_values(thread_state); if (SearchParamConst::rave) update_rave_values(thread_state); if (SearchParamConst::use_last_good_reply) update_last_good_reply(thread_state); on_search_iteration(nu_simulations, *thread_state.state, thread_state.simulation); } } template auto Search::select_child(ThreadState& thread_state, const Node& node, unsigned depth) -> const Node* { auto node_count = node.get_visit_count(); const Node* best_child = nullptr; Float best_value = -numeric_limits::max(); ChildIterator i(m_tree, node); LIBBOARDGAME_ASSERT(i); if (check_skip_bias_term(thread_state, node_count, depth)) { do { auto value = i->get_value(); if (value > best_value) { best_value = value; best_child = &(*i); } } while (++i); } else { m_bias_term.start_iteration(node_count); auto bias_upper_limit = m_bias_term.get(0); Float limit = best_value - bias_upper_limit; do { auto value = i->get_value(); if (value < limit) continue; value += m_bias_term.get(i->get_visit_count()); if (value > best_value) { best_value = value; best_child = &(*i); limit = best_value - bias_upper_limit; } } while (++i); } return best_child; } template auto Search::select_child_final(const Node& node, const vector* exclude_moves) const -> const Node* { // Select the child with the highest visit count, use value as tie breaker const Node* result = nullptr; Float max_count = -1; Float max_count_value = -numeric_limits::max(); for (ChildIterator i(m_tree, node); i; ++i) { Float count = i->get_visit_count(); if (count > max_count || (count == max_count && i->get_value() > max_count_value)) { if (exclude_moves != nullptr && find(exclude_moves->begin(), exclude_moves->end(), i->get_move()) != exclude_moves->end()) continue; max_count = count; max_count_value = i->get_value(); result = &(*i); } } return result; } template bool Search::select_move(Move& mv, const vector* exclude_moves) const { auto child = select_child_final(m_tree.get_root(), exclude_moves); if (child != nullptr) { mv = child->get_move(); return true; } else return false; } template void Search::set_bias_term_constant(Float c) { m_bias_term.set_bias_term_constant(c); } template void Search::set_bias_term_interval(unsigned n) { m_bias_term_interval = n; } template void Search::set_callback(function callback) { m_callback = callback; } template void Search::set_expand_threshold(Float n) { m_expand_threshold = n; } template void Search::set_expand_threshold_incr(Float n) { m_expand_threshold_incr = n; } template void Search::set_deterministic() { m_deterministic = true; } template void Search::set_prune_count_start(Float n) { m_prune_count_start = n; } template void Search::set_prune_full_tree(bool enable) { m_prune_full_tree = enable; } template void Search::set_rave_dist_final(Float v) { m_rave_dist_final = v; } template void Search::set_rave_max_parent_count(Float n) { m_rave_max_parent_count = n; } template void Search::set_rave_max_child_count(Float n) { m_rave_max_child_count = n; } template void Search::set_rave_weight(Float v) { m_rave_weight = v; } template void Search::set_reuse_subtree(bool enable) { m_reuse_subtree = enable; } template void Search::set_reuse_tree(bool enable) { m_reuse_tree = enable; } template void Search::set_skip_bias_term_min_count(Float n) { m_skip_bias_term_min_count = n; } template void Search::set_tree_memory(size_t memory) { m_tree_memory = memory; m_max_nodes = get_max_nodes(memory); m_tree.set_max_nodes(m_max_nodes); m_tmp_tree.set_max_nodes(m_max_nodes); } template void Search::update_last_good_reply(ThreadState& thread_state) { const auto& state = *thread_state.state; const auto& eval = thread_state.simulation.eval; auto max_eval = eval[0]; for (PlayerInt i = 1; i < m_nu_players; ++i) max_eval = max(eval[i], max_eval); array is_winner; for (PlayerInt i = 0; i < m_nu_players; ++i) // Note: this handles a draw as a win. Without additional information // we cannot make a good decision how to handle draws and some // experiments in Blokus Duo showed (with low confidence) that treating // them as a win for both players is slighly better than treating them // as a loss for both. is_winner[i] = (eval[i] == max_eval); unsigned nu_moves = state.get_nu_moves(); if (nu_moves >= 2) { // Iterate backwards to store first reply if move was played more than // once PlayerMove reply = state.get_move(nu_moves - 1); PlayerMove last = state.get_move(nu_moves - 2); PlayerMove second_last; if (nu_moves >= 3) for (unsigned i = nu_moves - 1; i >= 2; --i) { second_last = state.get_move(i - 2); if (is_winner[reply.player]) m_last_good_reply.store(reply.player, last.move, second_last.move, reply.move); else m_last_good_reply.forget(reply.player, last.move, second_last.move, reply.move); reply = last; last = second_last; } if (is_winner[reply.player]) m_last_good_reply.store(reply.player, last.move, reply.move); else m_last_good_reply.forget(reply.player, last.move, reply.move); } } template void Search::update_rave_values(ThreadState& thread_state) { const auto& state = *thread_state.state; unsigned nu_moves = state.get_nu_moves(); if (nu_moves == 0) return; auto& was_played = thread_state.was_played; auto& first_play = thread_state.first_play; auto& nodes = thread_state.simulation.nodes; unsigned nu_nodes = static_cast(nodes.size()); unsigned i = nu_moves - 1; LIBBOARDGAME_ASSERT(nu_nodes > 1); for ( ; i >= nu_nodes - 1; --i) { auto mv = state.get_move(i); if (! state.skip_rave(mv.move)) { was_played[mv.player].set(mv.move); first_play[mv.player][mv.move.to_int()] = i; } } while (true) { const auto node = nodes[i]; if (node->get_visit_count() > m_rave_max_parent_count) break; auto mv = state.get_move(i); update_rave_values(thread_state, i, mv.player); if (i == 0) break; if (! state.skip_rave(mv.move)) { was_played[mv.player].set(mv.move); first_play[mv.player][mv.move.to_int()] = i; } --i; } // Reset was_played while (true) { ++i; if (i >= nu_moves) break; auto mv = state.get_move(i); was_played[mv.player].clear_word(mv.move); } } template void Search::update_rave_values(ThreadState& thread_state, unsigned i, PlayerInt player) { auto& nodes = thread_state.simulation.nodes; LIBBOARDGAME_ASSERT(i < nodes.size()); const auto node = nodes[i]; const auto& state = *thread_state.state; auto& was_played = thread_state.was_played; auto& first_play = thread_state.first_play; unsigned len = state.get_nu_moves(); Float dist_weight_factor = (1 - m_rave_dist_final) / Float(len - i); ChildIterator it(m_tree, *node); LIBBOARDGAME_ASSERT(it); do { auto mv = it->get_move(); if (! was_played[player][mv] || it->get_value_count() > m_rave_max_child_count) continue; unsigned first = first_play[player][mv.to_int()]; LIBBOARDGAME_ASSERT(first > i); if (SearchParamConst::rave_check_same) { bool other_played_same = false; for (PlayerInt j = 0; j < m_nu_players; ++j) if (j != player && was_played[j][mv]) { unsigned first_other = first_play[j][mv.to_int()]; if (first_other >= i && first_other <= first) { other_played_same = true; break; } } if (other_played_same) continue; } Float weight = m_rave_weight; if (SearchParamConst::rave_dist_weighting) weight *= 1 - Float(first - i) * dist_weight_factor; m_tree.add_value(*it, thread_state.simulation.eval[player], weight); } while (++it); } template void Search::update_values(ThreadState& thread_state) { const auto& state = *thread_state.state; auto& nodes = thread_state.simulation.nodes; const auto& eval = thread_state.simulation.eval; m_tree.add_value(m_tree.get_root(), eval[m_player]); unsigned nu_nodes = static_cast(nodes.size()); for (unsigned i = 1; i < nu_nodes; ++i) { auto& node = *nodes[i]; auto mv = state.get_move(i - 1); m_tree.add_value(node, eval[mv.player]); } for (PlayerInt i = 0; i < m_nu_players; ++i) { m_root_val[i].add(eval[i]); m_init_val[i].add(eval[i]); } } //----------------------------------------------------------------------------- } // namespace libboardgame_mcts #endif // LIBBOARDGAME_MCTS_SEARCH_H pentobi-7.2/src/libboardgame_mcts/Tree.h000066400000000000000000000371321227240712600203300ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_mcts/Tree.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_MCTS_TREE_H #define LIBBOARDGAME_MCTS_TREE_H #include #include #include "Node.h" #include "libboardgame_util/Abort.h" #include "libboardgame_util/IntervalChecker.h" namespace libboardgame_mcts { using namespace std; using libboardgame_util::get_abort; using libboardgame_util::IntervalChecker; //----------------------------------------------------------------------------- /** Tree for Monte-Carlo tree search. The nodes can be modified only through member functions of this class, so that it can guarantee an intact tree structure. The user has access to all nodes, but only as const references.

The tree uses separate parts of the node storage for different threads, so it can be used without locking in multi-threaded search. Not all functions are thread-safe, only the ones that are used during a search (e.g. expanding a node is thread-safe, but clear() is not) */ template class Tree { friend class NodeExpander; public: typedef N Node; typedef typename Node::Move Move; typedef typename Node::Float Float; /** Helper class that is passed to the search state during node expansion. This class allows the search state to directly create children of a node at the node expansion, so that copying to a temporary move list is not necessary, but avoids that the search needs to expose a non-const reference to the tree to the state. */ class NodeExpander { public: NodeExpander(unsigned thread_id, Tree& tree, const Node& node); /** Add new child. The child will only be added if the tree is not full. */ void add_child(const Move& mv, Float value, Float count); /** Link the children to the parent node. */ void link_children(); /** Return if the tree capacity was reached during an add_child(). */ bool is_tree_full() const; /** Return the node to play after the node expansion. This returns the child with the highest value if prior knowledge was used, or the first child, or null if no children. This can be used for avoiding and extra iteration over the children when selecting a child after a node expansion. */ const Node* get_best_child() const; private: unsigned m_thread_id; bool m_is_tree_full; int m_nu_children; Float m_best_value; Tree& m_tree; const Node& m_node; const Node* m_first_child; const Node* m_best_child; }; Tree(size_t max_nodes, unsigned nu_threads); ~Tree() throw(); /** Remove all nodes and initialize the root node with count 0 and a given value. */ void clear(Float root_value); const Node& get_root() const; size_t get_nu_nodes() const; const Node& get_node(NodeIndex i) const; void link_children(const Node& node, const Node* first_child, unsigned nu_children); void set_max_nodes(size_t max_nodes); size_t get_max_nodes() const; bool create_node(unsigned thread_id, const Move& mv, Float value, Float count); void add_value(const Node& node, Float v); void add_value(const Node& node, Float v, Float weight); void inc_visit_count(const Node& node); /** Overwrite the root value and count. */ void init_root_value(Float value, Float count); void swap(Tree& tree); /** Extract a subtree. This operation can be lengthy and can be aborted by providing an abort checker argument. If the extraction was aborted, the copied subtree is a valid partial tree of the full tree that would have been extracted. Note that you still have to re-initialize the value of the subtree after the extraction because the value of the root node and the values of inner nodes have a different meaning. @pre Target tree is empty (! target.get_root().has_children()) @param target The target tree @param node The root node of the subtree. @param check_abort Whether to check util::get_abort() @param interval_checker An optional expensive function to check if the extraction should be aborted. @return @c false if the extraction was aborted. */ bool extract_subtree(Tree& target, const Node& node, bool check_abort = false, IntervalChecker* interval_checker = 0) const; /** Copy a subtree. This operation can be lengthy and can be aborted by providing an abort checker argument. If the copying was aborted, the copied subtree is a valid partial tree of the full tree that would have been copying. The caller is responsible that the trees have the same number of maximum nodes and that the target tree has room for the subtree. @param target The target tree @param target_node The target node @param node The root node of the subtree. @param min_count Don't copy subtrees of nodes below this count @param check_abort Whether to check util::get_abort() @param interval_checker @return @c false if the copying was aborted. */ bool copy_subtree(Tree& target, const Node& target_node, const Node& node, Float min_count = 0, bool check_abort = false, IntervalChecker* interval_checker = 0) const; /** Remove a child. This function keeps the order of the remaining children and invalidates references to children. the operation is not thread-safe. */ bool remove_child(const Node& node, const Move& mv); private: struct ThreadStorage { Node* begin; Node* end; Node* next; }; unsigned m_nu_threads; size_t m_max_nodes; size_t m_nodes_per_thread; unique_ptr m_thread_storage; unique_ptr m_nodes; bool contains(const Node& node) const; unsigned get_thread_storage(const Node& node) const; Node& non_const(const Node& node) const; }; template inline Tree::NodeExpander::NodeExpander(unsigned thread_id, Tree& tree, const Node& node) : m_thread_id(thread_id), m_is_tree_full(false), m_nu_children(0), m_best_value(0), m_tree(tree), m_node(node), m_first_child(m_tree.m_thread_storage[thread_id].next), m_best_child(m_first_child) { } template inline void Tree::NodeExpander::add_child(const Move& mv, Float value, Float count) { LIBBOARDGAME_ASSERT(m_nu_children < numeric_limits::max()); if (! (m_is_tree_full |= ! m_tree.create_node(m_thread_id, mv, value, count))) { if (m_nu_children == 0) m_best_value = value; else if (count > 0 && value > m_best_value) { m_best_child = m_first_child + m_nu_children; m_best_value = value; } ++m_nu_children; } } template inline auto Tree::NodeExpander::get_best_child() const -> const Node* { if (m_nu_children > 0) return m_best_child; else return nullptr; } template inline auto Tree::get_node(NodeIndex i) const -> const Node& { return m_nodes[i]; } template inline bool Tree::NodeExpander::is_tree_full() const { return m_is_tree_full; } template inline void Tree::NodeExpander::link_children() { if (m_nu_children > 0) m_tree.link_children(m_node, m_first_child, m_nu_children); } template Tree::Tree(size_t max_nodes, unsigned nu_threads) : m_nu_threads(nu_threads) { set_max_nodes(max_nodes); } template Tree::~Tree() throw() { } template inline void Tree::add_value(const Node& node, Float v) { non_const(node).add_value(v); } template inline void Tree::add_value(const Node& node, Float v, Float weight) { non_const(node).add_value(v, weight); } template void Tree::clear(Float root_value) { m_thread_storage[0].next = m_thread_storage[0].begin + 1; for (unsigned i = 1; i < m_nu_threads; ++i) m_thread_storage[i].next = m_thread_storage[i].begin; m_nodes[0].init(Move::null(), root_value, 0); } template bool Tree::contains(const Node& node) const { return (&node >= m_nodes.get() && &node < m_nodes.get() + m_max_nodes); } template bool Tree::copy_subtree(Tree& target, const Node& target_node, const Node& node, Float min_count, bool check_abort, IntervalChecker* interval_checker) const { LIBBOARDGAME_ASSERT(target.m_max_nodes == m_max_nodes); LIBBOARDGAME_ASSERT(target.m_nu_threads == m_nu_threads); LIBBOARDGAME_ASSERT(contains(node)); auto& target_node_non_const = target.non_const(target_node); target_node_non_const.copy_data_from(node); bool abort = (check_abort && get_abort()) || (interval_checker != nullptr && (*interval_checker)()); if (! node.has_children() || node.get_visit_count() < min_count || abort) { target_node_non_const.unlink_children(); return ! abort; } unsigned nu_children = node.get_nu_children(); auto& first_child = get_node(node.get_first_child()); // Create target children in the equivalent thread storage as in source. // This ensures that the thread storage will not overflow (because the // trees have identical nu_threads/max_nodes) ThreadStorage& thread_storage = target.m_thread_storage[get_thread_storage(first_child)]; auto target_child = thread_storage.next; auto target_first_child = static_cast(target_child - target.m_nodes.get()); target_node_non_const.link_children(target_first_child, nu_children); thread_storage.next += nu_children; // Without the extra () around thread_storage.next in the following // assert, GCC 4.7.2 gives the error: parse error in template argument list LIBBOARDGAME_ASSERT((thread_storage.next) < thread_storage.end); abort = false; auto end = &first_child + node.get_nu_children(); for (auto i = &first_child; i != end; ++i, ++target_child) if (! copy_subtree(target, *target_child, *i, min_count, check_abort, interval_checker)) // Finish this loop even on abort to make sure the children // node data is copied abort = true; return ! abort; } template bool Tree::create_node(unsigned thread_id, const Move& mv, Float value, Float count) { LIBBOARDGAME_ASSERT(thread_id < m_nu_threads); auto& thread_storage = m_thread_storage[thread_id]; if (thread_storage.next != thread_storage.end) { thread_storage.next->init(mv, value, count); ++thread_storage.next; return true; } return false; } template bool Tree::extract_subtree(Tree& target, const Node& node, bool check_abort, IntervalChecker* interval_checker) const { LIBBOARDGAME_ASSERT(contains(node)); LIBBOARDGAME_ASSERT(&target != this); LIBBOARDGAME_ASSERT(target.get_max_nodes() == get_max_nodes()); LIBBOARDGAME_ASSERT(! target.get_root().has_children()); return copy_subtree(target, target.m_nodes[0], node, 0, check_abort, interval_checker); } template size_t Tree::get_max_nodes() const { return m_max_nodes; } template size_t Tree::get_nu_nodes() const { size_t result = 0; for (unsigned i = 0; i < m_nu_threads; ++i) { auto& thread_storage = m_thread_storage[i]; result += thread_storage.next - thread_storage.begin; } return result; } template inline auto Tree::get_root() const -> const Node& { return m_nodes[0]; } /** Get the thread storage a node belongs to. */ template inline unsigned Tree::get_thread_storage(const Node& node) const { size_t diff = &node - m_nodes.get(); return static_cast(diff / m_nodes_per_thread); } template inline void Tree::inc_visit_count(const Node& node) { non_const(node).inc_visit_count(); } template inline void Tree::init_root_value(Float value, Float count) { m_nodes[0].init_value(value, count); } template inline void Tree::link_children(const Node& node, const Node* first_child, unsigned nu_children) { NodeIndex first_child_index = static_cast(first_child - m_nodes.get()); LIBBOARDGAME_ASSERT(first_child_index > 0); LIBBOARDGAME_ASSERT(first_child_index < m_max_nodes); non_const(node).link_children(first_child_index, nu_children); } /** Convert a const reference to node from user to a non-const reference. The user has only read access to the nodes, because the tree guarantees the validity of the tree structure. */ template inline auto Tree::non_const(const Node& node) const -> Node& { LIBBOARDGAME_ASSERT(contains(node)); return const_cast(node); } template bool Tree::remove_child(const Node& node, const Move& mv) { unsigned nu_children = node.get_nu_children(); if (nu_children == 0) return false; auto& first_child = non_const(*node.get_first_child()); auto child = &first_child; for (int i = 0; i < nu_children; ++i, ++child) if (child->get_move() == mv) { for ( ; i < nu_children - 1; ++i, ++child) *child = *(child + 1); non_const(node).link_children(first_child, nu_children - 1); return true; } return false; } template void Tree::set_max_nodes(size_t max_nodes) { max_nodes = min(max_nodes, static_cast(numeric_limits::max())); if (max_nodes == 0) // We need at least the root node (for useful searches we need of // course also children, but a root node is the minimum requirement to // avoid crashing). max_nodes = 1; m_max_nodes = max_nodes; m_nodes.reset(new Node[max_nodes]); m_thread_storage.reset(new ThreadStorage[m_nu_threads]); m_nodes_per_thread = max_nodes / m_nu_threads; for (unsigned i = 0; i < m_nu_threads; ++i) { auto& thread_storage = m_thread_storage[i]; thread_storage.begin = m_nodes.get() + i * m_nodes_per_thread; thread_storage.end = thread_storage.begin + m_nodes_per_thread; } clear(0); } template void Tree::swap(Tree& tree) { // Reminder to update this function when the class gets additional members struct Dummy { unsigned m_nu_threads; size_t m_max_nodes; size_t m_nodes_per_thread; unique_ptr m_thread_storage; unique_ptr m_nodes; }; static_assert(sizeof(Tree) == sizeof(Dummy), ""); std::swap(m_nu_threads, tree.m_nu_threads); std::swap(m_max_nodes, tree.m_max_nodes); std::swap(m_nodes_per_thread, tree.m_nodes_per_thread); m_thread_storage.swap(tree.m_thread_storage); m_nodes.swap(tree.m_nodes); } //----------------------------------------------------------------------------- } // namespace libboardgame_mcts #endif // LIBBOARDGAME_MCTS_TREE_H pentobi-7.2/src/libboardgame_mcts/TreeUtil.h000066400000000000000000000021451227240712600211620ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_mcts/TreeUtil.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_MCTS_TREE_UTIL_H #define LIBBOARDGAME_MCTS_TREE_UTIL_H #include "ChildIterator.h" #include "Tree.h" namespace libboardgame_mcts { namespace tree_util { //----------------------------------------------------------------------------- template const N* find_child(const Tree& tree, const N& node, typename N::Move mv) { for (ChildIterator i(tree, node); i; ++i) if (i->get_move() == mv) return &(*i); return nullptr; } template const N* find_node(const Tree& tree, const S& sequence) { auto node = &tree.get_root(); for (auto mv : sequence) if ((node = find_child(tree, *node, mv)) == nullptr) break; return node; } //----------------------------------------------------------------------------- } // namespace tree_util } // namespace libboardgame_mcts #endif // LIBBOARDGAME_MCTS_TREE_UTIL_H pentobi-7.2/src/libboardgame_sgf/000077500000000000000000000000001227240712600170635ustar00rootroot00000000000000pentobi-7.2/src/libboardgame_sgf/CMakeLists.txt000066400000000000000000000005331227240712600216240ustar00rootroot00000000000000include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) set(boardgame_sgf_STAT_SRCS InvalidPropertyValue.cpp InvalidTree.cpp MissingProperty.cpp Node.cpp Reader.cpp Tree.cpp TreeIterator.cpp TreeReader.cpp TreeWriter.cpp Util.cpp Writer.cpp ) add_library(boardgame_sgf STATIC ${boardgame_sgf_STAT_SRCS}) pentobi-7.2/src/libboardgame_sgf/InvalidPropertyValue.cpp000066400000000000000000000011071227240712600237160ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/InvalidPropertyValue.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "InvalidPropertyValue.h" namespace libboardgame_sgf { //----------------------------------------------------------------------------- InvalidPropertyValue::~InvalidPropertyValue() throw() { } //----------------------------------------------------------------------------- } // namespace libboardgame_sgf pentobi-7.2/src/libboardgame_sgf/InvalidPropertyValue.h000066400000000000000000000024451227240712600233710ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/InvalidPropertyValue.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_SGF_INVALID_PROPERTY_VALUE_H #define LIBBOARDGAME_SGF_INVALID_PROPERTY_VALUE_H #include "InvalidTree.h" #include "sstream" namespace libboardgame_sgf { using namespace std; //----------------------------------------------------------------------------- class InvalidPropertyValue : public InvalidTree { public: template InvalidPropertyValue(const string& id, const T& value); ~InvalidPropertyValue() throw(); private: template static string get_message(const string& id, const T& value); }; template InvalidPropertyValue::InvalidPropertyValue(const string& id, const T& value) : InvalidTree(get_message(id, value)) { } template string InvalidPropertyValue::get_message(const string& id, const T& value) { ostringstream msg; msg << "Invalid value '" << value << " for SGF property '" << id << "'"; return msg.str(); } //----------------------------------------------------------------------------- } // namespace libboardgame_sgf #endif // LIBBOARDGAME_SGF_INVALID_PROPERTY_VALUE_H pentobi-7.2/src/libboardgame_sgf/InvalidTree.cpp000066400000000000000000000010441227240712600217740ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/InvalidTree.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "InvalidTree.h" namespace libboardgame_sgf { //----------------------------------------------------------------------------- InvalidTree::~InvalidTree () throw() { } //----------------------------------------------------------------------------- } // namespace libboardgame_sgf pentobi-7.2/src/libboardgame_sgf/InvalidTree.h000066400000000000000000000027171227240712600214510ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/InvalidTree.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_SGF_INVALID_TREE_H #define LIBBOARDGAME_SGF_INVALID_TREE_H #include "libboardgame_util/Exception.h" namespace libboardgame_sgf { using namespace std; using libboardgame_util::Exception; //----------------------------------------------------------------------------- /** Exception indication a semantic error in the tree. This exception is used for semantic errors in SGF trees. If a SGF tree is loaded from an external file, it is usually only checked for (game-independent) syntax errors, but not for semantic errors (e.g. illegal moves) because that would be too expensive when loading large trees and not allow the user to partially use a tree if there is an error only in some variations. As a consequence, functions that use the tree may cause errors later (e.g. when trying to update the game state to a node in the tree). In this case, they should throw InvalidTree. */ class InvalidTree : public Exception { public: InvalidTree(const string& s); ~InvalidTree() throw(); }; inline InvalidTree::InvalidTree(const string& s) : Exception(s) { } //----------------------------------------------------------------------------- } // namespace libboardgame_sgf #endif // LIBBOARDGAME_SGF_INVALID_TREE_H pentobi-7.2/src/libboardgame_sgf/MissingProperty.cpp000066400000000000000000000014341227240712600227470ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/MissingProperty.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "MissingProperty.h" namespace libboardgame_sgf { using namespace std; //----------------------------------------------------------------------------- MissingProperty::MissingProperty(const string& message) : InvalidTree("Missing SGF property: " + message) { } MissingProperty::MissingProperty(const string& id, const string& message) : InvalidTree("Missing SGF property '" + id + ": " + message) { } //----------------------------------------------------------------------------- } // namespace libboardgame_sgf pentobi-7.2/src/libboardgame_sgf/MissingProperty.h000066400000000000000000000014211227240712600224100ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/MissingProperty.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_SGF_MISSING_PROPERTY_H #define LIBBOARDGAME_SGF_MISSING_PROPERTY_H #include "InvalidTree.h" namespace libboardgame_sgf { using namespace std; //----------------------------------------------------------------------------- class MissingProperty : public InvalidTree { public: MissingProperty(const string& message); MissingProperty(const string& id, const string& message); }; //----------------------------------------------------------------------------- } // namespace libboardgame_sgf #endif // LIBBOARDGAME_SGF_MISSING_PROPERTY_H pentobi-7.2/src/libboardgame_sgf/Node.cpp000066400000000000000000000170651227240712600204650ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/Node.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Node.h" #include "MissingProperty.h" #include "libboardgame_util/Assert.h" namespace libboardgame_sgf { using namespace std; //----------------------------------------------------------------------------- Property::Property(const string& id, const vector& values) : id(id), values(values) { LIBBOARDGAME_ASSERT(! values.empty()); } //----------------------------------------------------------------------------- Node::Node() : m_parent(nullptr) { } void Node::append(unique_ptr node) { node->m_parent = this; if (! m_first_child) m_first_child = move(node); else get_last_child()->m_sibling = move(node); } Node& Node::create_new_child() { unique_ptr node(new Node()); node->m_parent = this; Node& result = *(node.get()); auto last_child = get_last_child(); if (last_child == nullptr) m_first_child = move(node); else last_child->m_sibling = move(node); return result; } void Node::delete_variations() { if (m_first_child) m_first_child->m_sibling.reset(nullptr); } Property* Node::find_property(const string& id) const { auto property = m_first_property.get(); while (property != nullptr) { if (property->id == id) break; property = property->next.get(); } return property; } const vector Node::get_multi_property(const string& id) const { auto property = find_property(id); if (property == nullptr) return vector(); else return property->values; } bool Node::has_property(const string& id) const { return find_property(id) != nullptr; } const Node& Node::get_child(unsigned i) const { LIBBOARDGAME_ASSERT(i < get_nu_children()); auto child = m_first_child.get(); while (i > 0) { child = child->m_sibling.get(); --i; } return *child; } unsigned Node::get_child_index(const Node& child) const { auto current = m_first_child.get(); unsigned i = 0; while (true) { if (current == &child) return i; current = current->m_sibling.get(); LIBBOARDGAME_ASSERT(current != nullptr); ++i; } } Node* Node::get_last_child() const { auto node = m_first_child.get(); if (node == nullptr) return nullptr; while (node->m_sibling) node = node->m_sibling.get(); return node; } unsigned Node::get_nu_children() const { unsigned n = 0; auto child = m_first_child.get(); while (child != nullptr) { ++n; child = child->m_sibling.get(); } return n; } const Node* Node::get_previous_sibling() const { if (m_parent == nullptr) return nullptr; auto child = &m_parent->get_first_child(); if (child == this) return nullptr; do { if (child->get_sibling() == this) return child; child = child->get_sibling(); } while (child != nullptr); LIBBOARDGAME_ASSERT(false); return nullptr; } const string& Node::get_property(const string& id) const { auto property = find_property(id); if (property == nullptr) throw MissingProperty(id); return property->values[0]; } const string& Node::get_property(const string& id, const string& default_value) const { auto property = find_property(id); if (property == nullptr) return default_value; else return property->values[0]; } void Node::make_first_child() { LIBBOARDGAME_ASSERT(has_parent()); auto current_child = m_parent->m_first_child.get(); if (current_child == this) return; while (true) { auto sibling = current_child->m_sibling.get(); if (sibling == this) { unique_ptr tmp = move(m_parent->m_first_child); m_parent->m_first_child = move(current_child->m_sibling); current_child->m_sibling = move(m_sibling); m_sibling = move(tmp); return; } current_child = sibling; } } bool Node::move_property_to_front(const string& id) { auto current = m_first_property.get(); Property* last = nullptr; while (true) { if (current == nullptr) return false; if (current->id == id) { if (last != nullptr) { unique_ptr tmp = move(last->next); last->next = move(current->next); current->next = move(m_first_property); m_first_property = move(tmp); } return true; } last = current; current = current->next.get(); } } void Node::move_down() { LIBBOARDGAME_ASSERT(has_parent()); auto current = m_parent->m_first_child.get(); if (current == this) { unique_ptr tmp = move(m_parent->m_first_child); m_parent->m_first_child = move(m_sibling); m_sibling = move(m_parent->m_first_child->m_sibling); m_parent->m_first_child->m_sibling = move(tmp); return; } while (true) { auto sibling = current->m_sibling.get(); if (sibling == this) { if (! m_sibling) return; unique_ptr tmp = move(current->m_sibling); current->m_sibling = move(m_sibling); m_sibling = move(current->m_sibling->m_sibling); current->m_sibling->m_sibling = move(tmp); return; } current = sibling; } } void Node::move_up() { LIBBOARDGAME_ASSERT(has_parent()); auto current = m_parent->m_first_child.get(); if (current == this) return; Node* prev = nullptr; while (true) { auto sibling = current->m_sibling.get(); if (sibling == this) { if (prev == nullptr) { make_first_child(); return; } unique_ptr tmp = move(prev->m_sibling); prev->m_sibling = move(current->m_sibling); current->m_sibling = move(m_sibling); m_sibling = move(tmp); return; } prev = current; current = sibling; } } bool Node::remove_property(const string& id) { auto property = m_first_property.get(); Property* last = nullptr; while (property != nullptr) { if (property->id == id) break; last = property; property = property->next.get(); } if (property == nullptr) return false; if (last != nullptr) last->next = move(property->next); else m_first_property = move(property->next); return true; } unique_ptr Node::remove_child(Node& child) { auto node = &m_first_child; unique_ptr* previous = nullptr; while (true) { if (node->get() == &child) { unique_ptr result = move(*node); if (previous == nullptr) m_first_child = move(child.m_sibling); else (*previous)->m_sibling = move(child.m_sibling); result->m_parent = nullptr; return result; } previous = node; node = &(*node)->m_sibling; LIBBOARDGAME_ASSERT(node); } } //----------------------------------------------------------------------------- } // namespace libboardgame_sgf pentobi-7.2/src/libboardgame_sgf/Node.h000066400000000000000000000222351227240712600201250ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/Node.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_SGF_NODE_H #define LIBBOARDGAME_SGF_NODE_H #include #include #include #include "InvalidPropertyValue.h" #include "libboardgame_util/Assert.h" #include "libboardgame_util/StringUtil.h" namespace libboardgame_sgf { using namespace std; using libboardgame_util::from_string; using libboardgame_util::to_string; //----------------------------------------------------------------------------- struct Property { string id; vector values; unique_ptr next; Property(const string& id, const vector& values); }; //----------------------------------------------------------------------------- class Node { public: Node(); /** Append a new child. */ void append(unique_ptr node); bool has_property(const string& id) const; /** Get a property. @pre has_property(id) */ const string& get_property(const string& id) const; const string& get_property(const string& id, const string& default_value) const; const vector get_multi_property(const string& id) const; /** Get property parsed as a type. @pre has_property(id) @throws InvalidPropertyValue, MissingProperty */ template T parse_property(const string& id) const; /** Get property parsed as a type with default value. @throws InvalidPropertyValue */ template T parse_property(const string& id, const T& default_value) const; /** @return true, if property was added or changed. */ template bool set_property(const string& id, const T& value); /** @return true, if property was added or changed. */ bool set_property(const string& id, const char* value); /** @return true, if property was added or changed. */ template bool set_property(const string& id, const vector& values); /** @return true, if node contained the property. */ bool remove_property(const string& id); bool move_property_to_front(const string& id); const Property* get_first_property() const; Node* get_sibling(); Node& get_first_child(); const Node& get_first_child() const; Node* get_first_child_or_null(); const Node* get_first_child_or_null() const; const Node* get_sibling() const; const Node* get_previous_sibling() const; bool has_children() const; bool has_single_child() const; unsigned get_nu_children() const; /** @pre i < get_nu_children() */ const Node& get_child(unsigned i) const; unsigned get_child_index(const Node& child) const; /** Get single child. @pre has_single_child() */ const Node& get_child() const; bool has_parent() const; /** Get parent node. @pre has_parent() */ const Node& get_parent() const; /** Get parent node or null if node has no parent. */ const Node* get_parent_or_null() const; Node& get_parent(); Node& create_new_child(); /** Remove a child. @return The removed child node. */ unique_ptr remove_child(Node& child); /** Remove all children. @return A pointer to the first child (which also owns its siblings), which can be used to append the children to a different node. */ unique_ptr remove_children(); /** @pre has_parent() */ void make_first_child(); /** Switch place with previous sibling. If the node is already the first child, nothing happens. @pre has_parent() */ void move_up(); /** Switch place with sibling. If the node is the last sibling, nothing happens. @pre has_parent() */ void move_down(); /** Delete all siblings of the first child. */ void delete_variations(); private: Node* m_parent; unique_ptr m_first_child; unique_ptr m_sibling; unique_ptr m_first_property; Property* find_property(const string& id) const; Node* get_last_child() const; }; inline const Node& Node::get_child() const { LIBBOARDGAME_ASSERT(has_single_child()); return *m_first_child; } inline const Node& Node::get_parent() const { LIBBOARDGAME_ASSERT(has_parent()); return *m_parent; } inline Node& Node::get_parent() { LIBBOARDGAME_ASSERT(has_parent()); return *m_parent; } inline const Node* Node::get_parent_or_null() const { return m_parent; } inline Node& Node::get_first_child() { LIBBOARDGAME_ASSERT(has_children()); return *m_first_child.get(); } inline const Node& Node::get_first_child() const { LIBBOARDGAME_ASSERT(has_children()); return *(m_first_child.get()); } inline Node* Node::get_first_child_or_null() { return m_first_child.get(); } inline const Node* Node::get_first_child_or_null() const { return m_first_child.get(); } inline const Property* Node::get_first_property() const { return m_first_property.get(); } inline Node* Node::get_sibling() { return m_sibling.get(); } inline const Node* Node::get_sibling() const { return m_sibling.get(); } inline bool Node::has_children() const { return static_cast(m_first_child); } inline bool Node::has_parent() const { return m_parent != nullptr; } inline bool Node::has_single_child() const { return (m_first_child && ! m_first_child->m_sibling); } template T Node::parse_property(const string& id) const { string value = get_property(id); T result; if (! from_string(value, result)) throw InvalidPropertyValue(id, value); return result; } template T Node::parse_property(const string& id, const T& default_value) const { if (! has_property(id)) return default_value; return parse_property(id); } inline unique_ptr Node::remove_children() { if (m_first_child) m_first_child->m_parent = nullptr; return move(m_first_child); } template bool Node::set_property(const string& id, const T& value) { vector values(1, value); return set_property(id, values); } inline bool Node::set_property(const string& id, const char* value) { return set_property(id, value); } template bool Node::set_property(const string& id, const vector& values) { vector values_to_string; for (const T& v : values) values_to_string.push_back(to_string(v)); auto property = m_first_property.get(); if (property == nullptr) { m_first_property.reset(new Property(id, values_to_string)); return true; } while (true) { if (property->id == id) { bool was_changed = (property->values != values_to_string); property->values = values_to_string; return was_changed; } if (! property->next) { property->next.reset(new Property(id, values_to_string)); return true; } property = property->next.get(); } } //----------------------------------------------------------------------------- class PropertyIterator { public: PropertyIterator(const Node& node); operator bool() const; void operator++(); const Property& operator*() const; const Property* operator->() const; private: const Property* m_current; }; inline PropertyIterator::PropertyIterator(const Node& node) { m_current = node.get_first_property(); } inline PropertyIterator::operator bool() const { return m_current != nullptr; } inline void PropertyIterator::operator++() { LIBBOARDGAME_ASSERT(operator bool()); m_current = m_current->next.get(); } inline const Property& PropertyIterator::operator*() const { LIBBOARDGAME_ASSERT(operator bool()); return *m_current; } inline const Property* PropertyIterator::operator->() const { LIBBOARDGAME_ASSERT(operator bool()); return m_current; } //----------------------------------------------------------------------------- class ChildIterator { public: ChildIterator(const Node& node); operator bool() const; void operator++(); void operator--(); const Node& operator*() const; const Node* operator->() const; private: const Node* m_current; }; inline ChildIterator::ChildIterator(const Node& node) { m_current = node.get_first_child_or_null(); } inline ChildIterator::operator bool() const { return m_current != nullptr; } inline void ChildIterator::operator++() { LIBBOARDGAME_ASSERT(operator bool()); m_current = m_current->get_sibling(); } inline void ChildIterator::operator--() { LIBBOARDGAME_ASSERT(operator bool()); m_current = m_current->get_previous_sibling(); LIBBOARDGAME_ASSERT(operator bool()); } inline const Node& ChildIterator::operator*() const { LIBBOARDGAME_ASSERT(operator bool()); return *m_current; } inline const Node* ChildIterator::operator->() const { LIBBOARDGAME_ASSERT(operator bool()); return m_current; } //----------------------------------------------------------------------------- } // namespace libboardgame_sgf #endif // LIBBOARDGAME_SGF_NODE_H pentobi-7.2/src/libboardgame_sgf/Reader.cpp000066400000000000000000000141331227240712600207730ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/Reader.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Reader.h" #include #include #include #include "libboardgame_util/Assert.h" #include "libboardgame_util/Unused.h" namespace libboardgame_sgf { using namespace std; //----------------------------------------------------------------------------- namespace { /** Replacement for std::isspace() that returns true only for whitespaces in the ASCII range. */ bool is_ascii_space(int c) { return c >= 0 && c < 128 && isspace(c); } } // namespace //----------------------------------------------------------------------------- Reader::ReadError::ReadError(const string& s) : Exception(s) { } //----------------------------------------------------------------------------- Reader::Reader() : m_read_only_main_variation(false) { } Reader::~Reader() throw() { } void Reader::consume_char(char expected) { LIBBOARDGAME_UNUSED_IF_NOT_DEBUG(expected); char c = read_char(); LIBBOARDGAME_UNUSED_IF_NOT_DEBUG(c); LIBBOARDGAME_ASSERT(c == expected); } void Reader::consume_whitespace() { int c; while (is_ascii_space(c = peek())) m_in->get(); } void Reader::on_begin_node(bool is_root) { // Default implementation does nothing LIBBOARDGAME_UNUSED(is_root); } void Reader::on_begin_tree(bool is_root) { // Default implementation does nothing LIBBOARDGAME_UNUSED(is_root); } void Reader::on_end_node() { // Default implementation does nothing } void Reader::on_end_tree(bool is_root) { // Default implementation does nothing LIBBOARDGAME_UNUSED(is_root); } void Reader::on_property(const string& id, const vector& values) { // Default implementation does nothing LIBBOARDGAME_UNUSED(id); LIBBOARDGAME_UNUSED(values); } char Reader::peek() { int c = m_in->peek(); if (c == EOF) throw ReadError("Unexpected end of input"); return char(c); } void Reader::read(istream& in, bool check_single_tree, bool* more_game_trees_left) { m_in = ∈ m_is_in_main_variation = true; consume_whitespace(); read_tree(true); while (true) { int c = m_in->peek(); if (c == EOF) { if (more_game_trees_left != nullptr) *more_game_trees_left = false; return; } else if (c == '(') { if (check_single_tree) throw ReadError("Input has multiple game trees"); else { if (more_game_trees_left != nullptr) *more_game_trees_left = true; return; } } else if (is_ascii_space(c)) m_in->get(); else throw ReadError("Extra characters after end of tree."); } } void Reader::read(const string& file) { ifstream in(file); if (! in) throw ReadError("Could not open '" + file + "'"); try { read(in, true); } catch (const ReadError& e) { throw ReadError("Could not read '" + file + "': " + e.what()); } } char Reader::read_char() { int c = m_in->get(); if (c == EOF) throw ReadError("Unexpected end of SGF stream"); if (c == '\r') { // Convert CR+LF or single CR into LF if (peek() == '\n') m_in->get(); return '\n'; } return char(c); } void Reader::read_expected(char expected) { if (read_char() != expected) throw ReadError(string("Expected '") + expected + "'"); } void Reader::read_node(bool is_root) { read_expected(';'); if (! m_read_only_main_variation || m_is_in_main_variation) on_begin_node(is_root); while (true) { consume_whitespace(); char c = peek(); if (c == '(' || c == ')' || c == ';') break; read_property(); } if (! m_read_only_main_variation || m_is_in_main_variation) on_end_node(); } void Reader::read_property() { if (m_read_only_main_variation && ! m_is_in_main_variation) { while (peek() != '[') read_char(); while (peek() == '[') { consume_char('['); bool escape = false; while (peek() != ']' || escape) { char c = read_char(); if (c == '\\' && ! escape) { escape = true; continue; } escape = false; } consume_char(']'); consume_whitespace(); } } else { m_id.clear(); while (peek() != '[') m_id += read_char(); m_values.clear(); while (peek() == '[') { consume_char('['); m_value.clear(); bool escape = false; while (peek() != ']' || escape) { char c = read_char(); if (c == '\\' && ! escape) { escape = true; continue; } escape = false; m_value += c; } consume_char(']'); consume_whitespace(); m_values.push_back(m_value); } on_property(m_id, m_values); } } void Reader::read_tree(bool is_root) { read_expected('('); on_begin_tree(is_root); bool was_root = is_root; while (true) { consume_whitespace(); char c = peek(); if (c == ')') break; else if (c == ';') { read_node(is_root); is_root = false; } else if (c == '(') read_tree(false); else throw ReadError("Extra text before node"); } read_expected(')'); m_is_in_main_variation = false; on_end_tree(was_root); } //----------------------------------------------------------------------------- } // namespace libboardgame_sgf pentobi-7.2/src/libboardgame_sgf/Reader.h000066400000000000000000000054311227240712600204410ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/Reader.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_SGF_READER_H #define LIBBOARDGAME_SGF_READER_H #include #include #include #include "libboardgame_util/Exception.h" namespace libboardgame_sgf { using namespace std; using libboardgame_util::Exception; //----------------------------------------------------------------------------- class Reader { public: class ReadError : public Exception { public: ReadError(const string& s); }; Reader(); virtual ~Reader() throw(); virtual void on_begin_tree(bool is_root); virtual void on_end_tree(bool is_root); virtual void on_begin_node(bool is_root); virtual void on_end_node(); virtual void on_property(const string& id, const vector& values); /** Read only the main variation. Reduces CPU time and memory if only the main variation is needed. */ void set_read_only_main_variation(bool enable); /** Read a game tree from the file. @param in @param check_single_tree Throw an error if non-whitespace characters follow after the tree before the end of the stream. This is mainly useful to ensure that the input is not a SGF file with multiple game trees if the caller does not want to handle this case. If check_single_tree is false, you can call read() multiple times to read all game trees. @param[out] more_game_trees_left set to true if check_single_tree is false and there are more game trees to read. @throws ReadError */ void read(istream& in, bool check_single_tree = true, bool* more_game_trees_left = nullptr); /** See read(istream&,bool) */ void read(const string& file); private: bool m_read_only_main_variation; bool m_is_in_main_variation; istream* m_in; /** Local variable in read_property(). Reused for efficiency. */ string m_id; /** Local variable in read_property(). Reused for efficiency. */ string m_value; /** Local variable in read_property(). Reused for efficiency. */ vector m_values; void consume_char(char expected); void consume_whitespace(); char peek(); char read_char(); void read_expected(char expected); void read_node(bool is_root); void read_property(); void read_tree(bool is_root); }; inline void Reader::set_read_only_main_variation(bool enable) { m_read_only_main_variation = enable; } //----------------------------------------------------------------------------- } // namespace libboardgame_sgf #endif // LIBBOARDGAME_SGF_READER_H pentobi-7.2/src/libboardgame_sgf/Tree.cpp000066400000000000000000000123121227240712600204650ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/Tree.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Tree.h" #include #include #include #include "libboardgame_sgf/Util.h" namespace libboardgame_sgf { using libboardgame_sgf::util::find_root; using libboardgame_sgf::util::get_go_point_property_value; using libboardgame_sgf::util::parse_go_move_property_value; using libboardgame_sgf::util::parse_go_point_property_value; //----------------------------------------------------------------------------- Tree::Tree() { init(); } Tree::~Tree() { } bool Tree::contains(const Node& node) const { return &find_root(node) == &get_root(); } const Node& Tree::create_new_child(const Node& node) { m_modified = true; return non_const(node).create_new_child(); } void Tree::delete_all_variations() { auto node = &get_root(); while (node != nullptr) { non_const(*node).delete_variations(); node = node->get_first_child_or_null(); } } string Tree::get_comment(const Node& node) const { return node.get_property("C", ""); } string Tree::get_date_today() { time_t t = time(nullptr); auto tmp = localtime(&t); if (tmp == nullptr) return "?"; char date[128]; strftime(date, sizeof(date), "%Y-%m-%d", tmp); return date; } unique_ptr Tree::get_tree_transfer_ownership() { return move(m_root); } bool Tree::has_variations() const { auto node = m_root.get(); while (node != nullptr) { if (node->get_sibling() != nullptr) return true; node = node->get_first_child_or_null(); } return false; } void Tree::init() { unique_ptr root(new Node()); m_root = move(root); m_modified = false; } void Tree::init(unique_ptr& root) { m_root = move(root); m_modified = false; } void Tree::make_first_child(const Node& node) { auto parent = node.get_parent_or_null(); if (parent != nullptr && &parent->get_first_child() != &node) { non_const(node).make_first_child(); m_modified = true; } } void Tree::make_main_variation(const Node& node) { auto current = &non_const(node); while (current->has_parent()) { make_first_child(*current); current = ¤t->get_parent(); } } void Tree::make_root(const Node& node) { if (&node == &get_root()) return; LIBBOARDGAME_ASSERT(contains(node)); auto& parent = node.get_parent(); unique_ptr new_root = non_const(parent).remove_child(non_const(node)); m_root = move(new_root); m_modified = true; } bool Tree::move_property_to_front(const Node& node, const string& id) { PropertyIterator first(node); if (first && (*first).id != id && node.has_property(id)) m_modified = true; return non_const(node).move_property_to_front(id); } void Tree::move_down(const Node& node) { if (node.get_sibling()) { non_const(node).move_down(); m_modified = true; } } void Tree::move_up(const Node& node) { auto parent = node.get_parent_or_null(); if (parent != nullptr && &parent->get_first_child() != &node) { non_const(node).move_up(); m_modified = true; } } void Tree::remove_move_annotation(const Node& node) { remove_property(node, "BM"); remove_property(node, "DO"); remove_property(node, "IT"); remove_property(node, "TE"); } bool Tree::remove_property(const Node& node, const string& id) { bool prop_existed = non_const(node).remove_property(id); if (prop_existed) m_modified = true; return prop_existed; } void Tree::set_application(const string& name, const string& version) { if (version.empty()) set_property(get_root(), "AP", name); else set_property(get_root(), "AP", name + ":" + version); } void Tree::set_property(const Node& node, const string& id, const char* value) { bool was_changed = non_const(node).set_property(id, value); if (was_changed) m_modified = true; } void Tree::set_bad_move(const Node& node, double value) { remove_move_annotation(node); set_property(node, "BM", value); } void Tree::set_comment(const Node& node, const string& s) { string trimmed = trim(s); if (trimmed.empty()) remove_property(node, "C"); else set_property(node, "C", s); } void Tree::set_date_today() { set_date(get_date_today()); } void Tree::set_doubtful_move(const Node& node) { remove_move_annotation(node); set_property(node, "DO", ""); } void Tree::set_good_move(const Node& node, double value) { remove_move_annotation(node); set_property(node, "TE", value); } void Tree::set_interesting_move(const Node& node) { remove_move_annotation(node); set_property(node, "IT", ""); } const Node& Tree::truncate(const Node& node) { LIBBOARDGAME_ASSERT(node.has_parent()); auto& parent = node.get_parent(); non_const(parent).remove_child(non_const(node)); m_modified = true; return parent; } //----------------------------------------------------------------------------- } // namespace libboardgame_sgf pentobi-7.2/src/libboardgame_sgf/Tree.h000066400000000000000000000172371227240712600201450ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/Tree.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_SGF_TREE_H #define LIBBOARDGAME_SGF_TREE_H #include "libboardgame_sgf/Node.h" #include "libboardgame_util/StringUtil.h" namespace libboardgame_sgf { using namespace std; using libboardgame_util::trim; //----------------------------------------------------------------------------- /** SGF tree. Tree structure of the tree can only be manipulated through member functions to guarantee a consistent tree structure. Therefore the user is given only const references to nodes and non-const functions of nodes can only be called through wrapper functions of the tree (in which case the user passes in a const reference to the node as an identifier for the node). */ class Tree { public: Tree(); virtual ~Tree(); virtual void init(); /** Initialize from an existing SGF tree. @param root The root node of the SGF tree; the ownership is transfered to this class. */ virtual void init(unique_ptr& root); /** Get the root node and transfer the ownership to the caller. */ unique_ptr get_tree_transfer_ownership(); /** Check if the tree was modified since the construction or the last call to init() or clear_modified() */ bool is_modified() const; void set_modified(); void clear_modified(); const Node& get_root() const; const Node& create_new_child(const Node& node); /** Truncate a node and its subtree from the tree. Calling this function deletes the node that is to be truncated and its complete subtree. @pre node.has_parent() @param node The node to be truncated. @return The parent of the truncated node. */ const Node& truncate(const Node& node); /** Delete all variations but the main variation. */ void delete_all_variations(); /** Make a node the first child of its parent. */ void make_first_child(const Node& node); /** Make a node switch place with its previous sibling (if it is not already the first child). */ void move_up(const Node& node); /** Make a node switch place with its next sibling (if it is not already the last child). */ void move_down(const Node& node); /** Make a node the root node of the tree. All nodes that are not the given node or in the subtree below it are deleted. Note that this operation in general creates a semantically invalid tree (e.g. missing GM or CA property in the new root). You need to add those after this function. In general, you will also have to examine the nodes in the path to the node in the original tree and then make the tree valid again after calling make_root(). Typically, you will have to look at the moves played before this node and convert them into setup properties to add to the new root such that the board position at this node is the same as originally. */ void make_root(const Node& node); void make_main_variation(const Node& node); bool contains(const Node& node) const; template void set_property(const Node& node, const string& id, const T& value); void set_property(const Node& node, const string& id, const char* value); template void set_property(const Node& node, const string& id, const vector& values); bool remove_property(const Node& node, const string& id); bool move_property_to_front(const Node& node, const string& id); /** See Node::remove_children() */ unique_ptr remove_children(const Node& node); void append(const Node& node, unique_ptr child); /** Get comment. @return The comment, or an empty string if the node contains no comment. */ string get_comment(const Node& node) const; void set_comment(const Node& node, const string& s); void remove_move_annotation(const Node& node); double get_good_move(const Node& node) const; void set_good_move(const Node& node, double value = 1); double get_bad_move(const Node& node) const; void set_bad_move(const Node& node, double value = 1); bool is_doubtful_move(const Node& node) const; void set_doubtful_move(const Node& node); bool is_interesting_move(const Node& node) const; void set_interesting_move(const Node& node); void set_charset(const string& charset); void set_application(const string& name, const string& version = ""); string get_date() const; void set_date(const string& date); /** Get today's date in format YYYY-MM-DD as required by DT property. */ static string get_date_today(); void set_date_today(); string get_event() const; void set_event(const string& event); string get_round() const; void set_round(const string& date); string get_time() const; void set_time(const string& time); bool has_variations() const; private: bool m_modified; unique_ptr m_root; Node& non_const(const Node& node); }; inline void Tree::append(const Node& node, unique_ptr child) { if (child) m_modified = true; non_const(node).append(move(child)); } inline void Tree::clear_modified() { m_modified = false; } inline double Tree::get_bad_move(const Node& node) const { return node.parse_property("BM", 0); } inline string Tree::get_date() const { return m_root->get_property("DT", ""); } inline string Tree::get_event() const { return m_root->get_property("EV", ""); } inline double Tree::get_good_move(const Node& node) const { return node.parse_property("TE", 0); } inline bool Tree::is_modified() const { return m_modified; } inline string Tree::get_round() const { return m_root->get_property("RO", ""); } inline const Node& Tree::get_root() const { return *m_root; } inline string Tree::get_time() const { return m_root->get_property("TM", ""); } inline bool Tree::is_doubtful_move(const Node& node) const { return node.has_property("DO"); } inline bool Tree::is_interesting_move(const Node& node) const { return node.has_property("IT"); } inline Node& Tree::non_const(const Node& node) { LIBBOARDGAME_ASSERT(contains(node)); return const_cast(node); } inline unique_ptr Tree::remove_children(const Node& node) { if (node.has_children()) m_modified = true; return non_const(node).remove_children(); } inline void Tree::set_charset(const string& charset) { set_property(get_root(), "CA", charset); } inline void Tree::set_date(const string& date) { set_property(get_root(), "DT", date); } inline void Tree::set_event(const string& event) { set_property(get_root(), "EV", event); } inline void Tree::set_modified() { m_modified = true; } template void Tree::set_property(const Node& node, const string& id, const T& value) { bool was_changed = non_const(node).set_property(id, value); if (was_changed) m_modified = true; } template void Tree::set_property(const Node& node, const string& id, const vector& values) { bool was_changed = non_const(node).set_property(id, values); if (was_changed) m_modified = true; } inline void Tree::set_round(const string& round) { set_property(get_root(), "RO", round); } inline void Tree::set_time(const string& time) { set_property(get_root(), "TM", time); } //----------------------------------------------------------------------------- } // namespace libboardgame_sgf #endif // LIBBOARDGAME_SGF_TREE_H pentobi-7.2/src/libboardgame_sgf/TreeIterator.cpp000066400000000000000000000020731227240712600222020ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/TreeIterator.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "TreeIterator.h" namespace libboardgame_sgf { //----------------------------------------------------------------------------- void TreeIterator::operator++() { LIBBOARDGAME_ASSERT(operator bool()); auto first_child = m_current->get_first_child_or_null(); if (first_child != nullptr) { m_current = first_child; return; } if (m_current == &m_root) { m_current = nullptr; return; } while (m_current->get_sibling() == nullptr) { m_current = &m_current->get_parent(); if (m_current == &m_root) { m_current = nullptr; return; } } m_current = m_current->get_sibling(); } //----------------------------------------------------------------------------- } // namespace libboardgame_sgf pentobi-7.2/src/libboardgame_sgf/TreeIterator.h000066400000000000000000000021051227240712600216430ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/TreeIterator.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_SGF_TREE_ITERATOR_H #define LIBBOARDGAME_SGF_TREE_ITERATOR_H #include "Node.h" namespace libboardgame_sgf { //----------------------------------------------------------------------------- class TreeIterator { public: TreeIterator(const Node& root); operator bool() const; const Node& operator*() const; void operator++(); private: const Node& m_root; const Node* m_current; }; inline TreeIterator::TreeIterator(const Node& root) : m_root(root), m_current(&root) { } inline TreeIterator::operator bool() const { return m_current != nullptr; } inline const Node& TreeIterator::operator*() const { LIBBOARDGAME_ASSERT(operator bool()); return *m_current; } //----------------------------------------------------------------------------- } // namespace libboardgame_sgf #endif // LIBBOARDGAME_SGF_TREE_ITERATOR_H pentobi-7.2/src/libboardgame_sgf/TreeReader.cpp000066400000000000000000000026001227240712600216070ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/TreeReader.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "TreeReader.h" namespace libboardgame_sgf { using namespace std; //----------------------------------------------------------------------------- TreeReader::TreeReader() : m_current(nullptr) { } TreeReader::~TreeReader() throw() { } unique_ptr TreeReader::get_tree_transfer_ownership() { return move(m_root); } void TreeReader::on_begin_tree(bool is_root) { if (! is_root) m_stack.push(m_current); } void TreeReader::on_end_tree(bool is_root) { if (! is_root) { LIBBOARDGAME_ASSERT(! m_stack.empty()); m_current = m_stack.top(); m_stack.pop(); } } void TreeReader::on_begin_node(bool is_root) { if (is_root) { m_root.reset(new Node()); m_current = m_root.get(); } else m_current = &m_current->create_new_child(); } void TreeReader::on_end_node() { } void TreeReader::on_property(const string& identifier, const vector& values) { m_current->set_property(identifier, values); } //----------------------------------------------------------------------------- } // namespace libboardgame_sgf pentobi-7.2/src/libboardgame_sgf/TreeReader.h000066400000000000000000000025041227240712600212570ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/TreeReader.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_SGF_TREE_READER_H #define LIBBOARDGAME_SGF_TREE_READER_H #include #include #include "Reader.h" #include "Node.h" namespace libboardgame_sgf { using namespace std; //----------------------------------------------------------------------------- class TreeReader : public Reader { public: TreeReader(); ~TreeReader() throw(); void on_begin_tree(bool is_root) override; void on_end_tree(bool is_root) override; void on_begin_node(bool is_root) override; void on_end_node() override; void on_property(const string& identifier, const vector& values) override; const Node& get_tree() const; /** Get the tree and transfer the ownership to the caller. */ unique_ptr get_tree_transfer_ownership(); private: Node* m_current; unique_ptr m_root; stack m_stack; }; inline const Node& TreeReader::get_tree() const { return *m_root.get(); } //----------------------------------------------------------------------------- } // namespace libboardgame_sgf #endif // LIBBOARDGAME_SGF_TREE_READER_H pentobi-7.2/src/libboardgame_sgf/TreeWriter.cpp000066400000000000000000000025031227240712600216630ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/TreeWriter.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "TreeWriter.h" namespace libboardgame_sgf { //----------------------------------------------------------------------------- TreeWriter::TreeWriter(ostream& out, const Node& root) : m_root(root), m_writer(out) { } TreeWriter::~TreeWriter() { } void TreeWriter::write() { m_writer.begin_tree(); write_node(m_root); m_writer.end_tree(); } void TreeWriter::write_node(const Node& node) { m_writer.begin_node(); for (PropertyIterator i(node); i; ++i) write_property(i->id, i->values); m_writer.end_node(); if (! node.has_children()) return; else if (node.has_single_child()) write_node(node.get_child()); else for (ChildIterator i(node); i; ++i) { m_writer.begin_tree(); write_node(*i); m_writer.end_tree(); } } void TreeWriter::write_property(const string& id, const vector& values) { m_writer.write_property(id, values); } //----------------------------------------------------------------------------- } // namespace libboardgame_sgf pentobi-7.2/src/libboardgame_sgf/TreeWriter.h000066400000000000000000000032071227240712600213320ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/TreeWriter.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_SGF_TREE_WRITER_H #define LIBBOARDGAME_SGF_TREE_WRITER_H #include "Node.h" #include "Writer.h" namespace libboardgame_sgf { //----------------------------------------------------------------------------- class TreeWriter { public: TreeWriter(ostream& out, const Node& root); virtual ~TreeWriter(); /** Overridable function to write a property. Can be used in subclasses, for example, to replace or remove obsolete properties or do other sanitizing. */ virtual void write_property(const string& id, const vector& values); /** @name Formatting options. Should be set before starting to write. */ // @{ void set_one_prop_per_line(bool enable); void set_one_prop_value_per_line(bool enable); void set_indent(unsigned indent); // @} // @name void write(); private: const Node& m_root; Writer m_writer; void write_node(const Node& node); }; inline void TreeWriter::set_one_prop_per_line(bool enable) { m_writer.set_one_prop_per_line(enable); } inline void TreeWriter::set_one_prop_value_per_line(bool enable) { m_writer.set_one_prop_value_per_line(enable); } inline void TreeWriter::set_indent(unsigned indent) { m_writer.set_indent(indent); } //----------------------------------------------------------------------------- } // namespace libboardgame_sgf #endif // LIBBOARDGAME_SGF_TREE_WRITER_H pentobi-7.2/src/libboardgame_sgf/Util.cpp000066400000000000000000000110251227240712600205030ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/Util.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Util.h" #include #include #include #include "InvalidPropertyValue.h" #include "Node.h" #include "TreeWriter.h" #include "libboardgame_util/StringUtil.h" namespace libboardgame_sgf { namespace util { using namespace std; using libboardgame_util::get_letter_coord; //----------------------------------------------------------------------------- const Node& back_to_main_variation(const Node& node) { if (is_main_variation(node)) return node; auto current = &node; while (! is_main_variation(*current)) current = ¤t->get_parent(); return current->get_first_child(); } const Node& beginning_of_branch(const Node& node) { auto current = node.get_parent_or_null(); if (current == nullptr) return node; while (true) { auto parent = current->get_parent_or_null(); if (parent == nullptr || ! parent->has_single_child()) break; current = parent; } return *current; } const Node* find_next_comment(const Node& node) { auto current = get_next_node(node); while (current != nullptr) { if (has_comment(*current)) return current; current = get_next_node(*current); } return nullptr; } const Node& find_root(const Node& node) { auto current = &node; while (current->has_parent()) current = ¤t->get_parent(); return *current; } string get_go_point_property_value(int x, int y, int sz) { ostringstream result; result << char('a' + x); result << char('a' + (sz - y - 1)); return result.str(); } const Node& get_last_node(const Node& node) { auto n = &node; while (n->has_children()) n = &n->get_first_child(); return *n; } unsigned get_depth(const Node& node) { unsigned depth = 0; auto current = &node; while (current->has_parent()) { current = ¤t->get_parent(); ++depth; } return depth; } const Node* get_next_earlier_variation(const Node& node) { auto child = &node; auto current = node.get_parent_or_null(); while (current != nullptr && child->get_sibling() == nullptr) { child = current; current = current->get_parent_or_null(); } if (current == nullptr) return nullptr; return child->get_sibling(); } const Node* get_next_node(const Node& node) { auto child = node.get_first_child_or_null(); if (child != nullptr) return child; return get_next_earlier_variation(node); } void get_path_from_root(const Node& node, vector& path) { auto current = &node; path.assign(1, current); while(current->has_parent()) { current = ¤t->get_parent(); path.push_back(current); } reverse(path.begin(), path.end()); } string get_variation_string(const Node& node) { string result; auto current = &node; unsigned depth = get_depth(*current); while (current->has_parent()) { auto& parent = current->get_parent(); if (parent.get_nu_children() > 1) { unsigned index = parent.get_child_index(*current); if (index > 0) { ostringstream s; s << depth << get_letter_coord(index); if (! result.empty()) s << '-' << result; result = s.str(); } } current = &parent; --depth; } return result; } bool has_comment(const Node& node) { return node.has_property("C"); } bool has_earlier_variation(const Node& node) { auto current = node.get_parent_or_null(); if (current == nullptr) return false; while (true) { auto parent = current->get_parent_or_null(); if (parent == nullptr) return false; if (! parent->has_single_child()) return true; current = parent; } } bool is_main_variation(const Node& node) { auto current = &node; while (current->has_parent()) { auto& parent = current->get_parent(); if (current != &parent.get_first_child()) return false; current = &parent; } return true; } //----------------------------------------------------------------------------- } // namespace util } // namespace libboardgame_sgf pentobi-7.2/src/libboardgame_sgf/Util.h000066400000000000000000000060151227240712600201530ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/Util.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_SGF_UTIL_H #define LIBBOARDGAME_SGF_UTIL_H #include #include #include "Node.h" namespace libboardgame_sgf { namespace util { using namespace std; //----------------------------------------------------------------------------- /** Return the last node in the current variation that had a sibling. */ const Node& beginning_of_branch(const Node& node); /** Find next node with a comment in the iteration through complete tree. @param node The current node in the iteration. @return The next node in the iteration through the complete tree after the current node that has a comment. */ const Node* find_next_comment(const Node& node); const Node& find_root(const Node& node); /** Get the depth of a node. The root node has depth 0. */ unsigned get_depth(const Node& node); /** Get list of nodes from root to a target node. @param node The target node. @param[out] path The list of nodes. */ void get_path_from_root(const Node& node, vector& path); const Node& get_last_node(const Node& node); /** Get next node for iteration through complete tree. */ const Node* get_next_node(const Node& node); /** Return next variation before this node. */ const Node* get_next_earlier_variation(const Node& node); /** Get a text representation of the variation of a certain node. The variation string is a sequence of X.Y for each branching into a variation that is not the first child since the root node separated by commas, with X being the depth of the child node (starting at 0, and therefore equivalent to the move number if there are no non-root nodes without moves) and Y being the number of the child (starting at 1). */ string get_variation_string(const Node& node); /** Check if any previous node had a sibling. */ bool has_earlier_variation(const Node& node); bool is_main_variation(const Node& node); const Node& back_to_main_variation(const Node& node); /** Construct a property value as used for points in Go. */ string get_go_point_property_value(int x, int y, int sz); /** Parse a property value as used for points in Go. @param s The property value @param[out] x The x coordinate or -1, if the value is a pass move encoding compatible with FF[3] ("tt" for boards sizes less or equal 19). @param[out] y The y coordinate or -1, if the value is a pass move encoding compatible with FF[3] ("tt" for boards sizes less or equal 19). @param sz The board size @throws InvalidPropertyValue */ void parse_go_move_property_value(const string& s, int& x, int& y, int sz); void parse_go_point_property_value(const string& s, int& x, int& y, int sz); bool has_comment(const Node& node); //----------------------------------------------------------------------------- } // namespace util } // namespace libboardgame_sgf #endif // LIBBOARDGAME_SGF_UTIL_H pentobi-7.2/src/libboardgame_sgf/Writer.cpp000066400000000000000000000034611227240712600210470ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/Writer.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Writer.h" #include #include namespace libboardgame_sgf { using namespace std; //----------------------------------------------------------------------------- Writer::Writer(ostream& out) : m_out(out), m_one_prop_per_line(false), m_one_prop_value_per_line(false), m_indent(0), m_current_indent(0), m_level(0) { } Writer::~Writer() throw() { } void Writer::begin_node() { m_is_first_prop = true; write_indent(); m_out << ';'; } void Writer::begin_tree() { write_indent(); m_out << '('; // Don't indent the first level if (m_level > 0) m_current_indent += m_indent; ++m_level; if (m_one_prop_per_line) m_out << '\n'; } void Writer::end_node() { if (m_one_prop_per_line && m_is_first_prop) m_out << '\n'; } void Writer::end_tree() { --m_level; if (m_level > 0) m_current_indent -= m_indent; write_indent(); m_out << ")\n"; } string Writer::get_escaped(const string& s) { ostringstream buffer; for (char c : s) { if (c == ']' || c == '\\') buffer << '\\' << c; else if (c == '\t' || c == '\f' || c == '\v') // Replace whitespace as required by the SGF standard. buffer << ' '; else buffer << c; } return buffer.str(); } void Writer::write_indent() { for (unsigned i = 0; i < m_current_indent; ++i) m_out << ' '; } //----------------------------------------------------------------------------- } // namespace libboardgame_sgf pentobi-7.2/src/libboardgame_sgf/Writer.h000066400000000000000000000057131227240712600205160ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sgf/Writer.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_SGF_WRITER_H #define LIBBOARDGAME_SGF_WRITER_H #include #include #include #include #include "libboardgame_util/StringUtil.h" namespace libboardgame_sgf { using namespace std; using libboardgame_util::to_string; //----------------------------------------------------------------------------- class Writer { public: Writer(ostream& out); ~Writer() throw(); /** @name Formatting options. Should be set before starting to write. */ // @{ void set_one_prop_per_line(bool enable); void set_one_prop_value_per_line(bool enable); void set_indent(unsigned indent); // @} // @name void begin_tree(); void end_tree(); void begin_node(); void end_node(); void write_property(const string& id, const char* value); template void write_property(const string& id, const T& value); template void write_property(const string& id, const vector& values); private: ostream& m_out; bool m_one_prop_per_line; bool m_one_prop_value_per_line; bool m_is_first_prop; unsigned m_indent; unsigned m_current_indent; unsigned m_level; static string get_escaped(const string& s); void write_indent(); }; inline void Writer::set_one_prop_per_line(bool enable) { m_one_prop_per_line = enable; } inline void Writer::set_one_prop_value_per_line(bool enable) { m_one_prop_value_per_line = enable; } inline void Writer::set_indent(unsigned indent) { m_indent = indent; } inline void Writer::write_property(const string& id, const char* value) { vector values(1, value); write_property(id, values); } template void Writer::write_property(const string& id, const T& value) { vector values(1, value); write_property(id, values); } template void Writer::write_property(const string& id, const vector& values) { if (m_one_prop_per_line && ! m_is_first_prop) { write_indent(); m_out << ' '; } m_out << id; bool is_first_value = true; for (const T& i : values) { if (m_one_prop_per_line && m_one_prop_value_per_line && ! is_first_value) { m_out << '\n'; unsigned indent = static_cast(m_current_indent + 1 + id.size()); for (unsigned i = 0; i < indent; ++i) m_out << ' '; } m_out << '[' << get_escaped(to_string(i)) << ']'; is_first_value = false; } if (m_one_prop_per_line) m_out << '\n'; m_is_first_prop = false; } //----------------------------------------------------------------------------- } // namespace libboardgame_sgf #endif // LIBBOARDGAME_SGF_WRITER_H pentobi-7.2/src/libboardgame_sys/000077500000000000000000000000001227240712600171225ustar00rootroot00000000000000pentobi-7.2/src/libboardgame_sys/CMakeLists.txt000066400000000000000000000003031227240712600216560ustar00rootroot00000000000000include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) set(boardgame_sys_STAT_SRCS CpuTime.cpp Memory.cpp ) add_library(boardgame_sys STATIC ${boardgame_sys_STAT_SRCS}) pentobi-7.2/src/libboardgame_sys/Compiler.h000066400000000000000000000025741227240712600210550ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sys/Compiler.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_SYS_COMPILER_H #define LIBBOARDGAME_SYS_COMPILER_H #include #ifdef __GNUC__ #include #include #endif namespace libboardgame_sys { using namespace std; //----------------------------------------------------------------------------- #ifdef __GNUC__ #define LIBBOARDGAME_FORCE_INLINE inline __attribute__((always_inline)) #elif defined _MSC_VER #define LIBBOARDGAME_FORCE_INLINE inline __forceinline #else #define LIBBOARDGAME_FORCE_INLINE inline #endif #if defined __GNUC__ && ! defined __ICC && ! defined __clang__ && \ (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) #define LIBBOARDGAME_FLATTEN __attribute__((flatten)) #else #define LIBBOARDGAME_FLATTEN #endif template string get_type_name(const T& t) { #ifdef __GNUC__ int status; char* name_ptr = abi::__cxa_demangle(typeid(t).name(), 0, 0, &status); if (status == 0) { string result(name_ptr); free(name_ptr); return result; } #endif return typeid(t).name(); } //----------------------------------------------------------------------------- } // namespace libboardgame_sys #endif // LIBBOARDGAME_SYS_COMPILER_H pentobi-7.2/src/libboardgame_sys/CpuTime.cpp000066400000000000000000000027351227240712600212030ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sys/CpuTime.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "CpuTime.h" #ifdef _WIN32 #include #endif #if HAVE_UNISTD_H #include #endif #if HAVE_SYS_TIMES_H #include #endif namespace libboardgame_sys { //----------------------------------------------------------------------------- double cpu_time() { #ifdef _WIN32 FILETIME create; FILETIME exit; FILETIME sys; FILETIME user; if (! GetProcessTimes(GetCurrentProcess(), &create, &exit, &sys, &user)) return -1; ULARGE_INTEGER sys_int; sys_int.LowPart = sys.dwLowDateTime; sys_int.HighPart = sys.dwHighDateTime; ULARGE_INTEGER user_int; user_int.LowPart = user.dwLowDateTime; user_int.HighPart = user.dwHighDateTime; return (sys_int.QuadPart + user_int.QuadPart) * 1e-7; #elif HAVE_UNISTD_H && HAVE_SYS_TIMES_H static double ticks_per_second = double(sysconf(_SC_CLK_TCK)); struct tms buf; if (times(&buf) == clock_t(-1)) return -1; clock_t clock_ticks = buf.tms_utime + buf.tms_stime + buf.tms_cutime + buf.tms_cstime; return double(clock_ticks) / ticks_per_second; #else return -1; #endif } //----------------------------------------------------------------------------- } // namespace libboardgame_sys pentobi-7.2/src/libboardgame_sys/CpuTime.h000066400000000000000000000013141227240712600206400ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sys/CpuTime.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_SYS_CPU_TIME_H #define LIBBOARDGAME_SYS_CPU_TIME_H namespace libboardgame_sys { //----------------------------------------------------------------------------- /** Return the CPU time of the current process. @return The CPU time of the current process in seconds or -1, if the CPU time cannot be determined. */ double cpu_time(); //----------------------------------------------------------------------------- } // namespace libboardgame_sys #endif // LIBBOARDGAME_SYS_CPU_TIME_H pentobi-7.2/src/libboardgame_sys/Memory.cpp000066400000000000000000000034151227240712600211010ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sys/Memory.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Memory.h" #include #ifdef _WIN32 #include #else #include #endif // Don't include sys/sysctl.h on Linux with X32 ABI (__x86_64__ && __ILP32__): // it exists but causes a compilation error (last checked on Ubuntu 13.10) #if HAVE_SYS_SYSCTL_H && ! (defined __x86_64__ && defined __ILP32__) #include #endif namespace libboardgame_sys { using namespace std; //----------------------------------------------------------------------------- size_t get_memory() { #ifdef _WIN32 MEMORYSTATUSEX status; status.dwLength = sizeof(status); if (! GlobalMemoryStatusEx(&status)) return 0; auto total_virtual = static_cast(status.ullTotalVirtual); auto total_phys = static_cast(status.ullTotalPhys); return min(total_virtual, total_phys); #elif defined _SC_PHYS_PAGES long phys_pages = sysconf(_SC_PHYS_PAGES); if (phys_pages < 0) return 0; long page_size = sysconf(_SC_PAGE_SIZE); if (page_size < 0) return 0; return static_cast(phys_pages) * static_cast(page_size); #elif defined HW_PHYSMEM // Mac OS X unsigned int phys_mem; size_t len = sizeof(phys_mem); int name[2] = { CTL_HW, HW_PHYSMEM }; if (sysctl(name, 2, &phys_mem, &len, nullptr, 0) != 0 || len != sizeof(phys_mem)) return 0; else return phys_mem; #else return 0; #endif } //----------------------------------------------------------------------------- } // namespace libboardgame_sys pentobi-7.2/src/libboardgame_sys/Memory.h000066400000000000000000000013261227240712600205450ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_sys/Memory.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_SYS_MEMORY_H #define LIBBOARDGAME_SYS_MEMORY_H #include namespace libboardgame_sys { using namespace std; //----------------------------------------------------------------------------- /** Get the physical memory available on the system. @return The memory in bytes or 0 if the memory could not be determined. */ size_t get_memory(); //----------------------------------------------------------------------------- } // namespace libboardgame_sys #endif // LIBBOARDGAME_SYS_MEMORY_H pentobi-7.2/src/libboardgame_test/000077500000000000000000000000001227240712600172635ustar00rootroot00000000000000pentobi-7.2/src/libboardgame_test/CMakeLists.txt000066400000000000000000000002661227240712600220270ustar00rootroot00000000000000include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) set(boardgame_test_STAT_SRCS Test.cpp ) add_library(boardgame_test STATIC ${boardgame_test_STAT_SRCS}) pentobi-7.2/src/libboardgame_test/Test.cpp000066400000000000000000000057421227240712600207160ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_test/Test.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Test.h" #include #include #include "libboardgame_util/Assert.h" #include "libboardgame_util/Log.h" using libboardgame_util::log; namespace libboardgame_test { using namespace std; //----------------------------------------------------------------------------- namespace { map& get_all_tests() { static map all_tests; return all_tests; } string get_fail_msg(const char* file, int line, const string& s) { ostringstream msg; msg << file << ":" << line << ": " << s; return msg.str(); } } // namespace //----------------------------------------------------------------------------- TestFail::TestFail(const char* file, int line, const string& s) : Exception(get_fail_msg(file, line, s)) { } //----------------------------------------------------------------------------- void add_test(const string& name, TestFunction function) { auto& all_tests = get_all_tests(); LIBBOARDGAME_ASSERT(all_tests.find(name) == all_tests.end()); all_tests.insert(make_pair(name, function)); } bool run_all_tests() { unsigned nu_fail = 0; log() << "Running " << get_all_tests().size() << " tests...\n"; auto all_tests = get_all_tests(); for (auto i = all_tests.begin(); i != all_tests.end(); ++i) { try { (i->second)(); } catch (const TestFail& e) { log() << e.what() << '\n'; ++nu_fail; } } if (nu_fail == 0) { log() << "OK\n"; return true; } else { log() << nu_fail << " tests failed.\n" << "FAIL\n"; return false; } } bool run_test(const string& name) { auto all_tests = get_all_tests(); for (auto i = all_tests.begin(); i != all_tests.end(); ++i) if (i->first == name) { log() << "Running " << name << "...\n"; try { (i->second)(); log() << "OK\n"; return true; } catch (const TestFail& e) { log() << e.what() << '\n' << "FAIL\n"; return false; } } log() << "Test not found: " << name; return false; } int test_main(int argc, char* argv[]) { if (argc < 2) { if (libboardgame_test::run_all_tests()) return 0; else return 1; } else { int result = 0; for (int i = 1; i < argc; ++i) if (! libboardgame_test::run_test(argv[i])) result = 1; return result; } } //----------------------------------------------------------------------------- } // namespace libboardgame_test pentobi-7.2/src/libboardgame_test/Test.h000066400000000000000000000161231227240712600203560ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_test/Test.h Provides functionality similar to Boost.Test. */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_TEST_TEST_H #define LIBBOARDGAME_TEST_TEST_H #include #include #include #include "libboardgame_util/Exception.h" namespace libboardgame_test { using namespace std; using libboardgame_util::Exception; //----------------------------------------------------------------------------- typedef void (*TestFunction)(); //----------------------------------------------------------------------------- class TestFail : public Exception { public: TestFail(const char* file, int line, const string& s); }; //----------------------------------------------------------------------------- void add_test(const string& name, TestFunction function); bool run_all_tests(); bool run_test(const string& name); /** Main function that runs all tests (if no arguments) or only the tests given as arguments. */ int test_main(int argc, char* argv[]); //----------------------------------------------------------------------------- /** Helper class that automatically adds a test when and instance isdeclared. */ struct TestRegistrar { TestRegistrar(const string& name, TestFunction function) { add_test(name, function); } }; //----------------------------------------------------------------------------- } // namespace libboardgame_test //----------------------------------------------------------------------------- #define LIBBOARDGAME_TEST_CASE(name) \ void name(); \ libboardgame_test::TestRegistrar name##_registrar(#name, name); \ void name() #define LIBBOARDGAME_CHECK(expr) \ if (! (expr)) \ throw libboardgame_test::TestFail(__FILE__, __LINE__, "check failed") #define LIBBOARDGAME_CHECK_EQUAL(expr1, expr2) \ { \ using libboardgame_test::TestFail; \ auto result1 = (expr1); \ auto result2 = (expr2); \ if (result1 != result2) \ { \ ostringstream msg; \ msg << "'" << result1 << " != " << "'" << result2 << "'"; \ throw TestFail(__FILE__, __LINE__, msg.str()); \ } \ } #define LIBBOARDGAME_CHECK_THROW(expr, exception) \ { \ using libboardgame_test::TestFail; \ bool was_thrown = false; \ try \ { \ expr; \ } \ catch (const exception&) \ { \ was_thrown = true; \ } \ if (! was_thrown) \ { \ ostringstream msg; \ msg << "Exception '" << #exception << "' was not thrown"; \ throw TestFail(__FILE__, __LINE__, msg.str()); \ } \ } #define LIBBOARDGAME_CHECK_NO_THROW(expr) \ { \ using libboardgame_test::TestFail; \ try \ { \ expr; \ } \ catch (...) \ { \ throw TestFail(__FILE__, __LINE__, \ "Unexcpected exception was thrown"); \ } \ } /** Compare floating points using a tolerance in percent. */ #define LIBBOARDGAME_CHECK_CLOSE(expr1, expr2, tolerance) \ { \ using libboardgame_test::TestFail; \ auto result1 = (expr1); \ auto result2 = (expr2); \ if (fabs(result1 - result2) > 0.01 * tolerance * result1) \ { \ ostringstream msg; \ msg << "Difference between " << result1 << " and " \ << result2 << " exceeds " << (0.01 * tolerance) \ << " percent"; \ throw TestFail(__FILE__, __LINE__, msg.str()); \ } \ } /** Compare floating points using an epsilon. */ #define LIBBOARDGAME_CHECK_CLOSE_EPS(expr1, expr2, epsilon) \ { \ using libboardgame_test::TestFail; \ auto result1 = (expr1); \ auto result2 = (expr2); \ if (fabs(result1 - result2) > epsilon) \ { \ ostringstream msg; \ msg << "Difference between " << result1 << " and " \ << result2 << " exceeds " << epsilon; \ throw TestFail(__FILE__, __LINE__, msg.str()); \ } \ } //----------------------------------------------------------------------------- #endif // LIBBOARDGAME_TEST_TEST_H pentobi-7.2/src/libboardgame_test_main/000077500000000000000000000000001227240712600202675ustar00rootroot00000000000000pentobi-7.2/src/libboardgame_test_main/CMakeLists.txt000066400000000000000000000003051227240712600230250ustar00rootroot00000000000000include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) set(boardgame_test_main_STAT_SRCS Main.cpp ) add_library(boardgame_test_main STATIC ${boardgame_test_main_STAT_SRCS}) pentobi-7.2/src/libboardgame_test_main/Main.cpp000066400000000000000000000007561227240712600216670ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_test_main/Main.cpp */ //----------------------------------------------------------------------------- #include "libboardgame_test/Test.h" //----------------------------------------------------------------------------- int main(int argc, char* argv[]) { return libboardgame_test::test_main(argc, argv); } //---------------------------------------------------------------------------- pentobi-7.2/src/libboardgame_util/000077500000000000000000000000001227240712600172615ustar00rootroot00000000000000pentobi-7.2/src/libboardgame_util/Abort.cpp000066400000000000000000000015561227240712600210430ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/Abort.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Abort.h" #include namespace libboardgame_util { using namespace std; //----------------------------------------------------------------------------- namespace { atomic abort(false); } // namespace //---------------------------------------------------------------------------- void clear_abort() { abort.store(false, memory_order_seq_cst); } bool get_abort() { return abort.load(memory_order_relaxed); } void set_abort() { abort.store(true, memory_order_seq_cst); } //---------------------------------------------------------------------------- } // namespace libboardgame_util pentobi-7.2/src/libboardgame_util/Abort.h000066400000000000000000000012171227240712600205020ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/Abort.h Global flag to interrupt move generation or other commands. */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_ABORT_H #define LIBBOARDGAME_UTIL_ABORT_H namespace libboardgame_util { //----------------------------------------------------------------------------- void set_abort(); void clear_abort(); bool get_abort(); //----------------------------------------------------------------------------- } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_ABORT_H pentobi-7.2/src/libboardgame_util/ArrayList.h000066400000000000000000000177761227240712600213660ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/ArrayList.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_ARRAY_LIST_H #define LIBBOARDGAME_UTIL_ARRAY_LIST_H #include #include #include #include #include "Assert.h" namespace libboardgame_util { using namespace std; //----------------------------------------------------------------------------- /** Array-based list with maximum number of elements. The user is responsible for not inserting more than the maximum number of elements. The elements must be default-constructible. If the size of the list shrinks, the destructor of elements will be not be called immediately. The list contains iterator definitions that are compatible with STL containers. @tparam T The type of the elements @tparam M The maximum number of elements @tparam I The integer type for the array size */ template class ArrayList { public: static_assert(numeric_limits::is_integer, ""); typedef T* iterator; typedef const T* const_iterator; typedef T value_type; static const I max_size = M; ArrayList(); /** Construct list with a single element. */ explicit ArrayList(const T& t); T& operator[](I i); const T& operator[](I i) const; bool operator==(const ArrayList& array_list) const; bool operator!=(const ArrayList& array_list) const; iterator begin(); const_iterator begin() const; iterator end(); const_iterator end() const; T& back(); const T& back() const; I size() const; bool empty() const; const T& pop_back(); void push_back(const T& t); void clear(); void assign(const T& t); /** Change the size of the list. Does not call constructors on new elements if the size grows or destructors of elements if the size shrinks. */ void resize(I size); bool contains(const T& t) const; /** Push back element if not already contained in list. @return @c true if element was not already in list. */ bool include(const T& t); /** Removal of first occurance of value. Preserves the order of elements. @return @c true if value was removed. */ bool remove(const T& t); /** Fast removal of element. Does not preserve the order of elements. The element will be replaced with the last element and the list size decremented. */ void remove_fast(iterator i); /** Fast removal of first occurance of value. Does not preserve the order of elements. If the value is found, it will be replaced with the last element and the list size decremented. @return @c true if value was removed. */ bool remove_fast(const T& t); bool is_permutation(const ArrayList& l) const; private: I m_size; T m_a[max_size]; }; template inline ArrayList::ArrayList() : m_size(0) { } template inline ArrayList::ArrayList(const T& t) { assign(t); } template inline T& ArrayList::operator[](I i) { LIBBOARDGAME_ASSERT(i < m_size); return m_a[i]; } template inline const T& ArrayList::operator[](I i) const { LIBBOARDGAME_ASSERT(i < m_size); return m_a[i]; } template bool ArrayList::operator==(const ArrayList& array_list) const { if (m_size != array_list.m_size) return false; const T* elem_this = m_a; const T* elem_other = array_list.m_a; for (I i = 0; i < m_size; ++i, ++elem_this, ++elem_other) if (*elem_this != *elem_other) return false; return true; } template bool ArrayList::operator!=(const ArrayList& array_list) const { return ! operator==(array_list); } template inline void ArrayList::assign(const T& t) { m_size = 1; m_a[0] = t; } template inline T& ArrayList::back() { LIBBOARDGAME_ASSERT(m_size > 0); return m_a[m_size - 1]; } template inline const T& ArrayList::back() const { LIBBOARDGAME_ASSERT(m_size > 0); return m_a[m_size - 1]; } template inline auto ArrayList::begin() -> iterator { return m_a; } template inline auto ArrayList::begin() const -> const_iterator { return m_a; } template inline void ArrayList::clear() { m_size = 0; } template bool ArrayList::contains(const T& t) const { for (const_iterator i = begin(); i != end(); ++i) if (*i == t) return true; return false; } template inline bool ArrayList::empty() const { return m_size == 0; } template inline auto ArrayList::end() -> iterator { return begin() + m_size; } template inline auto ArrayList::end() const -> const_iterator { return begin() + m_size; } template bool ArrayList::include(const T& t) { iterator i; for (i = begin(); i != end(); ++i) if (*i == t) return false; LIBBOARDGAME_ASSERT(m_size < max_size); *i = t; ++m_size; return true; } template bool ArrayList::is_permutation(const ArrayList& l) const { if (size() != l.size()) return false; ArrayList sorted_this(*this); sort(sorted_this.begin(), sorted_this.end()); ArrayList sorted_other(l); sort(sorted_other.begin(), sorted_other.end()); return equal(sorted_this.begin(), sorted_this.end(), sorted_other.begin()); } template inline const T& ArrayList::pop_back() { LIBBOARDGAME_ASSERT(m_size > 0); return m_a[--m_size]; } template inline void ArrayList::push_back(const T& t) { LIBBOARDGAME_ASSERT(m_size < max_size); m_a[m_size++] = t; } template inline bool ArrayList::remove(const T& t) { T* end = this->end(); for (T* i = m_a; i != end; ++i) if (*i == t) { --end; for ( ; i != end; ++i) *i = *(i + 1); --m_size; return true; } return false; } template inline bool ArrayList::remove_fast(const T& t) { T* end = this->end(); for (T* i = m_a; i != end; ++i) if (*i == t) { remove_fast(i); return true; } return false; } template inline void ArrayList::remove_fast(iterator i) { LIBBOARDGAME_ASSERT(i >= begin()); LIBBOARDGAME_ASSERT(i < end()); --m_size; *i = *(m_a + m_size); } template inline void ArrayList::resize(I size) { LIBBOARDGAME_ASSERT(size <= max_size); m_size = size; } template inline I ArrayList::size() const { return m_size; } //----------------------------------------------------------------------------- template ostream& operator<<(ostream& out, const ArrayList& l) { auto begin = l.begin(); auto end = l.end(); if (begin != end) { out << *begin; for (auto i = begin + 1; i != end; ++i) out << ' ' << *i; } return out; } //----------------------------------------------------------------------------- } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_ARRAY_LIST_H pentobi-7.2/src/libboardgame_util/Assert.cpp000066400000000000000000000041411227240712600212260ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/Assert.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Assert.h" #include #if LIBBOARDGAME_DEBUG #include #include #include #include #include #include "Log.h" #endif namespace libboardgame_util { using namespace std; //----------------------------------------------------------------------------- namespace { list& get_all_handlers() { static list all_handlers; return all_handlers; } } // namespace //---------------------------------------------------------------------------- AssertionHandler::AssertionHandler() { get_all_handlers().push_back(this); } AssertionHandler::~AssertionHandler() throw() { get_all_handlers().remove(this); } //---------------------------------------------------------------------------- #if LIBBOARDGAME_DEBUG void handle_assertion(const char* expression, const char* file, int line) { static bool is_during_handle_assertion = false; static vector additional_assertions; ostringstream o; o << file << ":" << line << ": Assertion '" << expression << "' failed"; string message = o.str(); if (is_during_handle_assertion) { if (find(additional_assertions.begin(), additional_assertions.end(), message) == additional_assertions.end()) additional_assertions.push_back(message); return; } is_during_handle_assertion = true; log(message); for_each(get_all_handlers().begin(), get_all_handlers().end(), mem_fun(&AssertionHandler::run)); if (! additional_assertions.empty()) { log("Assertions triggered during execution of assertion handlers:"); for (const auto& s : additional_assertions) log(s); } abort(); } #endif //----------------------------------------------------------------------------- } // namespace libboardgame_util pentobi-7.2/src/libboardgame_util/Assert.h000066400000000000000000000046101227240712600206740ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/Assert.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_ASSERT_H #define LIBBOARDGAME_UTIL_ASSERT_H namespace libboardgame_util { //----------------------------------------------------------------------------- class AssertionHandler { public: /** Construct and register assertion handler. */ AssertionHandler(); /** Destruct and unregister assertion handler. */ virtual ~AssertionHandler() throw(); virtual void run() = 0; }; void handle_assertion(const char* expression, const char* file, int line); //----------------------------------------------------------------------------- } // namespace libboardgame_util //----------------------------------------------------------------------------- /** @def LIBBOARDGAME_DEBUG Determines if the assertions with LIBBOARDGAME_ASSERT are enabled. LIBBOARDGAME_DEBUG needs to be defined and set to a value that is not zero to enable assertions. If LIBBOARDGAME_DEBUG is not explicitely defined it will default to 1 if either DEBUG or _DEBUG are defined (independent of their value) and 0 otherwise. @warning The definition of LIBBOARDGAME_DEBUG must be consistent in all compilation units because the debug versions of some classes in LibBoardGame have additional members and the program will crash if compilation units are linked that do not agree on the size of objects. */ #ifndef LIBBOARDGAME_DEBUG #if defined(DEBUG) || defined(_DEBUG) #define LIBBOARDGAME_DEBUG 1 #else #define LIBBOARDGAME_DEBUG 0 #endif #endif /** @def LIBBOARDGAME_ASSERT Enhanced assert macro. This macro is similar to the assert macro in the standard library, but it allows to register assertion handlers that are executed before the program is aborted. Assertions are only enabled if LIBBOARDGAME_DEBUG is defined and not zero. */ #if LIBBOARDGAME_DEBUG #define LIBBOARDGAME_ASSERT(expr) \ ((expr) ? (static_cast(0)) \ : libboardgame_util::handle_assertion(#expr, __FILE__, __LINE__)) #else #define LIBBOARDGAME_ASSERT(expr) (static_cast(0)) #endif //----------------------------------------------------------------------------- #endif // LIBBOARDGAME_UTIL_ASSERT_H pentobi-7.2/src/libboardgame_util/Barrier.cpp000066400000000000000000000017421227240712600213570ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/Barrier.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Barrier.h" namespace libboardgame_util { #ifdef USE_BOOST_THREAD using boost::unique_lock; #endif //---------------------------------------------------------------------------- Barrier::Barrier(unsigned count) : m_threshold(count), m_count(count), m_current(0) { LIBBOARDGAME_ASSERT(count > 0); } void Barrier::wait() { unique_lock lock(m_mutex); unsigned current = m_current; if (--m_count == 0) { ++m_current; m_count = m_threshold; m_condition.notify_all(); } else while (current == m_current) m_condition.wait(lock); } //---------------------------------------------------------------------------- } // namespace libboardgame_util pentobi-7.2/src/libboardgame_util/Barrier.h000066400000000000000000000021321227240712600210160ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/Barrier.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_BARRIER_H #define LIBBOARDGAME_UTIL_BARRIER_H #include "Assert.h" #ifdef USE_BOOST_THREAD #include #include #else #include #include #endif namespace libboardgame_util { using namespace std; #ifdef USE_BOOST_THREAD using boost::condition_variable; using boost::mutex; #endif //----------------------------------------------------------------------------- /** Similar to boost::barrier, which does not exist in C++11 */ class Barrier { public: Barrier(unsigned count); void wait(); private: mutex m_mutex; condition_variable m_condition; unsigned m_threshold; unsigned m_count; unsigned m_current; }; //----------------------------------------------------------------------------- } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_BARRIER_H pentobi-7.2/src/libboardgame_util/BitMarker.h000066400000000000000000000066741227240712600213270ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/BitMarker.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_BIT_MARKER_H #define LIBBOARDGAME_UTIL_BIT_MARKER_H #include #include namespace libboardgame_util { using namespace std; //----------------------------------------------------------------------------- /** Bitset to mark objects that are integer wrappers. Similar to std::bitset but the objects are used directly as indexes (for better type safety and to avoid explicit conversions). It also contains additional functionality not present is std::bitset (like clear_all_set_known()). @tparam T The object class. Requires a function T::to_int() and a constant T::range */ template class BitMarker { public: BitMarker(); /** Clear all (slow). */ void clear(); void clear(T t); template void clear(const CONTAINER& container); /** Clear all from a list optimized for the case when we know that the list contains all marked objects in the marker. This function assumes that all objects not in the list are not marked and is faster than clear(CONTAINER). */ template void clear_all_set_known(const CONTAINER& container); /** Clear the word that contains an object. For use cases when the side effect that other objects are also cleared can be ignored (e.g. the implementation of clear_all_set_known()) */ void clear_word(T t); void set(T t); /** Mark all (slow). */ void set_all(); bool operator[](T t) const; private: typedef unsigned Word; typedef unsigned IntType; static const IntType word_bits = numeric_limits::digits; static const IntType array_size = T::range / word_bits + (T::range % word_bits == 0 ? 0 : 1); static IntType get_index(T t); static Word get_mask(T t); Word m_array[array_size]; }; template inline BitMarker::BitMarker() { clear(); } template inline bool BitMarker::operator[](T t) const { return ((m_array[get_index(t)] & get_mask(t)) != 0); } template inline void BitMarker::clear() { memset(m_array, 0, array_size * sizeof(Word)); } template inline void BitMarker::clear(T t) { m_array[get_index(t)] &= ~get_mask(t); } template template inline void BitMarker::clear(const CONTAINER& container) { for (T t : container) clear(t); } template template inline void BitMarker::clear_all_set_known(const CONTAINER& container) { for (T t : container) clear_word(t); } template inline void BitMarker::clear_word(T t) { m_array[get_index(t)] = 0; } template inline auto BitMarker::get_index(T t) -> IntType { return t.to_int() / word_bits; } template inline auto BitMarker::get_mask(T t) -> Word { return static_cast(1) << (t.to_int() % word_bits); } template inline void BitMarker::set(T t) { m_array[get_index(t)] |= get_mask(t); } template inline void BitMarker::set_all() { memset(m_array, ~0, array_size * sizeof(Word)); } //----------------------------------------------------------------------------- } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_BIT_MARKER_H pentobi-7.2/src/libboardgame_util/CMakeLists.txt000066400000000000000000000006311227240712600220210ustar00rootroot00000000000000include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) set(boardgame_util_STAT_SRCS Abort.cpp Assert.cpp Barrier.cpp CpuTime.cpp Exception.cpp FastLog.cpp IntervalChecker.cpp Log.cpp Options.cpp RandomGenerator.cpp StringUtil.cpp TimeIntervalChecker.cpp Timer.cpp TimeSource.cpp WallTime.cpp ) add_library(boardgame_util STATIC ${boardgame_util_STAT_SRCS}) pentobi-7.2/src/libboardgame_util/CpuTime.cpp000066400000000000000000000011461227240712600213350ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/CpuTime.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "CpuTime.h" #include "libboardgame_sys/CpuTime.h" namespace libboardgame_util { //----------------------------------------------------------------------------- double CpuTime::operator()() { return libboardgame_sys::cpu_time(); } //---------------------------------------------------------------------------- } // namespace libboardgame_util pentobi-7.2/src/libboardgame_util/CpuTime.h000066400000000000000000000013261227240712600210020ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/CpuTime.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_CPU_TIME_H #define LIBBOARDGAME_UTIL_CPU_TIME_H #include "TimeSource.h" namespace libboardgame_util { //----------------------------------------------------------------------------- /** CPU time. @ref libboardgame_doc_threadsafe_after_construction */ class CpuTime : public TimeSource { public: double operator()() override; }; //----------------------------------------------------------------------------- } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_CPU_TIME_H pentobi-7.2/src/libboardgame_util/Exception.cpp000066400000000000000000000012371227240712600217260ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/Exception.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Exception.h" namespace libboardgame_util { //----------------------------------------------------------------------------- Exception::Exception(const string& s) : m_s(s) { } Exception::~Exception() throw() { } const char* Exception::what() const throw() { return m_s.c_str(); } //----------------------------------------------------------------------------- } // namespace libboardgame_util pentobi-7.2/src/libboardgame_util/Exception.h000066400000000000000000000014441227240712600213730ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/Exception.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_EXCEPTION_H #define LIBBOARDGAME_UTIL_EXCEPTION_H #include #include namespace libboardgame_util { using namespace std; //----------------------------------------------------------------------------- class Exception : public exception { public: Exception(const string& s); virtual ~Exception() throw(); const char* what() const throw() override; private: string m_s; }; //----------------------------------------------------------------------------- } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_EXCEPTION_H pentobi-7.2/src/libboardgame_util/FastLog.cpp000066400000000000000000000020401227240712600213200ustar00rootroot00000000000000//---------------------------------------------------------------------------- /** @file libboardgame_util/FastLog.cpp */ //---------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "FastLog.h" #include #include namespace libboardgame_util { using namespace std; //---------------------------------------------------------------------------- static_assert(numeric_limits::is_iec559, ""); FastLog::FastLog(int mantissa_bits) : m_mantissa_bits_diff(max_mantissa_bits - mantissa_bits), m_table(new float[1 << mantissa_bits]) { IntFloat x; x.m_int = 0x3F800000; int incr = (1 << m_mantissa_bits_diff); int p = static_cast(pow(2.0f, mantissa_bits)); float inv_log_2 = 1.f / log(2.f); for (int i = 0; i < p; ++i) { m_table[i] = log(x.m_float) * inv_log_2; x.m_int += incr; } } //---------------------------------------------------------------------------- } // namespace libboardgame_util pentobi-7.2/src/libboardgame_util/FastLog.h000066400000000000000000000031711227240712600207730ustar00rootroot00000000000000//---------------------------------------------------------------------------- /** @file libboardgame_util/FastLog.h */ //---------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_FAST_LOG_H #define LIBBOARDGAME_UTIL_FAST_LOG_H #include #include namespace libboardgame_util { using namespace std; //----------------------------------------------------------------------------- /** Fast logarithm. Computes a fast single precision logarithm based on a lookup table. O. Vinyals, G. Friedland, N. Mirghafori: Revisiting a basic function on current CPUs: A fast logarithm implementation with adjustable accuracy. http://www.icsi.berkeley.edu/pubs/techreports/TR-07-002.pdf */ class FastLog { public: FastLog(int mantissa_bits); /** Get natural logarithm. */ float get_log(float val) const; private: union IntFloat { int32_t m_int; float m_float; }; static const int max_mantissa_bits = 23; const int m_mantissa_bits_diff; unique_ptr m_table; /** Not implemented. */ FastLog(const FastLog&); /** Not implemented. */ FastLog& operator=(const FastLog&); }; inline float FastLog::get_log(float val) const { IntFloat x; x.m_float = val; int32_t log_2 = ((x.m_int >> max_mantissa_bits) & 255) - 127; x.m_int &= 0x7FFFFF; x.m_int >>= m_mantissa_bits_diff; return ((m_table[x.m_int] + float(log_2)) * 0.69314718f); } //----------------------------------------------------------------------------- } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_FAST_LOG_H pentobi-7.2/src/libboardgame_util/FmtSaver.h000066400000000000000000000016331227240712600211640ustar00rootroot00000000000000//---------------------------------------------------------------------------- /** @file libboardgame_util/FmtSaver.h */ //---------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_FMT_SAVER_H #define LIBBOARDGAME_UTIL_FMT_SAVER_H #include #include namespace libboardgame_util { using namespace std; //----------------------------------------------------------------------------- /** Saves the formatting state of a stream and restores it in its destructor. */ class FmtSaver { public: FmtSaver(ostream& out) : m_out(out) { m_dummy.copyfmt(out); } ~FmtSaver() { m_out.copyfmt(m_dummy); } private: ostream& m_out; fstream m_dummy; }; //---------------------------------------------------------------------------- } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_FMT_SAVER_H pentobi-7.2/src/libboardgame_util/IntervalChecker.cpp000066400000000000000000000060631227240712600230430ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/IntervalChecker.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "IntervalChecker.h" #include #include "Assert.h" #if LIBBOARDGAME_UTIL_INTERVAL_CHECKER_DEBUG #include "Log.h" #endif namespace libboardgame_util { using namespace std; //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_INTERVAL_CHECKER_DEBUG #define LIBBOARDGAME_UTIL_INTERVAL_CHECKER_DEBUG 0 #endif //----------------------------------------------------------------------------- IntervalChecker::IntervalChecker(TimeSource& time_source, double time_interval, function f) : m_time_source(time_source), m_is_first_check(true), m_is_deterministic(false), m_result(false), m_count(1), m_count_interval(1), m_time_interval(time_interval), m_function(f) { #if LIBBOARDGAME_UTIL_INTERVAL_CHECKER_DEBUG log(format("IntervalChecker::IntervalChecker: time_interval=%1%") % time_interval); #endif LIBBOARDGAME_ASSERT(time_interval > 0); } bool IntervalChecker::check_expensive() { if (m_result) return true; if (m_is_deterministic) { m_result = m_function(); m_count = m_count_interval; return m_result; } double time = m_time_source(); if (! m_is_first_check) { double diff = time - m_last_time; double adjust_factor; if (diff == 0) adjust_factor = 10; else { adjust_factor = m_time_interval / diff; if (adjust_factor > 10) adjust_factor = 10; else if (adjust_factor < 0.1) adjust_factor = 0.1; } double new_count_interval = adjust_factor * double(m_count_interval); if (new_count_interval > double(numeric_limits::max())) m_count_interval = numeric_limits::max(); else if (new_count_interval < 1) m_count_interval = 1; else m_count_interval = (unsigned)(new_count_interval); m_result = m_function(); #if LIBBOARDGAME_UTIL_INTERVAL_CHECKER_DEBUG log(format("IntervalChecker::check_expensive: " "diff=%1% adjust_factor=%2% count_interval=%3%") % diff % adjust_factor % m_count_interval); #endif } else { #if LIBBOARDGAME_UTIL_INTERVAL_CHECKER_DEBUG log("IntervalChecker::check_expensive: is_first_check"); #endif m_is_first_check = false; } m_last_time = time; m_count = m_count_interval; return m_result; } void IntervalChecker::set_deterministic(unsigned interval) { LIBBOARDGAME_ASSERT(interval >= 1); m_is_deterministic = true; m_count = interval; m_count_interval = interval; } //----------------------------------------------------------------------------- } // namespace libboardgame_util pentobi-7.2/src/libboardgame_util/IntervalChecker.h000066400000000000000000000042641227240712600225110ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/IntervalChecker.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_INTERVAL_CHECKER_H #define LIBBOARDGAME_UTIL_INTERVAL_CHECKER_H #include #include "libboardgame_util/TimeSource.h" namespace libboardgame_util { using namespace std; //----------------------------------------------------------------------------- /** Reduces regular calls to an expensive function to a given time interval. The class assumes that its check() function is called in regular time intervals and forwards only every n'th call to the expensive function with n being adjusted dynamically to a given time interval. check() returns true, if the expensive function was called and returned true in the past. */ class IntervalChecker { public: /** Constructor. @param time_source (@ref libboardgame_doc_storesref) @param time_interval The time interval in seconds @param f The expensive function */ IntervalChecker(TimeSource& time_source, double time_interval, function f); bool operator()(); /** Disable the dynamic updating of the interval. Can be used if the non-reproducability of the time measurement used for dynamic updating of the check interval is undesirable. @param interval The fixed interval (number of calls) to use for calling the expensive function. (Must be greater zero). */ void set_deterministic(unsigned interval); protected: TimeSource& m_time_source; private: bool m_is_first_check; bool m_is_deterministic; bool m_result; unsigned m_count; unsigned m_count_interval; double m_time_interval; double m_last_time; function m_function; bool check_expensive(); }; inline bool IntervalChecker::operator()() { if (--m_count == 0) return check_expensive(); else return m_result; } //----------------------------------------------------------------------------- } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_INTERVAL_CHECKER_H pentobi-7.2/src/libboardgame_util/Log.cpp000066400000000000000000000017601227240712600205120ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/Log.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Log.h" #include #include namespace libboardgame_util { using namespace std; //----------------------------------------------------------------------------- namespace { ostream* log_stream = &cerr; /** Unopened file stream serves as null stream. */ ofstream null_stream; } // namespace //----------------------------------------------------------------------------- ostream& log() { return *log_stream; } void log(const string& s) { string line = s; line += '\n'; log() << line; } void set_log(ostream& out) { log_stream = &out; } void set_log_null() { set_log(null_stream); } //----------------------------------------------------------------------------- } // namespace libboardgame_util pentobi-7.2/src/libboardgame_util/Log.h000066400000000000000000000012471227240712600201570ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/Log.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_LOG_H #define LIBBOARDGAME_UTIL_LOG_H #include #include namespace libboardgame_util { using namespace std; //----------------------------------------------------------------------------- ostream& log(); void log(const string& s); void set_log(ostream& out); void set_log_null(); //----------------------------------------------------------------------------- } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_LOG_H pentobi-7.2/src/libboardgame_util/MathUtil.h000066400000000000000000000015731227240712600211670ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/MathUtil.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_MATH_UTIL_H #define LIBBOARDGAME_UTIL_MATH_UTIL_H #include namespace libboardgame_util { namespace math_util { using namespace std; //----------------------------------------------------------------------------- /** Same as std::round(), which is not available in MSVC10. */ inline float round(float val) { return floor(val + 0.5f); } /** Same as std::round(), which is not available in MSVC10. */ inline double round(double val) { return floor(val + 0.5); } //----------------------------------------------------------------------------- } // namespace math_util } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_MATH_UTIL_H pentobi-7.2/src/libboardgame_util/NullElement.h000066400000000000000000000017551227240712600216660ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/NullElement.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_NULL_ELEMENT_H #define LIBBOARDGAME_UTIL_NULL_ELEMENT_H #include "Assert.h" namespace libboardgame_util { using namespace std; //----------------------------------------------------------------------------- template inline void set_null(T& t) { t = T(0); } /** Test if object is null. The default implementation compares the element against an element constructed with 0 as an argument. This function needs to be specialized for other types (e.g. using Point::is_null() for libboardgame_base::Point) */ template inline bool is_null(const T& t) { return t == T(0); } //----------------------------------------------------------------------------- } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_NULL_ELEMENT_H pentobi-7.2/src/libboardgame_util/NullTermList.h000066400000000000000000000072171227240712600220370ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/NullTermList.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_NULL_TERM_LIST_H #define LIBBOARDGAME_UTIL_NULL_TERM_LIST_H #include "Assert.h" #include "NullElement.h" namespace libboardgame_util { using namespace std; //----------------------------------------------------------------------------- /** Array-based list terminated with a null element. Fast construction and iteration. */ template class NullTermList { friend class Init; friend class Iterator; public: class Init { public: Init(NullTermList& list); void push_back(const T& t); bool include(NullTermList& list, const T& t); void finish(); private: T* m_t; #if LIBBOARDGAME_DEBUG NullTermList* m_list; #endif }; class Iterator { public: Iterator(const NullTermList& list); const T& operator*() const; operator bool() const; Iterator& operator++(); private: const T* m_t; }; static const unsigned max_size = M; NullTermList(); unsigned size() const; bool empty() const; bool contains(const T& t) const; private: T m_a[max_size + 1]; #if LIBBOARDGAME_DEBUG bool m_is_initialized; #endif }; template inline NullTermList::Init::Init(NullTermList& list) : m_t(list.m_a) { #if LIBBOARDGAME_DEBUG m_list = &list; list.m_is_initialized = false; #endif } template inline void NullTermList::Init::push_back(const T& t) { LIBBOARDGAME_ASSERT(m_t - m_list->m_a < max_size); *m_t = t; ++m_t; } template inline void NullTermList::Init::finish() { set_null(*m_t); #if LIBBOARDGAME_DEBUG m_list->m_is_initialized = true; #endif } template bool NullTermList::Init::include(NullTermList& list, const T& t) { for (T* i = list.m_a; i != m_t; ++i) if (*i == t) return false; push_back(t); return true; } template inline NullTermList::Iterator::Iterator(const NullTermList& list) : m_t(list.m_a) { LIBBOARDGAME_ASSERT(list.m_is_initialized); } template inline const T& NullTermList::Iterator::operator*() const { LIBBOARDGAME_ASSERT(operator bool()); return *m_t; } template inline NullTermList::Iterator::operator bool() const { return ! is_null(*m_t); } template inline auto NullTermList::Iterator::operator++() -> Iterator& { LIBBOARDGAME_ASSERT(operator bool()); ++m_t; return *this; } template inline NullTermList::NullTermList() { #if LIBBOARDGAME_DEBUG m_is_initialized = false; #endif } template inline bool NullTermList::contains(const T& t) const { LIBBOARDGAME_ASSERT(m_is_initialized); for (Iterator i(*this); i; ++i) if (*i == t) return true; return false; } template inline bool NullTermList::empty() const { return is_null(m_a[0]); } template inline unsigned NullTermList::size() const { LIBBOARDGAME_ASSERT(m_is_initialized); unsigned n = 0; for (Iterator i(*this); i; ++i) ++n; return n; } //----------------------------------------------------------------------------- } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_NULL_TERM_LIST_H pentobi-7.2/src/libboardgame_util/Options.cpp000066400000000000000000000112661227240712600214260ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/Options.h */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Options.h" namespace libboardgame_util { //---------------------------------------------------------------------------- Options::Options(int argc, const char** argv, const vector& specs) { init(argc, argv, specs); } Options::Options(int argc, char** argv, const vector& specs) { init(argc, const_cast(argv), specs); } void Options::init(int argc, const char** argv, const vector& specs) { for (const auto& s : specs) { auto pos = s.find("|"); if (pos == string::npos) pos = s.find(":"); if (pos != string::npos) m_names.insert(s.substr(0, pos)); else m_names.insert(s); } bool end_of_options = false; for (int n = 1; n < argc; ++n) { const string arg = argv[n]; if (! end_of_options && arg.find("-") == 0 && arg != "-") { if (arg == "--") { end_of_options = true; continue; } string name; string value; bool needs_arg = false; if (arg.find("--") == 0) { // Long option name = arg.substr(2); auto sz = name.size(); bool found = false; for (auto& spec : specs) if (spec.find(name) == 0 && (spec.size() == sz || spec[sz] == '|' || spec[sz] == ':' )) { found = true; needs_arg = (! spec.empty() && spec.back() == ':'); break; } if (! found) throw OptionError("Unknown option " + arg); } else { // Short options for (unsigned i = 1; i < arg.size(); ++i) { auto c = arg[i]; bool found = false; for (auto& spec : specs) { auto pos = spec.find("|" + string(1, c)); if (pos != string::npos) { name = spec.substr(0, pos); found = true; if (! spec.empty() && spec.back() == ':') { // If not last option, no space was used to // append the value if (i != arg.size() - 1) value = arg.substr(i + 1); else needs_arg = true; } break; } } if (! found) throw OptionError("Unknown option -" + string(1, c)); if (needs_arg || ! value.empty()) break; m_map.insert(make_pair(name, "")); } } if (needs_arg) { bool value_found = false; ++n; if (n < argc) { value = argv[n]; if (value.find("-") != 0) value_found = true; } if (! value_found) throw OptionError("Option --" + name + " needs value"); } m_map.insert(make_pair(name, value)); } else m_args.push_back(arg); } } void Options::check_name(const string& name) const { if (m_names.find(name) == m_names.end()) throw OptionError("Internal error: invalid option name " + name); } string Options::get(const string& name) const { check_name(name); auto pos = m_map.find(name); if (pos == m_map.end()) throw OptionError("Missing option --" + name); return pos->second; } string Options::get(const string& name, const string& default_value) const { check_name(name); auto pos = m_map.find(name); if (pos == m_map.end()) return default_value; return pos->second; } string Options::get(const string& name, const char* default_value) const { return get(name, string(default_value)); } //---------------------------------------------------------------------------- } // namespace libboardgame_util pentobi-7.2/src/libboardgame_util/Options.h000066400000000000000000000074771227240712600211040ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/Options.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_OPTIONS_H #define LIBBOARDGAME_UTIL_OPTIONS_H #include #include #include #include #include "Exception.h" #include "StringUtil.h" #include "libboardgame_sys/Compiler.h" namespace libboardgame_util { using namespace std; using libboardgame_sys::get_type_name; //---------------------------------------------------------------------------- class OptionError : public Exception { public: OptionError(const string& s) : Exception(s) { } }; //---------------------------------------------------------------------------- /** Parser for command line options. The syntax of options is similar to GNU getopt. Options start with "--" and an option name. Options have optional short (single-character) names that are used with a single "-" and can be combined if all but the last option have no value. A single "--" stops option parsing to support non-option arguments that start with "-". */ class Options { public: /** Create options from arguments to main(). @param argc @param argv @param specs A string per option that describes the option. The description is the long name of the option, followed by and optional '|' and a character for the short name of the option, followed by an optional ':' if the option needs a value. @throws OptionError on error */ Options(int argc, const char** argv, const vector& specs); /** Overloaded version for con-const character strings in argv. Needed because the portable signature of main is (int, char**). argv is not modified by this constructor. */ Options(int argc, char** argv, const vector& specs); /** Check if an option exists in the command line arguments. @param name The (long) option name. */ bool contains(const string& name) const; string get(const string& name) const; string get(const string& name, const string& default_value) const; string get(const string& name, const char* default_value) const; /** Get option value. @param name The (long) option name. @throws OptionError If option does not exist or has the wrong type. */ template T get(const string& name) const; /** Get option value or default value. @param name The (long) option name. @return The option value or the default value if the option does not exist. */ template T get(const string& name, const T& default_value) const; /** Remaining command line arguments that are not an option or an option value. */ const vector& get_args() const; private: set m_names; vector m_args; map m_map; void check_name(const string& name) const; void init(int argc, const char** argv, const vector& specs); }; inline bool Options::contains(const string& name) const { check_name(name); return m_map.find(name) != m_map.end(); } template T Options::get(const string& name) const { T t; if (! from_string(get(name), t)) throw OptionError("Option --" + name + " needs type " + get_type_name(t)); return t; } template T Options::get(const string& name, const T& default_value) const { if (! contains(name)) return default_value; return get(name); } inline const vector& Options::get_args() const { return m_args; } //---------------------------------------------------------------------------- } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_OPTIONS_H pentobi-7.2/src/libboardgame_util/RandomGenerator.cpp000066400000000000000000000034531227240712600230610ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/RandomGenerator.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "RandomGenerator.h" #include #include namespace libboardgame_util { using namespace std; //---------------------------------------------------------------------------- namespace { bool is_seed_set = false; RandomGenerator::ResultType the_seed; list& get_all_generators() { static list all_generators; return all_generators; } RandomGenerator::ResultType get_nondet_seed() { // std::random_device should be used instead of std::time() in the future // but random_device is still broken on MinGW GCC 4.7.2 (it always throws) static RandomGenerator::Generator seed_generator; auto seed = static_cast(time(nullptr)); seed ^= seed_generator(); return seed; } } // namespace //----------------------------------------------------------------------------- RandomGenerator::RandomGenerator() { set_seed(is_seed_set ? the_seed : get_nondet_seed()); get_all_generators().push_back(this); } RandomGenerator::~RandomGenerator() throw() { get_all_generators().remove(this); } void RandomGenerator::set_global_seed(ResultType seed) { is_seed_set = true; the_seed = seed; for (RandomGenerator* i : get_all_generators()) i->set_seed(the_seed); } void RandomGenerator::set_global_seed_last() { if (is_seed_set) for (RandomGenerator* i : get_all_generators()) i->set_seed(the_seed); } //----------------------------------------------------------------------------- } // namespace libboardgame_util pentobi-7.2/src/libboardgame_util/RandomGenerator.h000066400000000000000000000101001227240712600225110ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/RandomGenerator.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_RANDOM_GENERATOR_H #define LIBBOARDGAME_UTIL_RANDOM_GENERATOR_H #include #include #include "Assert.h" // Use fast SIMD-based random generator if GCC. // This is a non-standard extension of GCC >=4.8 and so far only available on // little-endian systems (last checked with GCC 4.8.2) #if defined __GNUC__ \ && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) \ && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #define LIBBOARDGAME_USE_SIMD_MERSENNE_TWISTER #endif #ifdef LIBBOARDGAME_USE_SIMD_MERSENNE_TWISTER #include #endif namespace libboardgame_util { using namespace std; //----------------------------------------------------------------------------- /** Fast pseudo-random number generator. All instances of this class register themselves automatically at a global list of random generators, such that the random seed can be changed at all existing generators with a single function call. (@ref libboardgame_doc_threadsafe_after_construction) */ class RandomGenerator { public: /** @typedef Generator This generator is the same as mt11213b in the Boost library. According to the documentation of Boost 1.52, this generator is a bit faster and uses less memory than mt19937. (The typedef mt11213b does not exist in C++11.) */ #ifdef LIBBOARDGAME_USE_SIMD_MERSENNE_TWISTER typedef __gnu_cxx::sfmt11213 Generator; #else typedef mersenne_twister_engine Generator; #endif typedef Generator::result_type ResultType; /** Constructor. Constructs the random generator with the global seed, if one was defined, otherwise with a non-deterministic seed. */ RandomGenerator(); ~RandomGenerator() throw(); void set_seed(ResultType seed); ResultType generate(); /** Generate a small random number. Fast way to generate a small integer that avoids an expensive modulo operation. Uses only the lower 16 bits of the random generator. @param n The size of the range; must be smaller than 2^16 @return A random number between 0 and n - 1 */ int generate_small_int(int n); /** Like generate_small_int() but for unsigned. */ unsigned generate_small_uint(unsigned n); /** Generate a floating point value in [0..1]. */ double generate_float(); /** Set seed for all currently existing and future generators. If this function is never called, a non-deterministic seed is used. */ static void set_global_seed(ResultType seed); /** Set seed to last seed for all currently existing and future generators. Sets the seed to the last seed that was set with set_seed(). If no seed was explicitely defined with set_seed(), then this function does nothing. */ static void set_global_seed_last(); private: Generator m_generator; uniform_real_distribution m_float_distribution; }; inline RandomGenerator::ResultType RandomGenerator::generate() { return m_generator(); } inline double RandomGenerator::generate_float() { return m_float_distribution(m_generator); } inline int RandomGenerator::generate_small_int(int n) { return static_cast(generate_small_uint(static_cast(n))); } inline unsigned RandomGenerator::generate_small_uint(unsigned n) { LIBBOARDGAME_ASSERT(n < (1 << 16)); auto i = static_cast(((m_generator() & 0xffff) * n) >> 16); LIBBOARDGAME_ASSERT(i < n); return i; } inline void RandomGenerator::set_seed(ResultType seed) { m_generator.seed(seed); } //----------------------------------------------------------------------------- } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_RANDOM_GENERATOR_H pentobi-7.2/src/libboardgame_util/Statistics.h000066400000000000000000000223521227240712600215700ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/Statistics.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_STATISTICS_H #define LIBBOARDGAME_UTIL_STATISTICS_H #include #include #include #include #include #include #include #include "Assert.h" #include "FmtSaver.h" namespace libboardgame_util { using namespace std; //----------------------------------------------------------------------------- template class StatisticsBase { public: /** Constructor. @param init_val The value to return in get_mean() if count is 0. This value does not affect the mean returned if count is greater 0. */ StatisticsBase(FLOAT init_val = 0); void add(FLOAT val); void clear(FLOAT init_val = 0); FLOAT get_count() const; FLOAT get_mean() const; void write(ostream& out, bool fixed = false, unsigned precision = 6) const; private: FLOAT m_count; FLOAT m_mean; }; template inline StatisticsBase::StatisticsBase(FLOAT init_val) { clear(init_val); } template void StatisticsBase::add(FLOAT val) { FLOAT count = m_count; ++count; val -= m_mean; m_mean += val / count; m_count = count; } template inline void StatisticsBase::clear(FLOAT init_val) { m_count = 0; m_mean = init_val; } template inline FLOAT StatisticsBase::get_count() const { return m_count; } template inline FLOAT StatisticsBase::get_mean() const { return m_mean; } template void StatisticsBase::write(ostream& out, bool fixed, unsigned precision) const { FmtSaver saver(out); if (fixed) out << std::fixed; out << setprecision(precision) << m_mean; } //---------------------------------------------------------------------------- template class Statistics { public: Statistics(FLOAT init_val = 0); void add(FLOAT val); void clear(FLOAT init_val = 0); FLOAT get_mean() const; FLOAT get_count() const; FLOAT get_deviation() const; FLOAT get_variance() const; void write(ostream& out, bool fixed = false, unsigned precision = 6) const; private: StatisticsBase m_statistics_base; FLOAT m_variance; }; template inline Statistics::Statistics(FLOAT init_val) { clear(init_val); } template void Statistics::add(FLOAT val) { if (get_count() > 0) { FLOAT count_old = get_count(); FLOAT mean_old = get_mean(); m_statistics_base.add(val); FLOAT mean = get_mean(); FLOAT count = get_count(); m_variance = (count_old * (m_variance + mean_old * mean_old) + val * val) / count - mean * mean; } else { m_statistics_base.add(val); m_variance = 0; } } template inline void Statistics::clear(FLOAT init_val) { m_statistics_base.clear(init_val); m_variance = 0; } template inline FLOAT Statistics::get_count() const { return m_statistics_base.get_count(); } template inline FLOAT Statistics::get_deviation() const { return sqrt(m_variance); } template inline FLOAT Statistics::get_mean() const { return m_statistics_base.get_mean(); } template inline FLOAT Statistics::get_variance() const { return m_variance; } template void Statistics::write(ostream& out, bool fixed, unsigned precision) const { FmtSaver saver(out); if (fixed) out << std::fixed; out << setprecision(precision) << get_mean() << " dev=" << get_deviation(); } //---------------------------------------------------------------------------- template class StatisticsExt { public: StatisticsExt(FLOAT init_val = 0); void add(FLOAT val); void clear(FLOAT init_val = 0); FLOAT get_mean() const; FLOAT get_count() const; FLOAT get_max() const; FLOAT get_min() const; FLOAT get_deviation() const; FLOAT get_variance() const; void write(ostream& out, bool fixed = false, unsigned precision = 6, bool integer_values = false) const; string to_string(bool fixed = false, unsigned precision = 6, bool integer_values = false) const; private: Statistics m_statistics; FLOAT m_max; FLOAT m_min; }; template inline StatisticsExt::StatisticsExt(FLOAT init_val) { clear(init_val); } template void StatisticsExt::add(FLOAT val) { m_statistics.add(val); if (val > m_max) m_max = val; if (val < m_min) m_min = val; } template inline void StatisticsExt::clear(FLOAT init_val) { m_statistics.clear(init_val); m_min = numeric_limits::max(); m_max = -numeric_limits::max(); } template inline FLOAT StatisticsExt::get_count() const { return m_statistics.get_count(); } template inline FLOAT StatisticsExt::get_deviation() const { return m_statistics.get_deviation(); } template inline FLOAT StatisticsExt::get_max() const { return m_max; } template inline FLOAT StatisticsExt::get_mean() const { return m_statistics.get_mean(); } template inline FLOAT StatisticsExt::get_min() const { return m_min; } template inline FLOAT StatisticsExt::get_variance() const { return m_statistics.get_variance(); } template string StatisticsExt::to_string(bool fixed, unsigned precision, bool integer_values) const { ostringstream s; write(s, fixed, precision, integer_values); return s.str(); } template void StatisticsExt::write(ostream& out, bool fixed, unsigned precision, bool integer_values) const { m_statistics.write(out, fixed, precision); FmtSaver saver(out); if (fixed) out << std::fixed; if (integer_values) out << setprecision(0); else out << setprecision(precision); out << " min=" << m_min << " max=" << m_max; } //---------------------------------------------------------------------------- /** Like StatisticsBase, but for lock-free multithreading with potentially lost updates. Updates and accesses of the moving average and the count are atomic but not synchronized and use memory_order_relaxed. Therefore, updates can be lost. Initializing via the constructor, operator= or clear() uses memory_order_seq_cst */ template class StatisticsDirtyLockFree { public: /** Constructor. @param init_val See StatisticBase::StatisticBase() */ StatisticsDirtyLockFree(FLOAT init_val = 0); StatisticsDirtyLockFree& operator=(const StatisticsDirtyLockFree& s); void add(FLOAT val); void clear(FLOAT init_val = 0); FLOAT get_count() const; FLOAT get_mean() const; void write(ostream& out, bool fixed = false, unsigned precision = 6) const; private: atomic m_count; atomic m_mean; }; template inline StatisticsDirtyLockFree::StatisticsDirtyLockFree(FLOAT init_val) { clear(init_val); } template StatisticsDirtyLockFree& StatisticsDirtyLockFree::operator=(const StatisticsDirtyLockFree& s) { m_count = s.m_count.load(); m_mean = s.m_mean.load(); return *this; } template void StatisticsDirtyLockFree::add(FLOAT val) { FLOAT count = m_count.load(memory_order_relaxed); FLOAT mean = m_mean.load(memory_order_relaxed); ++count; mean += (val - mean) / count; m_mean.store(mean, memory_order_relaxed); m_count.store(count, memory_order_relaxed); } template inline void StatisticsDirtyLockFree::clear(FLOAT init_val) { m_count = 0; m_mean = init_val; } template inline FLOAT StatisticsDirtyLockFree::get_count() const { return m_count.load(memory_order_relaxed); } template inline FLOAT StatisticsDirtyLockFree::get_mean() const { return m_mean.load(memory_order_relaxed); } template void StatisticsDirtyLockFree::write(ostream& out, bool fixed, unsigned precision) const { FmtSaver saver(out); if (fixed) out << std::fixed; out << setprecision(precision) << get_mean(); } //---------------------------------------------------------------------------- template inline ostream& operator<<(ostream& out, const StatisticsExt& s) { s.write(out); return out; } //---------------------------------------------------------------------------- } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_STATISTICS_H pentobi-7.2/src/libboardgame_util/StringUtil.cpp000066400000000000000000000044361227240712600221000ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/StringUtil.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "StringUtil.h" #include #include namespace libboardgame_util { //----------------------------------------------------------------------------- template<> bool from_string(const string& s, string& t) { t = s; return true; } string get_letter_coord(unsigned i) { string result; while (true) { result.insert(0, 1, char('a' + i % 26)); i /= 26; if (i == 0) break; --i; } return result; } vector split(const string& s, char separator) { vector result; string current; for (char c : s) { if (c == separator) { result.push_back(current); current.clear(); continue; } current.push_back(c); } if (! current.empty() || ! result.empty()) result.push_back(current); return result; } string time_to_string(double seconds, bool with_seconds_as_double) { int int_seconds = int(seconds + 0.5); int hours = int_seconds / 3600; int_seconds -= hours * 3600; int minutes = int_seconds / 60; int_seconds -= minutes * 60; ostringstream s; s << setfill('0'); if (hours > 0) s << hours << ':'; s << setw(2) << minutes << ':' << setw(2) << int_seconds; if (with_seconds_as_double) s << " (" << seconds << ')'; return s.str(); } string to_lower(const string& s) { string r; r.reserve(s.size()); for (char c : s) r.push_back(static_cast(tolower(c))); return r; } string trim(const string& s) { string::size_type begin = 0; auto end = s.size(); while (begin != end && isspace(s[begin])) ++begin; while (end > begin && isspace(s[end - 1])) --end; return s.substr(begin, end - begin); } string trim_right(const string& s) { auto end = s.size(); while (end > 0 && isspace(s[end - 1])) --end; return s.substr(0, end); } //---------------------------------------------------------------------------- } // namespace libboardgame_util pentobi-7.2/src/libboardgame_util/StringUtil.h000066400000000000000000000025411227240712600215400ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/StringUtil.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_STRING_UTIL_H #define LIBBOARDGAME_UTIL_STRING_UTIL_H #include #include #include namespace libboardgame_util { using namespace std; //----------------------------------------------------------------------------- template bool from_string(const string& s, T& t) { istringstream in(s); in >> t; return ! in.fail(); } template<> bool from_string(const string& s, string& t); /** Get a letter representing a coordinate. Returns 'a' to 'z' for i between 0 and 25 and continues with 'aa','ab'... for coordinates larger than 25. */ string get_letter_coord(unsigned i); vector split(const string& s, char separator); string time_to_string(double seconds, bool with_seconds_as_double = false); template string to_string(const T& t) { ostringstream buffer; buffer << t; return buffer.str(); } string to_lower(const string& s); string trim(const string& s); string trim_right(const string& s); //----------------------------------------------------------------------------- } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_STRING_UTIL_H pentobi-7.2/src/libboardgame_util/TimeIntervalChecker.cpp000066400000000000000000000024441227240712600236610ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file TimeIntervalChecker.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "TimeIntervalChecker.h" namespace libboardgame_util { //----------------------------------------------------------------------------- TimeIntervalChecker::TimeIntervalChecker(TimeSource& time_source, double time_interval, double max_time) : IntervalChecker(time_source, time_interval, bind(&TimeIntervalChecker::check_time, this)), m_max_time(max_time), m_start_time(m_time_source()) { } TimeIntervalChecker::TimeIntervalChecker(TimeSource& time_source, double max_time) : IntervalChecker(time_source, max_time > 1 ? 0.1 : 0.1 * max_time, bind(&TimeIntervalChecker::check_time, this)), m_max_time(max_time), m_start_time(m_time_source()) { } bool TimeIntervalChecker::check_time() { return m_time_source() - m_start_time > m_max_time; } //----------------------------------------------------------------------------- } // namespace libboardgame_util pentobi-7.2/src/libboardgame_util/TimeIntervalChecker.h000066400000000000000000000022231227240712600233210ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file TimeIntervalChecker.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_TIME_INTERVAL_CHECKER_H #define LIBBOARDGAME_UTIL_TIME_INTERVAL_CHECKER_H #include "IntervalChecker.h" namespace libboardgame_util { //----------------------------------------------------------------------------- /** IntervalChecker that checks if a maximum total time was reached. */ class TimeIntervalChecker : public IntervalChecker { public: TimeIntervalChecker(TimeSource& time_source, double time_interval, double max_time); /** Constructor with automatically set time_interval. The time interval will be set to 0.1, if max_time > 1, otherwise to 0.1 * max_time */ TimeIntervalChecker(TimeSource& time_source, double max_time); private: double m_max_time; double m_start_time; bool check_time(); }; //----------------------------------------------------------------------------- } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_TIME_INTERVAL_CHECKER_H pentobi-7.2/src/libboardgame_util/TimeSource.cpp000066400000000000000000000010411227240712600220400ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/TimeSource.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "TimeSource.h" namespace libboardgame_util { //----------------------------------------------------------------------------- TimeSource::~TimeSource() throw() { } //---------------------------------------------------------------------------- } // namespace libboardgame_util pentobi-7.2/src/libboardgame_util/TimeSource.h000066400000000000000000000020671227240712600215160ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/TimeSource.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_TIME_SOURCE_H #define LIBBOARDGAME_UTIL_TIME_SOURCE_H namespace libboardgame_util { //----------------------------------------------------------------------------- /** Abstract time source for measuring thinking times for move generation. Typical implementations are wall time, CPU time or mock time sources for unit tests. They do not need to provide high resolutions (but should support at least 100 ms) and should support maximum times of days (or even months). @ref libboardgame_doc_threadsafe_after_construction */ class TimeSource { public: virtual ~TimeSource() throw(); /** Get the current time in seconds. */ virtual double operator()() = 0; }; //----------------------------------------------------------------------------- } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_TIME_SOURCE_H pentobi-7.2/src/libboardgame_util/Timer.cpp000066400000000000000000000014461227240712600210520ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/Timer.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Timer.h" namespace libboardgame_util { //----------------------------------------------------------------------------- Timer::Timer() : m_time_source(nullptr) { } Timer::Timer(TimeSource& time_source) : m_start(time_source()), m_time_source(&time_source) { } void Timer::reset() { m_start = (*m_time_source)(); } void Timer::reset(TimeSource& time_source) { m_time_source = &time_source; reset(); } //---------------------------------------------------------------------------- } // namespace libboardgame_util pentobi-7.2/src/libboardgame_util/Timer.h000066400000000000000000000021231227240712600205100ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/Timer.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_TIMER_H #define LIBBOARDGAME_UTIL_TIMER_H #include "Assert.h" #include "TimeSource.h" namespace libboardgame_util { class Timer { public: /** Constructor without time source. If constructed without time source, the timer cannot be used befor reset(TimeSource&) was called. */ Timer(); /** Constructor. @param time_source (@ref libboardgame_doc_storesref) */ Timer(TimeSource& time_source); double operator()() const; void reset(); void reset(TimeSource& time_source); private: double m_start; TimeSource* m_time_source; }; inline double Timer::operator()() const { LIBBOARDGAME_ASSERT(m_time_source != nullptr); return (*m_time_source)() - m_start; } //----------------------------------------------------------------------------- } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_TIMER_H pentobi-7.2/src/libboardgame_util/Unused.h000066400000000000000000000012571227240712600207020ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file Unused.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_UNUSED_H #define LIBBOARDGAME_UTIL_UNUSED_H #include "Assert.h" //----------------------------------------------------------------------------- template static void LIBBOARDGAME_UNUSED(const T&) { } #if LIBBOARDGAME_DEBUG #define LIBBOARDGAME_UNUSED_IF_NOT_DEBUG(x) #else #define LIBBOARDGAME_UNUSED_IF_NOT_DEBUG(x) LIBBOARDGAME_UNUSED(x) #endif //----------------------------------------------------------------------------- #endif // LIBBOARDGAME_UTIL_UNUSED_H pentobi-7.2/src/libboardgame_util/WallTime.cpp000066400000000000000000000016251227240712600215070ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/WallTime.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "WallTime.h" namespace libboardgame_util { using namespace std::chrono; //----------------------------------------------------------------------------- WallTime::WallTime() { m_start = system_clock::now(); } double WallTime::operator()() { // Logically, there is no need to return the time since m_start, we could // also use time_since_epoch(), but during debugging it is nicer to // deal with smaller numbers. auto diff = system_clock::now() - m_start; return duration_cast>(diff).count(); } //---------------------------------------------------------------------------- } // namespace libboardgame_util pentobi-7.2/src/libboardgame_util/WallTime.h000066400000000000000000000014741227240712600211560ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libboardgame_util/WallTime.h */ //----------------------------------------------------------------------------- #ifndef LIBBOARDGAME_UTIL_WALL_TIME_H #define LIBBOARDGAME_UTIL_WALL_TIME_H #include #include "TimeSource.h" namespace libboardgame_util { //----------------------------------------------------------------------------- /** Wall time. @ref libboardgame_doc_threadsafe_after_construction */ class WallTime : public TimeSource { public: WallTime(); double operator()() override; private: std::chrono::system_clock::time_point m_start; }; //----------------------------------------------------------------------------- } // namespace libboardgame_util #endif // LIBBOARDGAME_UTIL_WALL_TIME_H pentobi-7.2/src/libpentobi_base/000077500000000000000000000000001227240712600167355ustar00rootroot00000000000000pentobi-7.2/src/libpentobi_base/AdjDiagIterator.h000066400000000000000000000021751227240712600221100ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/AdjDiagIterator.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_ADJ_DIAG_ITERATOR_H #define LIBPENTOBI_BASE_ADJ_DIAG_ITERATOR_H #include "Board.h" #include "libboardgame_util/NullTermList.h" namespace libpentobi_base { using libboardgame_util::NullTermList; //----------------------------------------------------------------------------- class AdjDiagIterator : public NullTermList::Iterator { public: AdjDiagIterator(const Geometry& geometry, Point p); AdjDiagIterator(const Board& bd, Point p); }; inline AdjDiagIterator::AdjDiagIterator(const Geometry& geometry, Point p) : NullTermList::Iterator(geometry.get_adj_diag(p)) { } inline AdjDiagIterator::AdjDiagIterator(const Board& bd, Point p) : NullTermList::Iterator(bd.get_geometry().get_adj_diag(p)) { } //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_ADJ_DIAG_ITERATOR_H pentobi-7.2/src/libpentobi_base/AdjIterator.h000066400000000000000000000021011227240712600213100ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/AdjIterator.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_ADJ_ITERATOR_H #define LIBPENTOBI_BASE_ADJ_ITERATOR_H #include "Board.h" #include "libboardgame_util/NullTermList.h" namespace libpentobi_base { using libboardgame_util::NullTermList; //----------------------------------------------------------------------------- class AdjIterator : public NullTermList::Iterator { public: AdjIterator(const Geometry& geometry, Point p); AdjIterator(const Board& bd, Point p); }; inline AdjIterator::AdjIterator(const Geometry& geometry, Point p) : NullTermList::Iterator(geometry.get_adj(p)) { } inline AdjIterator::AdjIterator(const Board& bd, Point p) : NullTermList::Iterator(bd.get_geometry().get_adj(p)) { } //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_ADJ_ITERATOR_H pentobi-7.2/src/libpentobi_base/Board.cpp000066400000000000000000000444311227240712600204760ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Board.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Board.h" #include #include "libboardgame_util/Unused.h" namespace libpentobi_base { using namespace std; //----------------------------------------------------------------------------- namespace { void write_x_coord(ostream& out, unsigned width, unsigned offset) { for (unsigned i = 0; i < offset; ++i) out << ' '; char c = 'A'; for (unsigned x = 0; x < width; ++x, ++c) { if (x < 26) out << ' '; else out << 'a'; if (x == 26) c = 'A'; out << c; } out << '\n'; } void set_color(ostream& out, const char* esc_sequence) { if (Board::color_output) out << esc_sequence; } } // namespace //----------------------------------------------------------------------------- bool Board::color_output = false; Board::Board(Variant variant) { m_color_char[Color(0)] = 'X'; m_color_char[Color(1)] = 'O'; m_color_char[Color(2)] = '#'; m_color_char[Color(3)] = '@'; init_variant(variant); init(); } void Board::copy_from(const Board& bd) { if (m_variant != bd.m_variant) init_variant(bd.m_variant); m_moves = bd.m_moves; m_setup.to_play = bd.m_setup.to_play; // Note: the following memcpy's are for efficiency but are dangerous // because they might break if the classes change. In the future, this // should be replaced by a normal assignment as soon as C++11 type_traits // and defaulted functions are supported well enough by compilers that // they can figure out themselves if they can use memcpy here. memcpy(&m_state_base, &bd.m_state_base, sizeof(StateBase)); for (ColorIterator i(m_nu_colors); i; ++i) { memcpy(&m_state_color[*i], &bd.m_state_color[*i], sizeof(StateColor)); m_setup.placements[*i] = bd.m_setup.placements[*i]; m_attach_points[*i] = bd.m_attach_points[*i]; } } void Board::gen_moves(Color c, ArrayList& moves) const { moves.clear(); if (is_first_piece(c)) { for (Point p : get_starting_points(c)) if (! m_state_color[c].forbidden[p]) gen_moves(c, p, m_marker, moves); } else { for (Iterator i(*this); i; ++i) if (is_attach_point(*i, c) && ! m_state_color[c].forbidden[*i]) gen_moves(c, *i, get_adj_status(*i, c), m_marker, moves); } m_marker.clear_all_set_known(moves); } void Board::gen_moves(Color c, Point p, MoveMarker& marker, ArrayList& moves) const { for (Piece piece : m_state_color[c].pieces_left) for (Move mv : m_board_const->get_moves(piece, p)) { if (marker[mv]) continue; if (! is_forbidden(c, mv)) { moves.push_back(mv); marker.set(mv); } } } void Board::gen_moves(Color c, Point p, unsigned adj_status, MoveMarker& marker, ArrayList& moves) const { for (Piece piece : m_state_color[c].pieces_left) for (Move mv : m_board_const->get_moves(piece, p, adj_status)) { if (marker[mv]) continue; if (! is_forbidden(c, mv)) { moves.push_back(mv); marker.set(mv); } } } Color Board::get_effective_to_play() const { Color c = m_state_base.to_play; do { if (has_moves(c)) return c; c = get_next(c); } while (c != m_state_base.to_play); return c; } void Board::get_place(Color c, unsigned& place, bool& is_shared) const { array all_scores; for (Color::IntType i = 0; i < Color::range; ++i) all_scores[i] = get_score(Color(i)); int score = all_scores[c.to_int()]; sort(all_scores.begin(), all_scores.begin() + m_nu_players, greater()); is_shared = false; bool found = false; for (unsigned i = 0; i < m_nu_players; ++i) if (all_scores[i] == score) { if (! found) { place = i; found = true; } else is_shared = true; } } bool Board::has_moves(Color c) const { bool is_first = is_first_piece(c); for (Iterator i(*this); i; ++i) if (! m_state_color[c].forbidden[*i] && (is_attach_point(*i, c) || (is_first && get_starting_points(c).contains(*i)))) if (has_moves(c, *i)) return true; return false; } bool Board::has_moves(Color c, Point p) const { for (Piece piece : m_state_color[c].pieces_left) for (Move mv : m_board_const->get_moves(piece, p)) if (! is_forbidden(c, mv)) return true; return false; } void Board::init(Variant variant, const Setup* setup) { if (variant != m_variant) init_variant(variant); // If you make changes here, make sure that you also update copy_from() m_state_base.point_state.fill(PointState::empty()); m_state_base.played_move.fill(Move::null()); if (variant == Variant::junior) m_nu_piece_instances = 2; else m_nu_piece_instances = 1; for (ColorIterator i(m_nu_colors); i; ++i) { m_state_color[*i].forbidden.fill(false); m_state_color[*i].is_attach_point.fill(false); m_attach_points[*i].clear(); m_state_color[*i].pieces_left.clear(); m_state_color[*i].nu_onboard_pieces = 0; m_state_color[*i].points = 0; m_state_color[*i].bonus = 0; for (Piece::IntType j = 0; j < get_nu_uniq_pieces(); ++j) m_state_color[*i].pieces_left.push_back(Piece(j)); m_state_color[*i].nu_left_piece.fill(m_nu_piece_instances); } m_state_base.nu_onboard_pieces_all = 0; if (setup == nullptr) { m_setup.clear(); m_state_base.to_play = Color(0); } else { m_setup = *setup; for (ColorIterator i(m_nu_colors); i; ++i) for (Move mv : setup->placements[*i]) place(*i, mv); m_state_base.to_play = setup->to_play; optimize_attach_point_lists(); for (ColorIterator i(m_nu_colors); i; ++i) if (m_state_color[*i].pieces_left.empty()) m_state_color[*i].bonus = m_bonus_all_pieces; } m_moves.clear(); } void Board::init_variant(Variant variant) { m_variant = variant; m_nu_colors = libpentobi_base::get_nu_colors(variant); if (m_nu_colors == 2) { m_color_name[Color(0)] = "Blue"; m_color_name[Color(1)] = "Green"; m_color_esc_sequence[Color(0)] = "\x1B[1;34;47m"; m_color_esc_sequence[Color(1)] = "\x1B[1;32;47m"; m_color_esc_sequence_text[Color(0)] = "\x1B[1;34m"; m_color_esc_sequence_text[Color(1)] = "\x1B[1;32m"; } else { m_color_name[Color(0)] = "Blue"; m_color_name[Color(1)] = "Yellow"; m_color_name[Color(2)] = "Red"; m_color_name[Color(3)] = "Green"; m_color_esc_sequence[Color(0)] = "\x1B[1;34;47m"; m_color_esc_sequence[Color(1)] = "\x1B[1;33;47m"; m_color_esc_sequence[Color(2)] = "\x1B[1;31;47m"; m_color_esc_sequence[Color(3)] = "\x1B[1;32;47m"; m_color_esc_sequence_text[Color(0)] = "\x1B[1;34m"; m_color_esc_sequence_text[Color(1)] = "\x1B[1;33m"; m_color_esc_sequence_text[Color(2)] = "\x1B[1;31m"; m_color_esc_sequence_text[Color(3)] = "\x1B[1;32m"; } m_nu_players = libpentobi_base::get_nu_players(variant); m_board_const = &BoardConst::get(variant); if (m_variant == Variant::junior) { m_bonus_all_pieces = 0; m_bonus_one_piece = 0; } else { m_bonus_all_pieces = 15; m_bonus_one_piece = 5; } m_geometry = &m_board_const->get_geometry(); m_move_info_array = m_board_const->get_move_info_array(); m_move_info_ext_array = m_board_const->get_move_info_ext_array(); m_move_info_ext_2_array = m_board_const->get_move_info_ext_2_array(); m_starting_points.init(variant, *m_geometry); m_state_base.point_state.init(*m_geometry); m_state_base.played_move.init(*m_geometry); // m_forbidden needs to be initialized even for colors not used in current // game variant because it is written to in some unrolled color loops LIBPENTOBI_FOREACH_COLOR(c, m_state_color[c].forbidden.init(*m_geometry)); for (ColorIterator i(m_nu_colors); i; ++i) { if (variant == Variant::classic_2 || variant == Variant::trigon_2) m_second_color[*i] = get_next(get_next(*i)); else m_second_color[*i] = *i; m_state_color[*i].is_attach_point.init(*m_geometry); } } bool Board::is_game_over() const { for (ColorIterator i(m_nu_colors); i; ++i) if (has_moves(*i)) return false; return true; } /** Remove forbidden points from attach point lists. The attach point lists do not guarantee that they contain only non-forbidden attach points because that would be too expensive to incrementally update but at certain points that are not performance critical (e.g. before taking a snapshot), we can remove them. */ void Board::optimize_attach_point_lists() { for (ColorIterator i(m_nu_colors); i; ++i) { auto& attach_points = m_attach_points[*i]; for (auto j = attach_points.begin(); j != attach_points.end(); ++j) if (is_forbidden(*j, *i)) { m_state_color[*i].is_attach_point[*j] = false; attach_points.remove_fast(j); --j; continue; } } } void Board::take_snapshot() { if (! m_snapshot) m_snapshot.reset(new Snapshot()); optimize_attach_point_lists(); m_snapshot->moves_size = m_moves.size(); // See also the comment in copy_from() about the following memcpy's. memcpy(&m_snapshot->state_base, &m_state_base, sizeof(StateBase)); for (ColorIterator i(m_nu_colors); i; ++i) { m_snapshot->attach_points_size[*i] = m_attach_points[*i].size(); memcpy(&m_snapshot->state_color[*i], &m_state_color[*i], sizeof(StateColor)); } } void Board::undo() { LIBBOARDGAME_ASSERT(get_nu_moves() > 0); ArrayList moves = m_moves; moves.pop_back(); init(); for (unsigned i = 0; i < moves.size(); ++i) play(moves[i]); } void Board::write(ostream& out, bool mark_last_move) const { ColorMove last_mv = ColorMove::null(); if (mark_last_move) { unsigned n = get_nu_moves(); while (n > 0) { --n; last_mv = get_move(n); if (! last_mv.move.is_pass()) break; } } unsigned width = m_geometry->get_width(); unsigned height = m_geometry->get_height(); bool is_info_location_right = (width <= 20); auto board_type = get_board_type(); bool is_trigon = (board_type == BoardType::trigon || board_type == BoardType::trigon_3); write_x_coord(out, width, is_trigon ? 3 : 2); for (unsigned y = height - 1; ; --y) { if (y < 9) out << ' '; out << (y + 1) << ' '; for (unsigned x = 0; x < width; ++x) { Point p(x, y); bool is_offboard = ! is_onboard(p); if ((x > 0 || (is_trigon && x == 0 && is_onboard(p.get_right()))) && ! is_offboard) { // Print a space horizontally between fields on the board. On a // Trigon board, a slash or backslash is used instead of the // space to indicate the orientation of the triangles. A // less-than/greater-than character is used instead of the space // to mark the last piece played (the mark is not placed within // the piece or off-board). if (! last_mv.is_null() && get_played_move(p) == last_mv.move && (x == 0 || ! is_onboard(p.get_left()) || get_point_state(p.get_left()) != last_mv.color)) { set_color(out, "\x1B[1;37;47m"); out << '>'; last_mv = ColorMove::null(); } else if (! last_mv.is_null() && x > 0 && is_onboard(p.get_left()) && get_played_move(p.get_left()) == last_mv.move && get_point_state(p) != last_mv.color && get_point_state(p.get_left()) == last_mv.color) { set_color(out, "\x1B[1;37;47m"); out << '<'; last_mv = ColorMove::null(); } else if (is_trigon) { set_color(out, "\x1B[1;30;47m"); out << (m_geometry->get_point_type(x, y) == 0 ? '\\' : '/'); } else { set_color(out, "\x1B[1;30;47m"); out << ' '; } } if (is_offboard) { if (is_trigon && x > 0 && is_onboard(p.get_left())) { set_color(out, "\x1B[1;30;47m"); out << (m_geometry->get_point_type(p) == 0 ? '\\' : '/'); } else { set_color(out, "\x1B[0m"); out << " "; } } else { PointState s = get_point_state(p); if (s.is_empty()) { if (is_colored_starting_point(p)) { Color c = get_starting_point_color(p); set_color(out, m_color_esc_sequence[c]); out << '+'; } else if (is_colorless_starting_point(p)) { set_color(out, "\x1B[1;30;47m"); out << '+'; } else { set_color(out, "\x1B[1;30;47m"); out << (is_trigon ? ' ' : '.'); } } else { Color color = s.to_color(); set_color(out, m_color_esc_sequence[color]); out << m_color_char[color]; } } } if (is_trigon) { if (is_onboard(Point(width - 1, y))) { set_color(out, "\x1B[1;30;47m"); out << (m_geometry->get_point_type(width - 1, y) != 0 ? '\\' : '/'); } else { set_color(out, "\x1B[0m"); out << " "; } } set_color(out, "\x1B[0m"); out << ' ' << (y + 1); if (is_info_location_right) { if (y < 9) out << " "; else out << " "; write_info_line(out, height - y - 1); } out << '\n'; if (y == 0) break; } write_x_coord(out, width, is_trigon ? 3 : 2); if (! is_info_location_right) for (ColorIterator i(m_nu_colors); i; ++i) { write_color_info_line1(out, *i); out << ' '; write_color_info_line2(out, *i); out << ' '; write_color_info_line3(out, *i); out << '\n'; } } void Board::write_color_info_line1(ostream& out, Color c) const { set_color(out, m_color_esc_sequence_text[c]); if (get_to_play() == c) out << '*'; out << m_color_name[c] << "(" << m_color_char[c] << "): " << get_points(c); unsigned bonus = get_bonus(c); if (bonus > 0) out << " (+" << bonus << ')'; set_color(out, "\x1B[0m"); } void Board::write_color_info_line2(ostream& out, Color c) const { if (m_variant == Variant::junior) write_pieces_left(out, c, 0, 6); else write_pieces_left(out, c, 0, 10); } void Board::write_color_info_line3(ostream& out, Color c) const { if (m_variant == Variant::junior) write_pieces_left(out, c, 6, get_nu_uniq_pieces()); else write_pieces_left(out, c, 10, get_nu_uniq_pieces()); } void Board::write_info_line(ostream& out, unsigned y) const { if (y == 0) write_color_info_line1(out, Color(0)); else if (y == 1) write_color_info_line2(out, Color(0)); else if (y == 2) write_color_info_line3(out, Color(0)); else if (y == 4) write_color_info_line1(out, Color(1)); else if (y == 5) write_color_info_line2(out, Color(1)); else if (y == 6) write_color_info_line3(out, Color(1)); else if (y == 8 && m_nu_colors > 2) write_color_info_line1(out, Color(2)); else if (y == 9 && m_nu_colors > 2) write_color_info_line2(out, Color(2)); else if (y == 10 && m_nu_colors > 2) write_color_info_line3(out, Color(2)); else if (y == 12 && m_nu_colors > 3) write_color_info_line1(out, Color(3)); else if (y == 13 && m_nu_colors > 3) write_color_info_line2(out, Color(3)); else if (y == 14 && m_nu_colors > 3) write_color_info_line3(out, Color(3)); } void Board::write_pieces_left(ostream& out, Color c, unsigned begin, unsigned end) const { for (unsigned i = begin; i < end; ++i) if (i < m_state_color[c].pieces_left.size()) { if (i > begin) out << ' '; Piece piece = m_state_color[c].pieces_left[i]; auto& name = get_piece_info(piece).get_name(); unsigned nu_left = m_state_color[c].nu_left_piece[piece]; for (unsigned j = 0; j < nu_left; ++j) { if (j > 0) out << ' '; out << name; } } } //----------------------------------------------------------------------------- } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/Board.h000066400000000000000000000642031227240712600201420ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Board.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_BOARD_H #define LIBPENTOBI_BASE_BOARD_H #include #include "BoardConst.h" #include "ColorMap.h" #include "ColorMove.h" #include "Variant.h" #include "Geometry.h" #include "Grid.h" #include "MoveMarker.h" #include "PointList.h" #include "PointState.h" #include "Setup.h" #include "StartingPoints.h" #include "libboardgame_util/Unused.h" namespace libpentobi_base { using libboardgame_util::NullTermList; //----------------------------------------------------------------------------- /** Blokus board. @note @ref libboardgame_avoid_stack_allocation */ class Board { public: typedef Grid PointStateGrid; typedef BoardConst::LocalMovesListRange LocalMovesListRange; /** Iterator over all points on the board. */ class Iterator : public GeometryIterator { friend class Board; public: Iterator(const Board& bd); }; /** Maximum number of pieces per player in any game variant. */ static const unsigned max_pieces = Setup::max_pieces; typedef ArrayList PiecesLeftList; /** Maximum number of moves for a player in a game. Non-alternating moves and alternating pass moves are allowed but the game must end after all colors have passed in a row. Therefore, the maximum number of moves is reached in case that a piece move is followed by (Color::range-1) pass moves and an extra Color::range pass moves at the end. */ static const unsigned max_player_moves = Color::range * max_pieces + 1; static const unsigned max_nonpass_player_moves = max_pieces; /** Maximum number of moves in any game variant. Includes an extra pass move per color at the end of the game. */ static const unsigned max_game_moves = Color::range * max_player_moves; /** Maximum number of real (=non-pass) moves in any game variant. */ static const unsigned max_nonpass_game_moves = Color::range * max_nonpass_player_moves; /** Use ANSI escape sequences for colored text output in operator>> */ static bool color_output; Board(Variant variant); Variant get_variant() const; Color::IntType get_nu_colors() const; unsigned get_nu_players() const; Piece::IntType get_nu_uniq_pieces() const; /** Get number of pieces per player in the current game variant. */ unsigned get_nu_pieces() const; /** Number of instances of each unique piece per color. */ unsigned get_nu_piece_instances() const; unsigned get_max_nonpass_player_moves() const; /** Maximum number of real (=non-pass) moves in the current game variant. */ unsigned get_max_nonpass_game_moves() const; Color get_next(Color c) const; Color get_previous(Color c) const; const PieceTransforms& get_transforms() const; /** Get the state of an on-board point. */ PointState get_point_state(Point p) const; bool is_empty(Point p) const; bool is_onboard(Point p) const; const PointStateGrid& get_grid() const; /** Get next color to play. The next color to play is the next color of the color of the last move played even if it has no more moves to play (apart from a pass move). */ Color get_to_play() const; /** Get next color to play that still has moves. Colors are tried in their playing order starting with get_to_play(). If no color has moves left, get_to_play() is returned. */ Color get_effective_to_play() const; const PiecesLeftList& get_pieces_left(Color c) const; bool is_piece_left(Color c, Piece piece) const; /** Check if no piece of a color has been placed on the board yet. This includes setup pieces and played moves. */ bool is_first_piece(Color c) const; /** Get number of instances left of a piece. This value can be greater 1 in game variants that use multiple instances of a unique piece per player. */ unsigned get_nu_left_piece(Color c, Piece piece) const; /** Get number of points on board occupied by a color. */ unsigned get_points(Color c) const; unsigned get_bonus(Color c) const; /** Equivalent to get_points(c) + get_bonus(c) */ unsigned get_points_with_bonus(Color c) const; Move get_played_move(Point p) const; /** Is a point a potential attachment point for a color. Does not check if the point is forbidden. */ bool is_attach_point(Point p, Color c) const; /** Get potential attachment points for a color. Does not check if the point is forbidden. */ const PointList& get_attach_points(Color c) const; /** Initialize the current board for a given game variant. @param variant The game variant @param setup An optional setup position to initialize the board with. */ void init(Variant variant, const Setup* setup = nullptr); /** Clear the current board without changing the current game variant. See init(Variant,const Setup*) */ void init(const Setup* setup = nullptr); /** Copy the board state and move history from another board. This is like an assignment operator but because boards are rarely copied by value and copying is expensive, it is an explicit function to avoid accidental copying. */ void copy_from(const Board& bd); /** Play a move. @pre get_nu_moves() < max_game_moves */ void play(Color c, Move move); /** Play a pass move. @pre get_nu_moves() < max_game_moves */ void play_pass(Color c); /** Play a pass move for the current color to play. @pre get_nu_moves() < max_game_moves */ void play_pass(); /** Play a move that is known to be not a pass move. Slightly faster than play(Color,Move) @pre get_nu_moves() < max_game_moves */ void play_nonpass(Color c, Move move); /** Play a move that is known to be not a pass move for the current color to play. Slightly faster than play(Move) @pre get_nu_moves() < max_game_moves */ void play_nonpass(Move move); /** See play(Color,Move) */ void play(ColorMove move); /** Calls play(Color,Move) with the current color to play. */ void play(Move mv); void undo(); void set_to_play(Color c); void write(ostream& out, bool mark_last_move = true) const; /** Get the setup of the board before any moves were played. If the board was initialized without setup, the return value contains a setup with empty placement lists and Color(0) as the color to play. */ const Setup& get_setup() const; bool has_setup() const; unsigned get_nu_moves() const; /** Get the number of pieces on board. This is the number of setup pieces, if the board was initialized with a setup position, plus the number of pieces played as moves. */ unsigned get_nu_onboard_pieces() const; /** Get the number of pieces on board of a color. This is the number of setup pieces, if the board was initialized with a setup position, plus the number of pieces played as moves. */ unsigned get_nu_onboard_pieces(Color c) const; ColorMove get_move(unsigned n) const; /** Generate all moves for one player. The generated moves do not include the pass move. */ void gen_moves(Color c, ArrayList& moves) const; void gen_moves(Color c, Point p, MoveMarker& marker, ArrayList& moves) const; bool has_moves(Color c) const; /** Check that no color has any moves left. */ bool is_game_over() const; bool is_legal(Color c, Move mv) const; bool is_legal(Move mv) const; /** Check that point is not already occupied or adjacent to own color. @param c @param p The point. Off-board points are allowed and return true. */ bool is_forbidden(Point p, Color c) const; const Grid& is_forbidden(Color c) const; /** Check that no points of move are already occupied or adjacent to own color. Does not check if the move is diagonally adjacent to an existing occupied point of the same color. */ bool is_forbidden(Color c, Move mv) const; const BoardConst& get_board_const() const; BoardType get_board_type() const; unsigned get_adj_status(Point p, Color c) const; LocalMovesListRange get_moves(Piece piece, Point p, unsigned adj_status) const; /** Get score. The score is the number of points for a color minus the number of points of the opponent (or the average score of the opponents if there are more than two players). */ int get_score(Color c) const; /** Get the place of a player in the game result. @param c The color of the player. @param[out] place The place of the player with that color. The place numbers start with 0. A place can be shared if several players have the same score. If a place is shared by n players, the following n-1 places are not used. @param[out] is_shared True if the place was shared. */ void get_place(Color c, unsigned& place, bool& is_shared) const; const Geometry& get_geometry() const; /** See BoardConst::to_string() */ string to_string(Move mv, bool with_piece_name = true) const; /** See BoardConst::from_string() */ Move from_string(const string& s) const; bool find_move(const MovePoints& points, Move& move) const; const PieceInfo& get_piece_info(Piece piece) const; bool get_piece_by_name(const string& name, Piece& piece) const; const MoveInfo& get_move_info(Move mv) const; const MoveInfoExt& get_move_info_ext(Move mv) const; const MoveInfoExt2& get_move_info_ext_2(Move mv) const; bool is_colored_starting_point(Point p) const; bool is_colorless_starting_point(Point p) const; Color get_starting_point_color(Point p) const; const ArrayList& get_starting_points(Color c) const; /** Get the second color in game variants in which a player plays two colors. @return The second color of the player that plays color c, or c if the player plays only one color in the current game variant. */ Color get_second_color(Color c) const; bool is_same_player(Color c1, Color c2) const; /** Remember the board state to quickly restore it later. A snapshot can only be restored from a position that was reached after playing moves from the snapshot position. */ void take_snapshot(); /** See take_snapshot() */ void restore_snapshot(); private: /** Color-independent part of the board state for fast snapshot restoration. Must have only POD-like members such that it can quickly be copied with memcpy in copy_from(). */ struct StateBase { PointStateGrid point_state; Grid played_move; unsigned nu_onboard_pieces_all; Color to_play; }; /** Color-dependent part of the board state for fast snapshot restoration. Must have only POD-like members such that it can quickly be copied with memcpy in copy_from(). */ struct StateColor { Grid forbidden; Grid is_attach_point; PiecesLeftList pieces_left; PieceMap nu_left_piece; unsigned nu_onboard_pieces; unsigned points; unsigned bonus; }; struct Snapshot { unsigned moves_size; ColorMap attach_points_size; StateBase state_base; ColorMap state_color; }; Variant m_variant; Color::IntType m_nu_colors; unsigned m_nu_players; const BoardConst* m_board_const; /** Bonus for playing all pieces. */ unsigned m_bonus_all_pieces; /** Bonus for playing the 1-piece last. */ unsigned m_bonus_one_piece; /** See get_nu_piece_instances() */ unsigned m_nu_piece_instances; /** Same as m_board_const->get_move_info_array() */ const MoveInfo* m_move_info_array; /** Same as m_board_const->get_move_info_ext_array() */ const MoveInfoExt* m_move_info_ext_array; /** Same as m_board_const->get_move_info_ext_2_array() */ const MoveInfoExt2* m_move_info_ext_2_array; const Geometry* m_geometry; StateBase m_state_base; ColorMap m_state_color; ColorMap m_attach_points; /** See get_second_color() */ ColorMap m_second_color; ColorMap m_color_char; ColorMap m_color_esc_sequence; ColorMap m_color_esc_sequence_text; ColorMap m_color_name; ArrayList m_moves; unique_ptr m_snapshot; Setup m_setup; StartingPoints m_starting_points; /** Local variable during move generation. Reused for efficiency. */ mutable MoveMarker m_marker; /** Not to be implemented. Use copy_from() to copy a board state. */ Board(const Board&); /** Not to be implemented. Use copy_from() to copy a board state. */ Board& operator=(const Board&); void gen_moves(Color c, Point p, unsigned adj_status, MoveMarker& marker, ArrayList& moves) const; bool has_moves(Color c, Point p) const; void init_variant(Variant variant); void optimize_attach_point_lists(); void place(Color c, Move mv); void write_pieces_left(ostream& out, Color c, unsigned begin, unsigned end) const; void write_color_info_line1(ostream& out, Color c) const; void write_color_info_line2(ostream& out, Color c) const; void write_color_info_line3(ostream& out, Color c) const; void write_info_line(ostream& out, unsigned y) const; }; typedef Board::Iterator BoardIterator; inline Board::Iterator::Iterator(const Board& bd) : GeometryIterator(*bd.m_geometry) { } inline bool Board::find_move(const MovePoints& points, Move& move) const { return m_board_const->find_move(points, move); } inline Move Board::from_string(const string& s) const { return m_board_const->from_string(s); } inline unsigned Board::get_adj_status(Point p, Color c) const { unsigned result = 0; unsigned val = 1; NullTermList::Iterator i(m_geometry->get_adj_diag(p)); LIBBOARDGAME_ASSERT(i); static_assert(BoardConst::adj_status_nu_adj > 0, ""); do if (is_forbidden(*i, c)) result |= val; while ((val <<= 1) < (1 << BoardConst::adj_status_nu_adj) && ++i); return result; } inline const PointList& Board::get_attach_points(Color c) const { return m_attach_points[c]; } inline const BoardConst& Board::get_board_const() const { return *m_board_const; } inline BoardType Board::get_board_type() const { return m_board_const->get_board_type(); } inline unsigned Board::get_bonus(Color c) const { return m_state_color[c].bonus; } inline const Geometry& Board::get_geometry() const { return *m_geometry; } inline const Board::PointStateGrid& Board::get_grid() const { return m_state_base.point_state; } inline unsigned Board::get_max_nonpass_game_moves() const { return m_nu_colors * get_max_nonpass_player_moves(); } inline unsigned Board::get_max_nonpass_player_moves() const { return get_nu_pieces(); } inline ColorMove Board::get_move(unsigned n) const { return m_moves[n]; } inline const MoveInfo& Board::get_move_info(Move mv) const { LIBBOARDGAME_ASSERT(! mv.is_null()); LIBBOARDGAME_ASSERT(mv.to_int() < m_board_const->get_nu_all_moves()); return *(m_move_info_array + mv.to_int()); } inline const MoveInfoExt& Board::get_move_info_ext(Move mv) const { LIBBOARDGAME_ASSERT(! mv.is_null()); LIBBOARDGAME_ASSERT(mv.to_int() < m_board_const->get_nu_all_moves()); return *(m_move_info_ext_array + mv.to_int()); } inline const MoveInfoExt2& Board::get_move_info_ext_2(Move mv) const { LIBBOARDGAME_ASSERT(! mv.is_null()); LIBBOARDGAME_ASSERT(mv.to_int() < m_board_const->get_nu_all_moves()); return *(m_move_info_ext_2_array + mv.to_int()); } inline Board::LocalMovesListRange Board::get_moves(Piece piece, Point p, unsigned adj_status) const { return m_board_const->get_moves(piece, p, adj_status); } inline Color Board::get_next(Color c) const { return c.get_next(m_nu_colors); } inline Color::IntType Board::get_nu_colors() const { return m_nu_colors; } inline unsigned Board::get_nu_left_piece(Color c, Piece piece) const { LIBBOARDGAME_ASSERT(piece.to_int() < get_nu_uniq_pieces()); return m_state_color[c].nu_left_piece[piece]; } inline unsigned Board::get_nu_moves() const { return m_moves.size(); } inline unsigned Board::get_nu_onboard_pieces() const { return m_state_base.nu_onboard_pieces_all; } inline unsigned Board::get_nu_onboard_pieces(Color c) const { return m_state_color[c].nu_onboard_pieces; } inline unsigned Board::get_nu_players() const { return m_nu_players; } inline unsigned Board::get_nu_piece_instances() const { return m_nu_piece_instances; } inline unsigned Board::get_nu_pieces() const { return m_nu_piece_instances * m_board_const->get_nu_pieces(); } inline Piece::IntType Board::get_nu_uniq_pieces() const { return m_board_const->get_nu_pieces(); } inline const PieceInfo& Board::get_piece_info(Piece piece) const { return m_board_const->get_piece_info(piece); } inline bool Board::get_piece_by_name(const string& name, Piece& piece) const { return m_board_const->get_piece_by_name(name, piece); } inline const Board::PiecesLeftList& Board::get_pieces_left(Color c) const { return m_state_color[c].pieces_left; } inline Move Board::get_played_move(Point p) const { return m_state_base.played_move[p]; } inline PointState Board::get_point_state(Point p) const { LIBBOARDGAME_ASSERT(is_onboard(p)); return PointState(m_state_base.point_state[p].to_int()); } inline unsigned Board::get_points(Color c) const { return m_state_color[c].points; } inline unsigned Board::get_points_with_bonus(Color c) const { return get_points(c) + get_bonus(c); } inline Color Board::get_previous(Color c) const { return c.get_previous(m_nu_colors); } inline int Board::get_score(Color c) const { if (m_nu_colors == 2) { LIBBOARDGAME_ASSERT(m_nu_players == 2); unsigned points0 = get_points_with_bonus(Color(0)); unsigned points1 = get_points_with_bonus(Color(1)); if (c == Color(0)) return points0 - points1; else return points1 - points0; } else if (m_nu_players == 2) { LIBBOARDGAME_ASSERT(m_nu_colors == 4); unsigned points0 = get_points_with_bonus(Color(0)) + get_points_with_bonus(Color(2)); unsigned points1 = get_points_with_bonus(Color(1)) + get_points_with_bonus(Color(3)); if (c == Color(0) || c == Color(2)) return points0 - points1; else return points1 - points0; } else { LIBBOARDGAME_ASSERT(m_nu_colors == m_nu_players); int score = 0; for (ColorIterator i(m_nu_colors); i; ++i) if (*i != c) score -= get_points_with_bonus(*i); score = get_points_with_bonus(c) + score / (static_cast(m_nu_colors) - 1); return score; } } inline Color Board::get_second_color(Color c) const { return m_second_color[c]; } inline const Setup& Board::get_setup() const { return m_setup; } inline Color Board::get_starting_point_color(Point p) const { return m_starting_points.get_starting_point_color(p); } inline const ArrayList& Board::get_starting_points(Color c) const { return m_starting_points.get_starting_points(c); } inline Color Board::get_to_play() const { return m_state_base.to_play; } inline const PieceTransforms& Board::get_transforms() const { return m_board_const->get_transforms(); } inline Variant Board::get_variant() const { return m_variant; } inline bool Board::has_setup() const { for (ColorIterator i(m_nu_colors); i; ++i) if (! m_setup.placements[*i].empty()) return true; return false; } inline void Board::init(const Setup* setup) { init(m_variant, setup); } inline bool Board::is_attach_point(Point p, Color c) const { return m_state_color[c].is_attach_point[p]; } inline bool Board::is_colored_starting_point(Point p) const { return m_starting_points.is_colored_starting_point(p); } inline bool Board::is_colorless_starting_point(Point p) const { return m_starting_points.is_colorless_starting_point(p); } inline bool Board::is_empty(Point p) const { return get_point_state(p).is_empty(); } inline bool Board::is_first_piece(Color c) const { return m_state_color[c].nu_onboard_pieces == 0; } inline bool Board::is_forbidden(Point p, Color c) const { return m_state_color[c].forbidden[p]; } inline const Grid& Board::is_forbidden(Color c) const { return m_state_color[c].forbidden; } inline bool Board::is_forbidden(Color c, Move mv) const { auto& info = get_move_info(mv); auto i = info.begin(); auto end = info.end(); do if (m_state_color[c].forbidden[*i]) return true; while (++i != end); return false; } inline bool Board::is_legal(Move mv) const { return is_legal(m_state_base.to_play, mv); } inline bool Board::is_legal(Color c, Move mv) const { LIBBOARDGAME_ASSERT(! mv.is_null()); if (mv.is_pass()) return true; auto& info = get_move_info(mv); if (! is_piece_left(c, info.get_piece())) return false; bool has_attach_point = false; auto i = info.begin(); auto end = info.end(); do { if (m_state_color[c].forbidden[*i]) return false; if (is_attach_point(*i, c)) has_attach_point = true; } while (++i != end); if (has_attach_point) return true; if (! is_first_piece(c)) return false; i = info.begin(); do if (is_colorless_starting_point(*i) || (is_colored_starting_point(*i) && get_starting_point_color(*i) == c)) return true; while (++i != end); return false; } inline bool Board::is_onboard(Point p) const { return m_geometry->is_onboard(p); } inline bool Board::is_piece_left(Color c, Piece piece) const { LIBBOARDGAME_ASSERT(piece.to_int() < get_nu_uniq_pieces()); return m_state_color[c].nu_left_piece[piece] > 0; } inline bool Board::is_same_player(Color c1, Color c2) const { return (c1 == c2 || c1 == m_second_color[c2]); } inline void Board::place(Color c, Move mv) { LIBBOARDGAME_ASSERT(mv.is_regular()); auto& info = get_move_info(mv); auto& info_ext = get_move_info_ext(mv); auto piece = info.get_piece(); auto piece_size = info.size(); auto& state_color = m_state_color[c]; LIBBOARDGAME_ASSERT(state_color.nu_left_piece[piece] > 0); if (--state_color.nu_left_piece[piece] == 0) { state_color.pieces_left.remove_fast(piece); if (state_color.pieces_left.empty()) { state_color.bonus = m_bonus_all_pieces; if (piece_size == 1) state_color.bonus += m_bonus_one_piece; } } ++m_state_base.nu_onboard_pieces_all; ++state_color.nu_onboard_pieces; state_color.points += piece_size; auto i = info.begin(); auto end = info.end(); do { m_state_base.point_state[*i] = c; m_state_base.played_move[*i] = mv; LIBPENTOBI_FOREACH_COLOR(c, m_state_color[c].forbidden[*i] = true); } while (++i != end); i = info_ext.begin_attach(); end = info_ext.end_attach(); do if (! state_color.is_attach_point[*i] && ! state_color.forbidden[*i]) { state_color.is_attach_point[*i] = true; m_attach_points[c].push_back(*i); } while (++i != end); LIBBOARDGAME_ASSERT(i == info_ext.begin_adj()); end += info_ext.size_adj_points; do state_color.forbidden[*i] = true; while (++i != end); } inline void Board::play(ColorMove move) { play(move.color, move.move); } inline void Board::play(Color c, Move mv) { if (! mv.is_pass()) play_nonpass(c, mv); else play_pass(c); } inline void Board::play_nonpass(Color c, Move mv) { place(c, mv); m_moves.push_back(ColorMove(c, mv)); m_state_base.to_play = get_next(c); } inline void Board::play_nonpass(Move mv) { play_nonpass(m_state_base.to_play, mv); } inline void Board::play_pass(Color c) { m_moves.push_back(ColorMove(c, Move::pass())); m_state_base.to_play = get_next(c); } inline void Board::play_pass() { play_pass(m_state_base.to_play); } inline void Board::play(Move move) { play(m_state_base.to_play, move); } inline void Board::restore_snapshot() { LIBBOARDGAME_ASSERT(m_snapshot); LIBBOARDGAME_ASSERT(m_snapshot->moves_size <= m_moves.size()); m_moves.resize(m_snapshot->moves_size); // See also the comment in copy_from() about the following memcpy's. memcpy(&m_state_base, &m_snapshot->state_base, sizeof(StateBase)); for (ColorIterator i(m_nu_colors); i; ++i) { LIBBOARDGAME_ASSERT(m_snapshot->attach_points_size[*i] <= m_attach_points[*i].size()); m_attach_points[*i].resize(m_snapshot->attach_points_size[*i]); memcpy(&m_state_color[*i], &m_snapshot->state_color[*i], sizeof(StateColor)); } } inline void Board::set_to_play(Color c) { m_state_base.to_play = c; } inline string Board::to_string(Move mv, bool with_piece_name) const { return m_board_const->to_string(mv, with_piece_name); } //----------------------------------------------------------------------------- inline ostream& operator<<(ostream& out, const Board& bd) { bd.write(out); return out; } //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_BOARD_H pentobi-7.2/src/libpentobi_base/BoardConst.cpp000066400000000000000000001046521227240712600215070ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/BoardConst.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "BoardConst.h" #include #include "AdjIterator.h" #include "AdjDiagIterator.h" #include "DiagIterator.h" #include "Grid.h" #include "PieceTransformsClassic.h" #include "PieceTransformsTrigon.h" #include "SymmetricPoints.h" #include "libboardgame_base/RectGeometry.h" #include "libboardgame_base/Transform.h" #include "libboardgame_base/TrigonGeometry.h" #include "libboardgame_util/Log.h" #include "libboardgame_util/StringUtil.h" namespace libpentobi_base { using namespace std; using libboardgame_base::PointTransfRot180; using libboardgame_base::RectGeometry; using libboardgame_base::Transform; using libboardgame_base::TrigonGeometry; using libboardgame_util::log; using libboardgame_util::split; using libboardgame_util::to_lower; using libboardgame_util::trim; //----------------------------------------------------------------------------- namespace { const bool log_move_creation = false; vector create_pieces_classic(const Geometry& geometry, const PieceTransforms& transforms) { vector pieces; // Define the 21 standard pieces. The piece names are the standard names as // in http://blokusstrategy.com/?p=48. The default orientation is chosen // such that it resembles the letter in the piece name pieces.reserve(21); { PiecePoints points; points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, 0)); pieces.push_back(PieceInfo("X", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, 1)); points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, -1)); pieces.push_back(PieceInfo("F", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(1, -1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(0, 2)); pieces.push_back(PieceInfo("L5", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, -1)); points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(0, 2)); pieces.push_back(PieceInfo("N", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, 0)); points.push_back(CoordPoint(1, 1)); pieces.push_back(PieceInfo("P", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, 1)); points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, 1)); pieces.push_back(PieceInfo("T5", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(-1, 1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(1, 0)); points.push_back(CoordPoint(1, 1)); pieces.push_back(PieceInfo("U", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(0, 2)); points.push_back(CoordPoint(1, 0)); points.push_back(CoordPoint(2, 0)); pieces.push_back(PieceInfo("V5", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(-1, 1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(1, -1)); pieces.push_back(PieceInfo("W", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(0, -2)); pieces.push_back(PieceInfo("Y", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, 1)); points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, -1)); pieces.push_back(PieceInfo("Z5", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, -2)); points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(0, 2)); pieces.push_back(PieceInfo("I5", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, 0)); points.push_back(CoordPoint(1, 1)); pieces.push_back(PieceInfo("O", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(1, 0)); points.push_back(CoordPoint(0, -1)); pieces.push_back(PieceInfo("T4", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(1, -1)); pieces.push_back(PieceInfo("Z4", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, -1)); pieces.push_back(PieceInfo("L4", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(0, 2)); pieces.push_back(PieceInfo("I4", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, 0)); pieces.push_back(PieceInfo("V3", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); pieces.push_back(PieceInfo("I3", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(1, 0)); pieces.push_back(PieceInfo("2", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, 0)); pieces.push_back(PieceInfo("1", points, geometry, transforms)); } return pieces; } vector create_pieces_junior(const Geometry& geometry, const PieceTransforms& transforms) { vector pieces; pieces.reserve(12); { PiecePoints points; points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(1, -1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(0, 2)); pieces.push_back(PieceInfo("L5", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, 0)); points.push_back(CoordPoint(1, 1)); pieces.push_back(PieceInfo("P", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, -2)); points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(0, 2)); pieces.push_back(PieceInfo("I5", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, 0)); points.push_back(CoordPoint(1, 1)); pieces.push_back(PieceInfo("O", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(1, 0)); points.push_back(CoordPoint(0, -1)); pieces.push_back(PieceInfo("T4", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(1, -1)); pieces.push_back(PieceInfo("Z4", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, -1)); pieces.push_back(PieceInfo("L4", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(0, 2)); pieces.push_back(PieceInfo("I4", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, 0)); pieces.push_back(PieceInfo("V3", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); pieces.push_back(PieceInfo("I3", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(1, 0)); pieces.push_back(PieceInfo("2", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, 0)); pieces.push_back(PieceInfo("1", points, geometry, transforms)); } return pieces; } vector create_pieces_trigon(const Geometry& geometry, const PieceTransforms& transforms) { vector pieces; // Define the 22 standard Trigon pieces. The piece names are similar to one // of the possible notations from the thread "Trigon book: how to play, how // to win" from August 2010 in the Blokus forums // http://forum.blokus.refreshed.be/viewtopic.php?f=2&t=2539#p9867 // apart from that the smallest pieces are named '2' and '1' like in Classic // to avoid to many pieces with letter 'I' and that numbers are only used // if there is more than one piece with the same letter. The default // orientation is chosen such that it resembles the letter in the piece // name. The (0,0) point must have point type 0 (downside triangle). pieces.reserve(22); { PiecePoints points; points.push_back(CoordPoint(-2, -1)); points.push_back(CoordPoint(-1, -1)); points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, 1)); pieces.push_back(PieceInfo("I6", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, -1)); points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, 1)); pieces.push_back(PieceInfo("L6", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(-2, 0)); points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(1, 0)); points.push_back(CoordPoint(2, 0)); pieces.push_back(PieceInfo("W", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-2, -1)); points.push_back(CoordPoint(-1, -1)); points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(1, 0)); points.push_back(CoordPoint(0, 1)); pieces.push_back(PieceInfo("P6", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(1, -1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(1, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, 1)); pieces.push_back(PieceInfo("S", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(1, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, 1)); points.push_back(CoordPoint(2, 1)); pieces.push_back(PieceInfo("F", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, 1)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(1, 0)); points.push_back(CoordPoint(2, 0)); points.push_back(CoordPoint(3, 0)); pieces.push_back(PieceInfo("V", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(-1, 1)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, 1)); points.push_back(CoordPoint(-1, 2)); pieces.push_back(PieceInfo("Y", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(-1, -1)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(1, 0)); points.push_back(CoordPoint(1, -1)); pieces.push_back(PieceInfo("A6", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, -1)); points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(1, -1)); points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); pieces.push_back(PieceInfo("G", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(-1, -1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(1, 0)); points.push_back(CoordPoint(1, -1)); pieces.push_back(PieceInfo("O", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(-1, 1)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, 0)); points.push_back(CoordPoint(1, 1)); pieces.push_back(PieceInfo("X", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, 1)); points.push_back(CoordPoint(1, 2)); pieces.push_back(PieceInfo("I5", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, -1)); points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(1, 0)); points.push_back(CoordPoint(0, 1)); pieces.push_back(PieceInfo("P5", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, -1)); points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); pieces.push_back(PieceInfo("L5", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, -1)); points.push_back(CoordPoint(0, -1)); points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(1, 0)); pieces.push_back(PieceInfo("C5", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, 1)); pieces.push_back(PieceInfo("I4", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(1, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, 1)); pieces.push_back(PieceInfo("C4", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); points.push_back(CoordPoint(1, 0)); pieces.push_back(PieceInfo("A4", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); points.push_back(CoordPoint(0, 1)); pieces.push_back(PieceInfo("I3", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(-1, 0)); points.push_back(CoordPoint(0, 0)); pieces.push_back(PieceInfo("2", points, geometry, transforms)); } { PiecePoints points; points.push_back(CoordPoint(0, 0)); pieces.push_back(PieceInfo("1", points, geometry, transforms)); } return pieces; } const Geometry& create_geometry(BoardType board_type) { if (board_type == BoardType::classic) return *RectGeometry::get(20, 20); else if (board_type == BoardType::duo) return *RectGeometry::get(14, 14); else if (board_type == BoardType::trigon) return *TrigonGeometry::get(9); else { LIBBOARDGAME_ASSERT(board_type == BoardType::trigon_3); return *TrigonGeometry::get(8); } } } // namespace //----------------------------------------------------------------------------- BoardConst::BoardConst(BoardType board_type, Variant variant) : m_geometry(create_geometry(board_type)) { // TODO: Better distinction between board type and set of pieces. // Currently the variant parameter is used only to distinct between // the set of pieces in Duo and Junior, otherwise the parameter is ignored // because the set of pieces is derived from the board type. m_board_type = board_type; if (board_type == BoardType::trigon) { m_transforms.reset(new PieceTransformsTrigon()); m_pieces = create_pieces_trigon(m_geometry, *m_transforms); reserve_info(Move::onboard_moves_trigon); } else if (board_type == BoardType::trigon_3) { m_transforms.reset(new PieceTransformsTrigon()); m_pieces = create_pieces_trigon(m_geometry, *m_transforms); reserve_info(Move::onboard_moves_trigon_3); } else if (board_type == BoardType::classic) { m_transforms.reset(new PieceTransformsClassic()); m_pieces = create_pieces_classic(m_geometry, *m_transforms); reserve_info(Move::onboard_moves_classic); } else if (variant == Variant::junior) { m_transforms.reset(new PieceTransformsClassic()); m_pieces = create_pieces_junior(m_geometry, *m_transforms); reserve_info(Move::onboard_moves_junior); } else { LIBBOARDGAME_ASSERT(variant == Variant::duo); m_transforms.reset(new PieceTransformsClassic()); m_pieces = create_pieces_classic(m_geometry, *m_transforms); reserve_info(Move::onboard_moves_duo); } m_nu_pieces = static_cast(m_pieces.size()); init_adj_status(); create_moves(); if (board_type == BoardType::classic) LIBBOARDGAME_ASSERT(m_move_info.size() == Move::onboard_moves_classic); else if (board_type == BoardType::trigon) LIBBOARDGAME_ASSERT(m_move_info.size() == Move::onboard_moves_trigon); else if (board_type == BoardType::trigon_3) LIBBOARDGAME_ASSERT(m_move_info.size() == Move::onboard_moves_trigon_3); else if (variant == Variant::duo) LIBBOARDGAME_ASSERT(m_move_info.size() == Move::onboard_moves_duo); else if (variant == Variant::junior) LIBBOARDGAME_ASSERT(m_move_info.size() == Move::onboard_moves_junior); m_total_piece_points = 0; m_max_piece_size = 0; for (const PieceInfo& piece : m_pieces) { m_max_piece_size = max(m_max_piece_size, piece.get_size()); m_total_piece_points += piece.get_size(); } if (board_type == BoardType::classic || variant == Variant::duo) { LIBBOARDGAME_ASSERT(m_nu_pieces == 21); LIBBOARDGAME_ASSERT(m_total_piece_points == 89); } else if (variant == Variant::junior) { LIBBOARDGAME_ASSERT(m_nu_pieces == 12); LIBBOARDGAME_ASSERT(m_total_piece_points == 44); } else if (board_type == BoardType::trigon || board_type == BoardType::trigon_3) { LIBBOARDGAME_ASSERT(m_nu_pieces == 22); LIBBOARDGAME_ASSERT(m_total_piece_points == 110); } if (variant == Variant::duo || variant == Variant::junior || variant == Variant::trigon_2) init_symmetry_info(); } void BoardConst::create_move(Piece piece, const PiecePoints& coord_points, Point center) { MovePoints points; for (auto i = coord_points.begin(); i != coord_points.end(); ++i) points.push_back(Point((*i).x, (*i).y)); MoveInfo info(piece, points); MoveInfoExt info_ext; set_adj_and_attach_points(info, info_ext); MoveInfoExt2 info_ext_2; info_ext_2.center = center; info_ext_2.breaks_symmetry = false; info_ext_2.symmetric_move = Move::null(); m_move_info.push_back(info); m_move_info_ext.push_back(info_ext); m_move_info_ext_2.push_back(info_ext_2); Move move(static_cast(m_move_info.size() - 1)); if (log_move_creation) { Grid grid(m_geometry, '.'); for (Point p : info) grid[p] = 'O'; for (auto i = info_ext.begin_adj(); i != info_ext.end_adj(); ++i) grid[*i] = '+'; for (auto i = info_ext.begin_attach(); i != info_ext.end_attach(); ++i) grid[*i] = '*'; log() << "Move " << move.to_int() << ":\n" << grid << '\n'; } for (Point p : info) for (unsigned i = 0; i < nu_adj_status; ++i) { if (is_compatible_with_adj_status(p, i, info)) { (*m_full_move_table)[i][piece][p].push_back(move); ++m_move_lists_sum_length; } } } void BoardConst::create_moves() { m_full_move_table.reset(new FullMoveTable); m_moves_range.init(m_geometry); m_move_lists_sum_length = 0; for (Piece::IntType i = 0; i < m_nu_pieces; ++i) create_moves(Piece(i)); if (log_move_creation) log() << "Created moves: " << m_move_info.size() << ", " << "precomputed: " << m_move_lists_sum_length << '\n'; LIBBOARDGAME_ASSERT(m_move_lists_sum_length <= max_move_lists_sum_length); m_move_lists.reset(new Move[m_move_lists_sum_length]); unsigned current = 0; for (GeometryIterator i(m_geometry); i; ++i) for (unsigned j = 0; j < nu_adj_status; ++j) for (Piece::IntType k = 0; k < m_nu_pieces; ++k) { Piece piece(k); unsigned begin = current; auto& list = (*m_full_move_table)[j][piece][*i]; for (unsigned l = 0; l < list.size(); ++l) m_move_lists[current++] = list[l]; m_moves_range[*i][j][piece] = ListIndex(begin, current - begin); } m_full_move_table.reset(nullptr); // Free space, no longer needed } void BoardConst::create_moves(Piece piece) { auto& piece_info = m_pieces[piece.to_int()]; if (log_move_creation) log() << "Creating moves for piece " << piece_info.get_name() << "\n"; for (unsigned i = 0; i < nu_adj_status; ++i) (*m_full_move_table)[i][piece].init(m_geometry); PiecePoints points; for (GeometryIterator i(m_geometry); i; ++i) { if (log_move_creation) log() << "Creating moves at " << *i << "\n"; unsigned x = (*i).get_x(); unsigned y = (*i).get_y(); for (const Transform* transform : piece_info.get_transforms()) { if (log_move_creation) log() << "Transformation " << typeid(*transform).name() << "\n"; // Pieces are defined such that (0,0) has point type 0. Check if the // transformed type is compatible with the location on the board. unsigned point_type = m_geometry.get_point_type(x, y); LIBBOARDGAME_ASSERT(transform->get_point_type() == 0); if (transform->get_new_point_type() != point_type) continue; points = piece_info.get_points(); transform->transform(points.begin(), points.end()); sort(points.begin(), points.end()); bool is_onboard = true; for (CoordPoint& p : points) { p.x += x; p.y += y; if (! m_geometry.is_onboard(p)) { is_onboard = false; break; } } if (! is_onboard) continue; LIBBOARDGAME_ASSERT(points.contains(CoordPoint(x, y))); create_move(piece, points, Point(x, y)); } } } Move BoardConst::from_string(const string& s) const { string trimmed = to_lower(trim(s)); if (trimmed == "pass") return Move::pass(); else if (trimmed == "null") return Move::null(); vector v = split(trimmed, ','); if (v.size() > PieceInfo::max_size) throw Exception("illegal move (too many points)"); MovePoints points; for (const string& p : v) points.push_back(Point::from_string(p)); Move mv; if (! find_move(points, mv)) throw Exception("illegal move"); return mv; } const BoardConst& BoardConst::get(Variant variant) { static unique_ptr board_const_classic; static unique_ptr board_const_duo; static unique_ptr board_const_junior; static unique_ptr board_const_trigon; static unique_ptr board_const_trigon_2; static unique_ptr board_const_trigon_3; if (variant == Variant::classic || variant == Variant::classic_2) { if (! board_const_classic) board_const_classic.reset(new BoardConst(BoardType::classic, Variant::classic)); return *board_const_classic; } else if (variant == Variant::duo) { if (! board_const_duo) board_const_duo.reset(new BoardConst(BoardType::duo, Variant::duo)); return *board_const_duo; } else if (variant == Variant::junior) { if (! board_const_junior) board_const_junior.reset(new BoardConst(BoardType::duo, Variant::junior)); return *board_const_junior; } else if (variant == Variant::trigon) { if (! board_const_trigon) board_const_trigon.reset(new BoardConst(BoardType::trigon, Variant::trigon)); return *board_const_trigon; } else if (variant == Variant::trigon_2) { if (! board_const_trigon_2) board_const_trigon_2.reset(new BoardConst(BoardType::trigon, Variant::trigon_2)); return *board_const_trigon_2; } else { LIBBOARDGAME_ASSERT(variant == Variant::trigon_3); if (! board_const_trigon_3) board_const_trigon_3.reset(new BoardConst(BoardType::trigon_3, Variant::trigon_3)); return *board_const_trigon_3; } } bool BoardConst::get_piece_by_name(const string& name, Piece& piece) const { for (Piece::IntType i = 0; i < m_nu_pieces; ++i) if (get_piece_info(Piece(i)).get_name() == name) { piece = Piece(i); return true; } return false; } bool BoardConst::find_move(const MovePoints& points, Move& move) const { if (points.size() == 0) return false; MovePoints sorted_points = points; sort(sorted_points.begin(), sorted_points.end()); Point p = points[0]; if (! m_geometry.is_onboard(p)) return false; for (unsigned i = 0; i < m_pieces.size(); ++i) { Piece piece(i); if (get_piece_info(piece).get_size() == points.size()) { Board::LocalMovesListRange moves = get_moves(piece, p); for (auto j = moves.begin(); j != moves.end(); ++j) if (equal(sorted_points.begin(), sorted_points.end(), m_move_info[j->to_int()].begin())) { move = *j; return true; } } } return false; } void BoardConst::init_adj_status() { m_adj_status.init(m_geometry); array forbidden; for (GeometryIterator i(m_geometry); i; ++i) init_adj_status(*i, forbidden, 0); } void BoardConst::init_adj_status(Point p, array& forbidden, unsigned i) { if (i == adj_status_nu_adj || i == m_geometry.get_adj_diag(p).size()) { unsigned index = 0; for (unsigned j = 0; j < i; ++j) if (forbidden[j]) index |= (1 << j); unsigned n = 0; for (AdjDiagIterator j(m_geometry, p); n < i; ++j, ++n) if (forbidden[n]) m_adj_status[p][index].push_back(*j); return; } forbidden[i] = false; init_adj_status(p, forbidden, i + 1); forbidden[i] = true; init_adj_status(p, forbidden, i + 1); } void BoardConst::init_symmetry_info() { SymmetricPoints symmetric_points; PointTransfRot180 transform; symmetric_points.init(m_geometry, transform); for (unsigned i = 0; i < m_move_info.size(); ++i) { const auto& info = m_move_info[i]; auto& info_ext_2 = m_move_info_ext_2[i]; MovePoints sym_points; info_ext_2.breaks_symmetry = false; for (Point p : info) { if (info.contains(symmetric_points[p])) info_ext_2.breaks_symmetry = true; sym_points.push_back(symmetric_points[p]); } find_move(sym_points, info_ext_2.symmetric_move); } } bool BoardConst::is_compatible_with_adj_status(Point p, unsigned adj_status, const MoveInfo& info) const { for (Point p_adj : m_adj_status[p][adj_status]) if (info.contains(p_adj)) return false; return true; } void BoardConst::reserve_info(size_t nu_moves) { m_move_info.reserve(nu_moves); m_move_info_ext.reserve(nu_moves); m_move_info_ext_2.reserve(nu_moves); } void BoardConst::set_adj_and_attach_points(const MoveInfo& info, MoveInfoExt& info_ext) { auto begin = info.begin(); auto end = info.end(); m_marker.clear(); for (auto i = begin; i != end; ++i) m_marker.set(*i); ArrayList adj_points; for (auto i = begin; i != end; ++i) for (AdjIterator j(m_geometry, *i); j; ++j) if (m_geometry.is_onboard(*j) && ! m_marker[*j]) { m_marker.set(*j); adj_points.push_back(*j); } ArrayList attach_points; for (auto i = begin; i != end; ++i) for (DiagIterator j(m_geometry, *i); j; ++j) if (m_geometry.is_onboard(*j) && ! m_marker[*j]) { m_marker.set(*j); attach_points.push_back(*j); } info_ext.init(adj_points, attach_points); } string BoardConst::to_string(Move mv, bool with_piece_name) const { if (mv.is_null()) return "null"; if (mv.is_pass()) return "pass"; auto& info = get_move_info(mv); ostringstream s; if (with_piece_name) s << '[' << get_piece_info(info.get_piece()).get_name() << "]"; bool is_first = true; for (Point p : info) { if (! is_first) s << ','; else is_first = false; s << p; } return s.str(); } //----------------------------------------------------------------------------- } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/BoardConst.h000066400000000000000000000241711227240712600211510ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/BoardConst.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_BOARD_CONST_H #define LIBPENTOBI_BASE_BOARD_CONST_H #include #include #include "Variant.h" #include "Geometry.h" #include "Grid.h" #include "Marker.h" #include "Move.h" #include "MoveInfo.h" #include "Point.h" #include "PieceInfo.h" #include "PieceMap.h" #include "PieceTransforms.h" #include "libpentobi_base/Color.h" #include "libpentobi_base/ColorMap.h" #include "libboardgame_util/ArrayList.h" namespace libpentobi_base { using namespace std; using libboardgame_util::ArrayList; //----------------------------------------------------------------------------- enum class BoardType { classic, duo, trigon, trigon_3 }; //----------------------------------------------------------------------------- /** Constant precomputed data that is shared between all instances of Board with a given board type and set of unique pieces per color. */ class BoardConst { public: /** Maximum number of unique pieces per color. */ static const unsigned max_pieces = 22; static const unsigned max_moves_at_point = 40; /** The maximum sum of the sizes of all precomputed move lists in any game variant. */ static const unsigned max_move_lists_sum_length = 1425934; /** Begin/end range for lists with local moves. See get_moves(). */ class LocalMovesListRange { public: LocalMovesListRange(const Move* begin, const Move* end) { m_begin = begin; m_end = end; } const Move* begin() const { return m_begin; } const Move* end() const { return m_end; } private: const Move* m_begin; const Move* m_end; }; /** Compressed begin/end range for lists with local moves. This struct is used in the private implementation and will be unpacked into a LocalMovesListRange as a return value for get_moves(). The struct is public to make it reusable for implementing similar precomputed move lists outside this class. */ struct ListIndex { unsigned begin : 24; unsigned size : 8; ListIndex() { } ListIndex(unsigned begin, unsigned size) { LIBBOARDGAME_ASSERT(begin < max_move_lists_sum_length); LIBBOARDGAME_ASSERT(begin + size <= max_move_lists_sum_length); LIBBOARDGAME_ASSERT(begin < (1 << 24)); this->begin = begin & ((1 << 24) - 1); LIBBOARDGAME_ASSERT(size < (1 << 8)); this->size = size & ((1 << 8) - 1); } }; /** The number of neighbors used for computing the adjacent status. The adjacent status is a single number that encodes the forbidden status of the first adj_status_nu_adj neighbors (from the list Geometry::get_adj_diag()). It is used for speeding up the matching of moves at a given point. Increasing this number will make the precomputed lists shorter but exponentially increase the number of lists and the total memory used for all lists. Therefore, the optimal value for speeding up the matching depends on the CPU cache size. */ static const unsigned adj_status_nu_adj = 5; static const unsigned nu_adj_status = 1 << adj_status_nu_adj; /** Get the single instance for a given board size. The instance is created the first time this function is called. */ static const BoardConst& get(Variant variant); Piece::IntType get_nu_pieces() const; unsigned get_total_piece_points() const; unsigned get_max_piece_size() const; const PieceInfo& get_piece_info(Piece piece) const; bool get_piece_by_name(const string& name, Piece& piece) const; const PieceTransforms& get_transforms() const; /** Get move info. @pre move.is_regular() */ const MoveInfo& get_move_info(Move move) const; /** Get pointer to move info array. Can be used to speed up the access to the move info by avoiding the multiple pointer dereferencing of Board::get_move_info(Move) */ const MoveInfo* get_move_info_array() const; /** Get pointer to extended move info array. Can be used to speed up the access to the move info by avoiding the multiple pointer dereferencing of Board::get_move_info_ext(Move) */ const MoveInfoExt* get_move_info_ext_array() const; /** Get extended move info. @pre move.is_regular() */ const MoveInfoExt& get_move_info_ext(Move move) const; const MoveInfoExt2& get_move_info_ext_2(Move mv) const; const MoveInfoExt2* get_move_info_ext_2_array() const; unsigned get_nu_all_moves() const; bool find_move(const MovePoints& points, Move& move) const; /** Get all moves of a piece at a point constrained by the forbidden status of adjacent points. */ LocalMovesListRange get_moves(Piece piece, Point p, unsigned adj_status = 0) const; BoardType get_board_type() const; const Geometry& get_geometry() const; /** Convert a move to its string representation. The string representation is a comma-separated list of points (without spaces between the commas or points). If with_piece_name is true, it is prepended by the piece name in square brackets (also without any spaces). The representation without the piece name is used by the SGF files and GTP interface used by Pentobi (version >= 0.2). */ string to_string(Move mv, bool with_piece_name) const; Move from_string(const string& s) const; private: typedef ArrayList LocalMovesList; /** See m_full_move_table */ typedef array>,nu_adj_status> FullMoveTable; Piece::IntType m_nu_pieces; unsigned m_total_piece_points; unsigned m_max_piece_size; BoardType m_board_type; const Geometry& m_geometry; vector m_pieces; unique_ptr m_transforms; vector m_move_info; vector m_move_info_ext; vector m_move_info_ext_2; /** Non-compact representation of lists of moves of a piece at a point constrained by the forbidden status of adjacent points. Only used during construction of m_moves_range and m_move_lists. */ unique_ptr m_full_move_table; /** See m_move_lists. */ Grid,nu_adj_status>> m_moves_range; /** Compact representation of lists of moves of a piece at a point constrained by the forbidden status of adjacent points. All lists are stored in a single array; m_moves_range contains information about the actual begin/end indices. */ unique_ptr m_move_lists; /** Sum of sizes of all lists in m_full_move_table. Only used during construction of m_moves_range and m_move_lists. */ size_t m_move_lists_sum_length; /** Local variable reused for efficiency. */ Marker m_marker; /** Forbidden neighbors for a given adjacent status index at a given point. Only used during construction. */ Grid,nu_adj_status>> m_adj_status; BoardConst(BoardType board_type, Variant variant); void create_move(Piece piece, const PiecePoints& coord_points, Point center); void create_moves(); void create_moves(Piece piece); void init_adj_status(); void init_adj_status(Point p, array& forbidden, unsigned i); void init_symmetry_info(); bool is_compatible_with_adj_status(Point p, unsigned adj_status, const MoveInfo& info) const; void reserve_info(size_t nu_moves); void set_adj_and_attach_points(const MoveInfo& info, MoveInfoExt& info_ext); }; inline BoardType BoardConst::get_board_type() const { return m_board_type; } inline const Geometry& BoardConst::get_geometry() const { return m_geometry; } inline unsigned BoardConst::get_max_piece_size() const { return m_max_piece_size; } inline const MoveInfo& BoardConst::get_move_info(Move move) const { LIBBOARDGAME_ASSERT(move.to_int() < m_move_info.size()); return m_move_info[move.to_int()]; } inline const MoveInfo* BoardConst::get_move_info_array() const { return &m_move_info.front(); } inline const MoveInfoExt& BoardConst::get_move_info_ext(Move move) const { LIBBOARDGAME_ASSERT(move.to_int() < m_move_info_ext.size()); return m_move_info_ext[move.to_int()]; } inline const MoveInfoExt2& BoardConst::get_move_info_ext_2(Move mv) const { LIBBOARDGAME_ASSERT(mv.to_int() < m_move_info_ext_2.size()); return m_move_info_ext_2[mv.to_int()]; } inline const MoveInfoExt* BoardConst::get_move_info_ext_array() const { return &m_move_info_ext.front(); } inline const MoveInfoExt2* BoardConst::get_move_info_ext_2_array() const { return &m_move_info_ext_2.front(); } inline BoardConst::LocalMovesListRange BoardConst::get_moves( Piece piece, Point p, unsigned adj_status) const { ListIndex idx = m_moves_range[p][adj_status][piece]; auto begin = m_move_lists.get() + idx.begin; auto end = begin + idx.size; return LocalMovesListRange(begin, end); } inline unsigned BoardConst::get_nu_all_moves() const { return static_cast(m_move_info.size()); } inline Piece::IntType BoardConst::get_nu_pieces() const { return m_nu_pieces; } inline const PieceInfo& BoardConst::get_piece_info(Piece piece) const { LIBBOARDGAME_ASSERT(piece.to_int() < m_pieces.size()); return m_pieces[piece.to_int()]; } inline unsigned BoardConst::get_total_piece_points() const { return m_total_piece_points; } inline const PieceTransforms& BoardConst::get_transforms() const { return *m_transforms; } //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_BOARD_CONST_H pentobi-7.2/src/libpentobi_base/BoardUpdater.cpp000066400000000000000000000116051227240712600220200ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/BoardUpdater.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "BoardUpdater.h" #include "BoardUtil.h" #include "NodeUtil.h" #include "libboardgame_sgf/Util.h" namespace libpentobi_base { using namespace std; using libboardgame_sgf::InvalidTree; using libboardgame_sgf::util::get_path_from_root; using libpentobi_base::boardutil::get_current_position_as_setup; //----------------------------------------------------------------------------- namespace { typedef ArrayList AllPiecesLeftList; /** Helper function used in init_setup. */ void handle_setup_property(const Node& node, const char* id, Color c, const Board& bd, Setup& setup, ColorMap& pieces_left) { if (! node.has_property(id)) return; vector values = node.get_multi_property(id); for (const string& s : values) { Move mv; try { mv = bd.from_string(s); } catch (Exception& e) { throw InvalidTree(e.what()); } Piece piece = bd.get_move_info(mv).get_piece(); if (! pieces_left[c].remove(piece)) throw InvalidTree("piece played twice"); setup.placements[c].push_back(mv); } } /** Helper function used in init_setup. */ void handle_setup_empty(const Node& node, const Board& bd, Setup& setup, ColorMap& pieces_left) { if (! node.has_property("AE")) return; vector values = node.get_multi_property("AE"); for (const string& s : values) { Move mv; try { mv = bd.from_string(s); } catch (Exception& e) { throw InvalidTree(e.what()); } for (ColorIterator i(bd.get_nu_colors()); i; ++i) { if (setup.placements[*i].remove(mv)) { Piece piece = bd.get_move_info(mv).get_piece(); LIBBOARDGAME_ASSERT(! pieces_left[*i].contains(piece)); pieces_left[*i].push_back(piece); break; } throw InvalidTree("invalid value for AE property"); } } } /** Initialize the board with a new setup position. Class Board only supports setup positions before any moves are played. To support setup properties in any node, we create a new setup position from the current position and the setup properties from the node and initialize the board with it. */ void init_setup(Board& bd, const Node& node) { Setup setup; get_current_position_as_setup(bd, setup); ColorMap all_pieces_left; for (ColorIterator i(bd.get_nu_colors()); i; ++i) for (Piece piece : bd.get_pieces_left(*i)) for (unsigned j = 0; j < bd.get_nu_piece_instances(); ++j) all_pieces_left[*i].push_back(piece); handle_setup_property(node, "A1", Color(0), bd, setup, all_pieces_left); handle_setup_property(node, "A2", Color(1), bd, setup, all_pieces_left); handle_setup_property(node, "A3", Color(2), bd, setup, all_pieces_left); handle_setup_property(node, "A4", Color(3), bd, setup, all_pieces_left); // AB, AW are equivalent to A1, A2 but only used in games with two colors handle_setup_property(node, "AB", Color(0), bd, setup, all_pieces_left); handle_setup_property(node, "AW", Color(1), bd, setup, all_pieces_left); handle_setup_empty(node, bd, setup, all_pieces_left); Color to_play; if (! libpentobi_base::node_util::get_player(node, setup.to_play)) { // Try to guess who should be to play based on the setup pieces. setup.to_play = Color(0); for (ColorIterator i(bd.get_nu_colors()); i; ++i) if (setup.placements[*i].size() < setup.placements[Color(0)].size()) { setup.to_play = *i; break; } } bd.init(&setup); } } // namespace //----------------------------------------------------------------------------- void BoardUpdater::update(const Node& node) { LIBBOARDGAME_ASSERT(m_tree.contains(node)); m_bd.init(); get_path_from_root(node, m_path); for (const Node* i : m_path) { if (libpentobi_base::node_util::has_setup(*i)) init_setup(m_bd, *i); auto mv = m_tree.get_move(*i); if (mv.is_regular()) { auto& info = m_bd.get_move_info(mv.move); if (m_bd.get_nu_left_piece(mv.color, info.get_piece()) == 0) throw InvalidTree("piece played twice"); m_bd.play(mv); } } } //----------------------------------------------------------------------------- } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/BoardUpdater.h000066400000000000000000000023201227240712600214570ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/BoardUpdater.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_BOARD_UPDATER_H #define LIBPENTOBI_BASE_BOARD_UPDATER_H #include "Board.h" #include "Tree.h" namespace libpentobi_base { //----------------------------------------------------------------------------- /** Updates a board state to a node in a game tree. */ class BoardUpdater { public: BoardUpdater(const Tree& tree, Board& bd); /** Update the board to a node. @throws Exception if tree contains invalid properties, moves that play the same piece twice or other conditions that prevent the updater to update the board to the given node. */ void update(const Node& node); private: const Tree& m_tree; Board& m_bd; /** Local variable reused for efficiency. */ vector m_path; }; inline BoardUpdater::BoardUpdater(const Tree& tree, Board& bd) : m_tree(tree), m_bd(bd) { } //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_BOARD_UPDATER_H pentobi-7.2/src/libpentobi_base/BoardUtil.cpp000066400000000000000000000042451227240712600213330ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/BoardUtil.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "BoardUtil.h" #include #include "SgfUtil.h" namespace libpentobi_base { namespace boardutil { using namespace std; using sgf_util::get_color_id; using sgf_util::get_setup_id; //----------------------------------------------------------------------------- void dump(const Board& bd, ostream& out) { auto variant = bd.get_variant(); Writer writer(out); writer.set_one_prop_per_line(true); writer.set_one_prop_value_per_line(true); writer.begin_tree(); writer.begin_node(); writer.write_property("GM", to_string(variant)); write_setup(writer, variant, bd.get_setup()); writer.end_node(); for (unsigned i = 0; i < bd.get_nu_moves(); ++i) { writer.begin_node(); auto mv = bd.get_move(i); auto id = get_color_id(variant, mv.color); if (! mv.is_pass()) writer.write_property(id, bd.to_string(mv.move, false)); else writer.write_property(id, ""); writer.end_node(); } writer.end_tree(); } void get_current_position_as_setup(const Board& bd, Setup& setup) { setup = bd.get_setup(); for (unsigned i = 0; i < bd.get_nu_moves(); ++i) { auto mv = bd.get_move(i); if (! mv.is_pass()) setup.placements[mv.color].push_back(mv.move); } setup.to_play = bd.get_to_play(); } void write_setup(Writer& writer, Variant variant, const Setup& setup) { auto& board_const = BoardConst::get(variant); for (ColorIterator i(get_nu_colors(variant)); i; ++i) if (! setup.placements[*i].empty()) { vector values; for (Move mv : setup.placements[*i]) values.push_back(board_const.to_string(mv, false)); writer.write_property(get_setup_id(variant, *i), values); } } //----------------------------------------------------------------------------- } // namespace boardutil } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/BoardUtil.h000066400000000000000000000020651227240712600207760ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/BoardUtil.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_BOARDUTIL_H #define LIBPENTOBI_BASE_BOARDUTIL_H #include #include "Board.h" #include "libboardgame_sgf/Writer.h" namespace libpentobi_base { namespace boardutil { using libboardgame_sgf::Writer; //----------------------------------------------------------------------------- void dump(const Board& bd, ostream& out); /** Return the current position as setup. Merges all placements from Board::get_setup() and played moved into a single setup and sets the setup color to play to the current color to play. */ void get_current_position_as_setup(const Board& bd, Setup& setup); void write_setup(Writer& writer, Variant variant, const Setup& setup); //----------------------------------------------------------------------------- } // namespace boardutil } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_BOARDUTIL_H pentobi-7.2/src/libpentobi_base/Book.cpp000066400000000000000000000156741227240712600203500ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Book.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Book.h" #include "libboardgame_sgf/MissingProperty.h" #include "libboardgame_sgf/TreeReader.h" #include "libboardgame_util/Log.h" //----------------------------------------------------------------------------- namespace libpentobi_base { using namespace std; using libboardgame_base::PointTransfIdent; using libboardgame_base::PointTransfRefl; using libboardgame_base::PointTransfReflRot180; using libboardgame_base::PointTransfRot180; using libboardgame_base::PointTransfRot270Refl; using libboardgame_base::PointTransfTrigonReflRot60; using libboardgame_base::PointTransfTrigonReflRot120; using libboardgame_base::PointTransfTrigonReflRot240; using libboardgame_base::PointTransfTrigonReflRot300; using libboardgame_base::PointTransfTrigonRot60; using libboardgame_base::PointTransfTrigonRot120; using libboardgame_base::PointTransfTrigonRot240; using libboardgame_base::PointTransfTrigonRot300; using libboardgame_sgf::ChildIterator; using libboardgame_sgf::InvalidPropertyValue; using libboardgame_sgf::MissingProperty; using libboardgame_sgf::TreeReader; using libboardgame_util::log; //----------------------------------------------------------------------------- Book::Book(Variant variant) : m_tree(variant) { } Move Book::genmove(const Board& bd, Color c) { if (bd.has_setup()) // Book cannot handle setup positions return Move::null(); Move mv; if (genmove(bd, c, mv, PointTransfIdent(), PointTransfIdent())) return mv; auto board_type = bd.get_board_type(); if (board_type == BoardType::duo) if (genmove(bd, c, mv, PointTransfRot270Refl(), PointTransfRot270Refl())) return mv; if (board_type == BoardType::trigon || board_type == BoardType::trigon_3) { if (genmove(bd, c, mv, PointTransfTrigonRot60(), PointTransfTrigonRot300())) return mv; if (genmove(bd, c, mv, PointTransfTrigonRot120(), PointTransfTrigonRot240())) return mv; if (genmove(bd, c, mv, PointTransfRot180(), PointTransfRot180())) return mv; if (genmove(bd, c, mv, PointTransfTrigonRot240(), PointTransfTrigonRot120())) return mv; if (genmove(bd, c, mv, PointTransfTrigonRot300(), PointTransfTrigonRot60())) return mv; if (genmove(bd, c, mv, PointTransfRefl(), PointTransfRefl())) return mv; if (genmove(bd, c, mv, PointTransfTrigonReflRot60(), PointTransfTrigonReflRot60())) return mv; if (genmove(bd, c, mv, PointTransfTrigonReflRot120(), PointTransfTrigonReflRot120())) return mv; if (genmove(bd, c, mv, PointTransfReflRot180(), PointTransfReflRot180())) return mv; if (genmove(bd, c, mv, PointTransfTrigonReflRot240(), PointTransfTrigonReflRot240())) return mv; if (genmove(bd, c, mv, PointTransfTrigonReflRot300(), PointTransfTrigonReflRot300())) return mv; } return Move::null(); } bool Book::genmove(const Board& bd, Color c, Move& mv, const PointTransform& transform, const PointTransform& inv_transform) { LIBBOARDGAME_ASSERT(! bd.has_setup()); auto node = &m_tree.get_root(); for (unsigned i = 0; i < bd.get_nu_moves(); ++i) { ColorMove color_mv = bd.get_move(i); color_mv.move = get_transformed(bd, color_mv.move, transform); node = m_tree.find_child_with_move(*node, color_mv); if (node == nullptr) return false; } node = select_child(bd, c, m_tree, *node, inv_transform); if (node == nullptr) return false; mv = get_transformed(bd, m_tree.get_move(*node).move, inv_transform); return true; } Move Book::get_transformed(const Board& bd, Move mv, const PointTransform& transform) const { if (mv.is_pass()) return mv; unsigned width = bd.get_geometry().get_width(); unsigned height = bd.get_geometry().get_height(); MovePoints points; for (Point p : bd.get_move_info(mv)) points.push_back(transform.get_transformed(p, width, height)); Move transformed_mv; bd.find_move(points, transformed_mv); return transformed_mv; } void Book::load(istream& in) { TreeReader reader; try { reader.read(in); } catch (const TreeReader::ReadError& e) { throw Exception(string("could not read book: ") + e.what()); } unique_ptr root = reader.get_tree_transfer_ownership(); m_tree.init(root); } const Node* Book::select_child(const Board& bd, Color c, const Tree& tree, const Node& node, const PointTransform& inv_transform) { unsigned nu_children = node.get_nu_children(); if (nu_children == 0) return nullptr; vector good_moves; for (unsigned i = 0; i < nu_children; ++i) { auto& child = node.get_child(i); ColorMove color_mv = tree.get_move(child); if (color_mv.is_null()) { log() << "WARNING: Book contains nodes without moves\n"; continue; } if (color_mv.color != c) { log() << "WARNING: Book contains non-alternating move sequences\n"; continue; } auto mv = get_transformed(bd, color_mv.move, inv_transform); if (! bd.is_legal(color_mv.color, mv)) { log() << "WARNING: Book contains illegal move\n"; continue; } if (m_tree.get_good_move(child) > 0) { log() << bd.to_string(mv) << " !\n"; good_moves.push_back(&child); } else log() << bd.to_string(mv) << '\n'; } if (good_moves.empty()) return nullptr; log() << "Book moves: " << good_moves.size() << '\n'; unsigned nu_good_moves = static_cast(good_moves.size()); return good_moves[m_random.generate_small_int(nu_good_moves)]; } //----------------------------------------------------------------------------- } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/Book.h000066400000000000000000000034451227240712600200060ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Book.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_BOOK_H #define LIBPENTOBI_BASE_BOOK_H #include #include "Board.h" #include "Game.h" #include "Tree.h" #include "libboardgame_base/PointTransform.h" #include "libboardgame_util/RandomGenerator.h" namespace libpentobi_base { using libboardgame_util::RandomGenerator; //----------------------------------------------------------------------------- /** Opening book. Opening books are stored as trees in SGF files. Thay contain move annotation properties according to the SGF standard. The book will select randomly among the child nodes that have the move annotation good move or very good move (TE[1] or TE[2]). */ class Book { public: Book(Variant variant); void load(istream& in); Move genmove(const Board& bd, Color c); const Tree& get_tree() const; private: typedef libboardgame_base::PointTransform PointTransform; Tree m_tree; RandomGenerator m_random; bool genmove(const Board& bd, Color c, Move& mv, const PointTransform& transform, const PointTransform& inv_transform); Move get_transformed(const Board& bd, Move mv, const PointTransform& transform) const; const Node* select_child(const Board& bd, Color c, const Tree& tree, const Node& node, const PointTransform& inv_transform); }; inline const Tree& Book::get_tree() const { return m_tree; } //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_BOOK_H pentobi-7.2/src/libpentobi_base/CMakeLists.txt000066400000000000000000000012101227240712600214670ustar00rootroot00000000000000include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) set(pentobi_base_STAT_SRCS Board.cpp BoardConst.cpp BoardUpdater.cpp BoardUtil.cpp Book.cpp Color.cpp Game.cpp GameStateHistory.cpp NodeUtil.cpp PieceInfo.cpp Player.cpp PieceTransforms.cpp PieceTransformsClassic.cpp PieceTransformsTrigon.cpp PointState.cpp SgfUtil.cpp StartingPoints.cpp SymmetricPoints.cpp Tree.cpp TreeUtil.cpp TreeWriter.cpp Variant.cpp ) if (PENTOBI_BUILD_GTP) set(pentobi_base_STAT_SRCS ${pentobi_base_STAT_SRCS} Engine.cpp ) endif() add_library(pentobi_base STATIC ${pentobi_base_STAT_SRCS}) pentobi-7.2/src/libpentobi_base/Color.cpp000066400000000000000000000030201227240712600205120ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Color.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Color.h" #include #include "libboardgame_util/StringUtil.h" namespace libpentobi_base { using namespace std; using libboardgame_util::to_lower; //----------------------------------------------------------------------------- Color::Color(const string& s) { istringstream in(s); in >> *this; if (! in) throw InvalidString("Invalid color string '" + s + "'"); } //----------------------------------------------------------------------------- ostream& operator<<(ostream& out, const Color& c) { out << (c.to_int() + 1); return out; } istream& operator>>(istream& in, Color& c) { string s; in >> s; if (in) { s = to_lower(s); if (s == "1" || s == "b" || s == "black") { c = Color(0); return in; } else if (s == "2" || s == "w" || s == "white") { c = Color(1); return in; } else if (s == "3") { c = Color(2); return in; } else if (s == "4") { c = Color(3); return in; } } in.setstate(ios::failbit); return in; } //----------------------------------------------------------------------------- } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/Color.h000066400000000000000000000105111227240712600201620ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Color.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_COLOR_H #define LIBPENTOBI_BASE_COLOR_H #include #include #include #include "libboardgame_util/Assert.h" #include "libboardgame_util/Exception.h" namespace libpentobi_base { using namespace std; using libboardgame_util::Exception; //----------------------------------------------------------------------------- class Color { public: typedef uint_fast8_t IntType; class InvalidString : public Exception { public: InvalidString(const string& s); }; class Iterator { friend class Color; public: Iterator(IntType nu_colors); operator bool() const; void operator++(); Color operator*() const; private: const IntType m_nu_colors; IntType m_i; }; static const IntType range = 4; Color(); Color(const Color& c); explicit Color(IntType i); explicit Color(const string& s); bool operator==(const Color& c) const; bool operator!=(const Color& c) const; bool operator<(const Color& c) const; IntType to_int() const; Color get_next(IntType nu_colors) const; Color get_previous(IntType nu_colors) const; private: static const IntType value_uninitialized = range; IntType m_i; bool is_initialized() const; }; typedef Color::Iterator ColorIterator; inline Color::InvalidString::InvalidString(const string& s) : Exception(s) { } inline Color::Iterator::Iterator(IntType nu_colors) : m_nu_colors(nu_colors), m_i(0) { } inline Color::Iterator::operator bool() const { return m_i < m_nu_colors; } inline void Color::Iterator::operator++() { ++m_i; } inline Color Color::Iterator::operator*() const { LIBBOARDGAME_ASSERT(operator bool()); return Color(m_i); } inline Color::Color() { #if LIBBOARDGAME_DEBUG m_i = value_uninitialized; #endif } inline Color::Color(IntType i) { LIBBOARDGAME_ASSERT(i < range); m_i = i; } inline Color::Color(const Color& c) { m_i = c.m_i; } inline bool Color::operator==(const Color& c) const { LIBBOARDGAME_ASSERT(is_initialized()); LIBBOARDGAME_ASSERT(c.is_initialized()); return (m_i == c.m_i); } inline bool Color::operator!=(const Color& c) const { return ! operator==(c); } inline bool Color::operator<(const Color& c) const { LIBBOARDGAME_ASSERT(is_initialized()); LIBBOARDGAME_ASSERT(c.is_initialized()); return (m_i < c.m_i); } inline Color Color::get_next(IntType nu_colors) const { IntType i = static_cast(m_i + 1); if (i == nu_colors) return Color(0); return Color(i); } inline Color Color::get_previous(IntType nu_colors) const { LIBBOARDGAME_ASSERT(nu_colors > 1); if (m_i == 0) return Color(static_cast(nu_colors - 1)); return Color(static_cast(m_i - 1)); } inline bool Color::is_initialized() const { return m_i < value_uninitialized; } inline Color::IntType Color::to_int() const { LIBBOARDGAME_ASSERT(is_initialized()); return m_i; } //----------------------------------------------------------------------------- /** Output string representation of color. The strings "1", "2", ... are used for the colors. */ ostream& operator<<(ostream& out, const Color& c); /** Read color from input stream. Accepts the strings "1", "2", ..., as well as "b", "w" or "black", "white" for the first two colors. */ istream& operator>>(istream& in, Color& c); //----------------------------------------------------------------------------- /** Macro to unroll a loop over all colors. */ #define LIBPENTOBI_FOREACH_COLOR(c, statement) \ { \ static_assert(Color::range == 4, ""); \ { Color c(0); statement; } \ { Color c(1); statement; } \ { Color c(2); statement; } \ { Color c(3); statement; } \ } //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_COLOR_H pentobi-7.2/src/libpentobi_base/ColorMap.h000066400000000000000000000030261227240712600206230ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/ColorMap.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_COLOR_MAP_H #define LIBPENTOBI_BASE_COLOR_MAP_H #include "Color.h" namespace libpentobi_base { using namespace std; //----------------------------------------------------------------------------- /** Container mapping a color to another element type. The elements must be default-constructible. This requirement is due to the fact that elements are stored in an array for efficient access by color index and arrays need default-constructible elements. */ template class ColorMap { public: ColorMap(); ColorMap(const T& val); T& operator[](Color c); const T& operator[](Color c) const; void fill(const T& val); private: T m_a[Color::range]; }; template inline ColorMap::ColorMap() { } template inline ColorMap::ColorMap(const T& val) { fill(val); } template inline T& ColorMap::operator[](Color c) { return m_a[c.to_int()]; } template inline const T& ColorMap::operator[](Color c) const { return m_a[c.to_int()]; } template void ColorMap::fill(const T& val) { for (unsigned i = 0; i < Color::range; ++i) m_a[i] = val; } //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_COLOR_MAP_H pentobi-7.2/src/libpentobi_base/ColorMove.h000066400000000000000000000042731227240712600210210ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/ColorMove.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_COLOR_MOVE_H #define LIBPENTOBI_BASE_COLOR_MOVE_H #include "Move.h" #include "libpentobi_base/Color.h" namespace libpentobi_base { using namespace std; //----------------------------------------------------------------------------- struct ColorMove { Color color; Move move; /** Return a color move with a null move and an undefined color. Even if the color is logically not defined, it is still initialized (with Color(0)), such that this color move can be used in comparisons. If you are sure that the color is never used and don't want to initialize it for efficiency, use the default constructor and then assign only the move. */ static ColorMove null(); ColorMove(); ColorMove(Color c, Move mv); /** Equality operator. @pre move, color, mv.move, mv.color are initialized. */ bool operator==(const ColorMove& mv) const; /** Inequality operator. @pre move, color, mv.move, mv.color are initialized. */ bool operator!=(const ColorMove& mv) const; bool is_null() const; bool is_pass() const; bool is_regular() const; }; inline ColorMove::ColorMove() { } inline ColorMove::ColorMove(Color c, Move mv) : color(c), move(mv) { } inline bool ColorMove::operator==(const ColorMove& mv) const { return move == mv.move && color == mv.color; } inline bool ColorMove::operator!=(const ColorMove& mv) const { return ! operator==(mv); } inline bool ColorMove::is_null() const { return move.is_null(); } inline bool ColorMove::is_pass() const { return move.is_pass(); } inline bool ColorMove::is_regular() const { return move.is_regular(); } inline ColorMove ColorMove::null() { return ColorMove(Color(0), Move::null()); } //----------------------------------------------------------------------------- } // namespace libpentobi_base //----------------------------------------------------------------------------- #endif // LIBPENTOBI_BASE_COLOR_MOVE_H pentobi-7.2/src/libpentobi_base/DiagIterator.h000066400000000000000000000021161227240712600214640ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/DiagIterator.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_DIAG_ITERATOR_H #define LIBPENTOBI_BASE_DIAG_ITERATOR_H #include "Board.h" #include "libboardgame_util/NullTermList.h" namespace libpentobi_base { using libboardgame_util::NullTermList; //----------------------------------------------------------------------------- class DiagIterator : public NullTermList::Iterator { public: DiagIterator(const Geometry& geometry, Point p); DiagIterator(const Board& bd, Point p); }; inline DiagIterator::DiagIterator(const Geometry& geometry, Point p) : NullTermList::Iterator(geometry.get_diag(p)) { } inline DiagIterator::DiagIterator(const Board& bd, Point p) : NullTermList::Iterator(bd.get_geometry().get_diag(p)) { } //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_DIAG_ITERATOR_H pentobi-7.2/src/libpentobi_base/Engine.cpp000066400000000000000000000240551227240712600206540ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Engine.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Engine.h" #include "MoveList.h" #include "libboardgame_sgf/TreeReader.h" #include "libboardgame_sgf/Util.h" #include "libboardgame_util/Log.h" #include "libboardgame_util/RandomGenerator.h" namespace libpentobi_base { using libboardgame_gtp::Failure; using libboardgame_sgf::InvalidPropertyValue; using libboardgame_sgf::TreeReader; using libboardgame_sgf::util::get_last_node; using libboardgame_util::log; using libboardgame_util::ArrayList; using libboardgame_util::RandomGenerator; //----------------------------------------------------------------------------- Engine::Engine(Variant variant) : m_accept_illegal(false), m_show_board(false), m_resign(true), m_game(variant), m_player(nullptr) { add("all_legal", &Engine::cmd_all_legal); add("clear_board", &Engine::cmd_clear_board); add("final_score", &Engine::cmd_final_score); add("get_place", &Engine::cmd_get_place); add("loadsgf", &Engine::cmd_loadsgf); add("point_integers", &Engine::cmd_point_integers); add("move_info", &Engine::cmd_move_info); add("p", &Engine::cmd_p); add("param_base", &Engine::cmd_param_base); add("play", &Engine::cmd_play); add("set_game", &Engine::cmd_set_game); add("showboard", &Engine::cmd_showboard); add("undo", &Engine::cmd_undo); } Engine::~Engine() throw() { } void Engine::board_changed() { if (m_show_board) log() << get_board(); } void Engine::cmd_all_legal(const Arguments& args, Response& response) { auto& bd = get_board(); unique_ptr moves(new MoveList()); bd.gen_moves(get_color_arg(args), *moves); for (Move mv : *moves) response << bd.to_string(mv) << '\n'; } void Engine::cmd_clear_board() { m_game.init(); board_changed(); } void Engine::cmd_final_score(Response& response) { auto& bd = get_board(); if (get_nu_players(bd.get_variant()) > 2) { for (ColorIterator i(bd.get_nu_colors()); i; ++i) response << bd.get_points_with_bonus(*i) << ' '; } else { int score = bd.get_score(Color(0)); if (score > 0) response << "B+" << score; else if (score < 0) response << "W+" << (-score); else response << "0"; } } void Engine::cmd_g(Response& response) { genmove(get_board().get_to_play(), response); } void Engine::cmd_genmove(const Arguments& args, Response& response) { genmove(get_color_arg(args), response); } void Engine::cmd_get_place(const Arguments& args, Response& response) { auto& bd = get_board(); unsigned place; bool isPlaceShared; bd.get_place(get_color_arg(args), place, isPlaceShared); response << place; if (isPlaceShared) response << " shared"; } void Engine::cmd_loadsgf(const Arguments& args) { args.check_size_less_equal(2); string file = args.get(0); int move_number = -1; if (args.get_size() == 2) move_number = args.parse_min(1, 1) - 1; try { TreeReader reader; reader.read(file); auto tree = reader.get_tree_transfer_ownership(); m_game.init(tree); const Node* node = nullptr; if (move_number != -1) node = m_game.get_tree().get_node_before_move_number(move_number); if (node == nullptr) node = &get_last_node(m_game.get_root()); m_game.goto_node(*node); board_changed(); } catch (const Exception& e) { throw Failure(e.what()); } } /** Return move info of a move given by its integer or string representation. */ void Engine::cmd_move_info(const Arguments& args, Response& response) { auto& bd = get_board(); Move mv; try { mv = Move(args.parse()); } catch (const Failure&) { try { mv = bd.from_string(args.get()); } catch (Exception&) { ostringstream msg; msg << "invalid argument '" << args.get() << "' (expected move or move ID)"; throw Failure(msg.str()); } } auto& info = bd.get_move_info(mv); Piece piece = info.get_piece(); auto& info_ext = bd.get_move_info_ext(mv); auto& info_ext_2 = bd.get_move_info_ext_2(mv); response << "\n" << "ID: " << mv.to_int() << "\n" << "Piece: " << piece.to_int() << " (" << bd.get_piece_info(piece).get_name() << ")\n" << "Points:"; for (Point p : info) response << ' ' << p; response << "\n" << "Adj: "; for (auto i = info_ext.begin_adj(); i != info_ext.end_adj(); ++i) response << *i << " "; response << "\n" << "Attach: "; for (auto i = info_ext.begin_attach(); i != info_ext.end_attach(); ++i) response << *i << " "; response << "\n" << "BrkSym: " << info_ext_2.breaks_symmetry << "\n" << "SymMv: " << bd.to_string(info_ext_2.symmetric_move); } void Engine::cmd_p(const Arguments& args) { play(get_board().get_to_play(), args, 0); } void Engine::cmd_param_base(const Arguments& args, Response& response) { if (args.get_size() == 0) response << "accept_illegal " << m_accept_illegal << '\n' << "resign " << m_resign << '\n'; else { args.check_size(2); string name = args.get(0); if (name == "accept_illegal") m_accept_illegal = args.parse(1); else if (name == "resign") m_resign = args.parse(1); else { ostringstream msg; msg << "unknown parameter '" << name << "'"; throw Failure(msg.str()); } } } void Engine::cmd_play(const Arguments& args) { play(get_color_arg(args, 0), args, 1); } void Engine::cmd_point_integers(Response& response) { auto& geometry = get_board().get_geometry(); Grid grid(geometry); for (GeometryIterator i(geometry); i; ++i) grid[*i] = (*i).to_int(); response << '\n' << grid; } void Engine::cmd_reg_genmove(const Arguments& args, Response& response) { RandomGenerator::set_global_seed_last(); Move move = get_player().genmove(get_board(), get_color_arg(args)); if (move.is_null()) throw Failure("player failed to generate a move"); response << get_board().to_string(move, false); } /** Set the game variant. Arguments: Blokus|Blokus Two-Player|Blokus Duo|Blokus Trigon|Blokus Trigon Two-Player
This command is similar to the command that is used by Quarry (http://home.gna.org/quarry/) to set a game at GTP engines that support multiple games. */ void Engine::cmd_set_game(const Arguments& args) { Variant variant; if (! parse_variant(args.get_line(), variant)) throw Failure("invalid argument"); m_game.init(variant); board_changed(); } void Engine::cmd_showboard(Response& response) { response << '\n' << get_board(); } void Engine::cmd_undo() { auto& bd = get_board(); if (bd.get_nu_moves() == 0) throw Failure("cannot undo"); m_game.undo(); board_changed(); } void Engine::genmove(Color c, Response& response) { auto& bd = get_board(); auto& player = get_player(); auto mv = player.genmove(bd, c); if (mv.is_null()) throw Failure("player failed to generate a move"); if (! bd.is_legal(c, mv)) { ostringstream msg; msg << "player generated illegal move: " << bd.to_string(mv); throw Failure(msg.str()); } if (m_resign && player.resign()) { response << "resign"; return; } m_game.play(c, mv, true); response << bd.to_string(mv, false); board_changed(); } Color Engine::get_color_arg(const Arguments& args) const { if (args.get_size() > 1) throw Failure("too many arguments"); return get_color_arg(args, 0); } Color Engine::get_color_arg(const Arguments& args, unsigned i) const { string s = args.get_tolower(i); auto& bd = get_board(); auto variant = bd.get_variant(); if (variant == Variant::classic || variant == Variant::classic_2 || variant == Variant::trigon || variant == Variant::trigon_2 || variant == Variant::trigon_3) { if (s == "1" || s == "blue") return Color(0); if (s == "2" || s == "yellow") return Color(1); if (s == "3" || s == "red") return Color(2); if (s == "4" || s == "green") return Color(3); } if (variant == Variant::duo || variant == Variant::junior) { if (s == "blue" || s == "black" || s == "b") return Color(0); if (s == "green" || s == "white" || s == "w") return Color(1); } ostringstream msg; msg << "invalid color argument '" << s << "'"; throw Failure(msg.str()); } Player& Engine::get_player() const { if (m_player == nullptr) throw Failure("no player set"); return *m_player; } void Engine::play(Color c, const Arguments& args, unsigned arg_move_begin) { auto& bd = get_board(); if (bd.get_nu_moves() >= Board::max_game_moves) throw Failure("too many moves"); Move mv; try { mv = bd.from_string(args.get_remaining_line(arg_move_begin - 1)); } catch (const Exception& e) { throw Failure(e.what()); } if (! m_accept_illegal && ! bd.is_legal(c, mv)) throw Failure("illegal move"); m_game.play(c, mv, true); board_changed(); } void Engine::set_player(Player& player) { m_player = &player; add("genmove", &Engine::cmd_genmove); add("g", &Engine::cmd_g); add("reg_genmove", &Engine::cmd_reg_genmove); } void Engine::set_show_board(bool enable) { if (enable && ! m_show_board) log() << get_board(); m_show_board = enable; } //----------------------------------------------------------------------------- } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/Engine.h000066400000000000000000000047521227240712600203230ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Engine.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_ENGINE_H #define LIBPENTOBI_BASE_ENGINE_H #include "libpentobi_base/Game.h" #include "libpentobi_base/Player.h" #include "libboardgame_base/Engine.h" namespace libpentobi_base { using namespace std; using libboardgame_gtp::Arguments; using libboardgame_gtp::Response; //----------------------------------------------------------------------------- /** GTP Blokus engine. */ class Engine : public libboardgame_base::Engine { public: Engine(Variant variant); ~Engine() throw(); void cmd_all_legal(const Arguments&, Response&); void cmd_clear_board(); void cmd_final_score(Response&); void cmd_g(Response&); void cmd_genmove(const Arguments&, Response&); void cmd_get_place(const Arguments& args, Response&); void cmd_loadsgf(const Arguments&); void cmd_move_info(const Arguments&, Response&); void cmd_p(const Arguments&); void cmd_param_base(const Arguments&, Response&); void cmd_play(const Arguments&); void cmd_point_integers(Response&); void cmd_showboard(Response&); void cmd_reg_genmove(const Arguments&, Response&); void cmd_set_game(const Arguments&); void cmd_undo(); /** Set the player. @param player The player (@ref libboardgame_doc_storesref) */ void set_player(Player& player); void set_accept_illegal(bool enable); /** Enable or disable resigning. */ void set_resign(bool enable); void set_show_board(bool enable); const Board& get_board() const; protected: Color get_color_arg(const Arguments& args, unsigned i) const; Color get_color_arg(const Arguments& args) const; private: bool m_accept_illegal; bool m_show_board; bool m_resign; Game m_game; Player* m_player; void board_changed(); void genmove(Color c, Response& response); Player& get_player() const; void play(Color c, const Arguments& args, unsigned arg_move_begin); }; inline const Board& Engine::get_board() const { return m_game.get_board(); } inline void Engine::set_accept_illegal(bool enable) { m_accept_illegal = enable; } inline void Engine::set_resign(bool enable) { m_resign = enable; } //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_ENGINE_H pentobi-7.2/src/libpentobi_base/FullGrid.h000066400000000000000000000013341227240712600206170ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/FullGrid.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_FULL_GRID_H #define LIBPENTOBI_BASE_FULL_GRID_H #include "Point.h" #include "libboardgame_base/FullGrid.h" namespace libpentobi_base { //----------------------------------------------------------------------------- template class FullGrid : public libboardgame_base::FullGrid { public: explicit FullGrid() { } }; //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_FULL_GRID_H pentobi-7.2/src/libpentobi_base/Game.cpp000066400000000000000000000070171227240712600203170ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Game.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Game.h" #include "BoardUtil.h" #include "libboardgame_sgf/InvalidTree.h" #include "libboardgame_sgf/Util.h" namespace libpentobi_base { using namespace std; using libboardgame_sgf::InvalidTree; using libboardgame_sgf::util::back_to_main_variation; using libboardgame_sgf::util::is_main_variation; using libpentobi_base::boardutil::get_current_position_as_setup; //----------------------------------------------------------------------------- Game::Game(Variant variant) : m_bd(new Board(variant)), m_tree(variant), m_updater(m_tree, *m_bd) { init(variant); } Game::Game(unique_ptr& root) : m_bd(new Board(m_tree.get_variant())), m_tree(m_tree.get_variant()), m_updater(m_tree, *m_bd) { init(root); } void Game::add_setup(Color c, Move mv) { auto& node = m_tree.add_setup(*m_current, c, mv); goto_node(node); } void Game::delete_all_variations() { goto_node(back_to_main_variation(*m_current)); m_tree.delete_all_variations(); } void Game::goto_node(const Node& node) { auto old = m_current; try { m_updater.update(node); m_current = &node; } catch (const InvalidTree&) { // Try to restore the old state. if (old == nullptr) m_current = &node; else { try { m_updater.update(*old); } catch (const InvalidTree&) { } m_current = old; } throw; } } void Game::init(Variant variant) { m_bd->init(variant); m_tree.init_variant(variant); m_current = &m_tree.get_root(); } void Game::init(unique_ptr& root) { m_tree.init(root); m_bd->init(m_tree.get_variant()); m_current = nullptr; goto_node(m_tree.get_root()); } void Game::keep_only_position() { m_tree.keep_only_subtree(*m_current); m_tree.remove_children(m_tree.get_root()); m_current = nullptr; goto_node(m_tree.get_root()); } void Game::keep_only_subtree() { m_tree.keep_only_subtree(*m_current); m_current = nullptr; goto_node(m_tree.get_root()); } void Game::play(ColorMove mv, bool always_create_new_node) { m_bd->play(mv); const Node* child = nullptr; if (! always_create_new_node) child = m_tree.find_child_with_move(*m_current, mv); if (child != nullptr) m_current = child; else { m_current = &m_tree.create_new_child(*m_current); m_tree.set_move(*m_current, mv); } } void Game::remove_player() { m_tree.remove_player(*m_current); m_updater.update(*m_current); } void Game::remove_setup(Color c, Move mv) { auto& node = m_tree.remove_setup(*m_current, c, mv); goto_node(node); } void Game::set_player(Color c) { m_tree.set_player(*m_current, c); m_updater.update(*m_current); } void Game::set_result(int score) { if (is_main_variation(*m_current)) m_tree.set_result(m_tree.get_root(), score); } void Game::truncate() { goto_node(m_tree.truncate(*m_current)); } void Game::undo() { LIBBOARDGAME_ASSERT(! m_tree.get_move(*m_current).is_null()); LIBBOARDGAME_ASSERT(m_current->has_parent()); goto_node(m_current->get_parent()); } //----------------------------------------------------------------------------- } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/Game.h000066400000000000000000000221231227240712600177570ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Game.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_GAME_H #define LIBPENTOBI_BASE_GAME_H #include "Board.h" #include "BoardUpdater.h" #include "NodeUtil.h" #include "Tree.h" namespace libpentobi_base { //----------------------------------------------------------------------------- class Game { public: Game(Variant variant); Game(unique_ptr& root); void init(Variant variant); void init(); /** Initialize game from a SGF tree. @note If the tree contains invalid properties, future calls to goto_node() might throw an exception. @param root The root node of the SGF tree; the ownership is transfered to this class. @throws InvalidTree, if the root node contains invalid properties */ void init(unique_ptr& root); const Board& get_board() const; Variant get_variant() const; const Node& get_current() const; const Node& get_root() const; const Tree& get_tree() const; /** Get the current color to play. This takes not into account if the current color to play still has moves available. */ Color get_to_play() const; /** Get the next color to play that still has moves. The colors are tested in playing order starting with get_to_play(). */ Color get_effective_to_play() const; /** @param mv @param always_create_new_node Always create a new child of the current node even if a child with the move already exists. */ void play(ColorMove mv, bool always_create_new_node); void play(Color c, Move mv, bool always_create_new_node); /** Update game state to a node in the tree. @throws InvalidTree, if the game was constructed with an external SGF tree and the tree contained invalid property values (syntactically or sematically, like moves on occupied points). If an exception is thrown, the current node is not changed. */ void goto_node(const Node& node); /** Undo the current move and go to parent node. @pre ! get_current().get_move().is_null() @pre get_current()->has_parent() @note Even if the implementation of this function calls goto_node(), it cannot throw an InvalidPropertyValue because the class Game ensures that the current node is always reachable via a path of nodes with valid move properties. */ void undo(); ColorMove get_move() const; /** See libpentobi_base::Tree::get_move_ignore_invalid() */ ColorMove get_move_ignore_invalid() const; /** Add final score to root node if the current node is in the main variation. */ void set_result(int score); void set_charset(const string& charset); void remove_move_annotation(); double get_bad_move() const; double get_good_move() const; bool is_doubtful_move() const; bool is_interesting_move() const; void set_bad_move(double value = 1); void set_good_move(double value = 1); void set_doubtful_move(); void set_interesting_move(); string get_comment() const; void set_comment(const string& s); /** Delete the current node and its subtree and go to the parent node. @pre get_current().has_parent() */ void truncate(); void truncate_children(); /** Replace the game tree by a new one that has the current position as a setup in its root node. */ void keep_only_position(); /** Like keep_only_position() but does not delete the children of the current node. */ void keep_only_subtree(); void make_main_variation(); void move_up_variation(); void move_down_variation(); /** Delete all variations but the main variation. If the current node is not in the main variation it will be changed to the node as in libboardgame_sgf::util::back_to_main_variation() */ void delete_all_variations(); /** Make the current node the first child of its parent. */ void make_first_child(); void set_modified(); void clear_modified(); bool is_modified() const; /** Set the AP property at the root node. */ void set_application(const string& name, const string& version = ""); string get_player_name(Color c) const; void set_player_name(Color c, const string& name); string get_date() const; void set_date(const string& date); void set_date_today(); /** Get event info (standard property EV) from root node. */ string get_event() const; void set_event(const string& event); /** Get round info (standard property RO) from root node. */ string get_round() const; void set_round(const string& round); /** Get time info (standard property TM) from root node. */ string get_time() const; void set_time(const string& time); bool has_setup() const; void add_setup(Color c, Move mv); void remove_setup(Color c, Move mv); /** See libpentobi_base::Tree::set_player() */ void set_player(Color c); /** See libpentobi_base::Tree::remove_player() */ void remove_player(); private: const Node* m_current; unique_ptr m_bd; Tree m_tree; BoardUpdater m_updater; }; inline void Game::clear_modified() { m_tree.clear_modified(); } inline double Game::get_bad_move() const { return m_tree.get_bad_move(*m_current); } inline const Board& Game::get_board() const { return *m_bd; } inline string Game::get_comment() const { return m_tree.get_comment(*m_current); } inline string Game::get_date() const { return m_tree.get_date(); } inline string Game::get_event() const { return m_tree.get_event(); } inline Color Game::get_effective_to_play() const { return m_bd->get_effective_to_play(); } inline const Node& Game::get_current() const { return *m_current; } inline double Game::get_good_move() const { return m_tree.get_good_move(*m_current); } inline ColorMove Game::get_move() const { return m_tree.get_move(*m_current); } inline ColorMove Game::get_move_ignore_invalid() const { return m_tree.get_move_ignore_invalid(*m_current); } inline string Game::get_player_name(Color c) const { return m_tree.get_player_name(c); } inline Color Game::get_to_play() const { return m_bd->get_to_play(); } inline string Game::get_round() const { return m_tree.get_round(); } inline const Node& Game::get_root() const { return m_tree.get_root(); } inline string Game::get_time() const { return m_tree.get_time(); } inline const Tree& Game::get_tree() const { return m_tree; } inline bool Game::has_setup() const { return libpentobi_base::node_util::has_setup(*m_current); } inline Variant Game::get_variant() const { return m_bd->get_variant(); } inline void Game::init() { init(m_bd->get_variant()); } inline bool Game::is_doubtful_move() const { return m_tree.is_doubtful_move(*m_current); } inline bool Game::is_interesting_move() const { return m_tree.is_interesting_move(*m_current); } inline bool Game::is_modified() const { return m_tree.is_modified(); } inline void Game::make_first_child() { m_tree.make_first_child(*m_current); } inline void Game::make_main_variation() { m_tree.make_main_variation(*m_current); } inline void Game::move_down_variation() { m_tree.move_down(*m_current); } inline void Game::move_up_variation() { m_tree.move_up(*m_current); } inline void Game::play(Color c, Move mv, bool always_create_new_node) { play(ColorMove(c, mv), always_create_new_node); } inline void Game::remove_move_annotation() { m_tree.remove_move_annotation(*m_current); } inline void Game::set_application(const string& name, const string& version) { m_tree.set_application(name, version); } inline void Game::set_bad_move(double value) { m_tree.set_bad_move(*m_current, value); } inline void Game::set_charset(const string& charset) { m_tree.set_charset(charset); } inline void Game::set_comment(const string& s) { m_tree.set_comment(*m_current, s); } inline void Game::set_date(const string& date) { m_tree.set_date(date); } inline void Game::set_event(const string& event) { m_tree.set_event(event); } inline void Game::set_date_today() { m_tree.set_date_today(); } inline void Game::set_doubtful_move() { m_tree.set_doubtful_move(*m_current); } inline void Game::set_good_move(double value) { m_tree.set_good_move(*m_current, value); } inline void Game::set_interesting_move() { m_tree.set_interesting_move(*m_current); } inline void Game::set_modified() { m_tree.set_modified(); } inline void Game::set_player_name(Color c, const string& name) { m_tree.set_player_name(c, name); } inline void Game::set_round(const string& round) { m_tree.set_round(round); } inline void Game::set_time(const string& time) { m_tree.set_time(time); } inline void Game::truncate_children() { m_tree.remove_children(*m_current); } //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_GAME_H pentobi-7.2/src/libpentobi_base/GameStateHistory.cpp000066400000000000000000000040331227240712600226750ustar00rootroot00000000000000//---------------------------------------------------------------------------- /** @file libpentobi_base/GameStateHistory.cpp */ //---------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "GameStateHistory.h" #include "BoardUtil.h" namespace libpentobi_base { using namespace std; using boardutil::get_current_position_as_setup; //---------------------------------------------------------------------------- void GameStateHistory::get_as_setup(Variant& variant, Setup& setup) const { LIBBOARDGAME_ASSERT(is_valid()); variant = m_variant; unique_ptr bd(new Board(variant)); for (ColorMove mv : m_moves) bd->play(mv); get_current_position_as_setup(*bd, setup); } void GameStateHistory::init(const Board& bd) { init(bd, bd.get_to_play()); } void GameStateHistory::init(const Board& bd, Color to_play) { m_is_valid = true; m_variant = bd.get_variant(); m_nu_colors = bd.get_nu_colors(); m_moves.clear(); for (unsigned i = 0; i < bd.get_nu_moves(); ++i) m_moves.push_back(bd.get_move(i)); m_to_play = to_play; } bool GameStateHistory::is_followup(const GameStateHistory& other, vector& sequence) const { if (! m_is_valid || ! other.m_is_valid || m_variant != other.m_variant || m_moves.size() < other.m_moves.size()) return false; for (unsigned i = 0; i < other.m_moves.size(); ++i) if (m_moves[i] != other.m_moves[i]) return false; sequence.clear(); Color to_play = other.m_to_play; for (size_t i = other.m_moves.size(); i < m_moves.size(); ++i) { auto mv = m_moves[i]; if (mv.color != to_play) return false; sequence.push_back(mv.move); to_play = to_play.get_next(m_nu_colors); } if (to_play != m_to_play) return false; return true; } //---------------------------------------------------------------------------- } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/GameStateHistory.h000066400000000000000000000045451227240712600223520ustar00rootroot00000000000000//---------------------------------------------------------------------------- /** @file libpentobi_base/GameStateHistory.h */ //---------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_GAME_STATE_HISTORY_H #define LIBPENTOBI_BASE_GAME_STATE_HISTORY_H #include "Board.h" namespace libpentobi_base { using namespace std; //---------------------------------------------------------------------------- /** Identifier for board state including history. This class can be used, for instance, to uniquely remember a board position for reusing parts of previous computations. The state includes: - the game variant - the history of moves - the color to play */ class GameStateHistory { public: /** Constructor. The initial state is that the history does not correspond to any valid position. */ GameStateHistory(); /** Initialize from a current board position. */ void init(const Board& bd); /** Initialize from a current board position and explicit color to play. */ void init(const Board& bd, Color to_play); /** Clear the state. A cleared state does not correspond to any valid position. */ void clear(); /** Check if the state corresponds to any valid position. */ bool is_valid() const; /** Check if this position is a alternate-play followup to another one. @param other The other position @param[out] sequence The sequence leading from the other position to this one @return @c true If the position is a followup */ bool is_followup(const GameStateHistory& other, vector& sequence) const; /** Get the position of the board state as setup. @pre is_valid() @param[out] variant @param[out] setup */ void get_as_setup(Variant& variant, Setup& setup) const; private: bool m_is_valid; Color::IntType m_nu_colors; Variant m_variant; vector m_moves; Color m_to_play; }; inline GameStateHistory::GameStateHistory() { clear(); } inline void GameStateHistory::clear() { m_is_valid = false; } inline bool GameStateHistory::is_valid() const { return m_is_valid; } //---------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_GAME_STATE_HISTORY_H pentobi-7.2/src/libpentobi_base/Geometry.h000066400000000000000000000013051227240712600207000ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Geometry.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_GEOMETRY_H #define LIBPENTOBI_BASE_GEOMETRY_H #include "Point.h" #include "libboardgame_base/Geometry.h" namespace libpentobi_base { //----------------------------------------------------------------------------- typedef libboardgame_base::Geometry Geometry; typedef Geometry::Iterator GeometryIterator; //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_GEOMETRY_H pentobi-7.2/src/libpentobi_base/Grid.h000066400000000000000000000016561227240712600200030ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Grid.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_GRID_H #define LIBPENTOBI_BASE_GRID_H #include "Point.h" #include "Geometry.h" #include "libboardgame_base/Grid.h" namespace libpentobi_base { //----------------------------------------------------------------------------- template class Grid : public libboardgame_base::Grid { public: explicit Grid() { } Grid(const Geometry& geometry) : libboardgame_base::Grid(geometry) { } Grid(const Geometry& geometry, const T& val) : libboardgame_base::Grid(geometry, val) { } }; //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_GRID_H pentobi-7.2/src/libpentobi_base/Marker.h000066400000000000000000000011701227240712600203260ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Marker.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_MARKER_H #define LIBPENTOBI_BASE_MARKER_H #include "Point.h" #include "libboardgame_base/Marker.h" namespace libpentobi_base { //----------------------------------------------------------------------------- typedef libboardgame_base::Marker Marker; //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_MARKER_H pentobi-7.2/src/libpentobi_base/Move.h000066400000000000000000000071031227240712600200150ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Move.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_MOVE_H #define LIBPENTOBI_BASE_MOVE_H #include #include #include "libboardgame_util/Assert.h" namespace libpentobi_base { using namespace std; //----------------------------------------------------------------------------- class Move { public: /** Integer type used internally in this class to store a move. This class is optimized for size not for speed because there are large precomputed data structures that store moves and move lists. Therefore it uses uint_least16_t, not uint_fast16_t. */ typedef uint_least16_t IntType; static const IntType onboard_moves_classic = 30433; static const IntType onboard_moves_trigon = 32131; static const IntType onboard_moves_trigon_3 = 24859; static const IntType onboard_moves_duo = 13729; static const IntType onboard_moves_junior = 7217; /** Integer range of moves. The maximum is given by the number of on-board moves in game variant Trigon, plus a pass and a null move. */ static const IntType range = onboard_moves_trigon + 1 + 1; static Move pass(); static Move null(); Move(); explicit Move(IntType i); Move& operator=(const Move& mv); bool operator==(const Move& mv) const; bool operator!=(const Move& mv) const; bool operator<(const Move& mv) const; bool is_pass() const; bool is_null() const; /** Test if move is a regular move (not a null or pass move) */ bool is_regular() const; /** Return move as an integer between 0 and Move::range */ IntType to_int() const; private: static const IntType max_regular_value = range - 3; static const IntType value_pass = range - 2; static const IntType value_null = range - 1; static const IntType value_uninitialized = range; IntType m_i; bool is_initialized() const; }; inline Move::Move() { #if LIBBOARDGAME_DEBUG m_i = value_uninitialized; #endif } inline Move::Move(IntType i) { LIBBOARDGAME_ASSERT(i < range); m_i = i; } inline Move& Move::operator=(const Move& mv) { m_i = mv.m_i; return *this; } inline bool Move::operator==(const Move& mv) const { LIBBOARDGAME_ASSERT(is_initialized()); LIBBOARDGAME_ASSERT(mv.is_initialized()); return m_i == mv.m_i; } inline bool Move::operator!=(const Move& mv) const { return ! operator==(mv); } inline bool Move::operator<(const Move& mv) const { LIBBOARDGAME_ASSERT(is_initialized()); LIBBOARDGAME_ASSERT(mv.is_initialized()); return m_i < mv.m_i; } inline bool Move::is_initialized() const { return m_i < value_uninitialized; } inline bool Move::is_null() const { LIBBOARDGAME_ASSERT(is_initialized()); return m_i == value_null; } inline bool Move::is_pass() const { LIBBOARDGAME_ASSERT(is_initialized()); return m_i == value_pass; } inline bool Move::is_regular() const { LIBBOARDGAME_ASSERT(is_initialized()); return m_i <= max_regular_value; } inline Move Move::null() { return Move(value_null); } inline Move Move::pass() { return Move(value_pass); } inline Move::IntType Move::to_int() const { LIBBOARDGAME_ASSERT(is_initialized()); return m_i; } //----------------------------------------------------------------------------- } // namespace libpentobi_base //----------------------------------------------------------------------------- #endif // LIBPENTOBI_BASE_MOVE_H pentobi-7.2/src/libpentobi_base/MoveInfo.h000066400000000000000000000067551227240712600206450ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/MoveInfo.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_MOVE_INFO_H #define LIBPENTOBI_BASE_MOVE_INFO_H #include #include "MovePoints.h" #include "Piece.h" #include "PieceInfo.h" namespace libpentobi_base { using namespace std; //----------------------------------------------------------------------------- class MoveInfo { public: MoveInfo() { } MoveInfo(Piece piece, const MovePoints& points) { m_piece = static_cast(piece.to_int()); m_size = 0; for (auto p : points) m_points[m_size++] = p; } const Point* begin() const { return m_points; } const Point* end() const { return m_points + m_size; } uint8_t size() const { return m_size; } Piece get_piece() const { return Piece(m_piece); } bool contains(Point p) const { return find(begin(), end(), p) != end(); } private: uint8_t m_piece; uint8_t m_size; Point m_points[PieceInfo::max_size]; }; //----------------------------------------------------------------------------- /** Less frequently accessed move info. Stored separately from MoveInfo to improve CPU cache performance. */ struct MoveInfoExt { Point points[PieceInfo::max_adj_attach]; uint8_t size_attach_points; uint8_t size_adj_points; void init(const ArrayList& adj_points, const ArrayList& attach_points) { size_attach_points = static_cast(attach_points.size());; size_adj_points = static_cast(adj_points.size()); LIBBOARDGAME_ASSERT(size_attach_points + size_adj_points <= PieceInfo::max_adj_attach); Point* i = points; for (Point p : attach_points) *(i++) = p; for (Point p : adj_points) *(i++) = p; } const Point* begin_attach() const { return points; } const Point* end_attach() const { return points + size_attach_points; } const Point* begin_adj() const { return end_attach(); } const Point* end_adj() const { return begin_adj() + size_adj_points; } }; //----------------------------------------------------------------------------- /** Leeast frequently accessed move info. Stored separately from MoveInfo to improve CPU cache performance. */ struct MoveInfoExt2 { /** Whether the move breaks rotational symmetry of the board. Currently not initialized for classic and trigon_3 board types because enforced rotational-symmetric draws are not used in the MCTS search on these boards (trigon_3 has no 2-player game variant and classic_2 currently only supports colored starting points, which makes rotational draws impossible. */ bool breaks_symmetry; /** The rotational-symmetric counterpart to this move. Currently not initialized for classic and trigon_3 board types (see comment at breaks_symmetry. */ Move symmetric_move; Point center; }; //----------------------------------------------------------------------------- } // namespace libpentobi_base //----------------------------------------------------------------------------- #endif // LIBPENTOBI_BASE_MOVE_INFO_H pentobi-7.2/src/libpentobi_base/MoveList.h000066400000000000000000000012021227240712600206430ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/MoveList.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_MOVE_LIST_H #define LIBPENTOBI_BASE_MOVE_LIST_H #include "Move.h" #include "libboardgame_util/ArrayList.h" namespace libpentobi_base { //----------------------------------------------------------------------------- typedef ArrayList MoveList; //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_MOVE_LIST_H pentobi-7.2/src/libpentobi_base/MoveMarker.h000066400000000000000000000012231227240712600211540ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/MoveMarker.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_MOVE_MARKER_H #define LIBPENTOBI_BASE_MOVE_MARKER_H #include "Move.h" #include "libboardgame_util/BitMarker.h" namespace libpentobi_base { //----------------------------------------------------------------------------- typedef libboardgame_util::BitMarker MoveMarker; //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_MOVE_MARKER_H pentobi-7.2/src/libpentobi_base/MovePoints.h000066400000000000000000000014621227240712600212140ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/MovePoints.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_MOVE_POINTS_H #define LIBPENTOBI_BASE_MOVE_POINTS_H #include "PieceInfo.h" #include "Point.h" #include "libboardgame_util/ArrayList.h" namespace libpentobi_base { using libboardgame_util::ArrayList; //----------------------------------------------------------------------------- typedef ArrayList MovePoints; //----------------------------------------------------------------------------- } // namespace libpentobi_base //----------------------------------------------------------------------------- #endif // LIBPENTOBI_BASE_MOVE_POINTS_H pentobi-7.2/src/libpentobi_base/NodeUtil.cpp000066400000000000000000000075631227240712600211770ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/NodeUtil.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "NodeUtil.h" #include "libboardgame_util/StringUtil.h" namespace libpentobi_base { namespace node_util { using libboardgame_sgf::InvalidPropertyValue; using libboardgame_sgf::InvalidTree; using libboardgame_sgf::PropertyIterator; using libboardgame_util::split; using libboardgame_util::trim; //----------------------------------------------------------------------------- bool get_move(const Node& node, Variant variant, Color& c, MovePoints& points) { string id; if (variant == Variant::duo || variant == Variant::junior) { if (node.has_property("B")) { id = "B"; c = Color(0); } else if (node.has_property("W")) { id = "W"; c = Color(1); } else if (node.has_property("1")) { id = "1"; c = Color(0); } else if (node.has_property("2")) { id = "2"; c = Color(1); } } else { // Properties BLUE/YELLOW/RED/GREEN were used by Pentobi 0.1 // Newer versions of Pentobi use 1/2/3/4 as suggested by SGF FF[5] if (node.has_property("1")) { id = "1"; c = Color(0); } else if (node.has_property("2")) { id = "2"; c = Color(1); } else if (node.has_property("3")) { id = "3"; c = Color(2); } else if (node.has_property("4")) { id = "4"; c = Color(3); } else if (node.has_property("BLUE")) { id = "BLUE"; c = Color(0); } else if (node.has_property("YELLOW")) { id = "YELLOW"; c = Color(1); } else if (node.has_property("RED")) { id = "RED"; c = Color(2); } else if (node.has_property("GREEN")) { id = "GREEN"; c = Color(3); } } if (id.empty()) return false; vector values; values = node.get_multi_property(id); // Note: we still support having the points of a move in a list of point // values instead of a single value as used by Pentobi <= 0.2, but it // is deprecated points.clear(); for (const auto& s : values) { if (trim(s).empty()) continue; vector v = split(s, ','); for (const auto& p_str : v) { Point p; try { p = Point::from_string(p_str); } catch (const Point::InvalidString&) { throw InvalidPropertyValue(id, p_str); } points.push_back(p); } } return true; } bool get_player(const Node& node, Color& c) { if (! node.has_property("PL")) return false; string value = node.get_property("PL"); if (value == "B" || value == "1") c = Color(0); else if (value == "W" || value == "2") c = Color(1); else if (value == "3") c = Color(2); else if (value == "4") c = Color(3); else throw InvalidTree("invalid value for PL property"); return true; } bool has_setup(const Node& node) { for (PropertyIterator i(node); i; ++i) if (i->id == "AB" || i->id == "AW" || i->id == "A1" || i->id == "A2" || i->id == "A3" || i->id == "A4" || i->id == "AE") return true; return false; } //----------------------------------------------------------------------------- } // namespace node_util } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/NodeUtil.h000066400000000000000000000024431227240712600206340ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/NodeUtil.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_NODE_UTIL_H #define LIBPENTOBI_BASE_NODE_UTIL_H #include "Color.h" #include "MovePoints.h" #include "Variant.h" #include "libboardgame_sgf/Node.h" namespace libpentobi_base { namespace node_util { using libboardgame_sgf::Node; //----------------------------------------------------------------------------- /** Get move points. @param node @param variant @param[out] c The move color (only defined if return value is true) @param[out] points The move points (only defined if return value is true) @return true if the node has a move property and the move is not a pass move. */ bool get_move(const Node& node, Variant variant, Color& c, MovePoints& points); /** Check if a node has setup properties (not including the PL property). */ bool has_setup(const Node& node); /** Get the color to play in a setup position (PL property). */ bool get_player(const Node& node, Color& c); //----------------------------------------------------------------------------- } // namespace node_util } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_NODE_UTIL_H pentobi-7.2/src/libpentobi_base/Piece.h000066400000000000000000000046501227240712600201400ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Piece.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_PIECE_H #define LIBPENTOBI_BASE_PIECE_H #include "libboardgame_util/Assert.h" namespace libpentobi_base { using namespace std; //----------------------------------------------------------------------------- /** Wrapper around an integer representing a piece type in a certain game variant. */ class Piece { public: typedef unsigned IntType; /** Maximum number of unique pieces per color. */ static const IntType max_pieces = 22; /** Integer range used for unique pieces without the null piece. */ static const IntType range_not_null = max_pieces; /** Integer range used for unique pieces including the null piece */ static const IntType range = max_pieces + 1; static Piece null(); Piece(); explicit Piece(IntType i); Piece& operator=(const Piece& piece); bool operator==(const Piece& piece) const; bool operator!=(const Piece& piece) const; bool is_null() const; /** Return move as an integer between 0 and Piece::range */ IntType to_int() const; private: static const IntType value_null = range - 1; static const IntType value_uninitialized = range; IntType m_i; bool is_initialized() const; }; inline Piece::Piece() { #if LIBBOARDGAME_DEBUG m_i = value_uninitialized; #endif } inline Piece::Piece(IntType i) { LIBBOARDGAME_ASSERT(i < range); m_i = i; } inline Piece& Piece::operator=(const Piece& piece) { m_i = piece.m_i; return *this; } inline bool Piece::operator==(const Piece& piece) const { return m_i == piece.m_i; } inline bool Piece::operator!=(const Piece& piece) const { return ! operator==(piece); } inline bool Piece::is_initialized() const { return m_i < value_uninitialized; } inline bool Piece::is_null() const { LIBBOARDGAME_ASSERT(is_initialized()); return m_i == value_null; } inline Piece Piece::null() { return Piece(value_null); } inline auto Piece::to_int() const -> IntType { LIBBOARDGAME_ASSERT(is_initialized()); return m_i; } //----------------------------------------------------------------------------- } // namespace libpentobi_base //----------------------------------------------------------------------------- #endif // LIBPENTOBI_BASE_PIECE_H pentobi-7.2/src/libpentobi_base/PieceInfo.cpp000066400000000000000000000163361227240712600213130ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/PieceInfo.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "PieceInfo.h" #include #include "libboardgame_base/GeometryUtil.h" #include "libboardgame_util/Assert.h" #include "libboardgame_util/Log.h" namespace libpentobi_base { using namespace std; using libboardgame_base::geometry_util::normalize_offset; using libboardgame_base::geometry_util::type_match_shift; using libboardgame_util::log; //----------------------------------------------------------------------------- namespace { const bool log_piece_creation = false; struct NormalizedPoints { /** The normalized points of the transformed piece. The points were shifted using GeometryUtil::normalize_offset(). */ PiecePoints points; /** The point type of (0,0) in the normalized points. */ unsigned point_type; bool operator==(const NormalizedPoints& n) const { return points == n.points && point_type == n.point_type; } }; #if LIBBOARDGAME_DEBUG /** Check consistency of transformations. Checks that the point list (which must be already sorted) has no duplicates. */ bool check_consistency(const PiecePoints& points) { for (unsigned i = 0; i < points.size(); ++i) if (i > 0 && points[i] == points[i - 1]) return false; return true; } #endif // LIBBOARDGAME_DEBUG /** Bring piece points into a normal form that is constant under translation. */ NormalizedPoints normalize(const PiecePoints& points, unsigned point_type, const Geometry& geometry) { if (log_piece_creation) log() << "Points " << points << '\n'; NormalizedPoints normalized; normalized.points = points; type_match_shift(geometry, normalized.points.begin(), normalized.points.end(), point_type); if (log_piece_creation) log() << "Point type " << point_type << ", type match shift " << normalized.points << '\n'; // Make the coordinates positive and minimal unsigned width; // unused unsigned height; // unused CoordPoint offset; normalize_offset(normalized.points.begin(), normalized.points.end(), width, height, offset); normalized.point_type = geometry.get_point_type(offset); // Sort the coordinates sort(normalized.points.begin(), normalized.points.end()); return normalized; } } // namespace //----------------------------------------------------------------------------- PieceInfo::PieceInfo(const string& name, const PiecePoints& points, const Geometry& geometry, const PieceTransforms& transforms) : m_name(name), m_points(points), m_transforms(&transforms) { if (log_piece_creation) log() << "Creating transformations for piece " << name << ' ' << points << '\n'; LIBBOARDGAME_ASSERT(points.contains(CoordPoint(0, 0))); vector all_transformed_points; PiecePoints transformed_points; for (const Transform* transform : transforms.get_all()) { if (log_piece_creation) log() << "Transformation " << typeid(*transform).name() << '\n'; transformed_points = points; transform->transform(transformed_points.begin(), transformed_points.end()); NormalizedPoints normalized = normalize(transformed_points, transform->get_new_point_type(), geometry); if (log_piece_creation) log() << "Normalized " << normalized.points << " point type " << normalized.point_type << '\n'; LIBBOARDGAME_ASSERT(check_consistency(normalized.points)); auto begin = all_transformed_points.begin(); auto end = all_transformed_points.end(); auto pos = find(begin, end, normalized); if (pos != end) { if (log_piece_creation) log() << "Equivalent to " << (pos - begin) << '\n'; m_equivalent_transform[transform] = transforms.get_all()[pos - begin]; } else { if (log_piece_creation) log() << "New (" << m_uniq_transforms.size() << ")\n"; m_equivalent_transform[transform] = transform; m_uniq_transforms.push_back(transform); } all_transformed_points.push_back(normalized); }; } bool PieceInfo::can_flip_horizontally(const Transform* transform) const { transform = get_equivalent_transform(transform); auto flip = get_equivalent_transform( m_transforms->get_mirrored_horizontally(transform)); return flip != transform; } bool PieceInfo::can_flip_vertically(const Transform* transform) const { transform = get_equivalent_transform(transform); auto flip = get_equivalent_transform( m_transforms->get_mirrored_vertically(transform)); return flip != transform; } bool PieceInfo::can_rotate() const { auto transform = m_uniq_transforms[0]; auto rotate = get_equivalent_transform( m_transforms->get_rotated_clockwise(transform)); return rotate != transform; } const Transform* PieceInfo::find_transform(const Geometry& geometry, const Points& points) const { NormalizedPoints normalized = normalize(points, geometry.get_point_type(0, 0), geometry); for (const Transform* transform : get_transforms()) { Points piece_points = get_points(); transform->transform(piece_points.begin(), piece_points.end()); NormalizedPoints normalized_piece = normalize(piece_points, transform->get_new_point_type(), geometry); if (normalized_piece == normalized) return transform; } return nullptr; } const Transform* PieceInfo::get_equivalent_transform( const Transform* transform) const { auto pos = m_equivalent_transform.find(transform); if (pos == m_equivalent_transform.end()) { LIBBOARDGAME_ASSERT(false); return nullptr; } return pos->second; } const Transform* PieceInfo::get_next_transform(const Transform* transform) const { transform = get_equivalent_transform(transform); auto begin = m_uniq_transforms.begin(); auto end = m_uniq_transforms.end(); auto pos = find(begin, end, transform); LIBBOARDGAME_ASSERT(pos != end); if (pos + 1 == end) return *begin; else return *(pos + 1); } const Transform* PieceInfo::get_previous_transform( const Transform* transform) const { transform = get_equivalent_transform(transform); auto begin = m_uniq_transforms.begin(); auto end = m_uniq_transforms.end(); auto pos = find(begin, end, transform); LIBBOARDGAME_ASSERT(pos != end); if (pos == begin) return *(end - 1); else return *(pos - 1); } //----------------------------------------------------------------------------- } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/PieceInfo.h000066400000000000000000000074151227240712600207560ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/PieceInfo.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_PIECE_INFO_H #define LIBPENTOBI_BASE_PIECE_INFO_H #include #include #include #include "Geometry.h" #include "PieceTransforms.h" #include "libboardgame_base/CoordPoint.h" #include "libboardgame_base/Transform.h" #include "libboardgame_util/ArrayList.h" namespace libpentobi_base { using namespace std; using libboardgame_base::CoordPoint; using libboardgame_base::Transform; using libboardgame_util::ArrayList; //----------------------------------------------------------------------------- class PieceInfo { public: /** Maximum number of fields of a piece. */ static const unsigned max_size = 6; typedef ArrayList Points; /** Maximum number of adjacent points to a piece. */ static const unsigned max_adj = 12; /** Maximum number of attach points of a piece. */ static const unsigned max_attach = 14; /** Maximum number of attach and adjacent points of a piece. */ static const unsigned max_adj_attach = 22; /** Constructor. @param name A short unique name for the piece. @param points The coordinates of the fields. Must contain the point (0, 0). (0, 0) should be a field in or near the center of the coordinates; it is used as the center when moving the piece or for drawing a label on the piece. @param geometry @param transforms */ PieceInfo(const string& name, const Points& points, const Geometry& geometry, const PieceTransforms& transforms); const string& get_name() const; const Points& get_points() const; /** Return the number of fields of the piece. */ unsigned get_size() const; /** Get a list with unique transformations. The list has the same order as PieceTransforms::get_all() but transformations that are equivalent to a previous transformation (because of a symmetry of the piece) are omitted. */ const vector& get_transforms() const; /** Get next transform from the list of unique transforms. */ const Transform* get_next_transform(const Transform* transform) const; /** Get previous transform from the list of unique transforms. */ const Transform* get_previous_transform(const Transform* transform) const; /** Get the transform from the list of unique transforms that is equivalent to a given transform. */ const Transform* get_equivalent_transform(const Transform* transform) const; bool can_rotate() const; bool can_flip_horizontally(const Transform* transform) const; bool can_flip_vertically(const Transform* transform) const; const Transform* find_transform(const Geometry& geometry, const Points& points) const; private: string m_name; Points m_points; vector m_uniq_transforms; map m_equivalent_transform; const PieceTransforms* m_transforms; }; inline const string& PieceInfo::get_name() const { return m_name; } inline const PieceInfo::Points& PieceInfo::get_points() const { return m_points; } inline unsigned PieceInfo::get_size() const { return static_cast(m_points.size()); } inline const vector& PieceInfo::get_transforms() const { return m_uniq_transforms; } //----------------------------------------------------------------------------- typedef PieceInfo::Points PiecePoints; //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_PIECE_INFO_H pentobi-7.2/src/libpentobi_base/PieceMap.h000066400000000000000000000037731227240712600206030ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/PieceMap.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_PIECE_MAP_H #define LIBPENTOBI_BASE_PIECE_MAP_H #include "Piece.h" namespace libpentobi_base { //----------------------------------------------------------------------------- /** Container mapping a unique piece to another element type. The elements must be default-constructible. */ template class PieceMap { public: PieceMap(); PieceMap(const T& val); PieceMap& operator=(const PieceMap& piece_map); bool operator==(const PieceMap& piece_map) const; T& operator[](Piece piece); const T& operator[](Piece piece) const; void fill(const T& val); private: T m_a[Piece::range_not_null]; }; template inline PieceMap::PieceMap() { } template inline PieceMap::PieceMap(const T& val) { fill(val); } template PieceMap& PieceMap::operator=(const PieceMap& piece_map) { for (unsigned i = 0; i < Piece::range_not_null; ++i) m_a[i] = piece_map.m_a[i]; return *this; } template bool PieceMap::operator==(const PieceMap& piece_map) const { for (unsigned i = 0; i < Piece::range_not_null; ++i) if (m_a[i] != piece_map.m_a[i]) return false; return true; } template inline T& PieceMap::operator[](Piece piece) { LIBBOARDGAME_ASSERT(! piece.is_null()); return m_a[piece.to_int()]; } template inline const T& PieceMap::operator[](Piece piece) const { LIBBOARDGAME_ASSERT(! piece.is_null()); return m_a[piece.to_int()]; } template void PieceMap::fill(const T& val) { for (unsigned i = 0; i < Piece::range_not_null; ++i) m_a[i] = val; } //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_PIECE_MAP_H pentobi-7.2/src/libpentobi_base/PieceTransforms.cpp000066400000000000000000000010501227240712600225410ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/PieceTransforms.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "PieceTransforms.h" namespace libpentobi_base { //----------------------------------------------------------------------------- PieceTransforms::~PieceTransforms() { } //----------------------------------------------------------------------------- } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/PieceTransforms.h000066400000000000000000000034361227240712600222200ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/PieceTransforms.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_PIECE_TRANSFORMS_H #define LIBPENTOBI_PIECE_TRANSFORMS_H #include #include "libboardgame_base/Geometry.h" #include "libboardgame_base/Transform.h" #include "libboardgame_util/Assert.h" namespace libpentobi_base { using namespace std; using libboardgame_base::Transform; //----------------------------------------------------------------------------- class PieceTransforms { public: virtual ~PieceTransforms(); virtual const Transform* get_mirrored_horizontally( const Transform* transf) const = 0; virtual const Transform* get_mirrored_vertically( const Transform* transf) const = 0; virtual const Transform* get_rotated_anticlockwise( const Transform* transf) const = 0; virtual const Transform* get_rotated_clockwise( const Transform* transf) const = 0; const vector& get_all() const; const Transform* get_default() const; protected: /** All piece transformations. Must be initialized in constructor of subclass. */ vector m_all; }; inline const vector& PieceTransforms::get_all() const { return m_all; } inline const Transform* PieceTransforms::get_default() const { LIBBOARDGAME_ASSERT(m_all.size() > 0); return m_all[0]; } //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_PIECE_TRANSFORMS_H pentobi-7.2/src/libpentobi_base/PieceTransformsClassic.cpp000066400000000000000000000067541227240712600240630ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/PieceTransformsClassic.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "PieceTransformsClassic.h" #include "libboardgame_util/Assert.h" namespace libpentobi_base { //----------------------------------------------------------------------------- PieceTransformsClassic::PieceTransformsClassic() { m_all.reserve(8); m_all.push_back(&m_identity); m_all.push_back(&m_rot90); m_all.push_back(&m_rot180); m_all.push_back(&m_rot270); m_all.push_back(&m_refl); m_all.push_back(&m_rot90refl); m_all.push_back(&m_rot180refl); m_all.push_back(&m_rot270refl); } const Transform* PieceTransformsClassic::get_mirrored_horizontally( const Transform* transf) const { if (transf == &m_identity) return &m_refl; if (transf == &m_rot90) return &m_rot270refl; if (transf == &m_rot180) return &m_rot180refl; if (transf == &m_rot270) return &m_rot90refl; if (transf == &m_refl) return &m_identity; if (transf == &m_rot90refl) return &m_rot270; if (transf == &m_rot180refl) return &m_rot180; if (transf == &m_rot270refl) return &m_rot90; LIBBOARDGAME_ASSERT(false); return nullptr; } const Transform* PieceTransformsClassic::get_mirrored_vertically( const Transform* transf) const { if (transf == &m_identity) return &m_rot180refl; if (transf == &m_rot90) return &m_rot90refl; if (transf == &m_rot180) return &m_refl; if (transf == &m_rot270) return &m_rot270refl; if (transf == &m_refl) return &m_rot180; if (transf == &m_rot90refl) return &m_rot90; if (transf == &m_rot180refl) return &m_identity; if (transf == &m_rot270refl) return &m_rot270; LIBBOARDGAME_ASSERT(false); return nullptr; } const Transform* PieceTransformsClassic::get_rotated_anticlockwise( const Transform* transf) const { if (transf == &m_identity) return &m_rot270; if (transf == &m_rot90) return &m_identity; if (transf == &m_rot180) return &m_rot90; if (transf == &m_rot270) return &m_rot180; if (transf == &m_refl) return &m_rot270refl; if (transf == &m_rot90refl) return &m_refl; if (transf == &m_rot180refl) return &m_rot90refl; if (transf == &m_rot270refl) return &m_rot180refl; LIBBOARDGAME_ASSERT(false); return nullptr; } const Transform* PieceTransformsClassic::get_rotated_clockwise( const Transform* transf) const { if (transf == &m_identity) return &m_rot90; if (transf == &m_rot90) return &m_rot180; if (transf == &m_rot180) return &m_rot270; if (transf == &m_rot270) return &m_identity; if (transf == &m_refl) return &m_rot90refl; if (transf == &m_rot90refl) return &m_rot180refl; if (transf == &m_rot180refl) return &m_rot270refl; if (transf == &m_rot270refl) return &m_refl; LIBBOARDGAME_ASSERT(false); return nullptr; } //----------------------------------------------------------------------------- } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/PieceTransformsClassic.h000066400000000000000000000031711227240712600235160ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/PieceTransformsClassic.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_PIECE_TRANSFORMS_CLASSIC_H #define LIBPENTOBI_PIECE_TRANSFORMS_CLASSIC_H #include "PieceTransforms.h" namespace libpentobi_base { using libboardgame_base::TransfIdentity; using libboardgame_base::TransfRectRot90; using libboardgame_base::TransfRectRot180; using libboardgame_base::TransfRectRot270; using libboardgame_base::TransfRectRefl; using libboardgame_base::TransfRectRot90Refl; using libboardgame_base::TransfRectRot180Refl; using libboardgame_base::TransfRectRot270Refl; //----------------------------------------------------------------------------- class PieceTransformsClassic : public PieceTransforms { public: PieceTransformsClassic(); const Transform* get_mirrored_horizontally(const Transform* transf) const; const Transform* get_mirrored_vertically(const Transform* transf) const; const Transform* get_rotated_anticlockwise(const Transform* transf) const; const Transform* get_rotated_clockwise(const Transform* transf) const; private: TransfIdentity m_identity; TransfRectRot90 m_rot90; TransfRectRot180 m_rot180; TransfRectRot270 m_rot270; TransfRectRefl m_refl; TransfRectRot90Refl m_rot90refl; TransfRectRot180Refl m_rot180refl; TransfRectRot270Refl m_rot270refl; }; //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_PIECE_TRANSFORMS_CLASSIC_H pentobi-7.2/src/libpentobi_base/PieceTransformsTrigon.cpp000066400000000000000000000112141227240712600237270ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/PieceTransformsTrigon.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "PieceTransformsTrigon.h" #include "libboardgame_util/Assert.h" #include "libboardgame_util/Log.h" namespace libpentobi_base { using libboardgame_util::log; //----------------------------------------------------------------------------- PieceTransformsTrigon::PieceTransformsTrigon() { m_all.reserve(12); m_all.push_back(&m_identity); m_all.push_back(&m_rot60); m_all.push_back(&m_rot120); m_all.push_back(&m_rot180); m_all.push_back(&m_rot240); m_all.push_back(&m_rot300); m_all.push_back(&m_refl); m_all.push_back(&m_refl_rot60); m_all.push_back(&m_refl_rot120); m_all.push_back(&m_refl_rot180); m_all.push_back(&m_refl_rot240); m_all.push_back(&m_refl_rot300); } const Transform* PieceTransformsTrigon::get_mirrored_horizontally( const Transform* transf) const { if (transf == &m_identity) return &m_refl; if (transf == &m_rot60) return &m_refl_rot300; if (transf == &m_rot120) return &m_refl_rot240; if (transf == &m_rot180) return &m_refl_rot180; if (transf == &m_rot240) return &m_refl_rot120; if (transf == &m_rot300) return &m_refl_rot60; if (transf == &m_refl) return &m_identity; if (transf == &m_refl_rot60) return &m_rot300; if (transf == &m_refl_rot120) return &m_rot240; if (transf == &m_refl_rot180) return &m_rot180; if (transf == &m_refl_rot240) return &m_rot120; if (transf == &m_refl_rot300) return &m_rot60; LIBBOARDGAME_ASSERT(false); return nullptr; } const Transform* PieceTransformsTrigon::get_mirrored_vertically( const Transform* transf) const { if (transf == &m_identity) return &m_refl_rot180; if (transf == &m_rot60) return &m_refl_rot120; if (transf == &m_rot120) return &m_refl_rot60; if (transf == &m_rot180) return &m_refl; if (transf == &m_rot240) return &m_refl_rot300; if (transf == &m_rot300) return &m_refl_rot240; if (transf == &m_refl) return &m_rot180; if (transf == &m_refl_rot60) return &m_rot120; if (transf == &m_refl_rot120) return &m_rot60; if (transf == &m_refl_rot180) return &m_identity; if (transf == &m_refl_rot240) return &m_rot300; if (transf == &m_refl_rot300) return &m_rot240; LIBBOARDGAME_ASSERT(false); return nullptr; } const Transform* PieceTransformsTrigon::get_rotated_anticlockwise( const Transform* transf) const { if (transf == &m_identity) return &m_rot300; if (transf == &m_rot60) return &m_identity; if (transf == &m_rot120) return &m_rot60; if (transf == &m_rot180) return &m_rot120; if (transf == &m_rot240) return &m_rot180; if (transf == &m_rot300) return &m_rot240; if (transf == &m_refl) return &m_refl_rot300; if (transf == &m_refl_rot60) return &m_refl; if (transf == &m_refl_rot120) return &m_refl_rot60; if (transf == &m_refl_rot180) return &m_refl_rot120; if (transf == &m_refl_rot240) return &m_refl_rot180; if (transf == &m_refl_rot300) return &m_refl_rot240; LIBBOARDGAME_ASSERT(false); return nullptr; } const Transform* PieceTransformsTrigon::get_rotated_clockwise( const Transform* transf) const { if (transf == &m_identity) return &m_rot60; if (transf == &m_rot60) return &m_rot120; if (transf == &m_rot120) return &m_rot180; if (transf == &m_rot180) return &m_rot240; if (transf == &m_rot240) return &m_rot300; if (transf == &m_rot300) return &m_identity; if (transf == &m_refl) return &m_refl_rot60; if (transf == &m_refl_rot60) return &m_refl_rot120; if (transf == &m_refl_rot120) return &m_refl_rot180; if (transf == &m_refl_rot180) return &m_refl_rot240; if (transf == &m_refl_rot240) return &m_refl_rot300; if (transf == &m_refl_rot300) return &m_refl; LIBBOARDGAME_ASSERT(false); return nullptr; } //----------------------------------------------------------------------------- } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/PieceTransformsTrigon.h000066400000000000000000000037501227240712600234020ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/PieceTransformsTrigon.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_PIECE_TRANSFORMS_TRIGON_H #define LIBPENTOBI_PIECE_TRANSFORMS_TRIGON_H #include "PieceTransforms.h" namespace libpentobi_base { using libboardgame_base::TransfIdentity; using libboardgame_base::TransfTrigonRefl; using libboardgame_base::TransfTrigonReflRot60; using libboardgame_base::TransfTrigonReflRot120; using libboardgame_base::TransfTrigonReflRot180; using libboardgame_base::TransfTrigonReflRot240; using libboardgame_base::TransfTrigonReflRot300; using libboardgame_base::TransfTrigonRot60; using libboardgame_base::TransfTrigonRot120; using libboardgame_base::TransfTrigonRot180; using libboardgame_base::TransfTrigonRot240; using libboardgame_base::TransfTrigonRot300; //----------------------------------------------------------------------------- class PieceTransformsTrigon : public PieceTransforms { public: PieceTransformsTrigon(); const Transform* get_mirrored_horizontally(const Transform* transf) const; const Transform* get_mirrored_vertically(const Transform* transf) const; const Transform* get_rotated_anticlockwise(const Transform* transf) const; const Transform* get_rotated_clockwise(const Transform* transf) const; private: TransfIdentity m_identity; TransfTrigonRot60 m_rot60; TransfTrigonRot120 m_rot120; TransfTrigonRot180 m_rot180; TransfTrigonRot240 m_rot240; TransfTrigonRot300 m_rot300; TransfTrigonRefl m_refl; TransfTrigonReflRot60 m_refl_rot60; TransfTrigonReflRot120 m_refl_rot120; TransfTrigonReflRot180 m_refl_rot180; TransfTrigonReflRot240 m_refl_rot240; TransfTrigonReflRot300 m_refl_rot300; }; //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_PIECE_TRANSFORMS_TRIGON_H pentobi-7.2/src/libpentobi_base/Player.cpp000066400000000000000000000011251227240712600206740ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Player.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Player.h" namespace libpentobi_base { using namespace std; //----------------------------------------------------------------------------- Player::~Player() throw() { } bool Player::resign() const { return false; } //----------------------------------------------------------------------------- } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/Player.h000066400000000000000000000016501227240712600203440ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Player.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_PLAYER_H #define LIBPENTOBI_BASE_PLAYER_H #include "Board.h" namespace libpentobi_base { //----------------------------------------------------------------------------- class Player { public: virtual ~Player() throw(); virtual Move genmove(const Board& bd, Color c) = 0; /** Check if the player wants to resign. This may only be called after a genmove() and returns true if the players wants to resign in the position at the last genmove(). The default implementation returns false. */ virtual bool resign() const; }; //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_PLAYER_H pentobi-7.2/src/libpentobi_base/Point.h000066400000000000000000000016021227240712600201760ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Point.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_POINT_H #define LIBPENTOBI_BASE_POINT_H #include "libboardgame_base/Point.h" #include "libboardgame_base/SpreadsheetStringRep.h" namespace libpentobi_base { using libboardgame_base::SpreadsheetStringRep; //----------------------------------------------------------------------------- /** Point (coordinate of on-board field) for Blokus game variants. Supports RectGeometry up to size 20 and TrigonGeometry up to edge size 9. */ typedef libboardgame_base::Point<35,20,unsigned short,SpreadsheetStringRep,685> Point; //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_POINT_H pentobi-7.2/src/libpentobi_base/PointList.h000066400000000000000000000012201227240712600210260ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/PointList.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_POINT_LIST_H #define LIBPENTOBI_BASE_POINT_LIST_H #include "Point.h" #include "libboardgame_base/PointList.h" namespace libpentobi_base { //----------------------------------------------------------------------------- typedef libboardgame_base::PointList PointList; //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_POINT_LIST_H pentobi-7.2/src/libpentobi_base/PointState.cpp000066400000000000000000000012461227240712600215360ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/PointState.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "PointState.h" #include namespace libpentobi_base { //----------------------------------------------------------------------------- ostream& operator<<(ostream& out, const PointState& s) { if (s.is_color()) out << s.to_color(); else out << 'E'; return out; } //----------------------------------------------------------------------------- } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/PointState.h000066400000000000000000000066771227240712600212200ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/PointState.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_POINTSTATE_H #define LIBPENTOBI_BASE_POINTSTATE_H #include "Color.h" namespace libpentobi_base { using namespace std; //----------------------------------------------------------------------------- /** State of an on-board point for games in which the point state can be a color or empty */ class PointState { public: typedef Color::IntType IntType; class Iterator { friend class PointState; public: Iterator(); operator bool() const; void operator++(); PointState operator*() const; private: IntType m_i; }; static const IntType range = Color::range + 1; static const IntType value_empty = range - 1; PointState(); PointState(const PointState& s); PointState(Color c); explicit PointState(IntType i); bool operator==(const PointState& s) const; bool operator!=(const PointState& s) const; bool operator==(const Color& c) const; bool operator!=(const Color& c) const; IntType to_int() const; static PointState empty(); bool is_empty() const; bool is_color() const; Color to_color() const; private: static const IntType value_uninitialized = range; IntType m_i; bool is_initialized() const; }; typedef PointState::Iterator PointStateIterator; inline PointState::Iterator::Iterator() : m_i(0) { } inline PointState::Iterator::operator bool() const { return m_i < PointState::range; } inline void PointState::Iterator::operator++() { ++m_i; } inline PointState PointState::Iterator::operator*() const { LIBBOARDGAME_ASSERT(operator bool()); return PointState(m_i); } inline PointState::PointState() { #if LIBBOARDGAME_DEBUG m_i = value_uninitialized; #endif } inline PointState::PointState(const PointState& s) { m_i = s.m_i; } inline PointState::PointState(Color c) { m_i = c.to_int(); } inline PointState::PointState(IntType i) { LIBBOARDGAME_ASSERT(i < range); m_i = i; } inline bool PointState::operator==(const PointState& p) const { return m_i == p.m_i; } inline bool PointState::operator==(const Color& c) const { return m_i == c.to_int(); } inline bool PointState::operator!=(const PointState& s) const { return ! operator==(s); } inline bool PointState::operator!=(const Color& c) const { return ! operator==(c); } inline PointState PointState::empty() { return PointState(value_empty); } inline bool PointState::is_initialized() const { return m_i < value_uninitialized; } inline bool PointState::is_color() const { LIBBOARDGAME_ASSERT(is_initialized()); return m_i != value_empty; } inline bool PointState::is_empty() const { LIBBOARDGAME_ASSERT(is_initialized()); return m_i == value_empty; } inline Color PointState::to_color() const { LIBBOARDGAME_ASSERT(is_color()); return Color(m_i); } inline PointState::IntType PointState::to_int() const { LIBBOARDGAME_ASSERT(is_initialized()); return m_i; } //----------------------------------------------------------------------------- ostream& operator<<(ostream& out, const PointState& s); //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_POINTSTATE_H pentobi-7.2/src/libpentobi_base/Setup.h000066400000000000000000000022261227240712600202100ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Setup.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_SETUP_H #define LIBPENTOBI_BASE_SETUP_H #include "ColorMap.h" #include "Move.h" namespace libpentobi_base { //----------------------------------------------------------------------------- /** Definition of a setup position. A setup position consists of a number of pieces that are placed at once (in no particular order) on the board and a color to play next. */ struct Setup { /** Maximum number of pieces on board per color. */ static const unsigned max_pieces = 24; typedef ArrayList PlacementList; Color to_play; ColorMap placements; Setup(); void clear(); }; inline Setup::Setup() : to_play(Color(0)) { } inline void Setup::clear() { to_play = Color(0); LIBPENTOBI_FOREACH_COLOR(c, placements[c].clear()); } //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_SETUP_H pentobi-7.2/src/libpentobi_base/SgfUtil.cpp000066400000000000000000000024011227240712600210130ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/SgfUtil.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "SgfUtil.h" #include "libboardgame_util/Assert.h" namespace libpentobi_base { namespace sgf_util { //----------------------------------------------------------------------------- const char* get_color_id(Variant variant, Color c) { if (variant == Variant::duo || variant == Variant::junior) return (c == Color(0) ? "B" : "W"); if (c == Color(0)) return "1"; if (c == Color(1)) return "2"; if (c == Color(2)) return "3"; LIBBOARDGAME_ASSERT(c == Color(3)); return "4"; } const char* get_setup_id(Variant variant, Color c) { if (variant == Variant::duo || variant == Variant::junior) return (c == Color(0) ? "AB" : "AW"); if (c == Color(0)) return "A1"; if (c == Color(1)) return "A2"; if (c == Color(2)) return "A3"; LIBBOARDGAME_ASSERT(c == Color(3)); return "A4"; } //----------------------------------------------------------------------------- } // namespace sgf_util } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/SgfUtil.h000066400000000000000000000015221227240712600204630ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/SgfUtil.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_SGF_UTIL_H #define LIBPENTOBI_BASE_SGF_UTIL_H #include "Color.h" #include "Variant.h" namespace libpentobi_base { namespace sgf_util { //----------------------------------------------------------------------------- /** Get SGF move property ID for a color in a game variant. */ const char* get_color_id(Variant variant, Color c); /** Get SGF setup property ID for a color in a game variant. */ const char* get_setup_id(Variant variant, Color c); //----------------------------------------------------------------------------- } // namespace sgf_util } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_SGF_UTIL_H pentobi-7.2/src/libpentobi_base/StartingPoints.cpp000066400000000000000000000052461227240712600224400ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/StartingPoints.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "StartingPoints.h" namespace libpentobi_base { using namespace std; //----------------------------------------------------------------------------- void StartingPoints::add_colored_starting_point(unsigned x, unsigned y, Color c) { Point p(x, y); m_is_colored_starting_point[p] = true; m_starting_point_color[p] = c; m_starting_points[c].push_back(p); } void StartingPoints::add_colorless_starting_point(unsigned x, unsigned y) { Point p(x, y); m_is_colorless_starting_point[p] = true; m_starting_points[Color(0)].push_back(p); m_starting_points[Color(1)].push_back(p); m_starting_points[Color(2)].push_back(p); m_starting_points[Color(3)].push_back(p); } void StartingPoints::init(Variant variant, const Geometry& geometry) { m_is_colored_starting_point.init(geometry, false); m_is_colorless_starting_point.init(geometry, false); m_starting_point_color.init(geometry); m_starting_points[Color(0)].clear(); m_starting_points[Color(1)].clear(); m_starting_points[Color(2)].clear(); m_starting_points[Color(3)].clear(); if (variant == Variant::classic || variant == Variant::classic_2) { add_colored_starting_point(0, 19, Color(0)); add_colored_starting_point(19, 19, Color(1)); add_colored_starting_point(19, 0, Color(2)); add_colored_starting_point(0, 0, Color(3)); } else if (variant == Variant::duo || variant == Variant::junior) { add_colored_starting_point(4, 9, Color(0)); add_colored_starting_point(9, 4, Color(1)); } else if (variant == Variant::trigon || variant == Variant::trigon_2) { add_colorless_starting_point(17, 3); add_colorless_starting_point(17, 14); add_colorless_starting_point(9, 6); add_colorless_starting_point(9, 11); add_colorless_starting_point(25, 6); add_colorless_starting_point(25, 11); } else if (variant == Variant::trigon_3) { add_colorless_starting_point(15, 2); add_colorless_starting_point(15, 13); add_colorless_starting_point(7, 5); add_colorless_starting_point(7, 10); add_colorless_starting_point(23, 5); add_colorless_starting_point(23, 10); } else LIBBOARDGAME_ASSERT(false); } //----------------------------------------------------------------------------- } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/StartingPoints.h000066400000000000000000000041221227240712600220750ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/StartingPoints.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_STARTING_POINTS_H #define LIBPENTOBI_BASE_STARTING_POINTS_H #include "Color.h" #include "ColorMap.h" #include "Geometry.h" #include "Grid.h" #include "Variant.h" #include "libboardgame_util/ArrayList.h" namespace libpentobi_base { using libboardgame_util::ArrayList; //----------------------------------------------------------------------------- class StartingPoints { public: static const unsigned max_starting_points = 6; void init(Variant variant, const Geometry& geometry); bool is_colored_starting_point(Point p) const; bool is_colorless_starting_point(Point p) const; Color get_starting_point_color(Point p) const; const ArrayList& get_starting_points(Color c) const; private: Grid m_is_colored_starting_point; Grid m_is_colorless_starting_point; Grid m_starting_point_color; ColorMap> m_starting_points; void add_colored_starting_point(unsigned x, unsigned y, Color c); void add_colorless_starting_point(unsigned x, unsigned y); }; inline Color StartingPoints::get_starting_point_color(Point p) const { LIBBOARDGAME_ASSERT(m_is_colored_starting_point[p]); return m_starting_point_color[p]; } inline const ArrayList& StartingPoints::get_starting_points(Color c) const { return m_starting_points[c]; } inline bool StartingPoints::is_colored_starting_point(Point p) const { return m_is_colored_starting_point[p]; } inline bool StartingPoints::is_colorless_starting_point(Point p) const { return m_is_colorless_starting_point[p]; } //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_STARTING_POINTS_H pentobi-7.2/src/libpentobi_base/SymmetricPoints.cpp000066400000000000000000000015521227240712600226150ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file SymmetricPoints.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "SymmetricPoints.h" namespace libpentobi_base { //----------------------------------------------------------------------------- void SymmetricPoints::init(const Geometry& geometry, const PointTransform& transform) { m_symmetric_point.init(geometry); unsigned width = geometry.get_width(); unsigned height = geometry.get_height(); for (Geometry::Iterator i(geometry); i; ++i) m_symmetric_point[*i] = transform.get_transformed(*i, width, height); } //----------------------------------------------------------------------------- } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/SymmetricPoints.h000066400000000000000000000020571227240712600222630ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/SymmetricPoints.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_SYMMETRIC_POINTS_H #define LIBPENTOBI_BASE_SYMMETRIC_POINTS_H #include "Grid.h" #include "libboardgame_base/PointTransform.h" namespace libpentobi_base { using libboardgame_base::PointTransform; //----------------------------------------------------------------------------- /** Lookup table to quickly get points that are symmetric with respect to the center of the board. */ class SymmetricPoints { public: void init(const Geometry& geometry, const PointTransform& transform); Point operator[](Point p) const; private: Grid m_symmetric_point; }; inline Point SymmetricPoints::operator[](Point p) const { return m_symmetric_point[p]; } //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_SYMMETRIC_POINTS_H pentobi-7.2/src/libpentobi_base/Tree.cpp000066400000000000000000000252421227240712600203450ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Tree.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Tree.h" #include "BoardUpdater.h" #include "BoardUtil.h" #include "NodeUtil.h" #include "libboardgame_sgf/Util.h" #include "libboardgame_util/StringUtil.h" namespace libpentobi_base { using libboardgame_sgf::ChildIterator; using libboardgame_sgf::InvalidPropertyValue; using libboardgame_sgf::InvalidTree; using libboardgame_sgf::util::get_go_point_property_value; using libboardgame_sgf::util::parse_go_move_property_value; using libboardgame_sgf::util::parse_go_point_property_value; using libboardgame_util::to_string; using libpentobi_base::boardutil::get_current_position_as_setup; //----------------------------------------------------------------------------- Tree::Tree(Variant variant) { init_variant(variant); } Tree::Tree(unique_ptr& root) { init(root); } const Node& Tree::add_setup(const Node& node, Color c, Move mv) { const Node* result; if (has_move(node)) result = &create_new_child(node); else result = &node; Setup::PlacementList add_empty = get_setup_property(*result, "AE"); if (add_empty.remove(mv)) set_setup_property(*result, "AE", add_empty); auto id = get_setup_prop_id(c); Setup::PlacementList add_color = get_setup_property(*result, id); if (add_color.include(mv)) set_setup_property(*result, id, add_color); return *result; } const Node* Tree::find_child_with_move(const Node& node, ColorMove mv) const { for (ChildIterator i(node); i; ++i) if (get_move(*i) == mv) return &(*i); return nullptr; } ColorMove Tree::get_move(const Node& node) const { Color c; MovePoints points; if (! libpentobi_base::node_util::get_move(node, m_variant, c, points)) return ColorMove::null(); if (points.size() == 0) return ColorMove(c, Move::pass()); Move mv; if (! m_board_const->find_move(points, mv)) throw InvalidTree("Illegal move " + to_string(points)); return ColorMove(c, mv); } ColorMove Tree::get_move_ignore_invalid(const Node& node) const { try { return get_move(node); } catch (const InvalidTree&) { return ColorMove::null(); } } const Node* Tree::get_node_before_move_number(unsigned move_number) const { auto node = &get_root(); unsigned n = 0; while (node->has_children()) { auto& child = node->get_first_child(); if (! get_move(child).is_null() && n++ == move_number) return node; node = &child; } return nullptr; } string Tree::get_player_name(Color c) const { auto& root = get_root(); switch (m_variant) { case Variant::classic: case Variant::trigon: case Variant::trigon_3: if (c == Color(0)) return root.get_property("P1", ""); if (c == Color(1)) return root.get_property("P2", ""); if (c == Color(2)) return root.get_property("P3", ""); if (c == Color(3)) return root.get_property("P4", ""); break; case Variant::classic_2: case Variant::trigon_2: if (c == Color(0) || c == Color(2)) return root.get_property("PB", ""); if (c == Color(1) || c == Color(3)) return root.get_property("PW", ""); break; case Variant::duo: case Variant::junior: if (c == Color(0)) return root.get_property("PB", ""); if (c == Color(1)) return root.get_property("PW", ""); break; } LIBBOARDGAME_ASSERT(false); return ""; } Setup::PlacementList Tree::get_setup_property(const Node& node, const char* id) const { vector values = node.get_multi_property(id); Setup::PlacementList result; for (const string& s : values) result.push_back(m_board_const->from_string(s)); return result; } bool Tree::has_main_variation_moves() const { auto node = &get_root(); while (node != nullptr) { if (has_move_ignore_invalid(*node)) return true; node = node->get_first_child_or_null(); } return false; } void Tree::init(unique_ptr& root) { string game = root->get_property("GM"); Variant variant; if (! parse_variant(game, variant)) throw InvalidPropertyValue("GM", game); libboardgame_sgf::Tree::init(root); m_variant = variant; init_board_const(variant); } void Tree::init_board_const(Variant variant) { m_board_const = &BoardConst::get(variant); } void Tree::init_variant(Variant variant) { libboardgame_sgf::Tree::init(); m_variant = variant; set_game_property(); init_board_const(variant); clear_modified(); } void Tree::keep_only_subtree(const Node& node) { LIBBOARDGAME_ASSERT(contains(node)); if (&node == &get_root()) return; string charset = get_root().get_property("CA", ""); string application = get_root().get_property("AP", ""); bool create_new_setup = has_move(node); if (! create_new_setup) { auto current = node.get_parent_or_null(); while (current != nullptr) { if (has_move(*current) || node_util::has_setup(*current)) { create_new_setup = true; break; } current = current->get_parent_or_null(); } } if (create_new_setup) { unique_ptr bd(new Board(m_variant)); BoardUpdater updater(*this, *bd); updater.update(node); Setup setup; get_current_position_as_setup(*bd, setup); LIBBOARDGAME_ASSERT(! node_util::has_setup(node)); set_setup(node, setup); } make_root(node); if (! application.empty()) { set_property(node, "AP", application); move_property_to_front(node, "AP"); } if (! charset.empty()) { set_property(node, "CA", charset); move_property_to_front(node, "CA"); } set_game_property(); } void Tree::remove_player(const Node& node) { remove_property(node, "PL"); } const Node& Tree::remove_setup(const Node& node, Color c, Move mv) { const Node* result; if (has_move(node)) result = &create_new_child(node); else result = &node; auto id = get_setup_prop_id(c); auto add_color = get_setup_property(*result, id); if (add_color.remove(mv)) set_setup_property(*result, id, add_color); else { Setup::PlacementList add_empty = get_setup_property(*result, "AE"); if (add_empty.include(mv)) set_setup_property(*result, "AE", add_empty); } return *result; } void Tree::set_game_property() { auto& root = get_root(); set_property(root, "GM", to_string(m_variant)); move_property_to_front(root, "GM"); } void Tree::set_move(const Node& node, Color c, Move mv) { auto id = get_color(c); if (! mv.is_pass()) set_property(node, id, m_board_const->to_string(mv, false)); else set_property(node, id, ""); } void Tree::set_player(const Node& node, Color c) { set_property(node, "PL", get_color(c)); } void Tree::set_player_name(Color c, const string& name) { auto& root = get_root(); switch (m_variant) { case Variant::classic: case Variant::trigon: case Variant::trigon_3: if (c == Color(0)) set_property(root, "P1", name); else if (c == Color(1)) set_property(root, "P2", name); else if (c == Color(2)) set_property(root, "P3", name); else if (c == Color(3)) set_property(root, "P4", name); else LIBBOARDGAME_ASSERT(false); return; case Variant::classic_2: case Variant::trigon_2: if (c == Color(0) || c == Color(2)) set_property(root, "PB", name); else if (c == Color(1) || c == Color(3)) set_property(root, "PW", name); else LIBBOARDGAME_ASSERT(false); return; case Variant::duo: case Variant::junior: if (c == Color(0)) set_property(root, "PB", name); else if (c == Color(1)) set_property(root, "PW", name); else LIBBOARDGAME_ASSERT(false); return; } LIBBOARDGAME_ASSERT(false); } void Tree::set_result(const Node& node, int score) { if (score > 0) { ostringstream s; s << "B+" << score; set_property(node, "RE", s.str()); } else if (score < 0) { ostringstream s; s << "W+" << (-score); set_property(node, "RE", s.str()); } else set_property(node, "RE", "0"); } void Tree::set_setup(const Node& node, const Setup& setup) { remove_property(node, "B"); remove_property(node, "W"); remove_property(node, "1"); remove_property(node, "2"); remove_property(node, "3"); remove_property(node, "4"); remove_property(node, "AB"); remove_property(node, "AW"); remove_property(node, "A1"); remove_property(node, "A2"); remove_property(node, "A3"); remove_property(node, "A4"); remove_property(node, "AE"); switch (m_variant) { case Variant::classic: case Variant::classic_2: case Variant::trigon: case Variant::trigon_2: set_setup_property(node, "A1", setup.placements[Color(0)]); set_setup_property(node, "A2", setup.placements[Color(1)]); set_setup_property(node, "A3", setup.placements[Color(2)]); set_setup_property(node, "A4", setup.placements[Color(3)]); break; case Variant::trigon_3: set_setup_property(node, "A1", setup.placements[Color(0)]); set_setup_property(node, "A2", setup.placements[Color(1)]); set_setup_property(node, "A3", setup.placements[Color(2)]); break; default: LIBBOARDGAME_ASSERT(m_variant == Variant::duo || m_variant == Variant::junior); set_setup_property(node, "AB", setup.placements[Color(0)]); set_setup_property(node, "AW", setup.placements[Color(1)]); } set_player(node, setup.to_play); } void Tree::set_setup_property(const Node& node, const char* id, const Setup::PlacementList& placements) { if (placements.empty()) { remove_property(node, id); return; } vector values; for (Move mv : placements) values.push_back(m_board_const->to_string(mv, false)); set_property(node, id, values); } //----------------------------------------------------------------------------- } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/Tree.h000066400000000000000000000105261227240712600200110ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Tree.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_TREE_H #define LIBPENTOBI_BASE_TREE_H #include "ColorMove.h" #include "BoardConst.h" #include "Variant.h" #include "Setup.h" #include "SgfUtil.h" #include "libboardgame_sgf/Tree.h" namespace libpentobi_base { using namespace std; using libboardgame_sgf::Node; //----------------------------------------------------------------------------- /** Blokus SGF tree. See also doc/blksgf/Pentobi-SGF.html in the Pentobi distribution for a description of the properties used. */ class Tree : public libboardgame_sgf::Tree { public: Tree(Variant variant); Tree(unique_ptr& root); void init(unique_ptr& root); void init_variant(Variant variant); void set_move(const Node& node, ColorMove mv); void set_move(const Node& node, Color c, Move mv); /** Return move or ColorMove::null() if node has no move property. @throws InvalidTree if the node has a move property with an invalid value. */ ColorMove get_move(const Node& node) const; /** Like get_move() but returns ColorMove::null() on invalid property value. */ ColorMove get_move_ignore_invalid(const Node& node) const; /** Same as ! get_move.is_null() */ bool has_move(const Node& node) const; /** Same as ! get_move_ignore_invalid.is_null() */ bool has_move_ignore_invalid(const Node& node) const; const Node* find_child_with_move(const Node& node, ColorMove mv) const; void set_result(const Node& node, int score); const Node* get_node_before_move_number(unsigned move_number) const; Variant get_variant() const; string get_player_name(Color c) const; void set_player_name(Color c, const string& name); const BoardConst& get_board_const() const; /** Check if any node in the main variation has a move. Invalid move properties are ignored. */ bool has_main_variation_moves() const; void keep_only_subtree(const Node& node); /** Add a piece as setup. @pre mv.is_regular() If the node already contains a move, a new child will be created. @pre The piece points must be empty on the board @return The node or the new child if one was created. */ const Node& add_setup(const Node& node, Color c, Move mv); /** Remove a piece using setup properties. @pre mv.is_regular() If the node already contains a move, a new child will be created. @pre The move must exist on the board @return The node or the new child if one was created. */ const Node& remove_setup(const Node& node, Color c, Move mv); /** Set the color to play in a setup position (PL property). */ void set_player(const Node& node, Color c); /** Remove the PL property. @see set_player() */ void remove_player(const Node& node); private: Variant m_variant; const BoardConst* m_board_const; const char* get_color(Color c) const; Setup::PlacementList get_setup_property(const Node& node, const char* id) const; const char* get_setup_prop_id(Color c) const; void set_setup(const Node& node, const Setup& setup); void init_board_const(Variant variant); void set_game_property(); void set_setup_property(const Node& node, const char* id, const Setup::PlacementList& placements); }; inline const BoardConst& Tree::get_board_const() const { return *m_board_const; } inline const char* Tree::get_color(Color c) const { return sgf_util::get_color_id(m_variant, c); } inline const char* Tree::get_setup_prop_id(Color c) const { return sgf_util::get_setup_id(m_variant, c); } inline Variant Tree::get_variant() const { return m_variant; } inline bool Tree::has_move(const Node& node) const { return ! get_move(node).is_null(); } inline bool Tree::has_move_ignore_invalid(const Node& node) const { return ! get_move_ignore_invalid(node).is_null(); } inline void Tree::set_move(const Node& node, ColorMove mv) { set_move(node, mv.color, mv.move); } //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_TREE_H pentobi-7.2/src/libpentobi_base/TreeUtil.cpp000066400000000000000000000026051227240712600212010ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/TreeUtil.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "TreeUtil.h" #include "NodeUtil.h" namespace libpentobi_base { namespace tree_util { //----------------------------------------------------------------------------- unsigned get_move_number(const Tree& tree, const Node& node) { unsigned move_number = 0; auto current = &node; while (current != nullptr) { if (tree.get_move_ignore_invalid(*current).is_regular()) ++move_number; if (libpentobi_base::node_util::has_setup(*current)) break; current = current->get_parent_or_null(); } return move_number; } unsigned get_moves_left(const Tree& tree, const Node& node) { unsigned moves_left = 0; auto current = node.get_first_child_or_null(); while (current != nullptr) { if (libpentobi_base::node_util::has_setup(*current)) break; if (tree.get_move_ignore_invalid(*current).is_regular()) ++moves_left; current = current->get_first_child_or_null(); } return moves_left; } //----------------------------------------------------------------------------- } // namespace tree_util } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/TreeUtil.h000066400000000000000000000022121227240712600206400ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/TreeUtil.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_TREE_UTIL_H #define LIBPENTOBI_BASE_TREE_UTIL_H #include "Tree.h" namespace libpentobi_base { namespace tree_util { //----------------------------------------------------------------------------- /** Get the move number at a node. Counts the number of non-pass moves since the root node or the last node that contained setup properties. Invalid moves are ignored. */ unsigned get_move_number(const Tree& tree, const Node& node); /** Get the number of remaining moves in the current variation. Counts the number of non-pass moves remaining in the current variation until the end of the variation or the next node that contains setup properties. Invalid moves are ignored. */ unsigned get_moves_left(const Tree& tree, const Node& node); //----------------------------------------------------------------------------- } // namespace tree_util } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_TREE_UTIL_H pentobi-7.2/src/libpentobi_base/TreeWriter.cpp000066400000000000000000000037711227240712600215450ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/TreeWriter.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "TreeWriter.h" namespace libpentobi_base { //----------------------------------------------------------------------------- TreeWriter::TreeWriter(ostream& out, const Tree& tree) : libboardgame_sgf::TreeWriter(out, tree.get_root()), m_variant(tree.get_variant()) { } TreeWriter::~TreeWriter() { } void TreeWriter::write_property(const string& id, const vector& values) { // Replace obsolete move property IDs or multi-valued move properties // as used by early versions of Pentobi if (id == "BLUE" || id == "YELLOW" || id == "GREEN" || id == "RED" || ((id == "1" || id == "2" || id == "3" || id == "4" || id == "B" || id == "W") && values.size() > 1)) { string new_id; if (id == "BLUE") new_id = "1"; else if (id == "YELLOW") new_id = "2"; else if (id == "GREEN") { if (m_variant == Variant::duo || m_variant == Variant::junior) new_id = "2"; else new_id = "4"; } else if (id == "RED") new_id = "3"; else new_id = id; if (values.size() < 2) libboardgame_sgf::TreeWriter::write_property(new_id, values); else { string val = values[0]; for (size_t i = 1; i < values.size(); ++i) val += "," + values[i]; vector new_values; new_values.push_back(val); libboardgame_sgf::TreeWriter::write_property(new_id, new_values); } } else libboardgame_sgf::TreeWriter::write_property(id, values); } //----------------------------------------------------------------------------- } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/TreeWriter.h000066400000000000000000000017761227240712600212150ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/TreeWriter.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_TREE_WRITER_H #define LIBPENTOBI_BASE_TREE_WRITER_H #include "Tree.h" #include "libboardgame_sgf/TreeWriter.h" namespace libpentobi_base { //----------------------------------------------------------------------------- /** Blokus-specific tree writer. Automatically replaces obsolete move properties as used by early versions of Pentobi. */ class TreeWriter : public libboardgame_sgf::TreeWriter { public: TreeWriter(ostream& out, const Tree& tree); virtual ~TreeWriter(); void write_property(const string& id, const vector& values) override; private: Variant m_variant; }; //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_TREE_WRITER_H pentobi-7.2/src/libpentobi_base/Variant.cpp000066400000000000000000000073251227240712600210540ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Variant.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Variant.h" #include "libboardgame_util/Assert.h" #include "libboardgame_util/StringUtil.h" namespace libpentobi_base { using libboardgame_util::trim; using libboardgame_util::to_lower; //----------------------------------------------------------------------------- Color::IntType get_nu_colors(Variant variant) { switch (variant) { case Variant::duo: case Variant::junior: return 2; case Variant::trigon_3: return 3; case Variant::classic: case Variant::classic_2: case Variant::trigon: case Variant::trigon_2: return 4; } LIBBOARDGAME_ASSERT(false); return 0; } unsigned get_nu_players(Variant variant) { switch (variant) { case Variant::duo: case Variant::junior: case Variant::classic_2: case Variant::trigon_2: return 2; case Variant::trigon_3: return 3; case Variant::classic: case Variant::trigon: return 4; } LIBBOARDGAME_ASSERT(false); return 0; } bool parse_variant(const string& s, Variant& variant) { string t = to_lower(trim(s)); if (t == "blokus") variant = Variant::classic; else if (t == "blokus two-player") variant = Variant::classic_2; else if (t == "blokus trigon") variant = Variant::trigon; else if (t == "blokus trigon two-player") variant = Variant::trigon_2; else if (t == "blokus trigon three-player") variant = Variant::trigon_3; else if (t == "blokus duo") variant = Variant::duo; else if (t == "blokus junior") variant = Variant::junior; else return false; return true; } bool parse_variant_id(const string& s, Variant& variant) { string t = to_lower(trim(s)); if (t == "classic" || t == "c") variant = Variant::classic; else if (t == "classic_2" || t == "c2") variant = Variant::classic_2; else if (t == "trigon" || t == "t") variant = Variant::trigon; else if (t == "trigon_2" || t == "t2") variant = Variant::trigon_2; else if (t == "trigon_3" || t == "t3") variant = Variant::trigon_3; else if (t == "duo" || t == "d") variant = Variant::duo; else if (t == "junior" || t == "j") variant = Variant::junior; else return false; return true; } const char* to_string(Variant variant) { switch (variant) { case Variant::classic: return "Blokus"; case Variant::classic_2: return "Blokus Two-Player"; case Variant::duo: return "Blokus Duo"; case Variant::junior: return "Blokus Junior"; case Variant::trigon: return "Blokus Trigon"; case Variant::trigon_2: return "Blokus Trigon Two-Player"; case Variant::trigon_3: return "Blokus Trigon Three-Player"; default: LIBBOARDGAME_ASSERT(false); return "?"; } } const char* to_string_id(Variant variant) { switch (variant) { case Variant::classic: return "classic"; case Variant::classic_2: return "classic_2"; case Variant::duo: return "duo"; case Variant::junior: return "junior"; case Variant::trigon: return "trigon"; case Variant::trigon_2: return "trigon_2"; case Variant::trigon_3: return "trigon_3"; default: LIBBOARDGAME_ASSERT(false); return "?"; } } //----------------------------------------------------------------------------- } // namespace libpentobi_base pentobi-7.2/src/libpentobi_base/Variant.h000066400000000000000000000033231227240712600205130ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_base/Variant.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_BASE_VARIANT_H #define LIBPENTOBI_BASE_VARIANT_H #include #include "Color.h" namespace libpentobi_base { using namespace std; //----------------------------------------------------------------------------- /** Game variant. */ enum class Variant { classic, classic_2, duo, junior, trigon, trigon_2, trigon_3 }; /** Get name of game variant as in the GM property in Blokus SGF files. */ const char* to_string(Variant variant); /** Get a short lowercase string without spaces that can be used as a identifier for a game variant. The strings used are "classic", "classic_2", "duo", "trigon", "trigon_2", "trigon_3", "junior" */ const char* to_string_id(Variant variant); /** Parse name of game variant as in the GM property in Blokus SGF files. The parsing is case-insensitive, leading and trailing whitespaced are ignored. @param s @param[out] variant @result True if the string contained a valid game variant. */ bool parse_variant(const string& s, Variant& variant); /** Parse short lowercase name of game variant as returned to_string_id(). @param s @param[out] variant @result True if the string contained a valid game variant. */ bool parse_variant_id(const string& s, Variant& variant); Color::IntType get_nu_colors(Variant variant); unsigned get_nu_players(Variant variant); //----------------------------------------------------------------------------- } // namespace libpentobi_base #endif // LIBPENTOBI_BASE_VARIANT_H pentobi-7.2/src/libpentobi_gui/000077500000000000000000000000001227240712600166075ustar00rootroot00000000000000pentobi-7.2/src/libpentobi_gui/BoardPainter.cpp000066400000000000000000000262601227240712600216730ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/BoardPainter.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "BoardPainter.h" #include #include #include "Util.h" #include "libboardgame_util/Log.h" #include "libpentobi_base/AdjIterator.h" #include "libpentobi_base/DiagIterator.h" using namespace std; using libboardgame_util::log; using libpentobi_base::AdjIterator; using libpentobi_base::BoardIterator; using libpentobi_base::DiagIterator; using libpentobi_base::GeometryIterator; using libpentobi_base::Move; using libpentobi_base::PointState; //----------------------------------------------------------------------------- BoardPainter::BoardPainter() : m_hasPainted(false), m_coordinates(false), m_coordinateColor(Qt::black) { m_font.setFamily("Helvetica"); m_font.setStyleHint(QFont::SansSerif); m_font.setStyleStrategy(QFont::PreferOutline); m_fontSemiCondensed = m_font; m_fontSemiCondensed.setStretch(QFont::SemiCondensed); m_fontCondensed = m_font; m_fontCondensed.setStretch(QFont::Condensed); m_fontCoordLabels = m_font; m_fontCoordLabels.setStretch(QFont::SemiCondensed); } void BoardPainter::drawCoordinates(QPainter& painter, bool isTrigon) { painter.setPen(m_coordinateColor); for (int x = 0; x < m_width; ++x) { QString label; if (x < 26) label = QString(QChar('A' + x)); else { label = "A"; label.append(QChar('A' + (x - 26))); } drawLabel(painter, x * m_fieldWidth, m_height * m_fieldHeight, m_fieldWidth, m_fieldHeight, label, true); drawLabel(painter, x * m_fieldWidth, -m_fieldHeight, m_fieldWidth, m_fieldHeight, label, true); } for (int y = 0; y < m_height; ++y) { QString label; label.setNum(y + 1); qreal left; qreal right; if (isTrigon) { left = -1.5 * m_fieldWidth; right = (m_width + 0.5) * m_fieldWidth; } else { left = -m_fieldWidth; right = m_width * m_fieldWidth; } drawLabel(painter, left, (m_height - y - 1) * m_fieldHeight, m_fieldWidth, m_fieldHeight, label, true); drawLabel(painter, right, (m_height - y - 1) * m_fieldHeight, m_fieldWidth, m_fieldHeight, label, true); } } void BoardPainter::drawLabel(QPainter& painter, qreal x, qreal y, qreal width, qreal height, const QString& label, bool isCoordLabel) { if (isCoordLabel) painter.setFont(m_fontCoordLabels); else painter.setFont(m_font); QFontMetrics metrics(painter.font()); QRect boundingRect = metrics.boundingRect(label); if (! isCoordLabel) { if (boundingRect.width() > width) { painter.setFont(m_fontSemiCondensed); QFontMetrics metrics(painter.font()); boundingRect = metrics.boundingRect(label); } if (boundingRect.width() > width) { painter.setFont(m_fontCondensed); QFontMetrics metrics(painter.font()); boundingRect = metrics.boundingRect(label); } } qreal dx = 0.5 * (width - boundingRect.width()); qreal dy = 0.5 * (height - boundingRect.height()); QRectF rect; rect.setCoords(floor(x + dx), floor(y + dy), ceil(x + width - dx + 1), ceil(y + height - dy + 1)); painter.drawText(rect, Qt::TextDontClip, label); } void BoardPainter::drawLabels(QPainter& painter, const Grid& pointState, Variant variant, const Grid* labels) { if (labels == 0) return; auto& geometry = pointState.get_geometry(); bool isTrigon = (variant == Variant::trigon || variant == Variant::trigon_2 || variant == Variant::trigon_3); for (GeometryIterator i(geometry); i; ++i) if (! (*labels)[*i].isEmpty()) { PointState s = pointState[*i]; painter.setPen(Util::getLabelColor(variant, s)); qreal x = i->get_x() * m_fieldWidth; qreal y = (m_height - i->get_y() - 1) * m_fieldHeight; qreal width = m_fieldWidth; qreal height = m_fieldHeight; if (isTrigon) { bool isUpside = (geometry.get_point_type(*i) == 1); if (isUpside) y += 0.333 * height; height = 0.666 * height; } drawLabel(painter, x, y, width, height, (*labels)[*i], false); } } CoordPoint BoardPainter::getCoordPoint(int x, int y) { if (! m_hasPainted) return CoordPoint::null(); x = static_cast((x - m_boardOffset.x()) / m_fieldWidth); y = static_cast((y - m_boardOffset.y()) / m_fieldHeight); y = m_height - y - 1; if (x < 0 || x >= m_width || y < 0 || y >= m_height) return CoordPoint::null(); else return CoordPoint(x, y); } void BoardPainter::paintEmptyBoard(QPainter& painter, unsigned width, unsigned height, Variant variant, const Geometry& geometry) { m_hasPainted = true; painter.setRenderHint(QPainter::Antialiasing, true); m_variant = variant; m_geometry = &geometry; m_width = static_cast(m_geometry->get_width()); m_height = static_cast(m_geometry->get_height()); m_isTrigon = (variant == Variant::trigon || variant == Variant::trigon_2 || variant == Variant::trigon_3); if (m_isTrigon) { qreal ratio = 1.732; if (m_coordinates) m_fieldWidth = min(qreal(width) / (m_width + 3), height / (ratio * (m_height + 2))); else m_fieldWidth = min(qreal(width) / (m_width + 1), height / (ratio * m_height)); m_fieldHeight = ratio * m_fieldWidth; m_boardOffset = QPointF(0.5 * (width - m_fieldWidth * m_width), 0.5 * (height - m_fieldHeight * m_height)); } else { if (m_coordinates) m_fieldWidth = min(qreal(width) / (m_width + 2), qreal(height) / (m_height + 2)); else m_fieldWidth = min(qreal(width) / m_width, qreal(height) / m_height); m_fieldHeight = m_fieldWidth; m_boardOffset = QPointF(0.5 * (width - m_fieldWidth * m_width), 0.5 * (height - m_fieldHeight * m_height)); } if (m_isTrigon) { m_font.setPointSizeF(0.6 * m_fieldWidth); m_fontSemiCondensed.setPointSizeF(0.6 * m_fieldWidth); m_fontCondensed.setPointSizeF(0.6 * m_fieldWidth); m_fontCoordLabels.setPointSizeF(0.6 * m_fieldWidth); } else { m_font.setPointSizeF(0.4 * m_fieldWidth); m_fontSemiCondensed.setPointSizeF(0.4 * m_fieldWidth); m_fontCondensed.setPointSizeF(0.4 * m_fieldWidth); m_fontCoordLabels.setPointSizeF(0.36 * m_fieldWidth); } painter.save(); painter.translate(m_boardOffset); if (m_coordinates) drawCoordinates(painter, m_isTrigon); m_startingPoints.init(variant, *m_geometry); for (GeometryIterator i(*m_geometry); i; ++i) { int x = i->get_x(); int y = i->get_y(); qreal fieldX = x * m_fieldWidth; qreal fieldY = (m_height - y - 1) * m_fieldHeight; if (m_isTrigon) { bool isUpside = (m_geometry->get_point_type(x, y) == 1); if (m_startingPoints.is_colorless_starting_point(*i)) Util::paintEmptyTriangleStartingPoint(painter, isUpside, fieldX, fieldY, m_fieldWidth, m_fieldHeight); else Util::paintEmptyTriangle(painter, isUpside, fieldX, fieldY, m_fieldWidth, m_fieldHeight); } else { if (m_startingPoints.is_colored_starting_point(*i)) { Color color = m_startingPoints.get_starting_point_color(*i); Util::paintEmptySquareStartingPoint(painter, variant, color, fieldX, fieldY, m_fieldWidth); } else Util::paintEmptySquare(painter, fieldX, fieldY, m_fieldWidth); } } painter.restore(); } void BoardPainter::paintPieces(QPainter& painter, const Grid& pointState, const Grid* labels) { painter.setRenderHint(QPainter::Antialiasing, true); painter.save(); painter.translate(m_boardOffset); for (GeometryIterator i(*m_geometry); i; ++i) { int x = i->get_x(); int y = i->get_y(); PointState s = pointState[*i]; qreal fieldX = x * m_fieldWidth; qreal fieldY = (m_height - y - 1) * m_fieldHeight; if (m_isTrigon) { bool isUpside = (m_geometry->get_point_type(x, y) == 1); if (s.is_color()) Util::paintColorTriangle(painter, m_variant, s.to_color(), isUpside, fieldX, fieldY, m_fieldWidth, m_fieldHeight); } else { if (s.is_color()) Util::paintColorSquare(painter, m_variant, s.to_color(), fieldX, fieldY, m_fieldWidth); } } drawLabels(painter, pointState, m_variant, labels); painter.restore(); } void BoardPainter::paintSelectedPiece(QPainter& painter, Color c, const MovePoints& points, bool isLegal) { painter.setRenderHint(QPainter::Antialiasing, true); painter.save(); painter.translate(m_boardOffset); qreal alpha; qreal saturation; bool flat; if (isLegal) { alpha = 0.9; saturation = 0.8; flat = false; } else { alpha = 0.63; saturation = 0.5; flat = true; } for (Point p : points) { qreal fieldX = p.get_x() * m_fieldWidth; qreal fieldY = (m_height - p.get_y() - 1) * m_fieldHeight; if (m_isTrigon) { bool isUpside = (m_geometry->get_point_type(p) == 1); Util::paintColorTriangle(painter, m_variant, c, isUpside, fieldX, fieldY, m_fieldWidth, m_fieldHeight, alpha, saturation, flat); } else { Util::paintColorSquare(painter, m_variant, c, fieldX, fieldY, m_fieldWidth, alpha, saturation, flat); } } painter.restore(); } //----------------------------------------------------------------------------- pentobi-7.2/src/libpentobi_gui/BoardPainter.h000066400000000000000000000073141227240712600213370ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/BoardPainter.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_GUI_BOARD_PAINTER_H #define LIBPENTOBI_GUI_BOARD_PAINTER_H #include #include "libpentobi_base/Grid.h" #include "libpentobi_base/Board.h" using libboardgame_base::CoordPoint; using libboardgame_base::Transform; using libpentobi_base::Board; using libpentobi_base::Color; using libpentobi_base::Variant; using libpentobi_base::Geometry; using libpentobi_base::Grid; using libpentobi_base::MovePoints; using libpentobi_base::PieceInfo; using libpentobi_base::Point; using libpentobi_base::PointState; using libpentobi_base::StartingPoints; //----------------------------------------------------------------------------- /** Paints a board. The painter can be used without having to create an instance of class Board, which is undesirable for use cases like the thumbnailer because of the slow creation of the BoardConst class. Instead, the board state is passed to the paint() function as a grid of point states. */ class BoardPainter { public: BoardPainter(); void setCoordinates(bool enable); void setCoordinateColor(const QColor& color); void paintEmptyBoard(QPainter& painter, unsigned width, unsigned height, Variant variant, const Geometry& geometry); /** Paint the pieces and markup. This function must only be called after paintEmptyBoard() because it uses the arguments from the paintEmptyBoard() function to determine the board properties. */ void paintPieces(QPainter& painter, const Grid& pointState, const Grid* labels = nullptr); /** Paint the selected piece. Paints the selected piece either transparent (if not legal) or opaque (if legal). This function must only be called after paintEmptyBoard() because it uses the arguments from the paintEmptyBoard() function to determine the board properties. */ void paintSelectedPiece(QPainter& painter, Color c, const MovePoints& points, bool isLegal); /** Get the corresponding board coordinates of a pixel. @return The board coordinates or CoordPoint::null() if paint() was not called yet or the pixel is outside the board. */ CoordPoint getCoordPoint(int x, int y); bool hasPainted() const; private: bool m_hasPainted; bool m_coordinates; bool m_isTrigon; const Geometry* m_geometry; Variant m_variant; /** The width of the last board painted. */ int m_width; /** The height of the last board painted. */ int m_height; QColor m_coordinateColor; qreal m_fieldWidth; qreal m_fieldHeight; QPointF m_boardOffset; QFont m_font; QFont m_fontCondensed; QFont m_fontSemiCondensed; QFont m_fontCoordLabels; StartingPoints m_startingPoints; void drawCoordinates(QPainter& painter, bool isTrigon); void drawLabel(QPainter& painter, qreal x, qreal y, qreal width, qreal height, const QString& label, bool isCoordLabel); void drawLabels(QPainter& painter, const Grid& pointState, Variant variant, const Grid* labels); }; inline void BoardPainter::setCoordinateColor(const QColor& color) { m_coordinateColor = color; } inline bool BoardPainter::hasPainted() const { return m_hasPainted; } inline void BoardPainter::setCoordinates(bool enable) { m_coordinates = enable; } //----------------------------------------------------------------------------- #endif // LIBPENTOBI_GUI_BOARD_PAINTER_H pentobi-7.2/src/libpentobi_gui/CMakeLists.txt000066400000000000000000000035561227240712600213600ustar00rootroot00000000000000set(CMAKE_AUTOMOC TRUE) if (NOT USE_QT5) include(${QT_USE_FILE}) endif() include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) set(pentobi_gui_STAT_SRCS BoardPainter.cpp ComputerColorDialog.cpp GameInfoDialog.cpp GuiBoard.cpp GuiBoardUtil.cpp HelpWindow.cpp InitialRatingDialog.cpp LeaveFullscreenButton.cpp LineEdit.cpp OrientationDisplay.cpp PieceSelector.cpp SameHeightLayout.cpp ScoreDisplay.cpp Util.cpp ) set(pentobi_gui_RCS libpentobi_gui_resources.qrc ) set(pentobi_gui_TS libpentobi_gui_de.ts libpentobi_gui_en_CA.ts libpentobi_gui_en_GB.ts ) if (USE_QT5) qt5_add_resources(pentobi_gui_RC_SRCS ${pentobi_gui_RCS}) else() qt4_add_resources(pentobi_gui_RC_SRCS ${pentobi_gui_RCS}) endif() # We don't use qt4_add_translation because it doesn't use option -removeidentical # with lrelease, which causes larger sizes of the generated qm files if (USE_QT5) set(LRELEASE_EXECUTABLE ${Qt5_LRELEASE_EXECUTABLE}) else() set(LRELEASE_EXECUTABLE ${QT_LRELEASE_EXECUTABLE}) endif() foreach(ts ${pentobi_gui_TS}) get_filename_component(qm ${ts} NAME_WE) set(qm "${CMAKE_CURRENT_BINARY_DIR}/${qm}.qm") add_custom_command(OUTPUT ${qm} COMMAND ${LRELEASE_EXECUTABLE} ARGS -removeidentical -nounfinished ${CMAKE_CURRENT_SOURCE_DIR}/${ts} -qm ${qm} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${ts} VERBATIM ) set(pentobi_gui_QM_SRCS ${pentobi_gui_QM_SRCS} ${qm}) endforeach() add_library(pentobi_gui STATIC ${pentobi_gui_STAT_SRCS} ${pentobi_gui_MOC_SRCS} ${pentobi_gui_RC_SRCS} ${pentobi_gui_QM_SRCS}) if (USE_QT5) qt5_use_modules(pentobi_gui Widgets) endif() # Install translation files. If you change the destination, you need to # update the default for PENTOBI_TRANSLATIONS in the main CMakeLists.txt install(FILES ${pentobi_gui_QM_SRCS} DESTINATION ${CMAKE_INSTALL_DATADIR}/pentobi/translations) pentobi-7.2/src/libpentobi_gui/ComputerColorDialog.cpp000066400000000000000000000067331227240712600232410ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/ComputerColorDialog.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "ComputerColorDialog.h" #include #include #include using libpentobi_base::ColorIterator; //----------------------------------------------------------------------------- ComputerColorDialog::ComputerColorDialog(QWidget* parent, Variant variant, ColorMap& computerColor) : QDialog(parent), m_computerColor(computerColor), m_variant(variant) { setWindowTitle(tr("Computer Colors")); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); auto layout = new QVBoxLayout(); setLayout(layout); layout->addWidget(new QLabel(tr("Colors played by the computer:"))); for (Color::IntType i = 0; i < get_nu_players(m_variant); ++i) createCheckBox(layout, Color(i)); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); layout->addWidget(buttonBox); connect(buttonBox, SIGNAL(accepted()), SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), SLOT(reject())); buttonBox->setFocus(); } void ComputerColorDialog::accept() { unsigned nu_colors = get_nu_colors(m_variant); if (get_nu_players(m_variant) == nu_colors) for (ColorIterator i(nu_colors); i; ++i) m_computerColor[*i] = m_checkBox[(*i).to_int()]->isChecked(); else { LIBBOARDGAME_ASSERT(m_variant == Variant::classic_2 || m_variant == Variant::trigon_2); m_computerColor[Color(0)] = m_checkBox[0]->isChecked(); m_computerColor[Color(2)] = m_checkBox[0]->isChecked(); m_computerColor[Color(1)] = m_checkBox[1]->isChecked(); m_computerColor[Color(3)] = m_checkBox[1]->isChecked(); } QDialog::accept(); } void ComputerColorDialog::createCheckBox(QLayout* layout, Color c) { auto checkBox = new QCheckBox(getPlayerString(m_variant, c)); checkBox->setChecked(m_computerColor[c]); layout->addWidget(checkBox); m_checkBox[c.to_int()] = checkBox; } QString ComputerColorDialog::getPlayerString(Variant variant, Color c) { if (variant == Variant::duo || variant == Variant::junior) { if (c == Color(0)) return tr("&Blue"); if (c == Color(1)) return tr("&Green"); } else if (variant == Variant::classic || variant == Variant::trigon) { if (c == Color(0)) return tr("&Blue"); if (c == Color(1)) return tr("&Yellow"); if (c == Color(2)) return tr("&Red"); if (c == Color(3)) return tr("&Green"); } else if (variant == Variant::trigon_3) { if (c == Color(0)) return tr("&Blue"); if (c == Color(1)) return tr("&Yellow"); if (c == Color(2)) return tr("&Red"); } else if (variant == Variant::classic_2 || variant == Variant::trigon_2) { if (c == Color(0) || c == Color(2)) return tr("&Blue/Red"); if (c == Color(1) || c == Color(3)) return tr("&Yellow/Green"); } LIBBOARDGAME_ASSERT(false); return ""; } //----------------------------------------------------------------------------- pentobi-7.2/src/libpentobi_gui/ComputerColorDialog.h000066400000000000000000000024541227240712600227020ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/ComputerColorDialog.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_GUI_COMPUTER_COLOR_DIALOG_H #define LIBPENTOBI_GUI_COMPUTER_COLOR_DIALOG_H // Needed in the header because moc_*.cxx does not include config.h #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "libpentobi_base/Variant.h" #include "libpentobi_base/ColorMap.h" using namespace std; using libpentobi_base::Variant; using libpentobi_base::Color; using libpentobi_base::ColorMap; //----------------------------------------------------------------------------- class ComputerColorDialog : public QDialog { Q_OBJECT public: ComputerColorDialog(QWidget* parent, Variant variant, ColorMap& computerColor); public slots: void accept(); private: ColorMap& m_computerColor; Variant m_variant; array m_checkBox; void createCheckBox(QLayout* layout, Color c); QString getPlayerString(Variant variant, Color c); }; //----------------------------------------------------------------------------- #endif // LIBPENTOBI_GUI_COMPUTER_COLOR_DIALOG_H pentobi-7.2/src/libpentobi_gui/GameInfoDialog.cpp000066400000000000000000000133101227240712600221160ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/GameInfoDialog.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "GameInfoDialog.h" #include #include "LineEdit.h" #include "libpentobi_gui/Util.h" using libpentobi_base::Variant; //----------------------------------------------------------------------------- GameInfoDialog::GameInfoDialog(QWidget* parent, Game& game) : QDialog(parent), m_game(game) { m_charset = game.get_root().get_property("CA", ""); setWindowTitle(tr("Game Info")); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); auto layout = new QVBoxLayout(); setLayout(layout); m_formLayout = new QFormLayout(); layout->addLayout(m_formLayout); auto variant = game.get_variant(); if (variant == Variant::duo || variant == Variant::junior) { m_playerBlue = createPlayerName(tr("Player &Blue:"), Color(0)); m_playerGreen = createPlayerName(tr("Player &Green:"), Color(1)); } else if (variant == Variant::classic || variant == Variant::trigon) { m_playerBlue = createPlayerName(tr("Player &Blue:"), Color(0)); m_playerYellow = createPlayerName(tr("Player &Yellow:"), Color(1)); m_playerRed = createPlayerName(tr("Player &Red:"), Color(2)); m_playerGreen = createPlayerName(tr("Player &Green:"), Color(3)); } else if (variant == Variant::trigon_3) { m_playerBlue = createPlayerName(tr("Player &Blue:"), Color(0)); m_playerYellow = createPlayerName(tr("Player &Yellow:"), Color(1)); m_playerRed = createPlayerName(tr("Player &Red:"), Color(2)); } else { LIBBOARDGAME_ASSERT(variant == Variant::classic_2 || variant == Variant::trigon_2); m_playerBlueRed = createPlayerName(tr("Player &Blue/Red:"), Color(0)); m_playerYellowGreen = createPlayerName(tr("Player &Yellow/Green:"), Color(1)); } m_date = createLine(tr("&Date:"), m_game.get_date()); m_time = createLine(tr("&Time limits:"), m_game.get_time()); m_event = createLine(tr("&Event:"), m_game.get_event()); m_round = createLine(tr("R&ound:"), m_game.get_round()); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); layout->addWidget(buttonBox); // We assume that the user wants to edit the game info if it is still empty // and that he only wants to display it if not empty. Therefore, we leave // the focus at the first text field if it is empty and put it on the // button box otherwise. if (variant == Variant::classic_2 || variant == Variant::trigon_2) { if (! m_playerBlueRed->text().isEmpty()) buttonBox->setFocus(); } else if (! m_playerBlue->text().isEmpty()) buttonBox->setFocus(); connect(buttonBox, SIGNAL(accepted()), SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), SLOT(reject())); } void GameInfoDialog::accept() { auto variant = m_game.get_variant(); string value; if (variant == Variant::duo || variant == Variant::junior) { if (acceptLine(m_playerBlue, value)) m_game.set_player_name(Color(0), value); if (acceptLine(m_playerGreen, value)) m_game.set_player_name(Color(1), value); } else if (variant == Variant::classic || variant == Variant::trigon) { if (acceptLine(m_playerBlue, value)) m_game.set_player_name(Color(0), value); if (acceptLine(m_playerYellow, value)) m_game.set_player_name(Color(1), value); if (acceptLine(m_playerRed, value)) m_game.set_player_name(Color(2), value); if (acceptLine(m_playerGreen, value)) m_game.set_player_name(Color(3), value); } else if (variant == Variant::trigon_3) { if (acceptLine(m_playerBlue, value)) m_game.set_player_name(Color(0), value); if (acceptLine(m_playerYellow, value)) m_game.set_player_name(Color(1), value); if (acceptLine(m_playerRed, value)) m_game.set_player_name(Color(2), value); } else { LIBBOARDGAME_ASSERT(variant == Variant::classic_2 || variant == Variant::trigon_2); if (acceptLine(m_playerBlueRed, value)) m_game.set_player_name(Color(0), value); if (acceptLine(m_playerYellowGreen, value)) m_game.set_player_name(Color(1), value); } if (acceptLine(m_date, value)) m_game.set_date(value); if (acceptLine(m_time, value)) m_game.set_time(value); if (acceptLine(m_event, value)) m_game.set_event(value); if (acceptLine(m_round, value)) m_game.set_round(value); QDialog::accept(); } bool GameInfoDialog::acceptLine(QLineEdit* lineEdit, string& value) { if (! lineEdit->isModified()) return false; QString text = lineEdit->text(); if (text.trimmed().isEmpty()) return false; value = Util::convertSgfValueFromQString(text, m_charset); return true; } QLineEdit* GameInfoDialog::createLine(const QString& label, const string& text) { auto lineEdit = new LineEdit(0, 30); if (! text.empty()) { lineEdit->setText(Util::convertSgfValueToQString(text, m_charset)); lineEdit->setCursorPosition(0); } m_formLayout->addRow(label, lineEdit); return lineEdit; } QLineEdit* GameInfoDialog::createPlayerName(const QString& label, Color c) { return createLine(label, m_game.get_player_name(c)); } //----------------------------------------------------------------------------- pentobi-7.2/src/libpentobi_gui/GameInfoDialog.h000066400000000000000000000027501227240712600215710ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/GameInfoDialog.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_GUI_GAME_INFO_DIALOG_H #define LIBPENTOBI_GUI_GAME_INFO_DIALOG_H // Needed in the header because moc_*.cxx does not include config.h #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "libpentobi_base/Game.h" using namespace std; using libpentobi_base::Color; using libpentobi_base::Game; //----------------------------------------------------------------------------- class GameInfoDialog : public QDialog { Q_OBJECT public: GameInfoDialog(QWidget* parent, Game& game); public slots: void accept(); private: Game& m_game; string m_charset; QFormLayout* m_formLayout; QLineEdit* m_playerBlue; QLineEdit* m_playerYellow; QLineEdit* m_playerRed; QLineEdit* m_playerGreen; QLineEdit* m_playerBlueRed; QLineEdit* m_playerYellowGreen; QLineEdit* m_date; QLineEdit* m_event; QLineEdit* m_round; QLineEdit* m_time; bool acceptLine(QLineEdit* lineEdit, string& value); QLineEdit* createLine(const QString& label, const string& text); QLineEdit* createPlayerName(const QString& label, Color c); }; //----------------------------------------------------------------------------- #endif // LIBPENTOBI_GUI_GAME_INFO_DIALOG_H pentobi-7.2/src/libpentobi_gui/GuiBoard.cpp000066400000000000000000000327531227240712600210210ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/GuiBoard.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "GuiBoard.h" #include #include #include #include #include "libboardgame_base/Transform.h" #include "libboardgame_util/Log.h" #include "libpentobi_gui/Util.h" using namespace std; using libboardgame_base::Transform; using libboardgame_util::log; using libpentobi_base::BoardIterator; using libpentobi_base::BoardType; using libpentobi_base::Geometry; using libpentobi_base::MovePoints; using libpentobi_base::PiecePoints; using libpentobi_base::Point; using libpentobi_base::PointState; //----------------------------------------------------------------------------- namespace { bool allPointEmpty(const Board& bd, Move mv) { for (Point p : bd.get_move_info(mv)) if (! bd.is_empty(p)) return false; return true; } } // namespace //----------------------------------------------------------------------------- GuiBoard::GuiBoard(QWidget* parent, const Board& bd) : QWidget(parent), m_bd(bd), m_isInitialized(false), m_freePlacement(false), m_emptyBoardDirty(true), m_dirty(true), m_selectedPiece(Piece::null()), m_selectedPieceTransform(0), m_emptyBoardPixmap(0), m_boardPixmap(0), m_isMoveShown(false) { setMinimumWidth(14 * (Point::max_width + 2)); setMinimumHeight(14 * (Point::max_height + 2)); connect(&m_currentMoveShownAnimationTimer, SIGNAL(timeout()), SLOT(showMoveAnimation())); } GuiBoard::~GuiBoard() { delete m_emptyBoardPixmap; delete m_boardPixmap; } void GuiBoard::changeEvent(QEvent* event) { if (event->type() == QEvent::StyleChange) setEmptyBoardDirty(); } void GuiBoard::clearMarkup() { for (Geometry::Iterator i(m_labels.get_geometry()); i; ++i) setLabel(*i, ""); } void GuiBoard::clearSelectedPiece() { m_selectedPiece = Piece::null(); m_selectedPieceTransform = nullptr; setSelectedPiecePoints(); setMouseTracking(false); } void GuiBoard::copyFromBoard(const Board& bd) { auto& geometry = bd.get_geometry(); if (! m_isInitialized || m_variant != bd.get_variant()) { m_variant = bd.get_variant(); m_isInitialized = true; m_pointState = bd.get_grid(); m_labels.init(geometry, ""); setEmptyBoardDirty(); } else { for (BoardIterator i(bd); i; ++i) if (m_pointState[*i] != bd.get_point_state(*i)) m_pointState[*i] = bd.get_point_state(*i); setDirty(); } } Move GuiBoard::findSelectedPieceMove() { if (m_selectedPiece.is_null() || m_selectedPieceOffset.is_null()) return Move::null(); const PiecePoints& points = m_bd.get_piece_info(m_selectedPiece).get_points(); MovePoints movePoints; int width = static_cast(m_bd.get_geometry().get_width()); int height = static_cast(m_bd.get_geometry().get_height()); for (CoordPoint p : points) { p = m_selectedPieceTransform->get_transformed(p); int x = p.x + m_selectedPieceOffset.x; int y = p.y + m_selectedPieceOffset.y; if (x < 0 || x >= width || y < 0 || y >= height) return Move::null(); movePoints.push_back(Point(x, y)); } Move mv; if (! m_bd.find_move(movePoints, mv) || (m_freePlacement && ! allPointEmpty(m_bd, mv)) || (! m_freePlacement && ! m_bd.is_legal(m_selectedPieceColor, mv))) return Move::null(); else return mv; } int GuiBoard::heightForWidth(int width) const { return width; } void GuiBoard::leaveEvent(QEvent*) { m_selectedPieceOffset = CoordPoint::null(); setSelectedPiecePoints(); } void GuiBoard::mouseMoveEvent(QMouseEvent* event) { if (m_selectedPiece.is_null()) return; CoordPoint oldOffset = m_selectedPieceOffset; setSelectedPieceOffset(*event); if (m_selectedPieceOffset != oldOffset) setSelectedPiecePoints(); } void GuiBoard::mousePressEvent(QMouseEvent* event) { if (m_selectedPiece.is_null()) { CoordPoint p = m_boardPainter.getCoordPoint(event->x(), event->y()); if (m_bd.get_geometry().is_onboard(p)) emit pointClicked(Point(p.x, p.y)); return; } setSelectedPieceOffset(*event); placeSelectedPiece(); } void GuiBoard::moveSelectedPieceDown() { if (m_selectedPiece.is_null()) return; auto& geometry = m_bd.get_geometry(); CoordPoint newOffset; if (m_selectedPieceOffset.is_null()) { newOffset = CoordPoint(geometry.get_width() / 2, geometry.get_height() - 1); setSelectedPieceOffset(newOffset); setSelectedPiecePoints(); } else { newOffset = m_selectedPieceOffset; if (m_bd.get_board_type() == BoardType::trigon || m_bd.get_board_type() == BoardType::trigon_3) { if (m_selectedPieceOffset.x % 2 == 0) ++newOffset.x; else --newOffset.x; --newOffset.y; } else --newOffset.y; if (geometry.is_onboard(newOffset)) { setSelectedPieceOffset(newOffset); setSelectedPiecePoints(); } } } void GuiBoard::moveSelectedPieceLeft() { if (m_selectedPiece.is_null()) return; auto& geometry = m_bd.get_geometry(); CoordPoint newOffset; if (m_selectedPieceOffset.is_null()) { newOffset = CoordPoint(geometry.get_width() - 1, geometry.get_height() / 2); setSelectedPieceOffset(newOffset); setSelectedPiecePoints(); } else { newOffset = m_selectedPieceOffset; if (m_bd.get_board_type() == BoardType::trigon || m_bd.get_board_type() == BoardType::trigon_3) newOffset.x -= 2; else --newOffset.x; if (geometry.is_onboard(newOffset)) { setSelectedPieceOffset(newOffset); setSelectedPiecePoints(); } } } void GuiBoard::moveSelectedPieceRight() { if (m_selectedPiece.is_null()) return; auto& geometry = m_bd.get_geometry(); CoordPoint newOffset; if (m_selectedPieceOffset.is_null()) { newOffset = CoordPoint(0, geometry.get_height() / 2); setSelectedPieceOffset(newOffset); setSelectedPiecePoints(); } else { newOffset = m_selectedPieceOffset; if (m_bd.get_board_type() == BoardType::trigon || m_bd.get_board_type() == BoardType::trigon_3) newOffset.x += 2; else ++newOffset.x; if (geometry.is_onboard(newOffset)) { setSelectedPieceOffset(newOffset); setSelectedPiecePoints(); } } } void GuiBoard::moveSelectedPieceUp() { if (m_selectedPiece.is_null()) return; auto& geometry = m_bd.get_geometry(); CoordPoint newOffset; if (m_selectedPieceOffset.is_null()) { newOffset = CoordPoint(geometry.get_width() / 2, 0); setSelectedPieceOffset(newOffset); setSelectedPiecePoints(); } else { newOffset = m_selectedPieceOffset; if (m_bd.get_board_type() == BoardType::trigon || m_bd.get_board_type() == BoardType::trigon_3) { if (m_selectedPieceOffset.x % 2 == 0) ++newOffset.x; else --newOffset.x; ++newOffset.y; } else ++newOffset.y; if (geometry.is_onboard(newOffset)) { setSelectedPieceOffset(newOffset); setSelectedPiecePoints(); } } } void GuiBoard::paintEvent(QPaintEvent*) { if (! m_isInitialized) return; if (m_emptyBoardPixmap == nullptr || m_emptyBoardPixmap->size() != size()) { delete m_emptyBoardPixmap; m_emptyBoardPixmap = new QPixmap(size()); m_emptyBoardDirty = true; } if (m_boardPixmap == nullptr || m_boardPixmap->size() != size()) { delete m_boardPixmap; m_boardPixmap = new QPixmap(size()); m_dirty = true; } if (m_emptyBoardDirty) { QColor coordLabelColor = QApplication::palette().color(QPalette::WindowText); m_boardPainter.setCoordinateColor(coordLabelColor); m_emptyBoardPixmap->fill(Qt::transparent); QPainter painter(m_emptyBoardPixmap); m_boardPainter.paintEmptyBoard(painter, width(), height(), m_variant, m_pointState.get_geometry()); m_emptyBoardDirty = false; } if (m_dirty) { m_boardPixmap->fill(Qt::transparent); QPainter painter(m_boardPixmap); painter.drawPixmap(0, 0, *m_emptyBoardPixmap); m_boardPainter.paintPieces(painter, m_pointState, &m_labels); m_dirty = false; } QPainter painter(this); painter.drawPixmap(0, 0, *m_boardPixmap); if (m_isMoveShown) { if (m_currentMoveShownAnimationIndex % 2 == 0) m_boardPainter.paintSelectedPiece(painter, m_currentMoveShownColor, m_currentMoveShownPoints, true); } else { if (! m_selectedPiece.is_null() && ! m_selectedPieceOffset.is_null()) { bool isLegal = ! findSelectedPieceMove().is_null(); m_boardPainter.paintSelectedPiece(painter, m_selectedPieceColor, m_selectedPiecePoints, isLegal); } } } void GuiBoard::placeSelectedPiece() { auto mv = findSelectedPieceMove(); if (! mv.is_null()) emit play(m_selectedPieceColor, mv); } void GuiBoard::selectPiece(Color color, Piece piece) { if (m_selectedPiece == piece && m_selectedPieceColor == color) return; m_selectedPieceColor = color; m_selectedPieceTransform = m_bd.get_transforms().get_default(); if (m_selectedPiece.is_null()) m_selectedPieceOffset = CoordPoint::null(); m_selectedPiece = piece; setSelectedPieceOffset(m_selectedPieceOffset); setSelectedPiecePoints(); setMouseTracking(true); } void GuiBoard::setEmptyBoardDirty() { m_emptyBoardDirty = true; m_dirty = true; update(); } void GuiBoard::setDirty() { m_dirty = true; update(); } void GuiBoard::setCoordinates(bool enable) { m_boardPainter.setCoordinates(enable); setEmptyBoardDirty(); } void GuiBoard::setFreePlacement(bool enable) { m_freePlacement = enable; update(); } void GuiBoard::setLabel(Point p, const QString& text) { if (! m_isInitialized || ! m_labels.get_geometry().is_onboard(p)) { // Call copyFromBoard(), which initialized the current board size, // before calling setLabel() LIBBOARDGAME_ASSERT(false); return; } if (m_labels[p] != text) { m_labels[p] = text; setDirty(); } } void GuiBoard::setSelectedPieceOffset(const QMouseEvent& event) { setSelectedPieceOffset(m_boardPainter.getCoordPoint(event.x(), event.y())); } void GuiBoard::setSelectedPieceOffset(const CoordPoint& offset) { if (offset.is_null()) { m_selectedPieceOffset = offset; return; } auto& geometry = m_bd.get_geometry(); unsigned old_point_type = geometry.get_point_type(offset); unsigned point_type = m_selectedPieceTransform->get_new_point_type(); CoordPoint type_matching_offset = offset; if (old_point_type != point_type) { if ((point_type == 0 && geometry.is_onboard(CoordPoint(offset.x + 1, offset.y))) || (point_type == 1 && ! geometry.is_onboard(CoordPoint(offset.x - 1, offset.y)))) ++type_matching_offset.x; else --type_matching_offset.x; } m_selectedPieceOffset = type_matching_offset; } void GuiBoard::setSelectedPieceTransform(const Transform* transform) { if (m_selectedPieceTransform == transform) return; m_selectedPieceTransform = transform; setSelectedPieceOffset(m_selectedPieceOffset); setSelectedPiecePoints(); } void GuiBoard::showMove(Color c, Move mv) { m_isMoveShown = true; m_currentMoveShownColor = c; m_currentMoveShownPoints.clear(); for (Point p : m_bd.get_move_info(mv)) m_currentMoveShownPoints.push_back(p); m_currentMoveShownAnimationIndex = 0; m_currentMoveShownAnimationTimer.start(500); update(); } void GuiBoard::showMoveAnimation() { ++m_currentMoveShownAnimationIndex; if (m_currentMoveShownAnimationIndex > 5) { m_isMoveShown = false; m_currentMoveShownAnimationTimer.stop(); } update(); } void GuiBoard::setSelectedPiecePoints() { m_selectedPiecePoints.clear(); if (! m_selectedPiece.is_null() && ! m_selectedPieceOffset.is_null()) { int width = static_cast(m_bd.get_geometry().get_width()); int height = static_cast(m_bd.get_geometry().get_height()); for (CoordPoint p : m_bd.get_piece_info(m_selectedPiece).get_points()) { p = m_selectedPieceTransform->get_transformed(p); int x = p.x + m_selectedPieceOffset.x; int y = p.y + m_selectedPieceOffset.y; if (x >= 0 && x < width && y >= 0 && y < height) m_selectedPiecePoints.push_back(Point(x, y)); } } update(); } //----------------------------------------------------------------------------- pentobi-7.2/src/libpentobi_gui/GuiBoard.h000066400000000000000000000074011227240712600204560ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/GuiBoard.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_GUI_GUI_BOARD_H #define LIBPENTOBI_GUI_GUI_BOARD_H // Needed in the header because moc_*.cxx does not include config.h #ifdef HAVE_CONFIG_H #include #endif #include #include #include "BoardPainter.h" #include "libboardgame_base/CoordPoint.h" #include "libpentobi_base/Board.h" using libpentobi_base::Color; using libboardgame_base::CoordPoint; using libpentobi_base::Board; using libpentobi_base::Grid; using libpentobi_base::Move; using libpentobi_base::Piece; using libpentobi_base::PieceInfo; using libpentobi_base::Point; //----------------------------------------------------------------------------- class GuiBoard : public QWidget { Q_OBJECT public: GuiBoard(QWidget* parent, const Board& bd); ~GuiBoard(); void setCoordinates(bool enable); const Board& getBoard() const; const Grid& getLabels() const; Piece getSelectedPiece() const; const Transform* getSelectedPieceTransform() const; void setSelectedPieceTransform(const Transform* transform); void showMove(Color c, Move mv); int heightForWidth(int width) const; void copyFromBoard(const Board& bd); void setLabel(Point p, const QString& text); void clearMarkup(); void setFreePlacement(bool enable); public slots: void clearSelectedPiece(); void selectPiece(Color color, Piece piece); void moveSelectedPieceLeft(); void moveSelectedPieceRight(); void moveSelectedPieceUp(); void moveSelectedPieceDown(); void placeSelectedPiece(); signals: void play(Color color, Move mv); void pointClicked(Point p); protected: void changeEvent(QEvent* event) override; void leaveEvent(QEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; void mousePressEvent(QMouseEvent* event) override; void paintEvent(QPaintEvent* event) override; private: const Board& m_bd; bool m_isInitialized; bool m_freePlacement; /** Does the empty board need redrawing? */ bool m_emptyBoardDirty; /** Do the pieces and markup on the board need redrawing? If true, the cached board pixmap needs to be repainted. This does not include the selected piece (the selected piece is always painted). */ bool m_dirty; Variant m_variant; Board::PointStateGrid m_pointState; Piece m_selectedPiece; Color m_selectedPieceColor; const Transform* m_selectedPieceTransform; CoordPoint m_selectedPieceOffset; MovePoints m_selectedPiecePoints; Grid m_labels; BoardPainter m_boardPainter; QPixmap* m_emptyBoardPixmap; QPixmap* m_boardPixmap; bool m_isMoveShown; Color m_currentMoveShownColor; MovePoints m_currentMoveShownPoints; int m_currentMoveShownAnimationIndex; QTimer m_currentMoveShownAnimationTimer; Move findSelectedPieceMove(); void setEmptyBoardDirty(); void setDirty(); void setSelectedPieceOffset(const QMouseEvent& event); void setSelectedPieceOffset(const CoordPoint& offset); void setSelectedPiecePoints(); private slots: void showMoveAnimation(); }; inline const Board& GuiBoard::getBoard() const { return m_bd; } inline const Grid& GuiBoard::getLabels() const { return m_labels; } inline Piece GuiBoard::getSelectedPiece() const { return m_selectedPiece; } inline const Transform* GuiBoard::getSelectedPieceTransform() const { return m_selectedPieceTransform; } //----------------------------------------------------------------------------- #endif // LIBPENTOBI_GUI_GUI_BOARD_H pentobi-7.2/src/libpentobi_gui/GuiBoardUtil.cpp000066400000000000000000000105771227240712600216570ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/GuiBoardUtil.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "GuiBoardUtil.h" #include "libboardgame_util/Log.h" #include "libboardgame_util/StringUtil.h" namespace gui_board_util { using libpentobi_base::ColorMove; using libpentobi_base::Tree; using libboardgame_sgf::Node; using libboardgame_sgf::ChildIterator; using libboardgame_util::get_letter_coord; using libboardgame_util::log; using libboardgame_util::Exception; //----------------------------------------------------------------------------- namespace { void appendMoveAnnotation(QString& label, const Game& game, const Node& node) { auto& tree = game.get_tree(); double goodMove = tree.get_good_move(node); if (goodMove > 1) { label.append("!!"); return; } if (goodMove > 0) { label.append("!"); return; } double badMove = tree.get_bad_move(node); if (badMove > 1) { label.append("??"); return; } if (badMove > 0) { label.append("?"); return; } if (tree.is_interesting_move(node)) { label.append("!?"); return; } if (tree.is_doubtful_move(node)) { label.append("?!"); return; } } /** Get the index of a variation. This ignores child nodes without moves so that the moves are still labeled 1a, 1b, 1c, etc. even if this does not correspond to the child node index. (Note that this is a different convention from variation strings which does not use move number and child move index, but node depth and child node index) */ bool getVariationIndex(const Tree& tree, const Node& node, unsigned& moveIndex) { auto parent = node.get_parent_or_null(); if (parent == nullptr || parent->has_single_child()) return false; unsigned nuSiblingMoves = 0; moveIndex = 0; for (ChildIterator i(*parent); i; ++i) { if (! tree.has_move(*i)) continue; if (&(*i) == &node) moveIndex = nuSiblingMoves; ++nuSiblingMoves; } if (nuSiblingMoves == 1) return false; return true; } void setMoveLabel(GuiBoard& guiBoard, const Game& game, const Node& node, unsigned moveNumber, ColorMove mv, bool markVariations) { if (! mv.is_regular()) return; auto& bd = game.get_board(); Point p = bd.get_move_info_ext_2(mv.move).center; QString label; label.setNum(moveNumber); if (markVariations) { unsigned moveIndex; if (getVariationIndex(game.get_tree(), node, moveIndex)) label.append(get_letter_coord(moveIndex).c_str()); } appendMoveAnnotation(label, game, node); guiBoard.setLabel(p, label); } } // namespace //----------------------------------------------------------------------------- void setMarkup(GuiBoard& guiBoard, const Game& game, unsigned markMovesBegin, unsigned markMovesEnd, bool markVariations) { guiBoard.clearMarkup(); if (markMovesBegin > 0) { auto& tree = game.get_tree(); auto& bd = game.get_board(); unsigned displayedMoveNumber = 0; // pass moves have no number auto node = &game.get_current(); do { auto mv = tree.get_move_ignore_invalid(*node); if (mv.is_regular()) ++displayedMoveNumber; node = node->get_parent_or_null(); } while (node != nullptr); unsigned moveNumber = bd.get_nu_moves(); node = &game.get_current(); do { auto mv = tree.get_move_ignore_invalid(*node); if (! mv.is_null()) { if (! mv.move.is_pass()) { if (moveNumber >= markMovesBegin && moveNumber <= markMovesEnd) setMoveLabel(guiBoard, game, *node, moveNumber, mv, markVariations); --displayedMoveNumber; } --moveNumber; } node = node->get_parent_or_null(); } while (node != nullptr); } } //----------------------------------------------------------------------------- } // namespace gui_board_util pentobi-7.2/src/libpentobi_gui/GuiBoardUtil.h000066400000000000000000000014251227240712600213140ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/GuiBoardUtil.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_GUI_GUI_BOARD_UTIL_H #define LIBPENTOBI_GUI_GUI_BOARD_UTIL_H #include "GuiBoard.h" #include "libpentobi_base/Game.h" namespace gui_board_util { using libpentobi_base::Game; //----------------------------------------------------------------------------- void setMarkup(GuiBoard& guiBoard, const Game& game, unsigned markMovesBegin, unsigned markMovesEnd, bool markVariations); //----------------------------------------------------------------------------- } // namespace gui_board_util #endif // LIBPENTOBI_GUI_GUI_BOARD_UTIL_H pentobi-7.2/src/libpentobi_gui/HelpWindow.cpp000066400000000000000000000074401227240712600214000ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/HelpWindow.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "HelpWindow.h" #include #include #include #include #include #include "libboardgame_util/Log.h" using libboardgame_util::log; //----------------------------------------------------------------------------- namespace { void setIcon(QAction* action, const QString& name) { QString fallback = QString(":/libpentobi_gui/icons/%1.png").arg(name); action->setIcon(QIcon::fromTheme(name, QIcon(fallback))); } } // namespace //----------------------------------------------------------------------------- HelpWindow::HelpWindow(QWidget* parent, const QString& mainPage) : QMainWindow(parent) { log() << "Loading " << mainPage.toLocal8Bit().constData() << '\n'; setWindowTitle(tr("Pentobi User Manual")); if (QIcon::hasThemeIcon("help-browser")) setWindowIcon(QIcon::fromTheme("help-browser")); m_mainPageUrl = QUrl::fromLocalFile(mainPage); auto browser = new QTextBrowser(this); setCentralWidget(browser); browser->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); browser->setSource(m_mainPageUrl); auto actionBack = new QAction(tr("Back"), this); actionBack->setToolTip(tr("Show previous page in history")); actionBack->setEnabled(false); setIcon(actionBack, "go-previous"); connect(actionBack, SIGNAL(triggered()), browser, SLOT(backward())); connect(browser, SIGNAL(backwardAvailable(bool)), actionBack, SLOT(setEnabled(bool))); auto actionForward = new QAction(tr("Forward"), this); actionForward->setToolTip(tr("Show next page in history")); actionForward->setEnabled(false); setIcon(actionForward, "go-next"); connect(actionForward, SIGNAL(triggered()), browser, SLOT(forward())); connect(browser, SIGNAL(forwardAvailable(bool)), actionForward, SLOT(setEnabled(bool))); m_actionHome = new QAction(tr("Contents"), this); m_actionHome->setToolTip(tr("Show table of contents")); m_actionHome->setEnabled(false); setIcon(m_actionHome, "go-home"); connect(m_actionHome, SIGNAL(triggered()), browser, SLOT(home())); connect(browser, SIGNAL(sourceChanged(const QUrl&)), SLOT(handleSourceChanged(const QUrl&))); auto actionClose = new QAction("", this); actionClose->setShortcut(QKeySequence::Close); connect(actionClose, SIGNAL(triggered()), SLOT(hide())); addAction(actionClose); auto toolBar = new QToolBar(this); toolBar->setMovable(false); toolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle); toolBar->addAction(actionBack); toolBar->addAction(actionForward); toolBar->addAction(m_actionHome); addToolBar(toolBar); QSettings settings; if (! restoreGeometry(settings.value("helpwindow_geometry").toByteArray())) adjustSize(); } QString HelpWindow::findMainPage(QString dir, QString file, QString locale) { QString path; path = dir + "/" + locale + "/" + file; if (QFile(path).exists()) return path; QStringList list = locale.split("_"); path = dir + "/" + list[0] + "/" + file; if (QFile(path).exists()) return path; return dir + "/en/" + file; } void HelpWindow::closeEvent(QCloseEvent* event) { QSettings settings; settings.setValue("helpwindow_geometry", saveGeometry()); QMainWindow::closeEvent(event); } void HelpWindow::handleSourceChanged(const QUrl& src) { m_actionHome->setEnabled(src != m_mainPageUrl); } QSize HelpWindow::sizeHint() const { return QSize(600, 800); } //----------------------------------------------------------------------------- pentobi-7.2/src/libpentobi_gui/HelpWindow.h000066400000000000000000000033461227240712600210460ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/HelpWindow.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_GUI_HELP_WINDOW_H #define LIBPENTOBI_GUI_HELP_WINDOW_H // Needed in the header because moc_*.cxx does not include config.h #ifdef HAVE_CONFIG_H #include #endif #include #include //----------------------------------------------------------------------------- class HelpWindow : public QMainWindow { Q_OBJECT public: /** Find the main page for a given language. Assumes that the translations are in subdirectories of a given directory. The subdirectories use either the language code as the name (e.g. "en") or the language code followed by the country code separated by an underscore (e.g. "en_US"). "en" must always exist and is returned as the default if no translation for a given language exists. @param dir The directory @param file The file name of the main page @param locale The language code followed by the country code separated by an underscore (as returned by QLocale::system().name()) @return The full path of the main page. */ static QString findMainPage(QString dir, QString file, QString locale); HelpWindow(QWidget* parent, const QString& mainPage); QSize sizeHint() const; protected: void closeEvent(QCloseEvent* event) override; private: QUrl m_mainPageUrl; QAction* m_actionHome; private slots: void handleSourceChanged(const QUrl& src); }; //----------------------------------------------------------------------------- #endif // LIBPENTOBI_GUI_HELP_WINDOW_H pentobi-7.2/src/libpentobi_gui/InitialRatingDialog.cpp000066400000000000000000000040121227240712600231660ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/InitialRatingDialog.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "InitialRatingDialog.h" #include #include #include #include //----------------------------------------------------------------------------- InitialRatingDialog::InitialRatingDialog(QWidget* parent) : QDialog(parent) { setWindowTitle(tr("Initial Rating")); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); auto layout = new QVBoxLayout(); setLayout(layout); auto label = new QLabel(tr("You have not yet played rated games in this game" " variant. Estimate your playing strength to" " initialize your rating.")); label->setWordWrap(true); layout->addWidget(label); auto sliderBoxLayout = new QHBoxLayout(); layout->addLayout(sliderBoxLayout); sliderBoxLayout->addWidget(new QLabel(tr("Beginner"))); m_slider = new QSlider(Qt::Horizontal); m_slider->setMinimum(1000); m_slider->setMaximum(2000); m_slider->setSingleStep(10); m_slider->setPageStep(100); sliderBoxLayout->addWidget(m_slider); sliderBoxLayout->addWidget(new QLabel(tr("Expert"))); m_ratingLabel = new QLabel(); layout->addWidget(m_ratingLabel); setRating(1000); connect(m_slider, SIGNAL(valueChanged(int)), SLOT(setRating(int))); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); layout->addWidget(buttonBox); connect(buttonBox, SIGNAL(accepted()), SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), SLOT(reject())); } void InitialRatingDialog::setRating(int rating) { m_rating = rating; m_ratingLabel->setText(tr("Your initial rating: %1").arg(rating)); } //----------------------------------------------------------------------------- pentobi-7.2/src/libpentobi_gui/InitialRatingDialog.h000066400000000000000000000021371227240712600226410ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/InitialRatingDialog.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_GUI_INITIAL_RATING_DIALOG_H #define LIBPENTOBI_GUI_INITIAL_RATING_DIALOG_H // Needed in the header because moc_*.cxx does not include config.h #ifdef HAVE_CONFIG_H #include #endif #include class QLabel; class QSlider; using namespace std; //----------------------------------------------------------------------------- /** Dialog that asks the user to estimate his initial rating. */ class InitialRatingDialog : public QDialog { Q_OBJECT public: InitialRatingDialog(QWidget* parent); int getRating() const; public slots: void setRating(int rating); private: int m_rating; QSlider* m_slider; QLabel* m_ratingLabel; }; inline int InitialRatingDialog::getRating() const { return m_rating; } //----------------------------------------------------------------------------- #endif // LIBPENTOBI_GUI_INITIAL_RATING_DIALOG_H pentobi-7.2/src/libpentobi_gui/LeaveFullscreenButton.cpp000066400000000000000000000045201227240712600235670ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/LeaveFullscreenButton.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "LeaveFullscreenButton.h" #include #include #include #include #include //----------------------------------------------------------------------------- LeaveFullscreenButton::LeaveFullscreenButton(QWidget* parent, QAction* action) : QObject(parent) { m_timer = new QTimer(); m_timer->setSingleShot(true); m_triggerArea = new QWidget(parent); m_triggerArea->setMouseTracking(true); m_button = new QToolButton(parent); m_button->setDefaultAction(action); m_button->setToolTip(""); m_button->setToolButtonStyle(Qt::ToolButtonTextOnly); m_button->show(); // Resize to size hint as a workaround for a bug that clips the // long button text (tested on Qt 4.8.3 on Linux/KDE). m_button->resize(m_button->sizeHint()); int x = qApp->desktop()->screenGeometry().width() - m_button->width(); m_buttonPos = QPoint(x, 0); m_triggerArea->resize(QSize(m_button->width(), m_button->height() / 2)); m_triggerArea->move(m_buttonPos); m_animation = new QPropertyAnimation(m_button, "pos"); m_animation->setDuration(1000); m_animation->setStartValue(m_buttonPos); m_animation->setEndValue(QPoint(x, -m_button->height() + 5)); qApp->installEventFilter(this); connect(m_timer, SIGNAL(timeout()), SLOT(slideOut())); } void LeaveFullscreenButton::hideButton() { m_animation->stop(); m_timer->stop(); m_triggerArea->hide(); m_button->hide(); } bool LeaveFullscreenButton::eventFilter(QObject* watched, QEvent* event) { if (m_button->isVisible() && event->type() == QEvent::MouseMove && (watched == m_triggerArea || watched == m_button)) showButton(); return false; } void LeaveFullscreenButton::showButton() { m_animation->stop(); m_button->move(m_buttonPos); m_button->show(); m_triggerArea->hide(); m_timer->start(5000); } void LeaveFullscreenButton::slideOut() { m_triggerArea->show(); m_animation->start(); } //----------------------------------------------------------------------------- pentobi-7.2/src/libpentobi_gui/LeaveFullscreenButton.h000066400000000000000000000035541227240712600232420ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/LeaveFullscreenButton.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_GUI_LEAVE_FULLSCREEN_BUTTON_H #define LIBPENTOBI_GUI_LEAVE_FULLSCREEN_BUTTON_H // Needed in the header because moc_*.cxx does not include config.h #ifdef HAVE_CONFIG_H #include #endif #include #include class QAction; class QPropertyAnimation; class QTimer; class QToolButton; //----------------------------------------------------------------------------- /** A button at the top right of the screen to leave fullscreen mode that slides of the screen after a few seconds. A few pixels of the button stay visible and also an invisible slightly larger trigger area. If the mouse is moved over this area, the button becomes visible again. */ class LeaveFullscreenButton : public QObject { Q_OBJECT public: /** Constructor. @param parent The widget that will become fullscreen. This class adds two child widgets to the parent: the actual button and the trigger area (an invisible widget that listens for mouse movements and triggers the button to become visible again if it is slid out). @param action The action for leaving fullscreen mode associated with the button */ LeaveFullscreenButton(QWidget* parent, QAction* action); bool eventFilter(QObject* watched, QEvent* event); void showButton(); void hideButton(); private: QToolButton* m_button; QWidget* m_triggerArea; QPoint m_buttonPos; QTimer* m_timer; QPropertyAnimation* m_animation; private slots: void slideOut(); }; //----------------------------------------------------------------------------- #endif // LIBPENTOBI_GUI_LEAVE_FULLSCREEN_BUTTON_H pentobi-7.2/src/libpentobi_gui/LineEdit.cpp000066400000000000000000000014721227240712600210140ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/LineEdit.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "LineEdit.h" #include //----------------------------------------------------------------------------- LineEdit::LineEdit(QWidget* parent, int nuCharactersHint) : QLineEdit(parent), m_nuCharactersHint(nuCharactersHint) { } QSize LineEdit::sizeHint() const { QFont font = QApplication::font(); QFontMetrics metrics(font); QSize size = QLineEdit::sizeHint(); size.setWidth(m_nuCharactersHint * metrics.averageCharWidth()); return size; } //----------------------------------------------------------------------------- pentobi-7.2/src/libpentobi_gui/LineEdit.h000066400000000000000000000015761227240712600204660ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/LineEdit.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_GUI_LINE_EDIT_H #define LIBPENTOBI_GUI_LINE_EDIT_H // Needed in the header because moc_*.cxx does not include config.h #ifdef HAVE_CONFIG_H #include #endif #include //----------------------------------------------------------------------------- /** QLineEdit with a configurable size hint depending on the expected number of characters. */ class LineEdit : public QLineEdit { Q_OBJECT public: LineEdit(QWidget* parent, int nuCharactersHint); QSize sizeHint() const; private: int m_nuCharactersHint; }; //----------------------------------------------------------------------------- #endif // LIBPENTOBI_GUI_LINE_EDIT_H pentobi-7.2/src/libpentobi_gui/OrientationDisplay.cpp000066400000000000000000000120121227240712600231300ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/OrientationDisplay.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "OrientationDisplay.h" #include #include "libboardgame_base/GeometryUtil.h" #include "libpentobi_gui/Util.h" using namespace std; using libboardgame_base::CoordPoint; using libboardgame_base::Transform; using libboardgame_base::geometry_util::normalize_offset; using libboardgame_base::geometry_util::type_match_shift; using libpentobi_base::BoardType; using libpentobi_base::Geometry; using libpentobi_base::PiecePoints; //----------------------------------------------------------------------------- OrientationDisplay::OrientationDisplay(QWidget* parent, const Board& bd) : QWidget(parent), m_bd(bd), m_piece(Piece::null()), m_transform(0), m_isColorSelected(false) { setMinimumWidth(30); setMinimumHeight(30); } void OrientationDisplay::clearSelectedColor() { if (m_isColorSelected) { m_isColorSelected = false; update(); } } void OrientationDisplay::clearSelectedPiece() { if (m_piece.is_null()) return; m_piece = Piece::null(); update(); } int OrientationDisplay::heightForWidth(int width) const { return width; } void OrientationDisplay::mousePressEvent(QMouseEvent*) { if (m_isColorSelected && m_piece.is_null()) emit colorClicked(m_color); } void OrientationDisplay::paintEvent(QPaintEvent*) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); auto board_type = m_bd.get_board_type(); qreal fieldWidth; qreal fieldHeight; qreal displayWidth; qreal displayHeight; bool isTrigon = (board_type == BoardType::trigon || board_type == BoardType::trigon_3); if (isTrigon) { int columns = 7; int rows = 4; qreal ratio = 1.732; fieldWidth = min(qreal(width()) / columns, qreal(height()) / (ratio * rows)); fieldHeight = ratio * fieldWidth; displayWidth = fieldWidth * columns; displayHeight = fieldHeight * rows; } else { int columns = 5; int rows = 5; fieldWidth = min(qreal(width()) / columns, qreal(height()) / rows); fieldHeight = fieldWidth; displayWidth = fieldWidth * columns; displayHeight = fieldHeight * rows; } if (m_piece.is_null()) { if (m_isColorSelected) { qreal dotSize = 0.06 * height(); auto color = Util::getPaintColor(m_bd.get_variant(), m_color); painter.setPen(Qt::NoPen); painter.setBrush(color); painter.drawEllipse(QPointF(0.5 * width(), 0.5 * height()), dotSize, dotSize); } return; } painter.save(); painter.translate(0.5 * (width() - displayWidth), 0.5 * (height() - displayHeight)); PiecePoints points = m_bd.get_piece_info(m_piece).get_points(); m_transform->transform(points.begin(), points.end()); auto& geometry = m_bd.get_geometry(); type_match_shift(geometry, points.begin(), points.end(), m_transform->get_new_point_type()); unsigned width; unsigned height; CoordPoint offset; normalize_offset(points.begin(), points.end(), width, height, offset); bool invertPointType = (geometry.get_point_type(offset) != geometry.get_point_type(0, 0)); painter.save(); painter.translate(0.5 * (displayWidth - width * fieldWidth), 0.5 * (displayHeight - height * fieldHeight)); auto variant = m_bd.get_variant(); for (CoordPoint p : points) { qreal x = p.x * fieldWidth; qreal y = (height - p.y - 1) * fieldHeight; if (isTrigon) { bool isUpside = (geometry.get_point_type(p) != 0); if (invertPointType) isUpside = ! isUpside; Util::paintColorTriangle(painter, variant, m_color, isUpside, x, y, fieldWidth, fieldHeight); } else Util::paintColorSquare(painter, variant, m_color, x, y, fieldWidth); } painter.restore(); painter.restore(); } void OrientationDisplay::selectColor(Color c) { if (m_isColorSelected && m_color == c) return; m_isColorSelected = true; m_color = c; update(); } void OrientationDisplay::setSelectedPiece(Piece piece) { auto transform = m_bd.get_transforms().get_default(); if (m_piece == piece && m_transform == transform) return; m_piece = piece; m_transform = transform; update(); } void OrientationDisplay::setSelectedPieceTransform(const Transform* transform) { if (m_transform == transform) return; m_transform = transform; update(); } //----------------------------------------------------------------------------- pentobi-7.2/src/libpentobi_gui/OrientationDisplay.h000066400000000000000000000031301227240712600225760ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/OrientationDisplay.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_GUI_ORIENTATION_DISPLAY_H #define LIBPENTOBI_GUI_ORIENTATION_DISPLAY_H // Needed in the header because moc_*.cxx does not include config.h #ifdef HAVE_CONFIG_H #include #endif #include #include "libpentobi_base/Board.h" using libboardgame_base::Transform; using libpentobi_base::Board; using libpentobi_base::Color; using libpentobi_base::Piece; using libpentobi_base::PieceInfo; //----------------------------------------------------------------------------- class OrientationDisplay : public QWidget { Q_OBJECT public: OrientationDisplay(QWidget* parent, const Board& bd); int heightForWidth(int width) const; void selectColor(Color c); void clearSelectedColor(); void clearSelectedPiece(); void setSelectedPiece(Piece piece); void setSelectedPieceTransform(const Transform* transform); signals: /** A mouse click on the orientation display while a color but no no piece was selected. */ void colorClicked(Color color); protected: void mousePressEvent(QMouseEvent* event) override; void paintEvent(QPaintEvent* event) override; private: const Board& m_bd; Piece m_piece; const Transform* m_transform; bool m_isColorSelected; Color m_color; }; //----------------------------------------------------------------------------- #endif // LIBPENTOBI_GUI_ORIENTATION_DISPLAY_H pentobi-7.2/src/libpentobi_gui/PieceSelector.cpp000066400000000000000000000233161227240712600220460ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/PieceSelector.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "PieceSelector.h" #include #include #include #include "libboardgame_base/GeometryUtil.h" #include "libboardgame_util/StringUtil.h" #include "libpentobi_gui/Util.h" using namespace std; using libboardgame_base::CoordPoint; using libboardgame_base::geometry_util::type_match_shift; using libboardgame_util::log; using libboardgame_util::trim; using libpentobi_base::BoardConst; using libpentobi_base::BoardType; using libpentobi_base::Variant; using libpentobi_base::Geometry; using libpentobi_base::PieceMap; //----------------------------------------------------------------------------- namespace { const string pieceLayoutClassic = " 1 .Z4Z4 . .L4L4L4 . O O . P P .L5L5L5L5 .V5V5V5 . U U U . N . . ." " . . .Z4Z4 . . .L4 . O O . P P .L5 . . . .V5 . . . U . U . N N .I5" " 2 2 . . . .T4 . . . . . . P . . . . X . .V5 .Z5 . . . . . . N .I5" " . . .I3 .T4T4T4 . . W W . . . F . X X X . . .Z5Z5Z5 . .T5 . N .I5" "V3 . .I3 . . . . . . . W W . F F . . X . . Y . . .Z5 . .T5 . . .I5" "V3V3 .I3 . .I4I4I4I4 . . W . . F F . . . Y Y Y Y . . .T5T5T5 . .I5"; const string pieceLayoutJunior = "1 . 1 . V3V3. . L4L4L4. T4T4T4. . O O . O O . P P . . I5. I5. . L5L5" ". . . . V3. . . L4. . . . T4. . . O O . O O . P P . . I5. I5. . . L5" "2 . 2 . . . V3. . . . L4. . . T4. . . . . . . P . . . I5. I5. L5. L5" "2 . 2 . . V3V3. . L4L4L4. . T4T4T4. . Z4. Z4. . . P . I5. I5. L5. L5" ". . . . . . . . . . . . . . . . . . Z4Z4. Z4Z4. P P . I5. I5. L5. . " "I3I3I3. I3I3I3. I4I4I4I4. I4I4I4I4. Z4. . . Z4. P P . . . . . L5L5. "; const string pieceLayoutTrigon = "L5L5 . . F F F F . .L6L6 . . O O O . . X X X . . .A6A6 . . G G . G . .C4C4 . . Y Y Y Y" "L5L5 . . F . F . . .L6L6 . . O O O . . X X X . .A6A6A6A6 . . G G G . .C4C4 . . Y Y . ." " .L5 . . . . . . S . .L6L6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2" " . . . . . S S S S . . . . . . . .P5P5P5P5 . . .I6I6 . .I5I5I5I5I5 . . W W W W W . . 2" "C5C5 . . . S . . . . V V . .P6 . . . .P5 . .A4 . .I6I6 . . . . . . . . . . W . . . . ." "C5C5C5 . . . . V V V V . .P6P6P6P6P6 . . .A4A4A4 . .I6I6 . .I3I3I3 . . 1 . . .I4I4I4I4"; } //----------------------------------------------------------------------------- PieceSelector::PieceSelector(QWidget* parent, const Board& bd, Color color) : QWidget(parent), m_bd(bd), m_color(color) { setMinimumWidth(170); setMinimumHeight(30); init(); } void PieceSelector::checkUpdate() { bool disabledStatus[maxColumns][maxRows]; setDisabledStatus(disabledStatus); bool changed = false; for (unsigned x = 0; x < m_nuColumns; ++x) for (unsigned y = 0; y < m_nuRows; ++y) if (! m_piece[x][y].is_null() && disabledStatus[x][y] != m_disabledStatus[x][y]) { changed = true; break; } if (changed) update(); } void PieceSelector::findPiecePoints(Piece piece, unsigned x, unsigned y, PiecePoints& points) const { CoordPoint p(x, y); if (x >= m_nuColumns || y >= m_nuRows || m_piece[x][y] != piece || points.contains(p)) return; points.push_back(p); // This assumes that no Trigon pieces touch at the corners, otherwise // we would need to iterate over neighboring CoordPoint's like AdjIterator // iterates over neighboring Point's findPiecePoints(piece, x + 1, y, points); findPiecePoints(piece, x - 1, y, points); findPiecePoints(piece, x, y + 1, points); findPiecePoints(piece, x, y - 1, points); } int PieceSelector::heightForWidth(int width) const { // Use ratio for layout of classic pieces, which has larger relative width // because the limiting factor in the right panel of the main window is the // width return width / 33 * 6; } void PieceSelector::init() { BoardType boardType = m_bd.get_board_type(); auto variant = m_bd.get_variant(); const string* pieceLayout; if (boardType == BoardType::trigon || boardType == BoardType::trigon_3) { pieceLayout = &pieceLayoutTrigon; m_nuColumns = 43; m_nuRows = 6; } else if (variant == Variant::junior) { pieceLayout = &pieceLayoutJunior; m_nuColumns = 34; m_nuRows = 6; } else { pieceLayout = &pieceLayoutClassic; m_nuColumns = 33; m_nuRows = 6; } for (unsigned y = 0; y < m_nuRows; ++y) for (unsigned x = 0; x < m_nuColumns; ++x) { string name = pieceLayout->substr(y * m_nuColumns * 2 + x * 2, 2); name = trim(name); Piece piece = Piece::null(); if (name != ".") { m_bd.get_piece_by_name(name, piece); LIBBOARDGAME_ASSERT(! piece.is_null()); } m_piece[x][y] = piece; } auto& geometry = m_bd.get_geometry(); for (unsigned y = 0; y < m_nuRows; ++y) for (unsigned x = 0; x < m_nuColumns; ++x) { Piece piece = m_piece[x][y]; if (piece.is_null()) continue; PiecePoints points; findPiecePoints(piece, x, y, points); // Mirror y to match the convention of CoordPoint coordinates for (CoordPoint& p : points) p.y = m_nuRows - p.y - 1; type_match_shift(geometry, points.begin(), points.end(), 0); m_transform[x][y] = m_bd.get_piece_info(piece).find_transform(geometry, points); LIBBOARDGAME_ASSERT(m_transform[x][y] != 0); } setDisabledStatus(m_disabledStatus); update(); } void PieceSelector::mousePressEvent(QMouseEvent* event) { qreal pixelX = event->x() - 0.5 * (width() - m_selectorWidth); qreal pixelY = event->y() - 0.5 * (height() - m_selectorHeight); if (pixelX < 0 || pixelX >= m_selectorWidth || pixelY < 0 || pixelY >= m_selectorHeight) return; int x = static_cast(pixelX / m_fieldWidth); int y = static_cast(pixelY / m_fieldHeight); Piece piece = m_piece[x][y]; if (piece.is_null() || m_disabledStatus[x][y]) return; update(); emit pieceSelected(m_color, piece, m_transform[x][y]); } void PieceSelector::paintEvent(QPaintEvent*) { setDisabledStatus(m_disabledStatus); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); BoardType boardType = m_bd.get_board_type(); bool isTrigon = (boardType == BoardType::trigon || boardType == BoardType::trigon_3); if (isTrigon) { qreal ratio = 1.732; m_fieldWidth = min(qreal(width()) / (m_nuColumns + 1), qreal(height()) / (ratio * m_nuRows)); m_fieldHeight = ratio * m_fieldWidth; } else { m_fieldWidth = min(qreal(width()) / m_nuColumns, qreal(height()) / m_nuRows); m_fieldHeight = m_fieldWidth; } m_selectorWidth = m_fieldWidth * m_nuColumns; m_selectorHeight = m_fieldHeight * m_nuRows; painter.save(); painter.translate(0.5 * (width() - m_selectorWidth), 0.5 * (height() - m_selectorHeight)); auto variant = m_bd.get_variant(); auto& geometry = m_bd.get_geometry(); for (unsigned x = 0; x < m_nuColumns; ++x) for (unsigned y = 0; y < m_nuRows; ++y) { Piece piece = m_piece[x][y]; if (! piece.is_null() && ! m_disabledStatus[x][y]) { if (isTrigon) { bool isUpside = (geometry.get_point_type(x, m_nuRows - y - 1) != geometry.get_point_type(0, 0)); Util::paintColorTriangle(painter, variant, m_color, isUpside, x * m_fieldWidth, y * m_fieldHeight, m_fieldWidth, m_fieldHeight); } else Util::paintColorSquare(painter, variant, m_color, x * m_fieldWidth, y * m_fieldHeight, m_fieldWidth); } } painter.restore(); } void PieceSelector::setDisabledStatus(bool disabledStatus[maxColumns][maxRows]) { bool marker[maxColumns][maxRows]; for (unsigned x = 0; x < m_nuColumns; ++x) for (unsigned y = 0; y < m_nuRows; ++y) { marker[x][y] = false; disabledStatus[x][y] = false; } PieceMap nuInstances; nuInstances.fill(0); bool isColorUsed = (m_color.to_int() < m_bd.get_nu_colors()); for (unsigned x = 0; x < m_nuColumns; ++x) for (unsigned y = 0; y < m_nuRows; ++y) { if (marker[x][y]) continue; Piece piece = m_piece[x][y]; if (piece.is_null()) continue; PiecePoints points; findPiecePoints(piece, x, y, points); bool disabled = false; if (! isColorUsed || ++nuInstances[piece] > m_bd.get_nu_left_piece(m_color, piece)) disabled = true; for (CoordPoint p : points) { disabledStatus[p.x][p.y] = disabled; marker[p.x][p.y] = true; } } } //----------------------------------------------------------------------------- pentobi-7.2/src/libpentobi_gui/PieceSelector.h000066400000000000000000000043141227240712600215100ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/PieceSelector.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_GUI_PIECE_SELECTOR_H #define LIBPENTOBI_GUI_PIECE_SELECTOR_H // Needed in the header because moc_*.cxx does not include config.h #ifdef HAVE_CONFIG_H #include #endif #include #include "libpentobi_base/Board.h" #include "libpentobi_base/Color.h" using libboardgame_base::Transform; using libboardgame_util::ArrayList; using libpentobi_base::Color; using libpentobi_base::Board; using libpentobi_base::Piece; using libpentobi_base::PiecePoints; //----------------------------------------------------------------------------- class PieceSelector : public QWidget { Q_OBJECT public: PieceSelector(QWidget* parent, const Board& bd, Color color); /** Needs to be called after the game variant of the current board has changed because references to pieces are only unique within a game variant. */ void init(); int heightForWidth(int width) const; /** Call update() if pieces left have changed since last paint. */ void checkUpdate(); signals: void pieceSelected(Color color, Piece piece, const Transform* transform); protected: void mousePressEvent(QMouseEvent* event) override; void paintEvent(QPaintEvent* event) override; private: static const unsigned maxColumns = 47; static const unsigned maxRows = 6; const Board& m_bd; Color m_color; unsigned m_nuColumns; unsigned m_nuRows; Piece m_piece[maxColumns][maxRows]; const Transform* m_transform[maxColumns][maxRows]; /** Pieces left at last time the widget was painted. */ bool m_disabledStatus[maxColumns][maxRows]; qreal m_fieldWidth; qreal m_fieldHeight; qreal m_selectorWidth; qreal m_selectorHeight; void findPiecePoints(Piece piece, unsigned x, unsigned y, PiecePoints& points) const; void setDisabledStatus(bool disabledStatus[maxColumns][maxRows]); }; //----------------------------------------------------------------------------- #endif // LIBPENTOBI_GUI_PIECE_SELECTOR_H pentobi-7.2/src/libpentobi_gui/SameHeightLayout.cpp000066400000000000000000000055561227240712600225420ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/SameHeightLayout.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "SameHeightLayout.h" #include #include using namespace std; //----------------------------------------------------------------------------- SameHeightLayout::SameHeightLayout(QWidget* parent) : QLayout(parent) { } SameHeightLayout::~SameHeightLayout() { QLayoutItem* item; while ((item = takeAt(0))) delete item; } void SameHeightLayout::addItem(QLayoutItem* item) { m_list.append(item); } QSize SameHeightLayout::sizeHint() const { QSize s(0, 0); int count = m_list.count(); int i = 0; while (i < count) { QSize size = m_list.at(i)->sizeHint(); s.setWidth(max(size.width(), s.width())); s.setHeight(s.height() + size.height()); ++i; } return s + (count - 1) * QSize(0, getSpacing()); } QSize SameHeightLayout::minimumSize() const { QSize s(0, 0); int count = m_list.count(); int i = 0; while (i < count) { QSize size = m_list.at(i)->minimumSize(); s.setWidth(max(size.width(), s.width())); s.setHeight(s.height() + size.height()); ++i; } return s + (count - 1) * QSize(0, getSpacing()); } int SameHeightLayout::count() const { return m_list.size(); } int SameHeightLayout::getSpacing() const { // spacing() returns -1 with Qt 4.7 on KDE. It returns 6 on Gnome. Is this a // bug? The documentation says: "If no value is explicitly set, the layout's // spacing is inherited from the parent layout, or from the style settings // for the parent widget." int result = spacing(); if (result < 0 && parentWidget() != 0) result = parentWidget()->style()->layoutSpacing(QSizePolicy::Frame, QSizePolicy::Frame, Qt::Vertical); if (result < 0) result = 5; return result; } QLayoutItem* SameHeightLayout::itemAt(int i) const { return m_list.value(i); } QLayoutItem* SameHeightLayout::takeAt(int i) { return i >= 0 && i < m_list.size() ? m_list.takeAt(i) : 0; } void SameHeightLayout::setGeometry(const QRect& rect) { QLayout::setGeometry(rect); if (m_list.size() == 0) return; int count = m_list.count(); int width = rect.width(); int height = (rect.height() - (count - 1) * getSpacing()) / count; int x = rect.x(); int y = rect.y(); for (int i = 0; i < count; ++i) { QRect geom(x, y, width, height); m_list.at(i)->setGeometry(geom); y = y + height + getSpacing(); } } //----------------------------------------------------------------------------- pentobi-7.2/src/libpentobi_gui/SameHeightLayout.h000066400000000000000000000025341227240712600222000ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/SameHeightLayout.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_GUI_SAME_HEIGHT_LAYOUT_H #define LIBPENTOBI_GUI_SAME_HEIGHT_LAYOUT_H // Needed in the header because moc_*.cxx does not include config.h #ifdef HAVE_CONFIG_H #include #endif #include //----------------------------------------------------------------------------- /** Layout that assigns exactly the same height to all items. Needed for the box containing the piece selectors, because QBoxLayout and QGridLayout do not always assign the exact same height to all items if the height is not a multiple of the number of items. */ class SameHeightLayout : public QLayout { Q_OBJECT public: SameHeightLayout(QWidget* parent = nullptr); ~SameHeightLayout(); void addItem(QLayoutItem* item); QSize sizeHint() const; QSize minimumSize() const; int count() const; QLayoutItem* itemAt(int i) const; QLayoutItem* takeAt(int i); void setGeometry(const QRect& rect); private: QList m_list; int getSpacing() const; }; //----------------------------------------------------------------------------- #endif // LIBPENTOBI_GUI_SAME_HEIGHT_LAYOUT_H pentobi-7.2/src/libpentobi_gui/ScoreDisplay.cpp000066400000000000000000000231311227240712600217140ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/ScoreDisplay.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "ScoreDisplay.h" #include #include #include #include "libboardgame_util/Log.h" #include "libpentobi_gui/Util.h" using namespace std; using libboardgame_util::log; using libpentobi_base::ColorIterator; //----------------------------------------------------------------------------- ScoreDisplay::ScoreDisplay(QWidget* parent) : QWidget(parent), m_hasMoves(false), m_points(0), m_bonus(0) { m_variant = Variant::classic; m_font.setStyleStrategy(QFont::PreferOutline); setMinimumWidth(300); setMinimumHeight(20); setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); } void ScoreDisplay::drawScore(QPainter& painter, Color c, int x) { auto color = Util::getPaintColor(m_variant, c); painter.setPen(Qt::NoPen); painter.setBrush(color); QFontMetrics metrics(m_font); int ascent = metrics.ascent(); // y is baseline int y = static_cast(ceil(0.5 * (height() - ascent)) + ascent); painter.setRenderHint(QPainter::Antialiasing, true); painter.drawEllipse(x, y - m_colorDotSize, m_colorDotSize, m_colorDotSize); QString text = getScoreText(c); bool underline = ! m_hasMoves[c]; drawText(painter, text, x + m_colorDotWidth, y, underline); } void ScoreDisplay::drawScore2(QPainter& painter, Color c1, Color c2, int x) { auto color = Util::getPaintColor(m_variant, c1); painter.setPen(Qt::NoPen); painter.setBrush(color); QFontMetrics metrics(m_font); int ascent = metrics.ascent(); // y is baseline int y = static_cast(ceil(0.5 * (height() - ascent)) + ascent); painter.setRenderHint(QPainter::Antialiasing, true); painter.drawEllipse(x, y - m_colorDotSize, m_colorDotSize, m_colorDotSize); color = Util::getPaintColor(m_variant, c2); painter.setBrush(color); painter.drawEllipse(x + m_colorDotSize, y - m_colorDotSize, m_colorDotSize, m_colorDotSize); QString text = getScoreText2(c1, c2); bool underline = (! m_hasMoves[c1] && ! m_hasMoves[c2]); drawText(painter, text, x + m_twoColorDotWidth, y, underline); } void ScoreDisplay::drawText(QPainter& painter, const QString& text, int x, int y, bool underline) { painter.setFont(m_font); QFontMetrics metrics(m_font); auto color = QApplication::palette().color(QPalette::WindowText); painter.setPen(color); painter.setRenderHint(QPainter::Antialiasing, false); painter.drawText(x, y, text); if (underline) { // Draw underline (instead of using an underlined font because the // underline of some fonts is too close to the text and we want it // to be very visible) int lineWidth = metrics.lineWidth(); QPen pen(color); pen.setWidth(lineWidth); painter.setPen(pen); y += 2 * lineWidth; if (y > height() - 1) y = height() - 1; painter.drawLine(x, y, x + metrics.width(text), y); } } int ScoreDisplay::getMaxScoreTextWidth() const { return getTextWidth(getScoreText(188, 20)); } int ScoreDisplay::getMaxScoreTextWidth2() const { return getTextWidth(getScoreText(88, 20)); } QString ScoreDisplay::getScoreText(unsigned points, unsigned bonus) const { QString text; text.setNum(points + bonus); return text; } QString ScoreDisplay::getScoreText(Color c) { return getScoreText(m_points[c], m_bonus[c]); } QString ScoreDisplay::getScoreText2(Color c1, Color c2) { return getScoreText(m_points[c1] + m_points[c2], m_bonus[c1] + m_bonus[c2]); } int ScoreDisplay::getScoreTextWidth(Color c) { return getTextWidth(getScoreText(c)); } int ScoreDisplay::getScoreTextWidth2(Color c1, Color c2) { return getTextWidth(getScoreText2(c1, c2)); } int ScoreDisplay::getTextWidth(QString text) const { // Make text width only depend on number of digits to avoid frequent small // changes to the layout QFontMetrics metrics(m_font); int maxDigitWidth = 0; maxDigitWidth = max(maxDigitWidth, metrics.width('0')); maxDigitWidth = max(maxDigitWidth, metrics.width('1')); maxDigitWidth = max(maxDigitWidth, metrics.width('2')); maxDigitWidth = max(maxDigitWidth, metrics.width('3')); maxDigitWidth = max(maxDigitWidth, metrics.width('4')); maxDigitWidth = max(maxDigitWidth, metrics.width('5')); maxDigitWidth = max(maxDigitWidth, metrics.width('6')); maxDigitWidth = max(maxDigitWidth, metrics.width('7')); maxDigitWidth = max(maxDigitWidth, metrics.width('8')); maxDigitWidth = max(maxDigitWidth, metrics.width('9')); return max(text.length() * maxDigitWidth, metrics.boundingRect(text).width()); } void ScoreDisplay::paintEvent(QPaintEvent*) { QPainter painter(this); m_colorDotSize = static_cast(0.8 * m_fontSize); m_colorDotSpace = static_cast(0.3 * m_fontSize); m_colorDotWidth = m_colorDotSize + m_colorDotSpace; m_twoColorDotWidth = 2 * m_colorDotSize + m_colorDotSpace; if (m_variant == Variant::duo || m_variant == Variant::junior) { int textWidthBlue = getScoreTextWidth(Color(0)); int textWidthGreen = getScoreTextWidth(Color(1)); int totalWidth = textWidthBlue + textWidthGreen + 2 * m_colorDotWidth; qreal pad = qreal(width() - totalWidth) / 3.f; qreal x = pad; drawScore(painter, Color(0), static_cast(x)); x += m_colorDotWidth + textWidthBlue + pad; drawScore(painter, Color(1), static_cast(x)); } else if (m_variant == Variant::classic || m_variant == Variant::trigon) { int textWidthBlue = getScoreTextWidth(Color(0)); int textWidthYellow = getScoreTextWidth(Color(1)); int textWidthRed = getScoreTextWidth(Color(2)); int textWidthGreen = getScoreTextWidth(Color(3)); int totalWidth = textWidthBlue + textWidthRed + textWidthYellow + textWidthGreen + 4 * m_colorDotWidth; qreal pad = qreal(width() - totalWidth) / 5.f; qreal x = pad; drawScore(painter, Color(0), static_cast(x)); x += m_colorDotWidth + textWidthBlue + pad; drawScore(painter, Color(1), static_cast(x)); x += m_colorDotWidth + textWidthYellow + pad; drawScore(painter, Color(2), static_cast(x)); x += m_colorDotWidth + textWidthRed + pad; drawScore(painter, Color(3), static_cast(x)); } else if (m_variant == Variant::trigon_3) { int textWidthBlue = getScoreTextWidth(Color(0)); int textWidthYellow = getScoreTextWidth(Color(1)); int textWidthRed = getScoreTextWidth(Color(2)); int totalWidth = textWidthBlue + textWidthRed + textWidthYellow + 3 * m_colorDotWidth; qreal pad = qreal(width() - totalWidth) / 4.f; qreal x = pad; drawScore(painter, Color(0), static_cast(x)); x += m_colorDotWidth + textWidthBlue + pad; drawScore(painter, Color(1), static_cast(x)); x += m_colorDotWidth + textWidthYellow + pad; drawScore(painter, Color(2), static_cast(x)); } else { LIBBOARDGAME_ASSERT(m_variant == Variant::classic_2 || m_variant == Variant::trigon_2); int textWidthBlueRed = getScoreTextWidth2(Color(0), Color(2)); int textWidthYellowGreen = getScoreTextWidth2(Color(1), Color(3)); int textWidthBlue = getScoreTextWidth(Color(0)); int textWidthYellow = getScoreTextWidth(Color(1)); int textWidthRed = getScoreTextWidth(Color(2)); int textWidthGreen = getScoreTextWidth(Color(3)); int totalWidth = textWidthBlueRed + textWidthYellowGreen + textWidthBlue + textWidthRed + textWidthYellow + textWidthGreen + 2 * m_twoColorDotWidth + 4 * m_colorDotWidth; qreal pad = qreal(width() - totalWidth) / 7.f; qreal x = pad; drawScore2(painter, Color(0), Color(2), static_cast(x)); x += m_twoColorDotWidth + textWidthBlueRed + pad; drawScore2(painter, Color(1), Color(3), static_cast(x)); x += m_twoColorDotWidth + textWidthYellowGreen + pad; drawScore(painter, Color(0), static_cast(x)); x += m_colorDotWidth + textWidthBlue + pad; drawScore(painter, Color(1), static_cast(x)); x += m_colorDotWidth + textWidthYellow + pad; drawScore(painter, Color(2), static_cast(x)); x += m_colorDotWidth + textWidthRed + pad; drawScore(painter, Color(3), static_cast(x)); } } void ScoreDisplay::resizeEvent(QResizeEvent*) { m_fontSize = static_cast(floor(0.6 * height())); m_font.setPixelSize(m_fontSize); } void ScoreDisplay::updateScore(const Board& bd) { auto variant = bd.get_variant(); bool hasChanged = (m_variant != variant); m_variant = variant; for (ColorIterator i(bd.get_nu_colors()); i; ++i) { bool hasMoves = bd.has_moves(*i); unsigned points = bd.get_points(*i); unsigned bonus = bd.get_bonus(*i); if (hasMoves != m_hasMoves[*i] || m_points[*i] != points || m_bonus[*i] != bonus) { hasChanged = true; m_hasMoves[*i] = hasMoves; m_points[*i] = points; m_bonus[*i] = bonus; } } if (hasChanged) update(); } //----------------------------------------------------------------------------- pentobi-7.2/src/libpentobi_gui/ScoreDisplay.h000066400000000000000000000036001227240712600213600ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/ScoreDisplay.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_GUI_SCORE_DISPLAY_H #define LIBPENTOBI_GUI_SCORE_DISPLAY_H // Needed in the header because moc_*.cxx does not include config.h #ifdef HAVE_CONFIG_H #include #endif #include #include "libpentobi_base/Board.h" using libpentobi_base::Board; using libpentobi_base::Color; using libpentobi_base::ColorMap; using libpentobi_base::Variant; //----------------------------------------------------------------------------- class ScoreDisplay : public QWidget { Q_OBJECT public: ScoreDisplay(QWidget* parent = nullptr); void updateScore(const Board& bd); protected: void paintEvent(QPaintEvent* event) override; void resizeEvent(QResizeEvent* event) override; private: int m_fontSize; QFont m_font; Variant m_variant; ColorMap m_hasMoves; ColorMap m_points; ColorMap m_bonus; int m_colorDotSize; int m_colorDotSpace; int m_colorDotWidth; int m_twoColorDotWidth; QString getScoreText(Color c); QString getScoreText2(Color c1, Color c2); int getScoreTextWidth(Color c); int getScoreTextWidth2(Color c1, Color c2); void drawScore(QPainter& painter, Color c, int x); void drawScore2(QPainter& painter, Color c1, Color c2, int x); int getMaxScoreTextWidth() const; int getMaxScoreTextWidth2() const; QString getScoreText(unsigned points, unsigned bonus) const; int getTextWidth(QString text) const; void drawText(QPainter& painter, const QString& text, int x, int y, bool underline); }; //----------------------------------------------------------------------------- #endif // LIBPENTOBI_GUI_SCORE_DISPLAY_H pentobi-7.2/src/libpentobi_gui/Util.cpp000066400000000000000000000254711227240712600202410ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/Util.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Util.h" #include //----------------------------------------------------------------------------- namespace { const QColor blue(0, 115, 207); const QColor green(0, 192, 0); const QColor red(230, 62, 44); const QColor yellow(235, 205, 35); const QColor gray(174, 167, 172); void paintDot(QPainter& painter, QColor color, qreal x, qreal y, qreal width, qreal height, qreal size) { painter.save(); painter.translate(x, y); painter.setPen(Qt::NoPen); painter.setBrush(color); painter.drawEllipse(QPointF(0.5 * width, 0.5 * height), size, size); painter.restore(); } void paintSquare(QPainter& painter, qreal x, qreal y, qreal size, const QColor& rectColor, const QColor& upLeftColor, const QColor& downRightColor) { painter.save(); painter.translate(x, y); painter.fillRect(QRectF(0, 0, size, size), rectColor); qreal border = 0.05 * size; const QPointF downRightPolygon[6] = { QPointF(border, size - border), QPointF(size - border, size - border), QPointF(size - border, border), QPointF(size, 0), QPointF(size, size), QPointF(0, size) }; painter.setPen(Qt::NoPen); painter.setBrush(downRightColor); painter.drawPolygon(downRightPolygon, 6); const QPointF upLeftPolygon[6] = { QPointF(0, 0), QPointF(size, 0), QPointF(size - border, border), QPointF(border, border), QPointF(border, size - border), QPointF(0, size) }; painter.setBrush(upLeftColor); painter.drawPolygon(upLeftPolygon, 6); painter.restore(); } void paintTriangle(QPainter& painter, bool isUpside, qreal x, qreal y, qreal width, qreal height, const QColor& color, const QColor& upLeftColor, const QColor& downRightColor) { painter.save(); painter.translate(x, y); qreal left = -0.5 * width; qreal right = 1.5 * width; if (isUpside) { const QPointF polygon[3] = { QPointF(left, height), QPointF(right, height), QPointF(0.5 * width, 0) }; painter.setPen(Qt::NoPen); painter.setBrush(color); painter.drawConvexPolygon(polygon, 3); qreal border = 0.08 * width; const QPointF downRightPolygon[6] = { QPointF(left, height), QPointF(right, height), QPointF(0.5 * width, 0), QPointF(0.5 * width, 2 * border), QPointF(right - 1.732 * border, height - border), QPointF(left + 1.732 * border, height - border) }; painter.setBrush(downRightColor); painter.drawPolygon(downRightPolygon, 6); const QPointF upLeftPolygon[4] = { QPointF(0.5 * width, 0), QPointF(0.5 * width, 2 * border), QPointF(left + 1.732 * border, height - border), QPointF(left, height), }; painter.setBrush(upLeftColor); painter.drawPolygon(upLeftPolygon, 4); } else { const QPointF polygon[3] = { QPointF(left, 0), QPointF(right, 0), QPointF(0.5 * width, height) }; painter.setPen(Qt::NoPen); painter.setBrush(color); painter.drawConvexPolygon(polygon, 3); qreal border = 0.05 * width; const QPointF downRightPolygon[4] = { QPointF(0.5 * width, height), QPointF(0.5 * width, height - 2 * border), QPointF(right - 1.732 * border, border), QPointF(right, 0) }; painter.setBrush(downRightColor); painter.drawPolygon(downRightPolygon, 4); const QPointF upLeftPolygon[6] = { QPointF(right, 0), QPointF(right - 1.732 * border, border), QPointF(left + 1.732 * border, border), QPointF(0.5 * width, height - 2 * border), QPointF(0.5 * width, height), QPointF(left, 0) }; painter.setBrush(upLeftColor); painter.drawPolygon(upLeftPolygon, 6); } painter.restore(); } void setAlphaSaturation(QColor& c, qreal alpha, qreal saturation) { if (saturation != 1) c.setHsv(c.hue(), static_cast(saturation * c.saturation()), c.value()); if (alpha != 1) c.setAlphaF(alpha); } } //namespace //----------------------------------------------------------------------------- string Util::convertSgfValueFromQString(const QString& value, const string& charset) { // Is there a way in Qt to support arbitrary Ascii-compatible text // encodings? Currently, we only support UTF8 (used by Pentobi) and // treat everything else as ISO-8859-1/Latin1 (the default for SGF) // even if the charset property specifies some other encoding. QString charsetToLower = QString(charset.c_str()).trimmed().toLower(); if (charsetToLower == "utf-8" || charsetToLower == "utf8") return value.toUtf8().constData(); else return value.toLatin1().constData(); } QString Util::convertSgfValueToQString(const string& value, const string& charset) { // See comment in convertSgfValueFromQString() about supported encodings QString charsetToLower = QString(charset.c_str()).trimmed().toLower(); if (charsetToLower == "utf-8" || charsetToLower == "utf8") return QString::fromUtf8(value.c_str()); else return QString::fromLatin1(value.c_str()); } QColor Util::getLabelColor(Variant variant, PointState s) { if (s.is_empty()) return Qt::black; Color c = s.to_color(); QColor paintColor = getPaintColor(variant, c); if (paintColor == yellow || paintColor == green) return Qt::black; else return Qt::white; } QColor Util::getPaintColor(Variant variant, Color c) { if (variant == Variant::duo || variant == Variant::junior) return c == Color(0) ? blue : green; else { if (c == Color(0)) return blue; if (c == Color(1)) return yellow; if (c == Color(2)) return red; LIBBOARDGAME_ASSERT(c == Color(3)); return green; } } QColor Util::getPaintColorEmpty() { return gray; } QString Util::getPlayerString(Variant variant, Color c) { if (variant == Variant::duo || variant == Variant::junior) { if (c == Color(0)) return qApp->translate("Util", "Blue"); if (c == Color(1)) return qApp->translate("Util", "Green"); } else if (variant == Variant::classic || variant == Variant::trigon) { if (c == Color(0)) return qApp->translate("Util", "Blue"); if (c == Color(1)) return qApp->translate("Util", "Yellow"); if (c == Color(2)) return qApp->translate("Util", "Red"); if (c == Color(3)) return qApp->translate("Util", "Green"); } else if (variant == Variant::trigon_3) { if (c == Color(0)) return qApp->translate("Util", "Blue"); if (c == Color(1)) return qApp->translate("Util", "Yellow"); if (c == Color(2)) return qApp->translate("Util", "Red"); } else if (variant == Variant::classic_2 || variant == Variant::trigon_2) { if (c == Color(0) || c == Color(2)) return qApp->translate("Util", "Blue/Red"); if (c == Color(1) || c == Color(3)) return qApp->translate("Util", "Yellow/Green"); } LIBBOARDGAME_ASSERT(false); return ""; } void Util::paintColorSquare(QPainter& painter, Variant variant, Color c, qreal x, qreal y, qreal size, qreal alpha, qreal saturation, bool flat) { auto color = getPaintColor(variant, c); QColor upLeftColor; QColor downRightColor; if (flat) { upLeftColor = color; downRightColor = color; } else { upLeftColor = color.lighter(130); downRightColor = color.darker(160); } setAlphaSaturation(color, alpha, saturation); setAlphaSaturation(upLeftColor, alpha, saturation); setAlphaSaturation(downRightColor, alpha, saturation); paintSquare(painter, x, y, size, color, upLeftColor, downRightColor); } void Util::paintColorTriangle(QPainter& painter, Variant variant, Color c, bool isUpside, qreal x, qreal y, qreal width, qreal height, qreal alpha, qreal saturation, bool flat) { auto color = getPaintColor(variant, c); QColor upLeftColor; QColor downRightColor; if (flat) { upLeftColor = color; downRightColor = color; } else { upLeftColor = color.lighter(130); downRightColor = color.darker(160); } setAlphaSaturation(color, alpha, saturation); setAlphaSaturation(upLeftColor, alpha, saturation); setAlphaSaturation(downRightColor, alpha, saturation); paintTriangle(painter, isUpside, x, y, width, height, color, upLeftColor, downRightColor); } void Util::paintEmptySquare(QPainter& painter, qreal x, qreal y, qreal size) { paintSquare(painter, x, y, size, gray, gray.darker(130), gray.lighter(115)); } void Util::paintEmptyTriangle(QPainter& painter, bool isUpside, qreal x, qreal y, qreal width, qreal height) { paintTriangle(painter, isUpside, x, y, width, height, gray, gray.darker(130), gray.lighter(115)); } void Util::paintEmptyTriangleStartingPoint(QPainter& painter, bool isUpside, qreal x, qreal y, qreal width, qreal height) { paintEmptyTriangle(painter, isUpside, x, y, width, height); if (isUpside) y += 0.333 * height; height = 0.666 * height; paintDot(painter, gray.darker(130), x, y, width, height, 0.17 * width); } void Util::paintEmptySquareStartingPoint(QPainter& painter, Variant variant, Color c, qreal x, qreal y, qreal size) { paintEmptySquare(painter, x, y, size); paintDot(painter, getPaintColor(variant, c), x, y, size, size, 0.13 * size); } //----------------------------------------------------------------------------- pentobi-7.2/src/libpentobi_gui/Util.h000066400000000000000000000061211227240712600176750ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_gui/Util.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_GUI_UTIL_H #define LIBPENTOBI_GUI_UTIL_H #include #include #include "libpentobi_base/Color.h" #include "libpentobi_base/Variant.h" #include "libpentobi_base/PointState.h" using namespace std; using libpentobi_base::Color; using libpentobi_base::Variant; using libpentobi_base::PointState; //----------------------------------------------------------------------------- namespace Util { QColor getPaintColor(Variant variant, Color c); QColor getPaintColorEmpty(); QColor getLabelColor(Variant variant, PointState s); void paintColorSquare(QPainter& painter, Variant variant, Color c, qreal x, qreal y, qreal size, qreal alpha = 1, qreal saturation = 1, bool flat = false); void paintColorTriangle(QPainter& painter, Variant variant, Color c, bool isUpside, qreal x, qreal y, qreal width, qreal height, qreal alpha = 1, qreal saturation = 1, bool flat = false); void paintEmptySquare(QPainter& painter, qreal x, qreal y, qreal size); void paintEmptyTriangle(QPainter& painter, bool isUpside, qreal x, qreal y, qreal width, qreal height); void paintEmptyTriangleStartingPoint(QPainter& painter, bool isUpside, qreal x, qreal y, qreal width, qreal height); void paintEmptySquareStartingPoint(QPainter& painter, Variant variant, Color c, qreal x, qreal y, qreal size); /** Convert a property value of a SGF tree unto a QString. @param value @param charset The value of the CA property of the root node in the tree or an empty string if the tree has no such property. This function currently only recognizes UTF8 and ISO-8859-1 (the latter is the default for SGF if no CA property exists). Other charsets are ignored and the string is converted using the default system charset. */ string convertSgfValueFromQString(const QString& value, const string& charset); /** Convert a property value of a SGF tree unto a QString. @param value @param charset The value of the CA property of the root node in the tree or an empty string if the tree has no such property. This function currently only recognizes UTF8 and ISO-8859-1 (the latter is the default for SGF if no CA property exists). Other charsets are ignored and the string is converted using the default system charset. */ QString convertSgfValueToQString(const string& value, const string& charset); /** Get a translated string identifying a player, like "Blue" or "Blue/Red". @param variant The game variant @param c The player color or one of the player colors in game variants with multiple colors per player. */ QString getPlayerString(Variant variant, Color c); } //----------------------------------------------------------------------------- #endif // LIBPENTOBI_GUI_UTIL_H pentobi-7.2/src/libpentobi_gui/icons/000077500000000000000000000000001227240712600177225ustar00rootroot00000000000000pentobi-7.2/src/libpentobi_gui/icons/go-home.png000066400000000000000000000016061227240712600217660ustar00rootroot00000000000000‰PNG  IHDRФДl;sBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<IDAT8­е_hSWРёя9ЙщMтЌд&›8kmеŽсП@ж]”А—ЬёЩ•2p(2„ ™АБХќЋ-…В?ЂКaд'aАA|­џкšы`џTQfk[*Кдофќ|I“5ўžЮпљмпљsяU"B#Б1‰(еd]ЛzyЄ‘|нHRиqЂЏ=ъЗдх‘їїМ8ь8QŸђdжЏгызЉ€зњЉќЅpе™ўЎЕк_ШуЫЛє­щTЏч•ИzQУŽѕЁ3НЋкЕЁbО ЖЂин{ђoОа3žj.ЁЩхЫД+ТрЬ,ёd €d<Э‹с’“S/ФŸƒУŽЕEeСV=g ?фцˆЇŽ№ю;]ќўЧŸ$уi>ѓz№щG%W0ЯсUp 5ПЁsЦpЂ $г КжЎ.чˆ§}ƒФ‘Ÿц]l Яu%W*М ‡'j2_ћl§X„S‹єб$Ћ;;*ъJuмИy‹x,СО'O№‹0 =’3 И‘2њЅFЯ œёшэKбйбNeЇЊл&мМu›D,СЧгг,2†A@rBЯxvtHmˆlорW…‘УюSџ 0ДЄ…оОЋкЋ*ЌЗям!ўM‚]їягl пЖ,ŸУzЯ1Ъ@с’nВеї§iкWЖбГw?Sг3хњZ—ЖpцєI>9p‰Щ‡М ђуЩcЄŽ&9єљaЖ>šA\ЃЌый+у›КЛ7_єкƒѓJEWЖ­@D˜šžaћЖбZЃЕцч_Я#"LL>ф—sg;w#"Д­x›yЅ^м<ќT<‡ЎgЏŒ[зЦЦ~>кдэHБХ­кЖЏ ЗnЪН.ѕЛr<’лZкЁEM”ј|6JUТB(dЧЮтл ЋрЪЈ/$йЖ]SБpъјwU9"†:ю‡Ћouц СЕ=Ў…ыЃ/K‡W[qщ№ ђ.з‹ЊoEї–ў™wнЗ,Ы"ŸЯ/TP3Џ&ЏїСиЅ ЫъТЏ3њч§ŸxУm™g0Б‘IENDЎB`‚pentobi-7.2/src/libpentobi_gui/icons/go-home.svg000066400000000000000000000225601227240712600220030ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/libpentobi_gui/icons/go-next.png000066400000000000000000000012331227240712600220100ustar00rootroot00000000000000‰PNG  IHDRФДl;sBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<IDAT8Е•НoкPХŸЈ€RІЖRГ!БЄ”…Ё­К†J- !ŽВ6RњUiuШ@2@ mйЉвН" UЌgПЏ‰)З W~ђ=WіOззЧЯšR ‹Въ,`kgг.n_Ь,Ѕк44rjY/ч €|.бOўŸ ќtuѕСЛЗЙˆ~bšцЋАk4п;%ыќЩ$h4ЅЛЅнeyъК{ЭЫ•ђ­фШлЖн ›VQэя”Т]EљЧпк§YJЁ:Ž–H$ФЭMU*eG(U8:8њ оћА'Ў~^щRJ(Ѕ+LsЮa’ЩЄшѕzьќќьVrёў№№Ы7`dЦRIPJсК.\зЅt §мзŒ18ŽƒvЛ­ЧуёЅlіMŒњёжжЦыqАT`Œ ч<‡iЧqаjЕє•‡+Kыый1єcг4ŸУ`uџˆгŒbX !рRW“R.+hP0FGС НyДцч„ЄR)щ8ЊЕZ_2UАmћG,„€Їщ–‚Еgk’RŠj­к— лЖПvЌMЯd2P№7=п5j`?Ю8i6›Z:–.ЅЈ]\ Лm—ЌЎќбј'ŒH$тхђ9уwП1дыѕ1h уƒOŸO‚w~'šзѓdЃб…ŽbкшvЛВqyљO(0у&4 :˜§tzyѓŽ…§ѓўLрМ›YлфIENDЎB`‚pentobi-7.2/src/libpentobi_gui/icons/go-next.svg000066400000000000000000000100161227240712600220220ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/libpentobi_gui/icons/go-previous.png000066400000000000000000000012401227240712600227040ustar00rootroot00000000000000‰PNG  IHDRФДl;sBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<IDAT8Е•?oк@Цпћcˆь Јj&*ж2А4iВt)иY€УщwKЗ(`uЁ„ЊtAHљEjR%‘š-щц"ЁњЮwRˆ)&@^щt~uЏzєм#)Ѕ`…WB]\9ЈМЖпWeСєЁCл.oRLлRњ‰eС3лvy0iяя[њВа™`Ци`в6MЫиxББі№”ŒБ-LсЄdšF:ЂОяK]з=fWцЦ‡rs|дx€‚qЋTп„ZfЩв“ЉЄv}§“d2/F)ЅрnZСнkї;(€У‡рд?Ђ ХЕZy›Rв,M}}=Ё]]]Ю9œ}GRJPJWXŸЫх| VдjхmLIГX,‰DBЛИМ žчЭ…{пї'=fŒН"iп•Œx<Ўџ8'œѓPРCp)хєх!„ˆT2т§ёРѓ<B,Ќ6ј,ь8Ю7с)ЋгљтКП]Шd2RJ œѓёBЬь… „˜RŒЧ9žВОv:ю`0€l6+•R У9ч е$x"nŒБЊЁжолНX4К§~чѓyE)•Ѓ9ѕ/ZЃ}9B0i4œqмаџо0ЦvА†ZЛЛ…X4у™­O-1#0Ї!ПŽщP№=Z…B!–J&qНо+YДBПŽуœJVЗлuonoeиЬЃРAxЏзsŸ„cL>/ ѕј)jeџМПЁrЉыю§ЁIENDЎB`‚pentobi-7.2/src/libpentobi_gui/icons/go-previous.svg000066400000000000000000000110041227240712600227160ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/libpentobi_gui/libpentobi_gui_de.ts000066400000000000000000000203401227240712600226210ustar00rootroot00000000000000 ComputerColorDialog Computer Colors Computer-Farben Colors played by the computer: Vom Computer gespielte Farben: &Blue &Blau &Green &GrУМn &Yellow G&elb &Red &Rot &Blue/Red &Blau/Rot &Yellow/Green &Gelb/GrУМn GameInfoDialog Game Info Spielinformation Player &Blue: Player Blue: Spieler &Blau: Player &Green: Player Green: Spieler &GrУМn: Player &Yellow: Player Yellow: Spieler G&elb: Player &Red: Player Red: Spieler &Rot: Player &Blue/Red: Player Blue/Red: Spieler &Blau/Rot: Player &Yellow/Green: Player Yellow/Green: Spieler &Gelb/GrУМn: &Date: Date: &Datum: &Time limits: Bedenk&zeit: &Event: &Veranstaltung: R&ound: R&unde: HelpWindow Pentobi User Manual Pentobi-Benutzerhandbuch Back ZurУМck Show previous page in history Die vorherige Seite in der Chronik anzeigen Forward VorwУЄrts Show next page in history Die nУЄchste Seite in der Chronik anzeigen Contents Inhalt Show table of contents Das Inhaltsverzeichnis anzeigen InitialRatingDialog Initial Rating Anfangswertung You have not yet played rated games in this game variant. Estimate your playing strength to initialize your rating. Sie haben noch keine gewerteten Spiele in dieser Spielvariante gespielt. SchУЄtzen Sie Ihre SpielstУЄrke, um Ihre Wertung zu initialisieren. Beginner AnfУЄnger Expert Experte Your initial rating: %1 Ihre Anfangswertung: %1 Util Blue Blau Green GrУМn Yellow Gelb Red Rot Blue/Red Blau/Rot Yellow/Green Gelb/GrУМn pentobi-7.2/src/libpentobi_gui/libpentobi_gui_en_CA.ts000066400000000000000000000175231227240712600232070ustar00rootroot00000000000000 ComputerColorDialog Computer Colors Computer Colours Colors played by the computer: Colours played by the computer: &Blue &Blue &Green &Green &Yellow &Yellow &Red &Red &Blue/Red &Blue/Red &Yellow/Green &Yellow/Green GameInfoDialog Game Info Game Info Player &Blue: Player &Blue: Player &Green: Player &Green: Player &Red: Player &Red: Player &Blue/Red: Player &Blue/Red: Player &Yellow/Green: Player &Yellow/Green: &Date: &Date: &Time limits: &Time limits: &Event: &Event: R&ound: R&ound: Player &Yellow: Player &Yellow: HelpWindow Pentobi User Manual Pentobi User Manual Back Back Show previous page in history Show previous page in history Forward Forward Show next page in history Show next page in history Contents Contents Show table of contents Show table of contents InitialRatingDialog Initial Rating Initial Rating You have not yet played rated games in this game variant. Estimate your playing strength to initialize your rating. You have not yet played rated games in this game variant. Estimate your playing strength to initialize your rating. Beginner Beginner Expert Expert Your initial rating: %1 Your initial rating: %1 Util Blue Blue Green Green Yellow Yellow Red Red Blue/Red Blue/Red Yellow/Green Yellow/Green pentobi-7.2/src/libpentobi_gui/libpentobi_gui_en_GB.ts000066400000000000000000000175231227240712600232140ustar00rootroot00000000000000 ComputerColorDialog Computer Colors Computer Colours Colors played by the computer: Colours played by the computer: &Blue &Blue &Green &Green &Yellow &Yellow &Red &Red &Blue/Red &Blue/Red &Yellow/Green &Yellow/Green GameInfoDialog Game Info Game Info Player &Blue: Player &Blue: Player &Green: Player &Green: Player &Red: Player &Red: Player &Blue/Red: Player &Blue/Red: Player &Yellow/Green: Player &Yellow/Green: &Date: &Date: &Time limits: &Time limits: &Event: &Event: R&ound: R&ound: Player &Yellow: Player &Yellow: HelpWindow Pentobi User Manual Pentobi User Manual Back Back Show previous page in history Show previous page in history Forward Forward Show next page in history Show next page in history Contents Contents Show table of contents Show table of contents InitialRatingDialog Initial Rating Initial Rating You have not yet played rated games in this game variant. Estimate your playing strength to initialize your rating. You have not yet played rated games in this game variant. Estimate your playing strength to initialize your rating. Beginner Beginner Expert Expert Your initial rating: %1 Your initial rating: %1 Util Blue Blue Green Green Yellow Yellow Red Red Blue/Red Blue/Red Yellow/Green Yellow/Green pentobi-7.2/src/libpentobi_gui/libpentobi_gui_resources.qrc000066400000000000000000000003151227240712600244020ustar00rootroot00000000000000 icons/go-home.png icons/go-next.png icons/go-previous.png pentobi-7.2/src/libpentobi_kde_thumbnailer/000077500000000000000000000000001227240712600211605ustar00rootroot00000000000000pentobi-7.2/src/libpentobi_kde_thumbnailer/CMakeLists.txt000066400000000000000000000032101227240712600237140ustar00rootroot00000000000000# libpentobi_kde_thumbnailer contains the files needed by # the pentobi_kde_thumbnailer plugin compiled with shared library options # (usually -fPIC) because this is required for building shared libraries on # some targets (e.g. x86_64). # # The alternative would be to add -fPIC to the global compiler flags even for # executables but this slows down Pentobi's search by 10% on some targets. # # Adding the source files directly to pentobi_kde_thumbnailer/CMakeList.txt is # not possible because the KDE CMake macros add -fno-exceptions to the # compiler flags which causes errors in the Pentobi sources that use # exceptions (which should be fine as long as no exceptions are thrown # from the thumbnailer plugin functions). include(${QT_USE_FILE}) add_definitions(${CMAKE_SHARED_LIBRARY_CXX_FLAGS}) include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) set(pentobi_kde_thumbnailer_STAT_SRCS ../libboardgame_util/Assert.cpp ../libboardgame_util/Exception.cpp ../libboardgame_util/Log.cpp ../libboardgame_util/StringUtil.cpp ../libboardgame_base/SpreadsheetStringRep.cpp ../libboardgame_sgf/InvalidPropertyValue.cpp ../libboardgame_sgf/InvalidTree.cpp ../libboardgame_sgf/MissingProperty.cpp ../libboardgame_sgf/Node.cpp ../libboardgame_sgf/Reader.cpp ../libboardgame_sgf/Tree.cpp ../libboardgame_sgf/TreeReader.cpp ../libpentobi_base/NodeUtil.cpp ../libpentobi_base/StartingPoints.cpp ../libpentobi_base/Variant.cpp ../libpentobi_gui/BoardPainter.cpp ../libpentobi_gui/Util.cpp ../libpentobi_thumbnail/CreateThumbnail.cpp ) add_library(pentobi_kde_thumbnailer STATIC ${pentobi_kde_thumbnailer_STAT_SRCS} ) pentobi-7.2/src/libpentobi_mcts/000077500000000000000000000000001227240712600167715ustar00rootroot00000000000000pentobi-7.2/src/libpentobi_mcts/AnalyzeGame.cpp000066400000000000000000000075331227240712600217020ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_mcts/AnalyzeGame.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "AnalyzeGame.h" #include "Search.h" #include "libboardgame_util/Log.h" #include "libboardgame_util/WallTime.h" namespace libpentobi_mcts { using libboardgame_sgf::Node; using libboardgame_util::log; using libboardgame_util::clear_abort; using libboardgame_util::get_abort; using libboardgame_util::Exception; using libboardgame_util::WallTime; using libpentobi_base::BoardUpdater; using libpentobi_base::Tree; //----------------------------------------------------------------------------- void AnalyzeGame::run(const Game& game, Search& search, size_t nu_simulations, function progress_callback) { m_variant = game.get_variant(); m_moves.clear(); m_has_value.clear(); m_values.clear(); auto& tree = game.get_tree(); unique_ptr bd(new Board(m_variant)); BoardUpdater updater(tree, *bd); auto& root = game.get_root(); auto node = &root; unsigned total_moves = 0; while (node != nullptr) { if (tree.has_move(*node)) ++total_moves; node = node->get_first_child_or_null(); } WallTime time_source; clear_abort(); node = &root; unsigned move_number = 0; while (node != nullptr) { auto mv = tree.get_move(*node); if (mv.is_regular()) { if (! node->has_parent()) { m_moves.push_back(mv); m_has_value.push_back(false); m_values.push_back(0); } else { progress_callback(move_number, total_moves); try { updater.update(node->get_parent()); log() << "Analyzing move " << bd->get_nu_moves() << "\n"; const Float max_count = Float(nu_simulations); double max_time = 0; // Set min_simulations to a reasonable value because // nu_simulations can be reached without having that many // value updates if a subtree from a previous search is // reused (which re-initializes the value and value count // of the new root from the best child) const Float min_simulations = min(Float(100), Float(nu_simulations)); Move computer_mv; search.search(computer_mv, *bd, mv.color, max_count, min_simulations, max_time, time_source); if (get_abort()) break; auto& search_root = search.get_tree().get_root(); if (search_root.get_visit_count() == 0) { m_moves.push_back(mv); m_has_value.push_back(false); m_values.push_back(0); } else { m_moves.push_back(mv); m_has_value.push_back(true); m_values.push_back(search_root.get_value()); } } catch (const Exception&) { // BoardUpdater::update() can throw on invalid SGF tree // read from external file. We simply abort the analysis. break; } } ++move_number; } node = node->get_first_child_or_null(); } } //----------------------------------------------------------------------------- } // namespace libpentobi_mcts pentobi-7.2/src/libpentobi_mcts/AnalyzeGame.h000066400000000000000000000044121227240712600213400ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_mcts/AnalyzeGame.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_MCTS_ANALYZE_GAME_H #define LIBPENTOBI_MCTS_ANALYZE_GAME_H #include #include #include "libpentobi_base/Game.h" namespace libpentobi_mcts { class Search; using namespace std; using libpentobi_base::ColorMove; using libpentobi_base::Game; using libpentobi_base::Variant; //----------------------------------------------------------------------------- /** Evaluate each position in the main variation of a game. */ class AnalyzeGame { public: /** Run the analysis. The analysis can be aborted from a different thread with libboardgame_util::set_abort(). @param game @param search @param nu_simulations @param progress_callback Function that will be called at the beginning of the analysis of a position. Arguments: number moves analyzed so far, total number of moves. */ void run(const Game& game, Search& search, size_t nu_simulations, function progress_callback); Variant get_variant() const; unsigned get_nu_moves() const; bool has_value(unsigned i) const; ColorMove get_move(unsigned i) const; double get_value(unsigned i) const; private: Variant m_variant; vector m_moves; vector m_has_value; vector m_values; }; inline ColorMove AnalyzeGame::get_move(unsigned i) const { LIBBOARDGAME_ASSERT(i < m_moves.size()); return m_moves[i]; } inline unsigned AnalyzeGame::get_nu_moves() const { return static_cast(m_moves.size()); } inline double AnalyzeGame::get_value(unsigned i) const { LIBBOARDGAME_ASSERT(i < m_values.size()); LIBBOARDGAME_ASSERT(has_value(i)); return m_values[i]; } inline Variant AnalyzeGame::get_variant() const { return m_variant; } inline bool AnalyzeGame::has_value(unsigned i) const { LIBBOARDGAME_ASSERT(i < m_has_value.size()); return m_has_value[i]; } //----------------------------------------------------------------------------- } // namespace libpentobi_mcts #endif // LIBPENTOBI_MCTS_ANALYZE_GAME_H pentobi-7.2/src/libpentobi_mcts/CMakeLists.txt000066400000000000000000000003501227240712600215270ustar00rootroot00000000000000include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) set(pentobi_mcts_STAT_SRCS AnalyzeGame.cpp Player.cpp Search.cpp State.cpp Util.cpp ) add_library(pentobi_mcts STATIC ${pentobi_mcts_STAT_SRCS}) pentobi-7.2/src/libpentobi_mcts/Float.h000066400000000000000000000013271227240712600202120ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_mcts/Float.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_MCTS_FLOAT_H #define LIBPENTOBI_MCTS_FLOAT_H #include namespace libpentobi_mcts { using namespace std; //----------------------------------------------------------------------------- #ifdef LIBPENTOBI_MCTS_FLOAT_TYPE typedef LIBPENTOBI_MCTS_FLOAT_TYPE Float; #else typedef float Float; #endif static_assert(! numeric_limits::is_exact, ""); //----------------------------------------------------------------------------- } // namespace libpentobi_mcts #endif // LIBPENTOBI_MCTS_FLOAT_H pentobi-7.2/src/libpentobi_mcts/LocalValue.h000066400000000000000000000154341227240712600212000ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_mcts/LocalValue.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_MCTS_LOCAL_VALUE_H #define LIBPENTOBI_MCTS_LOCAL_VALUE_H #include #include "libboardgame_util/Log.h" #include "libpentobi_base/AdjIterator.h" #include "libpentobi_base/Board.h" #include "libpentobi_base/PointList.h" namespace libpentobi_mcts { using namespace std; using libboardgame_base::ArrayList; using libboardgame_util::log; using libpentobi_base::AdjIterator; using libpentobi_base::Board; using libpentobi_base::Color; using libpentobi_base::ColorMove; using libpentobi_base::Geometry; using libpentobi_base::Grid; using libpentobi_base::Move; using libpentobi_base::PieceInfo; using libpentobi_base::Point; using libpentobi_base::PointList; //----------------------------------------------------------------------------- /** Classify playout moves to prefer local response moves. In the playout policy, local response moves are preferred. That is, a random move is chosen from the set of moves with the highest local response value. The value depends on the proximity to the last opponent moves and the number of attach points of those moves occupied. */ class LocalValue { public: /** Compute the local response value for a move. */ class Compute { public: Compute(Point p, const LocalValue& local_value); /** Add a point of the move. */ void add_move_point(Point p, const LocalValue& local_value); /** Return the value. */ unsigned get() const; /** Return upper bound on the value. Faster than get() and often good enough to know that we already have a move with a higher value. */ unsigned get_upper_bound() const; private: unsigned m_value; }; friend class Compute; /** Initialize geometry. Must be called before using this class and whenever the board geometry changes. */ void init_geometry(const Geometry& geometry); /** Find the attach points of the last opponent moves in a given position. @param bd The board. Must have the same geometry as in the last call of init_geometry(). */ void init(const Board& bd); /** Clear the stored last opponent attach moves. */ void clear(); private: Grid m_point_value; /** Points with point value greater zero. */ PointList m_points; }; inline LocalValue::Compute::Compute(Point p, const LocalValue& local_value) { m_value = local_value.m_point_value[p]; } inline void LocalValue::Compute::add_move_point(Point p, const LocalValue& local_value) { m_value += local_value.m_point_value[p]; } inline unsigned LocalValue::Compute::get() const { // The bit ranges used in the value work only as long as there are not more // than 0x10 points covered by a piece. static_assert(PieceInfo::max_size < 0x10, ""); if (m_value == 0) return 0; if (m_value < 0x010u) // Only 2nd-order adjacent to opponent attach point. Don't care how // many return 0x001u; // Ignore 2nd-order adj. to opp. attach point if we have opp. attach // points or adj. to opp. attach point. Only care if we have any adj. to // opp. attach points, not how many. if ((m_value & 0x0f0u) != 0) return (m_value & 0xf00u) + 0x010u; else return m_value & 0xf00u; } inline unsigned LocalValue::Compute::get_upper_bound() const { return m_value; } inline void LocalValue::clear() { for (auto i = m_points.begin(); i != m_points.end(); ++i) m_point_value[*i] = 0; m_points.clear(); } inline void LocalValue::init(const Board& bd) { if (! m_points.empty()) clear(); Color to_play = bd.get_to_play(); Color second_color = bd.get_second_color(to_play); auto& geometry = bd.get_geometry(); unsigned move_number = bd.get_nu_moves(); // Consider last 3 moves for local points (i.e. last 2 opponent moves in // two-player variants) for (unsigned i = 0; i < 3; ++i) { if (move_number == 0) return; --move_number; ColorMove move = bd.get_move(move_number); Color c = move.color; if (c == to_play || c == second_color) continue; auto mv = move.move; if (mv.is_pass()) continue; auto& is_forbidden = bd.is_forbidden(c); auto& info_ext = bd.get_move_info_ext(mv); auto j = info_ext.begin_attach(); auto end = info_ext.end_attach(); do { if (! is_forbidden[*j]) { if (m_point_value[*j] == 0) m_points.push_back(*j); // Opponent attach point m_point_value[*j] = 0x100u; unsigned nu_adj = 0; for (AdjIterator k(geometry, *j); k; ++k) if (! is_forbidden[*k]) { ++nu_adj; if (m_point_value[*k] < 0x010u) { if (m_point_value[*k] == 0) m_points.push_back(*k); // Adjacent to opp. attach point m_point_value[*k] = 0x010u; for (AdjIterator l(geometry, *k); l; ++l) if (! is_forbidden[*l] && m_point_value[*l] == 0) { m_points.push_back(*l); // 2nd-order adj. to opp. attach point m_point_value[*l] = 0x001u; } } } // If occupying the attach point is forbidden for us but there // is only one adjacent point missing to make it a 1-point hole // for the opponent, then occupying this adjacent point is // (almost) as good as occupying the attach point. (This is // done only for 1-point holes that are forbidden for to_play.) if (nu_adj == 1 && bd.is_forbidden(*j, to_play)) for (AdjIterator k(geometry, *j); k; ++k) if (! is_forbidden[*k]) m_point_value[*k] = 0x100u; } } while (++j != end); } } inline void LocalValue::init_geometry(const Geometry& geometry) { m_points.clear(); m_point_value.init(geometry, 0); } //----------------------------------------------------------------------------- } // namespace libpentobi_mcts #endif // LIBPENTOBI_MCTS_LOCAL_VALUE_H pentobi-7.2/src/libpentobi_mcts/Player.cpp000066400000000000000000000256771227240712600207520ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_mcts/Player.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Player.h" #include #include #include "libboardgame_util/CpuTime.h" #include "libboardgame_util/FmtSaver.h" #include "libboardgame_util/WallTime.h" namespace libpentobi_mcts { using namespace std; using libboardgame_util::log; using libboardgame_util::CpuTime; using libboardgame_util::FmtSaver; using libboardgame_util::WallTime; using libpentobi_base::Variant; //----------------------------------------------------------------------------- Player::Player(Variant initial_variant, const string& books_dir, unsigned nu_threads, size_t memory) : m_is_book_loaded(false), m_use_book(true), m_resign(false), m_books_dir(books_dir), m_level(4), m_fixed_simulations(0), m_resign_threshold(0.09f), m_resign_min_simulations(500), m_search(initial_variant, nu_threads, memory), m_book(initial_variant), m_time_source(new WallTime()) { for (unsigned i = 0; i < Board::max_player_moves; ++i) { // Hand-tuned such that time per move is more evenly spread among all // moves than with a fixed number of simulations (because the // simulations per second increase rapidly with the move number) but // the average time per game is roughly the same. m_weight_max_count_duo[i] = 0.7f * exp(0.1f * static_cast(i)); m_weight_max_count_classic[i] = m_weight_max_count_duo[i]; m_weight_max_count_trigon[i] = m_weight_max_count_duo[i]; // Less weight for the first move(s) because number of legal moves // is lower and the search applies some pruning rules to reduce the // branching factor in early moves if (i == 0) { m_weight_max_count_classic[i] *= 0.2f; m_weight_max_count_trigon[i] *= 0.2f; m_weight_max_count_duo[i] *= 0.6f; } else if (i == 1) { m_weight_max_count_classic[i] *= 0.2f; m_weight_max_count_trigon[i] *= 0.5f; } else if (i == 2) { m_weight_max_count_classic[i] *= 0.3f; m_weight_max_count_trigon[i] *= 0.6f; } else if (i == 3) { m_weight_max_count_trigon[i] *= 0.8f; } } } Player::~Player() throw() { } Move Player::genmove(const Board& bd, Color c) { m_resign = false; if (! bd.has_moves(c)) return Move::pass(); Move mv; auto variant = bd.get_variant(); // Don't use more thane 2 moves per color from opening book in lower levels // because they are supposed to be weak if (m_use_book && (m_level >= 4 || bd.get_nu_moves() < 2u * bd.get_nu_colors())) { if (! m_is_book_loaded || m_book.get_tree().get_variant() != variant) { string filename; if (variant == Variant::duo) filename = "book_duo.blksgf"; else if (variant == Variant::junior) filename = "book_junior.blksgf"; else if (variant == Variant::classic_2) filename = "book_classic_2.blksgf"; else if (variant == Variant::classic) filename = "book_classic.blksgf"; else if (variant == Variant::trigon_2) filename = "book_trigon_2.blksgf"; else if (variant == Variant::trigon_3) filename = "book_trigon_3.blksgf"; else { LIBBOARDGAME_ASSERT(variant == Variant::trigon); filename = "book_trigon.blksgf"; } load_book(m_books_dir + "/" + filename); } if (m_is_book_loaded) { mv = m_book.genmove(bd, c); if (! mv.is_null()) return mv; } } Float max_count = 0; double max_time = 0; if (m_fixed_simulations > 0) max_count = m_fixed_simulations; else if (m_fixed_time > 0) max_time = m_fixed_time; else { // The minimum number of simulations and increase factor per level are // chosen such that the total time per game and player at level 7 is // less than 20 min (10 min for Duo) even on somewhat outdated PC // hardware. (This also takes the additional weighting of the number of // simulations depending on the move number into account.) // The increase factor should be no less than 3-4 to produce a // noticable effect on playing strength between levels. // The minimum number of simulations is very small to avoid that level // 1 is too strong for absolute beginners. Note that using the search // with simulations much smaller than the branching factor works only // because node values are initialized with prior knowledge and the // final move selection based on the visit count uses the value as a // a tie-breaker. Float minimum; Float factor_per_level; if (variant == Variant::classic || variant == Variant::classic_2) { minimum = 8; factor_per_level = 5.13f; } else if (variant == Variant::trigon || variant == Variant::trigon_2 || variant == Variant::trigon_3) { minimum = 20; factor_per_level = 3.74f; } else if (variant == Variant::junior) { minimum = 4; factor_per_level = 6.48f; } else { LIBBOARDGAME_ASSERT(variant == Variant::duo); minimum = 5; factor_per_level = 6.30f; } if (m_level <= 1) max_count = minimum; else max_count = Float(ceil(minimum * pow(factor_per_level, m_level - 1))); // Don't weight max_count in low levels, otherwise it is still too // strong for beginners (later in the game, the weight becomes much // greater than 1 because the simulations become very fast) bool weight_max_count = (m_level >= 4); if (weight_max_count) { unsigned player_move = bd.get_nu_onboard_pieces(c); float weight = 1; if (variant == Variant::duo || variant == Variant::junior) weight = m_weight_max_count_duo[player_move]; else if (variant == Variant::classic || variant == Variant::classic_2) weight = m_weight_max_count_classic[player_move]; else if (variant == Variant::trigon || variant == Variant::trigon_2 || variant == Variant::trigon_3) weight = m_weight_max_count_trigon[player_move]; max_count = ceil(max_count * weight); } } if (max_count != 0) { FmtSaver saver(log()); log() << "MaxCnt " << fixed << setprecision(0) << max_count << '\n'; } else log() << "MaxTime " << max_time << '\n'; if (! m_search.search(mv, bd, c, max_count, 0, max_time, *m_time_source)) return Move::null(); // Resign only in two-player game variants if (get_nu_players(variant) == 2) { auto& root = m_search.get_tree().get_root(); if (root.get_visit_count() > m_resign_min_simulations && root.get_value() < m_resign_threshold) m_resign = true; } return mv; } Rating Player::get_rating(Variant variant, int level) { // The initial rating estimates for version 2.0 are loosely based on // earlier experiments that measured the rating differences depending on // different number of simulations in Pentobi 1.0. A general factor of 0.5 // was applied to measured Elo differences to take into account that // self-play experiments usually overestimate Elo differences when // playing vs. humans. The ratings were anchored such that level 1 was // at beginner level (~1000 Elo) and level 6 at lower expert level (~2000 // Elo), which corresponds to estimates of the performance of Pentobi 1.0 // vs. humans. Not all game variants were tested (for example, the ratings // for Classic 2 was also used for Classic, and those of Duo for // Junior). Modifications for the estimated playing strength of Pentobi // 1.0 in different game variants were applied (e.g. stronger in Duo, // weaker in Trigon). // Ratings of future versions of Pentobi should be roughly calibrated by // testing vs. Pentobi 2.0 and the same factor of 0.5 should be used to // rescale Elo differences measured in self-play experiments such that // they are somewhat comparable. This avoids jumps in the ratings of humans // after an upgrade of Pentobi if the computer player is used to assign a // rating to the human user. level = max(level, 1); switch (variant) { case Variant::classic: case Variant::classic_2: { static float elo[] = { 1000, 1255, 1510, 1740, 1880, 1950, 1990, 2030 }; if (level <= 8) return Rating(elo[level - 1]); else // Ratings for levels greater 8 are not really tested. return Rating(elo[7] + static_cast(10 * (level - 8))); } case Variant::trigon: case Variant::trigon_2: case Variant::trigon_3: { static float elo[] = { 920, 1100, 1300, 1485, 1580, 1650, 1700, 1750 }; if (level <= 8) return Rating(elo[level - 1]); else // Ratings for levels greater 8 are not really tested. return Rating(elo[7] + static_cast(10 * (level - 8))); } case Variant::duo: case Variant::junior: { static float elo[] = { 1100, 1325, 1550, 1750, 1880, 1950, 2020, 2090 }; if (level <= 8) return Rating(elo[level - 1]); else // Ratings for levels greater 8 are not really tested. return Rating(elo[7] + static_cast(10 * (level - 8))); } } LIBBOARDGAME_ASSERT(false); return Rating(0); } void Player::load_book(istream& in) { m_book.load(in); m_is_book_loaded = true; } bool Player::load_book(const string& filepath) { log() << "Trying to load " << filepath << "... "; ifstream in(filepath); if (! in) { log() << "not found\n"; return false; } m_book.load(in); m_is_book_loaded = true; log() << "ok\n"; return true; } bool Player::resign() const { return m_resign; } void Player::use_cpu_time(bool enable) { if (enable) m_time_source.reset(new CpuTime()); else m_time_source.reset(new WallTime()); } //----------------------------------------------------------------------------- } // namespace libpentobi_mcts pentobi-7.2/src/libpentobi_mcts/Player.h000066400000000000000000000104311227240712600203750ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_mcts/Player.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_MCTS_PLAYER_H #define LIBPENTOBI_MCTS_PLAYER_H #include "Search.h" #include "libboardgame_base/Rating.h" #include "libpentobi_base/Book.h" #include "libpentobi_base/Player.h" namespace libpentobi_mcts { using libboardgame_base::Rating; using libpentobi_base::Book; using libpentobi_base::Variant; //----------------------------------------------------------------------------- class Player : public libpentobi_base::Player { public: /** Constructor. @param initial_variant Game variant to initialize the internal board with (may avoid unnecessary BoardConst creation for game variant that is never used) @param books_dir Directory containing opening books. @param nu_threads The number of threads to use in the search (0 means to select a reasonable default value) @param memory The memory to be used for (all) the search trees. */ Player(Variant initial_variant, const string& books_dir, unsigned nu_threads = 0, size_t memory = 0); ~Player() throw(); Move genmove(const Board& bd, Color c) override; bool resign() const override; Float get_fixed_simulations() const; double get_fixed_time() const; /** Use a fixed number of simulations in the search. If set to a value greater than zero, this value will enforce a fixed number of simulations per search independent of the playing level. */ void set_fixed_simulations(Float n); /** Use a fixed time limit per move. If set to a value greater than zero, this value will set a fixed (maximum) time per search independent of the playing level. */ void set_fixed_time(double seconds); bool get_use_book() const; void set_use_book(bool enable); int get_level() const; void set_level(int level); /** Use CPU time instead of Wall time to measure time. */ void use_cpu_time(bool enable); Search& get_search(); void load_book(istream& in); /** Get an estimated Elo-rating of a level. This rating is an estimated rating when playing vs. humans. Although it is based on computer vs. computer experiments, the ratings were modified and rescaled to take into account that self-play experiments usually overestimate the rating differences when playing against humans. */ static Rating get_rating(Variant variant, int level); /** Get an estimated Elo-rating of the current level. */ Rating get_rating(Variant variant) const; private: bool m_is_book_loaded; bool m_use_book; bool m_resign; string m_books_dir; int m_level; array m_weight_max_count_classic; array m_weight_max_count_trigon; array m_weight_max_count_duo; Float m_fixed_simulations; Float m_resign_threshold; Float m_resign_min_simulations; double m_fixed_time; Search m_search; Book m_book; unique_ptr m_time_source; void init_settings(); bool load_book(const string& filepath); }; inline Float Player::get_fixed_simulations() const { return m_fixed_simulations; } inline double Player::get_fixed_time() const { return m_fixed_time; } inline int Player::get_level() const { return m_level; } inline Rating Player::get_rating(Variant variant) const { return get_rating(variant, m_level); } inline Search& Player::get_search() { return m_search; } inline bool Player::get_use_book() const { return m_use_book; } inline void Player::set_fixed_simulations(Float n) { m_fixed_simulations = n; m_fixed_time = 0; } inline void Player::set_fixed_time(double seconds) { m_fixed_time = seconds; m_fixed_simulations = 0; } inline void Player::set_level(int level) { m_level = level; m_fixed_simulations = 0; m_fixed_time = 0; } inline void Player::set_use_book(bool enable) { m_use_book = enable; } //----------------------------------------------------------------------------- } // namespace libpentobi_mcts #endif // LIBPENTOBI_MCTS_PLAYER_H pentobi-7.2/src/libpentobi_mcts/Search.cpp000066400000000000000000000247351227240712600207150ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_mcts/Search.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Search.h" #include "Util.h" #include "libboardgame_util/FmtSaver.h" namespace libpentobi_mcts { using libboardgame_base::PointTransfRot180; using libboardgame_util::FmtSaver; using libpentobi_base::BoardIterator; using libpentobi_base::BoardType; using libpentobi_base::ColorIterator; using libpentobi_base::Piece; //----------------------------------------------------------------------------- namespace { void filter_min_size(const BoardConst& board_const, unsigned min_size, PieceMap& is_piece_considered) { for (Piece::IntType i = 0; i < board_const.get_nu_pieces(); ++i) { Piece piece(i); auto& piece_info = board_const.get_piece_info(piece); if (piece_info.get_size() < min_size) is_piece_considered[piece] = false; } } void set_piece_considered(const BoardConst& board_const, const char* name, PieceMap& is_piece_considered, bool is_considered = true) { Piece piece; bool found = board_const.get_piece_by_name(name, piece); LIBBOARDGAME_UNUSED_IF_NOT_DEBUG(found); LIBBOARDGAME_ASSERT(found); is_piece_considered[piece] = is_considered; } void set_pieces_considered(const Board& bd, unsigned nu_moves, PieceMap& is_piece_considered) { auto& board_const = bd.get_board_const(); auto board_type = board_const.get_board_type(); unsigned nu_colors = bd.get_nu_colors(); is_piece_considered.fill(true); if (board_type == BoardType::duo) { if (nu_moves < 2 * nu_colors) filter_min_size(board_const, 5, is_piece_considered); else if (nu_moves < 3 * nu_colors) filter_min_size(board_const, 4, is_piece_considered); else if (nu_moves < 5 * nu_colors) filter_min_size(board_const, 3, is_piece_considered); } else if (board_type == BoardType::classic) { if (nu_moves < 1 * nu_colors) { is_piece_considered.fill(false); set_piece_considered(board_const, "V5", is_piece_considered); set_piece_considered(board_const, "Z5", is_piece_considered); } else if (nu_moves < 3 * nu_colors) filter_min_size(board_const, 5, is_piece_considered); else if (nu_moves < 5 * nu_colors) filter_min_size(board_const, 4, is_piece_considered); else if (nu_moves < 7 * nu_colors) filter_min_size(board_const, 3, is_piece_considered); } else if (board_type == BoardType::trigon || board_type == BoardType::trigon_3) { if (nu_moves < 1 * nu_colors) { is_piece_considered.fill(false); set_piece_considered(board_const, "V", is_piece_considered); // I5 would also be good but the distance to center pruning in // State would prune them anyway } if (nu_moves < 4 * nu_colors) { filter_min_size(board_const, 6, is_piece_considered); // O is a bad early move, it neither extends, nor blocks well set_piece_considered(board_const, "O", is_piece_considered, false); } else if (nu_moves < 5 * nu_colors) filter_min_size(board_const, 5, is_piece_considered); else if (nu_moves < 7 * nu_colors) filter_min_size(board_const, 4, is_piece_considered); else if (nu_moves < 9 * nu_colors) filter_min_size(board_const, 3, is_piece_considered); } } } // namespace //----------------------------------------------------------------------------- Search::Search(Variant initial_variant, unsigned nu_threads, size_t memory) : ParentClass(nu_threads == 0 ? util::get_nu_threads() : nu_threads, memory == 0 ? util::get_memory() : memory), m_auto_param(true), m_variant(initial_variant), m_shared_const(m_to_play) { set_default_param(m_variant); create_threads(); } Search::~Search() throw() { } bool Search::check_followup(vector& sequence) { m_state.init(get_board(), m_to_play); bool is_followup = m_state.is_followup(m_last_state, sequence); m_last_state = m_state; return is_followup; } unique_ptr Search::create_state() { return unique_ptr(new State(m_variant, m_shared_const)); } string Search::get_move_string(Move mv) const { return get_board().to_string(mv); } void Search::get_root_position(Variant& variant, Setup& setup) const { m_last_state.get_as_setup(variant, setup); setup.to_play = m_to_play; } Float Search::get_tie_value() const { return 0.5; } void Search::on_start_search() { auto& bd = get_board(); auto& bc = bd.get_board_const(); auto nu_colors = bd.get_nu_colors(); for (ColorIterator i(nu_colors); i; ++i) { auto& is_forbidden_at_root = m_shared_const.is_forbidden_at_root[*i]; is_forbidden_at_root.set_all(); for (BoardIterator j(bd); j; ++j) if (! bd.is_forbidden(*j, *i)) { auto adj_status = bd.get_adj_status(*j, *i); for (Piece piece : bd.get_pieces_left(*i)) { for (Move mv : bd.get_moves(piece, *j, adj_status)) { if (! is_forbidden_at_root[mv]) continue; if (! bd.is_forbidden(*i, mv)) is_forbidden_at_root.clear(mv); } } } } // Initialize m_shared_const.moves_lists/moves_range for (ColorIterator i(nu_colors); i; ++i) m_shared_const.moves_range[*i].init(bd.get_geometry()); ColorMap current(0); for (BoardIterator i(bd); i; ++i) for (unsigned j = 0; j < BoardConst::nu_adj_status; ++j) for (Piece::IntType k = 0; k < bc.get_nu_pieces(); ++k) { Piece piece(k); auto moves = bc.get_moves(piece, *i, j); for (ColorIterator l(nu_colors); l; ++l) { if (! bd.is_piece_left(*l, piece) || bd.is_forbidden(*i, *l)) continue; auto& move_lists = m_shared_const.move_lists[*l]; unsigned begin = current[*l]; for (Move mv : moves) if (! m_shared_const.is_forbidden_at_root[*l][mv]) move_lists[current[*l]++] = mv; m_shared_const.moves_range[*l][*i][j][piece] = BoardConst::ListIndex(begin, current[*l] - begin); } } auto& is_piece_considered_list = m_shared_const.is_piece_considered_list; is_piece_considered_list.clear(); for (unsigned i = 0; i < Board::max_game_moves; ++i) { PieceMap is_piece_considered; set_pieces_considered(bd, i, is_piece_considered); bool are_all_considered = true; for (Piece::IntType j = 0; j < bc.get_nu_pieces(); ++j) if (! is_piece_considered[Piece(j)]) { are_all_considered = false; break; } if (are_all_considered) { m_shared_const.min_move_all_considered = i; break; } auto pos = find(is_piece_considered_list.begin(), is_piece_considered_list.end(), is_piece_considered); if (pos != is_piece_considered_list.end()) m_shared_const.is_piece_considered[i] = pos; else { is_piece_considered_list.push_back(is_piece_considered); m_shared_const.is_piece_considered[i] = &is_piece_considered_list.back(); } } m_shared_const.is_piece_considered_all.fill(true); PointTransfRot180 transform; m_shared_const.symmetric_points.init(bd.get_geometry(), transform); } bool Search::search(Move& mv, const Board& bd, Color to_play, Float max_count, Float min_simulations, double max_time, TimeSource& time_source) { m_shared_const.board = &bd; m_to_play = to_play; auto variant = bd.get_variant(); if (m_auto_param && variant != m_variant) set_default_param(variant); m_variant = variant; bool result = ParentClass::search(mv, max_count, min_simulations, max_time, time_source); return result; } void Search::set_default_param(Variant variant) { log() << "Setting default parameters for " << to_string(variant) << '\n'; set_skip_bias_term_min_count(30000); set_bias_term_interval(20); set_expand_threshold(1); set_expand_threshold_incr(0.5f); set_rave_weight(0.7f); set_rave_max_child_count(2000); // The following parameters are currently tuned for duo, classic_2 and // trigon_2 and used for all other game variants with the same board type switch (variant) { case Variant::duo: case Variant::junior: set_bias_term_constant(0.05f); set_rave_max_parent_count(25000); break; case Variant::classic_2: case Variant::classic: set_bias_term_constant(0.06f); set_rave_max_parent_count(50000); break; case Variant::trigon_2: case Variant::trigon_3: case Variant::trigon: set_bias_term_constant(0.06f); set_rave_max_parent_count(50000); break; default: LIBBOARDGAME_ASSERT(false); } } void Search::write_info(ostream& out) const { if (get_nu_simulations() == 0) return; auto& root = get_tree().get_root(); if (! root.has_children()) return; ParentClass::write_info(out); FmtSaver saver(out); out << "Mov: " << root.get_nu_children() << ", "; if (libpentobi_base::get_nu_players(m_variant) > 2) { out << "All:"; for (unsigned i = 0; i < libpentobi_base::get_nu_colors(m_variant); ++i) { if (get_root_val()[i].get_count() == 0) out << " -"; else out << " " << setprecision(2) << get_root_val()[i].get_mean(); } out << ", "; } get_state(0).write_info(out); } //----------------------------------------------------------------------------- } // namespace libpentobi_mcts pentobi-7.2/src/libpentobi_mcts/Search.h000066400000000000000000000115671227240712600203610ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_mcts/Search.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_MCTS_SEARCH_H #define LIBPENTOBI_MCTS_SEARCH_H #include "State.h" #include "libboardgame_mcts/Search.h" #include "libpentobi_base/GameStateHistory.h" namespace libpentobi_mcts { using namespace std; using libboardgame_mcts::PlayerInt; using libboardgame_util::Timer; using libboardgame_util::TimeSource; using libpentobi_base::GameStateHistory; using libpentobi_base::Setup; //----------------------------------------------------------------------------- /** Optional compile-time parameters for libboardgame_mcts::Search. See libboardgame_mcts::SearchParamConstDefault for the meaning of the members. */ struct SearchParamConst { typedef libpentobi_mcts::Float Float; static const PlayerInt max_players = 4; static const bool rave = true; static const bool rave_check_same = false; static const bool rave_dist_weighting = true; static const bool use_last_good_reply = true; }; //----------------------------------------------------------------------------- /** Monte-Carlo tree search implementation for Blokus. @note @ref libboardgame_avoid_stack_allocation */ class Search : public libboardgame_mcts::Search { public: Search(Variant initial_variant, unsigned nu_threads, size_t memory); ~Search() throw(); unique_ptr create_state() override; string get_move_string(Move mv) const override; PlayerInt get_nu_players() const override; PlayerInt get_player() const override; Float get_tie_value() const override; bool check_followup(vector& sequence) override; void write_info(ostream& out) const override; /** @name Parameters */ // @{ Float get_score_modification() const; void set_score_modification(Float value); bool get_detect_symmetry() const; void set_detect_symmetry(bool enable); bool get_avoid_symmetric_draw() const; void set_avoid_symmetric_draw(bool enable); /** Automatically set some user-changeable parameters that have different optimal values for different game variants whenever the game variant changes. Default is true. */ bool get_auto_param() const; void set_auto_param(bool enable); // @} // @name bool search(Move& mv, const Board& bd, Color to_play, Float max_count, Float min_simulations, double max_time, TimeSource& time_source); /** Get color to play at root node of the last search. */ Color get_to_play() const; const GameStateHistory& get_last_state() const; /** Get board position of last search at root node as setup. @param[out] variant @param[out] setup */ void get_root_position(Variant& variant, Setup& setup) const; protected: void on_start_search() override; private: typedef libboardgame_mcts::Search ParentClass; /** Automatically set default parameters for the game variant if the game variant changes. */ bool m_auto_param; /** Game variant of last search. */ Variant m_variant; Color m_to_play; SharedConst m_shared_const; /** Local variable reused for efficiency. */ GameStateHistory m_state; GameStateHistory m_last_state; const Board& get_board() const; void set_default_param(Variant variant); }; inline bool Search::get_auto_param() const { return m_auto_param; } inline bool Search::get_avoid_symmetric_draw() const { return m_shared_const.avoid_symmetric_draw; } inline const Board& Search::get_board() const { return *m_shared_const.board; } inline bool Search::get_detect_symmetry() const { return m_shared_const.detect_symmetry; } inline const GameStateHistory& Search::get_last_state() const { return m_last_state; } inline PlayerInt Search::get_nu_players() const { return get_board().get_nu_colors(); } inline PlayerInt Search::get_player() const { return m_to_play.to_int(); } inline Float Search::get_score_modification() const { return m_shared_const.score_modification; } inline Color Search::get_to_play() const { return m_to_play; } inline void Search::set_auto_param(bool enable) { m_auto_param = enable; } inline void Search::set_avoid_symmetric_draw(bool enable) { m_shared_const.avoid_symmetric_draw = enable; } inline void Search::set_detect_symmetry(bool enable) { m_shared_const.detect_symmetry = enable; } inline void Search::set_score_modification(Float value) { m_shared_const.score_modification = value; } //----------------------------------------------------------------------------- } // namespace libpentobi_mcts #endif // LIBPENTOBI_MCTS_SEARCH_H pentobi-7.2/src/libpentobi_mcts/State.cpp000066400000000000000000001106431227240712600205620ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_mcts/State.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "State.h" #include #include "libboardgame_util/FmtSaver.h" #include "libboardgame_util/Log.h" #include "libboardgame_util/MathUtil.h" #include "libpentobi_base/BoardUtil.h" #include "libpentobi_base/Geometry.h" namespace libpentobi_mcts { using namespace std; using libboardgame_mcts::Tree; using libboardgame_util::log; using libboardgame_util::FmtSaver; using libpentobi_base::BoardIterator; using libpentobi_base::BoardType; using libpentobi_base::ColorIterator; using libpentobi_base::ColorMove; using libpentobi_base::Geometry; using libpentobi_base::GeometryIterator; using libpentobi_base::MoveInfo; using libpentobi_base::MoveInfoExt; using libpentobi_base::PieceInfo; using libpentobi_base::Point; using libpentobi_base::PointState; //----------------------------------------------------------------------------- namespace { const bool use_prior_knowledge = true; const bool pure_random_playout = false; /** Return the symmetric point state for symmetry detection. Only used for Variant::duo. Returns the other color or empty, if the given point state is empty. */ PointState get_symmetric_state(Color c) { if (c == Color(0)) return Color(1); else if (c == Color(1)) return Color(0); else if (c == Color(2)) return Color(3); else { LIBBOARDGAME_ASSERT(c == Color(3)); return Color(2); } } } // namespace //----------------------------------------------------------------------------- SharedConst::SharedConst(const Color& to_play) : board(nullptr), to_play(to_play), detect_symmetry(true), avoid_symmetric_draw(true), score_modification(0.1f) { // Game variant and position dependent variables are initialized in // libpentobi_mcts::Search::start_search() } //----------------------------------------------------------------------------- State::State(Variant initial_variant, const SharedConst& shared_const) : m_shared_const(shared_const), m_bd(initial_variant), m_tmp_moves(new MoveList()) { } State::~State() throw() { } inline void State::add_moves(Point p, Color c, const Board::PiecesLeftList& pieces_considered) { auto& moves = *m_moves[c]; auto& marker = m_marker[c]; auto adj_status = m_bd.get_adj_status(p, c); auto& is_forbidden = m_bd.is_forbidden(c); for (Piece piece : pieces_considered) { auto move_candidates = get_moves(c, piece, p, adj_status); for (auto i = move_candidates.begin(); i != move_candidates.end(); ++i) if (! marker[*i] && check_move(is_forbidden, *i, get_move_info(*i))) { marker.set(*i); moves.push_back(*i); } } m_moves_added_at[c].set(p); } inline void State::add_moves(Point p, Color c, Piece piece, unsigned adj_status) { auto& moves = *m_moves[c]; auto& marker = m_marker[c]; auto move_candidates = get_moves(c, piece, p, adj_status); auto& is_forbidden = m_bd.is_forbidden(c); for (auto i = move_candidates.begin(); i != move_candidates.end(); ++i) if (! marker[*i] && check_move(is_forbidden, *i, get_move_info(*i))) { marker.set(*i); moves.push_back(*i); } } void State::add_starting_moves(Color c, const Board::PiecesLeftList& pieces_considered) { // Using only one starting point (if game variant has more than one) not // only reduces the branching factor but is also necessary because // update_moves() assumes that a move stays legal if the forbidden // status for all of its points does not change. Point p = find_best_starting_point(c); if (p.is_null()) return; auto& moves = *m_moves[c]; auto& marker = m_marker[c]; auto& is_forbidden = m_bd.is_forbidden(c); // We use adj_status 0 and don't check if moves should be in the local // list, which is appropriate given the starting point locations in the // standard game variants and assuming normal game play with alternating // moves and adj_status is only an optimization and local moves only a // heuristic anyway. However, we check if all the moves are legal to avoid // the generation of illegal moves in non-standard board positions. for (Piece piece : pieces_considered) for (Move mv : get_moves(c, piece, p, 0)) { LIBBOARDGAME_ASSERT(! marker[mv]); if (check_move_without_local(is_forbidden, mv)) { marker.set(mv); moves.push_back(mv); } } } /** Check if move is not forbidden and compute/handle its local value in the same loop. */ bool State::check_move(const Grid& is_forbidden, Move mv, const MoveInfo& info) { auto i = info.begin(); if (is_forbidden[*i]) return false; LocalValue::Compute compute_local(*i, m_local_value); auto end = info.end(); while (++i != end) { if (is_forbidden[*i]) return false; compute_local.add_move_point(*i, m_local_value); } unsigned piece_size = info.size(); if (piece_size > m_max_playable_piece_size) m_max_playable_piece_size = piece_size; if (compute_local.get_upper_bound() >= m_max_local_value) { auto local_value = compute_local.get(); if (local_value > m_max_local_value) { m_local_moves.clear(); m_max_local_value = local_value; m_max_playable_piece_size_local = piece_size; m_local_moves.push_back(mv); } else if (local_value == m_max_local_value) { if (piece_size > m_max_playable_piece_size_local) m_max_playable_piece_size_local = piece_size; m_local_moves.push_back(mv); } } return true; } bool State::check_move_without_local(const Grid& is_forbidden, Move mv) { auto& info = get_move_info(mv); auto i = info.begin(); if (is_forbidden[*i]) return false; auto end = info.end(); while (++i != end) if (is_forbidden[*i]) return false; return true; } void State::compute_features() { auto to_play = m_bd.get_to_play(); auto second_color = m_bd.get_second_color(to_play); auto board_type = m_bc->get_board_type(); auto& moves = *m_moves[to_play]; auto& geometry = m_bc->get_geometry(); auto& is_forbidden = m_bd.is_forbidden(to_play); Grid point_value(geometry); Grid attach_point_value(geometry); Grid adj_point_value(geometry); for (GeometryIterator i(geometry); i; ++i) { point_value[*i] = 1; auto s = m_bd.get_point_state(*i); if (is_forbidden[*i] && s != to_play) attach_point_value[*i] = -2.5; else attach_point_value[*i] = 0.5; if (! is_forbidden[*i]) { if (m_bd.is_attach_point(*i, to_play)) // Making own attach point forbidden is especially bad adj_point_value[*i] = -1; else // Creating new forbidden points is a bad thing adj_point_value[*i] = -0.1f; } else if (s == second_color) // Connecting 2 player colors in 2-colors-per-player game variants // is good (in other variants second_color is the same as to_play // but there it doesn't matter what adj_point_value[*i] is because // moves adjacent to to_play are not legal anyway). adj_point_value[*i] = 1; else adj_point_value[*i] = 0; } for (ColorIterator i(m_nu_colors); i; ++i) { if (*i == to_play || *i == second_color) continue; auto& is_forbidden = m_bd.is_forbidden(*i); for (Point p : m_bd.get_attach_points(*i)) if (! is_forbidden[p]) { point_value[p] = 3.2f; for (AdjIterator j(geometry, p); j; ++j) if (! is_forbidden[*j]) point_value[*j] = max(point_value[*j], Float(2.5)); } } m_features.resize(moves.size()); m_max_heuristic = -numeric_limits::max(); m_min_dist_to_center = numeric_limits::max(); m_has_connect_move = false; unsigned nu_onboard_pieces = m_bd.get_nu_onboard_pieces(); bool compute_dist_to_center = ((board_type == BoardType::classic && nu_onboard_pieces < 13) || (board_type == BoardType::trigon && nu_onboard_pieces < 5) || (board_type == BoardType::trigon_3 && nu_onboard_pieces < 5)); bool check_connect = (board_type == BoardType::classic && m_bd.get_nu_onboard_pieces() < 14); for (unsigned i = 0; i < moves.size(); ++i) { auto& info = get_move_info(moves[i]); auto& info_ext = get_move_info_ext(moves[i]); auto& features = m_features[i]; features.heuristic = 0; features.connect = false; features.dist_to_center = numeric_limits::max(); { auto j = info.begin(); auto end = info.end(); do features.heuristic += point_value[*j]; while (++j != end); } auto j = info_ext.begin_attach(); auto end = info_ext.end_attach(); do features.heuristic += attach_point_value[*j]; while (++j != end); j = info_ext.begin_adj(); end = info_ext.end_adj(); if (! check_connect) { do features.heuristic += adj_point_value[*j]; while (++j != end); } else { do { features.heuristic += adj_point_value[*j]; if (m_bd.get_point_state(*j) == second_color) features.connect = true; } while (++j != end); if (features.connect) m_has_connect_move = true; } if (compute_dist_to_center) { for (auto j = info.begin(); j != info.end(); ++j) features.dist_to_center = min(features.dist_to_center, m_dist_to_center[*j]); m_min_dist_to_center = min(m_min_dist_to_center, features.dist_to_center); } if (features.heuristic > m_max_heuristic) m_max_heuristic = features.heuristic; } } bool State::check_symmetry_broken() { Color to_play = m_bd.get_to_play(); if (to_play == Color(0) || to_play == Color(2)) { // First player to play: the symmetry is broken if the position is // not symmetric. for (BoardIterator i(m_bd); i; ++i) { PointState s1 = m_bd.get_point_state(*i); if (! s1.is_empty()) { Point symm_p = m_shared_const.symmetric_points[*i]; PointState s2 = m_bd.get_point_state(symm_p); if (s2 != get_symmetric_state(s1.to_color())) return true; } } } else { // Second player to play: the symmetry is broken if the second player // cannot copy the first player's last move to make the position // symmetric again. unsigned nu_moves = m_bd.get_nu_moves(); if (nu_moves == 0) // Don't try to handle the case if the second player has to play as // first move (e.g. in setup positions) return true; Color previous_color = m_bd.get_previous(to_play); ColorMove last_mv = m_bd.get_move(nu_moves - 1); if (last_mv.color != previous_color || last_mv.move.is_pass()) // Don't try to handle non-alternating moves or pass moves in // board history return true; auto& info = get_move_info(last_mv.move); for (BoardIterator i(m_bd); i; ++i) { PointState s1 = m_bd.get_point_state(*i); if (! s1.is_empty()) { Point symm_p = m_shared_const.symmetric_points[*i]; PointState s2 = m_bd.get_point_state(symm_p); if (s2 != get_symmetric_state(s1.to_color())) if (! (info.contains(*i) && s2.is_empty())) return true; } } } return false; } void State::dump(ostream& out) const { out << "pentobi_mcts::State:\n"; libpentobi_base::boardutil::dump(m_bd, out); } array State::evaluate_playout() { // Always evaluate symmetric positions as a draw in the playouts. This // will encourage the first player to break the symmetry and the second // player to preserve it. if (! m_is_symmetry_broken && m_bd.get_nu_onboard_pieces() >= m_symmetry_min_nu_pieces) { if (log_simulations) log("Result: 0.5 (symmetry)"); array result; for (ColorIterator i(m_nu_colors); i; ++i) result[(*i).to_int()] = 0.5; return result; } return evaluate_terminal(); } /** Get the game result for each color. The result is 0,0.5,1 for loss/tie/win in 2-player variants. If there are n > 2 players, this is generalized in the following way: The scores are sorted in ascending order. Each rank r_i (i in 0..n-1) is assigned a result value of r_i/(n-1). If multiple players have the same score, the result value is the average of all ranks with this score. So being the single winner still gives the result 1 and having the lowest score gives the result 0. Being the single winner is better than sharing the best place, which is better than getting the second place, etc. There is small modification applied to the result that encorages wins with larger scores. */ array State::evaluate_terminal() { unsigned nu_players = m_bd.get_nu_players(); ColorMap points; ColorMap score; for (ColorIterator i(m_nu_colors); i; ++i) points[*i] = Float(m_bd.get_points_with_bonus(*i)); for (Color::IntType i = 0; i < nu_players; ++i) score[Color(i)] = Float(m_bd.get_score(Color(i))); if (m_nu_colors > nu_players) { LIBBOARDGAME_ASSERT(m_nu_colors == 4); score[Color(2)] = score[Color(0)]; score[Color(3)] = score[Color(1)]; } array sorted_points; if (nu_players > 2) { for (ColorIterator i(m_nu_colors); i; ++i) sorted_points[(*i).to_int()] = points[*i]; sort(sorted_points.begin(), sorted_points.begin() + m_nu_colors); } array result_array; for (Color::IntType i = 0; i < nu_players; ++i) { Color c(i); Float game_result; if (nu_players == 2) { if (score[c] > 0) game_result = 1; else if (score[c] < 0) game_result = 0; else game_result = 0.5; } else { game_result = 0; Float n = 0; for (Color::IntType j = 0; j < m_nu_colors; ++j) if (sorted_points[j] == points[c]) { game_result += Float(j) / Float(m_nu_colors - 1); ++n; } game_result /= n; } Float score_modification = m_shared_const.score_modification; // Apply score modification. Example: If score modification is 0.1, // the game result is rescaled to [0..0.9] and the score modification // is added with 0.05 as the middle (corresponding to score 0). Float result = (1.f - score_modification) * game_result + 0.5f * (score_modification + score[c] * m_score_modification_factor); result_array[i] = result; if (log_simulations) log() << "Result color " << c << ": score=" << score[c] << " game_result=" << game_result << " result=" << result << '\n'; } if (m_nu_colors > nu_players) { LIBBOARDGAME_ASSERT(m_nu_colors == 4); result_array[2] = result_array[0]; result_array[3] = result_array[1]; } return result_array; } Point State::find_best_starting_point(Color c) const { // We use the starting point that maximizes the distance to occupied // starting points, especially to the ones occupied by the player (their // distance is weighted with a factor of 2) Point best = Point::null(); float max_distance = -1; bool is_trigon = (m_bd.get_board_type() == BoardType::trigon || m_bd.get_board_type() == BoardType::trigon_3); float ratio = (is_trigon ? 1.732f : 1); for (Point p : m_bd.get_starting_points(c)) { if (m_bd.is_forbidden(p, c)) continue; float px = static_cast(p.get_x()); float py = static_cast(p.get_y()); float d = 0; for (ColorIterator i(m_nu_colors); i; ++i) { for (Point pp : m_bd.get_starting_points(*i)) { PointState s = m_bd.get_point_state(pp); if (! s.is_empty()) { float ppx = static_cast(pp.get_x()); float ppy = static_cast(pp.get_y()); float dx = ppx - px; float dy = ratio * (ppy - py); float weight = 1; if (s == c || s == m_bd.get_second_color(c)) weight = 2; d += weight * sqrt(dx * dx + dy * dy); } } } if (d > max_distance) { best = p; max_distance = d; } } return best; } bool State::gen_playout_move(Move last_good_reply_1, Move last_good_reply_2, Move& mv) { if (m_nu_passes == m_nu_colors) return false; if (! m_is_symmetry_broken && m_bd.get_nu_onboard_pieces() >= m_symmetry_min_nu_pieces) { // See also the comment in evaluate_playout() if (log_simulations) log("Terminate playout. Symmetry not broken."); return false; } ++m_nu_playout_moves; if (last_good_reply_2.is_regular() && m_bd.is_legal(last_good_reply_2)) { if (log_simulations) log("Playing last good reply 2"); ++m_nu_last_good_reply_moves; mv = last_good_reply_2; return true; } if (last_good_reply_1.is_regular() && m_bd.is_legal(last_good_reply_1)) { if (log_simulations) log("Playing last good reply 1"); ++m_nu_last_good_reply_moves; mv = last_good_reply_1; return true; } Color to_play; while (true) { to_play = m_bd.get_to_play(); if (! m_is_move_list_initialized[to_play]) init_moves_with_local(to_play); else if (m_has_moves[to_play]) update_moves(to_play); if ((m_has_moves[to_play] = ! m_moves[to_play]->empty())) break; if (m_nu_passes + 1 == m_nu_colors) return false; if (m_check_terminate_early && m_bd.get_score(to_play) < 0 && ! m_has_moves[m_bd.get_second_color(to_play)]) { if (log_simulations) log("Terminate early (no moves and negative score)"); return false; } ++m_nu_passes; m_bd.set_to_play(m_bd.get_next(to_play)); // Don't try to handle symmetry after pass moves m_is_symmetry_broken = true; } // Choose a random move from the list of local moves (if not empty) or // the list of all moves. Try more than once if a bad move (e.g. not // maximum playable piece size) is chosen to reduce the probabilty // for such moves without becoming deterministic. const MoveList* moves; unsigned max_playable_piece_size; if (pure_random_playout || m_local_moves.empty()) { moves = m_moves[to_play].get(); max_playable_piece_size = m_max_playable_piece_size; if (log_simulations) log() << "Moves: " << moves->size() << "\n"; } else { moves = &m_local_moves; max_playable_piece_size = m_max_playable_piece_size_local; if (log_simulations) { FmtSaver saver(log()); log() << "Moves: " << m_moves[to_play]->size() << ", local: " << m_local_moves.size() << ", local_val: 0x" << setfill('0') << hex << setw(3) << m_max_local_value << "\n"; } } const unsigned max_try = 3; unsigned nu_try = 0; do { ++nu_try; unsigned i = m_random.generate_small_uint(moves->size()); mv = (*moves)[i]; } while (get_move_info(mv).size() < max_playable_piece_size && nu_try < max_try); return true; } bool State::gen_and_play_playout_move(Move last_good_reply_1, Move last_good_reply_2) { Move mv; if (gen_playout_move(last_good_reply_1, last_good_reply_2, mv)) { play_playout(mv); return true; } return false; } void State::gen_children(Tree::NodeExpander& expander, Float init_val) { if (m_nu_passes == m_nu_colors) return; Color to_play = m_bd.get_to_play(); init_moves_without_local(to_play); const auto& moves = *m_moves[to_play]; if (moves.empty()) { expander.add_child(Move::pass(), 0.5, 0); return; } if (! use_prior_knowledge) { for (Move mv : moves) expander.add_child(mv, 0.5, 0); return; } compute_features(); Move symmetric_mv = Move::null(); bool has_symmetry_breaker = false; if (! m_is_symmetry_broken) { unsigned nu_moves = m_bd.get_nu_moves(); if (to_play == Color(1) || to_play == Color(3)) { if (nu_moves > 0) { ColorMove last = m_bd.get_move(nu_moves - 1); if (! last.is_pass()) symmetric_mv = m_bd.get_move_info_ext_2(last.move).symmetric_move; } } else if (nu_moves > 0) for (Move mv : moves) if (m_bd.get_move_info_ext_2(mv).breaks_symmetry) { has_symmetry_breaker = true; break; } } for (unsigned i = 0; i < moves.size(); ++i) { auto mv = moves[i]; const auto& features = m_features[i]; if (m_min_dist_to_center != numeric_limits::max() && features.dist_to_center != m_min_dist_to_center) // Prune early moves that don't minimize dist to center continue; if (m_bc->get_board_type() == BoardType::classic && m_bd.get_nu_onboard_pieces() < 14 && m_has_connect_move && ! features.connect) // Prune moves that don't connect in the middle if connection is // possible continue; // Convert the heuristic, which is so far estimated in score points, // into a win/loss value in [0..1] by making it relative to the // heuristic of the best move and let it decrease exponentially with a // certain width. Float heuristic = 0.6f * (m_max_heuristic - features.heuristic); // Piecewise linear approximation of exp(-x) (make sure the // approximation is always greater than 0 and is always monotonically // decreasing) if (heuristic < 0.5) heuristic = 1.f - 0.9f * heuristic; else if (heuristic < 2) heuristic = 0.45f - 0.2f * (heuristic - 0.45f); else if (heuristic < 4) heuristic = 0.27f - 0.08f * (heuristic - 0.27f); else heuristic = 0.0248f / (heuristic - 3.0f); // Rescale to [0.1..1]. If the value is too close to 0, the move might // never get explored (in practice) if the bias term constant is small. heuristic = 0.1f + 0.9f * heuristic; // Initialize value from heuristic and init_val, each with a count // of 1.5 Float value = 0.5f * heuristic + 0.5f * init_val; Float count = 3; // If a symmetric draw is still possible, encourage exploring a move // that keeps or breaks the symmetry by adding 5 wins or 5 losses // (use 0.1 for a loss to avoid values too close to 0). See also the // comment in evaluate_playout() if (! symmetric_mv.is_null()) { if (mv == symmetric_mv) value = (3.f / 8) * value + (5.f / 8) * 1.0f; else value = (3.f / 8) * value + (5.f / 8) * 0.1f; count = 8; } else if (has_symmetry_breaker) { if (m_bd.get_move_info_ext_2(mv).breaks_symmetry) value = (3.f / 8) * value + (5.f / 8) * 1.0f; else value = (3.f / 8) * value + (5.f / 8) * 0.1f; count = 8; } expander.add_child(mv, value, count); } } inline const PieceMap& State::get_pieces_considered() const { // Use number of on-board pieces for move number to handle the case where // there are more pieces on the board than moves (setup positions) unsigned nu_moves = m_bd.get_nu_onboard_pieces(); if (nu_moves >= m_shared_const.min_move_all_considered || m_force_consider_all_pieces) return m_shared_const.is_piece_considered_all; else return *m_shared_const.is_piece_considered[nu_moves]; } void State::init_moves_with_local(Color c) { m_is_piece_considered[c] = &get_pieces_considered(); m_local_value.init(m_bd); m_local_moves.clear(); m_max_local_value = 1; m_max_playable_piece_size = 1; m_max_playable_piece_size_local = 1; auto& marker = m_marker[c]; auto& moves = *m_moves[c]; marker.clear_all_set_known(moves); moves.clear(); Board::PiecesLeftList pieces_considered; for (Piece piece : m_bd.get_pieces_left(c)) if ((*m_is_piece_considered[c])[piece]) pieces_considered.push_back(piece); if (m_bd.is_first_piece(c)) add_starting_moves(c, pieces_considered); else for (Point p : m_bd.get_attach_points(c)) if (! m_bd.is_forbidden(p, c)) add_moves(p, c, pieces_considered); m_is_move_list_initialized[c] = true; m_new_moves[c].clear(); if (moves.empty() && ! m_force_consider_all_pieces) { m_force_consider_all_pieces = true; init_moves_with_local(c); } } void State::init_moves_without_local(Color c) { m_is_piece_considered[c] = &get_pieces_considered(); auto& marker = m_marker[c]; auto& moves = *m_moves[c]; marker.clear_all_set_known(moves); moves.clear(); Board::PiecesLeftList pieces_considered; for (Piece piece : m_bd.get_pieces_left(c)) if ((*m_is_piece_considered[c])[piece]) pieces_considered.push_back(piece); auto& is_forbidden = m_bd.is_forbidden(c); if (m_bd.is_first_piece(c)) add_starting_moves(c, pieces_considered); else for (Point p : m_bd.get_attach_points(c)) if (! is_forbidden[p]) { auto adj_status = m_bd.get_adj_status(p, c); for (Piece piece : pieces_considered) for (Move mv : get_moves(c, piece, p, adj_status)) if (! marker[mv] && check_move_without_local(is_forbidden, mv)) { marker.set(mv); moves.push_back(mv); } m_moves_added_at[c].set(p); } m_is_move_list_initialized[c] = true; m_new_moves[c].clear(); if (moves.empty() && ! m_force_consider_all_pieces) { m_force_consider_all_pieces = true; init_moves_without_local(c); } } void State::play_expanded_child(Move mv) { if (log_simulations) log("Playing expanded child"); if (! mv.is_pass()) play_playout(mv); else { m_bd.play_pass(); ++m_nu_passes; // Don't try to handle pass moves: a pass move either breaks symmetry // or both players have passed and it's the end of the game and we need // symmetry detection only as a heuristic ((playouts and move value // initialization) m_is_symmetry_broken = true; if (log_simulations) log() << m_bd; } } void State::play_playout(Move mv) { LIBBOARDGAME_ASSERT(m_bd.is_legal(mv)); m_new_moves[m_bd.get_to_play()].push_back(mv); m_bd.play_nonpass(mv); m_nu_passes = 0; if (! m_is_symmetry_broken) update_symmetry_broken(mv); if (log_simulations) log() << m_bd; } void State::start_search() { auto& bd = *m_shared_const.board; m_bd.copy_from(bd); m_bd.set_to_play(m_shared_const.to_play); m_bd.take_snapshot(); m_bc = &m_bd.get_board_const(); m_nu_colors = bd.get_nu_colors(); m_move_info_array = m_bc->get_move_info_array(); m_move_info_ext_array = m_bc->get_move_info_ext_array(); auto& geometry = bd.get_geometry(); m_local_value.init_geometry(geometry); m_nu_moves_initial = bd.get_nu_moves(); m_check_terminate_early = (m_nu_moves_initial < 10u * m_nu_colors && m_bd.get_nu_players() == 2); Float total_piece_points = Float(m_bc->get_total_piece_points()); m_score_modification_factor = m_shared_const.score_modification / total_piece_points; m_nu_simulations = 0; m_nu_playout_moves = 0; m_nu_last_good_reply_moves = 0; auto variant = bd.get_variant(); m_check_symmetric_draw = ((variant == Variant::duo || variant == Variant::junior || variant == Variant::trigon_2) && m_shared_const.detect_symmetry && ! ((m_shared_const.to_play == Color(1) || m_shared_const.to_play == Color(3)) && m_shared_const.avoid_symmetric_draw) && ! check_symmetry_broken()); if (variant == Variant::trigon_2) m_symmetry_min_nu_pieces = 5; else m_symmetry_min_nu_pieces = 3; // Only used in Duo for (ColorIterator i(m_nu_colors); i; ++i) if (! m_moves[*i]) m_moves[*i].reset(new MoveList()); // Init m_dist_to_center m_dist_to_center.init(geometry); float width = static_cast(geometry.get_width()); float height = static_cast(geometry.get_height()); float center_x = 0.5f * width - 0.5f; float center_y = 0.5f * height - 0.5f; bool is_trigon = (bd.get_board_type() == BoardType::trigon || bd.get_board_type() == BoardType::trigon_3); float ratio = (is_trigon ? 1.732f : 1); for (GeometryIterator i(geometry); i; ++i) { float x = static_cast(i->get_x()); float y = static_cast(i->get_y()); float dx = x - center_x; float dy = ratio * (y - center_y); // Multiply Euklidian distance by 4, so that distances that differ // by max. 0.25 are treated as equal float d = libboardgame_util::math_util::round(4 * sqrt(dx * dx + dy * dy)); if (bd.get_board_type() == BoardType::classic) // Don't make a distinction between moves close enough to the center // in game variant Classic/Classic2 d = max(d, 10.f); m_dist_to_center[*i] = static_cast(d); } //log() << "Dist to center:\n" << m_dist_to_center; } void State::start_simulation(size_t n) { if (log_simulations) log() << "==========================================================\n" << "Simulation " << n << "\n" << "==========================================================\n"; ++m_nu_simulations; m_bd.restore_snapshot(); m_force_consider_all_pieces = false; for (ColorIterator i(m_nu_colors); i; ++i) { m_has_moves[*i] = true; m_is_move_list_initialized[*i] = false; m_new_moves[*i].clear(); m_moves_added_at[*i].clear(); } m_nu_passes = 0; // TODO: m_nu_passes should be initialized without assuming alternating // colors in the board's move history for (unsigned i = m_bd.get_nu_moves(); i > 0; --i) { if (! m_bd.get_move(i - 1).move.is_pass()) break; ++m_nu_passes; } } void State::update_moves(Color c) { m_local_value.init(m_bd); m_local_moves.clear(); m_max_local_value = 1; m_max_playable_piece_size = 1; m_max_playable_piece_size_local = 1; auto& marker = m_marker[c]; // Find old moves that are still legal PieceMap is_piece_left(false); for (Piece piece : m_bd.get_pieces_left(c)) is_piece_left[piece] = true; auto& is_forbidden = m_bd.is_forbidden(c); m_tmp_moves->clear(); for (Move mv : *m_moves[c]) { auto& info = get_move_info(mv); if (is_piece_left[info.get_piece()] && check_move(is_forbidden, mv, info)) m_tmp_moves->push_back(mv); else marker.clear(mv); } swap(m_tmp_moves, m_moves[c]); // Find new legal moves because of new pieces played by this color Board::PiecesLeftList pieces_considered; for (Piece piece : m_bd.get_pieces_left(c)) if ((*m_is_piece_considered[c])[piece]) pieces_considered.push_back(piece); for (Move mv : m_new_moves[c]) { auto& info_ext = get_move_info_ext(mv); auto i = info_ext.begin_attach(); auto end = info_ext.end_attach(); do if (! is_forbidden[*i] && ! m_moves_added_at[c][*i]) add_moves(*i, c, pieces_considered); while (++i != end); } m_new_moves[c].clear(); // Generate moves for pieces not considered in the last position auto& is_piece_considered = *m_is_piece_considered[c]; if (&is_piece_considered != &m_shared_const.is_piece_considered_all) { if (m_moves[c]->empty()) m_force_consider_all_pieces = true; auto& is_piece_considered_new = get_pieces_considered(); if (&is_piece_considered != &is_piece_considered_new) { pieces_considered.clear(); for (Piece piece : m_bd.get_pieces_left(c)) if (! is_piece_considered[piece] && is_piece_considered_new[piece]) pieces_considered.push_back(piece); for (Point p : m_bd.get_attach_points(c)) if (! is_forbidden[p]) { auto adj_status = m_bd.get_adj_status(p, c); for (Piece piece : pieces_considered) add_moves(p, c, piece, adj_status); } m_is_piece_considered[c] = &is_piece_considered_new; } } } void State::update_symmetry_broken(Move mv) { LIBBOARDGAME_ASSERT(! mv.is_pass()); auto& info = get_move_info(mv); Color to_play = m_bd.get_to_play(); Color second_color = m_bd.get_second_color(to_play); if (to_play == Color(0) || to_play == Color(2)) { // First player to play: Check that all symmetric points of the last // move of the second player are occupied by the first player for (auto i = info.begin(); i != info.end(); ++i) { Point symm_p = m_shared_const.symmetric_points[*i]; if (m_bd.get_point_state(symm_p) != second_color) { m_is_symmetry_broken = true; return; } } } else { // Second player to play: Check that all symmetric points of the last // move of the first player are empty (i.e. the second player can play // there to preserve the symmetry) for (auto i = info.begin(); i != info.end(); ++i) { Point symm_p = m_shared_const.symmetric_points[*i]; if (! m_bd.get_point_state(symm_p).is_empty()) { m_is_symmetry_broken = true; return; } } } } void State::write_info(ostream& out) const { if (m_nu_playout_moves > 0) { FmtSaver saver(log()); out << "LGR: " << fixed << setprecision(1) << (100.0 * static_cast(m_nu_last_good_reply_moves) / static_cast(m_nu_playout_moves)) << "%"; } out << '\n'; } //----------------------------------------------------------------------------- } // namespace libpentobi_mcts pentobi-7.2/src/libpentobi_mcts/State.h000066400000000000000000000312651227240712600202310ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_mcts/State.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_MCTS_STATE_H #define LIBPENTOBI_MCTS_STATE_H #include "Float.h" #include "LocalValue.h" #include "libboardgame_mcts/PlayerMove.h" #include "libboardgame_mcts/Tree.h" #include "libboardgame_util/RandomGenerator.h" #include "libboardgame_util/Statistics.h" #include "libboardgame_util/Unused.h" #include "libpentobi_base/Board.h" #include "libpentobi_base/ColorMap.h" #include "libpentobi_base/MoveList.h" #include "libpentobi_base/MoveMarker.h" #include "libpentobi_base/PieceMap.h" #include "libpentobi_base/PointList.h" #include "libpentobi_base/SymmetricPoints.h" namespace libpentobi_mcts { using namespace std; using libboardgame_mcts::PlayerInt; using libboardgame_mcts::PlayerMove; using libboardgame_util::ArrayList; using libboardgame_util::RandomGenerator; using libboardgame_util::Statistics; using libboardgame_util::StatisticsBase; using libpentobi_base::Board; using libpentobi_base::BoardConst; using libpentobi_base::ColorMove; using libpentobi_base::Variant; using libpentobi_base::Grid; using libpentobi_base::Marker; using libpentobi_base::Move; using libpentobi_base::MoveInfo; using libpentobi_base::MoveInfoExt; using libpentobi_base::MoveList; using libpentobi_base::MoveMarker; using libpentobi_base::MovePoints; using libpentobi_base::Piece; using libpentobi_base::PieceMap; using libpentobi_base::Point; using libpentobi_base::PointList; using libpentobi_base::SymmetricPoints; using libpentobi_base::Color; using libpentobi_base::ColorMap; //----------------------------------------------------------------------------- /** Constant data shared between the search states. */ struct SharedConst { /** Like BoardConst::m_moves_range but for SharedConst::move_lists. Only elements for pieces still available and non-forbidden points are initialized. */ ColorMap, BoardConst::nu_adj_status>>> moves_range; /** Like BoardConst::m_move_lists but moves that are forbidden at the root position filtered out. */ ColorMap> move_lists; /** The game board. Contains the current position. */ const Board* board; /** The color to play at the root of the search. */ const Color& to_play; bool detect_symmetry; bool avoid_symmetric_draw; /** Maximum value to modify the win/loss result by the score. */ Float score_modification; /** Lookup table for symmetric points (only used in Duo). */ SymmetricPoints symmetric_points; /** Precomputed information if move is forbidden at the start position (and therefore in all positions in the search). */ ColorMap is_forbidden_at_root; /** Minimum move number where all pieces are considered until the rest of the simulation. */ unsigned min_move_all_considered; /** Precomputed lists of considered pieces depending on the move number. Only initialized for move numbers less than min_move_all_considered. Contains pointers to unique values auch that the comparison of the lists can be done by comparing the pointers to the lists. */ array*,Board::max_game_moves> is_piece_considered; /** List of unique values for is_piece_considered. */ ArrayList,Board::max_game_moves> is_piece_considered_list; /** Precomputed lists of considered pieces if all pieces are enforced to be considered (because using the restricted set of pieces would generate no moves). */ PieceMap is_piece_considered_all; SharedConst(const Color& to_play); }; //----------------------------------------------------------------------------- /** A state of a simulation. This class contains modifiable data used in a simulation. In multi-threaded search (not yet implemented), each thread uses its own instance of this class. */ class State { public: typedef libboardgame_mcts::Node Node; typedef libboardgame_mcts::Tree Tree; /** Constructor. @param initial_variant Game variant to initialize the internal board with (may avoid unnecessary BoardConst creation for game variant that is never used) @param shared_const (@ref libboardgame_doc_storesref) */ State(Variant initial_variant, const SharedConst& shared_const); ~State() throw(); /** Play a move in the in-tree phase of the search. */ void play_in_tree(Move mv); /** Handle end of in-tree phase. */ void finish_in_tree(); /** Play a move right after expanding a node. */ void play_expanded_child(Move mv); /** Finish in-tree phase without expanding a node. */ void finish_in_tree_no_expansion(); PlayerInt get_to_play() const; void start_search(); void start_simulation(size_t n); void gen_children(Tree::NodeExpander& expander, Float init_val); void start_playout(); /** Generate and play a playout move. @return @c false if end of game was reached, and no move was played */ bool gen_and_play_playout_move(Move last_good_reply_1, Move last_good_reply_2); array evaluate_playout(); array evaluate_terminal(); /** Get number of moves in the current simulation. */ unsigned get_nu_moves() const; /** Get move in the current simulation. */ PlayerMove get_move(unsigned n) const; /** Do not update RAVE values for n'th move of the current simulation. */ bool skip_rave(Move mv) const; void dump(ostream& out) const; void write_info(ostream& out) const; private: struct MoveFeatures { /** Heuristic value of the move expressed in score points. */ Float heuristic; /** Only used on Classic and Trigon boards. */ unsigned dist_to_center; /** Does the move touch a piece of the same player? */ bool connect; }; static const bool log_simulations = false; bool m_has_connect_move; unsigned m_nu_moves_initial; Color::IntType m_nu_passes; unsigned m_max_local_value; unsigned m_max_playable_piece_size; unsigned m_max_playable_piece_size_local; /** Maximum of Features::heuristic for all moves. */ Float m_max_heuristic; unsigned m_min_dist_to_center; const SharedConst& m_shared_const; Board m_bd; const BoardConst* m_bc; Color::IntType m_nu_colors; const MoveInfo* m_move_info_array; const MoveInfoExt* m_move_info_ext_array; /** Incrementally updated lists of legal moves for both colors. Only the move list for the color to play van be used in any given position, the other color is not updated immediately after a move. */ ColorMap> m_moves; /** Temporary variable reused for efficiency. */ unique_ptr m_tmp_moves; ColorMap*> m_is_piece_considered; ArrayList m_features; /** Moves played by a color since the last update of its move list. */ ColorMap> m_new_moves; ColorMap m_is_move_list_initialized; ColorMap m_has_moves; /** Moves that are a local response to the last move. These moves occupy at least one of the corner points of the last piece played. */ MoveList m_local_moves; /** Marks moves contained in m_moves. */ ColorMap m_marker; LocalValue m_local_value; RandomGenerator m_random; /** Precomputed State::m_score_modification / BoardConst::max_score. */ Float m_score_modification_factor; /** Number of simulations of this state in the current search. */ size_t m_nu_simulations; size_t m_nu_playout_moves; size_t m_nu_last_good_reply_moves; bool m_check_symmetric_draw; bool m_check_terminate_early; bool m_is_symmetry_broken; /** Enforce all pieces to be considered for the rest of the simulation. */ bool m_force_consider_all_pieces; /** Minimum number of pieces on board to perform a symmetry check. 3 in Duo/Junior or 5 in Trigon because this is the earliest move number to break the symmetry. The early playout termination that evaluates all symmetric positions as a draw should not be used earlier because it can case bad move selection in very short searches if all moves are evaluated as draw and the search is not deep enough to find that the symmetry can be broken a few moves later. */ unsigned m_symmetry_min_nu_pieces; /** Remember attach points that were already used for move generation. Allows the incremental update of the move lists to skip attach points of newly played pieces that were already attach points of previously played pieces. */ ColorMap m_moves_added_at; /** Distance to center heuristic. */ Grid m_dist_to_center; /** Not implemented. */ State& operator=(const State&); void add_moves(Point p, Color c, const Board::PiecesLeftList& pieces_considered); void add_moves(Point p, Color c, Piece piece, unsigned adj_status); void add_starting_moves(Color c, const Board::PiecesLeftList& pieces_considered); void compute_features(); Point find_best_starting_point(Color c) const; bool gen_playout_move(Move last_good_reply_1, Move last_good_reply_2, Move& result); /** Equivalent to but faster than m_bd.get_move_info() */ const MoveInfo& get_move_info(Move move) const; /** Equivalent to but faster than m_bd.get_move_info_ext() */ const MoveInfoExt& get_move_info_ext(Move move) const; BoardConst::LocalMovesListRange get_moves(Color c, Piece piece, Point p, unsigned adj_status) const; const PieceMap& get_pieces_considered() const; void init_moves_with_local(Color c); void init_moves_without_local(Color c); void play_playout(Move mv); bool check_move(const Grid& is_forbidden, Move mv, const MoveInfo& info); bool check_move_without_local(const Grid& is_forbidden, Move mv); void update_moves(Color c); bool check_symmetry_broken(); void update_symmetry_broken(Move mv); }; inline void State::finish_in_tree() { if (log_simulations) log() << "Finish in-tree\n"; if (m_check_symmetric_draw) m_is_symmetry_broken = check_symmetry_broken(); else // Pretending that the symmetry is always broken is equivalent to // ignoring symmetric draws m_is_symmetry_broken = true; } inline PlayerMove State::get_move(unsigned n) const { auto mv = m_bd.get_move(m_nu_moves_initial + n); return PlayerMove(mv.color.to_int(), mv.move); } inline const MoveInfo& State::get_move_info(Move mv) const { LIBBOARDGAME_ASSERT(! mv.is_null()); LIBBOARDGAME_ASSERT(mv.to_int() < m_bc->get_nu_all_moves()); return *(m_move_info_array + mv.to_int()); } inline const MoveInfoExt& State::get_move_info_ext(Move mv) const { LIBBOARDGAME_ASSERT(! mv.is_null()); LIBBOARDGAME_ASSERT(mv.to_int() < m_bc->get_nu_all_moves()); return *(m_move_info_ext_array + mv.to_int()); } inline BoardConst::LocalMovesListRange State::get_moves(Color c, Piece piece, Point p, unsigned adj_status) const { BoardConst::ListIndex idx = m_shared_const.moves_range[c][p][adj_status][piece]; auto begin = &m_shared_const.move_lists[c][idx.begin]; auto end = begin + idx.size; return BoardConst::LocalMovesListRange(begin, end); } inline unsigned State::get_nu_moves() const { LIBBOARDGAME_ASSERT(m_bd.get_nu_moves() >= m_nu_moves_initial); return m_bd.get_nu_moves() - m_nu_moves_initial; } inline PlayerInt State::get_to_play() const { return m_bd.get_to_play().to_int(); } inline void State::play_in_tree(Move mv) { Color to_play = m_bd.get_to_play(); if (! mv.is_pass()) { LIBBOARDGAME_ASSERT(m_bd.is_legal(to_play, mv)); m_bd.play_nonpass(to_play, mv); m_nu_passes = 0; } else { m_bd.play_pass(to_play); ++m_nu_passes; } if (log_simulations) log() << m_bd; } inline void State::start_playout() { } inline bool State::skip_rave(Move mv) const { LIBBOARDGAME_UNUSED(mv); return false; } //----------------------------------------------------------------------------- } // namespace libpentobi_mcts #endif // LIBPENTOBI_MCTS_STATE_H pentobi-7.2/src/libpentobi_mcts/Util.cpp000066400000000000000000000111631227240712600204140ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_mcts/Util.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Util.h" #include "libboardgame_sgf/Writer.h" #include "libboardgame_sys/Memory.h" #include "libboardgame_util/Log.h" #include "libpentobi_base/BoardUtil.h" #include "libpentobi_base/SgfUtil.h" #ifdef USE_BOOST_THREAD #include #else #include #endif namespace libpentobi_mcts { namespace util { using libboardgame_mcts::ChildIterator; using libboardgame_mcts::Node; using libboardgame_mcts::Tree; using libboardgame_sgf::Writer; using libboardgame_util::log; using libpentobi_base::boardutil::write_setup; using libpentobi_base::sgf_util::get_color_id; #ifdef USE_BOOST_THREAD using boost::thread; #endif //----------------------------------------------------------------------------- namespace { void dump_tree_recurse(Writer& writer, Variant variant, const Search::Tree& tree, const Search::Node& node, Color to_play) { ostringstream comment; comment << "Visits: " << node.get_visit_count() << "\nVal: " << node.get_value() << "\nCnt: " << node.get_value_count(); writer.write_property("C", comment.str()); writer.end_node(); Color next_to_play = to_play.get_next(get_nu_colors(variant)); vector children; for (Search::ChildIterator i(tree, node); i; ++i) children.push_back(&(*i)); sort(children.begin(), children.end(), compare_node); for (const auto i : children) { writer.begin_tree(); writer.begin_node(); auto mv = i->get_move(); if (! mv.is_null()) { auto& board_const = BoardConst::get(variant); auto id = get_color_id(variant, to_play); if (! mv.is_pass()) writer.write_property(id, board_const.to_string(mv, false)); else writer.write_property(id, ""); } dump_tree_recurse(writer, variant, tree, *i, next_to_play); writer.end_tree(); } } } // namespace //----------------------------------------------------------------------------- bool compare_node(const Search::Node* n1, const Search::Node* n2) { Float count1 = n1->get_visit_count(); Float count2 = n2->get_visit_count(); if (count1 != count2) return count1 > count2; return n1->get_value() > n2->get_value(); } size_t get_memory() { size_t memory; size_t total_mem = libboardgame_sys::get_memory(); // Use a third of the system memory but not more than 768 MB if (total_mem == 0) { log("WARNING: could not determine system memory (assuming 512 MB)"); memory = 512000000; } else memory = total_mem / 3; if (memory > 768000000) memory = 768000000; log() << "Using " << memory << " of " << total_mem << " bytes\n"; return memory; } unsigned get_nu_threads() { unsigned nu_threads = thread::hardware_concurrency(); if (nu_threads == 0) { log("Could not determine the number of hardware threads"); nu_threads = 1; } // The lock-free search probably scales up to 16-32 threads, but we // haven't tested more than 4 threads, we still use single precision // float for LIBBOARDGAME_MCTS_FLOAT_TYPE (which limits the maximum number // of simulations per search) and CPUs with more than 4 cores are // currently not very common anyway. Also, the loss of playing strength // of a multi-threaded search with the same count as a single-threaded // search will become larger with many threads, so there would need to be // a correction factor in the number of simulations per level to take this // into account. if (nu_threads > 4) nu_threads = 4; return nu_threads; } void dump_tree(ostream& out, const Search& search) { Variant variant; Setup setup; search.get_root_position(variant, setup); Writer writer(out); writer.set_one_prop_per_line(true); writer.set_one_prop_value_per_line(true); writer.begin_tree(); writer.begin_node(); writer.write_property("GM", to_string(variant)); write_setup(writer, variant, setup); writer.write_property("PL", get_color_id(variant, setup.to_play)); auto& tree = search.get_tree(); dump_tree_recurse(writer, variant, tree, tree.get_root(), setup.to_play); writer.end_tree(); } //----------------------------------------------------------------------------- } // namespace util } // namespace libpentobi_mcts pentobi-7.2/src/libpentobi_mcts/Util.h000066400000000000000000000022141227240712600200560ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_mcts/Util.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_MCTS_UTIL_H #define LIBPENTOBI_MCTS_UTIL_H #include "Search.h" namespace libpentobi_mcts { namespace util { using namespace std; //----------------------------------------------------------------------------- /** Comparison function for sorting children of a node by count. Prefers nodes with higher counts. Uses the node value as a tie breaker. */ bool compare_node(const Search::Node* n1, const Search::Node* n2); /** Dump the search tree in SGF format. */ void dump_tree(ostream& out, const Search& search); /** Suggest how much memory to use for the trees depending on the total physical memory available on the system. */ size_t get_memory(); /** Suggest how many threads to use in the search depending on the current system. */ unsigned get_nu_threads(); //----------------------------------------------------------------------------- } // namespace util } // namespace libpentobi_mcts #endif // LIBPENTOBI_MCTS_UTIL_H pentobi-7.2/src/libpentobi_thumbnail/000077500000000000000000000000001227240712600200065ustar00rootroot00000000000000pentobi-7.2/src/libpentobi_thumbnail/CMakeLists.txt000066400000000000000000000005041227240712600225450ustar00rootroot00000000000000if (NOT USE_QT5) include(${QT_USE_FILE}) endif() include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) set(pentobi_thumbnail_STAT_SRCS CreateThumbnail.cpp ) add_library(pentobi_thumbnail STATIC ${pentobi_thumbnail_STAT_SRCS} ) if (USE_QT5) qt5_use_modules(pentobi_thumbnail Widgets) endif() pentobi-7.2/src/libpentobi_thumbnail/CreateThumbnail.cpp000066400000000000000000000127701227240712600235700ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_thumbnail/CreateThumbnail.cpp */ //----------------------------------------------------------------------------- #include "CreateThumbnail.h" #include #include "libboardgame_base/RectGeometry.h" #include "libboardgame_base/TrigonGeometry.h" #include "libboardgame_sgf/TreeReader.h" #include "libboardgame_sgf/Util.h" #include "libboardgame_util/StringUtil.h" #include "libpentobi_base/NodeUtil.h" #include "libpentobi_gui/BoardPainter.h" using namespace std; using libboardgame_base::RectGeometry; using libboardgame_base::TrigonGeometry; using libboardgame_sgf::Node; using libboardgame_sgf::TreeReader; using libboardgame_util::split; using libboardgame_util::trim; using libpentobi_base::Variant; using libpentobi_base::Geometry; using libpentobi_base::Grid; using libpentobi_base::PointState; //----------------------------------------------------------------------------- namespace { /** Helper function for getFinalPosition() */ void handleSetup(const char* id, Color c, const Node& node, const Geometry& geometry, Grid& pointState) { vector values = node.get_multi_property(id); for (const string& s : values) { if (trim(s).empty()) continue; vector v = split(s, ','); for (const string& p_str : v) { try { Point p = Point::from_string(p_str); if (geometry.is_onboard(p)) pointState[p] = c; } catch (const Point::InvalidString&) { continue; } } } } /** Helper function for getFinalPosition() */ void handleSetupEmpty(const Node& node, const Geometry& geometry, Grid& pointState) { vector values = node.get_multi_property("AE"); for (const string& s : values) { if (trim(s).empty()) continue; vector v = split(s, ','); for (const string& p_str : v) { try { Point p = Point::from_string(p_str); if (geometry.is_onboard(p)) pointState[p] = PointState::empty(); } catch (const Point::InvalidString&) { continue; } } } } /** Get the board state of the final position of the main variation. Avoids constructing an instance of a Tree or Game, which would do a costly initialization of BoardConst and slow down the thumbnailer unnecessarily. */ bool getFinalPosition(const Node& root, Variant& variant, Grid& pointState) { if (! parse_variant(root.get_property("GM", ""), variant)) return false; const Geometry* geometry; switch (variant) { case Variant::duo: case Variant::junior: geometry = RectGeometry::get(14, 14); break; case Variant::classic: case Variant::classic_2: geometry = RectGeometry::get(20, 20); break; case Variant::trigon: case Variant::trigon_2: geometry = TrigonGeometry::get(9); break; case Variant::trigon_3: geometry = TrigonGeometry::get(8); break; default: LIBBOARDGAME_ASSERT(false); return false; } pointState.init(*geometry, PointState::empty()); auto node = &root; while (node != 0) { if (libpentobi_base::node_util::has_setup(*node)) { handleSetup("AB", Color(0), *node, *geometry, pointState); handleSetup("AW", Color(1), *node, *geometry, pointState); handleSetup("A1", Color(0), *node, *geometry, pointState); handleSetup("A2", Color(1), *node, *geometry, pointState); handleSetup("A3", Color(2), *node, *geometry, pointState); handleSetup("A4", Color(3), *node, *geometry, pointState); handleSetupEmpty(*node, *geometry, pointState); if (node == &root) // If the file starts with a setup (e.g. a puzzle), we use this // position for the thumbnail. break; } Color c; MovePoints points; if (libpentobi_base::node_util::get_move(*node, variant, c, points)) for (Point p : points) { if (geometry->is_onboard(p)) pointState[p] = c; } node = node->get_first_child_or_null(); } return true; } } // namespace //----------------------------------------------------------------------------- bool createThumbnail(const QString& path, int width, int height, QImage& image) { TreeReader reader; reader.set_read_only_main_variation(true); reader.read(path.toLocal8Bit().constData()); auto variant = Variant::classic; // Initialize to avoid compiler warning Grid pointState; if (! getFinalPosition(reader.get_tree(), variant, pointState)) { cerr << "Not a valid Blokus SGF file\n"; return false; } BoardPainter boardPainter; QPainter painter; painter.begin(&image); boardPainter.paintEmptyBoard(painter, width, height, variant, pointState.get_geometry()); boardPainter.paintPieces(painter, pointState); painter.end(); return true; } //----------------------------------------------------------------------------- pentobi-7.2/src/libpentobi_thumbnail/CreateThumbnail.h000066400000000000000000000012171227240712600232270ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file libpentobi_thumbnail/CreateThumbnail.h */ //----------------------------------------------------------------------------- #ifndef LIBPENTOBI_THUMBNAIL_CREATE_THUMBNAIL_H #define LIBPENTOBI_THUMBNAIL_CREATE_THUMBNAIL_H class QImage; class QString; //----------------------------------------------------------------------------- bool createThumbnail(const QString& path, int width, int height, QImage& image); //----------------------------------------------------------------------------- #endif // LIBPENTOBI_THUMBNAIL_CREATE_THUMBNAIL_H pentobi-7.2/src/pentobi/000077500000000000000000000000001227240712600152545ustar00rootroot00000000000000pentobi-7.2/src/pentobi/AnalyzeGameWidget.cpp000066400000000000000000000172001227240712600213210ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/AnalyzeGameWidget.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "AnalyzeGameWidget.h" #include #include #include #include #include "Util.h" #include "libboardgame_sgf/Util.h" #include "libboardgame_util/Abort.h" #include "libboardgame_util/Log.h" #include "libpentobi_gui/Util.h" using libboardgame_sgf::util::find_root; using libboardgame_sgf::util::is_main_variation; using libboardgame_util::log; using libboardgame_util::set_abort; using libboardgame_util::ArrayList; using libpentobi_base::Board; using libpentobi_base::Tree; //----------------------------------------------------------------------------- AnalyzeGameWidget::AnalyzeGameWidget(QWidget* parent) : QWidget(parent) { setMinimumSize(240, 120); m_isInitialized = false; m_currentPosition = -1; } void AnalyzeGameWidget::cancel() { if (! m_isRunning) return; set_abort(); m_future.waitForFinished(); } void AnalyzeGameWidget::initSize() { m_borderX = width() / 50; m_borderY = height() / 20; m_maxX = width() - 2 * m_borderX; // Assume max. Board::max_nonpass_game_moves. This is not really true // because the tree may contain pass moves but such trees are only written // in search state dumps and cannot be created by the user. Using // max_game_moves would result in too small m_dX (size of the color dots) // and adapting m_dX to the number of moves in this game would result in // color dots of different sizes for different analyses, which also looks // bad. m_dX = qreal(m_maxX) / Board::max_nonpass_game_moves; m_maxY = height() - 2 * m_borderY; } void AnalyzeGameWidget::mousePressEvent(QMouseEvent* event) { if (! m_isInitialized && m_isRunning) return; unsigned moveNumber = static_cast((event->x() - m_borderX) / m_dX); if (moveNumber >= m_analyzeGame.get_nu_moves()) return; vector moves; for (unsigned i = 0; i < moveNumber; ++i) moves.push_back(m_analyzeGame.get_move(i)); emit gotoPosition(m_analyzeGame.get_variant(), moves); } void AnalyzeGameWidget::paintEvent(QPaintEvent*) { if (! m_isInitialized) return; QPainter painter(this); QFont font; font.setStyleStrategy(QFont::PreferOutline); font.setPointSizeF(0.05 * height()); QFontMetrics metrics(font); painter.translate(m_borderX, m_borderY); painter.setPen(Qt::NoPen); painter.setBrush(QColor(240, 240, 240)); painter.drawRect(0, 0, m_maxX, m_maxY); unsigned nu_moves = m_analyzeGame.get_nu_moves(); if (m_currentPosition >= 0 && static_cast(m_currentPosition) < nu_moves) { QPen pen(QColor(96, 96, 96)); pen.setStyle(Qt::DotLine); painter.setPen(pen); int x = static_cast(m_currentPosition * m_dX + 0.5 * m_dX); painter.drawLine(x, 0, x, m_maxY); } painter.setPen(QColor(32, 32, 32)); painter.drawLine(0, 0, m_maxX, 0); painter.drawLine(0, m_maxY, m_maxX, m_maxY); painter.setRenderHint(QPainter::Antialiasing, true); QString labelWin = tr("Win"); QRect boundingRectWin = metrics.boundingRect(labelWin); painter.drawText(QRect(0, 0, boundingRectWin.width(), boundingRectWin.height()), Qt::AlignLeft | Qt::AlignTop | Qt::TextDontClip, labelWin); QString labelLoss = tr("Loss"); QRect boundingRectLoss = metrics.boundingRect(labelLoss); painter.drawText(QRect(0, m_maxY - boundingRectLoss.height(), boundingRectLoss.width(), boundingRectLoss.height()), Qt::AlignLeft | Qt::AlignBottom | Qt::TextDontClip, labelLoss); painter.setRenderHint(QPainter::Antialiasing, false); painter.setPen(QColor(128, 128, 128)); painter.drawLine(0, m_maxY / 2, m_maxX, m_maxY / 2); painter.setRenderHint(QPainter::Antialiasing, true); for (unsigned i = 0; i < nu_moves; ++i) { if (! m_analyzeGame.has_value(i)) continue; double value = m_analyzeGame.get_value(i); auto color = Util::getPaintColor(m_analyzeGame.get_variant(), m_analyzeGame.get_move(i).color); painter.setPen(Qt::NoPen); painter.setBrush(color); painter.drawEllipse(QPointF((i + 0.5) * m_dX, (1 - value) * m_maxY), 0.5 * m_dX, 0.5 * m_dX); } } void AnalyzeGameWidget::resizeEvent(QResizeEvent*) { if (! m_isInitialized) return; initSize(); } void AnalyzeGameWidget::setCurrentPosition(const Game& game, const Node& node) { update(); m_currentPosition = -1; if (is_main_variation(node)) { ArrayList moves; auto& tree = game.get_tree(); auto current = &find_root(node); while (current != 0) { auto mv = tree.get_move(*current); if (! mv.is_null() && moves.size() < Board::max_game_moves) moves.push_back(mv); if (current == &node) break; current = current->get_first_child_or_null(); } if (moves.size() <= m_analyzeGame.get_nu_moves()) { for (unsigned i = 0; i < moves.size(); ++i) if (moves[i] != m_analyzeGame.get_move(i)) return; m_currentPosition = moves.size(); } } } void AnalyzeGameWidget::showProgress(int progress) { m_progressDialog->setValue(progress); // Repaint the window with the current status of the analysis update(); } QSize AnalyzeGameWidget::sizeHint() const { return QSize(800, 240); } void AnalyzeGameWidget::start(const Game& game, Search& search, size_t nuSimulations) { m_isInitialized = true; m_game = &game; m_search = &search; m_nuSimulations = nuSimulations; initSize(); m_progressDialog = new QProgressDialog(this); m_progressDialog->setWindowModality(Qt::WindowModal); m_progressDialog->setWindowFlags(m_progressDialog->windowFlags() & ~Qt::WindowContextHelpButtonHint); m_progressDialog->setLabel(new QLabel(tr("Running game analysis..."), this)); Util::setNoTitle(*m_progressDialog); m_progressDialog->setMinimumDuration(0); connect(m_progressDialog, SIGNAL(canceled()), SLOT(cancel())); m_progressDialog->show(); m_isRunning = true; m_future = QtConcurrent::run(this, &AnalyzeGameWidget::threadFunction); } void AnalyzeGameWidget::threadFunction() { // This function and the progress callback are not called from the GUI // thread. So we need to invoke showProgress() with invokeMethod(). auto progressCallback = [&](unsigned movesAnalyzed, unsigned totalMoves) { if (totalMoves == 0) return; int progress = 100 * movesAnalyzed / totalMoves; QMetaObject::invokeMethod(this, "showProgress", Qt::QueuedConnection, Q_ARG(int, progress)); }; m_analyzeGame.run(*m_game, *m_search, m_nuSimulations, progressCallback); QMetaObject::invokeMethod(this, "showProgress", Qt::QueuedConnection, Q_ARG(int, 100)); m_isRunning = false; emit finished(); } //----------------------------------------------------------------------------- pentobi-7.2/src/pentobi/AnalyzeGameWidget.h000066400000000000000000000056561227240712600210020ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/AnalyzeGameWidget.h */ //----------------------------------------------------------------------------- #ifndef PENTOBI_ANALYZE_GAME_WIDGET_H #define PENTOBI_ANALYZE_GAME_WIDGET_H // Needed in the header because moc_*.cxx does not include config.h #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "libpentobi_mcts/AnalyzeGame.h" class QProgressDialog; using namespace std; using libpentobi_base::ColorMove; using libpentobi_base::Game; using libpentobi_base::Variant; using libpentobi_base::Node; using libpentobi_mcts::AnalyzeGame; using libpentobi_mcts::Search; //----------------------------------------------------------------------------- class AnalyzeGameWidget : public QWidget { Q_OBJECT public slots: /** Cancel a running analysis. The function waits for the analysis to finish. The finished() signal will still be invoked. */ void cancel(); public: AnalyzeGameWidget(QWidget* parent); /** Start an analysis. This function will return after the analysis has started but the window will be protected by a modal cancelable progress dialog. Don't modify the game or use the search from a different thread until the signal finished() was emitted. This will walk through every game position in the main variation and use the search to evaluate positions. During the analysis, the parent window is protected with a modal progress dialog. */ void start(const Game& game, Search& search, size_t nuSimulations); /** Mark the current position. Will clear the current position if the target node is not in the main variation or does not correspond to a move in the move sequence when the analysis was done. */ void setCurrentPosition(const Game& game, const Node& node); QSize sizeHint() const; signals: /** Tells that the analysis has finished. */ void finished(); void gotoPosition(Variant variant, const vector& moves); protected: void mousePressEvent(QMouseEvent* event) override; void paintEvent(QPaintEvent* event) override; void resizeEvent(QResizeEvent* event) override; private slots: void showProgress(int progress); private: bool m_isInitialized; bool m_isRunning; const Game* m_game; Search* m_search; size_t m_nuSimulations; AnalyzeGame m_analyzeGame; QProgressDialog* m_progressDialog; QFuture m_future; int m_borderX; int m_borderY; qreal m_dX; int m_maxX; int m_maxY; /** Current position that will be marked or -1 if no position is marked. */ int m_currentPosition; void initSize(); void threadFunction(); }; //----------------------------------------------------------------------------- #endif // PENTOBI_ANALYZE_GAME_WIDGET_H pentobi-7.2/src/pentobi/AnalyzeGameWindow.cpp000066400000000000000000000020121227240712600213400ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/AnalyzeGameWindow.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "AnalyzeGameWindow.h" #include #include //----------------------------------------------------------------------------- AnalyzeGameWindow::AnalyzeGameWindow(QWidget* parent) : QDialog(parent) { setWindowTitle(tr("Game Analysis")); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); auto layout = new QVBoxLayout(); setLayout(layout); analyzeGameWidget = new AnalyzeGameWidget(this); layout->addWidget(analyzeGameWidget); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); layout->addWidget(buttonBox); connect(buttonBox, SIGNAL(rejected()), SLOT(reject())); buttonBox->setFocus(); } //----------------------------------------------------------------------------- pentobi-7.2/src/pentobi/AnalyzeGameWindow.h000066400000000000000000000015151227240712600210140ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/AnalyzeGameWindow.h */ //----------------------------------------------------------------------------- #ifndef PENTOBI_ANALYZE_GAME_WINDOW_H #define PENTOBI_ANALYZE_GAME_WINDOW_H // Needed in the header because moc_*.cxx does not include config.h #ifdef HAVE_CONFIG_H #include #endif #include #include #include "AnalyzeGameWidget.h" using namespace std; //----------------------------------------------------------------------------- class AnalyzeGameWindow : public QDialog { Q_OBJECT public: AnalyzeGameWidget* analyzeGameWidget; AnalyzeGameWindow(QWidget* parent); }; //----------------------------------------------------------------------------- #endif // PENTOBI_ANALYZE_GAME_WINDOW_H pentobi-7.2/src/pentobi/AnalyzeSpeedDialog.cpp000066400000000000000000000017411227240712600214670ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/AnalyzeSpeedDialog.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "AnalyzeSpeedDialog.h" //----------------------------------------------------------------------------- AnalyzeSpeedDialog::AnalyzeSpeedDialog(QWidget* parent, const QString& title) : QInputDialog(parent), m_speedValue(0) { m_items << tr("Fast") << tr("Normal") << tr("Slow"); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowTitle(title); setLabelText(tr("Analysis speed:")); setInputMode(QInputDialog::TextInput); setComboBoxItems(m_items); setComboBoxEditable(false); } void AnalyzeSpeedDialog::accept() { m_speedValue = m_items.indexOf(textValue()); QDialog::accept(); } //----------------------------------------------------------------------------- pentobi-7.2/src/pentobi/AnalyzeSpeedDialog.h000066400000000000000000000017721227240712600211400ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/AnalyzeSpeedDialog.h */ //----------------------------------------------------------------------------- #ifndef PENTOBI_ANALYZE_SPEED_DIALOG_H #define PENTOBI_ANALYZE_SPEED_DIALOG_H // Needed in the header because moc_*.cxx does not include config.h #ifdef HAVE_CONFIG_H #include #endif #include //----------------------------------------------------------------------------- class AnalyzeSpeedDialog : public QInputDialog { Q_OBJECT public: AnalyzeSpeedDialog(QWidget* parent, const QString& title); /** Get return value if dialog was accepted. 0 = fast, 1 = normal, 2 = slow */ int getSpeedValue() { return m_speedValue; } public slots: void accept(); private: int m_speedValue; QStringList m_items; }; //----------------------------------------------------------------------------- #endif // PENTOBI_ANALYZE_SPEED_DIALOG_H pentobi-7.2/src/pentobi/Application.cpp000066400000000000000000000020231227240712600202200ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/Application.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Application.h" #include "ShowMessage.h" #include "libboardgame_sys/Compiler.h" using namespace std; using libboardgame_sys::get_type_name; //----------------------------------------------------------------------------- Application::Application(int& argc, char** argv) : QApplication(argc, argv) { } bool Application::notify(QObject* receiver, QEvent* event) { try { return QApplication::notify(receiver, event); } catch (const exception& e) { string detailedText = get_type_name(e) + ": " + e.what(); showFatal(QString::fromLocal8Bit(detailedText.c_str())); } catch (...) { showFatal("Unknown exception"); } return false; } //----------------------------------------------------------------------------- pentobi-7.2/src/pentobi/Application.h000066400000000000000000000015431227240712600176730ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/Application.h */ //----------------------------------------------------------------------------- #ifndef PENTOBI_APPLICATION_H #define PENTOBI_APPLICATION_H // Needed in the header because moc_*.cxx does not include config.h #ifdef HAVE_CONFIG_H #include #endif #include //----------------------------------------------------------------------------- class Application : public QApplication { Q_OBJECT public: Application(int& argc, char** argv); /** Reimplemented from QApplication::notify(). Catches exceptions and shows an error message. */ bool notify(QObject* receiver, QEvent* event); }; //----------------------------------------------------------------------------- #endif // PENTOBI_APPLICATION_H pentobi-7.2/src/pentobi/CMakeLists.txt000066400000000000000000000055331227240712600200220ustar00rootroot00000000000000set(CMAKE_AUTOMOC TRUE) if (NOT USE_QT5) include(${QT_USE_FILE}) endif() include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) set(pentobi_SRCS AnalyzeGameWidget.cpp AnalyzeGameWindow.cpp AnalyzeSpeedDialog.cpp Application.cpp ExportImage.cpp MainWindow.cpp Main.cpp RatedGamesList.cpp RatingDialog.cpp RatingHistory.cpp RatingGraph.cpp ShowMessage.cpp Util.cpp ) set(pentobi_RCS resources.qrc ) set(pentobi_TS pentobi.ts pentobi_de.ts pentobi_en_CA.ts pentobi_en_GB.ts ) link_directories(${Boost_LIBRARY_DIRS}) if (USE_QT5) qt5_add_resources(pentobi_RC_SRCS ${pentobi_RCS}) else() qt4_add_resources(pentobi_RC_SRCS ${pentobi_RCS}) endif() # We don't use qt4_add_translation because it doesn't use option -removeidentical # with lrelease, which causes larger sizes of the generated qm files if (USE_QT5) set(LRELEASE_EXECUTABLE ${Qt5_LRELEASE_EXECUTABLE}) else() set(LRELEASE_EXECUTABLE ${QT_LRELEASE_EXECUTABLE}) endif() foreach(ts ${pentobi_TS}) get_filename_component(qm ${ts} NAME_WE) set(qm "${CMAKE_CURRENT_BINARY_DIR}/${qm}.qm") add_custom_command(OUTPUT ${qm} COMMAND ${LRELEASE_EXECUTABLE} ARGS -removeidentical -nounfinished ${CMAKE_CURRENT_SOURCE_DIR}/${ts} -qm ${qm} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${ts} VERBATIM ) set(pentobi_QM_SRCS ${pentobi_QM_SRCS} ${qm}) endforeach() if (MINGW OR CYGWIN) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/icon.o COMMAND windres.exe -I${CMAKE_CURRENT_SOURCE_DIR} -i${CMAKE_CURRENT_SOURCE_DIR}/icon.rc -o ${CMAKE_CURRENT_BINARY_DIR}/icon.o) set(pentobi_SRCS ${pentobi_SRCS} ${CMAKE_CURRENT_BINARY_DIR}/icon.o) else() set(pentobi_SRCS ${pentobi_SRCS} icon.rc) endif() add_executable(pentobi WIN32 ${pentobi_SRCS} ${pentobi_MOC_SRCS} ${pentobi_QM_SRCS} ${pentobi_RC_SRCS} ) if (USE_QT5) qt5_use_modules(pentobi Widgets Concurrent) endif() target_link_libraries(pentobi pentobi_gui ${QT_LIBRARIES} pentobi_mcts pentobi_base boardgame_base boardgame_sgf boardgame_util boardgame_sys ${Boost_THREAD_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ) if (WIN32) target_link_libraries(pentobi ${QT_QTMAIN_LIBRARY} ) endif() install(TARGETS pentobi DESTINATION ${CMAKE_INSTALL_BINDIR}) # Install translation files. If you change the destination, you need to # update the default for PENTOBI_TRANSLATIONS in the main CMakeLists.txt install(FILES ${pentobi_QM_SRCS} DESTINATION ${CMAKE_INSTALL_DATADIR}/pentobi/translations) install(DIRECTORY manual DESTINATION ${CMAKE_INSTALL_DOCDIR} FILES_MATCHING PATTERN "*.css" PATTERN "*.html" PATTERN "*.png") if(MSVC) configure_file(pentobi.conf.in Debug/pentobi.conf @ONLY) configure_file(pentobi.conf.in Release/pentobi.conf @ONLY) else() configure_file(pentobi.conf.in pentobi.conf @ONLY) endif() pentobi-7.2/src/pentobi/ExportImage.cpp000066400000000000000000000043241227240712600202070ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/ExportImage.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "ExportImage.h" #include #include #include #include #include #include "ShowMessage.h" #include "libpentobi_gui/BoardPainter.h" //----------------------------------------------------------------------------- void exportImage(QWidget* parent, const Board& bd, bool coordinates, const Grid& labels) { QSettings settings; auto size = settings.value("export_image_size", 420).toInt(); QInputDialog dialog(parent); dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); dialog.setWindowTitle(qApp->translate("ExportImage", "Export Image")); dialog.setLabelText(qApp->translate("ExportImage", "Image size:")); dialog.setInputMode(QInputDialog::IntInput); dialog.setIntRange(0, 2147483647); dialog.setIntStep(40); dialog.setIntValue(size); if (! dialog.exec()) return; size = dialog.intValue(); settings.setValue("export_image_size", size); BoardPainter boardPainter; boardPainter.setCoordinates(coordinates); boardPainter.setCoordinateColor(QColor(100, 100, 100)); QImage image(size, size, QImage::Format_ARGB32); image.fill(Qt::transparent); QPainter painter; painter.begin(&image); if (coordinates) painter.fillRect(0, 0, size, size, QColor(216, 216, 216)); boardPainter.paintEmptyBoard(painter, size, size, bd.get_variant(), bd.get_geometry()); boardPainter.paintPieces(painter, bd.get_grid(), &labels); painter.end(); QString file; while (true) { file = QFileDialog::getSaveFileName(parent, file); if (file.isEmpty()) break; QImageWriter writer(file); if (writer.write(image)) break; else showError(parent, writer.errorString()); } } //----------------------------------------------------------------------------- pentobi-7.2/src/pentobi/ExportImage.h000066400000000000000000000013431227240712600176520ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/ExportImage.h */ //----------------------------------------------------------------------------- #ifndef PENTOBI_EXPORT_IMAGE_H #define PENTOBI_EXPORT_IMAGE_H class QString; class QWidget; namespace libpentobi_base { class Board; template class Grid; } using libpentobi_base::Board; using libpentobi_base::Grid; //----------------------------------------------------------------------------- void exportImage(QWidget* parent, const Board& bd, bool coordinates, const Grid& labels); //----------------------------------------------------------------------------- #endif // PENTOBI_EXPORT_IMAGE_H pentobi-7.2/src/pentobi/Main.cpp000066400000000000000000000144321227240712600166500ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/Main.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "Application.h" #include "MainWindow.h" #include "ShowMessage.h" #include "libboardgame_util/Options.h" #include "libboardgame_sys/Compiler.h" #ifdef Q_WS_WIN #include #include #include #include #endif using libboardgame_util::set_log_null; using libboardgame_util::OptionError; using libboardgame_util::Options; using libboardgame_util::RandomGenerator; using libboardgame_sys::get_type_name; //----------------------------------------------------------------------------- namespace { #ifdef Q_WS_WIN /** @todo: Move to an extra file to encapsulate the inclusion of , which does some ugly stuff, like defining a macro named max that breaks standard headers. */ void redirectStdErr() { CONSOLE_SCREEN_BUFFER_INFO info; AllocConsole(); GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info); info.dwSize.Y = 500; SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), info.dwSize); long stdErrHandle = (long)GetStdHandle(STD_ERROR_HANDLE); int conHandle = _open_osfhandle(stdErrHandle, _O_TEXT); auto f = _fdopen(conHandle, "w"); *stderr = *f; setvbuf(stderr, NULL, _IONBF, 0); ios::sync_with_stdio(); } #endif } // namespace //----------------------------------------------------------------------------- int main(int argc, char* argv[]) { Q_INIT_RESOURCE(libpentobi_gui_resources); QCoreApplication::setOrganizationName("Pentobi"); QCoreApplication::setApplicationName("Pentobi"); Application app(argc, argv); try { // Allow the user to override installation paths with a config file in // the directory of the executable to test it without installation QString manualDir; QString booksDir; QString translationsPentobiDir; QString translationsLibPentobiGuiDir; QString appDir = QCoreApplication::applicationDirPath(); #ifdef PENTOBI_MANUAL_DIR manualDir = PENTOBI_MANUAL_DIR; #endif if (manualDir.isEmpty()) manualDir = appDir + "/manual"; #ifdef PENTOBI_BOOKS_DIR booksDir = PENTOBI_BOOKS_DIR; #endif if (booksDir.isEmpty()) booksDir = appDir + "/books"; #ifdef PENTOBI_TRANSLATIONS translationsPentobiDir = PENTOBI_TRANSLATIONS; translationsLibPentobiGuiDir = PENTOBI_TRANSLATIONS; #endif if (translationsPentobiDir.isEmpty()) translationsPentobiDir = appDir + "/translations"; if (translationsLibPentobiGuiDir.isEmpty()) translationsLibPentobiGuiDir = appDir + "/translations"; QString overrideConfigFile = appDir + "/pentobi.conf"; if (QFileInfo(overrideConfigFile).exists()) { QSettings settings(overrideConfigFile, QSettings::IniFormat); manualDir = settings.value("ManualDir", manualDir).toString(); booksDir = settings.value("BooksDir", booksDir).toString(); translationsPentobiDir = settings.value("TranslationsPentobiDir", translationsPentobiDir).toString(); translationsLibPentobiGuiDir = settings.value("TranslationsLibPentobiGuiDir", translationsLibPentobiGuiDir).toString(); } QTranslator qtTranslator; QString qtTranslationPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath); QString locale = QLocale::system().name(); qtTranslator.load("qt_" + locale, qtTranslationPath); app.installTranslator(&qtTranslator); QTranslator libPentobiGuiTranslator; libPentobiGuiTranslator.load("libpentobi_gui_" + locale, translationsLibPentobiGuiDir); app.installTranslator(&libPentobiGuiTranslator); QTranslator pentobiTranslator; pentobiTranslator.load("pentobi_" + locale, translationsPentobiDir); app.installTranslator(&pentobiTranslator); vector specs; specs.push_back("memory:"); specs.push_back("nobook"); specs.push_back("seed|r:"); specs.push_back("threads:"); specs.push_back("verbose"); Options opt(argc, argv, specs); size_t memory = 0; if (opt.contains("memory")) { memory = opt.get("memory"); if (memory == 0) throw OptionError("Value for memory must be greater zero."); } unsigned threads = 0; if (opt.contains("threads")) { threads = opt.get("threads"); if (threads == 0) throw OptionError("Number of threads must be greater zero."); } if (! opt.contains("verbose")) set_log_null(); #ifdef Q_WS_WIN if (opt.contains("verbose")) redirectStdErr(); #endif if (opt.contains("seed")) RandomGenerator::set_global_seed(opt.get("seed")); bool noBook = opt.contains("nobook"); QString initialFile; auto& args = opt.get_args(); if (! args.empty()) initialFile = args[0].c_str(); MainWindow mainWindow(initialFile, manualDir, booksDir, noBook, threads, memory); if (opt.contains("seed")) mainWindow.setDeterministic(); mainWindow.show(); return app.exec(); } catch (const OptionError& e) { QMessageBox::critical(nullptr, "Pentobi", "Invalid command line option:\n\n" + QString::fromLocal8Bit(e.what())); return 1; } catch (const exception& e) { string detailedText = get_type_name(e) + ": " + e.what(); showFatal(QString::fromLocal8Bit(detailedText.c_str())); return 1; } catch (...) { showFatal("Unknown exception"); return 1; } } //----------------------------------------------------------------------------- pentobi-7.2/src/pentobi/MainWindow.cpp000066400000000000000000003562471227240712600200550ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/MainWindow.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "MainWindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AnalyzeGameWindow.h" #include "AnalyzeSpeedDialog.h" #include "ExportImage.h" #include "RatingDialog.h" #include "ShowMessage.h" #include "Util.h" #include "libboardgame_sgf/TreeReader.h" #include "libboardgame_sgf/Util.h" #include "libboardgame_util/Assert.h" #include "libpentobi_base/TreeUtil.h" #include "libpentobi_base/TreeWriter.h" #include "libpentobi_gui/ComputerColorDialog.h" #include "libpentobi_gui/GameInfoDialog.h" #include "libpentobi_gui/GuiBoard.h" #include "libpentobi_gui/GuiBoardUtil.h" #include "libpentobi_gui/HelpWindow.h" #include "libpentobi_gui/InitialRatingDialog.h" #include "libpentobi_gui/LeaveFullscreenButton.h" #include "libpentobi_gui/OrientationDisplay.h" #include "libpentobi_gui/PieceSelector.h" #include "libpentobi_gui/SameHeightLayout.h" #include "libpentobi_gui/ScoreDisplay.h" #include "libpentobi_gui/Util.h" #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) #include #else #include #endif using namespace std; using Util::getPlayerString; using libboardgame_sgf::ChildIterator; using libboardgame_sgf::InvalidTree; using libboardgame_sgf::TreeReader; using libboardgame_sgf::util::back_to_main_variation; using libboardgame_sgf::util::beginning_of_branch; using libboardgame_sgf::util::find_next_comment; using libboardgame_sgf::util::get_last_node; using libboardgame_sgf::util::get_variation_string; using libboardgame_sgf::util::has_comment; using libboardgame_sgf::util::has_earlier_variation; using libboardgame_sgf::util::is_main_variation; using libboardgame_util::clear_abort; using libboardgame_util::get_abort; using libboardgame_util::log; using libboardgame_util::set_abort; using libboardgame_util::trim_right; using libboardgame_util::ArrayList; using libpentobi_base::ColorIterator; using libpentobi_base::MoveInfo; using libpentobi_base::MoveInfoExt; using libpentobi_base::MoveList; using libpentobi_base::PieceInfo; using libpentobi_base::Tree; using libpentobi_base::TreeWriter; using libpentobi_base::tree_util::get_move_number; using libpentobi_base::tree_util::get_moves_left; using libpentobi_mcts::Search; //----------------------------------------------------------------------------- namespace { QToolButton* createOBoxToolButton(QAction* action) { auto button = new QToolButton(); button->setDefaultAction(action); button->setAutoRaise(true); button->setFocusPolicy(Qt::NoFocus); return button; } /** Return auto-save file name as a native path name. */ QString getAutoSaveFile() { return Util::getDataDir() + QDir::separator() + "autosave.blksgf"; } /** Determine the current color at the current node in the game. - If the color was explicitely set with a setup property, it will be used. - Otherwise, if the current node has children all with moves of the same color, this color will be used. This is because some trees like search dumps contain pass moves, so using the effective color to play is not appropriate there. - Otherwise, the effective color to play will be used. (see Game::get_effective_to_play()) */ Color getCurrentColor(const Game& game) { auto& tree = game.get_tree(); auto node = &game.get_current(); Color c; while (node != nullptr && ! tree.has_move(*node)) { if (libpentobi_base::node_util::get_player(*node, c)) return c; node = node->get_parent_or_null(); } bool all_same_color = true; bool is_first = true; for (ChildIterator i(game.get_current()); i; ++i) { if (! tree.has_move(*i)) continue; if (is_first) { c = tree.get_move(*i).color; is_first = false; continue; } if (tree.get_move(*i).color != c) { all_same_color = false; break; } } if (! is_first && all_same_color) return c; return game.get_effective_to_play(); } bool hasCurrentVariationOtherMoves(const Tree& tree, const Node& current) { auto node = current.get_parent_or_null(); while (node != nullptr) { if (! tree.get_move(*node).is_null()) return true; node = node->get_parent_or_null(); } node = current.get_first_child_or_null(); while (node != nullptr) { if (! tree.get_move(*node).is_null()) return true; node = node->get_first_child_or_null(); } return false; } void setIcon(QAction* action, const QString& name) { QIcon icon(QString(":/pentobi/icons/%1.png").arg(name)); QString file16 = QString(":/pentobi/icons/%1-16.png").arg(name); if (QFile::exists(file16)) icon.addFile(file16, QSize(16, 16)); action->setIcon(icon); } /** Simple heuristic that prefers moves with more piece points, more attach points and less adjacent points. Used for sorting the list used in Find Move. */ float getMoveHeuristic(const Board& bd, Move mv) { auto& info = bd.get_move_info(mv); auto& info_ext = bd.get_move_info_ext(mv); return static_cast((1000 * info.size() + 10 * info_ext.size_attach_points - info_ext.size_adj_points)); } /** Comparison for sorting move list in Find Move. */ bool isMoveBetter(const Board* bd, Move mv1, Move mv2) { return getMoveHeuristic(*bd, mv1) > getMoveHeuristic(*bd, mv2); } } // namespace //----------------------------------------------------------------------------- MainWindow::MainWindow(const QString& initialFile, const QString& manualDir, const QString& booksDir, bool noBook, unsigned nu_threads, size_t memory) : m_isGenMoveRunning(false), m_isAnalyzeRunning(false), m_autoPlay(true), m_ignoreCommentTextChanged(false), m_genMoveId(0), m_lastComputerMovesBegin(0), m_lastComputerMovesEnd(0), m_manualDir(manualDir), m_helpWindow(nullptr), m_ratingDialog(nullptr), m_analyzeGameWindow(nullptr), m_legalMoves(new MoveList()), m_leaveFullscreenButton(nullptr) { Util::initDataDir(); QSettings settings; m_level = settings.value("level", 3).toInt(); if (m_level < 1 || m_level > maxLevel) m_level = 3; auto variantString = settings.value("variant", "").toString(); Variant variant; if (! parse_variant_id(variantString.toLocal8Bit().constData(), variant)) variant = Variant::classic; m_game.reset(new Game(variant)); m_history.reset(new RatingHistory(variant)); createActions(); setCentralWidget(createCentralWidget()); initPieceSelectors(); m_moveNumber = new QLabel(); statusBar()->addPermanentWidget(m_moveNumber); m_setupModeLabel = new QLabel(tr("Setup mode")); statusBar()->addWidget(m_setupModeLabel); m_setupModeLabel->hide(); m_ratedGameLabel = new QLabel(tr("Rated game")); statusBar()->addWidget(m_ratedGameLabel); m_ratedGameLabel->hide(); initGame(); m_player.reset(new Player(variant, booksDir.toLocal8Bit().constData(), nu_threads, memory)); m_player->get_search().set_callback(bind(&MainWindow::searchCallback, this, placeholders::_1, placeholders::_2)); m_player->set_use_book(! noBook); createToolBar(); connect(&m_genMoveWatcher, SIGNAL(finished()), SLOT(genMoveFinished())); connect(m_guiBoard, SIGNAL(play(Color, Move)), SLOT(placePiece(Color, Move))); connect(m_guiBoard, SIGNAL(pointClicked(Point)), SLOT(pointClicked(Point))); connect(m_actionMoveSelectedPieceLeft, SIGNAL(triggered()), m_guiBoard, SLOT(moveSelectedPieceLeft())); connect(m_actionMoveSelectedPieceRight, SIGNAL(triggered()), m_guiBoard, SLOT(moveSelectedPieceRight())); connect(m_actionMoveSelectedPieceUp, SIGNAL(triggered()), m_guiBoard, SLOT(moveSelectedPieceUp())); connect(m_actionMoveSelectedPieceDown, SIGNAL(triggered()), m_guiBoard, SLOT(moveSelectedPieceDown())); connect(m_actionPlaceSelectedPiece, SIGNAL(triggered()), m_guiBoard, SLOT(placeSelectedPiece())); createMenu(); qApp->installEventFilter(this); updateRecentFiles(); auto moveNumbers = settings.value("move_numbers", "last").toString(); if (moveNumbers == "last") m_actionMoveNumbersLast->setChecked(true); else if (moveNumbers == "all") m_actionMoveNumbersAll->setChecked(true); else m_actionMoveNumbersNone->setChecked(true); auto coordinates = settings.value("coordinates", false).toBool(); m_guiBoard->setCoordinates(coordinates); m_actionCoordinates->setChecked(coordinates); auto showToolbar = settings.value("toolbar", true).toBool(); m_toolBar->setVisible(showToolbar); m_menuToolBarText->setEnabled(showToolbar); m_actionShowToolbar->setChecked(showToolbar); auto showVariations = settings.value("show_variations", true).toBool(); m_actionShowVariations->setChecked(showVariations); initVariantActions(); QIcon icon; icon.addFile(":/pentobi/icons/pentobi.png"); icon.addFile(":/pentobi/icons/pentobi-16.png"); icon.addFile(":/pentobi/icons/pentobi-32.png"); setWindowIcon(icon); bool centerOnScreen = false; QRect screenGeometry = QApplication::desktop()->screenGeometry(); if (restoreGeometry(settings.value("geometry").toByteArray())) { // We don't save the geometry anymore if it is fullscreen, but this can // happen if the geometry was saved by a previous version of Pentobi if (isFullScreen()) showNormal(); if (! screenGeometry.contains(geometry())) { if (width() > screenGeometry.width() || height() > screenGeometry.height()) adjustSize(); centerOnScreen = true; } } else { adjustSize(); centerOnScreen = true; } if (centerOnScreen) { int x = (screenGeometry.width() - width()) / 2; int y = (screenGeometry.height() - height()) / 2; move(x, y); } auto showComment = settings.value("show_comment", false).toBool(); m_comment->setVisible(showComment); if (showComment) m_splitter->restoreState( settings.value("splitter_state").toByteArray()); m_actionShowComment->setChecked(showComment); updateWindow(true); clearFile(); if (! initialFile.isEmpty()) { if (open(initialFile)) rememberFile(initialFile); } else { QString autoSaveFile = getAutoSaveFile(); if (QFile(autoSaveFile).exists()) { open(autoSaveFile, true); m_isAutoSaveLoaded = true; deleteAutoSaveFile(); m_gameFinished = getBoard().is_game_over(); if (settings.value("autosave_rated", false).toBool()) { auto variant = getVariant(); unsigned ratedGameColor = settings.value("autosave_rated_color", 0).toUInt(); if (ratedGameColor < get_nu_colors(variant)) { m_ratedGameColor = Color(ratedGameColor); m_computerColors.fill(true); auto& bd = getBoard(); for (ColorIterator i(bd.get_nu_colors()); i; ++i) if (bd.is_same_player(*i, m_ratedGameColor)) m_computerColors[*i] = false; setRated(true); updateWindow(true); show(); showInfo(tr("Continuing unfinished rated game."), tr("You play %1 in this game.") .arg(getPlayerString(variant, m_ratedGameColor))); m_autoPlay = true; checkComputerMove(); } } else updateWindow(true); } } } void MainWindow::about() { QMessageBox::about(this, tr("About Pentobi"), "" "

" + tr("Pentobi") + "

" "

" + tr("Version %1").arg(getVersion()) + "

" "

" + tr("Pentobi is a computer opponent for the board game Blokus.") + "

" + tr("Copyright © 2011–2014 Markus Enzenberger") + + "
" + "http://pentobi.sourceforge.net" "

"); } void MainWindow::analyzeGame() { if (! is_main_variation(m_game->get_current())) { showInfo(tr("Game analysis is only possible in the main variation.")); return; } AnalyzeSpeedDialog dialog(this, tr("Analyze Game")); if (! dialog.exec()) return; int speed = dialog.getSpeedValue(); cancelThread(); if (m_analyzeGameWindow != nullptr) delete m_analyzeGameWindow; m_analyzeGameWindow = new AnalyzeGameWindow(this); // Make sure all action shortcuts work when the analyze dialog has the // focus apart from m_actionLeaveFullscreen because the Esc key is used // to close the dialog m_analyzeGameWindow->addActions(actions()); m_analyzeGameWindow->removeAction(m_actionLeaveFullscreen); m_analyzeGameWindow->show(); m_isAnalyzeRunning = true; connect(m_analyzeGameWindow->analyzeGameWidget, SIGNAL(finished()), SLOT(analyzeGameFinished())); connect(m_analyzeGameWindow->analyzeGameWidget, SIGNAL(gotoPosition(Variant,const vector&)), SLOT(gotoPosition(Variant,const vector&))); size_t nuSimulations; switch (speed) { case 0: nuSimulations = 5000; break; case 1: nuSimulations = 20000; break; default: nuSimulations = 80000; } m_analyzeGameWindow->analyzeGameWidget->start(*m_game, m_player->get_search(), nuSimulations); } void MainWindow::analyzeGameFinished() { m_analyzeGameWindow->analyzeGameWidget ->setCurrentPosition(*m_game, m_game->get_current()); m_isAnalyzeRunning = false; } /** Call to Player::genmove() that runs in a different thread. */ MainWindow::GenMoveResult MainWindow::asyncGenMove(Color c, int genMoveId, bool playSingleMove) { GenMoveResult result; result.playSingleMove = playSingleMove; result.color = c; result.genMoveId = genMoveId; result.move = m_player->genmove(getBoard(), c); return result; } void MainWindow::badMove(bool checked) { if (! checked) return; m_game->set_bad_move(); updateWindow(false); } void MainWindow::backward() { auto& node = m_game->get_current(); if (! node.has_parent()) return; gotoNode(node.get_parent()); } void MainWindow::backward10() { auto& tree = m_game->get_tree(); auto node = &m_game->get_current(); unsigned n = 0; while (n < 10) { if (tree.has_move(*node)) ++n; auto parent = node->get_parent_or_null(); if (parent == nullptr) break; node = parent; } gotoNode(*node); } void MainWindow::backToMainVariation() { gotoNode(back_to_main_variation(m_game->get_current())); } void MainWindow::beginning() { gotoNode(m_game->get_root()); } void MainWindow::beginningOfBranch() { gotoNode(beginning_of_branch(m_game->get_current())); } void MainWindow::cancelThread() { if (m_isAnalyzeRunning) { // This should never happen because AnalyzeGameWindow protects the // parent with a modal progress dialog while it is running. However, // due to bugs in Unity 2D (tested with Ubuntu 11.04 and 11.10), the // global menu can still trigger menu item events. m_analyzeGameWindow->analyzeGameWidget->cancel(); } if (! m_isGenMoveRunning) return; // After waitForFinished() returns, we can be sure that the move generation // is no longer running, but we will still receive the finished event. // Increasing m_genMoveId will make genMoveFinished() ignore the event. ++m_genMoveId; m_genMoveInterrupted = true; set_abort(); m_genMoveWatcher.waitForFinished(); m_isGenMoveRunning = false; clearStatus(); setCursor(QCursor(Qt::ArrowCursor)); m_actionInterrupt->setEnabled(false); m_actionPlay->setEnabled(true); m_actionPlaySingleMove->setEnabled(true); } void MainWindow::checkComputerMove() { if (! m_autoPlay || ! m_computerColors[m_currentColor] || getBoard().is_game_over()) m_lastComputerMovesBegin = 0; else genMove(); } bool MainWindow::checkSave() { if (! m_file.isEmpty()) { if (! m_game->is_modified()) return true; QMessageBox msgBox(this); initQuestion(msgBox, tr("The file has been modified."), tr("Do you want to save your changes?")); // Don't use QMessageBox::Discard because on some platforms it uses the // text "Close without saving" which implies that the window would be // closed auto discardButton = msgBox.addButton(tr("&Don't Save"), QMessageBox::DestructiveRole); auto saveButton = msgBox.addButton(QMessageBox::Save); auto cancelButton = msgBox.addButton(QMessageBox::Cancel); msgBox.setDefaultButton(cancelButton); msgBox.exec(); auto result = msgBox.clickedButton(); if (result == saveButton) { save(); return true; } return (result == discardButton); } // Don't ask if game should be saved if it was finished because the user // might only want to play and never save games. if (m_game->get_tree().get_root().has_children() && ! m_gameFinished) { QMessageBox msgBox(this); initQuestion(msgBox, tr("The current game is not finished."), tr("Do you want to abort the game?")); auto abortGameButton = msgBox.addButton(tr("&Abort Game"), QMessageBox::DestructiveRole); auto cancelButton = msgBox.addButton(QMessageBox::Cancel); msgBox.setDefaultButton(cancelButton); msgBox.exec(); if (msgBox.clickedButton() != abortGameButton) return false; return true; } return true; } bool MainWindow::checkQuit() { if (! m_file.isEmpty() && m_game->is_modified()) { QMessageBox msgBox(this); initQuestion(msgBox, tr("The file has been modified."), tr("Do you want to save your changes?")); auto discardButton = msgBox.addButton(QMessageBox::Discard); auto saveButton = msgBox.addButton(QMessageBox::Save); auto cancelButton = msgBox.addButton(QMessageBox::Cancel); msgBox.setDefaultButton(cancelButton); msgBox.exec(); auto result = msgBox.clickedButton(); if (result == saveButton) { save(); return true; } return (result == discardButton); } cancelThread(); QSettings settings; if (m_file.isEmpty() && ! m_gameFinished && (m_game->is_modified() || m_isAutoSaveLoaded)) { writeGame(getAutoSaveFile().toLocal8Bit().constData()); settings.setValue("autosave_rated", m_isRated); if (m_isRated) settings.setValue("autosave_rated_color", m_ratedGameColor.to_int()); } if (! isFullScreen()) settings.setValue("geometry", saveGeometry()); if (m_comment->isVisible()) settings.setValue("splitter_state", m_splitter->saveState()); return true; } void MainWindow::clearFile() { setFile(""); } void MainWindow::clearSelectedPiece() { m_actionRotatePieceClockwise->setEnabled(false); m_actionRotatePieceAnticlockwise->setEnabled(false); m_actionFlipPieceHorizontally->setEnabled(false); m_actionFlipPieceVertically->setEnabled(false); m_actionClearSelectedPiece->setEnabled(false); m_guiBoard->clearSelectedPiece(); m_orientationDisplay->clearSelectedPiece(); } void MainWindow::clearStatus() { statusBar()->clearMessage(); } void MainWindow::closeEvent(QCloseEvent* event) { if (checkQuit()) event->accept(); else event->ignore(); } void MainWindow::coordinates(bool checked) { m_guiBoard->setCoordinates(checked); QSettings settings; settings.setValue("coordinates", checked); } void MainWindow::commentChanged() { if (m_ignoreCommentTextChanged) return; QString comment = m_comment->toPlainText(); if (comment.isEmpty()) m_game->set_comment(""); else { string charset = m_game->get_root().get_property("CA", ""); string value = Util::convertSgfValueFromQString(comment, charset); trim_right(value); m_game->set_comment(value); } updateWindowModified(); } void MainWindow::computerColors() { ColorMap oldComputerColors = m_computerColors; auto variant = getVariant(); ComputerColorDialog dialog(this, variant, m_computerColors); dialog.exec(); auto nu_colors = getBoard().get_nu_colors(); bool computerNone = true; for (ColorIterator i(nu_colors); i; ++i) if (m_computerColors[*i]) { computerNone = false; break; } QSettings settings; settings.setValue("computer_color_none", computerNone); // Enable auto play only if any color has changed because that means that // the user probably wants to continue playing, otherwise the user could // have only opened the dialog to check the current settings for (ColorIterator i(nu_colors); i; ++i) if (m_computerColors[*i] != oldComputerColors[*i]) { m_autoPlay = true; break; } checkComputerMove(); } bool MainWindow::computerPlaysAll() const { for (ColorIterator i(getBoard().get_nu_colors()); i; ++i) if (! m_computerColors[*i]) return false; return true; } QAction* MainWindow::createAction(const QString& text) { auto action = new QAction(text, this); // Add all actions also to main window. if an action is only added to // the menu bar, shortcuts won,t work in fullscreen mode because the menu // is not visible in fullscreen mode addAction(action); return action; } void MainWindow::createActions() { auto groupVariant = new QActionGroup(this); auto groupLevel = new QActionGroup(this); auto groupMoveNumbers = new QActionGroup(this); auto groupMoveAnnotation = new QActionGroup(this); auto groupToolBarText = new QActionGroup(this); m_actionAbout = createAction(tr("&About")); m_actionAbout->setIcon(QIcon::fromTheme("help-about")); connect(m_actionAbout, SIGNAL(triggered()), SLOT(about())); m_actionAnalyzeGame = createAction(tr("&Analyze Game...")); connect(m_actionAnalyzeGame, SIGNAL(triggered()), SLOT(analyzeGame())); m_actionBackward = createAction(tr("B&ackward")); m_actionBackward->setToolTip(tr("Go one move backward")); m_actionBackward->setPriority(QAction::LowPriority); setIcon(m_actionBackward, "pentobi-backward"); m_actionBackward->setShortcut(QString("Ctrl+Left")); connect(m_actionBackward, SIGNAL(triggered()), SLOT(backward())); m_actionBackward10 = createAction(tr("10 Back&ward")); m_actionBackward10->setToolTip(tr("Go ten moves backward")); m_actionBackward10->setPriority(QAction::LowPriority); setIcon(m_actionBackward10, "pentobi-backward10"); m_actionBackward10->setShortcut(QString("Ctrl+Shift+Left")); connect(m_actionBackward10, SIGNAL(triggered()), SLOT(backward10())); m_actionBackToMainVariation = createAction(tr("Back to &Main Variation")); m_actionBackToMainVariation->setShortcut(QString("Ctrl+M")); connect(m_actionBackToMainVariation, SIGNAL(triggered()), SLOT(backToMainVariation())); m_actionBadMove = createAction(tr("&Bad")); m_actionBadMove->setActionGroup(groupMoveAnnotation); m_actionBadMove->setCheckable(true); connect(m_actionBadMove, SIGNAL(triggered(bool)), SLOT(badMove(bool))); m_actionBeginning = createAction(tr("&Beginning")); m_actionBeginning->setToolTip(tr("Go to beginning of game")); m_actionBeginning->setPriority(QAction::LowPriority); setIcon(m_actionBeginning, "pentobi-beginning"); m_actionBeginning->setShortcut(QString("Ctrl+Home")); connect(m_actionBeginning, SIGNAL(triggered()), SLOT(beginning())); m_actionBeginningOfBranch = createAction(tr("Beginning of Bran&ch")); m_actionBeginningOfBranch->setShortcut(QString("Ctrl+B")); connect(m_actionBeginningOfBranch, SIGNAL(triggered()), SLOT(beginningOfBranch())); m_actionClearSelectedPiece = createAction(tr("Clear Piece")); setIcon(m_actionClearSelectedPiece, "pentobi-piece-clear"); m_actionClearSelectedPiece->setShortcut(QString("0")); connect(m_actionClearSelectedPiece, SIGNAL(triggered()), SLOT(clearSelectedPiece())); m_actionComputerColors = createAction(tr("&Computer Colors")); m_actionComputerColors->setShortcut(QString("Ctrl+U")); m_actionComputerColors->setToolTip( tr("Set the colors played by the computer")); setIcon(m_actionComputerColors, "pentobi-computer-color"); connect(m_actionComputerColors, SIGNAL(triggered()), SLOT(computerColors())); m_actionCoordinates = createAction(tr("C&oordinates")); m_actionCoordinates->setCheckable(true); connect(m_actionCoordinates, SIGNAL(triggered(bool)), SLOT(coordinates(bool))); m_actionDeleteAllVariations = createAction(tr("&Delete All Variations")); connect(m_actionDeleteAllVariations, SIGNAL(triggered()), SLOT(deleteAllVariations())); m_actionDoubtfulMove = createAction(tr("&Doubtful")); m_actionDoubtfulMove->setActionGroup(groupMoveAnnotation); m_actionDoubtfulMove->setCheckable(true); connect(m_actionDoubtfulMove, SIGNAL(triggered(bool)), SLOT(doubtfulMove(bool))); m_actionEnd = createAction(tr("&End")); m_actionEnd->setToolTip(tr("Go to end of moves")); m_actionEnd->setPriority(QAction::LowPriority); m_actionEnd->setShortcut(QString("Ctrl+End")); setIcon(m_actionEnd, "pentobi-end"); connect(m_actionEnd, SIGNAL(triggered()), SLOT(end())); m_actionExportAsciiArt = createAction(tr("&ASCII Art")); connect(m_actionExportAsciiArt, SIGNAL(triggered()), SLOT(exportAsciiArt())); m_actionExportImage = createAction(tr("I&mage")); connect(m_actionExportImage, SIGNAL(triggered()), SLOT(exportImage())); m_actionFindMove = createAction(tr("&Find Move")); m_actionFindMove->setShortcut(QString("F6")); connect(m_actionFindMove, SIGNAL(triggered()), SLOT(findMove())); m_actionFindNextComment = createAction(tr("Find Next &Comment")); m_actionFindNextComment->setShortcut(QString("F3")); connect(m_actionFindNextComment, SIGNAL(triggered()), SLOT(findNextComment())); m_actionFlipPieceHorizontally = createAction(tr("Flip Horizontally")); setIcon(m_actionFlipPieceHorizontally, "pentobi-flip-horizontal"); connect(m_actionFlipPieceHorizontally, SIGNAL(triggered()), SLOT(flipPieceHorizontally())); m_actionFlipPieceVertically = createAction(tr("Flip Vertically")); setIcon(m_actionFlipPieceVertically, "pentobi-flip-vertical"); m_actionForward = createAction(tr("&Forward")); m_actionForward->setToolTip(tr("Go one move forward")); m_actionForward->setPriority(QAction::LowPriority); m_actionForward->setShortcut(QString("Ctrl+Right")); setIcon(m_actionForward, "pentobi-forward"); connect(m_actionForward, SIGNAL(triggered()), SLOT(forward())); m_actionForward10 = createAction(tr("10 F&orward")); m_actionForward10->setToolTip(tr("Go ten moves forward")); m_actionForward10->setPriority(QAction::LowPriority); m_actionForward10->setShortcut(QString("Ctrl+Shift+Right")); setIcon(m_actionForward10, "pentobi-forward10"); connect(m_actionForward10, SIGNAL(triggered()), SLOT(forward10())); m_actionFullscreen = createAction(tr("&Fullscreen")); m_actionFullscreen->setIcon(QIcon::fromTheme("view-fullscreen")); m_actionFullscreen->setShortcut(QString("F11")); connect(m_actionFullscreen, SIGNAL(triggered()), SLOT(fullscreen())); m_actionGameInfo = createAction(tr("Ga&me Info")); m_actionGameInfo->setShortcut(QString("Ctrl+I")); connect(m_actionGameInfo, SIGNAL(triggered()), SLOT(gameInfo())); m_actionGoodMove = createAction(tr("&Good")); m_actionGoodMove->setActionGroup(groupMoveAnnotation); m_actionGoodMove->setCheckable(true); connect(m_actionGoodMove, SIGNAL(triggered(bool)), SLOT(goodMove(bool))); m_actionGotoMove = createAction(tr("&Go to Move...")); m_actionGotoMove->setShortcut(QString("Ctrl+G")); connect(m_actionGotoMove, SIGNAL(triggered()), SLOT(gotoMove())); m_actionHelp = createAction(tr("&Contents")); m_actionHelp->setIcon(QIcon::fromTheme("help-contents")); m_actionHelp->setShortcut(QKeySequence::HelpContents); connect(m_actionHelp, SIGNAL(triggered()), SLOT(help())); m_actionInterestingMove = createAction(tr("I&nteresting")); m_actionInterestingMove->setActionGroup(groupMoveAnnotation); m_actionInterestingMove->setCheckable(true); connect(m_actionInterestingMove, SIGNAL(triggered(bool)), SLOT(interestingMove(bool))); m_actionInterrupt = createAction(tr("St&op")); m_actionInterrupt->setIcon(QIcon::fromTheme("process-stop")); m_actionInterrupt->setEnabled(false); connect(m_actionInterrupt, SIGNAL(triggered()), SLOT(interrupt())); m_actionInterruptPlay = createAction(); m_actionInterruptPlay->setShortcut(QString("Shift+Esc")); connect(m_actionInterruptPlay, SIGNAL(triggered()), SLOT(interruptPlay())); m_actionKeepOnlyPosition = createAction(tr("&Keep Only Position")); connect(m_actionKeepOnlyPosition, SIGNAL(triggered()), SLOT(keepOnlyPosition())); m_actionKeepOnlySubtree = createAction(tr("Keep Only &Subtree")); connect(m_actionKeepOnlySubtree, SIGNAL(triggered()), SLOT(keepOnlySubtree())); m_actionLeaveFullscreen = createAction(tr("Leave Fullscreen")); m_actionLeaveFullscreen->setShortcut(QString("Esc")); connect(m_actionLeaveFullscreen, SIGNAL(triggered()), SLOT(leaveFullscreen())); m_actionMakeMainVariation = createAction(tr("M&ake Main Variation")); connect(m_actionMakeMainVariation, SIGNAL(triggered()), SLOT(makeMainVariation())); m_actionMoveDownVariation = createAction(tr("Move Variation D&own")); connect(m_actionMoveDownVariation, SIGNAL(triggered()), SLOT(moveDownVariation())); m_actionMoveUpVariation = createAction(tr("Move Variation &Up")); connect(m_actionMoveUpVariation, SIGNAL(triggered()), SLOT(moveUpVariation())); static_assert(maxLevel == 8, ""); QString levelText[maxLevel] = { tr("&1"), tr("&2"), tr("&3"), tr("&4"), tr("&5"), tr("&6"), tr("&7"), tr("&8") }; for (int i = 0; i < maxLevel; ++i) m_actionLevel[i] = createLevelAction(groupLevel, i + 1, levelText[i]); connect(m_actionFlipPieceVertically, SIGNAL(triggered()), SLOT(flipPieceVertically())); m_actionMoveNumbersAll = createAction(tr("&All")); m_actionMoveNumbersAll->setActionGroup(groupMoveNumbers); m_actionMoveNumbersAll->setCheckable(true); connect(m_actionMoveNumbersAll, SIGNAL(triggered(bool)), SLOT(setMoveNumbersAll(bool))); m_actionMoveNumbersLast = createAction(tr("&Last")); m_actionMoveNumbersLast->setActionGroup(groupMoveNumbers); m_actionMoveNumbersLast->setCheckable(true); m_actionMoveNumbersLast->setChecked(true); connect(m_actionMoveNumbersLast, SIGNAL(triggered(bool)), SLOT(setMoveNumbersLast(bool))); m_actionMoveNumbersNone = createAction(tr("&None", "move numbers")); m_actionMoveNumbersNone->setActionGroup(groupMoveNumbers); m_actionMoveNumbersNone->setCheckable(true); connect(m_actionMoveNumbersNone, SIGNAL(triggered(bool)), SLOT(setMoveNumbersNone(bool))); m_actionMoveSelectedPieceLeft = createAction(); m_actionMoveSelectedPieceLeft->setShortcut(QString("Left")); m_actionMoveSelectedPieceRight = createAction(); m_actionMoveSelectedPieceRight->setShortcut(QString("Right")); m_actionMoveSelectedPieceUp = createAction(); m_actionMoveSelectedPieceUp->setShortcut(QString("Up")); m_actionMoveSelectedPieceDown = createAction(); m_actionMoveSelectedPieceDown->setShortcut(QString("Down")); m_actionNextPiece = createAction(tr("Next Piece")); setIcon(m_actionNextPiece, "pentobi-next-piece"); m_actionNextPiece->setShortcut(QString("+")); connect(m_actionNextPiece, SIGNAL(triggered()), SLOT(nextPiece())); m_actionNextTransform = createAction(); m_actionNextTransform->setShortcut(QString("Space")); connect(m_actionNextTransform, SIGNAL(triggered()), SLOT(nextTransform())); m_actionNextVariation = createAction(tr("&Next Variation")); m_actionNextVariation->setToolTip(tr("Go to next variation")); m_actionNextVariation->setPriority(QAction::LowPriority); m_actionNextVariation->setShortcut(QString("Ctrl+Down")); setIcon(m_actionNextVariation, "pentobi-next-variation"); connect(m_actionNextVariation, SIGNAL(triggered()), SLOT(nextVariation())); m_actionNextVariation10 = createAction(); m_actionNextVariation10->setShortcut(QString("Ctrl+Shift+Down")); connect(m_actionNextVariation10, SIGNAL(triggered()), SLOT(nextVariation10())); m_actionNewRatedGame = createAction(tr("New &Rated Game")); m_actionNewRatedGame->setShortcut(QString("Ctrl+Shift+N")); connect(m_actionNewRatedGame, SIGNAL(triggered()), SLOT(newRatedGame())); m_actionNew = createAction(tr("&New")); m_actionNew->setShortcut(QKeySequence::New); m_actionNew->setToolTip(tr("Start a new game")); setIcon(m_actionNew, "pentobi-newgame"); connect(m_actionNew, SIGNAL(triggered()), SLOT(newGame())); m_actionNoMoveAnnotation = createAction(tr("N&one", "move annotation")); m_actionNoMoveAnnotation->setActionGroup(groupMoveAnnotation); m_actionNoMoveAnnotation->setCheckable(true); connect(m_actionNoMoveAnnotation, SIGNAL(triggered(bool)), SLOT(noMoveAnnotation(bool))); m_actionOpen = createAction(tr("&Open...")); m_actionOpen->setIcon(QIcon::fromTheme("document-open")); m_actionOpen->setShortcut(QKeySequence::Open); connect(m_actionOpen, SIGNAL(triggered()), SLOT(open())); m_actionPlaceSelectedPiece = createAction(); m_actionPlaceSelectedPiece->setShortcut(QString("Return")); m_actionPlay = createAction(tr("&Play")); m_actionPlay->setShortcut(QString("Ctrl+L")); setIcon(m_actionPlay, "pentobi-play"); connect(m_actionPlay, SIGNAL(triggered()), SLOT(play())); m_actionPlaySingleMove = createAction(tr("Play &Single Move")); m_actionPlaySingleMove->setShortcut(QString("Ctrl+Shift+L")); connect(m_actionPlaySingleMove, SIGNAL(triggered()), SLOT(playSingleMove())); m_actionPreviousPiece = createAction(tr("Previous Piece")); setIcon(m_actionPreviousPiece, "pentobi-previous-piece"); m_actionPreviousPiece->setShortcut(QString("-")); connect(m_actionPreviousPiece, SIGNAL(triggered()), SLOT(previousPiece())); m_actionPreviousTransform = createAction(); m_actionPreviousTransform->setShortcut(QString("Shift+Space")); connect(m_actionPreviousTransform, SIGNAL(triggered()), SLOT(previousTransform())); m_actionPreviousVariation = createAction(tr("&Previous Variation")); m_actionPreviousVariation->setToolTip(tr("Go to previous variation")); m_actionPreviousVariation->setPriority(QAction::LowPriority); m_actionPreviousVariation->setShortcut(QString("Ctrl+Up")); setIcon(m_actionPreviousVariation, "pentobi-previous-variation"); connect(m_actionPreviousVariation, SIGNAL(triggered()), SLOT(previousVariation())); m_actionPreviousVariation10 = createAction(); m_actionPreviousVariation10->setShortcut(QString("Ctrl+Shift+Up")); connect(m_actionPreviousVariation10, SIGNAL(triggered()), SLOT(previousVariation10())); for (int i = 0; i < maxRecentFiles; ++i) { m_actionRecentFile[i] = createAction(); m_actionRecentFile[i]->setVisible(false); connect(m_actionRecentFile[i], SIGNAL(triggered()), SLOT(openRecentFile())); } m_actionRotatePieceAnticlockwise = createAction(tr("Rotate Anticlockwise")); setIcon(m_actionRotatePieceAnticlockwise, "pentobi-rotate-left"); connect(m_actionRotatePieceAnticlockwise, SIGNAL(triggered()), SLOT(rotatePieceAnticlockwise())); m_actionRotatePieceClockwise = createAction(tr("Rotate Clockwise")); setIcon(m_actionRotatePieceClockwise, "pentobi-rotate-right"); connect(m_actionRotatePieceClockwise, SIGNAL(triggered()), SLOT(rotatePieceClockwise())); m_actionQuit = createAction(tr("&Quit")); m_actionQuit->setIcon(QIcon::fromTheme("application-exit")); m_actionQuit->setShortcut(QKeySequence::Quit); connect(m_actionQuit, SIGNAL(triggered()), SLOT(close())); m_actionSave = createAction(tr("&Save")); m_actionSave->setIcon(QIcon::fromTheme("document-save")); m_actionSave->setShortcut(QKeySequence::Save); connect(m_actionSave, SIGNAL(triggered()), SLOT(save())); m_actionSaveAs = createAction(tr("Save &As...")); m_actionSaveAs->setShortcut(QKeySequence::SaveAs); connect(m_actionSaveAs, SIGNAL(triggered()), SLOT(saveAs())); m_actionSelectNextColor = createAction(tr("Select Next &Color")); m_actionSelectNextColor->setShortcut(QString("Ctrl+C")); connect(m_actionSelectNextColor, SIGNAL(triggered()), SLOT(selectNextColor())); m_actionSelectPiece1 = createAction(); m_actionSelectPiece1->setShortcut(QString("1")); connect(m_actionSelectPiece1, SIGNAL(triggered()), SLOT(selectPiece1())); m_actionSelectPiece2 = createAction(); m_actionSelectPiece2->setShortcut(QString("2")); connect(m_actionSelectPiece2, SIGNAL(triggered()), SLOT(selectPiece2())); m_actionSelectPieceA = createAction(); m_actionSelectPieceA->setShortcut(QString("A")); connect(m_actionSelectPieceA, SIGNAL(triggered()), SLOT(selectPieceA())); m_actionSelectPieceC = createAction(); m_actionSelectPieceC->setShortcut(QString("C")); connect(m_actionSelectPieceC, SIGNAL(triggered()), SLOT(selectPieceC())); m_actionSelectPieceF = createAction(); m_actionSelectPieceF->setShortcut(QString("F")); connect(m_actionSelectPieceF, SIGNAL(triggered()), SLOT(selectPieceF())); m_actionSelectPieceG = createAction(); m_actionSelectPieceG->setShortcut(QString("G")); connect(m_actionSelectPieceG, SIGNAL(triggered()), SLOT(selectPieceG())); m_actionSelectPieceI = createAction(); m_actionSelectPieceI->setShortcut(QString("I")); connect(m_actionSelectPieceI, SIGNAL(triggered()), SLOT(selectPieceI())); m_actionSelectPieceL = createAction(); m_actionSelectPieceL->setShortcut(QString("L")); connect(m_actionSelectPieceL, SIGNAL(triggered()), SLOT(selectPieceL())); m_actionSelectPieceN = createAction(); m_actionSelectPieceN->setShortcut(QString("N")); connect(m_actionSelectPieceN, SIGNAL(triggered()), SLOT(selectPieceN())); m_actionSelectPieceO = createAction(); m_actionSelectPieceO->setShortcut(QString("O")); connect(m_actionSelectPieceO, SIGNAL(triggered()), SLOT(selectPieceO())); m_actionSelectPieceP = createAction(); m_actionSelectPieceP->setShortcut(QString("P")); connect(m_actionSelectPieceP, SIGNAL(triggered()), SLOT(selectPieceP())); m_actionSelectPieceS = createAction(); m_actionSelectPieceS->setShortcut(QString("S")); connect(m_actionSelectPieceS, SIGNAL(triggered()), SLOT(selectPieceS())); m_actionSelectPieceT = createAction(); m_actionSelectPieceT->setShortcut(QString("T")); connect(m_actionSelectPieceT, SIGNAL(triggered()), SLOT(selectPieceT())); m_actionSelectPieceU = createAction(); m_actionSelectPieceU->setShortcut(QString("U")); connect(m_actionSelectPieceU, SIGNAL(triggered()), SLOT(selectPieceU())); m_actionSelectPieceV = createAction(); m_actionSelectPieceV->setShortcut(QString("V")); connect(m_actionSelectPieceV, SIGNAL(triggered()), SLOT(selectPieceV())); m_actionSelectPieceW = createAction(); m_actionSelectPieceW->setShortcut(QString("W")); connect(m_actionSelectPieceW, SIGNAL(triggered()), SLOT(selectPieceW())); m_actionSelectPieceX = createAction(); m_actionSelectPieceX->setShortcut(QString("X")); connect(m_actionSelectPieceX, SIGNAL(triggered()), SLOT(selectPieceX())); m_actionSelectPieceY = createAction(); m_actionSelectPieceY->setShortcut(QString("Y")); connect(m_actionSelectPieceY, SIGNAL(triggered()), SLOT(selectPieceY())); m_actionSelectPieceZ = createAction(); m_actionSelectPieceZ->setShortcut(QString("Z")); connect(m_actionSelectPieceZ, SIGNAL(triggered()), SLOT(selectPieceZ())); m_actionSetupMode = createAction(tr("S&etup Mode")); m_actionSetupMode->setCheckable(true); connect(m_actionSetupMode, SIGNAL(triggered(bool)), SLOT(setupMode(bool))); m_actionShowComment = createAction(tr("&Comment")); m_actionShowComment->setCheckable(true); m_actionShowComment->setShortcut(QString("Ctrl+T")); connect(m_actionShowComment, SIGNAL(triggered(bool)), SLOT(showComment(bool))); m_actionShowRating = createAction(tr("Your &Rating")); m_actionShowRating->setShortcut(QString("F7")); connect(m_actionShowRating, SIGNAL(triggered()), SLOT(showRating())); m_actionToolBarNoText = createAction(tr("&No Text")); m_actionToolBarNoText->setActionGroup(groupToolBarText); m_actionToolBarNoText->setCheckable(true); connect(m_actionToolBarNoText, SIGNAL(triggered(bool)), SLOT(toolBarNoText(bool))); m_actionToolBarTextBesideIcons = createAction(tr("Text &Beside Icons")); m_actionToolBarTextBesideIcons->setActionGroup(groupToolBarText); m_actionToolBarTextBesideIcons->setCheckable(true); connect(m_actionToolBarTextBesideIcons, SIGNAL(triggered(bool)), SLOT(toolBarTextBesideIcons(bool))); m_actionToolBarTextBelowIcons = createAction(tr("Text Bel&ow Icons")); m_actionToolBarTextBelowIcons->setActionGroup(groupToolBarText); m_actionToolBarTextBelowIcons->setCheckable(true); connect(m_actionToolBarTextBelowIcons, SIGNAL(triggered(bool)), SLOT(toolBarTextBelowIcons(bool))); m_actionToolBarTextOnly = createAction(tr("&Text Only")); m_actionToolBarTextOnly->setActionGroup(groupToolBarText); m_actionToolBarTextOnly->setCheckable(true); connect(m_actionToolBarTextOnly, SIGNAL(triggered(bool)), SLOT(toolBarTextOnly(bool))); m_actionToolBarTextSystem = createAction(tr("&System Default")); m_actionToolBarTextSystem->setActionGroup(groupToolBarText); m_actionToolBarTextSystem->setCheckable(true); connect(m_actionToolBarTextSystem, SIGNAL(triggered(bool)), SLOT(toolBarTextSystem(bool))); m_actionTruncate = createAction(tr("&Truncate")); connect(m_actionTruncate, SIGNAL(triggered()), SLOT(truncate())); m_actionTruncateChildren = createAction(tr("Truncate C&hildren")); connect(m_actionTruncateChildren, SIGNAL(triggered()), SLOT(truncateChildren())); m_actionShowToolbar = createAction(tr("&Toolbar")); m_actionShowToolbar->setCheckable(true); connect(m_actionShowToolbar, SIGNAL(triggered(bool)), SLOT(showToolbar(bool))); m_actionShowVariations = createAction(tr("Show &Variations")); m_actionShowVariations->setCheckable(true); connect(m_actionShowVariations, SIGNAL(triggered(bool)), SLOT(showVariations(bool))); m_actionUndo = createAction(tr("&Undo Move")); connect(m_actionUndo, SIGNAL(triggered()), SLOT(undo())); m_actionVariantClassic = createAction(tr("&Classic (4 Players)")); m_actionVariantClassic->setActionGroup(groupVariant); m_actionVariantClassic->setCheckable(true); connect(m_actionVariantClassic, SIGNAL(triggered(bool)), SLOT(variantClassic(bool))); m_actionVariantClassic2 = createAction(tr("Classic (&2 Players)")); m_actionVariantClassic2->setActionGroup(groupVariant); m_actionVariantClassic2->setCheckable(true); connect(m_actionVariantClassic2, SIGNAL(triggered(bool)), SLOT(variantClassic2(bool))); m_actionVariantDuo = createAction(tr("&Duo")); m_actionVariantDuo->setActionGroup(groupVariant); m_actionVariantDuo->setCheckable(true); connect(m_actionVariantDuo, SIGNAL(triggered(bool)), SLOT(variantDuo(bool))); m_actionVariantJunior = createAction(tr("J&unior")); m_actionVariantJunior->setActionGroup(groupVariant); m_actionVariantJunior->setCheckable(true); connect(m_actionVariantJunior, SIGNAL(triggered(bool)), SLOT(variantJunior(bool))); m_actionVariantTrigon = createAction(tr("&Trigon (4 Players)")); m_actionVariantTrigon->setActionGroup(groupVariant); m_actionVariantTrigon->setCheckable(true); connect(m_actionVariantTrigon, SIGNAL(triggered(bool)), SLOT(variantTrigon(bool))); m_actionVariantTrigon2 = createAction(tr("Tri&gon (2 Players)")); m_actionVariantTrigon2->setActionGroup(groupVariant); m_actionVariantTrigon2->setCheckable(true); connect(m_actionVariantTrigon2, SIGNAL(triggered(bool)), SLOT(variantTrigon2(bool))); m_actionVariantTrigon3 = createAction(tr("Trigon (&3 Players)")); m_actionVariantTrigon3->setActionGroup(groupVariant); m_actionVariantTrigon3->setCheckable(true); connect(m_actionVariantTrigon3, SIGNAL(triggered(bool)), SLOT(variantTrigon3(bool))); m_actionVeryBadMove = createAction(tr("V&ery Bad")); m_actionVeryBadMove->setActionGroup(groupMoveAnnotation); m_actionVeryBadMove->setCheckable(true); connect(m_actionVeryBadMove, SIGNAL(triggered(bool)), SLOT(veryBadMove(bool))); m_actionVeryGoodMove = createAction(tr("&Very Good")); m_actionVeryGoodMove->setActionGroup(groupMoveAnnotation); m_actionVeryGoodMove->setCheckable(true); connect(m_actionVeryGoodMove, SIGNAL(triggered(bool)), SLOT(veryGoodMove(bool))); } QWidget* MainWindow::createCentralWidget() { auto widget = new QWidget(); // We add spacing around and between the two panels using streches (such // that the spacing grows with the window size) auto outerLayout = new QVBoxLayout(); widget->setLayout(outerLayout); auto innerLayout = new QHBoxLayout(); outerLayout->addStretch(1); outerLayout->addLayout(innerLayout, 100); outerLayout->addStretch(1); innerLayout->addStretch(1); innerLayout->addWidget(createLeftPanel(), 60); innerLayout->addStretch(1); innerLayout->addLayout(createRightPanel(), 40); innerLayout->addStretch(1); // The central widget doesn't do anything with the focus right now, but we // allow it to receive the focus such that the user can switch away the // focus from the comment field and its blinking cursor. widget->setFocusPolicy(Qt::StrongFocus); return widget; } QWidget* MainWindow::createLeftPanel() { m_splitter = new QSplitter(Qt::Vertical); m_guiBoard = new GuiBoard(0, getBoard()); m_splitter->addWidget(m_guiBoard); m_comment = new QPlainTextEdit(); m_comment->setTabChangesFocus(true); connect(m_comment, SIGNAL(textChanged()), SLOT(commentChanged())); m_splitter->addWidget(m_comment); m_splitter->setStretchFactor(0, 85); m_splitter->setStretchFactor(1, 15); m_splitter->setCollapsible(0, false); m_splitter->setCollapsible(1, false); return m_splitter; } QAction* MainWindow::createLevelAction(QActionGroup* group, int level, const QString& text) { LIBBOARDGAME_ASSERT(level >= 1 && level <= maxLevel); auto action = createAction(text); action->setCheckable(true); if (level == m_level) action->setChecked(true); action->setActionGroup(group); action->setData(level); connect(action, SIGNAL(triggered(bool)), SLOT(setLevel(bool))); return action; } void MainWindow::createMenu() { auto menuGame = menuBar()->addMenu(tr("&Game")); menuGame->addAction(m_actionNew); menuGame->addAction(m_actionNewRatedGame); menuGame->addSeparator(); auto menuVariant = menuGame->addMenu(tr("Game &Variant")); menuVariant->addAction(m_actionVariantClassic); menuVariant->addAction(m_actionVariantClassic2); menuVariant->addAction(m_actionVariantDuo); menuVariant->addAction(m_actionVariantTrigon); menuVariant->addAction(m_actionVariantTrigon3); menuVariant->addAction(m_actionVariantTrigon2); menuVariant->addAction(m_actionVariantJunior); menuGame->addAction(m_actionComputerColors); menuGame->addAction(m_actionGameInfo); menuGame->addSeparator(); menuGame->addAction(m_actionUndo); menuGame->addAction(m_actionFindMove); menuGame->addSeparator(); menuGame->addAction(m_actionOpen); m_menuOpenRecent = menuGame->addMenu(tr("Open R&ecent")); for (int i = 0; i < maxRecentFiles; ++i) m_menuOpenRecent->addAction(m_actionRecentFile[i]); menuGame->addSeparator(); menuGame->addAction(m_actionSave); menuGame->addAction(m_actionSaveAs); auto menuExport = menuGame->addMenu(tr("E&xport")); menuExport->addAction(m_actionExportImage); menuExport->addAction(m_actionExportAsciiArt); menuGame->addSeparator(); menuGame->addAction(m_actionQuit); auto menuGo = menuBar()->addMenu(tr("G&o")); menuGo->addAction(m_actionBeginning); menuGo->addAction(m_actionBackward10); menuGo->addAction(m_actionBackward); menuGo->addAction(m_actionForward); menuGo->addAction(m_actionForward10); menuGo->addAction(m_actionEnd); menuGo->addSeparator(); menuGo->addAction(m_actionNextVariation); menuGo->addAction(m_actionPreviousVariation); menuGo->addSeparator(); menuGo->addAction(m_actionGotoMove); menuGo->addAction(m_actionBackToMainVariation); menuGo->addAction(m_actionBeginningOfBranch); menuGo->addAction(m_actionFindNextComment); auto menuEdit = menuBar()->addMenu(tr("&Edit")); m_menuMoveAnnotation = menuEdit->addMenu(tr("&Move Annotation")); m_menuMoveAnnotation->addAction(m_actionGoodMove); m_menuMoveAnnotation->addAction(m_actionVeryGoodMove); m_menuMoveAnnotation->addAction(m_actionBadMove); m_menuMoveAnnotation->addAction(m_actionVeryBadMove); m_menuMoveAnnotation->addAction(m_actionInterestingMove); m_menuMoveAnnotation->addAction(m_actionDoubtfulMove); m_menuMoveAnnotation->addAction(m_actionNoMoveAnnotation); menuEdit->addSeparator(); menuEdit->addAction(m_actionMakeMainVariation); menuEdit->addAction(m_actionMoveUpVariation); menuEdit->addAction(m_actionMoveDownVariation); menuEdit->addSeparator(); menuEdit->addAction(m_actionDeleteAllVariations); menuEdit->addAction(m_actionTruncate); menuEdit->addAction(m_actionTruncateChildren); menuEdit->addAction(m_actionKeepOnlyPosition); menuEdit->addAction(m_actionKeepOnlySubtree); menuEdit->addSeparator(); menuEdit->addAction(m_actionSetupMode); menuEdit->addAction(m_actionSelectNextColor); auto menuView = menuBar()->addMenu(tr("&View")); menuView->addAction(m_actionShowToolbar); m_menuToolBarText = menuView->addMenu(tr("Toolbar T&ext")); m_menuToolBarText->addAction(m_actionToolBarNoText); m_menuToolBarText->addAction(m_actionToolBarTextBesideIcons); m_menuToolBarText->addAction(m_actionToolBarTextBelowIcons); m_menuToolBarText->addAction(m_actionToolBarTextOnly); m_menuToolBarText->addAction(m_actionToolBarTextSystem); menuView->addAction(m_actionShowComment); menuView->addSeparator(); auto menuMoveNumbers = menuView->addMenu(tr("&Move Numbers")); menuMoveNumbers->addAction(m_actionMoveNumbersLast); menuMoveNumbers->addAction(m_actionMoveNumbersAll); menuMoveNumbers->addAction(m_actionMoveNumbersNone); menuView->addAction(m_actionCoordinates); menuView->addAction(m_actionShowVariations); menuView->addSeparator(); menuView->addAction(m_actionFullscreen); auto menuComputer = menuBar()->addMenu(tr("&Computer")); menuComputer->addAction(m_actionPlay); menuComputer->addAction(m_actionPlaySingleMove); menuComputer->addAction(m_actionInterrupt); menuComputer->addSeparator(); auto menuLevel = menuComputer->addMenu(tr("&Level")); for (int i = 0; i < maxLevel; ++i) menuLevel->addAction(m_actionLevel[i]); auto menuTools = menuBar()->addMenu(tr("&Tools")); menuTools->addAction(m_actionShowRating); menuTools->addAction(m_actionAnalyzeGame); auto menuHelp = menuBar()->addMenu(tr("&Help")); menuHelp->addAction(m_actionHelp); menuHelp->addAction(m_actionAbout); } QLayout* MainWindow::createOrientationButtonBoxLeft() { auto outerLayout = new QVBoxLayout(); auto layout = new QGridLayout(); layout->addWidget(createOBoxToolButton(m_actionRotatePieceAnticlockwise), 0, 0); layout->addWidget(createOBoxToolButton(m_actionRotatePieceClockwise), 0, 1); layout->addWidget(createOBoxToolButton(m_actionFlipPieceHorizontally), 1, 0); layout->addWidget(createOBoxToolButton(m_actionFlipPieceVertically), 1, 1); outerLayout->addStretch(); outerLayout->addLayout(layout); outerLayout->addStretch(); return outerLayout; } QLayout* MainWindow::createOrientationButtonBoxRight() { auto outerLayout = new QVBoxLayout(); auto layout = new QGridLayout(); layout->addWidget(createOBoxToolButton(m_actionPreviousPiece), 0, 0); layout->addWidget(createOBoxToolButton(m_actionNextPiece), 0, 1); layout->addWidget(createOBoxToolButton(m_actionClearSelectedPiece), 1, 0, 1, 2, Qt::AlignHCenter); outerLayout->addStretch(); outerLayout->addLayout(layout); outerLayout->addStretch(); return outerLayout; } QLayout* MainWindow::createOrientationSelector() { auto layout = new QHBoxLayout(); layout->addStretch(); layout->addLayout(createOrientationButtonBoxLeft()); layout->addSpacing(8); m_orientationDisplay = new OrientationDisplay(0, getBoard()); connect(m_orientationDisplay, SIGNAL(colorClicked(Color)), SLOT(orientationDisplayColorClicked(Color))); m_orientationDisplay->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); layout->addWidget(m_orientationDisplay); layout->addSpacing(8); layout->addLayout(createOrientationButtonBoxRight()); layout->addStretch(); return layout; } QLayout* MainWindow::createRightPanel() { auto layout = new QBoxLayout(QBoxLayout::TopToBottom); layout->addLayout(createOrientationSelector(), 20); m_scoreDisplay = new ScoreDisplay(); layout->addWidget(m_scoreDisplay, 5); auto pieceSelectorLayout = new SameHeightLayout(); layout->addLayout(pieceSelectorLayout, 80); for (ColorIterator i(Color::range); i; ++i) { m_pieceSelector[*i] = new PieceSelector(0, getBoard(), *i); connect(m_pieceSelector[*i], SIGNAL(pieceSelected(Color,Piece,const Transform*)), SLOT(selectPiece(Color,Piece,const Transform*))); pieceSelectorLayout->addWidget(m_pieceSelector[*i]); } return layout; } void MainWindow::deleteAllVariations() { QMessageBox msgBox(this); initQuestion(msgBox, tr("Delete all variations?"), tr("All variations but the main variation will be" " removed from the game tree.")); auto deleteButton = msgBox.addButton(tr("Delete Variations"), QMessageBox::DestructiveRole); auto cancelButton = msgBox.addButton(QMessageBox::Cancel); msgBox.setDefaultButton(cancelButton); msgBox.exec(); if (msgBox.clickedButton() != deleteButton) return; bool currentNodeChanges = ! is_main_variation(m_game->get_current()); if (currentNodeChanges) cancelThread(); m_game->delete_all_variations(); updateWindow(currentNodeChanges); } void MainWindow::doubtfulMove(bool checked) { if (! checked) return; m_game->set_doubtful_move(); updateWindow(false); } void MainWindow::createToolBar() { m_toolBar = new QToolBar(); m_toolBar->setMovable(false); m_toolBar->setContextMenuPolicy(Qt::PreventContextMenu); m_toolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle); m_toolBar->addAction(m_actionNew); m_toolBar->addAction(m_actionPlay); m_toolBar->addAction(m_actionComputerColors); m_toolBar->addSeparator(); m_toolBar->addAction(m_actionBeginning); m_toolBar->addAction(m_actionBackward10); m_toolBar->addAction(m_actionBackward); m_toolBar->addAction(m_actionForward); m_toolBar->addAction(m_actionForward10); m_toolBar->addAction(m_actionEnd); m_toolBar->addSeparator(); m_toolBar->addAction(m_actionNextVariation); m_toolBar->addAction(m_actionPreviousVariation); addToolBar(m_toolBar); QSettings settings; auto toolBarText = settings.value("toolbar_text", "system").toString(); if (toolBarText == "no_text") { m_toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); m_actionToolBarNoText->setChecked(true); } else if (toolBarText == "beside_icons") { m_toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); m_actionToolBarTextBesideIcons->setChecked(true); } else if (toolBarText == "below_icons") { m_toolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); m_actionToolBarTextBelowIcons->setChecked(true); } else if (toolBarText == "text_only") { m_toolBar->setToolButtonStyle(Qt::ToolButtonTextOnly); m_actionToolBarTextOnly->setChecked(true); } else { m_toolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle); m_actionToolBarTextSystem->setChecked(true); } } void MainWindow::deleteAutoSaveFile() { QString autoSaveFile = getAutoSaveFile(); QFile file(autoSaveFile); if (file.exists() && ! file.remove()) showError(tr("Could not delete %1").arg(autoSaveFile)); } void MainWindow::enablePieceSelector(Color c) { for (ColorIterator i(getBoard().get_nu_colors()); i; ++i) { m_pieceSelector[*i]->checkUpdate(); m_pieceSelector[*i]->setEnabled(*i == c); } } void MainWindow::end() { gotoNode(get_last_node(m_game->get_current())); } bool MainWindow::eventFilter(QObject* object, QEvent* event) { // By default, Qt 4.7 shows status tips in the status bar if the mouse // goes over a menu. This is undesirable because it deletes the current // text in the status line (e.g. the "The computer is thinking..." status) if (event->type() == QEvent::StatusTip) return true; return QMainWindow::eventFilter(object, event); } void MainWindow::exportAsciiArt() { QString file = QFileDialog::getSaveFileName(this, "", "", tr("Text files (*.txt)")); if (file.isEmpty()) return; ofstream out(file.toLocal8Bit().constData()); auto& bd = getBoard(); bd.write(out, false); if (! out) showError(strerror(errno)); } void MainWindow::exportImage() { ::exportImage(this, getBoard(), m_actionCoordinates->isChecked(), m_guiBoard->getLabels()); } void MainWindow::findMove() { auto& bd = getBoard(); if (bd.is_game_over()) return; if (m_legalMoves->empty()) { bd.gen_moves(m_currentColor, *m_legalMoves); sort(m_legalMoves->begin(), m_legalMoves->end(), bind(&isMoveBetter, &bd, placeholders::_1, placeholders::_2)); } if (m_legalMoves->empty()) { // m_currentColor must have moves if game is not over LIBBOARDGAME_ASSERT(false); return; } if (m_legalMoveIndex >= m_legalMoves->size()) m_legalMoveIndex = 0; auto mv = (*m_legalMoves)[m_legalMoveIndex]; selectPiece(m_currentColor, bd.get_move_info(mv).get_piece()); m_guiBoard->showMove(m_currentColor, mv); ++m_legalMoveIndex; } void MainWindow::findNextComment() { auto& root = m_game->get_root(); auto& current = m_game->get_current(); auto node = find_next_comment(current); if (node == nullptr && ¤t != &root) { QMessageBox msgBox(this); initQuestion(msgBox, tr("The end of the tree was reached."), tr("Continue the search from the start of the tree?")); auto continueButton = msgBox.addButton(tr("Continue From Start"), QMessageBox::AcceptRole); msgBox.addButton(QMessageBox::Cancel); msgBox.setDefaultButton(continueButton); msgBox.exec(); if (msgBox.clickedButton() == continueButton) { node = &root; if (! has_comment(*node)) node = find_next_comment(*node); } else return; } if (node == nullptr) { showInfo(tr("No comment found")); return; } showComment(true); gotoNode(*node); } void MainWindow::flipPieceHorizontally() { Piece piece = m_guiBoard->getSelectedPiece(); if (piece.is_null()) return; auto& bd = getBoard(); auto transform = m_guiBoard->getSelectedPieceTransform(); transform = bd.get_transforms().get_mirrored_horizontally(transform); transform = bd.get_piece_info(piece).get_equivalent_transform(transform); m_guiBoard->setSelectedPieceTransform(transform); m_orientationDisplay->setSelectedPieceTransform(transform); } void MainWindow::flipPieceVertically() { Piece piece = m_guiBoard->getSelectedPiece(); if (piece.is_null()) return; auto transform = m_guiBoard->getSelectedPieceTransform(); auto& bd = getBoard(); transform = bd.get_transforms().get_mirrored_vertically(transform); transform = bd.get_piece_info(piece).get_equivalent_transform(transform); m_guiBoard->setSelectedPieceTransform(transform); m_orientationDisplay->setSelectedPieceTransform(transform); } void MainWindow::forward() { auto node = m_game->get_current().get_first_child_or_null(); if (node == nullptr) return; gotoNode(*node); } void MainWindow::forward10() { auto& tree = m_game->get_tree(); auto node = &m_game->get_current(); unsigned n = 0; while (n < 10) { if (tree.has_move(*node)) ++n; auto child = node->get_first_child_or_null(); if (child == nullptr) break; node = child; } gotoNode(*node); } void MainWindow::fullscreen() { if (isFullScreen()) { // If F11 is pressed in fullscreen, we switch to normal leaveFullscreen(); return; } QSettings settings; menuBar()->hide(); m_toolBar->hide(); settings.setValue("geometry", saveGeometry()); showFullScreen(); if (m_leaveFullscreenButton == nullptr) m_leaveFullscreenButton = new LeaveFullscreenButton(this, m_actionLeaveFullscreen); m_leaveFullscreenButton->showButton(); } void MainWindow::gameInfo() { GameInfoDialog dialog(this, *m_game); dialog.exec(); updateWindow(false); } void MainWindow::gameOver() { auto variant = getVariant(); auto& bd = getBoard(); QString info; if (variant == Variant::duo || variant == Variant::junior) { int score = bd.get_score(Color(0)); if (score > 0) info = tr("Blue wins with %n point(s).", "", score); else if (score < 0) info = tr("Green wins with %n point(s).", "", -score); else info = tr("The game ends in a tie."); } else if (variant == Variant::classic_2 || variant == Variant::trigon_2) { int score = bd.get_score(Color(0)); if (score > 0) info = tr("Blue/Red wins with %n point(s).", "", score); else if (score < 0) info = tr("Yellow/Green wins with %n point(s).", "", -score); else info = tr("The game ends in a tie."); } else if (variant == Variant::trigon_3) { unsigned blue = bd.get_points_with_bonus(Color(0)); unsigned yellow = bd.get_points_with_bonus(Color(1)); unsigned red = bd.get_points_with_bonus(Color(2)); unsigned maxPoints = max(blue, max(yellow, red)); if (blue == yellow && yellow == red) info = tr("The game ends in a tie between all colors."); else if (blue == maxPoints && blue == yellow) info = tr("The game ends in a tie between Blue and Yellow."); else if (blue == maxPoints && blue == red) info = tr("The game ends in a tie between Blue and Red."); else if (yellow == maxPoints && yellow == red) info = tr("The game ends in a tie between Yellow and Red."); else if (blue == maxPoints) info = tr("Blue wins."); else if (yellow == maxPoints) info = tr("Yellow wins."); else info = tr("Red wins."); } else { LIBBOARDGAME_ASSERT(variant == Variant::classic || variant == Variant::trigon); unsigned blue = bd.get_points_with_bonus(Color(0)); unsigned yellow = bd.get_points_with_bonus(Color(1)); unsigned red = bd.get_points_with_bonus(Color(2)); unsigned green = bd.get_points_with_bonus(Color(3)); unsigned maxPoints = max(blue, max(yellow, max(red, green))); if (blue == yellow && yellow == red && red == green) info = tr("The game ends in a tie between all colors."); else if (blue == maxPoints && blue == yellow && yellow == red) info = tr("The game ends in a tie between Blue, Yellow and Red."); else if (blue == maxPoints && blue == yellow && yellow == green) info = tr("The game ends in a tie between Blue, Yellow and Green."); else if (blue == maxPoints && blue == red && red == green) info = tr("The game ends in a tie between Blue, Red and Green."); else if (yellow == maxPoints && yellow == red && red == green) info = tr("The game ends in a tie between Yellow, Red and Green."); else if (blue == maxPoints && blue == yellow) info = tr("The game ends in a tie between Blue and Yellow."); else if (blue == maxPoints && blue == red) info = tr("The game ends in a tie between Blue and Red."); else if (blue == maxPoints && blue == green) info = tr("The game ends in a tie between Blue and Green."); else if (yellow == maxPoints && yellow == red) info = tr("The game ends in a tie between Yellow and Red."); else if (yellow == maxPoints && yellow == green) info = tr("The game ends in a tie between Yellow and Green."); else if (red == maxPoints && red == green) info = tr("The game ends in a tie between Red and Green."); else if (blue == maxPoints) info = tr("Blue wins."); else if (yellow == maxPoints) info = tr("Yellow wins."); else if (red == maxPoints) info = tr("Red wins."); else info = tr("Green wins."); } QString detailText; if (m_isRated) { int oldRating = m_history->getRating().toInt(); unsigned place; bool isPlaceShared; bd.get_place(m_ratedGameColor, place, isPlaceShared); float gameResult; if (place == 0 && ! isPlaceShared) gameResult = 1; else if (place == 0 && isPlaceShared) gameResult = 0.5; else gameResult = 0; unsigned nuOpp = get_nu_players(variant) - 1; Rating oppRating = m_player->get_rating(variant); QString date = QString(Tree::get_date_today().c_str()); m_history->addGame(gameResult, oppRating, nuOpp, m_ratedGameColor, gameResult, date, m_level, m_game->get_tree()); if (m_ratingDialog != nullptr) m_ratingDialog->updateContent(); int newRating = m_history->getRating().toInt(); if (newRating > oldRating) detailText = tr("Your rating has increased from %1 to %2.") .arg(oldRating).arg(newRating); else if (newRating == oldRating) detailText = tr("Your rating stays at %1.").arg(oldRating); else detailText = tr("Your rating has decreased from %1 to %2.") .arg(oldRating).arg(newRating); setRated(false); } showInfo(info, detailText, "", true); } void MainWindow::variantClassic(bool checked) { if (checked) setVariant(Variant::classic); } void MainWindow::variantClassic2(bool checked) { if (checked) setVariant(Variant::classic_2); } void MainWindow::variantDuo(bool checked) { if (checked) setVariant(Variant::duo); } void MainWindow::variantJunior(bool checked) { if (checked) setVariant(Variant::junior); } void MainWindow::variantTrigon(bool checked) { if (checked) setVariant(Variant::trigon); } void MainWindow::variantTrigon2(bool checked) { if (checked) setVariant(Variant::trigon_2); } void MainWindow::variantTrigon3(bool checked) { if (checked) setVariant(Variant::trigon_3); } void MainWindow::genMove(bool playSingleMove) { m_genMoveTime.start(); ++m_genMoveId; setCursor(QCursor(Qt::BusyCursor)); m_actionPlay->setEnabled(false); m_actionPlaySingleMove->setEnabled(false); showStatus(tr("The computer is thinking...")); m_actionInterrupt->setEnabled(true); clearSelectedPiece(); clear_abort(); m_lastRemainingSeconds = 0; m_lastRemainingMinutes = 0; m_genMoveInterrupted = false; m_player->set_level(m_level); QFuture future = QtConcurrent::run(this, &MainWindow::asyncGenMove, m_currentColor, m_genMoveId, playSingleMove); m_genMoveWatcher.setFuture(future); m_isGenMoveRunning = true; auto& bd = getBoard(); unsigned nuMoves = bd.get_nu_moves(); if (m_lastComputerMovesBegin == 0 && ! computerPlaysAll()) { m_lastComputerMovesBegin = nuMoves + 1; m_lastComputerMovesEnd = m_lastComputerMovesBegin; } } void MainWindow::genMoveFinished() { auto elapsed = m_genMoveTime.elapsed(); if (elapsed < 800 && ! m_genMoveInterrupted) { // Enforce minimum thinking time QTimer::singleShot(800 - elapsed, this, SLOT(genMoveFinished())); return; } GenMoveResult result = m_genMoveWatcher.future().result(); if (result.genMoveId != m_genMoveId) // Callback from a move generation canceled with cancelThread() return; LIBBOARDGAME_ASSERT(m_isGenMoveRunning); m_isGenMoveRunning = false; setCursor(QCursor(Qt::ArrowCursor)); m_actionInterrupt->setEnabled(false); clearStatus(); if (get_abort() && computerPlaysAll()) m_computerColors.fill(false); auto& bd = getBoard(); Color c = result.color; auto mv = result.move; if (mv.is_null() || ! bd.is_legal(c, mv)) { // No need to translate this message, it should never occur if the // program is correct showError("Computer failed to generate a move"); return; } if (mv.is_pass()) return; m_lastComputerMovesEnd = bd.get_nu_moves() + 1; play(c, mv); // Call updateWindow() before checkComputerMove() because checkComputerMove // resets m_lastComputerMovesBegin if computer doesn't play current color // and updateWindow needs m_lastComputerMovesBegin updateWindow(true); if (! result.playSingleMove) checkComputerMove(); } QString MainWindow::getFilter() const { return tr("Blokus games (*.blksgf);;All files (*.*)"); } QString MainWindow::getLastDir() { QSettings settings; auto dir = settings.value("last_dir", "").toString(); if (dir.isEmpty() || ! QFileInfo(dir).exists()) #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) dir = QStandardPaths::writableLocation( QStandardPaths::DocumentsLocation); #else dir = QDesktopServices::storageLocation( QDesktopServices::DocumentsLocation); #endif return dir; } QString MainWindow::getVersion() const { QString version; #ifdef VERSION version = VERSION; #endif if (version.isEmpty()) version = "UNKNOWN"; return version; } void MainWindow::goodMove(bool checked) { if (! checked) return; m_game->set_good_move(); updateWindow(false); } void MainWindow::gotoMove() { QSettings settings; vector nodes; auto& tree = m_game->get_tree(); auto node = &m_game->get_current(); do { if (! tree.get_move(*node).is_null()) nodes.insert(nodes.begin(), node); node = node->get_parent_or_null(); } while (node != nullptr); node = m_game->get_current().get_first_child_or_null(); while (node != nullptr) { if (! tree.get_move(*node).is_null()) nodes.push_back(node); node = node->get_first_child_or_null(); } int maxMoves = int(nodes.size()); if (maxMoves == 0) return; int defaultValue = getBoard().get_nu_moves(); if (defaultValue == 0) defaultValue = maxMoves; QInputDialog dialog(this); dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); dialog.setWindowTitle(tr("Go to Move")); dialog.setLabelText(tr("Move number:")); dialog.setInputMode(QInputDialog::IntInput); dialog.setIntRange(1, static_cast(nodes.size())); dialog.setIntStep(1); dialog.setIntValue(defaultValue); if (! dialog.exec()) return; gotoNode(*nodes[dialog.intValue() - 1]); } void MainWindow::gotoNode(const Node& node) { cancelThread(); leaveSetupMode(); try { m_game->goto_node(node); } catch (const InvalidTree& e) { showInvalidFile(m_file, e); return; } m_currentColor = getCurrentColor(*m_game); m_lastComputerMovesBegin = 0; if (m_analyzeGameWindow != 0 && m_analyzeGameWindow->isVisible()) m_analyzeGameWindow->analyzeGameWidget ->setCurrentPosition(*m_game, node); m_autoPlay = false; updateWindow(true); } void MainWindow::gotoPosition(Variant variant, const vector& moves) { if (getVariant() != variant) return; auto& tree = m_game->get_tree(); auto node = &tree.get_root(); if (tree.has_move_ignore_invalid(*node)) // Move in root node not supported. return; for (ColorMove mv : moves) { bool found = false; for (ChildIterator i(*node); i; ++i) if (tree.get_move(*i) == mv) { found = true; node = &(*i); break; } if (! found) return; } gotoNode(*node); } void MainWindow::help() { if (m_helpWindow != nullptr) { m_helpWindow->show(); m_helpWindow->raise(); return; } QString path = HelpWindow::findMainPage(m_manualDir, "index.html", QLocale::system().name()); m_helpWindow = new HelpWindow(0, path); m_helpWindow->show(); } void MainWindow::initGame() { setRated(false); if (m_analyzeGameWindow != nullptr) { delete m_analyzeGameWindow; m_analyzeGameWindow = nullptr; } m_game->init(); m_game->set_charset("UTF-8"); #ifdef VERSION m_game->set_application("Pentobi", VERSION); #else m_game->set_application("Pentobi"); #endif m_game->set_date_today(); m_game->clear_modified(); QSettings settings; if (! settings.value("computer_color_none").toBool()) { auto& bd = getBoard(); for (ColorIterator i(bd.get_nu_colors()); i; ++i) m_computerColors[*i] = ! bd.is_same_player(*i, Color(0)); m_autoPlay = true; } else { m_computerColors.fill(false); m_autoPlay = false; } m_currentColor = Color(0); leaveSetupMode(); m_lastComputerMovesBegin = 0; m_gameFinished = false; m_isAutoSaveLoaded = false; setFile(""); } void MainWindow::initVariantActions() { switch (getVariant()) { case Variant::classic: m_actionVariantClassic->setChecked(true); break; case Variant::classic_2: m_actionVariantClassic2->setChecked(true); break; case Variant::duo: m_actionVariantDuo->setChecked(true); break; case Variant::junior: m_actionVariantJunior->setChecked(true); break; case Variant::trigon: m_actionVariantTrigon->setChecked(true); break; case Variant::trigon_2: m_actionVariantTrigon2->setChecked(true); break; case Variant::trigon_3: m_actionVariantTrigon3->setChecked(true); break; } } void MainWindow::initPieceSelectors() { auto& bd = getBoard(); for (unsigned i = 0; i < Color::range; ++i) { bool isVisible = (i < bd.get_nu_colors()); m_pieceSelector[Color(i)]->setVisible(isVisible); if (isVisible) m_pieceSelector[Color(i)]->init(); } } void MainWindow::interestingMove(bool checked) { if (! checked) return; m_game->set_interesting_move(); updateWindow(false); } void MainWindow::interrupt() { cancelThread(); m_autoPlay = false; } void MainWindow::interruptPlay() { if (! m_isGenMoveRunning) return; set_abort(); m_genMoveInterrupted = true; m_autoPlay = false; } void MainWindow::keepOnlyPosition() { QMessageBox msgBox(this); initQuestion(msgBox, tr("Keep only position?"), tr("All previous and following moves and variations will" " be removed from the game tree.")); auto keepOnlyPositionButton = msgBox.addButton(tr("Keep Only Position"), QMessageBox::DestructiveRole); auto cancelButton = msgBox.addButton(QMessageBox::Cancel); msgBox.setDefaultButton(cancelButton); msgBox.exec(); if (msgBox.clickedButton() != keepOnlyPositionButton) return; cancelThread(); m_game->keep_only_position(); updateWindow(true); } void MainWindow::keepOnlySubtree() { QMessageBox msgBox(this); initQuestion(msgBox, tr("Keep only subtree?"), tr("All previous moves and variations will be removed" " from the game tree.")); auto keepOnlySubtreeButton = msgBox.addButton(tr("Keep Only Subtree"), QMessageBox::DestructiveRole); auto cancelButton = msgBox.addButton(QMessageBox::Cancel); msgBox.setDefaultButton(cancelButton); msgBox.exec(); if (msgBox.clickedButton() != keepOnlySubtreeButton) return; cancelThread(); m_game->keep_only_subtree(); updateWindow(true); } void MainWindow::leaveFullscreen() { if (! isFullScreen()) return; QSettings settings; auto showToolbar = settings.value("toolbar", true).toBool(); menuBar()->show(); m_toolBar->setVisible(showToolbar); // m_leaveFullscreenButton can be null if the window was put in fullscreen // mode by a "generic" method by the window manager (e.g. the title bar // menu on KDE) and not by MainWindow::fullscreen() if (m_leaveFullscreenButton != nullptr) m_leaveFullscreenButton->hideButton(); showNormal(); } void MainWindow::leaveSetupMode() { if (! m_actionSetupMode->isChecked()) return; setupMode(false); } void MainWindow::loadHistory() { auto variant = m_game->get_variant(); if (m_history->getVariant() == variant) return; m_history->load(variant); if (m_ratingDialog != nullptr) m_ratingDialog->updateContent(); } void MainWindow::makeMainVariation() { m_game->make_main_variation(); updateWindow(false); } void MainWindow::moveDownVariation() { m_game->move_down_variation(); updateWindow(false); } void MainWindow::moveUpVariation() { m_game->move_up_variation(); updateWindow(false); } void MainWindow::nextPiece() { auto& bd = getBoard(); const Board::PiecesLeftList& piecesLeft = bd.get_pieces_left(m_currentColor); unsigned nuPiecesLeft = piecesLeft.size(); if (nuPiecesLeft == 0) return; Piece piece = m_guiBoard->getSelectedPiece(); if (piece.is_null()) piece = piecesLeft[0]; else { for (unsigned i = 0; i < nuPiecesLeft; ++i) if (piecesLeft[i] == piece) { if (i + 1 >= nuPiecesLeft) piece = piecesLeft[0]; else piece = piecesLeft[i + 1]; break; } } selectPiece(m_currentColor, piece); } void MainWindow::nextTransform() { Piece piece = m_guiBoard->getSelectedPiece(); if (piece.is_null()) return; auto transform = m_guiBoard->getSelectedPieceTransform(); transform = getBoard().get_piece_info(piece).get_next_transform(transform); m_guiBoard->setSelectedPieceTransform(transform); m_orientationDisplay->setSelectedPieceTransform(transform); } void MainWindow::nextVariation() { auto node = m_game->get_current().get_sibling(); if (node == nullptr) return; gotoNode(*node); } void MainWindow::nextVariation10() { auto node = &m_game->get_current(); for (unsigned i = 0; i < 10; ++i) { if (node->get_sibling() == nullptr) break; node = node->get_sibling(); } gotoNode(*node); } void MainWindow::newRatedGame() { if (! checkSave()) return; cancelThread(); if (m_history->getNuGames() == 0) { InitialRatingDialog dialog(this); if (dialog.exec() != QDialog::Accepted) return; m_history->init(Rating(dialog.getRating())); } int level; m_history->getNextRatedGameSettings(maxLevel, level, m_ratedGameColor); QMessageBox msgBox(this); initQuestion(msgBox, tr("Start new rated game?"), "" + tr("In the next game, you will play %1 against" " Pentobi level %2.") .arg(getPlayerString(getVariant(), m_ratedGameColor)) .arg(level)); auto startGameButton = msgBox.addButton(tr("&Start Game"), QMessageBox::AcceptRole); msgBox.addButton(QMessageBox::Cancel); msgBox.setDefaultButton(startGameButton); msgBox.exec(); auto result = msgBox.clickedButton(); if (result != startGameButton) return; setLevel(level); initGame(); setFile(""); setRated(true); auto& bd = getBoard(); m_computerColors.fill(true); for (ColorIterator i(bd.get_nu_colors()); i; ++i) if (bd.is_same_player(*i, m_ratedGameColor)) m_computerColors[*i] = false; m_autoPlay = true; QString computerPlayerName = //: The first argument is the version of Pentobi tr("Pentobi %1 (level %2)").arg(getVersion()).arg(level); string charset = m_game->get_root().get_property("CA", ""); string computerPlayerNameStdStr = Util::convertSgfValueFromQString(computerPlayerName, charset); string humanPlayerNameStdStr = Util::convertSgfValueFromQString(tr("Human"), charset); for (ColorIterator i(bd.get_nu_colors()); i; ++i) if (m_computerColors[*i]) m_game->set_player_name(*i, computerPlayerNameStdStr); else m_game->set_player_name(*i, humanPlayerNameStdStr); // Setting the player names marks the game as modified but there is nothing // important that would need to be saved yet m_game->clear_modified(); deleteAutoSaveFile(); updateWindow(true); checkComputerMove(); } void MainWindow::newGame() { if (! checkSave()) return; cancelThread(); initGame(); deleteAutoSaveFile(); updateWindow(true); } void MainWindow::noMoveAnnotation(bool checked) { if (! checked) return; m_game->remove_move_annotation(); updateWindow(false); } void MainWindow::open() { if (! checkSave()) return; QSettings settings; QString file = QFileDialog::getOpenFileName(this, tr("Open"), getLastDir(), getFilter()); if (file.isEmpty()) return; rememberDir(file); if (open(file)) rememberFile(file); } bool MainWindow::open(const QString& file, bool isTemporary) { if (file.isEmpty()) return false; cancelThread(); TreeReader reader; ifstream in(file.toLocal8Bit().constData()); try { reader.read(in); } catch (const TreeReader::ReadError& e) { if (! in) { QString text = tr("Could not read file '%1'").arg(QFileInfo(file).fileName()); showError(text, strerror(errno)); } else { showInvalidFile(file, e); } return false; } m_isAutoSaveLoaded = false; if (! isTemporary) { setFile(file); deleteAutoSaveFile(); } if (m_analyzeGameWindow != nullptr) { delete m_analyzeGameWindow; m_analyzeGameWindow = nullptr; } setRated(false); try { auto tree = reader.get_tree_transfer_ownership(); m_game->init(tree); if (! libpentobi_base::node_util::has_setup(m_game->get_root())) m_game->goto_node(get_last_node(m_game->get_root())); m_currentColor = getCurrentColor(*m_game); initPieceSelectors(); } catch (const InvalidTree& e) { showInvalidFile(file, e); } m_computerColors.fill(false); m_autoPlay = false; leaveSetupMode(); m_lastComputerMovesBegin = 0; initVariantActions(); updateWindow(true); loadHistory(); return true; } void MainWindow::openRecentFile() { auto action = qobject_cast(sender()); if (action == nullptr) return; if (! checkSave()) return; open(action->data().toString()); } void MainWindow::orientationDisplayColorClicked(Color) { if (m_actionSetupMode->isChecked()) selectNextColor(); } void MainWindow::placePiece(Color c, Move mv) { cancelThread(); bool isSetupMode = m_actionSetupMode->isChecked(); if (m_computerColors[c] || isSetupMode) // If the user enters a move previously played by the computer (e.g. // after undoing moves) then it is unlikely that the user wants to keep // the computer color settings. m_computerColors.fill(false); if (isSetupMode) { m_game->add_setup(c, mv); setSetupPlayer(); updateWindow(true); } else { play(c, mv); updateWindow(true); checkComputerMove(); } } void MainWindow::play() { cancelThread(); leaveSetupMode(); auto variant = getVariant(); if (variant != Variant::classic && variant != Variant::trigon && variant != Variant::trigon_3) { QSettings settings; settings.setValue("computer_color_none", false); } if (! m_computerColors[m_currentColor]) { m_computerColors.fill(false); m_computerColors[m_currentColor] = true; if (variant == Variant::classic_2 || variant == Variant::trigon_2) { if (m_currentColor == Color(0) || m_currentColor == Color(2)) m_computerColors[Color(0)] = m_computerColors[Color(2)] = true; else m_computerColors[Color(1)] = m_computerColors[Color(3)] = true; } else m_computerColors[m_currentColor] = true; } m_autoPlay = true; m_lastComputerMovesBegin = 0; genMove(); } void MainWindow::play(Color c, Move mv) { auto& bd = getBoard(); m_game->play(c, mv, false); c = m_game->get_to_play(); m_gameFinished = false; if (bd.is_game_over()) { updateWindow(true); repaint(); gameOver(); m_gameFinished = true; deleteAutoSaveFile(); return; } m_currentColor = m_game->get_effective_to_play(); } void MainWindow::playSingleMove() { cancelThread(); leaveSetupMode(); m_autoPlay = false; m_lastComputerMovesBegin = 0; genMove(true); } void MainWindow::pointClicked(Point p) { if (! m_actionSetupMode->isChecked()) return; auto& bd = getBoard(); PointState s = bd.get_point_state(p); if (s.is_empty()) return; m_game->remove_setup(s.to_color(), bd.get_played_move(p)); setSetupPlayer(); updateWindow(true); } void MainWindow::previousPiece() { auto& bd = getBoard(); auto& piecesLeft = bd.get_pieces_left(m_currentColor); unsigned nuPiecesLeft = piecesLeft.size(); if (nuPiecesLeft == 0) return; Piece piece = m_guiBoard->getSelectedPiece(); if (piece.is_null()) piece = piecesLeft[nuPiecesLeft - 1]; else { for (unsigned i = 0; i < nuPiecesLeft; ++i) if (piecesLeft[i] == piece) { if (i == 0) piece = piecesLeft[nuPiecesLeft - 1]; else piece = piecesLeft[i - 1]; break; } } selectPiece(m_currentColor, piece); } void MainWindow::previousTransform() { Piece piece = m_guiBoard->getSelectedPiece(); if (piece.is_null()) return; auto transform = m_guiBoard->getSelectedPieceTransform(); transform = getBoard().get_piece_info(piece).get_previous_transform(transform); m_guiBoard->setSelectedPieceTransform(transform); m_orientationDisplay->setSelectedPieceTransform(transform); } void MainWindow::previousVariation() { auto node = m_game->get_current().get_previous_sibling(); if (node == nullptr) return; gotoNode(*node); } void MainWindow::previousVariation10() { auto node = &m_game->get_current(); for (unsigned i = 0; i < 10; ++i) { if (node->get_previous_sibling() == nullptr) break; node = node->get_previous_sibling(); } gotoNode(*node); } void MainWindow::rememberDir(const QString& file) { if (file.isEmpty()) return; QString canonicalFile = file; QString canonicalFilePath = QFileInfo(file).canonicalFilePath(); if (! canonicalFilePath.isEmpty()) canonicalFile = canonicalFilePath; QFileInfo info(canonicalFile); QSettings settings; settings.setValue("last_dir", info.dir().path()); } void MainWindow::rememberFile(const QString& file) { if (file.isEmpty()) return; QString canonicalFile = file; QString canonicalFilePath = QFileInfo(file).canonicalFilePath(); if (! canonicalFilePath.isEmpty()) canonicalFile = canonicalFilePath; QFileInfo info(canonicalFile); QSettings settings; auto files = settings.value("recent_files").toStringList(); files.removeAll(canonicalFile); files.prepend(canonicalFile); while (files.size() > maxRecentFiles) files.removeLast(); settings.setValue("recent_files", files); settings.sync(); // updateRecentFiles() needs the new settings updateRecentFiles(); } void MainWindow::rotatePieceAnticlockwise() { Piece piece = m_guiBoard->getSelectedPiece(); if (piece.is_null()) return; auto& bd = getBoard(); auto transform = m_guiBoard->getSelectedPieceTransform(); transform = bd.get_transforms().get_rotated_anticlockwise(transform); transform = bd.get_piece_info(piece).get_equivalent_transform(transform); m_guiBoard->setSelectedPieceTransform(transform); m_orientationDisplay->setSelectedPieceTransform(transform); updateFlipActions(); } void MainWindow::rotatePieceClockwise() { Piece piece = m_guiBoard->getSelectedPiece(); if (piece.is_null()) return; auto& bd = getBoard(); auto transform = m_guiBoard->getSelectedPieceTransform(); transform = bd.get_transforms().get_rotated_clockwise(transform); transform = bd.get_piece_info(piece).get_equivalent_transform(transform); m_guiBoard->setSelectedPieceTransform(transform); m_orientationDisplay->setSelectedPieceTransform(transform); updateFlipActions(); } void MainWindow::save() { if (m_file.isEmpty()) { saveAs(); return; } if (save(m_file)) { m_game->clear_modified(); updateWindow(false); } } bool MainWindow::save(const QString& file) { if (! writeGame(file.toLocal8Bit().constData())) { showError(tr("The file could not be saved."), /*: Error message if file cannot be saved. %1 is replaced by the file name, %2 by the error message of the operating system. */ tr("%1: %2").arg(file).arg(strerror(errno))); return false; } else { Util::removeThumbnail(file); showStatus(tr("Game saved: %1").arg(file), true); return true; } } void MainWindow::saveAs() { QString file = m_file; if (file.isEmpty()) { file = getLastDir(); file.append(QDir::separator()); file.append(tr("Untitled Game.blksgf")); if (QFileInfo(file).exists()) for (unsigned i = 1; ; ++i) { file = getLastDir(); file.append(QDir::separator()); file.append(tr("Untitled Game %1.blksgf").arg(i)); if (! QFileInfo(file).exists()) break; } } file = QFileDialog::getSaveFileName(this, tr("Save"), file, getFilter()); if (! file.isEmpty()) { if (save(file)) { m_game->clear_modified(); updateWindow(false); } setFile(file); rememberFile(file); } } void MainWindow::searchCallback(double elapsedSeconds, double remainingSeconds) { // If the search is longer than 10 sec, we show the (maximum) remaining // time (only during a move generation, ignore search callbacks during // game analysis) if (! m_isGenMoveRunning || elapsedSeconds < 10) return; QString text; int seconds = static_cast(ceil(remainingSeconds)); if (seconds < 90) { if (seconds == m_lastRemainingSeconds) return; m_lastRemainingSeconds = seconds; text = tr("The computer is thinking... (max. %1 seconds remaining)") .arg(seconds); } else { int minutes = static_cast(ceil(remainingSeconds / 60)); if (minutes == m_lastRemainingMinutes) return; m_lastRemainingMinutes = minutes; text = tr("The computer is thinking... (max. %1 minutes remaining)") .arg(minutes); } QMetaObject::invokeMethod(statusBar(), "showMessage", Q_ARG(QString, text), Q_ARG(int, 0)); } void MainWindow::selectNamedPiece(const char* name1, const char* name2, const char* name3, const char* name4) { auto& bd = getBoard(); vector pieces; Piece piece; if (bd.get_piece_by_name(name1, piece) && bd.is_piece_left(m_currentColor, piece)) pieces.push_back(piece); if (name2 != nullptr && bd.get_piece_by_name(name2, piece) && bd.is_piece_left(m_currentColor, piece)) pieces.push_back(piece); if (name3 != nullptr && bd.get_piece_by_name(name3, piece) && bd.is_piece_left(m_currentColor, piece)) pieces.push_back(piece); if (name4 != nullptr && bd.get_piece_by_name(name4, piece) && bd.is_piece_left(m_currentColor, piece)) pieces.push_back(piece); if (pieces.empty()) return; piece = m_guiBoard->getSelectedPiece(); if (piece.is_null()) piece = pieces[0]; else { auto pos = std::find(pieces.begin(), pieces.end(), piece); if (pos == pieces.end()) piece = pieces[0]; else { ++pos; if (pos == pieces.end()) piece = pieces[0]; else piece = *pos; } } selectPiece(m_currentColor, piece); } void MainWindow::selectNextColor() { auto& bd = getBoard(); m_currentColor = bd.get_next(m_currentColor); m_orientationDisplay->selectColor(m_currentColor); clearSelectedPiece(); for (ColorIterator i(bd.get_nu_colors()); i; ++i) m_pieceSelector[*i]->setEnabled(m_currentColor == *i); if (m_actionSetupMode->isChecked()) setSetupPlayer(); updateWindow(false); } void MainWindow::selectPiece(Color c, Piece piece) { selectPiece(c, piece, getBoard().get_transforms().get_default()); } void MainWindow::selectPiece(Color c, Piece piece, const Transform* transform) { m_currentColor = c; m_guiBoard->selectPiece(c, piece); m_guiBoard->setSelectedPieceTransform(transform); m_orientationDisplay->selectColor(c); m_orientationDisplay->setSelectedPiece(piece); m_orientationDisplay->setSelectedPieceTransform(transform); bool can_rotate = getBoard().get_piece_info(piece).can_rotate(); m_actionRotatePieceClockwise->setEnabled(can_rotate); m_actionRotatePieceAnticlockwise->setEnabled(can_rotate); updateFlipActions(); m_actionClearSelectedPiece->setEnabled(true); } void MainWindow::selectPiece1() { selectNamedPiece("1"); } void MainWindow::selectPiece2() { selectNamedPiece("2"); } void MainWindow::selectPieceA() { selectNamedPiece("A6", "A4"); } void MainWindow::selectPieceC() { selectNamedPiece("C5", "C4"); } void MainWindow::selectPieceF() { selectNamedPiece("F"); } void MainWindow::selectPieceG() { selectNamedPiece("G"); } void MainWindow::selectPieceI() { selectNamedPiece("I6", "I5", "I4", "I3"); } void MainWindow::selectPieceL() { selectNamedPiece("L6", "L5", "L4"); } void MainWindow::selectPieceN() { selectNamedPiece("N"); } void MainWindow::selectPieceO() { selectNamedPiece("O"); } void MainWindow::selectPieceP() { selectNamedPiece("P6", "P5", "P"); } void MainWindow::selectPieceS() { selectNamedPiece("S"); } void MainWindow::selectPieceT() { selectNamedPiece("T5", "T4"); } void MainWindow::selectPieceU() { selectNamedPiece("U"); } void MainWindow::selectPieceV() { selectNamedPiece("V", "V5", "V3"); } void MainWindow::selectPieceW() { selectNamedPiece("W"); } void MainWindow::selectPieceX() { selectNamedPiece("X"); } void MainWindow::selectPieceY() { selectNamedPiece("Y"); } void MainWindow::selectPieceZ() { selectNamedPiece("Z5", "Z4"); } void MainWindow::setCommentText(const QString& text) { m_ignoreCommentTextChanged = true; m_comment->setPlainText(text); m_ignoreCommentTextChanged = false; if (! text.isEmpty()) m_comment->ensureCursorVisible(); m_comment->clearFocus(); } void MainWindow::setDeterministic() { m_player->get_search().set_deterministic(); } void MainWindow::setVariant(Variant variant) { if (getVariant() == variant) return; if (! checkSave()) { initVariantActions(); return; } cancelThread(); QSettings settings; settings.setValue("variant", to_string_id(variant)); clearSelectedPiece(); m_game->init(variant); initPieceSelectors(); newGame(); loadHistory(); } void MainWindow::setFile(const QString& file) { m_file = file; // Don't use setWindowFilePath(), it is buggy in Qt 4.6. if (m_file.isEmpty()) setWindowTitle(tr("Pentobi")); else setWindowTitle(tr("[*]%1").arg(QFileInfo(m_file).fileName())); } void MainWindow::setLevel(int level) { if (level <= 0 || level > maxLevel) return; m_level = level; m_actionLevel[level - 1]->setChecked(true); QSettings settings; settings.setValue("level", m_level); } void MainWindow::setLevel(bool checked) { if (! checked) return; setLevel(qobject_cast(sender())->data().toInt()); } void MainWindow::setMoveNumbersAll(bool checked) { if (checked) { QSettings settings; settings.setValue("move_numbers", "all"); updateWindow(false); } } void MainWindow::setMoveNumbersLast(bool checked) { if (checked) { QSettings settings; settings.setValue("move_numbers", "last"); updateWindow(false); } } void MainWindow::setMoveNumbersNone(bool checked) { if (checked) { QSettings settings; settings.setValue("move_numbers", "none"); updateWindow(false); } } void MainWindow::setPlayToolTip() { QString s; auto variant = getVariant(); Color c = m_currentColor; bool isComputerColor = m_computerColors[m_currentColor]; if (variant == Variant::classic_2 || variant == Variant::trigon_2) { if (c == Color(0) || c == Color(2)) { if (isComputerColor) s = tr("Make the computer continue to play Blue/Red"); else s = tr("Make the computer play Blue/Red"); } else { if (isComputerColor) s = tr("Make the computer continue to play Yellow/Green"); else s = tr("Make the computer play Yellow/Green"); } } else { bool isTwoColorVariant = (variant == Variant::duo || variant == Variant::junior); if (c == Color(0)) { if (isComputerColor) s = tr("Make the computer continue to play Blue"); else s = tr("Make the computer play Blue"); } else if (c == Color(1) && ! isTwoColorVariant) { if (isComputerColor) s = tr("Make the computer continue to play Yellow"); else s = tr("Make the computer play Yellow"); } else if ((c == Color(1) && isTwoColorVariant) || (c == Color(3) && ! isTwoColorVariant)) { if (isComputerColor) s = tr("Make the computer continue to play Green"); else s = tr("Make the computer play Green"); } else { if (isComputerColor) s = tr("Make the computer continue to play Red"); else s = tr("Make the computer play Red"); } } m_actionPlay->setToolTip(s); } void MainWindow::setRated(bool isRated) { m_isRated = isRated; if (isRated) { statusBar()->addWidget(m_ratedGameLabel); m_ratedGameLabel->show(); } else statusBar()->removeWidget(m_ratedGameLabel); } void MainWindow::setSetupPlayer() { if (! m_game->has_setup()) m_game->remove_player(); else m_game->set_player(m_currentColor); } void MainWindow::setupMode(bool enable) { // Currently, we allow setup mode only if no moves have been played. It // should also work in inner nodes but this might be confusing for users // and violate some assumptions in the user interface (e.g. node depth is // equal to move number). Therefore, m_actionSetupMode is disabled if the // root node has children, but we still need to check for it here because // due to bugs in the Unitiy interface in Ubuntu 11.10, menu items are // not always disabled if the corresponding action is disabled. if (enable && m_game->get_root().has_children()) { showInfo(tr("Setup mode cannot be used if moves have been played.")); enable = false; } m_actionSetupMode->setChecked(enable); m_guiBoard->setFreePlacement(enable); if (enable) { m_setupModeLabel->show(); for (ColorIterator i(getBoard().get_nu_colors()); i; ++i) m_pieceSelector[*i]->setEnabled(true); m_computerColors.fill(false); } else { setSetupPlayer(); m_setupModeLabel->hide(); enablePieceSelector(m_currentColor); } } void MainWindow::showComment(bool checked) { QSettings settings; bool wasVisible = m_comment->isVisible(); if (wasVisible && ! checked) settings.setValue("splitter_state", m_splitter->saveState()); settings.setValue("show_comment", checked); m_comment->setVisible(checked); if (! wasVisible && checked) m_splitter->restoreState( settings.value("splitter_state").toByteArray()); } void MainWindow::showError(const QString& text, const QString& infoText, const QString& detailText) { ::showError(this, text, infoText, detailText); } void MainWindow::showInfo(const QString& text, const QString& infoText, const QString& detailText, bool withIcon) { ::showInfo(this, text, infoText, detailText, withIcon); } void MainWindow::showInvalidFile(QString file, const Exception& e) { showError(tr("Error in file '%1'").arg(QFileInfo(file).fileName()), tr("The file is not a valid Blokus SGF file."), e.what()); } void MainWindow::showRating() { if (m_ratingDialog == nullptr) { m_ratingDialog = new RatingDialog(this, *m_history); connect(m_ratingDialog, SIGNAL(open(const QString&)), SLOT(open(const QString&))); } loadHistory(); m_ratingDialog->show(); } void MainWindow::showStatus(const QString& text, bool temporary) { int timeout = (temporary ? 4000 : 0); statusBar()->showMessage(text, timeout); } void MainWindow::showToolbar(bool checked) { QSettings settings; settings.setValue("toolbar", checked); m_toolBar->setVisible(checked); m_menuToolBarText->setEnabled(checked); } QSize MainWindow::sizeHint() const { return QSize(1020, 634); } void MainWindow::toolBarNoText(bool checked) { if (! checked) return; QSettings settings; settings.setValue("toolbar_text", "no_text"); m_toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); } void MainWindow::toolBarTextBesideIcons(bool checked) { if (! checked) return; QSettings settings; settings.setValue("toolbar_text", "beside_icons"); m_toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); } void MainWindow::toolBarTextBelowIcons(bool checked) { if (! checked) return; QSettings settings; settings.setValue("toolbar_text", "below_icons"); m_toolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); } void MainWindow::toolBarTextOnly(bool checked) { if (! checked) return; QSettings settings; settings.setValue("toolbar_text", "text_only"); m_toolBar->setToolButtonStyle(Qt::ToolButtonTextOnly); } void MainWindow::toolBarTextSystem(bool checked) { if (! checked) return; QSettings settings; settings.setValue("toolbar_text", "system"); m_toolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle); } void MainWindow::truncate() { auto& current = m_game->get_current(); if (! current.has_parent()) return; cancelThread(); if (current.has_children()) { QMessageBox msgBox(this); initQuestion(msgBox, tr("Truncate this subtree?"), tr("This position and all following moves and" " variations will be removed from the game tree.")); auto truncateButton = msgBox.addButton(tr("Truncate"), QMessageBox::DestructiveRole); auto cancelButton = msgBox.addButton(QMessageBox::Cancel); msgBox.setDefaultButton(cancelButton); msgBox.exec(); if (msgBox.clickedButton() != truncateButton) return; } m_game->truncate(); m_currentColor = getCurrentColor(*m_game); updateWindow(true); } void MainWindow::truncateChildren() { if (! m_game->get_current().has_children()) return; cancelThread(); QMessageBox msgBox(this); initQuestion(msgBox, tr("Truncate children?"), tr("All following moves and variations will" " be removed from the game tree.")); auto truncateButton = msgBox.addButton(tr("Truncate Children"), QMessageBox::DestructiveRole); auto cancelButton = msgBox.addButton(QMessageBox::Cancel); msgBox.setDefaultButton(cancelButton); msgBox.exec(); if (msgBox.clickedButton() != truncateButton) return; m_game->truncate_children(); updateWindow(false); } void MainWindow::showVariations(bool checked) { { QSettings settings; settings.setValue("show_variations", checked); } updateWindow(false); } void MainWindow::undo() { auto& current = m_game->get_current(); if (current.has_children() || ! m_game->get_tree().has_move_ignore_invalid(current)) return; truncate(); } void MainWindow::updateComment() { string comment = m_game->get_comment(); if (comment.empty()) { setCommentText(""); return; } string charset = m_game->get_root().get_property("CA", ""); setCommentText(Util::convertSgfValueToQString(comment, charset)); } void MainWindow::updateFlipActions() { Piece piece = m_guiBoard->getSelectedPiece(); if (piece.is_null()) return; auto transform = m_guiBoard->getSelectedPieceTransform(); bool can_flip_horizontally = getBoard().get_piece_info(piece).can_flip_horizontally(transform); m_actionFlipPieceHorizontally->setEnabled(can_flip_horizontally); bool can_flip_vertically = getBoard().get_piece_info(piece).can_flip_vertically(transform); m_actionFlipPieceVertically->setEnabled(can_flip_vertically); } void MainWindow::updateMoveAnnotationActions() { if (m_game->get_move_ignore_invalid().is_null()) { m_menuMoveAnnotation->setEnabled(false); return; } m_menuMoveAnnotation->setEnabled(true); double goodMove = m_game->get_good_move(); if (goodMove > 1) { m_actionVeryGoodMove->setChecked(true); return; } if (goodMove > 0) { m_actionGoodMove->setChecked(true); return; } double badMove = m_game->get_bad_move(); if (badMove > 1) { m_actionVeryBadMove->setChecked(true); return; } if (badMove > 0) { m_actionBadMove->setChecked(true); return; } if (m_game->is_interesting_move()) { m_actionInterestingMove->setChecked(true); return; } if (m_game->is_doubtful_move()) { m_actionDoubtfulMove->setChecked(true); return; } m_actionNoMoveAnnotation->setChecked(true); } void MainWindow::updateMoveNumber() { auto& tree = m_game->get_tree(); auto& current = m_game->get_current(); unsigned move = get_move_number(tree, current); unsigned movesLeft = get_moves_left(tree, current); unsigned totalMoves = move + movesLeft; string variation = get_variation_string(current); QString text; QString toolTip; if (variation.empty()) { if (movesLeft == 0) { // If last move in main variation, show the number only if it is // not already displayed on the board. if (! m_actionMoveNumbersLast->isChecked()) { text = QString("%1").arg(move); toolTip = tr("Move %1").arg(move); } } else { text = QString("%1/%2").arg(move).arg(totalMoves); if (move == 0) toolTip = tr("%n move(s)", "", totalMoves); else toolTip = tr("Move %1 of %2").arg(move).arg(totalMoves); } } else { if (movesLeft == 0) text = QString("%1 (%2)").arg(move).arg(variation.c_str()); else text = QString("%1/%2 (%3)") .arg(move).arg(totalMoves).arg(variation.c_str()); toolTip = tr("Move %1 of %2 in variation %3") .arg(move).arg(totalMoves).arg(variation.c_str()); } if (text.isEmpty()) statusBar()->removeWidget(m_moveNumber); else { m_moveNumber->setText(text); m_moveNumber->setToolTip(toolTip); if (! m_moveNumber->isVisible()) { statusBar()->addPermanentWidget(m_moveNumber); m_moveNumber->show(); } } } void MainWindow::updateRecentFiles() { QSettings settings; auto files = settings.value("recent_files").toStringList(); for (int i = 0; i < files.size(); ++i) if (! QFileInfo(files[i]).exists()) { files.removeAt(i); --i; } int nuRecentFiles = files.size(); if (nuRecentFiles > maxRecentFiles) nuRecentFiles = maxRecentFiles; m_menuOpenRecent->setEnabled(nuRecentFiles > 0); for (int i = 0; i < nuRecentFiles; ++i) { QFileInfo info = QFileInfo(files[i]); QString name = info.absoluteFilePath(); QString text; #ifdef Q_WS_MAC const bool isMac = true; #else const bool isMac = false; #endif if (! isMac && i + 1 <= 9) text = /*: Label in Recent Files menu. The first 10 items are numbered to provide a mnemonic. %1 is replaced by the number, %2 by the file name. */ tr("&%1: %2").arg(i + 1).arg(name); else text = QString("%1").arg(name); m_actionRecentFile[i]->setText(text); m_actionRecentFile[i]->setData(files[i]); m_actionRecentFile[i]->setVisible(true); } for (int j = nuRecentFiles; j < maxRecentFiles; ++j) m_actionRecentFile[j]->setVisible(false); } void MainWindow::updateWindow(bool currentNodeChanged) { auto& bd = getBoard(); updateWindowModified(); m_guiBoard->copyFromBoard(bd); QSettings settings; auto markVariations = settings.value("show_variations", true).toBool(); unsigned nuMoves = bd.get_nu_moves(); unsigned markMovesBegin = 0; unsigned markMovesEnd = 0; if (m_actionMoveNumbersAll->isChecked()) { markMovesBegin = 1; markMovesEnd = nuMoves; } else if (m_actionMoveNumbersLast->isChecked()) { if (m_lastComputerMovesBegin != 0) { markMovesBegin = m_lastComputerMovesBegin; markMovesEnd = m_lastComputerMovesEnd; } else { markMovesBegin = nuMoves; markMovesEnd = nuMoves; } } gui_board_util::setMarkup(*m_guiBoard, *m_game, markMovesBegin, markMovesEnd, markVariations); m_scoreDisplay->updateScore(bd); m_legalMoves->clear(); m_legalMoveIndex = 0; bool isGameOver = bd.is_game_over(); if (isGameOver && ! m_actionSetupMode->isChecked()) m_orientationDisplay->clearSelectedColor(); else m_orientationDisplay->selectColor(m_currentColor); if (currentNodeChanged) { clearSelectedPiece(); for (ColorIterator i(bd.get_nu_colors()); i; ++i) m_pieceSelector[*i]->checkUpdate(); if (! m_actionSetupMode->isChecked()) enablePieceSelector(m_currentColor); updateComment(); updateMoveAnnotationActions(); } updateMoveNumber(); setPlayToolTip(); auto& tree = m_game->get_tree(); auto& current = m_game->get_current(); bool isMain = is_main_variation(current); bool hasEarlierVariation = has_earlier_variation(current); bool hasParent = current.has_parent(); bool hasChildren = current.has_children(); bool hasMove = tree.has_move_ignore_invalid(current); bool hasMoves = bd.has_moves(m_currentColor); m_actionAnalyzeGame->setEnabled(tree.has_main_variation_moves()); m_actionBackToMainVariation->setEnabled(! isMain); m_actionBeginning->setEnabled(hasParent); m_actionBeginningOfBranch->setEnabled(hasEarlierVariation); m_actionBackward->setEnabled(hasParent); m_actionBackward10->setEnabled(hasParent); m_actionDeleteAllVariations->setEnabled(tree.has_variations()); m_actionForward->setEnabled(hasChildren); m_actionForward10->setEnabled(hasChildren); m_actionEnd->setEnabled(hasChildren); m_actionFindMove->setEnabled(! isGameOver); m_actionGotoMove->setEnabled(hasCurrentVariationOtherMoves(tree, current)); m_actionKeepOnlyPosition->setEnabled(hasParent || hasChildren); m_actionKeepOnlySubtree->setEnabled(hasParent && hasChildren); m_actionMakeMainVariation->setEnabled(! isMain); m_actionMoveDownVariation->setEnabled(current.get_sibling()); m_actionMoveUpVariation->setEnabled(hasParent && ¤t.get_parent().get_first_child() != ¤t); m_actionNextVariation->setEnabled(current.get_sibling() != nullptr); if (! m_isGenMoveRunning) { m_actionPlay->setEnabled(hasMoves); m_actionPlaySingleMove->setEnabled(hasMoves); } m_actionPreviousVariation->setEnabled( current.get_previous_sibling() != nullptr); // See also comment in setupMode() m_actionSetupMode->setEnabled(! hasParent && ! hasChildren); m_actionTruncate->setEnabled(hasParent); m_actionTruncateChildren->setEnabled(hasChildren); m_actionUndo->setEnabled(hasParent && ! hasChildren && hasMove); } void MainWindow::updateWindowModified() { if (! m_file.isEmpty()) setWindowModified(m_game->is_modified()); } void MainWindow::veryBadMove(bool checked) { if (! checked) return; m_game->set_bad_move(2); updateWindow(false); } void MainWindow::veryGoodMove(bool checked) { if (! checked) return; m_game->set_good_move(2); updateWindow(false); } void MainWindow::wheelEvent(QWheelEvent* event) { if (m_isGenMoveRunning) return; int delta = event->delta() / 8 / 15; if (delta > 0) { if (m_guiBoard->getSelectedPiece().is_null()) for (int i = 0; i < delta; ++i) backward(); else for (int i = 0; i < delta; ++i) nextTransform(); } else if (delta < 0) { if (m_guiBoard->getSelectedPiece().is_null()) for (int i = 0; i < -delta; ++i) forward(); else for (int i = 0; i < -delta; ++i) previousTransform(); } event->accept(); } bool MainWindow::writeGame(const string& file) { ofstream out(file); TreeWriter writer(out, m_game->get_tree()); writer.set_one_prop_per_line(true); writer.set_one_prop_value_per_line(true); writer.set_indent(2); writer.write(); return static_cast(out); } //----------------------------------------------------------------------------- pentobi-7.2/src/pentobi/MainWindow.h000066400000000000000000000400041227240712600174770ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/MainWindow.h */ //----------------------------------------------------------------------------- #ifndef PENTOBI_MAIN_WINDOW_H #define PENTOBI_MAIN_WINDOW_H // Needed in the header because moc_*.cxx does not include config.h #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "RatingHistory.h" #include "libpentobi_base/ColorMap.h" #include "libpentobi_base/Game.h" #include "libpentobi_mcts/Player.h" class QActionGroup; class QLabel; class QMessageBox; class QPlainTextEdit; class QSplitter; class AnalyzeGameWindow; class GuiBoard; class HelpWindow; class LeaveFullscreenButton; class OrientationDisplay; class PieceSelector; class RatingDialog; class ScoreDisplay; using namespace std; using libboardgame_sgf::Node; using libboardgame_base::Transform; using libboardgame_util::ArrayList; using libboardgame_util::Exception; using libpentobi_base::Board; using libpentobi_base::ColorMap; using libpentobi_base::ColorMove; using libpentobi_base::Game; using libpentobi_base::Piece; using libpentobi_base::Point; using libpentobi_base::Variant; using libpentobi_base::Move; using libpentobi_mcts::Player; //----------------------------------------------------------------------------- class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(const QString& initialFile = "", const QString& manualDir = "", const QString& booksDir = "", bool noBook = false, unsigned nu_threads = 0, size_t memory = 0); bool eventFilter(QObject* object, QEvent* event); QSize sizeHint() const; public slots: void about(); void analyzeGame(); void backward(); void backward10(); void backToMainVariation(); void beginning(); void beginningOfBranch(); void clearSelectedPiece(); void computerColors(); void deleteAllVariations(); void end(); void exportAsciiArt(); void exportImage(); void findMove(); void findNextComment(); void flipPieceHorizontally(); void flipPieceVertically(); void forward(); void forward10(); void gotoMove(); /** Go to a node if a node with a position defined by a sequence of moves still exists. */ void gotoPosition(Variant variant, const vector& moves); void help(); void gameInfo(); /** Abort current move generation and don't play a move. */ void interrupt(); /** Abort current move generation and play best move found so far. */ void interruptPlay(); void keepOnlyPosition(); void keepOnlySubtree(); void makeMainVariation(); void moveDownVariation(); void moveUpVariation(); void newGame(); void newRatedGame(); void nextVariation(); void nextVariation10(); void nextPiece(); void nextTransform(); void open(); bool open(const QString& file, bool isTemporary = false); void placePiece(Color c, Move mv); void play(); void playSingleMove(); void pointClicked(Point p); void previousPiece(); void previousTransform(); void previousVariation(); void previousVariation10(); void rotatePieceAnticlockwise(); void rotatePieceClockwise(); void save(); void saveAs(); void selectNextColor(); void selectPiece1(); void selectPiece2(); void selectPieceA(); void selectPieceC(); void selectPieceF(); void selectPieceG(); void selectPieceI(); void selectPieceL(); void selectPieceN(); void selectPieceO(); void selectPieceP(); void selectPieceS(); void selectPieceT(); void selectPieceU(); void selectPieceV(); void selectPieceW(); void selectPieceX(); void selectPieceY(); void selectPieceZ(); void selectPiece(Color c, Piece piece); void selectPiece(Color c, Piece piece, const Transform* transform); void setLevel(int level); void truncate(); void truncateChildren(); void undo(); void showToolbar(bool checked); void showVariations(bool checked); void showRating(); void setDeterministic(); protected: void closeEvent(QCloseEvent* event) override; void wheelEvent(QWheelEvent* event) override; private: struct GenMoveResult { bool playSingleMove; Color color; Move move; unsigned genMoveId; }; /** Possible values for m_level are in 1..maxLevel */ static const int maxLevel = 8; static const int maxRecentFiles = 9; unique_ptr m_game; Color m_currentColor; unique_ptr m_player; bool m_isGenMoveRunning; bool m_isAnalyzeRunning; /** Should the computer generate a move if it is its turn? Enabled on game start (if the computer plays at least one color) or after selecting Play. Disabled when navigating in the game. */ bool m_autoPlay; /** Flag indicating that the position after the last move played was a terminal position. */ bool m_gameFinished; bool m_isRated; /** Flag set while setting the text in m_comment for fast return in the textChanged() handler. Used because QPlainTextEdit does not have a textEdited() signal and we only need to handle edits. */ bool m_ignoreCommentTextChanged; bool m_genMoveInterrupted; /** Color played by the user in a rated game. Only defined if m_isRated is true. In game variants with multiple colors per player, the user plays all colors of the player with this color. */ Color m_ratedGameColor; /** Integer ID assigned to the currently running move generation. Used to ignore finished events from canceled move generations. */ unsigned m_genMoveId; /** Beginning of move number range of last moves played by the computer without pause. Used to mark all last moves played by the computer if it plays several moves in a row because the other players have no more moves. Otherwise, it would be hard to see what the last moves were if the computer plays quickly. A value of 0 means that there are no last moves played by the computer to be marked. */ unsigned m_lastComputerMovesBegin; /** End (inclusive) of move number range of last moves played by the computer without pause. Only defined if m_lastComputerMovesBegin is not 0. @see m_lastComputerMovesBegin */ unsigned m_lastComputerMovesEnd; /** Current playing level of m_player. Only use if m_useTimeLimit is false. Possible values for m_level are in 1..maxLevel. Only used if m_timeLimit is zero. Stored independently of the player and set at the player before each move generation, such that setting a new level does not require to abort a running move generation. */ int m_level; unique_ptr m_history; QToolBar* m_toolBar; GuiBoard* m_guiBoard; QString m_manualDir; ColorMap m_computerColors; ColorMap m_pieceSelector; OrientationDisplay* m_orientationDisplay; ScoreDisplay* m_scoreDisplay; QSplitter* m_splitter; QPlainTextEdit* m_comment; HelpWindow* m_helpWindow; RatingDialog* m_ratingDialog; AnalyzeGameWindow* m_analyzeGameWindow; QAction* m_actionAbout; QAction* m_actionAnalyzeGame; QAction* m_actionBackward; QAction* m_actionBackward10; QAction* m_actionBackToMainVariation; QAction* m_actionBadMove; QAction* m_actionBeginning; QAction* m_actionBeginningOfBranch; QAction* m_actionClearSelectedPiece; QAction* m_actionComputerColors; QAction* m_actionCoordinates; QAction* m_actionDeleteAllVariations; QAction* m_actionDoubtfulMove; QAction* m_actionEnd; QAction* m_actionExportAsciiArt; QAction* m_actionExportImage; QAction* m_actionFindMove; QAction* m_actionFindNextComment; QAction* m_actionFlipPieceHorizontally; QAction* m_actionFlipPieceVertically; QAction* m_actionForward; QAction* m_actionForward10; QAction* m_actionFullscreen; QAction* m_actionGameInfo; QAction* m_actionVariantClassic; QAction* m_actionVariantClassic2; QAction* m_actionVariantDuo; QAction* m_actionVariantJunior; QAction* m_actionVariantTrigon; QAction* m_actionVariantTrigon2; QAction* m_actionVariantTrigon3; QAction* m_actionGoodMove; QAction* m_actionGotoMove; QAction* m_actionHelp; QAction* m_actionInterestingMove; QAction* m_actionInterrupt; QAction* m_actionInterruptPlay; QAction* m_actionKeepOnlyPosition; QAction* m_actionKeepOnlySubtree; QAction* m_actionLeaveFullscreen; QAction* m_actionLevel[maxLevel]; QAction* m_actionMakeMainVariation; QAction* m_actionMoveDownVariation; QAction* m_actionMoveNumbersAll; QAction* m_actionMoveNumbersLast; QAction* m_actionMoveNumbersNone; QAction* m_actionMoveUpVariation; QAction* m_actionMoveSelectedPieceLeft; QAction* m_actionMoveSelectedPieceRight; QAction* m_actionMoveSelectedPieceUp; QAction* m_actionMoveSelectedPieceDown; QAction* m_actionNextPiece; QAction* m_actionNextTransform; QAction* m_actionNextVariation; QAction* m_actionNextVariation10; QAction* m_actionNew; QAction* m_actionNewRatedGame; QAction* m_actionNoMoveAnnotation; QAction* m_actionOpen; QAction* m_actionPlaceSelectedPiece; QAction* m_actionPlay; QAction* m_actionPlaySingleMove; QAction* m_actionPreviousPiece; QAction* m_actionPreviousTransform; QAction* m_actionPreviousVariation; QAction* m_actionPreviousVariation10; QAction* m_actionQuit; QAction* m_actionRecentFile[maxRecentFiles]; QAction* m_actionRotatePieceAnticlockwise; QAction* m_actionRotatePieceClockwise; QAction* m_actionSave; QAction* m_actionSaveAs; QAction* m_actionShowComment; QAction* m_actionShowRating; QAction* m_actionShowToolbar; QAction* m_actionSelectNextColor; QAction* m_actionSelectPiece1; QAction* m_actionSelectPiece2; QAction* m_actionSelectPieceA; QAction* m_actionSelectPieceC; QAction* m_actionSelectPieceF; QAction* m_actionSelectPieceG; QAction* m_actionSelectPieceI; QAction* m_actionSelectPieceL; QAction* m_actionSelectPieceN; QAction* m_actionSelectPieceO; QAction* m_actionSelectPieceP; QAction* m_actionSelectPieceS; QAction* m_actionSelectPieceT; QAction* m_actionSelectPieceU; QAction* m_actionSelectPieceV; QAction* m_actionSelectPieceW; QAction* m_actionSelectPieceX; QAction* m_actionSelectPieceY; QAction* m_actionSelectPieceZ; QAction* m_actionSetupMode; QAction* m_actionToolBarNoText; QAction* m_actionToolBarTextBesideIcons; QAction* m_actionToolBarTextBelowIcons; QAction* m_actionToolBarTextOnly; QAction* m_actionToolBarTextSystem; QAction* m_actionTruncate; QAction* m_actionTruncateChildren; QAction* m_actionShowVariations; QAction* m_actionUndo; QAction* m_actionVeryGoodMove; QAction* m_actionVeryBadMove; QMenu* m_menuMoveAnnotation; QMenu* m_menuOpenRecent; QMenu* m_menuToolBarText; QLabel* m_setupModeLabel; QLabel* m_ratedGameLabel; QFutureWatcher m_genMoveWatcher; QElapsedTimer m_genMoveTime; QString m_file; unique_ptr> m_legalMoves; unsigned m_legalMoveIndex; QLabel* m_moveNumber; LeaveFullscreenButton* m_leaveFullscreenButton; int m_lastRemainingSeconds; int m_lastRemainingMinutes; /** Is the current game a game loaded from the autosave file? If yes, we need it to save again on quit even if it was not modified. Note that the autosave game is deleted after loading to avoid that it is used twice if two instances of Pentobi are started. */ bool m_isAutoSaveLoaded; GenMoveResult asyncGenMove(Color c, int genMoveId, bool playSingleMove); bool checkSave(); bool checkQuit(); void clearFile(); QAction* createAction(const QString& text = ""); void createActions(); QWidget* createCentralWidget(); QWidget* createLeftPanel(); QAction* createLevelAction(QActionGroup* group, int level, const QString& text); void createMenu(); QLayout* createOrientationButtonBoxLeft(); QLayout* createOrientationButtonBoxRight(); QLayout* createOrientationSelector(); QLayout* createRightPanel(); void createToolBar(); void cancelThread(); void checkComputerMove(); void clearStatus(); bool computerPlaysAll() const; void deleteAutoSaveFile(); void enablePieceSelector(Color c); void gameOver(); void genMove(bool playSingleMove = false); const Board& getBoard() const; Variant getVariant() const; QString getFilter() const; QString getLastDir(); QString getVersion() const; void gotoNode(const Node& node); void initGame(); void initVariantActions(); void initPieceSelectors(); void leaveSetupMode(); void play(Color c, Move mv); bool save(const QString& file); void searchCallback(double elapsedSeconds, double remainingSeconds); void setCommentText(const QString& text); void setVariant(Variant variant); void setPlayToolTip(); void setRated(bool isRated); void setFile(const QString& file); void showError(const QString& message, const QString& infoText = "", const QString& detailText = ""); void showInfo(const QString& message, const QString& infoText = "", const QString& detailText = "", bool withIcon = false); void showInvalidFile(QString file, const Exception& e); void showStatus(const QString& text, bool temporary = false); void updateMoveNumber(); void updateWindow(bool currentNodeChanged); void updateWindowModified(); void updateComment(); void updateMoveAnnotationActions(); void loadHistory(); void updateRecentFiles(); void updateFlipActions(); bool writeGame(const string& file); private slots: void analyzeGameFinished(); void badMove(bool checked); void commentChanged(); void coordinates(bool checked); void doubtfulMove(bool checked); void fullscreen(); void variantClassic(bool checked); void variantClassic2(bool checked); void variantDuo(bool checked); void variantJunior(bool checked); void variantTrigon(bool checked); void variantTrigon2(bool checked); void variantTrigon3(bool checked); void genMoveFinished(); void goodMove(bool checked); void interestingMove(bool checked); void leaveFullscreen(); void noMoveAnnotation(bool checked); void openRecentFile(); void orientationDisplayColorClicked(Color c); void rememberFile(const QString& file); void rememberDir(const QString& file); void selectNamedPiece(const char* name1, const char* name2 = nullptr, const char* name3 = nullptr, const char* name4 = nullptr); void setLevel(bool checked); void setMoveNumbersAll(bool checked); void setMoveNumbersLast(bool checked); void setMoveNumbersNone(bool checked); void setSetupPlayer(); void setupMode(bool checked); void showComment(bool checked); void toolBarNoText(bool checked); void toolBarTextBesideIcons(bool checked); void toolBarTextBelowIcons(bool checked); void toolBarTextOnly(bool checked); void toolBarTextSystem(bool checked); void veryBadMove(bool checked); void veryGoodMove(bool checked); }; inline const Board& MainWindow::getBoard() const { return m_game->get_board(); } inline Variant MainWindow::getVariant() const { return m_game->get_variant(); } //----------------------------------------------------------------------------- #endif // PENTOBI_MAIN_WINDOW_H pentobi-7.2/src/pentobi/RatedGamesList.cpp000066400000000000000000000100601227240712600206250ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/RatedGamesList.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "RatedGamesList.h" #include #include #include #include "libboardgame_util/Log.h" #include "libpentobi_gui/Util.h" using libboardgame_util::log; //----------------------------------------------------------------------------- RatedGamesList::RatedGamesList(QWidget* parent) : QTableView(parent) { verticalHeader()->setVisible(false); setShowGrid(false); setEditTriggers(QAbstractItemView::NoEditTriggers); setTabKeyNavigation(false); setSelectionBehavior(QAbstractItemView::SelectRows); setAlternatingRowColors(true); m_model = new QStandardItemModel(this); setModel(m_model); connect(this, SIGNAL(doubleClicked(const QModelIndex&)), SLOT(activateGame(const QModelIndex&))); } void RatedGamesList::activateGame(const QModelIndex& index) { auto item = m_model->item(index.row(), 0); if (item == 0) return; bool ok; unsigned n = item->text().toUInt(&ok); if (ok) emit openRatedGame(n); } void RatedGamesList::focusInEvent(QFocusEvent* event) { // Select current index if list has focus selectRow(currentIndex().row()); scrollTo(currentIndex()); QTableView::focusInEvent(event); } void RatedGamesList::focusOutEvent(QFocusEvent* event) { // Show selection only if list has focus clearSelection(); QTableView::focusOutEvent(event); } void RatedGamesList::keyPressEvent(QKeyEvent* event) { if (event->type() == QEvent::KeyPress && static_cast(event)->key() == Qt::Key_Space) { QModelIndexList indexes = selectionModel()->selection().indexes(); if (! indexes.isEmpty()) activateGame(indexes[0]); return; } QTableView::keyPressEvent(event); } void RatedGamesList::updateContent(Variant variant, const RatingHistory& history) { m_model->clear(); QStringList headers; headers << tr("Game") << tr("Your Color") << tr("Level") << tr("Result") << tr("Date"); m_model->setHorizontalHeaderLabels(headers); auto header = horizontalHeader(); header->setDefaultAlignment(Qt::AlignLeft); header->setHighlightSections(false); #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) header->setSectionResizeMode(QHeaderView::ResizeToContents); #else header->setResizeMode(QHeaderView::ResizeToContents); #endif header->setStretchLastSection(true); int nuRows = history.getGameInfos().size(); m_model->setRowCount(nuRows); setSortingEnabled(false); for (int i = 0; i < nuRows; ++i) { auto& info = history.getGameInfos()[i]; auto number = new QStandardItem(); number->setData(info.number, Qt::DisplayRole); auto color = new QStandardItem(); if (info.color.to_int() < get_nu_colors(variant)) color->setText(Util::getPlayerString(variant, info.color)); else log() << "Error: invalid color in rating history\n"; auto level = new QStandardItem(); level->setData(info.level, Qt::DisplayRole); QString result; if (info.result == 1) result = tr("Win"); else if (info.result == 0.5) result = tr("Tie"); else if (info.result == 0) result = tr("Loss"); int row = nuRows - i - 1; m_model->setItem(row, 0, number); m_model->setItem(row, 1, color); m_model->setItem(row, 2, level); m_model->setItem(row, 3, new QStandardItem(result)); m_model->setItem(row, 4, new QStandardItem(info.date)); } setSortingEnabled(true); if (nuRows > 0) selectionModel()->setCurrentIndex(model()->index(0, 0), QItemSelectionModel::NoUpdate); } //----------------------------------------------------------------------------- pentobi-7.2/src/pentobi/RatedGamesList.h000066400000000000000000000022251227240712600202760ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/RatedGamesList.h */ //----------------------------------------------------------------------------- #ifndef PENTOBI_RATED_GAMES_LIST #define PENTOBI_RATED_GAMES_LIST // Needed in the header because moc_*.cxx does not include config.h #ifdef HAVE_CONFIG_H #include #endif #include #include "RatingHistory.h" class QStandardItemModel; //----------------------------------------------------------------------------- class RatedGamesList : public QTableView { Q_OBJECT public: RatedGamesList(QWidget* parent = nullptr); void updateContent(Variant variant, const RatingHistory& history); signals: void openRatedGame(unsigned n); protected: void focusInEvent(QFocusEvent* event) override; void focusOutEvent(QFocusEvent* event) override; void keyPressEvent(QKeyEvent* event) override; private: QStandardItemModel* m_model; private slots: void activateGame(const QModelIndex& index); }; //----------------------------------------------------------------------------- #endif // PENTOBI_RATED_GAMES_LIST pentobi-7.2/src/pentobi/RatingDialog.cpp000066400000000000000000000110601227240712600203220ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/RatingDialog.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "RatingDialog.h" #include #include #include #include #include #include #include #include #include #include #include "Util.h" using namespace std; //----------------------------------------------------------------------------- RatingDialog::RatingDialog(QWidget* parent, RatingHistory& history) : QDialog(parent), m_history(history) { setWindowTitle(tr("Your Rating")); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); auto layout = new QVBoxLayout(); setLayout(layout); auto formLayout = new QFormLayout(); layout->addLayout(formLayout); formLayout->setLabelAlignment(Qt::AlignLeft); m_labelRating = new QLabel(); formLayout->addRow(tr("Your rating:"), m_labelRating); m_labelVariant = new QLabel(); formLayout->addRow(tr("Game variant:"), m_labelVariant); m_labelNuGames = new QLabel(); formLayout->addRow(tr("Number rated games:"), m_labelNuGames); m_labelBestRating = new QLabel(); formLayout->addRow(tr("Best previous rating:"), m_labelBestRating); layout->addSpacing(layout->margin()); layout->addWidget(new QLabel(tr("Recent development:"))); m_graph = new RatingGraph(); layout->addWidget(m_graph, 1); layout->addSpacing(layout->margin()); layout->addWidget(new QLabel(tr("Recent games:"))); m_list = new RatedGamesList(); layout->addWidget(m_list, 1); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); layout->addWidget(buttonBox); m_clearButton = buttonBox->addButton(tr("&Clear"), QDialogButtonBox::ActionRole); buttonBox->button(QDialogButtonBox::Close)->setDefault(true); buttonBox->button(QDialogButtonBox::Close)->setAutoDefault(true); buttonBox->button(QDialogButtonBox::Close)->setFocus(); updateContent(); connect(buttonBox, SIGNAL(rejected()), SLOT(reject())); connect(buttonBox, SIGNAL(clicked(QAbstractButton*)), SLOT(buttonClicked(QAbstractButton*))); connect(m_list, SIGNAL(openRatedGame(unsigned)), SLOT(activateGame(unsigned))); } void RatingDialog::activateGame(unsigned n) { emit open(m_history.getFile(n)); } void RatingDialog::buttonClicked(QAbstractButton* button) { if (button != static_cast(m_clearButton)) return; QMessageBox msgBox(QMessageBox::Warning, "", tr("Clear rating and delete rating history?"), QMessageBox::Cancel, this); Util::setNoTitle(msgBox); auto clearButton = msgBox.addButton(tr("Clear rating"), QMessageBox::DestructiveRole); msgBox.setDefaultButton(clearButton); msgBox.exec(); if (msgBox.clickedButton() != clearButton) return; m_history.clear(); updateContent(); } void RatingDialog::updateContent() { auto variant = m_history.getVariant(); unsigned nuGames = m_history.getNuGames(); Rating rating = m_history.getRating(); Rating bestRating = m_history.getBestRating(); if (nuGames == 0) rating = Rating(0); QString variantStr; switch (variant) { case Variant::classic: variantStr = tr("Classic (4 players)"); break; case Variant::classic_2: variantStr = tr("Classic (2 players)"); break; case Variant::duo: variantStr = tr("Duo"); break; case Variant::trigon: variantStr = tr("Trigon (4 players)"); break; case Variant::trigon_2: variantStr = tr("Trigon (2 players)"); break; case Variant::trigon_3: variantStr = tr("Trigon (3 players)"); break; case Variant::junior: variantStr = tr("Junior"); break; } m_labelVariant->setText(variantStr); m_labelNuGames->setText(QString("%1").arg(nuGames)); if (nuGames == 0) { m_labelRating->setText(""); m_labelBestRating->setText(""); } else { m_labelRating->setText(QString("%1").arg(rating.toInt())); m_labelBestRating->setText(QString("%1").arg(bestRating.toInt())); } m_graph->updateContent(m_history); m_list->updateContent(variant, m_history); m_clearButton->setEnabled(nuGames > 0); } //----------------------------------------------------------------------------- pentobi-7.2/src/pentobi/RatingDialog.h000066400000000000000000000026151227240712600177750ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/RatingDialog.h */ //----------------------------------------------------------------------------- #ifndef PENTOBI_RATING_DIALOG_H #define PENTOBI_RATING_DIALOG_H // Needed in the header because moc_*.cxx does not include config.h #ifdef HAVE_CONFIG_H #include #endif #include #include "RatedGamesList.h" #include "RatingGraph.h" #include "libpentobi_base/Variant.h" class QAbstractButton; class QLabel; using namespace std; using libpentobi_base::Variant; //----------------------------------------------------------------------------- class RatingDialog : public QDialog { Q_OBJECT public: /** Constructor. @param parent @param history (@ref libboardgame_doc_storesref) */ RatingDialog(QWidget* parent, RatingHistory& history); void updateContent(); signals: void open(const QString& file); private: RatingHistory& m_history; QPushButton* m_clearButton; QLabel* m_labelVariant; QLabel* m_labelNuGames; QLabel* m_labelRating; QLabel* m_labelBestRating; RatingGraph* m_graph; RatedGamesList* m_list; private slots: void activateGame(unsigned n); void buttonClicked(QAbstractButton*); }; //----------------------------------------------------------------------------- #endif // PENTOBI_RATING_DIALOG_H pentobi-7.2/src/pentobi/RatingGraph.cpp000066400000000000000000000065701227240712600201760ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/RatingGraph.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "RatingGraph.h" #include #include //----------------------------------------------------------------------------- RatingGraph::RatingGraph(QWidget* parent) : QFrame(parent) { setMinimumSize(200, 60); setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); } void RatingGraph::paintEvent(QPaintEvent* event) { QFrame::paintEvent(event); QRect contentsRect = QFrame::contentsRect(); int width = contentsRect.width(); int height = contentsRect.height(); QPainter painter(this); painter.translate(contentsRect.x(), contentsRect.y()); painter.setRenderHint(QPainter::Antialiasing, true); painter.setPen(Qt::NoPen); painter.setBrush(QColor(255, 255, 255)); painter.drawRect(0, 0, width, height); if (! m_values.empty()) { QFontMetrics metrics(painter.font()); float yRange = m_yMax - m_yMin; float yTic = m_yMin; float topMargin = ceil(1.2f * metrics.height()); float bottomMargin = ceil(0.3f * metrics.height()); float graphHeight = height - topMargin - bottomMargin; QPen pen(QColor(96, 96, 96)); pen.setStyle(Qt::DotLine); painter.setPen(pen); int maxLabelWidth = 0; while (yTic <= m_yMax) { qreal y = topMargin + graphHeight - (yTic - m_yMin) / yRange * graphHeight; painter.drawLine(0, y, width, y); QString label; label.setNum(yTic, 'f', 0); int labelWidth = metrics.width(label + " "); maxLabelWidth = max(maxLabelWidth, labelWidth); painter.drawText(width - labelWidth, y - metrics.descent(), label); if (yRange < 600) yTic += 100; else yTic += 200; } qreal dX = qreal(width - maxLabelWidth) / RatingHistory::maxGames; qreal x = 0; QPainterPath path; for (unsigned i = 0; i < m_values.size(); ++i) { qreal y = topMargin + graphHeight - (m_values[i] - m_yMin) / yRange * graphHeight; if (i == 0) path.moveTo(x, y); else path.lineTo(x, y); x += dX; } painter.setPen(Qt::red); painter.setBrush(Qt::NoBrush); painter.drawPath(path); } } QSize RatingGraph::sizeHint() const { return QSize(480, 120); } void RatingGraph::updateContent(const RatingHistory& history) { m_values.clear(); auto& games = history.getGameInfos(); if (games.empty()) { update(); return; } m_yMin = games[0].rating.get(); m_yMax = m_yMin; for (const RatingHistory::GameInfo& info : games) { float rating = info.rating.get(); m_yMin = min(m_yMin, rating); m_yMax = max(m_yMax, rating); m_values.push_back(rating); } m_yMin = floor((m_yMin / 100.f)) * 100; m_yMax = ceil((m_yMax / 100.f)) * 100; if (m_yMax == m_yMin) m_yMax = m_yMin + 100; update(); } //----------------------------------------------------------------------------- pentobi-7.2/src/pentobi/RatingGraph.h000066400000000000000000000016651227240712600176430ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/RatingGraph.h */ //----------------------------------------------------------------------------- #ifndef PENTOBI_RATING_GRAPH_H #define PENTOBI_RATING_GRAPH_H // Needed in the header because moc_*.cxx does not include config.h #ifdef HAVE_CONFIG_H #include #endif #include #include "RatingHistory.h" //----------------------------------------------------------------------------- class RatingGraph : public QFrame { Q_OBJECT public: RatingGraph(QWidget* parent = nullptr); void updateContent(const RatingHistory& history); QSize sizeHint() const; protected: void paintEvent(QPaintEvent* event) override; private: float m_yMin; float m_yMax; vector m_values; }; //----------------------------------------------------------------------------- #endif // PENTOBI_RATING_GRAPH_H pentobi-7.2/src/pentobi/RatingHistory.cpp000066400000000000000000000140451227240712600205720ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/RatingHistory.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "RatingHistory.h" #include #include #include #include #include #include #include "Util.h" #include "libpentobi_base/TreeWriter.h" #include "libpentobi_mcts/Player.h" using libpentobi_base::to_string_id; using libpentobi_base::TreeWriter; using libpentobi_mcts::Player; //----------------------------------------------------------------------------- namespace { /** 1000 Elo represents a beginner level. */ const double startRating = 1000; QString getRatedGamesDir(Variant variant) { return Util::getDataDir() + "/rated_games/" + QString(to_string_id(variant)); } } // namespace //----------------------------------------------------------------------------- RatingHistory::RatingHistory(Variant variant) { load(variant); } void RatingHistory::addGame(float score, Rating opponentRating, unsigned nuOpponents, Color color, float result, const QString& date, int level, const Tree& tree) { m_rating.update_multiplayer(score, opponentRating, nuOpponents, 32); if (m_rating.get() > m_bestRating.get()) m_bestRating = m_rating; ++m_nuGames; GameInfo info; info.number = m_nuGames; info.color = color; info.result = result; info.date = date; info.level = level; info.rating = m_rating; m_games.push_back(info); size_t nuGames = m_games.size(); if (nuGames > maxGames) m_games.erase(m_games.begin(), m_games.begin() + nuGames - maxGames); save(); ofstream out(getFile(m_nuGames).toLocal8Bit().constData()); TreeWriter writer(out, tree); writer.set_one_prop_per_line(true); writer.set_one_prop_value_per_line(true); writer.set_indent(2); writer.write(); // Only save the last RatingHistory::maxGames games if (m_nuGames > maxGames) QFile::remove(getFile(m_nuGames - maxGames)); } void RatingHistory::clear() { QString variantStr = QString(to_string_id(m_variant)); QSettings settings; settings.remove("rated_games_" + variantStr); settings.remove("rating_" + variantStr); settings.remove("best_rating_" + variantStr); for (const RatingHistory::GameInfo& info : getGameInfos()) QFile::remove(getFile(info.number)); QFile::remove(m_file); m_nuGames = 0; m_rating = Rating(startRating); m_bestRating = Rating(startRating); m_games.clear(); } QString RatingHistory::getFile(unsigned n) const { return QString("%1/%2.blksgf").arg(m_dir).arg(n); } void RatingHistory::getNextRatedGameSettings(int maxLevel, int& level, Color& userColor) { userColor = Color(m_random.generate() % get_nu_players(m_variant)); float minDiff = 0; // Initialize to avoid compiler warning for (int i = 1; i <= maxLevel; ++i) { float diff = abs(m_rating.get() - Player::get_rating(m_variant, i).get()); if (i == 1 || diff < minDiff) { minDiff = diff; level = i; } } } void RatingHistory::init(Rating rating) { m_rating = rating; m_bestRating = rating; m_nuGames = 0; m_games.clear(); save(); } void RatingHistory::load(Variant variant) { m_variant = variant; QString variantStr = QString(to_string_id(variant)); QSettings settings; m_nuGames = settings.value("rated_games_" + variantStr, 0).toUInt(); // Default value is 1000 (Elo-rating for beginner-level play) m_rating = Rating(settings.value("rating_" + variantStr, startRating).toFloat()); m_bestRating = Rating(settings.value("best_rating_" + variantStr, 0).toFloat()); m_games.clear(); m_dir = getRatedGamesDir(variant); m_file = m_dir + "/history.dat"; ifstream file(m_file.toLocal8Bit().constData()); if (! file) return; string line; while (getline(file, line)) { istringstream in(line); GameInfo info; unsigned c; string date; in >> info.number >> c >> info.result >> date >> info.level >> info.rating; info.date = QString(date.c_str()); if (! in || c >= get_nu_colors(variant)) return; info.color = Color(c); m_games.push_back(info); } size_t nuGames = m_games.size(); if (nuGames > maxGames) m_games.erase(m_games.begin(), m_games.begin() + nuGames - maxGames); // Make the all-time best rating consistent with the rating history. Older // versions of Pentobi (up to version 3) did not save the all-time best // rating, so after an upgrade to a newer version of Pentobi, the history // of recent rated games can contain a higher rating than the stored // all-time best rating. for (const RatingHistory::GameInfo& info : getGameInfos()) if (info.rating.get() > m_bestRating.get()) m_bestRating = info.rating; } void RatingHistory::save() const { QString variantStr = QString(to_string_id(m_variant)); QSettings settings; settings.setValue("rated_games_" + variantStr, m_nuGames); settings.setValue("rating_" + variantStr, static_cast(m_rating.get())); settings.setValue("best_rating_" + variantStr, static_cast(m_bestRating.get())); LIBBOARDGAME_ASSERT(! m_file.isEmpty()); QDir dir(""); dir.mkpath(m_dir); ofstream out(m_file.toLocal8Bit().constData()); for (size_t i = 0; i < m_games.size(); ++i) { auto& info = m_games[i]; out << info.number << ' ' << static_cast(info.color.to_int()) << ' ' << info.result << ' ' << info.date.toLocal8Bit().constData() << ' ' << info.level << ' ' << info.rating << '\n'; } } //----------------------------------------------------------------------------- pentobi-7.2/src/pentobi/RatingHistory.h000066400000000000000000000063221227240712600202360ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/RatingHistory.h */ //----------------------------------------------------------------------------- #ifndef PENTOBI_RATING_HISTORY_H #define PENTOBI_RATING_HISTORY_H #include #include #include "libboardgame_base/Rating.h" #include "libboardgame_util/RandomGenerator.h" #include "libpentobi_base/Color.h" #include "libpentobi_base/Tree.h" #include "libpentobi_base/Variant.h" using namespace std; using libboardgame_base::Rating; using libboardgame_util::RandomGenerator; using libpentobi_base::Color; using libpentobi_base::Tree; using libpentobi_base::Variant; //----------------------------------------------------------------------------- /** History of rated games in a certain game variant. */ class RatingHistory { public: /** Maximum number of games to remember in the history. */ static const unsigned maxGames = 100; struct GameInfo { /** Game number. The first game played has number 0. */ unsigned number; /** Color played by the human. In game variants with multiple colors per player, the human played all colors played by the player of this color. */ Color color; /** Game result. 0=Loss, 0.5=tie, 1=win from the viewpoint of the human. */ float result; /** Date of the game in "YYYY-MM-DD" format. */ QString date; /** The playing level of the computer opponent. */ int level; /** The rating of the human after the game. */ Rating rating; }; RatingHistory(Variant variant); /** Initialize rating to a given a-priori value. */ void init(Rating rating); /** Get level and user color for next rated games. */ void getNextRatedGameSettings(int maxLevel, int& level, Color& userColor); /** Append a new game. */ void addGame(float score, Rating opponentRating, unsigned nuOpponents, Color color, float result, const QString& date, int level, const Tree& tree); /** Get file name of the n'th rated game. */ QString getFile(unsigned n) const; void load(Variant variant); /** Saves the history. */ void save() const; const vector& getGameInfos() const; Variant getVariant() const; const Rating& getRating() const; const Rating& getBestRating() const; unsigned getNuGames() const; void clear(); private: RandomGenerator m_random; Variant m_variant; Rating m_rating; unsigned m_nuGames; Rating m_bestRating; QString m_dir; QString m_file; vector m_games; }; inline const vector& RatingHistory::getGameInfos() const { return m_games; } inline unsigned RatingHistory::getNuGames() const { return m_nuGames; } inline const Rating& RatingHistory::getBestRating() const { return m_bestRating; } inline const Rating& RatingHistory::getRating() const { return m_rating; } inline Variant RatingHistory::getVariant() const { return m_variant; } //----------------------------------------------------------------------------- #endif // PENTOBI_RATING_HISTORY_H pentobi-7.2/src/pentobi/ShowMessage.cpp000066400000000000000000000064321227240712600202120ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/ShowMessage.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "ShowMessage.h" #include #include #include "Util.h" //----------------------------------------------------------------------------- namespace { void showMessage(QWidget* parent, QMessageBox::Icon icon, const QString& text, const QString& infoText, const QString& detailText) { // Workaround to avoid very small widths if the main text is short, which // causes ugly word wrapping with single-word lines in the informative text. // Why does QMessageBox::setMinimumWidth() not work (tested in Qt 4.7)? QString expandedText = text; QFontMetrics metrics(qApp->font("QLabel")); int minWidth = 30 * metrics.averageCharWidth(); while (metrics.width(expandedText) < minWidth) expandedText.append(" "); QMessageBox msgBox(parent); Util::setNoTitle(msgBox); msgBox.setIcon(icon); msgBox.setText(expandedText); msgBox.setInformativeText(infoText); msgBox.setDetailedText(detailText); msgBox.exec(); } } // namespace //----------------------------------------------------------------------------- void initQuestion(QMessageBox& msgBox, const QString& text, const QString& infoText) { Util::setNoTitle(msgBox); // Workaround to avoid very small widths if the main text is short, which // causes ugly word wrapping with single-word lines in the informative text. // Why does QMessageBox::setMinimumWidth() not work (tested in Qt 4.7)? QString expandedText = text; QFontMetrics metrics(qApp->font("QLabel")); int minWidth = 30 * metrics.averageCharWidth(); while (metrics.width(expandedText) < minWidth) expandedText.append(" "); msgBox.setText(expandedText); msgBox.setInformativeText(infoText); } void showFatal(const QString& detailedText) { // Don't translate these error messages. They shouldn't occur if the // program is correct and if it is not, they can occur in situations // when the translators are not yet installed. QMessageBox msgBox; msgBox.setWindowTitle("Unexpected Error"); msgBox.setIcon(QMessageBox::Critical); msgBox.setText("An unexpected error occurred."); QString infoText = "Please report this error together with any details available with" " the button below and other context information at the Pentobi" " bug tracker."; msgBox.setInformativeText("" + infoText); msgBox.setDetailedText(detailedText); msgBox.exec(); } void showError(QWidget* parent, const QString& text, const QString& infoText, const QString& detailText) { showMessage(parent,QMessageBox::Critical, text, infoText, detailText); } void showInfo(QWidget* parent, const QString& text, const QString& infoText, const QString& detailText, bool withIcon) { showMessage(parent, withIcon ? QMessageBox::Information : QMessageBox::NoIcon, text, infoText, detailText); } //----------------------------------------------------------------------------- pentobi-7.2/src/pentobi/ShowMessage.h000066400000000000000000000017041227240712600176540ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/ShowMessage.h */ //----------------------------------------------------------------------------- #ifndef PENTOBI_SHOW_MESSAGE_H #define PENTOBI_SHOW_MESSAGE_H #include class QMessageBox; class QWidget; //----------------------------------------------------------------------------- void initQuestion(QMessageBox& msgBox, const QString& text, const QString& infoText = ""); void showError(QWidget* parent, const QString& text, const QString& infoText = "", const QString& detailText = ""); void showInfo(QWidget* parent, const QString& text, const QString& infoText = "", const QString& detailText = "", bool withIcon = false); void showFatal(const QString& detailedText); //----------------------------------------------------------------------------- #endif // PENTOBI_SHOW_MESSAGE_H pentobi-7.2/src/pentobi/Util.cpp000066400000000000000000000043461227240712600167040ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/Util.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Util.h" #include #include #include #include #include #include #include "libpentobi_mcts/Player.h" #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) #include #else #include #endif using libpentobi_mcts::Player; //----------------------------------------------------------------------------- namespace Util { QString getDataDir() { #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) return QStandardPaths::writableLocation(QStandardPaths::DataLocation); #else return QDesktopServices::storageLocation(QDesktopServices::DataLocation); #endif } void initDataDir() { QString dataLocation = getDataDir(); QDir dir(dataLocation); if (! dir.exists()) // Note: dataLocation is an absolute path but there is no static // function QDir::mkpath() dir.mkpath(dataLocation); } void removeThumbnail(const QString& file) { // Note: in the future, it might be possible to trigger a thumbnail // update via D-Bus instead of removing it, but this is not yet // implemented in Gnome QFileInfo info(file); QString canonicalFile = info.canonicalFilePath(); if (canonicalFile.isEmpty()) canonicalFile = info.absoluteFilePath(); QByteArray url = QUrl::fromLocalFile(canonicalFile).toEncoded(); QByteArray md5 = QCryptographicHash::hash(url, QCryptographicHash::Md5).toHex(); QString home = QDir::home().path(); QFile::remove(home + "/.thumbnails/normal/" + md5 + ".png"); QFile::remove(home + "/.thumbnails/large/" + md5 + ".png"); } void setNoTitle(QDialog& dialog) { // On many platforms, message boxes should have no title but using // an emtpy string causes Qt to use the lower-case application name (tested // on Linux with Qt 4.8). As a workaround, we set the title to a space // character. dialog.setWindowTitle(" "); } } // namespace Util //----------------------------------------------------------------------------- pentobi-7.2/src/pentobi/Util.h000066400000000000000000000026321227240712600163450ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi/Util.h */ //----------------------------------------------------------------------------- #ifndef PENTOBI_UTIL_H #define PENTOBI_UTIL_H #include "RatingHistory.h" #include "libboardgame_base/Rating.h" #include "libpentobi_base/Color.h" #include "libpentobi_base/Variant.h" class QDialog; class QString; using libboardgame_base::Rating; using libpentobi_base::Color; using libpentobi_base::Variant; //----------------------------------------------------------------------------- namespace Util { /** Remove a thumbnail for a given file. Currently, the QT open file dialog shows thumbnails even if they belong to old versions of a file (see QTBUG-24724). This function can be used to remove an out-of-date freedesktop.org thumbnail if we know a file has changed (e.g. after saving). */ void removeThumbnail(const QString& file); /** Return the platform-dependent directory for storing data for the current application. */ QString getDataDir(); /** Create the platform-dependent directory for storing data for the current application if it does not exist yet. */ void initDataDir(); /** Set an empty window title for message boxes and similar small dialogs. */ void setNoTitle(QDialog& dialog); } //----------------------------------------------------------------------------- #endif // PENTOBI_UTIL_H pentobi-7.2/src/pentobi/icon.rc000066400000000000000000000000771227240712600165360ustar00rootroot00000000000000 IDI_ICON1 ICON DISCARDABLE "pentobi.ico" pentobi-7.2/src/pentobi/icons/000077500000000000000000000000001227240712600163675ustar00rootroot00000000000000pentobi-7.2/src/pentobi/icons/pentobi-16.png000066400000000000000000000007371227240712600207700ustar00rootroot00000000000000‰PNG  IHDRѓџasBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<\IDAT8’ПOТ@ЧПm/$˜0hтр"“‰‰q'Ђ‰‰“ƒ‹qУПС8јЇ`тъH„СЄ$шР: $ †Ž”ћс@ьqEZоіywїончžQЩч%ў‚sРВ0ЫЕѓUD€эJ%HиЙќR)рСЭЛw­_мm0˜’R­ЂєМШŽс0EЛ­%DГЉqkXdУ)_ЩНнiwЩ@o™Ze Dуr!ЅŠНt@8РwkLС™[ГТNPPNК}BНWє{Sfp>A*ф Ю ЁnCO˜‹Ф9!k›—икQŠgYѕd.@,SуLЂŒtbZьы_YысЉ:=Hc–3'ˆvрб‰vХ0Ч:xыќhТ;†Ёў]J†т§b'яyvђауъRNFНЯyIО‚ёАЕДBн:žхРЖЋ№}ХŽу`дSУф8пџzЋй‚KЌIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-16.svg000066400000000000000000002346521227240712600210100ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-32.png000066400000000000000000000013371227240712600207630ustar00rootroot00000000000000‰PNG  IHDR szzєsBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<\IDATX…Х—Пkд`Ч?9гчоzаEhЗ B]Tа[]К чаЁBAA….7A'Aм,E(ИјтPдтuЁWИJ‹Mѓу’&У™івїЧyзІ} CђIочћцђЩ›sпЂтМІ8лj’Щ}ЧY&ЯИ9РŽГьтt›?НЈ?-ŽatдШо-mEQHи ‰Ђ€кј„‘эЌм‡IЗ0оъЊвcЧuЙ4;kd,MђuѓГТ’@ЈO™+7pzЌќннЁXП*"rИЃЋаѓЌ,Ib-‹}БВМяQ€Xrї 3ѓCпиФЦђОn>{ Cc sЫћ~ДZњц‡ћt"!‰„ƒ[ йх}GO Рыз‹#gл§†ДM№rL?§(КѕцZёЄо{eœ,ќ<=uEEfдƒжIU5НnоОy“(Њj˜ъВ}ŸRеивžьљх(ЊМH[tМ„!„бб–$х(ЊМЈ6ЈVTѓЋ{4œ[_€щЦ™šX§ђѓТ~Џ†?П€MУО}_WXкёЙruЦШъsX4œV Zi Х@ї ЁaП§4,Аhиnok™Hfe§4<РЌсŸН=-;ГiшŠ€фЋ†оі/ВДšчХЦl^PX4\ЋзŠ#ї*КВ`М›Ÿю(Чђo@Щ^uї`жpdьіЉ+:Mў dУkhЋ~†ШШ Ћa ŠЖ– Јf12kX†Ђy§зjX†Ђy9{w›œЯŸS€ "ѓ@ѓš7™џ ‰‘ђMј$IENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-32.svg000066400000000000000000002024431227240712600207770ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-backward-16.png000066400000000000000000000011141227240712600225320ustar00rootroot00000000000000‰PNG  IHDRѓџasBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<ЩIDAT8Ѕ“ЯkAЧП3ЛkMZ“Ц” HЩPя9lDњЋ 0MвzМxё^Я…оМйHБ(”ФKЁЭ1їFJLэ^rЌ4t3ЬМчЁmPHЊ3сћоwоfЦџ„Œ:\*?[-VŠљ”*KoЕжЋЄЭТ•BQ*ЋЩDЂ23sOD‰@ќщяћВ}мњœL=~ДИ ‚{ћ{#ХЮ5чеP(ЌБыЮvъfjn~~С УаК11fƒЯWpQ7 |=XЗРѓ<{њЮ­ЯщЉД7ћ`ж ‚Ћзыˆ@D0Ц іDзuСЬ6мО;Н3•NчѓљћvћИ)&"h­AL`fЖР–’XЦh„aЅдH€1Zk01KјѕѓьЩггнZ­FйlжH)Ё”BПп‡Rj—Е1Ь bц‰ОяЫя'ээD2ё0—ЫЩnЗkЙїмД /Э3šЭ&›k=ЃB,Џ”ЖтууO=Яs:ъѕzдМУўТђѓr5‹3зiЕО­}мњєzaш(Ппи\ УГЃЃVдэЃP}Зљ"Пq,љ% 0Д…ЋФoЕ§иЇE˜іIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-backward-16.svg000066400000000000000000000167431227240712600225630ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-backward.png000066400000000000000000000012321227240712600223070ustar00rootroot00000000000000‰PNG  IHDRФДl;sBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<IDAT8ЕеKoк@рй]*‘„@UЅG‹cЙЁцQ) НЄzЇ?Ž[иr‰€Jizhвќ*!8pMz!kяі4`^БZF^ЩZПF6’RТ&oD}.œ>OП5?g˜XY—`šЉ“Š"р^YБiІ“J2љIsƒЎ„)Ѕ‡€IХH$|Џїї_И…Ж‚Rzˆ(qУ †лЖ…ІiCjІзŽ!фсђЂ№ 9Ч-sž9"• #Ёƒ{jЋе"КЎKŒ0eJ="ЧзxwМ—Ых€х‹hІтl6uЌ(ф*ўбаіЕйlЮ9дj5$Ѕ)%!`rя\‘HФ2гŠl6uŒGЈogwW­7ъ„sОYt€bЖЧ”в7D%WggqпіЮЖкhдЩp8\YнЂц`ˆH)<ƒСњ§>иЖНіеј4ŒcП,.“пЎЏлNt]BрœƒeY`YpЮ—ЎIސxŒп[\&ПпмДЛн.„УсПјsSЯЅєVQщC,цїzНP­Vq4•ŠBФdЄžІm4r“=‚1aь А|ЭСгxьєдяёЈрлк‚rЉlѕz=Я\В#!П// /ТO8”оŸœјƒЁf,_DырI,§V0Цю‡флліуУЃX–чžЦя~оЕџ+<cLОК—іј_ccџМ?кЁІц™кmIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-backward.svg000066400000000000000000000110031227240712600223170ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-backward10-16.png000066400000000000000000000012471227240712600227020ustar00rootroot00000000000000‰PNG  IHDRѓџasBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<$IDAT8ЅSЯkAўffГйЦиn%iŠа&Mk–zH”Ц‹š‹во,Ф‘PќЧzѓWšK‘"xАBлƒжІ"HЊЙД*Н ЩЮюЮѓАЕкдPФwљ†я›яНї=FDјŸрK–+хЋїм[)хleI>ыGРzH)Јйr•1Ч уЙRЪzњxEœЉ RЙ=fХЬ7ЅR)"b̘Ÿ_ˆшлЇёЧЯЩX|№mqІ8˜œФDf‚ђ…‚9šJС4ЭрnхŽ€1Lт€Є”CёѓБг—ЇSйlЖm#tаnщt:@ PˆЦ€ххeRЪ!;О=6>žЪЄ3hЗлАЂZ{-бщtpxx­5Дж Ђc,‹a ‰ф№І=<<žЫхАїyZkь~и…RъDA/‰ж:$PОп!"pЦI)ХДжрœУѓМОХ!сОэŸл?иџдмmВt: ЅЧчJ)ИЎ зuпП08RРˆ‹‹‹і }Ў™Ы9SЉ$“#№|/иyП# W QИG"сh „juхЗ‘Є”Ѓц€бЬчѓ.MMЁ^ohЧЩ!‘HђеееnЗлЕz= „јzТ‰хЅr6Ъ"яfЎЭФ7^o 1ЛГГз­ѕѕWЈ>ЉБ3X{XkЙкЛБЕЙеЯѕoжы§\xŠjjлОжЗЂІљВZ­6|Ѕчk§NгПЦOєф(:Oзщ7IENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-backward10-16.svg000066400000000000000000000117361227240712600227210ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-backward10.png000066400000000000000000000015061227240712600224540ustar00rootroot00000000000000‰PNG  IHDRФДl;sBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<УIDAT8Е•]OгpЦŸџK‹ŒЕ[ˆ/nFˆ†nфХˆ/ %ъPуGуЋFƒDРHТ ш%"hl`ŒЙЖчx1›l@TNкє9M3у,Bž ѕ4рсбсž<|YЊG'_œTЇЛ™LЕ›Ъ˜іђљHZЃЭwž—џѕФЩфPЛPЦtb`Р€фуdћ9Уœэы‹Ÿ­ vЇC(cz ‘АЏ440”šээз5\n8 їЈŽуt(CN%њћэH4ЪОяs}}=К:ЛB555 &„jC9'9\uДжQКn#Ѓ#ZЪ7‰ў„‰FБОО&cБ&’BШtйlWЏ^РT(у§у№*„ccc‡?x0дez2яЗ,лТъъŠ ‚Ь,зП}E&“3cqёˆH23Š'ф­­­>­‹PeшЩxМЯЖ- ++(amэ ВйlYq%`1'Ђ‚Žу\зІžŒїійсА%–?/‹§IСЬHЇгUa•tбZ Ьб/­”ЩЬЪѓМВ‡J'"вuн‡Сщ™ЉНLv+‹5RJјОЯѓаииˆ ДчyeљŸКh…зuƒogоюэьdѓЭЭЭОя#"жуJ№JЭ…З,[7Чqn*W=w{B;j.--ЉЖЖ6RJЩэmўєё“шшь€€ b‚€`"ƒСD`6ДвЎћЌ\ Пнs'Б#Іi"•JэЖДДдІ +ЦФФk?—Ы™е>Ѕєжp)ќжэюаЅ‹kЧЧŸB*ѕНЛћ–uсќхК.мёчЂјРу?Уuн…РЧрм\jocssШуљЙљщЭtў8рБр"œ} ІцRЙУf|я§ќћŸџ.… !'Šš|м—BLžЎшёџˆ3ћч§ЛіЄw_'ЗIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-backward10.svg000066400000000000000000000157651227240712600225030ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-beginning-16.png000066400000000000000000000012071227240712600227170ustar00rootroot00000000000000‰PNG  IHDRѓџasBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<IDAT8ЅSMoQ=gEpЁM!И(‹Qq!5БІ&nlfгMi‡ƒк•n]›и_@4R,Ф…3ћL\гEMŠ л2qQ$™оН.†тЄРЊous?NЮ=ї<СЬ8ЫЋЙьo"Š€ЊiЧяпЃў†Ќ™]:RžnKЦ„ˆ(ўќй:Œ7qХ\~q^е^:ЎsiƒIЄaв0 uеЬЃ‘шК™ЫM€H"` ХкквT8rсs"‘ˆ->^М ыКГb.ˆ †ь”8ЉJV~ЄгwфНЙЙјўСbггШчѓš‡Я`f€ЃP(LyD`0bБXћњь,ЯЬ$Џэ5іDЋеBГy"ђz˜‡q&“ёЏ  УїћRп§ЙhЗлУцгУЬ )х€“$`&быѕрКюиA?ш№ RJЌЏжпЦЏ†L&IзutЛ]ИЎ зuGbIЇ€бl6/кЖнК™КёчжэєеzН.RЉ”Ч ˆ‡“бEїn}gчЫбQKЙ??Y’еOUЧqmєŒAл'ЂP*UmУ0qјЎVЋ ТŽуh•­bœ‘<'JђЬ4x–eЙ6+fЇгyН§mл™hC!Ф~Й\FЅ\Ђ(ЧўbЉИѕŠњн'ЊЊ~Ÿ ЮњџИFl2?‘иIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-beginning-16.svg000066400000000000000000000205271227240712600227400ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-beginning.png000066400000000000000000000013561227240712600225000ustar00rootroot00000000000000‰PNG  IHDRФДl;sBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<kIDAT8е•ЯKAЧП;ЛІb~‘CЋеExЈёbŠёф щ)н*=љT(єпАA.ХжНйХ* VятбŠb[а†‚…˜,Е3ћ^›I7i\sёаa‡™7яёсё}ѓf fЦm q+TЦтГЇп•RƒњРВЌъъыЗCн‚‹‹ХЉ>CМxГъ>Й l)Ѕ—Ÿ/#P„БђjeА[`qБ83­їJЉL/ b|§іDнѕжPлžOї+”R ІЈ~02"`iiЉ? zzzЊ0Є” j;Ž3ыГжэ9;Щd@DH&’^НQЏG‡юпЛА€)sь8ЮД3ЪЖ=ŸJЇS899йl  F№вщњшЕT*нmЯИ)EšL&p||,”Rипп3ƒ™AD­}иžœœ kЬЦЭтY1Ѓ<7gЇёŽŽŽ„яћ‘АNЛK1<2ќsтсD:у№ѓЁ№}П а ќoЦ!)˜јСY'ј&И‹ЖыF„ѓГГЬ‡ЭЭкe§ЃЃЃЄ}J)H)!ЅьjыНЎS ,Ѕ7•фТіжVЭѓ<Œ‘і‡Sћ8,37яq МыК{ŽуЖЖЗЫГГљT6›Ѕƒƒ‘ЫхаКdм}ЅpёtFсб№ЪЧЪЛ™ќLz||œˆIl”7<ЯѓюD5ˆтBштukiзuї|Щ+•Jэъї˜Fc` ?‘ˆšеѓУзv^gц;;;хGЙ\ €(•JПЂ2n+^~6џЯ[†“Dсгюnэ& –išеЕ5Зѕ›ІYНю8NA˜ЦЫ^РЦїЯћ ^5VЛѓС’IENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-beginning.svg000066400000000000000000000152161227240712600225130ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-computer-color-16.png000066400000000000000000000012761227240712600237370ustar00rootroot00000000000000‰PNG  IHDRѓџasBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<;IDAT8u“ЯKUAЧ?oю}ЯwЕўQAh-Œ 2ћ‚ŠкЕЉlгŸRЛ@24‚H*Phg*ъЦк˜+ЅM‹ŠЈxzп}zчЭ93-юѕљ+‡Юї3_ЮœЉŒ=;л]ыЗm{Јp`…№>‚'„"{№!аU­}jхљУИЛV›ЙzўђЅaŒ1eсAQ!8И–——.~\X˜ˆ[;;CУУWИ7zŸ…Х"a"Cd ЦLEcЂт,2є 0>ў”™ййЁXD*!жзП09ѕŠZ­†Š""Eh‘uo/ТффsZ;;ˆ“J,Nq$IBКR­V ­ЕdY“Ќ•‘e-’$Aесœ#veqвІлDqŒˆ`ѓœFЃAжlВ›чЈWМ*ЊJ=ЉЃОp‹sˆ8ъIД™bsЫжVƒ••%lлrвНћ€(ŠJ"$IТцЦ&Њ‚Њbл–ЗН}я;1§ь+*u/0{чEДьAš‚GEpЛЛ‡Жхё%@ЅpKЧAэF†zjhД[ОМ+Ў|…~~|џ‰‰Šїџ§чзџ”ЗЋ {ЂТЭлз‹Q-Їmuѕ3}/џ"P ЈТкд‚зNРуТAЛm™~§ўXЇѓмЃОˆ‚КќтИЏ-Ю/ЛqыT*™Ÿ››'ЗћU№ЮюB HГциьЬ‡гoохчЂмњBь)ВГ„pиA%Х v‰Hч;їœюйzOœ$€ пўЌMPГнIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-computer-color-16.svg000066400000000000000000000343141227240712600237510ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-computer-color.png000066400000000000000000000016711227240712600235120ustar00rootroot00000000000000‰PNG  IHDRФДl;sBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<6IDAT8•Я‹EЧ?]лщФЫ2й&фЄТЂУ^ФУ,шIТžVёВА"xЈ(˜\<Š QWї`.Сл’У^МˆЂ‰ЛDі˜eЦщљQ]я=U=лГ?ЂEw?Њ>я[пWU­ЏЏ?“wм–ЫмF]ћ.g43K0УАV,ЦЭŒ ђ# zЋШnцyЧm]yўЪцыkЫЫ—#‹€4Ёб€h%j%9<<ьnџАНyАp.s7ЎЏu–/-ЃЊˆ *‚ˆ  щ=umП‡8&}ї.ѕшїћ3лШЇгIЗwё""їо}ŸœЫШœУ9‡ЫЮe8чR,KБжЗsЌЎЎђС‡[єz=&“I7oВЦН{?ёэї?тВьД:Tt!оЌЬћšлЗ?G$†ˆ7ЭŒЂ,ё~ж rЖo%(ЫQkР!)6Ѓ, ЊЊ:WйБЗ:M&ЊЊЂ(‹9'„@R‘ Ѓ(JЦејl`‚yяЉЊUUQUcBЈQUЪЂœsNYQ–уquJŸЭ TЃS?У4ZЁšК(eY<ЭŠ’j\ХI"ŒF#ƒALІŠ%ˆЈЂ*Ч`ŠВD$CЃXB,^Q0њgФp8`8тНŸOўэї_ЉыњЌƒ9ooПѕЮ9ђ6И­xєшЯу­Ѕ•‰Rз5п]Н†ЉЦ“І fмљђT@Ха`ЈРЮ›Я&+Zлm2ž jбЧжRТtz|?ЄѕДb m”eСt:MGЛ)Œ зГй‚Z3Ѓžш\iѓl“EЩ“бUM•7дЂЁЖ0Ї‡–/ОєћћёŽpYКœsью> žЭЂв–ЯѕФN(ЖŠЭx­џъЙзуююЎ}§UPЕXX_Оњ3UЬтJсђЅЅЅЃП?ю.?ЗЬнoЖŸК|m*†* ^c*-x49їООuїЮіцкы§Юк§ЈИ§зHЊяпџя5Љ^Š‚ŸЌ‘Ep№сцУ‡ёйЧ_lЬfОЛdn[ZJрЄT‰ЪOcЁѓННН1№QъчЖ•—WЬзЧоFx'o;ВfщџеV^YйЧИњПgќ љКСЪIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-computer-color.svg000066400000000000000000000331261227240712600235250ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-end-16.png000066400000000000000000000011761227240712600215320ustar00rootroot00000000000000‰PNG  IHDRѓџasBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<ћIDAT8ЅSПoRQў?ђ^@}]LMH #иЉu4.jˆЁ ХWашјXœu№_0дjыRMˆ qMдNbЛA!V „ћzЯ9Ш+ж2ѕ ї;љnЮ—ѓsЏ!"8N„І]фoфоxzџсњ“ѕIўцэќЎ*@;0M`_{—,г|”u2w'y5TvС-Рu `циTX^vNйіЬыЮR1•J™cž˜СD# ЙМгбЄgG"‘ЁišжR&sњхыWл­жл\n1 „СLЯ.ЄIЯКЎ "јh11ZэЎ^ОћИљI7ъЕM br0D&FНQ3ƒ™!"? ЦљљљГ3ЖнкйљВЧL'§FžJ)Пр0vЛ]єћ§@"‘ˆХуёр˜?ш€JЉ#‹ЧЈЕ#‡т[Œ<<ЯƒRъHŒFЃH$мl6ЛхJЅЧL ц;XИА Ѓу/ ЖЖj˜›;'Z§Ччэm‘Ф Œ-ƒСnБXќo–e г‹i+™LJѕнћŸяпО яš–p‡™ЦZ]yzцЈG”u22шP­V{Но^љїnџVЉTRY'&†LЎqZ”+•!i}ueэо$OL~>UР4ЭЂН‹kЯ&љp8ќыљЦ  УhЧ§Ю ьWfilŽВIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-end-16.svg000066400000000000000000000174331227240712600215500ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-end.png000066400000000000000000000014141227240712600213010ustar00rootroot00000000000000‰PNG  IHDRФДl;sBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<‰IDAT8е•OKAЦŸй”HE(ЄVФMNо{ъA1 ^cl{Ў~врЁ‰—š*MƒБ_ЁŠZа‹ЅДЉ Л3яћіьжќ5]x™}˜хaоп<3ЋDё8т @ђбЪыхDєaГX:ь6џъMўТZ;hзuЏZ13/i­?чђйнц‰hbѕэ* …5о "уЃH%SДжх^ц,ŒЃЃя fЫg5юAЂ”B,ідIЇгЃхrЙœЫgгэX˜жZ03DŽыК—Žум8ЪЙQaЉ–1PРXtL%“ЉQ­ѕЇ\>ћђЎБ0Уi,šˆžЌЏ­C@Єћ‰@ччjђйЄ$“ЩБ­\>›й,–* k-XšЦ6'Ї'aСд]}іуЬ™ž~Ю бннн­\>›QJ™C@3Ч,cLXжкžкї}œžž8УУӘŸŸj­ЗDФхEѓР9Ÿ~fэкѓ<;CCC˜››‹ЦbБ_J)cТы€1Ьдбz/,J)QCC:P„Œ­5 ъ4юІ ГчyиллЋбcKŽ:6ЯZ km_Cf†R ‰D‚=ЯУўў~ˆ2JЉ/ТЌ[тжH•`vvС;ЭЬAšусс7ЬЬЬАяћЈT*u"ЪlK•х•%›Gд4v]їjcccМпЩ‹D"ўттb$žˆ‹ё ОVЋП‰(d8HVKމhїщллл?–,|Я—jЕZo7 pЖ и,–,лЯxye ззз\;Ј…эЗ#Ь0іпц |Леj7НLŠt*ztб+ЅЖ‰ш}/SЧq.З?mOнб?еїЯћ э7QVъW IENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-end.svg000066400000000000000000000126411227240712600213200ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-flip-horizontal.png000066400000000000000000000010261227240712600236530ustar00rootroot00000000000000‰PNG  IHDRѓџasBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<“IDAT8Х“СNл@†џнšт$wлIhЯєд>ёlЉDЂШЕ/Re""хТЁТл$/а[ %^_БCяєPтаpса=Vѓ}њGЃaD„—<ў"њ_!„†їыџнуюnЏž!ŒцлЦ@ƒвЈёcЫ4zы^‚wƒFнimŠk[–їzлИ\•№мtZОяW7 <пЋYЖЕПm§Ѕ„ЙЎk4wœAНQoyž_eœсќь|že™Й W*•y~6 ­_ЧwI2“З?ЇmvrzмslћгўСA•3"€@бc "4d,ГTЉ+š ICˆѓgСe­I?$ћ;ТЇoYЖТ­1Юq]Ьѓ<2BЛsdjM G™Rj8Й™RЪEGГYвcљБхюеђ<7Ѓ/=Ж*Т.‹уё8S*NnІ‡RЪ€(ŠŠпї‹ЖJ’kЛлД…бЈ ?Ќq)љu_tвT}н$Hзр’р1‰юjŽwы0‡ў0Й-УРўћ5ў šхлф SюIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-flip-horizontal.svg000066400000000000000000000061611227240712600236730ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-flip-vertical.png000066400000000000000000000011041227240712600232700ustar00rootroot00000000000000‰PNG  IHDRѓџasBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<СIDAT8­“Бoг@Ц?‡:юˆгиgKЌ0С”ЪrЁ%Žд Њ(ВPў<ŠЌ(*( ЂЂ%+ $&P)’]ІX щНЧPттT МщNяОпћєнТЬШ+ЯѓTчК џmtЛнѓМsJ бh\ЙZ*ОД…иV§Срэрћp?RШЏiХТВМZ­ІW§Њn скŽшxžЇ^ ȘЌ™–Йэћў:РЛvua›Ўэ˜KBV\ддveЃђРїЋы ‰@$юьш–А– iѕЇѕТчyCзѕIГйдŒУg‡“$IДХTК†G %ЕŸЗПPRGA‰$˜$IДVиN{йZ %[’XqЭџ )qЙќ:‚ƒ[„Т‡љОT*MˆЄЦ|Бnѕ”Dмќй0§ј$ѓеFЙьnЙ[: h$ ЦоўžF$qђюtЧбый”ОЮu image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-forward-16.png000066400000000000000000000010571227240712600224260ustar00rootroot00000000000000‰PNG  IHDRѓџasBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<ЌIDAT8ЅRБjA}Ї[q'ЅI—4B} 7 v“тH ФФ"dЫНђAД1(В,ЙJ8СadˆдЄP 5rчJ}јvow&…ь‹эШVpЛ0Уь{ЬМY‡™ё?Ш-{АйЈ}’R>{А@ššfоЫUые• @МzьЙљHJЙzЛц03ъ[ЕScЬ“EфbБ˜4лўЩЩaž“I_ЗлНуВVхfѓ#Р РЬ—1c~Гй ёyŒƒУƒXѓvяы^тJЩZУуёи!"03ˆ(ЛWyЉTBТ0ќ.ыоuZћ‡™L ­5ДжPJA)ѕW>™L@D‚ ЯyпфІ|žu`Ќq”RИЏ"B’$№<Žу8d‰2цyw‰хrBєћ§ Ѓѕ›юnoјЧВЈT*sСрKѓРѓx:BQ]Єжно 3бющЮ—Ї‹жшћ~ВБёоw…‹Ср8NЩОьД:ЃkМВVхЕѕ5  ceгѕnЋћѓz]мEМŽбptfSѕЂлоџuЛЖє+ …Я‰еЋэdрFX†пУ`!BА image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-forward.png000066400000000000000000000012331227240712600221760ustar00rootroot00000000000000‰PNG  IHDRФДl;sBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<IDAT8Е•НoкPХŸЈ€RІЖRГ!БЄ”…Ё­К†J- !ŽВ6RњUiuШ@2@ mйЉвН" UЌgПЏ‰)З W~ђ=WіOззЧЯšR ‹Въ,`kgг.n_Ь,Ѕк44rjY/ч €|.бOўŸ ќtuѕСЛЗЙˆ~bšцЋАk4п;%ыќЩ$h4ЅЛЅнeyъК{ЭЫ•ђ­фШлЖн ›VQэя”Т]EљЧпк§YJЁ:Ž–H$ФЭMU*eG(U8:8њ оћА'Ў~^щRJ(Ѕ+LsЮa’ЩЄшѕzьќќьVrёў№№Ы7`dЦRIPJсК.\зЅt §мзŒ18ŽƒvЛ­ЧуёЅlіMŒњёжжЦыqАT`Œ ч<‡iЧqаjЕє•‡+Kыый1єcг4ŸУ`uџˆгŒbX !рRW“R.+hP0FGС НyДцч„ЄR)щ8ЊЕZ_2UАmћG,„€Їщ–‚Еgk’RŠj­к— лЖПvЌMЯd2P№7=п5j`?Ю8i6›Z:–.ЅЈ]\ Лm—ЌЎќбј'ŒH$тхђ9уwП1дыѕ1h уƒOŸO‚w~'šзѓdЃб…ŽbкшvЛВqyљO(0у&4 :˜§tzyѓŽ…§ѓўLрМ›YлфIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-forward.svg000066400000000000000000000100161227240712600222100ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-forward10-16.png000066400000000000000000000012511227240712600225630ustar00rootroot00000000000000‰PNG  IHDRѓџasBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<&IDAT8ЅRMKQ=3oœ˜RКM\LС$PбB)iБQАдš•ButшtYV .†€`qгEСTВh jЖiRCЄVЃVЊM:oоЬМлE ˆ_zWюЙчžG!"ќЯЈW-Ќчц;г4‡реы—KSжд§ЫxЪUfЌщ@зu.}?эСВЎ‡‚{lл.uфЅЧг7X—іF†‡o†Тњ'Ыš4Ю’4ыХьnрћQЄ(JлщКDЂQіtl\п(nP,Sюм*VзMгьГmћ”щ™g4їf’ ‚”DPTеj†aЊЪиЏУCдЖk(•Ож›јлЖ5RbѓЫ&Є”-SЁ6n6›,‹aЋКУ0р8NdgчGб4Э> )сКю…У6цœЃX,BrЙŒоо^pЮokZзчS„—~ocЦ„ "ЈЊJDЄxžpдv!„p]їfŒЁППžч!‘H ђНЂдїї+ѕЃ‘V„ @2™@­~жжж1pw аcёx{{ЛЈеЖ6Ž›џjŒБz&“‰œ/A8ц“н==Q™Яч•ЁT '''(•О ю'~_лDsvŠFтcn…{žш|4ˆТjЁ!ШП—}›нъЄ‰Ш-чžы?€ТjСвO=ОV€Љъ’/dкЖэ•Ў№ЅЫЮg‹чyWFшtў›__–^ћ•IENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-forward10-16.svg000066400000000000000000000142131227240712600226000ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-forward10.png000066400000000000000000000015031227240712600223370ustar00rootroot00000000000000‰PNG  IHDRФДl;sBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<РIDAT8ееЭOA№gfЗ­$p0b= =pAвЃƒЁp -ЖEQяјi(!&#h,mМрI@ИYы~—э|М`—‰†ƒ“MvžУўvойwwс2Пѕ"№Фdlv|bќŽŸ>{ђІ>џ5ЌЄ ]qЙ?к˜Е17cБpп?С6{нžDl2ж##СVf˜ чс‚}О›z4дф2ŒG™ƒ‡x$ЙћЛkиФd,+…Ижmђ6<ётJ>ŸCБXТъъŠ…ТЈV+ШчђьнТЛ‚4Ч?Ÿ€GУ45ѕZ)ID "6 вdьюэВ–ц\яш€VJo~лфКP(bqqЁ(ЕЮМžYЕa””j{gл$"h­смрT.•JЈ‰|>З, ;;;мяїыСССЋ>М†GЇЇgWœ=жZC!Є”RžШі\L&ƒНН=!`YЖЖЖИзыХРРУVУeЮGЃс{Ч0щ3РiМ>g2gnY666ИЧуa Д.s>‰єš‡+&!Ю”о(лUкйэvУх2uЙЌjЄI9+&в K?•R№ћ§N6 =З{дў~ЎЖ”HT”ФX<џъь1cьИUs2c œsg;КЛЛЉНН RJpЮбллЋrћћЕФв’.;]РАПttдjŽЮkk_аеu‹ккл˜’JsЮy PЙ\Ў–LЅ*T‡‹MDО+­:Н Ї …ZJхU+UЌЏЏ—ћяї7YT(kЉTњ цыWг7Ё‰“&JЙŒd2]TJЖ@>ŸЏІ?-џuоŸЦЯl–’ЩTA =ййђyш…сT*]P‚Fm$§)}pz!˜16W2ЦцHВsQ`џн?яuА њчQ5ЈIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-forward10.svg000066400000000000000000000117751227240712600223660ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-newgame-16.png000066400000000000000000000011311227240712600223760ustar00rootroot00000000000000‰PNG  IHDRѓџasBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<жIDAT8…“ЭkAЦ3™Щ&Г›T ­є "((Й((ЅЅBНy(ˆра›ˆџёфХƒ и‹ˆЂR?ІD‘Ђ…`‘Bќ`7fЛяюzH­”$ы УР<Ьћ>ѓќuыіЭ%T~(ЈЩFŸХљЏм{8ХцV $WЫЅ™й‹}п*Я+яЎ №Љеъh­˜ЈiЎ/l Нѓœ=ў‘Я›‡){ѕrЏ]0yžZ+šЭцž‰Je4‚ОmsєрOЖѕ R^Фh Oљтб“(0"‚жzШВVЧІ^’щ„ц*^ѕЮZ{№їeЊю>ѓГДˆрœ?д Э ыпЎ`ђї(~эИRƒˆВjqїA-‰рœ\_ŽАо™Є$o‰у˜^ЏGЧ”вWtУ“|њЂ1"‚_v`ŒХ`ЩвЏ љ@ЪiвЌв4pЮБ_cd“ WzAOгэOSЋМСyDщ9’D0I"ЌЕ[ЌЕ[{.Zk1Ц’lƒЄЇvNЗkЯрyЩ?РF—x§nuЌіќйъ€Т(Œ3(вDd<ЦС3LЁ&‰c4ЦjЛŠ0iƒS E$‡qтnї;Y–…jnювRџ§Ю#*TАќТ Пз@эЮљIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-newgame-16.svg000066400000000000000000002403521227240712600224230ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-newgame.png000066400000000000000000000013771227240712600221660ustar00rootroot00000000000000‰PNG  IHDRФДl;sBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<|IDAT8•KkAEOUuu9‰šQQбh|ФGte@18dук;qу&K‚ў„€ ]ћEEPD…l"‚СФWbdђ0=]“б,4гу Mѕ­У­т^Z\Н~Ѕ/x-Ю™уStmYфіNœхDHЂр\ЕЪ”ДжнЌ”Zz"~ў\јН~xЯ7NУ†ЮїOsїY7J)ВЬ–Њеъ`”Дж”Ыхн /y5ђ’rЙЬкТWЪЛ_3ЫeМшЄЛы§Н†O3ћС+=y\ŠBЙŽ]\=EхФ=Л”ЬЈ‰3ШјJэEіmНЩо-Cќ˜пЦфј<‘Г)хŠP%-З?йЮlИ„P=ФqFk„XuŒ*‡№й8…Ж{\8їщœУГ"иљˆї_Ю‰oQ@”­5ZзпЅŒ№l&т Ÿw,у•СsщFоэ (n ТwМїXkЩВ я=!xжp‹4‹ЙџДНŽ›8†z:’=јр і3Y–‘І)išRЋеpv#п21]!ЫQ^ЧR*LlPТ’й˜j7dXПk^k” 8зчp,ЅФƒ 1†gDašo!@vВШ„А Ь2plиPмиnbCf7 љHuЁ‹‰…гјS,$Ќ6у8пŽumРЩи’Б7џМлхЭKЦ6-}™†—щ:Pj=J 5Р Ѕц5г НЈЇ"Я(ЅrыœsupГцA=yuПСyRЁ”Ъ­ћЮ‘cЅdnГЎЕцхеЕфИбМ<К–юИбМ<:я=‘Г.™œ˜,:йлtSž†ŽŽŽ$ЂRщыCˆџњ™ўcBј@ image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-next-piece.png000066400000000000000000000007001227240712600225710ustar00rootroot00000000000000‰PNG  IHDRѓџasBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<=IDAT8Ѕ“БKqЧ?wœ‰bm…žЎMэщЙ6Yцв"ˆ“cвб нE{{[ƒ ъЄчЅчя^C"v\љ…ЧяёрћхћОМŸ&"l}#vTJхтШВ,упњ{v?§АN$ђ ™Ќ™72ѕz=Ж<з …‚‘Ыхжк[РѕF^ЕZЅн~žtКнЗTbчЌбhјzкмыLОЦуАJmЇ<Ќb1if2'coєДpb(ЅvkЕ‚€?яr  Тi>ŸtlћИлынч€яЯсW]г@гЅ˜Э|\#fŸЗЗwfXёј–*•Jq(ьЧэ­љTнhQ.Б\Й”ђu™WлqћУak>UWЭfS„ІП ЧvмС ўpыШ‘:‡ѓЉњX%CФ ТАёoќW4У bx=IENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-next-piece.svg000066400000000000000000000062221227240712600226110ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-next-variation-16.png000066400000000000000000000011171227240712600237270ustar00rootroot00000000000000‰PNG  IHDRѓџasBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<ЬIDAT8Ѕ’OkQХЯ›ЬЬгI„ЦрЂ DPHё;+ˆ-!"""h?ƒ{w~€ИЪТ?i'"нИq9Џ qЗС…YжQ;uцНyїКhR4NЄт…ЛЙœћуœЫЬŒџ)w~pћЮ­Ц˜ZЁи-MЄœыѕzщBј` Оєff  д}к==™LNX 8‚XŒЧŸ`ŒD$˜axСž(X"dY†<ЯADг.м/0Дж0Ц€ˆРЬ Ђ“ь€ˆ`э?8 ВаZCk}ьрЏ€{їя>N’фHщgBЄi:wBЙ\Жжк/эЮ&<з}§тљі† щсзЎч[ЋЦйzН.™֘yЮњњЕ`џ3”R‰бєФьлэіŠ/нсjГБTЉœСh4:>ЁVЋС—>TЌcэк ?PрЬВDQ4NI_?|;8јŽ0 ‘eВ,УђЪ2ЄєЧъаs}Жќ›ƒYu:7/:ЎџОеjV€беътX§0ЙН1иМ§Uџ`чrЩwо]i5ƒ @Х{ini3ъGoцЕ…€#'MЧѕі рX›wv^Ок-в-Lу\žЌіŸѕе"ЭOчў!fНp‰IENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-next-variation-16.svg000066400000000000000000000162031227240712600237440ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-next-variation.png000066400000000000000000000013201227240712600234770ustar00rootroot00000000000000‰PNG  IHDRФДl;sBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<MIDAT8ЕдЛnAрfжЦND’†ИЂСv\Фр…w0% ˆLЧ+№дTюlЏ/ub'DBŠ)R$$hYV*Ђ,bmfц kНkK(лl1{О=ѓЯбfЦE<ђBTNиТГч›ћОячЃŠЅ’П$R•Ъћ“™abОЕЙљЩDв2ў&ЦУЗR•Ыхп?ћ4`v`0О}џЊŒ1`f03ˆЬŒхх”‰кM 1Œ1аZсsœЌ~№‘0ƒ`Д1fищgŠ*C=вёљˆЂЧ4&€˜BЃ`њŽ‰Zы№("š…ѓшсqkm”;e온uHЦL˜i*Џ?|ЫФЏ‡ ŽcЧ‰Ш˜ПWў‰џљб“"@ љЉV­п-ј#хFЁPИ–JЅЎ0БВdYk-&e< дƒЕЕЋR*œžў ­­­3cЭ›sOoЗтzёЖЙ—П—/-,ХŽUПп‹™!ЅD:& НН}І Нhд‰p_НЛ_МЙ;::RОяt+„@6›%!:эі™еіЅы6ыAgькlVš_ шўСўзэvu&“ЖЪQУC$"Є3i€іvл3ж”ўE'ТAќ№№аыѕz:—ЫYЧq@DШd3$…@ЇгёŒЅRНкr'cQLŠ%—ЫХчoЬЧњО/ЅRМЛГыKЅF­Q Ћ„ƒјЪJ6žH&.}ийѓШкWЎлЌFеM…ƒ8.йRНкЊLЋ™ €тF1Ѕ /Иnыу,пџ“ћы2EyѓєIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-next-variation.svg000066400000000000000000000136301227240712600235210ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-piece-clear.png000066400000000000000000000013321227240712600227030ustar00rootroot00000000000000‰PNG  IHDRѓџasBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<WIDAT8Ѕ‘=haЧŸцюН IЌCю:6‚‹`IJЂ"4MњЁв„J“\Ю тЂ‚“ƒ ƒЋ›Ѓƒ"ŠhОЊD 6&з$PiBЅTШb“\ыzImююНsа„&A§O/ЯяЯџбuў'dїСѓa'Aб)YVnІщ7ƒ‹\|)@‘єљЧSЉдFwnшСˆЪŸ9}ъ8M‘Щ(Н0 ГN—ыI…Hь$H*?3;3j;fƒo{{ k-зccYЈзАКњ^ъЈъ\цeцурp~Пtм6 ы ыѕzrЙ\KЦъьaИwB7‰Фђ'UVn‹Х cРšš†Ё\*яЋЊzwpч&КяѓљŒšІAWтіL™BїbБАїЗŽум$EЌfцБ1Э&Єг™vГй+УРєДЯL є6 O ~Т†w€пЬВ,ˆт.ђyщ{{џ–P$Ql‚•aРыѕ˜ „Vx>|ЖO€(тЉcвnaXvE …М„e>™Ь<ФŠ2П&[ЛЂV†‰‰“BЯћ2VЏT6Њэ­­-A’U|>‘X^џUьК‚ёЙRЉмњВН ››Ÿ[*–Џ }cєRtфcЌШ—“Щхђ`YБXxŠ@д3Uы\MНxѕaH№Џ1ќ}хЯљl ь[лсIIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-piece-clear.svg000066400000000000000000000065451227240712600227310ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-play-16.png000066400000000000000000000014051227240712600217240ustar00rootroot00000000000000‰PNG  IHDRѓџasBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<‚IDAT8m“ЯkYЧ?§К'3A#jітцЈвPЃ(EYі$тzЭХЃА{г`aўŠŠ: zTDOA0{Y5 ‹s ъСq‰3щющnЛъН=Ь$qB ОМїъНzѕ§UоЉSQЕППžчYФ sжaХ9зuиюОVЋЮфi:TћћыућЧЃнc{‘юух Хч,8p8ІЇ_EЯЇІъAЛFЛvŽёЫёМyѓЯ3прƒё§žе3c УУ›™М|‘GGˆрАМŸ}ЯЕЗшыыCDTэœU–ќwnп$Ы3D„ ,;Ю0ЌБаjT*нР.TЩѓœ4‰I’„4IЉжjЈ eY,ў†! јОˆeЭf“$‰ЩѓUэЂ“LЕУ,ВDЄ$ CZ­&y^аl5iЇ)ЊUaг№џ5цI“U% CФ e)Ѕ,K˜ХZл“Mеђэл ›иЦёaюыжЏ§ž кaЧ1ЮYTД›]QЋEСoЇчюНЛЈќ…1fЙ‹ jaHѓkŒЕЋв ЖЈZŠЂРѓ<Ž=ЦШ#дoорѕпo{k02В…?aŒСї Цјн~№iЕšЈ жZЖmнЪй3g9wўƒk%‡:длЖK “Ў"Њvжвh|сsу3q’t$EЮУOVŽТ’EБФрщГЇмПwŸƒGp§Ъ-‚JP™y1ѕ2:ђѓ!ŒёVaрјѓw$qТфхKЬЭЭqќфQў§чA%˜ёЖnТОjНfQЯ№иe9Cж300@Ѓё…ЏѓѓЈuдЊЕ™ЌMЌШИ:іяоБcєзеюўћ?P†ъЙIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-play-16.svg000066400000000000000000000334721227240712600217500ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-play.png000066400000000000000000000021501227240712600214760ustar00rootroot00000000000000‰PNG  IHDRФДl;sBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<хIDAT8•ЯkUЧПsпdц‘уCѓšTэЎ6СEдТг*.mаt%fЅZ‹рB(эЂEђ0h ”vЁR55 Ѓ‹К”BњTкњlъKBпЬМ;oцžs\ЬЬЫФФа —Йчpячœ{Ю™s­ёёё^;ЏІ”Ѕ&Ђ(,`—!"Щ dtБ^DагcзЩ№ДcзЯиv^M|трфБЃcљЁСЁ‰Щ„„ŒЁŒ‘еееТќељЩ;+JYjтибБќрўA03ˆ L"‘БIжЩфькФ{ЙИПˆrЙœ‘ ;tЁ80"ƒо?‰……(eСR J)(KA) JЉDg%КŒЌJЅ>ўd ХbZы‚ZС?§ eY;Н#oгЇ7 У•Ъ"‚n8Ў‹0ьd@њl›>cРu] )и$‹РuјОџПžmХ–Л:­5|п‡у:]Ž1ЖI’$8Ž‹Жпо˜РТ0„я{№}Оп†1˜Ўуv9;BсКкm‡waЇƒfГ пѓаџШ>4ъ-ј›ЬObИЎГW(\јmDБwžчЁйlЦ֘є>дƒЁЁ"jjkЕ !0зQ6е­W2pоІ‡к?їpћі-TЋЁбЈ#t‚Z0ЦрдЩI<џмГxtрa0н‰=NjкС6‰ ˆCБМ|kЋДXРЏYТŒ(2А”ТёзO`ј№О™ћыыїс8qђвrSdЖВяИ.t[Cы‚ @шјлбД†Da&3cdxЇOŠссУPJЁоЈЧy1iђШ@p]A$ПvšГ€%ўFQ\" A_oN}јћч.Ѓєв‹™ЊHCсИИяЕтƒL J€Ф ,XТв5№Ъ‘#xђа!œПp}}ћ`ГUnУ#‡БВr'юЪ‚•є„T†eЁQo€#Т1T„!,X^^FЋеТцІ—ёXЏ•_нГ=c0S™MnД t€йЙYмќѓ&оzћ>џь,ь\.WџЛZ- Ф•oчwыѓл~7y ДZ­тьЙs€оyїMЌЏm —Ыеэ0ŒІЏ|??9іF9?vМ{œ}52MоD•/ОŠ“*ŒkП_УЅK—Qzљ<§ЬSИ{ї~ќюjvЂikttД SЪТDЇЖБ-4№иу0ѓх *• –nм@­Ж2&yšœ:šжZŸБв2JЅRž$jїїїыVг›ї=џНЅЅЅіn{эІЦУф”}}m­6s§Ѕ‹{mќ‘Ьня/нБIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-play.svg000066400000000000000000000211571227240712600215210ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-previous-piece.png000066400000000000000000000006421227240712600234740ustar00rootroot00000000000000‰PNG  IHDRѓџasBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<IDAT8Х“СNТ@EяДc$e---бЕ+?ЁV\KHS~b5!щ”_№41[SP;з…Ф-А№­^^цœЬ}“$БM[б? ”R2ŠъG ”Rвлw у~mRJz7qЫŽŸ^АчјЕк™ЕŽ@~ТNRvm?N-€А,kF_ŸЧ4ЭчЫі•#+nзБmџ$,€Ш4бlF  rйƒDЋua€„&45Дж +‡јЕџ>›Gx|xŠі§RUU4„@_Oг4-ќI„ahюьЪ^ЉДWѕ§bЇнAмНy–h@Чйль§|<ня†/yРС\ђ:Ыъ“ЩИП‘`yнаѓ ФПџЦ2†…Ї%,IENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-previous-piece.svg000066400000000000000000000061331227240712600235100ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-previous-variation-16.png000066400000000000000000000011021227240712600246170ustar00rootroot00000000000000‰PNG  IHDRѓџasBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<ПIDAT8Ѕ“MkAЧџ3ћ‘]›s‹<ˆ ‚KЗ)жХ—*ДЫf“€?…аOгJ[*$V№b бЯ‘xШбKД3Г3‡ВKХІFќgžпчy†aD„YЩВЭлЬёЊћЛћУY5|жEвJюYŽ;t8џи~бој'A–mнё,ч$Ž-l<{юY o:f<— Mг[–у~zАО~5№}„'ŸzмЖпЖлщкЅ‚,лМiЛќsЃбИFу&“ Д1ˆЦnля›Э­•ѓ +–˜t’.sОFбJ†uŒЧ#ІЕ†1Цдj5 §ўЩЉЬѓЕУНУ/eiš.zЬ./п–—XЕКР”RB@J )%Т0Єz=D­VЫъЗZЩнR`ЛќЅЪUm0АнЁ”*A!„ШѓмtЛнЧЧ ”КтV*ЏР€НзлЖ еiў,`)%ˆЈc:ZJш зы}/v`_є4֘RPР֘‹Jg 4„PJ•А6z> џЏњC ѓ9;(FXmDDА3%#‚EDљ_чпŽŽо]Чћ[8чSпїOЯŸБЫОѓ<љ'Єјў~мžIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-previous-variation-16.svg000066400000000000000000000162061227240712600246450ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-previous-variation.png000066400000000000000000000012301227240712600243750ustar00rootroot00000000000000‰PNG  IHDRФДl;sBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<IDAT8­еMoA№џЬ@%,ЌBк&5’и“gуЫЉ†CГМUщ–~O~ ижCEвіdтEя~ƒ0J=ъЦьЬѓxРХBaK€'™УюьќіЩ73‚™1OЙЎћ<Яћ:ЯѓrNєylM|Rq|<8xѕl%№?Д]ЋэYЕъЫ”ŒЉ“y№H8DЋе=ыЎ}й\6^)зR2ІNіїŸ.‡hЅ\Гl;ƒўЗžьv/U6——œJJ бv]їЩЌѕbкЧ бrЙjйЖ~П'1`fH)БЕuп ƒ гљ№›4Jžч}ОЁЅŠ•ЮdаыuЅ1Dfсљ|о ~ єщйщ/qšЭw_fТ!ъ8e+“Nутђbд)3пР …‚ЙКњЉЯЯЯnрrннuЌѕu_[CўAžƒ @8Дж‚D„э‡лЌ”››wŠХbRuъ‡ѕЧЁН!.оhm2эі{@2™дFCi­еѕN™B@@Дš­?Оя'$$bRМрŽСЭЗ-g,ыF‰иA05"‚яћ Џy,І§Бi7Уb&„№ѕœ•R ІЈЅбА1ZыБn‰hxMб{L$LєПуЩœ‰–шx2Š'ЂхЂ тЉЃX &h­WЭˆ‚™AЗ‘АBэМи1`ЦаaB0ˆH-+ЅGGЧ93%еїYsSЗЭUд\gо"ѕъ ˜;бPgIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-previous-variation.svg000066400000000000000000000133431227240712600244200ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-rotate-left.png000066400000000000000000000013351227240712600227630ustar00rootroot00000000000000‰PNG  IHDRѓџasBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<ZIDAT8•гOOa№ч-яЛ]Ж$№ЯAL!Zиv  7.rБ)%ФЋпBОƒ'уY ыІхтФƒБ)[-ЌŒ11‚4…nЫюьюыI…F>ЇЙЬo&“ “Rтhђ…мb4Н+ЅМFDУBˆoŒБВыКЋ–Y*Ђ'ь/ф.*ŠВЂiкd&“щD"‘@ГйФюю.lлnдˆhС2K_ŽљBю<чќ}6›UЌ!Є”eˆЃuеЎ’mлŽяћW,Гє8(ŠВЌыzЬ0 с“JЅтеыuЗгщєЧbБіјјX4“1”)}JЋе6—м€HО›WUUOЇг СВЌv­V[wgFJwgfsѓнњъjЉэ!59ЉЈЊЊч ЙyрŠЂЬЅR)MBbcЃтэяяП.>Y™=r'РьтН…еъ›[К>ЅLLLh•JeРГ€щГƒ[[]"ZъН4бƒFЃqш!ƒ16 œˆ.Јj?‚0€у8g4ўxлэvтгb њњњ\рBˆЯ{{?. AгДNЋеJц Й!Ф#"Кc™Ѕ&XfщРЙ^5"Ѕ|ѕ}gA"™LЦ8ч9ч/ƒ И ўmўDdж?дђ<ŒёббKЃ7nN'„]ьDР2Kkžч­mиЖв™Д:<6–1цhАоwўпќœb#з›œuIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-rotate-left.svg000066400000000000000000000065421227240712600230030ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi-rotate-right.png000066400000000000000000000013201227240712600231400ustar00rootroot00000000000000‰PNG  IHDRѓџasBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<MIDAT8•“MkSQ†п9їœло|*t!ЈаЦ&6ijьJщBл]Vнв”тVpсOаџрТў‚cˆ+Б›j#BEЅІMшЅ‚рІ‹”єо$'7уЊ‹ёY ѓ№2У3cl.ГЂ”КХЬзzНо9)хw"zЏЕ~V,”уt"Шц2Ѕ”O,ЫJMN^ „Тa~8ŽƒfГ {пn9ŽћЉзы- ЅЦ/‚l.sо0ŒЯБXдХ”"30Xзj5]нЋ{ž—,Jп@€”2NњІтSŠ™QнЋuыѕzЇнnFGG[ууу#гЩ„™œžVђеjе<€› ВЙЬ’eYГёDм$"”7_Зlл.ЛЎЛРЬ!зulл.Пмxеff’ІeљfГЙЬHЅдbьr4( ‰JeЗы8ЮЛќZ!=АЇmщ•лЫ{ЛЛ7ЎЄRf"noX№T˜ ‡Я€СЈзПЖЕжїOoДжОt !066"šЩЬђfЙBˆc'Ая8Ž% ЁPZы ѓk…Љ? ›ЫœUJ=зZпƒŽ!DшшшЪTѕŸWј aЯѓЎ+Ѕоh$qŸ0 ‚ћќvLгtчччУFCЅRWЅчyийй9жZ†РDd‚"‘ˆŸˆАЕЕещvЛыХBi}(Сˆ€PЉTdПпxвџ031Ь3#N Ѕд‹l.33L‚ІжКГњhud03ћMгМр~чџх?Књ6sПIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi-rotate-right.svg000066400000000000000000000065761227240712600231750ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/icons/pentobi.png000066400000000000000000000022641227240712600205410ustar00rootroot00000000000000‰PNG  IHDR00Wљ‡sBIT|dˆ pHYs з зB(›xtEXtSoftwarewww.inkscape.org›ю<1IDAThеšЯkœEЧПѓОЛЅФm”‚Д‹ тЁg/žд€–ž=Њ‡€ёЂ@{ˆ‚‡‚Oz**T< )єT’ŠPR(‰mb2яМ3ЯЬуЁyЗЛйнwОI7&} Аl>Ь;пw~|fHŒЊтYЎтИ;№Деi>,--K&|m`оўў„•(єчBЛ///ЏCUП10я_пЈъ˜+jb р20ђІuОАН]лК0LЇtЛFЋЊыvёУЕmуe2cDL‚( /<6Ыеџ”јћњМiњК/РуЯНЪ!оКeк:† byцL™хњ§фc2ЋVZЙ`5]œЛdжr\Ѕ ˜7У§>д"–Њš)ЗЦ P}ќгдxчИŽ\А–ъXђ†ф1`dЗQUЊcб9šѓЁЮr>фSHP”ћМѕdJ  фА\№љŽ%Qš+ІаНФМчhށш/WЊCR“Œ™фkсCШЗ@sšFG`Аˆ›ХСL šр}ўЭjЩM]ФЭ—ag™щ!1–$Wи‡uiчМіпїѓмўУчX€o€ГВ'7ЁHъaдюѓТМтмЎiНS\§Жc@pп_л6стdn`ь‹лПщˆьЦ$y„­ћ_•mЯ››Cдњ§2ЎЌДrшїSйы†ѓ"fэA;Ќ&еўH€Б5РБу8cЋъ“ “1кGи РšxЦЦžz”h~УЃlC>рŒM˜XZЮBƒВm(„c2і„Уми"Ža+лїG`lвФ)З&ЩЈЩ)$ф8ˆБГйBћ-€rSˆ=*іLœЙ‰7&ЎБЎкЦцf{C@›˜66ibфœ{ѕМ|>oт>[2Еїуœ*’&$Uœ>uЊ(ЫХНљк—”‰џјeF&іaБ\]ЛлЪН>>Й †сМTЄ‰_š‰жVЛ4ЧдЄ;ёиА&Ё}О€ГѓХ1•&ьB‡6БH~лГv—ци;qЛ‰ob‘ќиЊЂЙЇ7ёоU-"ѓЩO5WYšcMЌгюФƒ]ˆ8JдФ›uЮбмLм€5БxŽ6qš•‰kї№^ћЃ,7Г;1kтппИcRцю\Ј/>ќюSћ~жФџ|лHYNѕъlLlzŸ–9ює’‹”‰+ЭънПВœЊўwтƒ;п^cіжkbŠ ЄБ+ЫэVжЮюNЬpС“ЦоЕЄБ™Є‰ЮгЦЖ4GрL|\ЦЮžFY3œїМБ™pЎЇ‡71СщAŒM˜TУ@чоКƒI™‡”ŒБƒ€6БЏЋ,WЪN3хiMѓцоњQ“ў‡n€~КyѓзЫРа"Ž‹PНЁt'АЊ7bФbѓ…aэ{Rы™џo•џЁМѕЂЬi&ОIENDЎB`‚pentobi-7.2/src/pentobi/icons/pentobi.svg000066400000000000000000001745471227240712600205720ustar00rootroot00000000000000 image/svg+xml pentobi-7.2/src/pentobi/manual/000077500000000000000000000000001227240712600165315ustar00rootroot00000000000000pentobi-7.2/src/pentobi/manual/de/000077500000000000000000000000001227240712600171215ustar00rootroot00000000000000pentobi-7.2/src/pentobi/manual/de/become_stronger.html000066400000000000000000000105241227240712600231660ustar00rootroot00000000000000 Pentobi-Benutzerhandbuch

ZurУМck | Weiter

Ein stУЄrkerer Spieler werden

Pentobi besitzt Funktionen, die Ihnen helfen kУЖnnen, ein stУЄrkerer Blokus-Spieler zu werden.

Spielanalyse

Sie kУЖnnen ein Spiel analysieren, indem Sie Spiel analysieren aus dem Extras-MenУМ wУЄhlen. Dies lУЄsst den Computer eine Bewertung jeder Brettstellung der Hauptvariante ausfУМhren. Das Ergebnis wird in einem Fenster mit einem Diagramm farbiger Punkte dargestellt.

Spielanalyse-Fenster

Eine Spielanalyse eines Spiels der Spielvariante Klassisch (2 Spieler).

Jeder Punkt reprУЄsentiert eine Spielstellung, in der die Farbe des Punkts am Zug ist. Die Punkte sind horizontal nach Zugnummer angeordnet. Die vertikale Achse reprУЄsentiert die Wahrscheinlichkeit, dass die Farbe das Spiel gewinnt. Mausklicks im Diagramm gehen zur jeweiligen Stellung.

Die Werte stellen nur SchУЄtzwerte dar und der Computer wird manchmal Stellungen nicht korrekt bewerten. Aber ein plУЖtzliches Abfallen des Wertes kann Ihnen dabei helfen, ZУМge zu finden, die mУЖglicherweise schlecht waren. Sie kУЖnnen zur Stellung vor dem Zug zurУМckgehen und versuchen, einen besseren Zug zu finden oder den Computer fragen, was er gespielt hУЄtte, indem Sie Einzelnen Zug spielen aus dem Computer-MenУМ auswУЄhlen.

Ihre Wertung ermitteln

Sie kУЖnnen Ihre Fortschritte verfolgen, indem Sie gewertete Spiele gegen den Computer spielen. Die Spielergebnisse werden benutzt, um Ihre gegenwУЄrtige Wertung zu ermitteln. Die Wertung ist eine Zahl, die Ihre SpielstУЄrke darstellt.

Ein gewertetes Spiel wird mit Neues gewertetes Spiel aus dem Spiel-MenУМ gestartet. Wenn Sie in der gegenwУЄrtigen Spielvariante noch keine gewerteten Spiele gespielt haben, werden Sie danach gefragt, eine Anfangswertung zu wУЄhlen, wodurch die Anzahl der Spiele reduziert wird, die nУЖtig ist, um Ihre wirkliche Wertung zu bestimmen. Falls Sie AnfУЄnger sind, belassen Sie die Anfangswertung auf 1000.

FУМr jedes gewertete Spiel wird der Computer eine Spielstufe fУМr den Computerspieler gemУЄУŸ Ihrer gegenwУЄrtigen Wertung wУЄhlen. Die Farbe, die Sie spielen, wird in jedem Spiel zufУЄllig ausgewУЄhlt. Um eine akkurate Wertung zu erhalten, sollten Sie gewertete Spiele immer bis zum Ende spielen und wУЄhrend des Spiels nicht die Spielstufe oder die Farben, die vom Computer gespielt werden, УЄndern. Nachdem das Spiel beendet ist, wird Ihre Wertung in AbhУЄngigkeit vom Spielergebnis und der Spielstufe, gegen die Sie gespielt haben, aktualisiert. FУМr das Spielergebnis zУЄhlt nur, ob Sie gewonnen oder verloren haben, oder ob das Spiel in einem Unentschieden endete. Die genaue Anzahl der Spielpunkte spielt keine Rolle.

Wertungsfenster

Fenster mit dem Wertungsgraph.

Sie kУЖnnen Ihre aktuelle Wertung jederzeit mit Ihre Wertung aus dem Extras-MenУМ sehen. Dies УЖffnet ein Fenster, in dem die Entwicklung Ihrer Wertung wУЄhrend der letzten 100 Spiele als Graph gezeigt wird. Die letzten 100 Spiele werden automatisch gespeichert und kУЖnnen durch Doppelklick auf die Zeilen der Spieltabelle unter dem Graph geladen werden.

ZurУМck | Weiter

pentobi-7.2/src/pentobi/manual/de/classic_rules.html000066400000000000000000000076401227240712600226510ustar00rootroot00000000000000 Pentobi-Benutzerhandbuch

ZurУМck | Weiter

Klassische Regeln

Es gibt vier Spieler, Blau, Gelb, Rot und GrУМn, und ein Brett, das aus 20×20 Quadraten besteht.

Jeder Spieler besitzt 21 Spielsteine seiner Farbe, die die Form von Polyominos bis zur GrУЖУŸe FУМnf haben. (Ein Polyomino ist eine Figur, die aus einer Anzahl von Quadraten besteht, die entlang der Kanten verbunden sind.)

Spielsteine fУМr Spielvariante Klassisch

Die 21 Spielsteine.

Die Spieler setzen abwechselnd einen ihrer Spielsteine aufs Brett. Blau fУЄngt an, gefolgt von Gelb, dann Rot, dann GrУМn.

Jeder Spieler hat ein Startfeld. Das Startfeld von Blau ist in der oberen linken Ecke, das von Gelb in der oberen rechten Ecke, das von Rot in der unteren rechten Ecke, und das von GrУМn in der unteren linken Ecke. Der erste Spielstein eines Spielers muss sein Startfeld abdecken.

Spielbrett fУМr Spielvariante Klassisch

Das 20×20-Brett mit den Startfeldern
durch farbige Punkte markiert.

Die folgenden Spielsteine mУМssen so auf leere Quadrate gesetzt werden, dass der neue Spielstein mindestens einen Spielstein der eigenen Farbe Ecke an Ecke berУМhrt, aber keinen Spielstein der eigenen Farbe entlang der Kanten. Der neue Spielstein darf die Kanten von gegnerischen Spielsteinen berУМhren.

Beispielstellung fУМr Spielvariante Klassisch

Eine Beispielstellung nach ein paar ZУМgen.

Wenn der Spieler einer Farbe keine Spielsteine mehr setzen kann, muss der Spieler aussetzen und die nУЄchste Farbe ist am Zug.

Wenn kein Spieler mehr einen Spielstein setzen kann, gewinnt der Spieler mit der hУЖchsten Punktzahl. Die Punktzahl einer Farbe ist die Anzahl der Quadrate auf dem Brett, die von der Farbe besetzt sind, plus ein Bonus von 15 Punkten, wenn die Farbe alle ihre Spielsteine setzen konnte, plus ein zusУЄtzlicher Bonus von 5 Punkten, wenn die Farbe alle ihre Spielsteine setzen konnte und der zuletzt gespielte Spielstein der Spielstein war, der aus einem Quadrat besteht.

Klassische Regeln fУМr zwei Spieler

Das Spiel kann mit zwei Spielern gespielt werden. Der erste Spieler spielt Blau und Rot, der zweite Spieler Gelb und GrУМn. Die Punkte von beiden Farben eines Spielers werden addiert.

Farblose Startfelder

Beachten Sie, dass die ursprУМnglichen klassischen Regeln fУМr Blokus farblose Startfelder benutzen. Dies bedeutet, dass jede Farbe frei wУЄhlen darf, welches der verbleibenden noch freien Startfelder sie fУМr ihren ersten Zug benutzt. Pentobi unterstУМtzt zur Zeit nur die Regelvariante mit farbigen Startfeldern, weil diese Variante auf dem Blokus-Online-Server auf blokus.com verwendet wird und auch in den meisten bisherigen Blokus-Turnieren verwendet wurde.

ZurУМck | Weiter

pentobi-7.2/src/pentobi/manual/de/duo_rules.html000066400000000000000000000027231227240712600220140ustar00rootroot00000000000000 Pentobi-Benutzerhandbuch

ZurУМck | Weiter

Duo-Regeln

Die Spielvariante Duo ist eine andere Spielvariante fУМr zwei Spieler. Das Spiel wird auf einem kleineren Brett mit 14×14 Quadraten gespielt. Es gibt eine Farbe pro Spieler (Blau und GrУМn) und die Startfelder befinden sich nicht in den Ecken, sondern auf dem Feld mit den Koordinaten (5,10) fУМr Blau und auf (10,5) fУМr GrУМn.

Spielbrett fУМr Spielvariante Duo

Das 14×14-Brett, das in der Spielvariante Duo benutzt
wird, mit den Startfeldern durch farbige Punkte markiert.

Beispielstellung fУМr Spielvariante Duo

Eine Beispielstellung in der Spielvariante Duo.

ZurУМck | Weiter

pentobi-7.2/src/pentobi/manual/de/index.html000066400000000000000000000025511227240712600211210ustar00rootroot00000000000000 Pentobi-Benutzerhandbuch

Weiter

Pentobi-Benutzerhandbuch

Pentobi ist ein Computer-Gegner fУМr das Brettspiel Blokus. In diesem Spiel setzen vier Spieler Spielsteine, die УЄhnlich den Spielsteinen des Computerspiels Tetris sind, auf ein 20×20-Brett. Pentobi unterstУМtzt auch die Spielvariante fУМr zwei Spieler und die Spielvarianten Duo, Trigon und Junior.

Klassische Regeln
Duo-Regeln
Trigon-Regeln
Junior-Regeln
Wie Sie Pentobi benutzen
Ein stУЄrkerer Spieler werden
Das FenstermenУМ erklУЄrt
TastenkУМrzel
Systemminima
Lizenz

pentobi-7.2/src/pentobi/manual/de/junior_rules.html000066400000000000000000000021611227240712600225270ustar00rootroot00000000000000 Pentobi-Benutzerhandbuch

ZurУМck | Weiter

Junior-Regeln

Junior ist eine vereinfachte Spielvariante fУМr zwei Spieler. Es wird auf dem gleichen 14×14-Brett gespielt wie die Spielvariante Duo, benutzt aber nur die Pentominos mit relativ einfacher Form.

Spielsteine fУМr Spielvariante Junior

Die 24 Spielsteine, die in Junior benutzt werden.

Bonuspunkte werden in der Spielvariante Junior nicht benutzt.

ZurУМck | Weiter

pentobi-7.2/src/pentobi/manual/de/license.html000066400000000000000000000026351227240712600214370ustar00rootroot00000000000000 Pentobi-Benutzerhandbuch

ZurУМck

Lizenz

Copyright © 2011т€“2013 Markus Enzenberger

Dieses Programm ist Freie Software: Sie kУЖnnen es weitergeben und/oder modifizieren gemУЄУŸ den Bedingungen der GNU General Public License, wie verУЖffentlicht von der Free Software Foundation, entweder Version 3 der Lizenz oder (nach Ihrer Wahl) jede spУЄtere Version.

Die VerУЖffentlichung dieses Programms erfolgt in der Hoffnung, dass es von Nutzen ist, jedoch OHNE JEDE GEWУ„HRLEISTUNG; sogar ohne eine implizite Garantie der MARKTREIFE oder der VERWENDBARKEIT FУœR EINEN BESTIMMTEN ZWECK. Siehe die GNU General Public License fУМr weitere Details.

Hinweis zu Markennamen

Der Markenname Blokus und andere erwУЄhnte Marken sind Eigentum ihrer jeweiligen Markeninhaber. Die Markeninhaber stehen in keiner Verbindung mit dem Autor des Programms Pentobi.

ZurУМck

pentobi-7.2/src/pentobi/manual/de/shortcuts.html000066400000000000000000000042021227240712600220430ustar00rootroot00000000000000 Pentobi-Benutzerhandbuch

ZurУМck | Weiter

TastenkУМrzel

ZusУЄtzlich zu den TastenkУМrzeln der MenУМpunkte, die im FenstermenУМ gezeigt werden, werden die folgenden weiteren TastenkУМrzel von Pentobi unterstУМtzt. Beachten Sie, dass diese TastenkУМrzel nicht aktiv sind, wenn das Kommentarfeld sichtbar ist und den Fokus besitzt. In diesem Fall kann der Fokus vom Kommentartext durch die Tabulator-Taste entfernt werden.

Plus

NУЄchsten Spielstein auswУЄhlen

Minus

Vorherigen Spielstein auswУЄhlen

0

Spielsteinauswahl lУЖschen

Leertaste

NУЄchste Ausrichtung des ausgewУЄhlten Spielsteins

Umschalt+Leertaste

Vorherige Ausrichtung des ausgewУЄhlten Spielsteins

Links, Rechts, Oben, Unten

Bewegen des ausgewУЄhlten Spielsteins.

Enter

Spielen des ausgewУЄhlten Spielsteins.

1, 2, A, C, F, G, I, L, N, O, P, S, T, U, V, W, X, Y, Z

Einen Spielstein entsprechend der УМblicherweise benutzten Spielsteinnamen auswУЄhlen. Wenn es mehrere Spielsteine mit dem Buchstaben gibt (z. B. I3, I4, I5), dann kann durch mehrmaliges DrУМcken der Taste zwischen ihnen gewechselt werden. Einige Buchstaben werden nur in bestimmten Spielvarianten benutzt. Zum Beispiel wird A nur in Trigon fУМr die Spielsteine A6 und A4 benutzt (auch bekannt als т€žHummerт€œ und т€žDreieckт€œ).

ZurУМck | Weiter

pentobi-7.2/src/pentobi/manual/de/system.html000066400000000000000000000020071227240712600213320ustar00rootroot00000000000000 Pentobi-Benutzerhandbuch

ZurУМck | Weiter

Systemminima

Minima: 512 MB RAM, 1 GHz CPU
Empfohlen fУМr Spielstufe 8: 2 GB RAM, dual-core 2 GHz CPU

Pentobi funktioniert auch auf Systemen, welche die Systemminima nicht erfУМllen, aber die hУЖchste Spielstufe kann auf diesen Systemen sehr langsam sein (wenn die CPU zu langsam ist) oder eine reduzierte SpielstУЄrke haben (wenn nicht genУМgend Arbeitsspeicher vorhanden ist).

ZurУМck | Weiter

pentobi-7.2/src/pentobi/manual/de/trigon_rules.html000066400000000000000000000053441227240712600225310ustar00rootroot00000000000000 Pentobi-Benutzerhandbuch

ZurУМck | Weiter

Trigon-Regeln

Trigon ist eine weitere Spielvariante. Die Regeln sind УЄhnlich wie in der Spielvariante Klassisch, aber es werden ein anders geformtes Brett und andere Spielsteine verwendet. Jede Farbe benutzt 22 Spielsteine, die wie die Polyiamonds bis zur GrУЖУŸe sechs geformt sind. (Ein Polyiamond ist eine Figur, die aus einer Anzahl von gleichseitigen Dreiecken besteht, die entlang der Kanten verbunden sind.)

Spielsteine fУМr Spielvariante Trigon

Die 22 Trigon-Spielsteine.

Das Spielbrett besteht ebenfalls aus Dreiecken und hat die Form eines Sechsecks mit jeweils neun Dreiecken pro Kante.

Spielbrett fУМr Spielvariante Trigon

Das Brett mit den Startfeldern
durch graue Punkte markiert.

Es gibt sechs Startfelder auf dem Brett, jedes ist vier Reihen von der Mitte einer Kante entfernt. Die Startfelder sind nicht farbig und die Spieler dУМrfen das Startfeld fУМr den ersten Spielstein einer Farbe frei wУЄhlen.

Beispielstellung fУМr Spielvariante Trigon

Eine Beispielstellung nach ein paar ZУМgen.

Trigon-Regeln fУМr zwei Spieler

Wie in der Spielvariante Klassisch kann Trigon mit zwei Spielern gespielt werden, indem ein Spieler Blau und Rot und der andere Gelb und GrУМn spielt.

Trigon-Regeln fУМr drei Spieler

Trigon kann mit drei Spielern gespielt werden, wobei die selben Regeln wie fУМr die Variante mit vier Spielern benutzt werden. Die Variante fУМr drei Spieler wird auf einem kleineren Spielbrett mit einer KantenlУЄnge von acht Dreiecken gespielt. Die sechs Startfelder sind drei Reihen von der Mitte einer Kante entfernt.

ZurУМck | Weiter

pentobi-7.2/src/pentobi/manual/de/user_interface.html000066400000000000000000000126261227240712600230140ustar00rootroot00000000000000 Pentobi-Benutzerhandbuch

ZurУМck | Weiter

Wie Sie Pentobi benutzen

Spielbrett

Pentobis Hauptfenster zeigt das Spielbrett auf der linken Seite. Auf den gespielten Spielsteinen auf dem Brett kУЖnnen sich Nummern befinden, die die Zugnummer angeben, zu der der Spielstein gespielt wurde. Ein Buchstabe nach der Zugnummer zeigt an, dass zu diesem Zug eine Variante existiert (siehe unten).

Spielsteine kУЖnnen gespielt werden, indem sie mit der Maus oder den Pfeiltasten an eine Position gebracht werden, die einem legalen Zug entspricht, und dann die linke Maustaste oder die Eingabetaste gedrУМckt wird.

Spielsteine und Punkte

Auf der rechten Seite werden die verbleibenden Spielsteine gezeigt. Уœber den verbleibenden Spielsteinen befinden sich eine Orientierungsauswahl, die den ausgewУЄhlten Spielstein zeigt und es dem Spieler erlaubt, seine Orientierung zu УЄndern. Wenn kein Spielstein ausgewУЄhlt ist und das Spiel noch nicht beendet ist, zeigt ein farbiger Punkt in der Orientierungsauswahl, welche Farbe am Zug ist.

Spielsteine kУЖnnen durch Klicken auf einen gezeigten verbleibenden Spielstein ausgewУЄhlt werden, durch Benutzen der SchaltflУЄchen mit dem Links/Rechts-Pfeil in der Orientierungsauswahl oder durch Benutzen von TastenkУМrzeln.

Unterhalb der Orientierungsauswahl befindet sich eine Punkteanzeige, die die gegenwУЄrtigen Punkte fУМr jede Farbe oder jeden Spieler zeigt. Die Punkte sind die Summe aus den Punkten auf dem Spielbrett und den Bonuspunkten. Punkte sind unterstrichen, wenn sie endgУМltig sind (weil die Farbe keine Spielsteine mehr spielen kann).

Gegen den Computer spielen

Das Spielbrett kann benutzt werden, um ZУМge einzugeben, die von Menschen gespielt werden, oder um Spiele gegen den Computer zu spielen. In Spielen gegen den Computer kann der Computer jede der Farben (oder mehrere) spielen.

Wenn Sie ein neues Spiel beginnen, ist voreingestellt, dass der Mensch die Farbe(n) des ersten Spielers spielt und der Computer alle anderen Farben. Um dies zu УЄndern, benutzen Sie Computer-Farben aus dem MenУМ Spiel oder der Werkzeugleiste und wУЄhlen Sie die Farben, die der Computer spielen soll.

Die Ausnahme ist, dass es voreingestellt ist, dass der Computer keine Farbe spielt, wenn er im letzten Spiel keine Farbe gespielt hat. Damit wird vermieden, dass der Computer unbeabsichtigt automatisch zu spielen beginnt, wenn der Benutzer das Spielbrett hauptsУЄchlich zum Eingeben von Zugsequenzen oder УЄhnliche Aufgaben benutzen will. Wenn Sie also das Spielbrett benutzen wollen ohne gegen den Computer zu spielen, brauchen Sie nur einmal die Farben des Computers im Dialogfenster Computer-Farben abschalten und diese Einstellung wird sich nicht УЄndern. Nach dem Laden eines Spiels ist ebenfalls voreingestellt, dass der Computer keine Farbe spielt.

Die Auswahl von Spielen aus dem MenУМ Computer oder der Werkzeugleiste lУЄsst den Computer immer einen Zug fУМr die gegenwУЄrtige Farbe spielen. Wenn der Computer diese Farbe bisher nicht gespielt hat, wird er auУŸerdem im weiteren Spielverlauf diese Farbe (und nur diese Farbe) spielen.

Zugvarianten und der Spielbaum

Wenn Sie ein Spiel spielen, wird Pentobi die Abfolge der ZУМge speichern und es ist jederzeit mУЖglich, zu einer frУМheren Brettstellung zurУМckzugehen und anders zu spielen. Wenn Sie das tun, wird die neue Zugfolgen als eine alternative Zugfolge (genannt Variante) gespeichert. Varianten kУЖnnen auch von Kommentatoren benutzt werden, um Kommentierungen zu existierenden Spielen hinzuzufУМgen. Varianten kУЖnnen in jeder Brettstellung existieren und ihrerseits Untervarianten besitzen. Das Spiel kann daher zu einem Spielbaum werden, in dem jeder Knoten eine Brettstellung reprУЄsentiert. Sie kУЖnnen im Spielbaum mit den MenУМpunkten des MenУМs Gehe zu oder der Werkzeugleiste navigieren.

Die Hauptvariante ist die Zugfolge, die in der Startstellung beginnt und immer den ersten Kindknoten in jeder Brettstellung wУЄhlt (z. B. indem Sie VorwУЄrts im MenУМ Gehe zu oder der Werkzeugleiste benutzen). Die Hauptvariante sollte das wirklich gespielte Spiel darstellen. Wenn Sie eine Nebenvariante zur Hauptvariante machen wollen, wУЄhlen Sie Zu Hauptvariante machen aus dem Bearbeiten-MenУМ.

ZurУМck | Weiter

pentobi-7.2/src/pentobi/manual/de/window_menu.html000066400000000000000000000274131227240712600223510ustar00rootroot00000000000000 Pentobi-Benutzerhandbuch

ZurУМck | Weiter

Das FenstermenУМ erklУЄrt

Spiel

Neu
Beginnt ein neues Spiel.
Neues gewertetes Spiel
Beginnt ein neues gewertetes Spiel gegen den Computer.
Spielvariante
WУЄhlt eine Spielvariante und beginnt ein neues Spiel dieser Spielvariante.
Computer-Farben
WУЄhlt aus, welche Farben vom Computer gespielt werden.
Spielinformation
У–ffnet ein Dialogfenster zum Anzeigen oder Bearbeiten zusУЄtzlicher Informationen УМber das Spiel, wie die Namen der Spieler oder das Datum, an dem das Spiel gespielt wurde.
Zug rУМckgУЄngig
Nimmt den zuletzt gespielten Zug zurУМck und entfernt ihn aus dem Spielbaum. Das ZurУМcknehmen eines Zugs ist nur mУЖglich, wenn er der letzte Zug der gegenwУЄrtigen Variante ist (d. h. ein Endknoten im Spielbaum; benutzen Sie Bearbeiten/Abschneiden zum Entfernen innerer Knoten aus dem Spielbaum).
Zug finden
Findet einen mУЖglichen legalen Zug fУМr die gegenwУЄrtige Farbe und zeigt ihn fУМr ein paar Sekunden auf dem Spielbrett. Das wiederholte AuswУЄhlen dieses MenУМpunkts zeigt alle mУЖglichen legalen ZУМge.
У–ffnen
LУЄdt ein gespeichertes Spiel. Die Brettstellung nach dem Laden ist die letzte Stellung in der Hauptvariante, sofern das Spiel nicht mit einer aufgebauten Brettstellung beginnt. Wenn das Spiel mit einer aufgebauten Brettstellung beginnt, ist die Stellung nach dem Laden stattdessen die Anfangsstellung. Dies vermeidet, dass LУЖsungen sofort angezeigt werden, wenn die Datei ein Blokus-Problem als aufgebaute Brettstellung enthУЄlt mit der LУЖsung in der Hauptvariante.
Zuletzt benutzte Dateien
LУЄdt ein kУМrzlich benutztes gespeichertes Spiel.
Speichern
Speichert das gegenwУЄrtige Spiel.
Speichern unter
Speichert das gegenwУЄrtige Spiel unter einem neuen Dateinamen.
Exportieren/Grafik
Speichert die gegenwУЄrtige Brettstellung als eine Grafikdatei. Mehrere Grafikdateiformate werden unterstУМtzt, das Dateiformat wird von der Dateiendung abgeleitet (z.B. т€ž.pngт€œ fУМr das PNG-Format).
Exportieren/ASCII-Art
Speichert die gegenwУЄrtige Brettstellung als Textdiagramm. Das Textdiagramm sollte mit einer Schriftart fester Breite betrachtet werden.
Beenden
Beendet Pentobi.

Gehe zu

Anfang
Geht zum Anfang des Spiels.
10 zurУМck
Geht zehn ZУМge in der gegenwУЄrtigen Variante zurУМck.
ZurУМck
Geht einen Zug in der gegenwУЄrtigen Variante zurУМck.
VorwУЄrts
Geht einen Zug in der gegenwУЄrtigen Variante vorwУЄrts. Wenn die gegenwУЄrtige Brettstellung mehrerer nachfolgende Varianten hat (d. h. der gegenwУЄrtige Knoten im Spielbaum mehrere Kindknoten hat), wird die erste Variante benutzt.
10 vorwУЄrts
Geht zehn ZУМge in der gegenwУЄrtigen Variante vorwУЄrts. Wie bei VorwУЄrts wird auch hier jeweils die erste Variante benutzt, wenn die Brettstellung mehrere nachfolgende Varianten hat.
Ende
Geht zum Ende der gegenwУЄrtigen Variante. Wie bei VorwУЄrts wird auch hier jeweils die erste Variante benutzt, wenn die Brettstellung mehrere nachfolgende Varianten hat.
NУЄchste Variante
Geht zur nУЄchsten Variante zum zuletzt gespielten Zug (d. h. zum nУЄchsten Geschwisterknoten des gegenwУЄrtigen Knotens im Spielbaum).
Vorherige Variante
Geht zur vorherigen Variante zum zuletzt gespielten Zug (d. h. zum vorherigen Geschwisterknoten des gegenwУЄrtigen Knotens im Spielbaum).
Gehe zu Zug
Geht zum Zug mit einer bestimmten Nummer in der gegenwУЄrtigen Variante.
ZurУМck zu Hauptvariante
Kehrt zum letzten Zug in der Hauptvariante zurУМck, der eine Variante hatte.
Anfang der Verzweigung
Kehrt zur letzten Brettstellung in der gegenwУЄrtigen Variante zurУМck, die einen alternativen Zug hatte.
NУЄchsten Kommentar finden
Geht zur nУЄchsten Brettstellung, die einen Kommentar besitzt. Wenn das Kommentarfeld nicht sichtbar ist, wird es sichtbar gemacht. Das wiederholte AuswУЄhlen dieses MenУМpunkts zeigt nacheinander alle Brettstellungen mit Kommentaren im Spielbaum.

Bearbeiten

Zugkommentierung
FУМgt ein wie in der Schachnotation benutztes Kommentierungssymbol (z.B. !!) zum gegenwУЄrtigen Zug hinzu. Kommentierungssymbole werden nach der Zugnummer auf dem Spielbrett angezeigt und sind nur sichtbar, wenn die Anzeige von Zugnummern angeschaltet ist.
Zu Hauptvariante machen
Macht die gegenwУЄrtige Variante zur Hauptvariante des Spiels. Dies ordnet die Knoten im Spielbaum so um, dass die gegenwУЄrtige Variante die Hauptvariante wird.
Variante nach oben schieben
У„ndert die Reihenfolge der Varianten so, dass die gegenwУЄrtige Brettstellung beim Durchlaufen der Varianten mit NУЄchste/Vorherige Variante frУМher erscheint.
Variante nach unten schieben
У„ndert die Reihenfolge der Varianten so, dass die gegenwУЄrtige Brettstellung beim Durchlaufen der Varianten mit NУЄchste/Vorherige Variante spУЄter erscheint.
Alle Varianten lУЖschen
LУЖscht alle Varianten auУŸer der Hauptvariante. Wenn sich die gegenwУЄrtige Brettstellung nicht in der Hauptvariante befindet, wird zu einer Brettstellung in der Hauptvariante gewechselt wie in ZurУМck zu Hauptvariante.
Abschneiden
Entfernt den Knoten mit der gegenwУЄrtigen Brettstellung zusammen mit dem auf ihn folgenden Teilbaum aus dem Spielbaum.
Kindknoten abschneiden
Entfernt alle Kindknoten des Knotens mit der gegenwУЄrtigen Brettstellung aus dem Spielbaum.
Nur Brettstellung behalten
LУЖscht alle ZУМge und behУЄlt nur die gegenwУЄrtige Brettstellung als feste Stellung. Dies kann zur Erzeugung von Dateien benutzt werden, die mit einer festgelegten Brettstellung beginnen.
Nur Teilbaum behalten
Wie Nur Brettstellung behalten, aber die ZУМge nach der gegenwУЄrtigen Brettstellung werden nicht gelУЖscht.
Stellungsaufbau
Aktiviert oder deaktiviert den Stellungsaufbau-Modus. Im Stellungsaufbau-Modus kУЖnnen Spielsteine УМberall auf dem Brett abgelegt werden, auch unter Verletzung der Spielregeln. Existierende Spielsteine kУЖnnen durch Anklicken vom Brett entfernt werden. Die gegenwУЄrtig selektierte Farbe legt auch die Farbe fest, die nach Beenden des Stellungsaufbaus am Zug ist. Sie kann mit NУЄchste Farbe selektieren oder durch Klicken auf die Orientierungsauswahl wУЄhrend kein Spielstein ausgewУЄhlt ist geУЄndert werden. Der Stellungsaufbau-Modus kann nur benutzt werden, wenn noch keine ZУМge gespielt wurden.
NУЄchste Farbe selektieren
Selektiert die nУЄchste Farbe zum AuswУЄhlen eines Spielsteins. Dies kann zum Beispiel benutzt werden, um Partien einzugeben, bei denen ZУМge einer Farbe УМbersprungen wurden, da die Farbe aufgrund einer BedenkzeitУМberschreitung vom Weiterspielen ausgeschlossen wurde.

Ansicht

Werkzeugleiste
Zeigt oder verbirgt die Werkzeugleiste.
Werkzeugleistentext
Konfiguriert das Aussehen der Werkzeugleiste.
Kommentar
Zeigt oder verbirgt ein Textfeld zum Anzeigen oder Bearbeiten von Kommentaren zur gegenwУЄrtigen Brettstellung.
Zugnummern
У„ndert die Anzeigeart von Zugnummern auf dem Spielbrett. Die Optionen sind nur die Nummer des letzten Zugs zu zeigen, die Nummern aller ZУМge oder gar keine Nummern.
Koordinaten
Zeigt die Koordinaten fУМr die Felder auf dem Spielbrett an den RУЄndern des Spielbretts. Die Konvention fУМr die Koordinaten ist die selbe wie im von Pentobi benutzten Blokus-SGF-Dateiformat.
Varianten zeigen
FУМgt einen Buchstaben an die Zugnummer auf dem Spielbrett an, wenn der Zug eine Variante besitzt.
Vollbild
Schaltet das Hauptfenster in den Vollbildmodus oder verlУЄsst den Vollbildmodus. Es ist systemabhУЄngig, ob das FenstermenУМ im Vollbildmodus angezeigt wird. Um den Vollbildmodus ohne das Benutzen des FenstermenУМs zu verlassen, drУМcken Sie die F11-Taste.

Computer

Spielen
LУЄsst den Computer einen Zug fУМr die gegenwУЄrtige Farbe spielen. Dies kann zum У„ndern der Computer-Farbe benutzt werden oder um nach dem Navigieren im Spielbaum mit dem Spielen fortzufahren. Wenn der Computer die gegenwУЄrtige Farbe nicht bereits spielte, wird er diese Farbe (und nur diese) im weiteren Spielverlauf spielen.
Einzelnen Zug spielen
LУЄsst den Computer einen einzelnen Zug fУМr die gegenwУЄrtige Farbe spielen ohne die vom Computer gespielten Farben zu УЄndern.
Stopp
Bricht die gegenwУЄrtige Zuggenerierung ab. Sie kУЖnnen den Computer weiterspielen lassen, indem Sie Spielen auswУЄhlen.
Spielstufe
У„ndert die SpielstУЄrke des Computers. HУЖhere Spielstufen sind stУЄrker, kУЖnnen aber die Bedenkzeit des Computers beim Spielen von ZУМgen auf langsamen Computern sehr verlУЄngern.

Extras

Ihre Wertung
Zeigt ein Dialogfenster mit der Wertung des Benutzers in der gegenwУЄrtigen Spielvariante.
Spiel analysieren
FУМhrt eine Spielanalyse durch.

Hilfe

Inhalt
Zeigt ein Fenster mit dem Pentobi-Benutzerhandbuch.
Info
Zeigt eine Dialogfenster mit Informationen УМber diese Version von Pentobi.

ZurУМck | Weiter

pentobi-7.2/src/pentobi/manual/en/000077500000000000000000000000001227240712600171335ustar00rootroot00000000000000pentobi-7.2/src/pentobi/manual/en/analysis.png000066400000000000000000000347421227240712600214760ustar00rootroot00000000000000‰PNG  IHDR@~U—ШУbKGDџџџ НЇ“ pHYs  šœtIMEн-;5„ЊЅ IDATxкэНwИ\Wuџ§Yћœ3хЮ-’ЎЄ+Щ*–mЙЫF€`cSŒiЖ!Ф&!B€^B ф $o oј‘@ ?$HЁ8Жу‚{A.2юЖ,YВ­.н6э”Но?ЮЬhюhъНъ:[Я}4sffя}ОГfэЕзњюЕфч?џљK–,љзt:НRUщБ) uџ7^ЃюzГЯЕЛ^?iёZЋБ;зюyЛq;aA—яM№ы?еИ‘=?Ѓ* *Л_L№KфЏ-~"BЙ\~hуЦПы.]Кє'žxтЉг0iIKZвЅVеsЇЉъ7\ЯѓNUU УPšЌАIKкжbp]Їv)Š"D ЦQb\wї*Њ– Œp\#B†TwЦѕp$–Щ q=lЉтЙ.;ЗldGЩaХВEXk‰Хд лŸ{š­х,'{ˆТЋJ"ЫIkбDUq]W=Я[щFQ„ЕЖ"Xв­ЙД#xљL{†uЎцЗо‹яЭст7НŽ‘СхrЙ,ЅBЋ‚1‚qRžGP.Q ,}Ba|п§яkxЩЋ.хEЧЮЁTЬS”ўў>Ыlexю\НћF~Й1ЭЧя8EŠ!$$ХумЩъёaN>і(ЦFGё2Y\#LУ“Д#f зšЮs+Dу– “ДЖжŸ›ЪА§™ћљлќWN;ыхЬŸ7—ДчВщЩћјЮ pёХВцWѓјж вЎадJ~чвsЙч—зёр†oКјRNZ:Œу8—]rЁЬ›•C§ ‘ПфїлђГUcЏjb­M–…Єuмџ–Ы!'Ÿq6ўЯОФЎђЙїК[Й№]—ёиƒO0<>ЦFєАќЈy„~РиŽI7УQG-dСмЃ9iйИя–ŸГ5ЬqіŠc)lЛƒ;nЛ•д№R^ёВsиЕѓvЙћVжEŒ7 'ђёехј9ўѕgыјдKЯ`лšлYѓдŒf–`МVœt<б­7ВюЙэЌX4Е–d?“Д–S$vћ=єаCzмqЧ†aFя†ѕыжr§ў Ю‰|ўЯпЯЏnО‘мЂуtAП‘Rм*›KiЮZЕR2ž„Q”Ш_ђћm‰Ÿу8Ќ[Зy№Сk А9KLАuПЊЈˆІR)qD#јОЏЦёФs?еSнbˆ†‘8•ˆoaˆыЅqM6Ў‹ЈЊ‚H\ЧЉD=аq<&З?Ы7ОѕCЮЛєМш˜љX5 ‘FVEUŒxŽP.ћY+•€^"Щяw>UЧqXП~=ВfЭ]О|9Qћ30Р=№ЊњQDЄJHFDъƒiBЄЌhќ†ŠCQбъуX(ЋSДBcЉіЃЊ8^šЁхТ$љRPЁТˆŠ 1O:іaз1љK~Пm-РѕызуVїТ‰0iгv жБš1 І\ЋœъиуqУ{ћ‰‚2;w–ыЛЁІT[•ДЄЕёЊ*n%œNв’–Д#nбvЬХi+д.їкOЗ§&4„ПППiсч6š…ѕкБогЬ|lѕўњcH>ЏŽ-ЕъЏёZ§\ћoМ—nцзиWГз;­. ~ ~ ~~ѕЭдЯІGЊ‹ИMy<ўіp еѕз8†v;FУœК™O§'ј%ј%јšј™™ьŸ[iр§Н?ауї:ПППƒ#`eІNН кhўіrSѕ_`ГЧ]Ќ mчнЎŸ™€пhŠwšG‚_‚_‚пСƒ_K`Л‰V3($-iIKкСмDcЬЪН™?А+ЈЊcшяяOаMZв’vP7пїё}П­•йLJЃ–Ќ‘]—|>ЯM7нчУAЧ8aI]$'Ё!LcбšСН%ј%ј%јU\ErьБЧВbХ Ђ(ъјйЮ Њc K—-ЧѓЖї\ўzzb''-ВќђЖЙлПwщqКsч™Еp s‡4 ƒj\Кз гкBBЛ=KиmПэ^пŸg1[/$јuй,БІqЄ2œ’vв`PZёеЇzъXL:§YPЛћh]кЄ!оиHhCBPФCŒ‰;ЖЁ QZтŸШпо“ПъѕЪжЗg pPU‚Ш2oNŽЃцїёэыžтeG[юн0Щ,q(mпРSk7ёо?џSюќі7йАy3‹дОєvШњpwГ›š‰мlЬfьѓnУщиѕэюГЖ"в’Ъа =сHЦOФAmЛѓЋh4†3ћ}ˆ;Т “уоќН\ќєХфKyn›џuЬ‘зk' 7}-м‹d_‚,Лq†Шš4W>w%woП›oЙптјЬё\4tc€нёD[>ЦУ‘ЮьїХs™ј!с Ж€†[q|4D§Ч7}ќитxKЏu€(‘П(­Zg"Д‚ rѓ—2”Гlлђ'Офl}ьvњŸNЮ…Оў>Š! ’J{D‡]ц` bЪI(ізм-Ж"Šё?g;q бжЯaw|;њuprŒFЃфЫyА№Dщ‰ЪіW@#PL_ќ?џˆцКsСу™x;\лЅpfDъwR ˜8C з‘ 8ƒn[`"ћvюЭh0ЭљР>№CCCДˆтжОmД—х„“ŽaоЂх,рш'1+з'G-YЬЌСЩdВ ЭšMЦsЅ.O‘TWš†GЏе_oцЙn|­њЄўb§{ЧkCъAi|оlмЦ~›­žMциђЏq*ŽtќDЋ–~гХтO< Щ‚џvђZгџz1}ЏРкqцy#’ssЯ№ЙEŸ“%Љ%„ZFL6о’:§8sўHLњxP‹хмўsJ ёЧ#,чіŸKIK >’9qчŠx=f№-"„ %Ф[ЉebВЇcfПODРGœй"™SС[„3ќФ™„{ќЎљл7ђз Ѕ•N3Ц066'D]КtiЅЖkћdЉtз~рy)ЂаWq1„Оq]АЖbŠVsa61eЕБгžўнящ&ICекЊИSƒaЪћšŒні z/ЋfЛїWчбТч1хЕУ?Gq5м3т:uйЅЯєqOсўzг_Га[ШЇ|š…оBBDCЕ“7vxbњQ 1т0ш *‚ј‘OСvЯЭєI)ж­Лž2)њLЁ†фmОš Uj~F,иIД2_І?Жэ$Њ!T^њ@<аb-а’ШпО—Пz`'—у8lиАЁ;"tЕЃ( ЈК}П+VжЂ_Q68)ЛяЛйtѓХЖ’_~лLцй­КЭ—иЕщ~ИрWU~Y“ЅlЫє™ОЊВiŠŸХ’vвќ§цПчч[~РЪьJЎ\p%cС(*.fш• ШЊqvЋ–сNХ`0ВлгЃ6v‚ИАњюыОѕ)кbэ§ЛПуЂЕэ№юыЂб=ЎЧcLVЂгNK<љл7ђ7`GввTswŠyЊьvЊъ”TщНэхЕaЅhšM‚йš8QЕƒџ`ОЋужпC‡ѓŽZYСZ~1mЮiъдЗ8ќ%дjЎцъЪМ7№sХХˆсЊmWqбгёŸ;џ“ДЄЋ jќ$&ЂАЊoЄ!›ЫrLњд*Š (A4ІБђл=G\qеˆajв_AФ­љўЊ"‚+Ў S~Xcш тдc[Й?SЛ>œzYIфo?џ~щ2Œ;]Эйы вjеj\1f=jеoЇеАзћэдяОXї5~е~ћLi'Mhk[СНВ‹}ІЕхЕ|шйAПЪџŠзНž~гOб+ Ј‰еАЦжлx4Ю•#WrbцDцЙѓxyџЫF+JГ‘F&‰ќЂђЗЏёыЦьДJ4}_ЇŒѕзЏ7 wяєќ­VЪz ЖўRc_Vй^Vрƒ?!c2Ќ.Ќц}ыпЧЕcзтрLй>ЮПˆˆœЩБ,Г NЩž‚'^-Ъ‹d@В }ˆАj„п˜§Мrр•”m™ЊU—Шпс#ћПНnжOvoLф@Дƒ…†А/№Ћіkеж|aэVMO%М›к4uўCЮзŽ]ЫižІЇ>z*?§CЮ†ЈˆЇ`Q;ЉHК­C=~юЋЄVЈ3їJLпЋˆi*vŸт—Шпс‹ћВ(вtVЦ#3э|3uЪ6;JГ7VЙnћэfЬН„рєcw}“hл_mљ3ьјС yѕРЋyу‚7ВrЮJ>:џЃ6РH е;і_„[>Ž-м’ЊѕЏZ9q[wX>~lгЇzЊ0OŒcИeтТRˆ–”›ЦoBСтЁvŒhЧџ!кєQДp7b2SЌІїЏEАEа|L@оЧј%ђwфрзѕИL’ЭЄЗШдоЮЦQso˜>tХw–…’-qlњXОЙь›фmžEЉEфЃ<ЦBKk7 ТэhёAЬб7UЖУQгяED@\Дєkpч ю"АEЪa™wЯy7wхя"вˆпžћл”У2Ў;„ŽпBДэ/у6›ћ1PjЛ=‹_‹і~‰ќ%й`ЬA0‡щуч@4Ž™§^Ф’Aњ/;†Cбщwњ™хЮЊ uХ‹ЄЮС}РёЋ/aиСqНwёыQўќ/ќкѕ@,РNaєД#ф}хяoхŠlсжѕE2ž™6хЂй˜ѕMсzUХ1BЦ5<7тСsк“=gВяЎPfтКВuЭ NUхŠK A\œ„ЦБЗф/Ся№ЦЏkx Š"ѕe ПzОФg~А•Ÿм2ЪЧ~КƒŒ3Г gЭђЮЙŽаŸŠџRNѓиŽРgoкЩљ_+КМЏЄ]ЏхЂ2гЂ4БђK–@М)„cЋ–В-5$HŠњ$E‘ќі’l?1ˆЌ6LэžеиЪвfЪ Ј*XџUћŠ`VЦа?пƒA—чІА K&ЛGЦЊX­vED#хлN№wЗВГ`qM›BAŽP •ПНy›жљкЭЛи<щMy•z&О‡|сSП`pˆЖžр™Г‰ЖўПФЧШ<Ц–а8ќцЙяUŒށЌc(…:Х!"ЩA QkV#”CхћПždИЯсŒХi"ЋЛ+ИцњуCЌХИXЯXЩђтEiЎО|!mіyћЉ9Šеq\ |єЩG‘%G#ž‡†! d]!“u ŒћhEЪTUfѕ9ќћ§ќЮw6C Ќпrе[чБc2Њ)ТњЯ„VIЛТ/ф{k&Й№јA†нЇ(lОыžŽЩф!єбРп#›JЯцКxhД‹hчз кAДыk˜9яGмЃТНцDяД…i\aG‚пЁ€_‡8ХЬh0)G-<Б­ЬIѓRhEAˆу€ЊDПОГфhdpHЂВЯЌДс3Зэ”ПКz;є;\ћž\ДЂёВE<шў{ \ТyЩ9HХzє#‘W,ЫpС1YђО%ˆTФѓ ёџљџHtЫѕИ\Dъƒё<вђиЖ@ўeѕЮZœцв“s8FbEk$Š­ГРУЬп‚Џ)уe[u“ЦїžJуA€†!Až#ђХ7 sХЊAN]0HvыG™мu5’Z„wєЕшC2'ƒ,Z,Z.7ЕЛІ!"&'ЮаeDу?Р \,тЬЊедhќђ›DBуHh0G,~ћ,LЦv">tэvЎЊРЋјт†q]‡Шё№ПќЗ„?§цЄ•Є?љWшМ…рмї|| ;-kw†Oo€№ЦŸтўЯбР'ѕсНу=06 ŽУЄoQ#P\%ќŸoЃЃЛvnУ{Я`цЬ%ЃЙv;З­žрŸnўрbЮ?.Ыh>ЧAŸ}R)dd!јeЦ –ЗžвЯіЗЭчљёМlˆBбтРqб‰qtћжX‘›x{DŠk„W.ЯR …rф"fЩ |я_ Д™—#§щПСœДђ“W­ж+V€ˆ‹™їiЬаЛС[ 8двUuс4NВ™$й`Ždќfтl™ &у [&#ЎОgŒвц2_ЛgŒrЄИЎUТЎUћь3„7ўЛmЋ:™4хВхгЏœЅ/ё—ПzŸдЧdС"Ўƒn\}ъ1t§ZьгO ŽйЅ„:хJЈфњqпvЙšуNФ{лхHЎ_ ƒъВW)юS™ld‘ўТыџ—Т{пJёїƒш{Uњњ‰TH Ÿ|х,ОєІЙ3лЃ)’эSнБв'џтoНQ§ў‚’J+&Ю–bUЩћж†И#…3їcИKЎ"К+vэ§Dїм†}юYзщ*GЕЂšС‰keд–pеPХє#й3gPUУ^|!I6“$L‚_й`КВ‹Вhахђѓfsэy~їЅƒd\! "p ЉїќСOў gе™˜ХK‘b‘B`yЩт ЗџўQ 0щ[ТH‘|чќ ё6ЎC МKо‰Š`Lѓ•,з%§сObпqfСЂXх%ўёsљ—‘g-ЩАjQš‰BˆЬqˆюП нќ<ЊŠ}є!œ—Ÿ)Ц~ЩАВѓЊz3•ТnнLtї­`-б-зУ‡?YWwЗ ИЄŽЦ™ї№ ЉwэТЗeЬВcqN9-–Zј2ЊЉ№тUЫЊ%kВLD<<У ™№еЧЊ­фЭѓуП&ЋjГ­pЗє†^Vєn}0гё+uъw_X0 ~G6~-ЧZГf.]К”(ŠД™ЯЁкRŽ!Pa§Ўs=­Њ#Иžъ–Dц ƒыЉA•Р‰жхыŒSВ[$QЂPд*’JЁхb•юЁu~ƒњ•ODDq=! т%Њ2зŒk4›5B Œ•­ЊЕ"й,ізPўЪч‘!Rњ5K– APПЪэіЕЄвЊ“у|§Ы„ПКCSПљЛИoК |_тЗю>’6E2tЧvшЫ!щДт—QDRސЉœf)†JM§ЂrNŽ ў>ДсCККАZЎЙR?>ђqЉдэак§жљgО|mцj…_ЭИlx­:Nн§i‹я7Э№Ћ›OЛq;l§šy;lƒЊ$зfDоП# ПjQTcЄHГЙ8ŽУ† КW€ dѕ]GŸџї(6ъ'//ХП›yH—shлG`aQП№Руe№]@ЙџёЭ”OzŽRйчЉбљЬ &Ш% #|Њј)ž 6Вjь4ЙfлюнюrС‚Ђ 3F`e?тзsПћП™ИrЌќ%ј,јYkeіьй wЄuБжbŒaЮœ9g QЕЈЦЛ[^хuф†оMjрeˆŽюПi(`”OОжeжМЧЩёўуюbtУч # Ў?љж:‚]ыpоќ.–ўцћY­рЁэМѓћл)ŒОЗHЙсŠ98"6YЭ“–Д^wƒщtКыIWй`TзuYА`AЛLвсq/> К№д]W“#ШќСж1$u ГМ'•C5еЭ<іŠF ВШX!ю КыЦж?€8CЬн1Iљ!ŸШсЇ!;2—О Р0Y-„y!_fRћuddИЪЁю'ќ:ŽБ_№›%s€х/Ся`СODдї}Ђ(ЊКл~жэEГ•(jcˆКrїkЛяПйч:…ш_Ÿњй1Єямe/\,–ШЯ#•Œ*Э(эПЦЄМh>ЏfDTU%ЦIА%Bїм˜дlŠшYяТМ§ијrщЛJe&Ъ> sТ7п>ыž(œЯOVcIDAT№ž@P№™RE­оН o…_ЇяГќК9’дПfsm7Noђ—рw8уWН^ ‚tЕgя6 ,"8Žг8pзat‘Tœт @}U РˆЁЯєсрPА 4КмbSVТvaєš&F,=а*я%gr"Ћ[NYВ›г1Y4[FrKаќШч‘y#hЙЄ•X•ЄDŒ+jоЗR #ЁС$јэ3Ь ђ: 9Р jЋ4tЌZR&kb3Ѕˆxhјбцn§34кQЩ|УЃХGЙmђ6|ѕ1‹­цTЋўuœO5гŒнЃZ•Ђxт‘’T­’5СˆP be%‚CJR\3v ПНўЗљхФ/IK#ІэиЭV8ƒкт "оZCВ}ШШ‚8в\ сЈ2с+уK!˜yU­§щoIцрwАЮ}ERD\дцЁ|ЄGЬh"d%ЫF#sм9dM–’Ф8ГАлПEДыk€ ю|œЙЃ_CюœМ“7<§ђ~žП\њ—ќйТ?cWИЋЮKЃ&Іv7й$ЌZњLы§ѕXЕ“>†‚-`Иo3'ў?* ъгяєГбпШ;žyA9рgc?уёSЇпєSЂДЧx­JN1ЫыNuT й{˜њ=|qI6“™ ~‚пс‡_'зAГ~Lƒ™*Еx€mћсѓWmљ ЊeQ“ІЯєён]п•7>§Fў`УА)и$9“‹йюQ Hq‚‚k\.>LО‡ЎП^bR‹EФALŸˆЄDL_|8rї9GQеъsi•QUщwњЙmђ6ЙtэЅ\МіbnПg€Јb-FЃп%мђбрYDR(*Ž8’–Д`!mвb0S|е1ъ•lжd%-iЩJ–”ЉГ4о_џW}}ц‹њЬ Э>пцš4y.ѕ§іŠ_=• ёЕКџч,us—vsЏ{.ѓO№K№ыПЊЋлвяхЂHтЂЖˆП 6a'Ў;xxтёљЭŸчЩб'љўІяѓЋќЏH9}`Чq†оŽЛј;И‹ПƒщПЕcфУ< ]ФХ#sќрёќщ‚?Х|ŒdP-mџ‚ѕbЧО‹HzJvфjЂйэеЏ žуqѕше<Оѓqž}šяяњ>ЎуI?Zz€hѓGАлўšhћчСd)и<Ую0?>іЧ|bй'јяcў›ŒЩPжrгTнV-9“у–Щ[8уё3xѓк7ГОМžŒЩД]“Ђ>IQŸПCБ(’ˆЩс ;іп˜С‹gŽЊ–%Rс­ГоЊŸ/^ŽOЯŠЬ UЋЂjЩb.‰ћАyаŸоОwЬї5$#-и‚ˆ3 -=AД§ Š-EЛФ ^ІŠ#JDF2šq2„6”ЩhВmQ•0 y§аыѕšYзHЈ!—ЬО„(ŠpФФФU$ЮПoеjHШљчsСРЂЈNкЩ–Aˆˆ”“тK[ОЄя|˜‡хaљпџхOў e[nщØiQšЦyTС$E}’ЂH ~Дыf>@ A Ю№afП1ЙX)j@оF|jСЇxѕрЋYъ-eIj cбXхK ;>0…@<‰­Ч’–ЊQbp†1ЙѓБ…{0§ЏqH™4”с'Ѓ?смўs9'w‹вЃЂHЭ4я(ЊRв \ ]ˆЕ– ;!­Р:Є‹вЈ‚cDFСƒЋ‘ЅЫ!л‡X‹ZЫ[NЮqЫSEЮv9cq†РЗД(Mну*ЇЕђЉё­ьwќ’ЂHG~ћЌ(вL5}З}u:[XН>MEƒ#NKSї.Jc-DщџчГ8чПчј“уrХу(П§тЮ\œa0m8zЖKоЗД(U%эY/л„B`+#нџјэљл—јtђwˆтзkыгмМм/aєъŠbФЈСTW˜))ыыПќC™†@Ј,Z"оe—ƒ XˆSЎЮY–СZeЂ<ѕ> СˆH)TОїаЄnЫGќцщЬЩ эюŒ6 уа‘П„Г—-Р„†аћ ЌЊqф7NЯПGбіБтю T/$д}пPж№­ћ&xпЗ7C`Y?ђпw$ЁqŠђw8сзЕlS:ЏЉжoіУkfЗѓ%і’Ћ—шP3гКqŽн‚иЩ/к-fНрWУЄEzŸƒ ?b'ПРљВNYw~‰ќ%јѕЌЇ3鄆phтWЗXС1ˆqZcЃu5ўЊФlgя/Xо~j?у—ЭgгDФ‡Я"_Дq^ФУПDўnќ}L—§&4„н JЊEˆы•UGЙфњ‘t -—бbБхНT<ёџщtЌƒ ІЉ’ѕ„ПbAЄCХt˜{BуHh0 †„†АW№ЋЊМT:žMHМ}ŽѓЋКF0ЁElХzУq%Z}'б­7тœ!Ющ/0ЌmЛ]VE"ЋЕŠ’Щ`7ЌGвidd!Z,ŠˆDJd‘ЏД4ѓ”ј%ђwHу7Ь Ђ&й8_;ай8bхЁЅšŸЌ•(UЯRn|vИJU!•F''(џхŸрџЫ?Pўы?E‹№т„Ж9O№ŒачUЮG’ы'МёЇ”?і~JђAЂ‡юGњrЛ…ЖњзФG“d39МхяPЬSЏЋВ;§ЗN}iJЪќ^њвMЏu}h‹ЙjNTэeўФgЕёšф)kьC[МжnuлkјI&ЋбSњнЗQќ­7­ОK%лЇiЃф}е?џХN.њж&n~ІЈYЯьЦЯ8дў*-э Ћ_(ѓОoуыЋ'4Др‰"žKtћMDkVнq3іЁћ :,№Kфя№СЏ]џ3іNG;'4„}OCL{з­Dkюcoњ)юyЏІOѓќbm‰ПЛf;XхуeЫCД„RЙŒєўѓП#К§fœWО6NйјЄ=сƒWoу‡ѓ|;ыpмАЧЋЭ0Vіq_w1ійg sц+ Xjїыhюџi!™ уа•ПƒПЎ`Bƒщ`eФ4-•1gŸ‡ГъLДXРНр"Дь†љ§™aвŽ€гЄтЯZ‹ZgеYИgŸ‹–ЪhЉW!Р0;у€#вБАPР9ы\вЫŽAR)dЮ<ДTм#рRП‹1Ю€1P.з”_Ъ2ЎPŽ”rЈ]я:љ;јфя`ЦoЏ[€эР:TксFCаRчЄSЩ|х;`-2k6Z,0&Тщ RміўEЌнrС1Yђ~дˆ§~ЅbвЪЁrеЅѓјо1Y^zTšU‹вŒ+|DПŒYД$ЖюJEгrЇЇ3иЧ&zњ мГ^Я+Š в~Єl ™ŸsH91С;‘ПCSў6ќКЁСL+`ƒП БјА6ьpЄ•aасzЛ#-эŽиH“Ui|bL|е?ящ(MCа$bеў;м{јею3“пS.UЃЙbDД/%т”BеŠЕеtьъмГžС3`A‹Jd›yj‹_Ў_эгOHщЃяХ>Лїѕ—љыЏр„>–O]П“МsŒЫWѕы—о4—”#тGПDўmќЊЏMЗ,f—E‘HŠвЄEijU,:ЕЖіZUЩћЪxYёЃнEcšсWmЅP™є•М+Пfл“Nјс8ЂљItѓs Лa=TЂв“ОђЗЂc!џ~ћ˜lšˆ$уšЄ(RRiкјБЯ‹"M3вM_ aпсW5џїЌ]мžфjыЊпM Пќ$ц˜Є>ўYМзП…єT CKJј3!eИјЅƒŒє;”CЛO/ћ ќљ;4№ыY5n™JXœђƒЉO‰_ЙІ­вZŒ)Ечнl>ћ;%љсŽŸЊЊ8ސJЧз‹-TР1Бrk>bnЮQ„ЖвO:ƒ>тКqА& HќљыПщЄФпo`ЋUЋйЪ4SэоnЅœЩ*зmПћb>”ёг(‚r9ідд[bŸ",rq…ZDRiьЃk№џіЯўу_бBёМ#ПDўі˜а`Т>ХЏЖѕЖиhK)Aд€m&‹џѕ/^ї#Шd0ЧЌРyѕE0—0ЕšЬšШ_"{WNgВ !СoІŸŸ‚ХЫy#ШьaŒCбЊ8FшOХGњђ~еŠLф/‘ПЮVb’ І I6Žƒ ?ŠRПїQœNХ,=sЪ‹а‰1#ф}Ыwзp \|bŽŒ+5KА…вЩСžШ_’ ІЅЙ-"ЈЕh%хR’cЪ8I6“Н€ŸH%щBІївwBBЙD)ГО|зŸўЏ-``т7№ЧЏ˜Хh!ЊN0F№ЫБпБBЪTјŒ~Уi1&NEё˜ ZйbЇbХ*СnNЂˆž‰љу~УuзP!vгL!'ђwШgƒipH&4„„†А/№Ћ§јЂЋEŒ+Њ†э…ˆJŽ}v,˜JўCЯC_иˆ}јA кЌАž љЩcЦЫ1џА6—T‚82-ЮдDХРb-xfїЩњДџZШT2ы5хWŽты3љŽљ;ГС8ЎGi|”‡з<ˆяІa—С!rй4"ЂЙAВ™Ў—вўўщ”ЕЛps$й8’l&ѕsкfпд2N{&‹9gˆ^8‡\4ЬяŸ9Шd!Bњr=ў0ХП‡тяПS§я}r§є{шІ‰ˆ7ќћ&оі/ЯsХїЗ žk •&МўjŠxў7П‚жѕД?eИњё<Ч}aЏћж&}v4дŒ+d\б'ЖМўп6qСз_аћ_(kŸgHЛЂЛŠ–ї§xЋўi#?~,O.eя1‘ПC9Œу8”ѓyž}цi–Нфl$bчіИсКеЬY~ '-Ч}wоBjx њ…'жНР‚ХЫ8ўИ%DAXINœdуHВ™L?!Œ”Й9‡Џ\<€Мo "ГYo|ћјУ ‚}рp?ŒРhЩђфГ%([n]_"ŒРK;†_§{ЂgŸС>Мїх ЇЌТ%Я—юcчѓeю|ЎЬ/NычУЏОѓр.n[=џ€хГ]ўттЙМш”џєцЙЄ\! -8.цХg‚ZЬЉ/BfЯAТ -—šƒœУШВ g-Nу— ,g/IУlЮ[ž@ #хИa‘Х0ТE'dљ;хЏЅВэ&B*“aЧ†uмtѓMЌzУлШnцћюц˜зОSз]џ?Вќeчiaэ}ђРѓ–‹.8GяНѕє-вwН§ ”''›йбћќ0u‡ёк=ящ0z3М—oEлмУ=Œ~HсЇЊЄв‚ \Vщ-UЯˆxL”-C‡B`QEqЁTТЎ}9j 2kЂPqАq,д\Jюs№ЃИўtЪˆ<Н3 ДЊ'ЬMUO­Јчˆlšй2щ)ѓSb’к$ђЗхЏ>иТд(5…Z= ве8 #rCC,^4ТїџŠЙГ8уќWqїMW3ы„—АbбV?щАтјcIKDf`˜Ѓ—/Ї/•"=8Шg;%эˆh•DxnœП8‘lЄєЇ+Љ UahFFтЯ„a\ф Сuр”YqŠЏ(T2•Ÿ˜c`еЌxгЄ‘Ѕrl#pќp<j Т–щ“ЖZЉTЂ\.wEƒЉЗ›ВЙUзuЩч <№рƒ8%Š”TЪSB)ћЉLБ!AhIЇSDa ХRYZ˜ЉнЌjнЎ|гљЬtњо­н<ІћZ‚_‚п‰ŸЊb­хшЃцИуŽУV {5S|=Y€ЊŠуцЭŽїсHЅcŒH>Žq aюЁЮЇUЉ]Z^mtАv›ЎЇй ™ГЙ‡шLo&4„ПП#ПЪCk-ОяуКnэ˜ZЕуPšиШ/~ysцЭСeУЏяgхоLЖЯЃTіqН‚Ђ(QdёМЈEŒ! oЗЊ?РKWЊ *НћКŽиvЁъщЌP{лoгЪч20zГЈS75RќќŽhќТ0$ CЂ(jА/exфž{Ш-;™sV‡_,АљЩј“мљЋе<Дv'ОшL†йЩ/я~„зМщdз3\{гнШР"Ўј‹ЪИaДЇ Я‰яЅAšщhњNЋ[ЋЯю\a3нЎїт„ю&Ф>B‚_‚пƒŸЦGjЋJАй_Й\f`pЯup]ƒ›ђиёьzђšс-я΂ↇ)z},9j!ўјNж<№ѓO\Хт~‡|ЙDаЂп Ажvф&4G‚_‚пО С(*ЊZЕўЄЮŒ?рœpкJnИч6=œbўЂЃ Ыж=П…Д„ђѓЋL63ФЩsц№ј]7qO9ХIG/gS>`сбЫ%ы‚ ФZ;%XE‘XkБжтЇ­ьЩЌOh #С/СЏW АЊŒ}€Њ!az˜SŽ[ТЎЩ)N8}%+ЄиКm'ѓŽZŠц9§œз1<В>}_@RYдZЌ(жю™В?Š"ldСЃ­Lh$4ŽППНŸˆˆЕVUUJЅQЉкZ)OUTRžЧНїнЯЎб12щўКuœsійЬФB]КdБA‰‡‘‘Ђ H aёвЅЈД49!ХЉ8 "E‘њО/A5йщA:љ5BBуH№K№kеЌЕ8ЎУмЙs) ”'Ыˆ™:fЁXрЄŽЇЈUƒ€­лЗUh/ZSЏ“•|ЄФĘvЊ?“ЭNЇЩЄ3XЕэ`BCш^№G‚_‚_gќЌЕЄSi†‡‡™=kvЌќКйuO'Ÿ4{кЯеЌі‚tV€œ !Ёq$ј%јMЧ 4bp<ч€YЫVmлЃpГ4ДZйіVўЎnУє2›DЏsM№K№K№л}mг:н‡kŒСqœi9O[ЋНfzhgєЪ^cЏd“иЫ{‚_‚_‚пТЯqŒ1И;vьЈ™ЈIKZв’v$4c ;vьрџQ2XЭЏG-ЄIENDЎB`‚pentobi-7.2/src/pentobi/manual/en/become_stronger.html000066400000000000000000000074751227240712600232130ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

Become a Stronger Player

Pentobi has functionality that can help you to become a stronger Blokus player.

Game Analysis

A game can be analyzed by selecting Analyze Game from the Tools menu. This will make the computer player evaluate each position in the main variation. The result is displayed in a window with a diagram of colored dots.

Game analysis window

A game analysis of a game of variant Classic (2 players).

Each dot represents a game position in which the color of the dot is to play. The dots are ordered horizontally by move number. The vertical axis represents the estimated probability of winning the game for the color to play. Mouse clicks in the diagram will go to the corresponding position.

The position values are only estimates and the computer will sometimes evaluate positions incorrectly. But sudden drops in the value can help you to find moves that were potentially bad. You can go back to the position before the move and try to find a better move or ask the computer what it would have played by selecting Play Single Move from the Computer menu.

Determine Your Rating

You can track your progress by playing rated games against the computer. The game results are used to determine your current rating. The rating is a number that represents your playing strength.

A rated game is started with New Rated Game from the Game menu. If you have not played any rated games in the current game variant, you will be asked to choose a start value, which can reduce the number of games needed for determining your real rating. If you are a beginner, leave the start value at 1000.

For each rated game, the computer will choose a playing level of the computer opponent according to your current rating. The color you play will be randomly chosen in each game. To get an accurate rating, you should always play rated games until the end and not change the computer level or the colors played by the computer during the game. After the game has ended, your rating will be updated depending on the game result and the computer level you played against. For the game result, it only matters if the game was won, lost or a tie. The exact number of score points does not matter.

Rating window

Window with the rating graph.

You can always see your current rating by selecting Your Rating from the Tools menu. This will open a window that shows the development of your rating during the last 100 games as a graph. The last 100 games are automatically saved and can be loaded by double-clicking on the rows in the game table below the graph.

Previous | Next

pentobi-7.2/src/pentobi/manual/en/board_classic.png000066400000000000000000000047561227240712600224450ustar00rootroot00000000000000‰PNG  IHDR@@ЭЅЊsRGBЎЮщbKGDџџџ НЇ“ pHYsФФ•+tIMEл ,$Zн nIDATxкэмСŠdgЦсї;uЊІ™…!ЄЛЋ{33&.\H +Гы\@p№’Hю@D‚W`Dя`6’"˜­рZ0` $‘ШЬ(LŒdьIUЯEЗœЎІtSЇОчйЄ'ЬЏfёRsђ/?§ёлЕяћlj†ЭђбЧe6›щѕz§ЈњУƒУє}пчж›jншдZs|tœOўђ‰^ЏзЎ?и?HŸ$C­Љƒ!УАŠ^ЏзГвхАщІзыѕли?ц?ўе”‹Џ’\ќ `Л=К‘хŠљъЎŠћ’<К<Кєхђ7ивљK­“LКE&e‘Z'уеЏ‹Ї]ђлП}-я|јL’фЭgяцфщЯГЌ> л§ЩЏж>‡O§>Зіo'IоПѓZюм)ЅЌ.}Мє А$yАъЮЦЏ IђГŸЩщЪ№лЏŸœцжўэЌ†YVУ4ЯяпNз-žќGрЫ“шч_`4?ѕEIR‡+kЗЎН>ђцГw“к%5yы›wГ71‚Рі[{yџЮk™tЋLВШŸюќ CЎџДИюпbHNžў<пћЦ?“${“šХPЎ|’А JIJYцЮ§—rяпM’ ušЎ,ГюљEе7YжЄяЮ>ѕ-kŒ0–L)Ћдкџj•Ћоіћ&c§$ј$ўkН!Ўзы[ъћaRkM}Ь“’ѕий?ѕzН~Ќ}4?ЪёбёХџXМ‰ЩЄЯёбБ^ЏзЎ/ЅKyћG?Љ]ЗљM„aЮЯб|œЩЄзыѕњQѕGѓyњйlінуњєЏŸцлЯП зыѕЃъндыѕюnЪг&Н^П }€F@Р@` €0€ух ^ЏoЖwPЏзЛшž˜^ЏoЉwPЏзЛш ^Џo­wPЏзЛИ)O›єz§.єоše` €0€РŒ—{€zНОйо=@Н^я {bzНОЅо=@Н^пl?ŸЯгOЇг|ыц-їФєz}S§ќ№0})Х=1Н^п\П\Ўмдыѕэіоše` €0€РŒ—{€zНОйо=@Н^я {bzНОЅо=@Н^пl4ŸЇŸЭfЙuуІ{bzНОЉў`џрь!ˆ{bzНОН~pPЏзЗл{h– €0€Р@0^юъѕњf{їѕzН{€ю‰щѕњ–zїѕzН{€юъѕњжzїѕzН{€›ђДIЏзяBя=@ Y0€Р@` РxЙЈзы›эндыѕюК'Ізы[ъндыѕЭіsїѕzН{€ю‰щѕњцzїѕz}УНїf@Р@` €0€ух ^ЏoЖwPЏзЛшž˜^ЏoЉwPЏз7лЙЈзынtOLЏз7зЛЈзыюН4ЫР@` €/їѕz}ГН{€zНо=@їФєz}KН{€zНо=@їѕz}kН{€zНо=РMyкЄзыwЁї а,@` €0€`МмдыѕЭіюъѕzїнгыѕ-ѕюъѕzїндыѕ­ѕюъѕzї7хi“^Џп…о{€@Г ` €0€Р@€ёrPЏз7лЛЈзынtOLЏзЗдЛЈзынtPЏзЗжЛЈзынм”ЇMzН~zяЭ2€€0€Р@`ЦЫ=@Н^пlя ^Џwа=1Н^пRя ^Џwа=@Н^пZя ^ЏwpSž6щѕњ]шН4ЫР@` €/їѕz}ГН{€zНо=@їФєz}KН{€zНо=@їѕz}kН{€zНо=РMyкЄзыwЁї а,@` €0€`МмдыѕЭіюъѕzїнгыѕ-ѕюъѕzїндыѕ­ѕюъѕzї7хi“^Џп…о{€@Г ` €0€Р@€ёrPЏз7лЛЈзынtOLЏзЗдЛЈзынtPЏзЗжЛЈзынм”ЇMzН~zяЭ2€€0€Р@`ЦЫ=@Н^пlя ^Џwа=1Н^пRя ^Џwа=@Н^пZя ^ЏwpSž6щѕњ]шН4ЫР@` €/їѕz}ГН{€zНо=@їФєz}KН{€zНо=@їѕz}kН{€zНо=РMyкЄзыwЁї а,@` €0€`МмдыѕЭіюъѕzїнгыѕ-ѕюъѕzїндыѕ­ѕюъѕzї7хi“^Џп…о{€@Г ` €0€Р@€ёrPЏз7лЛЈзынtOLЏзЗдЛЈзынtPЏзЗжЛЈзынм”ЇMzН~zяЭ2€€0€Р@`ЦЫ=@Н^пlя ^Џwа=1Н^пRя ^Џwа=@Н^пZя ^ЏwpSž6щѕњ]шЏўсЙ&%хќЫšѓ/ЖоЃyе|uыуš>}NЛг<ьІO6‚[П~5Е›Є[.в-ЉнфЋї^бЏ›ЮiІљѕѕпфO§2ЉЩŸНžWœd™ЅO‚РVђЋ“>ћќCž{янд$œМš{/̘2Ќ.ЭзЅO€%%_t_œпљЛ…я|§ч9-§э[oђхiž{янЌІгдОЯЭп§*нтЫ'џИЄœMЉO{Р?ж$х??і>fЫКЫmЭѕсzојьѕ‹їж§fЏ^ѓ lНеЕН|pђjКa™nЕЬŸOОŸЁŸ­§Н§šYd‘WœфхНœ$йЋзВ(‹‹ЇТлЈ$)ЋeюН№bў~у;II†~–nЕLJy‚<џx™eњ:I’,Г4~РHVАЄ ЋдЩй~•aЕvќЎР‹)щ'С'с ^ЏoЖwPЏзЗ{№№р0ћ†aуѕ,ЅЫб|ЎзыѕЃыЛЎЫПbюІL‰л|ГIENDЎB`‚pentobi-7.2/src/pentobi/manual/en/board_duo.png000066400000000000000000000024301227240712600215760ustar00rootroot00000000000000‰PNG  IHDRрр-jсsRGBЎЮщbKGDџџџ НЇ“ pHYsФФ•+tIMEл \ѓ№˜IDATxкэлЭjg†сgь‘,AВHˆ4Z6 MHНЉšƒ)ДДєX…єˆъ@Ъ.^И -ŒО.œ:вТЇт_WЖНЕ0ŸыѕњJ'ЇЇiЇгižѓ<ЛнЎ*.ЅЄ[tљ№сН^ПGќь8mJВлэRJЉў€RJJŠ^ЏпЃO’ƒƒ1@0@0@РС  0@‡6љђџЕејњПзыѕ{є%i‹.нЂЋў€$9::ЪВ[ъѕњ=њУУУ4?џ№S™Д“”=š–н2—я/3;šщѕњJgЇgiчѓљџК‡кl69??зыѕ•Н{@Но= <\  `€`€€‚урPЏwЈзЛЌўї`zН{@Но= ^ЏЏ†єd€`€`€€‚  `€0юѕzї€zН{РъpІзЛдынъѕњњgHoAa@  `€`€€‚урPЏwЈзЛЌўї`zН{@Но= ^ЏЏ†єd€0 жŸр_$dvpћ3aЛk’4ў$`_у[ožd§ёi’’Uw•‹Х•т'hf%ыOsS’›вфѕцЩнЗ!`Oп‚ѓН‡іhЛkВъЎ2iJІMЩjљщѓs xьA“‹ХU^-?н вї иѓ}ыс'( po?AнsщѕюѕњеЛдынъѕюнsщѕ=ї‰З 0(  0@0@РС„qpЈзЛдынV€{0Но= ^яPЏзз?Cz 2@0@0@РС  0@ї€zН{@Но=`ѕИгынъѕюѕz}§3ЄЗ 0   0@0@РС„qpЈзЛдынV€{0Но= ^яPЏзз?Cz 2@0@0@РС  0@ї€zН{@Но=`ѕИгынъѕюѕz}§3ЄЗ 0 „Еўм‹’ЬЪ,IВmЖIуOтоЦЗ~ќ6/К—yбНЬЛGПe—„ћ˜•YжоццѓПзпм}b€єЬЏOЄGлf›еѕw™d’i&Y]ЏnŸљ/aИ—ЏМ‹?ПЭЋыяящkащy„Оѕќўу'Ј{.Но= ^џ zї€zН{@Но= {.НОч>ёe€`€`€€‚  `€0юѕzї€zН{РъpІзЛдынъѕњњgHoAa@  `€`€€‚урPЏwЈзЛЌўї`zН{@Но= ^ЏЏ†єd€`€`€€‚  `€0юѕzї€zН{РъpІзЛдынъѕњњgHoAa@  `€`€€‚урPЏђ№фф$ЧЯŽыЇ[nя™ЮNЯєz§};iѓ‰1­!Нф(IENDЎB`‚pentobi-7.2/src/pentobi/manual/en/board_trigon.png000066400000000000000000003200271227240712600223160ustar00rootroot00000000000000‰PNG  IHDR@-M/0sRGBЎЮщbKGDџџџ НЇ“ pHYs  šœtIMEл "~ZTtEXtCommentCreated with GIMPW IDATxкЌНi“$7В-vˆ=2ЋК9sgЄ+Н+{&Г'г™ўџЯн’Cr’гьn6ЋЛ+—из, "ЋЊчО2+ЋЪЬH„"р№хјqъЮ'f0ˆˆa ˜DЖo№Џ03%псЩ2_‹Пwы‡СDцд Иѓ2ЗПЫЬ"Іф="b;Pt~71ляБDFjоЛŽp|fцНЯ^ў:}{ћЦМ‡рVм:7%З-xŸxHPr›љ™ѓ?%vф{nкyЬј‰9~Щћ{s‚§yіѓBЯЫ§тŸ—ШК‘‡ЬO*ЋуіZм§Ф>юp#ђ^Ѓ9жЎНkєйu`ПhзT {žWdзшf.зЋ]eZЧfЪœ Ь ЋтЋ§зyе?і_ЂЅ“*Eћ‘Q: АЛFБŸР(Xf$wжЊш@1FзJ`bї^rЋнXі|ФСSуЯУ›kсфšidїЈеy,Ѕ„тЉ…NЯ(0ГНЂ*МНёйюZєE˜ўŸЪBДП„шeЪ–ˆТЯ|їц5ђК =5žИѓœ1˜мvіeз>смоR–З”Ќ}r˜Ќ,єФМаsзЊ”‚жк)У`вj+јч80\ж%n”‚1 ЬГ_Ov7жEh5%;ЃШ›L^Aјн"еЯДЎяPVЏП;G  ЭД^“WsЬіsЋHЬyЌeU™}ЯъlЭ[Zл=ЮO˜§œ6зЕоЉ`эб:^ЎШрY%ŠЮчЯБѓX1ТyЂpK\Эз№щ ѕ$гЭЅЗjŠnлў~Iо<B`š&Lг„еќ‚ћ•,ЯРЬP‹z~‰пZљDBBk ЭњП&‹f(ѕ/ЪТ‰џ Вg9Джџ%Y„Bќe!фYЅДвџђ=ddQZ§ыЯ UU!ЫЬ3уžХUэЦ‹go;ЄфйЪ6MUўf р§н`U–nН$ЫŽр};~ђЙ~bWZ2˜ы€œŽ_?ѓ:Щ˜E“Гю›ГЙCUкd‘€ДŽFсoVАspАmЄч_•(ЏљЖ‡ш|їщцF+5№љCЛзKў§№œD)%ІiТзњП§њЪВќWє 2)A‚0 Є0жЄЭрчCyžAkyž!H 6_(K–`ŒУ)%Hˆи$Б,9”ZА,ЫY–a‘eYє ПhћЌeЁ,B€@_ЊoйЙЧ y–Н4фЫ"y–c^f(Ѕ „ќ"=Ъ`аЌёЛпџџзџ§?ч”вСƒNšŽ’‘(xцSMЌmzъСЏ…uЭpbB„Kš“uОІHЗ€і ыHЙчGр#F‘rЧ XЛыZЯр<іxц‘э˜и‘‘o˜hЙ2їŸЙГБХH3эЊЯ эœgу obЋЖп1†5ХwЩЦ4vю3Cf’Ѕјю/пућoР4hšY–Yз„^d&”e>ўЃЎj–RТФ v7FŽџšƒу0@)…E-м4 I)ЁЕоSбЮa&”йXJ’>ўФUY!“YрьКпЁ,  A‡J-Ќ”BлЖЮуФС~р“Y "тOŸ?AfeY!вM?=ЏГ˜…Ѓ™ЅкІeA‚Œ%ИCC0/ю^ њєј y–sY–$Ѕф– я„X0 #+Ѕ YsлЖо/~"ЎIA№y‘cF|xџ~џћпућoџ­gh­Зšї4тNЈМК7‰ћзЯG‡ли§jБјxўЊ x'мzЭ›Ї[Џ‰\fэюXE›ё}ЫЩ™qЄ+9дп{к;OMh{ƒCч”c+Я>RљПЁОтРzп.5ђŸл/ёzы"5ю=y—=сРKчфЉнfbятЦяЛXЃq"“’>~ќ„П|ѓ&ОњъwдuБ8ВŒя†ЖбtvЧтёѓ#‘ ќўЋпуt>AB:EJ7жбн—BƒбѕюяюYf’ЦбXM/•E !%>ў„,Ышѕыз8N@†PЉ??d–R’R]зсЋзЏ Lг)ЅuйnЧкмMs.ыЇOŸP–%юяюёx~ф<Ы …L•зxS–e˜ч…КЎЧяОњ š5цyІЌЬ Тyй›drВ>~ќˆІnшp8рt:!ЯsЂр9yЮvЫВ у8Ёя{ќюїПУb­РЂ(œжИ ДѓBRJрОяЩ оџ†Џ~џŠ"ЇР^…"Є+'š„Кж…ШhХрћ{ЮБ"pЧЎЋЯg9ˆІЦуЊ"2аЮ„ММіLЃ^с\‡=PЧFЂHG@ИoЎПfЂD„гZЩ `ZџњџiтхPћ„КТД‹фR:ЙЬVнБз\сЏSМЮЌuQ…(ь˜l­ц<œšщцЌ#_˜є`ўќŸ_у|К =ДЈыšАqm a Jї+AС%&‡b,4…aИ,J.ђ‚›К†R Джvё… ѓыЦ№у ! 3‰ѓљ )%—U‰ІnРЌ§іXЖrАdВВ@HЅŒгˆВ,ЙШ 4Me™СьЛРЪтЦ`“A|Йœ8Я3”U‰Іi ЕffgaЎѓbЦА2 bиЭЫВ,ЧФP…ˆŸ!OгЅдM‡јѕэЏЮ №!&NЌиѕ9w)Ьt­:еФЮтuM!ВиŽчзX”ѕУЧ…л5ЖrЉ|aєМс@GИзь4‚=–Н)d SЊЋ9ХЎЭNД“2Є›ј€'6RЄЦ'о-?1>EОяŒKA.#5яЇљХЩРеAТXmoўљ ~ќЧЯЈЪ BЬгŒУсрwіM"–З1U)$‘ч9ЪВФ8ЈЊкgљыCQVЊВТВЬМXY cёЄЪЫNwЈИњОg­5ъЊb)Ѕ=?аЖŒуШЁ–кЩЄКkуѓљŒКnИ( ^–J+”EЩ …ш”˜злОЮ qзwЬ Ў*ГбгШD„І=№8Ž‘š2№Ёxž\ьѓrОpг4Шѓœ—efЭeQЙЭ.:7)TЂ.ФRU•INё№№€_пџЅ4“ ƒЮrїУл[мЌ5=c‰8ўыs#‚5эfЮ5 уtnMSУ#k-К5KLdБШЛиПѕћ~mЎу2yџ–Уѕюзwјоš6cŠ­7Ыt+цЌчTE;o7ДЌэerј>У'V+u§Ў;–“ЭЧырШЃNМхPvv)ŽTЮP­Рd@Ь~kdLЖ–3ŠМ Пў№7М{ѓŽmыaaJkZ–™ŒИRк#U™у9Г VК\.”чЊЊ"Ѕ E)’2Ѓ<Я1Я3й˜c˜Ь&7^–etэЎаZS]з^Fs~…Ус€yšHыT–-0тr9SQ”T–%М,ЫBY–S–e4ЯГ—=ˆђКŒI)щzН‚СTWЕп]УЁmiœ&к*,ZŸƒ­ЄЫхLUUS^ЄЕ&f&Ѕy)%MѓZШBV\ЎВЪТП?M@ Жi1ŽcєlЄ№I!i­щzЙPгдd!'V…"ЯIJщцХп#їь23 !HA—Ы…Є”T•Ћ,у8’DMг`G 2Œі˜ѕо! ”ЂkwЅІieЊ5ˆу4тэ›Зtњ|ЂЬФi}F8Œ*K˜Щ˜~эы2V‰иЌЁPOЌыЯуW9Œvы)\Уб:^_3ћЕеЦŠМuыЇя'ІwВО“cVћ-T€Бž"#3™Ws‚Mиhdцx8ŽЖљи+їІЈЧЊЎЧ2Чі3G9ЕфщgžoйсСZфиС&>Ю.aqz|фяО§Z3кЖѕю.М(cБUUХѓŸКЇЫSи3‡Шьeђ#“œƒЂБ6џрFœ[КQвЙ/нFЮ&сќФњG)ŠяпОЧп~јђ,G(‹xс˜vhЧ0№o­П,Уљt8X 2 р+`ЏЪ x ЋЛЁ€=#ЊЊђСњXЙ Lг "Bл4‡qѓpdY†гщ !šЖй—Х*,#Kœq }GЬгle[Y„Р4OBЂЉk УА‘Х$„ŒХU[E™&YŒТвЦ"гМIЊјФЧ<ЃЎk8+9ULу4"“ъЊіЪ+ќ\ŠЋЊ*SљБ# ”Ui*LЌ,qІ7лЊ†Д•ХКАEQ *Jє}П‘…Œѕ‡ЊЊќfЙWTpЙ^№ю—wшЏ=,\fЯкЯоŒюggБYЇœРyS=Bл“SRа@ќDM яќНѕ‚МSЮ‚шм"”ьLА Щэkь—ЃкїВ<Яя%2аЮ^@xКдr“KZїћ9 р›ЏПХщё„ћWїPюOFfЦЂфyn,1ЛPLХG†aœ0ЭГ]БВ№чѓ<Ь8Д-ЦiˆЌ)%.—3H3Й!‹)!Ы‹b#‹”§0bžэЅDqy'НЫз6m”ЕtŠы|>#Ы$ЬdO­š…ЗZbYz(ЅPWѕfqЇЪ66ƒX^NщœЯ'иИjЄаSYДіМx^„ФЕыЁЕF]W.IВ+Ы0 ШГUUЁњЭ1ЇѓUYСЦUWpj ‹л` [EфC*vnЏ]PWѕZЃ›œЧ\“Т‡їПсЗ_.ѓRЧоНУOˆeрфинї‘М‡$`ШіЯJ­А †зЯYY–јщЧŸ№г?ЃmкHYИБнCэ]>0‡ƒЏvXЛыѕ„ЦКŠёbчHьeQШѓ y–ЫвЦћОЧМ,hъ&.mс†ю!2kŽcЭИ\Я™єW$ У/ fЦВЅžЩU–,ЫpэЏPZЁЎ›Шч%T^REщ,Rgх\.фyю—‡мpl‘š Ц(/)…)fY†ыѕ fX8Pрк&.‘ƒŠW•qЇЄˆСИ\/(ЫEQFВЄжБƒ•ЅЩrЛp”вмg˜Œ-‡Ш]о*фqQфХЦжЌqЙ^PU6<ЁжPH:/Ёя{Мћх=Ўч.ВoƒOЂ \Мvв5ЗЃ8YƒcЭю­яриmюc§’Z€щ9тЩйеgЁмx(„–PDeSЮо%—CZcr|+oD!к‘|Zоœ@_hћ?­n5љдЗ,Л”y˜v'кц]Ъ Р=Œ—z EpЉ5'ЬŒ<Яб]ЏјЫ7пёиh>о@CxUЎія<Я,…dћPsQмѕOѓ„Жid“Уя­џ2“ LГ8ь˜л?Ўз Є”\U[KдЫТ.UТkТdZ&–Y†Ђ(Ьb+ ОvW,Ы‚ЖiиDcИ'Ц%(Іi2№Кfg23Кы啇qшЎщD–йФЮђ>”Џю_™GS>h­!… КЌpОœЁ”ТЁ= *+' пc•K0Дua™ЈШ ОПЛ7Ш”ФlуЛ‘—EJ”eiБЦЁ= ,JRjС>iSr]‚Ёд‚Њ, ЬxјјUYтюp0“c}yfG3sž+№zН>Д*‹’YvI;ЮK-ЪmRєёу'4MCЧУZ3ђ,бђзZгх|С›Ÿ~AY•\VЅП7!%еŠ‹{‚а˜#­Ш~Ьšц ›lпъˆ7,aЊ]vŠБКТЃ i#Їчэ HйТz}Z“ЅL;bЋЈ˜ˆ,цчKЕэЮ;ЏУќkD4@оРИЙМbЎ‚фЅВьСD ‰люy >РTюdmи/X+ДжјЧ?~ТщёDїїwPjСL$”fрЕ |k›и(mР>/мp™Fљ0iг4djtwe`ь@•š h6EѕuYињ’š@jЊСŸ (rЊы ‰,O*u"@-Г-f2CU•ŸaІn§Œaш§§(Ы‚ЊЊєёЩНiиCIЈyv4сШ3‰Њ*А sМt^@}пyзДЊJ*Ыѓ<бЃѕЭшМš'а6єR–%43Д‘хЉ 7Z г<тџw-§Чџ4ЩF'pЁђŠeЦ6wRCСS†к‘Wь6%1OŠt'%•@;гСО”$`CЅефйƒЫФЫ)$ЯKŒБ@Я1В-˜Wz™uR˜НNŽй‚аћv0Хц[єпМoЄFŒГ+›EŠг&/зЊќЦћЖЛЎ г‡I AТUUсчŸџI?|їЄ>vХ|“.|[жlіdЩАЈѓВ„єљ7•жі–IdФВ(*Ъ‚ћбd™oвГoБ&‘AJ)' ™’=НGђБkе23eYЦУ0в 2+ИЪГœyehсчЏ‹1/ CLРѓВ@Ъeяx#ŠOn^њЁgЧтэЌuJ„ЯLѕЭЬ–$цy0DЎLл­xїкМ,}яУѓN­еsзЩД,3Дв`ж4Я3йyсgцФЯ )ёщг'ќуя?сџЫQVE˜gЂАšƒ}Ÿ'љІmEї–c’&јcжъ=N™ж!Чvs№šЋ‰y ын~œ8Њь$/{КО‰bŠgKбˆgр&8nА[Јэфкљ|­XЁTSѓ†Ї!EsяСqu[Tх‚цчН­г"исy8СЊpОћЫїш.MB,р/шРЬШђЫ2уёёwЧ#ђЬўwшѓoeуGУ8@J‰"/0O3О„Lи№Ш˜І Їѓ їwї2Г љтq\ЦЖzd23фžгќEБ“X*0Ž.—3^нп[ЬтєDцr_ ХfЇЇyК„zB–]зук]ёъеkŸљRYŒaPцЅ…1ЭЛБЏл˜ џѕzE?єx§ъ5Xk,ЫђeВH ЖаžП}ФoПўцYТŒБЧиђsPЗ=ь`ТћpКlЦ 'р.“0аNѕ+B‚DvЯb9P№}Š˜NЗКG$аDтјЊМњїLAБыЙ™8i[+XX LЎиХ b[dВCкЫишЃшt гндТ)0ьФB. ЬЎд’‚ѓ…WF7jQžр„чД6tY–Иœ/јцOп(J“15˜+h–y[/ТЄ4Škмя0ŒЦR)ЪƒyШ7а™ˆсЬŽЁ•Fwэ DBЊќВ*=6oƒQл‘%Ы2 §€išpww‡~шQ8к+k‘Fpw‹“1–yAпѕЈЪЪпWтЕ' nШвu–eСнёˆ~05Л$>ldЙ5Ц<ЯцЛUх­ОВЌ|9тKd‘2УЕы ЕЦсp@зkпU‹ФВь‘Ыг4aŸe'" ~^bLуk2`№ —ЫЁm[єCЖmA LѓЩEц’y‡бd›B |ќє яоМ‡šUD™ЕˆЄ,э†Ы%"$ЧхГ‚Ц=Џ­|!вЬ[:OЙИ‚PHRУXЧ д\гЊ[ыаbsyЯIL'š’ВБЕ(КЭˆЯЉц$~І$iгъZюшѓнT[˜oвŽyї€€џё#~љч/8RZ7ЫUЧqŒkaУ]•к>ˆJ-И\.Ј-ЄТ5і1ЪУ<д›Š„DЖŒL у€ižp8Œ+< (‹EžcиqЇУ:gЭьAдзЮ,ng9YœЫЪ’ЉЛъ”~ш1+У{у8јц<{Ў=ѓ:Џ.А?M†й)_jWUoeёcƒJ)бu”V‚3 FI ИeсГЕЂ1 –ВпPе›ј/Ћtƒ 0w‘Ы)pэЎ`АЏ„Gг SLзU*K￘Ц‡Ж‰Jэ\)^КС„ЫТ*Hрв] ао[Lу„їoХщѓ)†xk7уИ_ЧMf§D7щЧMЊ4z3ЄO9б+ŒЅе‘бzъ ŽŽ‰ќ]„~ ћГoт!сё IDATё–J‹иГ{ёэ+УQ"2цT1ОИ ЧІv˜іKѓ-СЙЪЊЄOŸ№ч?}MeUЁ( <ЅyšP•ЅЁcš’й9с*„ qАЈMгјёЧq4V ‰ЧQРѕGсv Е†ЅУТхzЁіа’ТгžЯѓLU]›Zви‚‹Z[CпCkEV M}YШГЬ—К‘ Šš2­<Ч4шњЋWФNnЧgшЌœ€Эaч…\Ж4`žС4MT••Ї—В ƒЂ…deЩ cAїCусшcfRЫbЎбЬQjMRBA}зQ\92#jЫ§чЦи œ,yŽОы0Ž#G Ё(J)ДMCZk ЦЁDџ єнB Њ,•˜ЛGMmr—e!!œa’Š<ЧЕЛ`YІ—I$чѓ o~~‹iœ Ѕ$‹YЃuэQšJђПќє‹‘ИнQxнMщfњ0ЭФДt1MЭѓЇwŽлЧсЉ,}œiнwOжѓnЧ\sыЯЖ=ѕ7wЫKKOЁŸt}ŸHфyЕ(|џ§_ёљуЃq?KdšgH™Ё, OЧД69]Яe–eСљrССВ‡Ђ[`{V“ƒc8рl? .fY ѓ<#З•ѓ2GѕЅi€пYЧУqsЬ8MЈ­%Y*e+„Ёњ Д ЁђBa)цeŽЎƒY†qDпї8ЗtšFДvГpюg”ОsВ€бї=Є>р~Іy6%rAщрžхUњОЧ0ŽPžШ2ЯhлжЪ5н–…M9š*wЭЎЪФвћЏ№nІУ4ЯмюЎIkyYp8 ”ђжёž,Š}? ЫL˜…5€я~§€‡ŸР@pŸУТŠ*AžФ­эс‰nИЯ7пIл€а‹WЛК)њЛƒЃKetЧ іY•”э5юЏЗRRТѕ}/ўѕЮ:­Чг†јŸЈЌ%r№9lЗІЖoPUЄ%3VЮА_%Ч[ім,Я№ыћ_љЛoОуЖiЙШsу€ЊЊ9Ы2 УРBˆЈœI`€аu )ИЊЊЈl ЯЫ’‹МрeYo%K,3Щѓ<уzНђсpррsэУ0pS7,…Ф8ŒN>Яи]YJЩEQЄВ˜ Ѓ\˜РП+{ѓхSЬЬY–ё4Žмw=wvŒЁSУ8rлЖL ŒуШBŠЈЮђрqзwœeЙAмx№\šЮБЧ3Dp};Œа)lJrзхФЗŒаHЁ­ИFhмb„NЭWіьіЗЁC—;И%›,0œl Ђ(h'|§чoh'Њ›:`~]M[[оFЖР–e н*Rа<ЯшКŽэ \SЯи<еuMY–‘Е#atОœ‘ч9EБ2ќ’GŸ’вš-ЂeQ.[kВŠц2є‡шГp+ЦšІ5nћ8†ю'I™‘жš.з EAЩо5uў›RŠ@D•IBRЪЛxfIУ0а8Žфт‡{иЋqща šІЩЙ|dё‡Xє‚kwЅЊˆš‚‡Lецм‚P–-ІІgkЖ‰ъ‡г<бёpмkљбсp ЭLѓМћ@ЬцYѕk€з4ц„{#tbnЌы,dnžй1’ЎыžV(]ѓ›Оk #Дг оxх@ŸЙ{Тоt„ЁВž З=A\BbэЧБjUђ§DbЫЬ#ВЏ <Џзї љГv…ГZŸ‘Ђ Њˆ0_ХЎ—RШXАБ@…,„рўќOќєŸЙm[vт0’Р6 СeY"Ы3Ї‰;!ЅБ4ЮчŠВ0cи:њpчЭš•VьXчyіœ”‚ЇiтqбЖ- ЋUАmуР–Їy2cX+GГЦљ|FUUўzYœ›ХšЕ/ўЗLв^–qyš&nлvsлП7Ž#зM ‚'s=pЄRŠ/— зжzіжŸц’ыЈЧš57MЭJ)^–Х~f,ZлфˆлC›x’Zg с›ГЬЪ/I\ѕжМ8(ЬЁm lKK!ШRFЙ$Ьv^bЮРiž>RS7œч99w†уяЛ28—Џi[f0MѓDRJъњŽ˜™lќ0ќYaТF/4Э3ђ,Їм€fа˜Юч3ЗMЫІ1:яЩт[WX:&:ЄДb—qьКЮИйeЩKбк†Щєл(L6w1sЄ”ЂЫ9Ъd{KЭ^ЇЩš.t8hQ )ЅH ЎыОЮЩЮыѓД™cЬѓLy^В’цeІ<Яiš'О^Џt<M‰kџLКq‚8Љq9•‘e2=^й/•eIЩyЃ_w=ЫВPY”$„0ВгH}згёpф м|wН7DФЫВвš‡ЦiєaљЎыYf’ЪЂєїї†,.SOПОћ@Ÿ>{ї+IкЕіsщКІйзЧ†с'W†Шks&OfЏуH7 *fрmXЭъNоїЧFUџNBpў(ћЭь<ЋРДЕ $ˆзqBГq=)4OзЖKЋŽsЄ,ФMЋЎєяЙsВїїнѕј оo‹ЙژЖц’DDœeќє™ПўѓЗ\dеuХJEюLиЧ=‡†aфЖ9А0€gъћŽI—FY„n%юˆН, !ЈШ ƒчyцЫхт—жW†6Ў3ЦqфЖ=А Су8Ђы;Т,а'd‰РEV.ђ‚ЌЩзы…‡K)7эЬ‹w7lьŒнЂЦ‘Џне4D*Ъ@aE +КS<Г”’ѓ,їЎ~з]q<!„р$VЖ™гV@ѓ2ЯdЦEяњŽВ<уЂ0瘺UHЋђ™™yYЖ ˜фSЯУа;%Кыn&В№В(^гюTkЭу8pзuœ›Fы”&ИвgЭ)ТeY8ЯrgЙQзu4Ž#ню8P~7Йт­%ШЇЧG~їцЯгЬRŠЕpvС­cё ’ Ьф1"ltіЪ•яЙuk‹?’зцмї:OЮЙРМЯKЬЊЋ‚ущ Ÿ4aІlЅcBˆЭfвьt%H’,ДВЙ$]зA)…Џ^eТЋ,Osдлѕњеk<|zР<Я8А–hEО мГm`ёёСд џћћ_A‚РЪЗƒCJ%Ц‘CОЎvJo}Œ$Nж>Ї_жEЄ5ч€•b!pB75fœ—"О(Nђž2ԘвСН"˜А=вV{QЈЏœі[нх•ТqŒmZLœ\DPДЧXЉЪx’Jь8fаI9^\€гсЖǘtг(ЯјУ‡пшн›w†гЎ*Б,ŠЅЗИш6pьй2Фа@hš†] ЈM"<` ˜ь5gY)އхyЅД+]т›Š8Р,ЬЫŒ<Я™zSBхЪе‚šZp;%bliЁ2ž„$S “Ео”Qн‚І3ƒеВ ШsъћЧУeQ0)їЦ~ЊЫ3Р”I‰Lf8>і'ЗЌ)S‹П&Е,(ЧО;Љ(r;†р—Ђbб Ш,у\k:AЇ?кЛG‘LB˜ниr2ёнёHyž13“$‘Юэ‹Т`жB"“`80ИхyЂgя;ЧѕксЇІци№§Ћ{bЭЮ’sEжOриЬ0.}а…R  )ЌС‚gdwЗМa ‹ŒWІ˜Рž‰g,UжћДчёQЮ•œЫЉ… ‘ИЧ‡J–8ъ‹чs:>ѓуg$Qъu‘}qN:5ёцџ-!#(Uђ”ž)гhЈі €‰M- пќщ[ўєё“­ˆyгx€n-R—YSz4…ќš}iZ2Ю-J&—%­ БжŒqšиЖrМUЩ;‹ХїУЕДNЃiЙfяxБ,Юš?ЯB iZCPЛGdqCm•(kЦчOŸё№ы>|єДk{Yфg—щІd'A›VaмN ПL<ЅЏш%™cИОР+ŽлgW9ђšO№АМЈ>}ѕхƒlЉЛqОўбЖИYщrиQсИз+I„ЏU\ЛаљКџНшћ)ˆ2@21!)%]/W|§чoАЬ нппSžх4Ќ&aњNИПиМYžSзwЄ”FQИПЛѓ$Bw|8Vєк–)‘АбВ,CлЖШѓ‚”Rd•u*Ђ1B@fu]Gš5ЪВФнёЮбnЙ1n_‹•Хh/— Š"GлД”чЙ“…ž˜3†“EH\ћЋ# Лу9ЅѓЬѕ@ИvR˜qЙ^Ј, jŒ,ЎЕ$йљНѕKdcМBt]G`P]зNЉћŒh"УfŽˆˆ\§zНЂЊ*4uC"фГЈ7яЯкЧ™@D]з #Ыс@nгЅлї‡’yвнѕŠКЎQ[ЛaRђмЅЯž7Щ ЬРЕЛЂ( ъЎоџђЋЏ{^ЛёБЏхrkЬ—o„”WlkБШЎЋ‰aшцМNqЧ0Т5Œр|[ щVвaYнФAыЅЈ6ХŸф:Ќœk $eЯOBФ‘WПэМu\Oƒ”оьN ‘C\nA§y8ћ™‡+бъ aг8цлЏПХЇ‡ЯЖmхˆІmv)т.cЕL™ЪВ0ѕžjAUзŽcoЋЅm:ЋMJ‰ѓх Aei,Шж6(пЅКтD7ЏЖ#ZYќœв eUa^YДEмШлєїЭ щъ2ЃmZЬѓМЁ— ыUУџ іQЁыК6В„–—oUГ9+ЧЩRфЄ4Ь:N–mч9Dз`Q КОCmЛЭ9š*OœГmёИZ\'SІзrг4‘…ЛFћѓВXњЎІi •†ф-ѕ­,[њ-iнхѓщdmйчЛЎk#ЫnO(‹”„i1Œ‡#”жxїі>~јѕŽ1IщзNщ?эЋ… ю.ЭV9і!~М)JtSШsТ;DNu’@Ф]зQWBІŒѓНDasKwХqщА/9ПQmљlw8AkrУРo†Y6Йь”/Яsќіс7ќ№§прВІг4yюВ,о‚ ШЄ зLf8OОYјВ,\›.`ЫВxІi<мсƒш@В}o(ЅU”щКХ*кiŒ-e†гщ)%Zћ=Ѕšк’zъІx7б.аyY0 Њz%jLKЮ)‘…‚ІёЁ,B<ž‘ч9ъЊŽdqмx^–• 4кXЋŒузsВ0]эFЋ0ЂЭ!‘ХАE›№„#Дэ}ЎksŸ"%ЂhcхdS˜ІЩАВ4 ЄІЗБ”ЈЊЪЯnє іВАQЂu]лі™Ѓя_l6Нu i;/У8 Н%АЦ™mЦвДн’ХэЯж 7–Їгя~yО<ћtТtДЃ].MЌш_ )Д’ЕП]Чю5mщёvPA]сч7Š6, ѕЦЊ‡d[CЬ ы:NдjвХ8ФFяяћ’дTуQHRЖkFŠѓ{˜&&‰$ˆЕжјцыoјrКрўўо— -ЫТn‡ Ю9 Г{ˆІyТ0Œ\••)эВ }ДR8Джjв*(iBXм›ˆСущФRJпз6к6$ ,ЫТ;Ј~_њ%„рq1Ž#зUэњРšя1Ѓm[Lг”єћЕ˜Ё rRгщ‘ѓ<чКЊ,ЌД•…™•RN–ЕOУЊxXJЩУ0bžfдUэѓѓ<3hšу8rjэ0#Ђ%6JєФeQЂ,Kf{n­ДeЂбМDJйы†ž—yA]зЖ7-Y7ИЉ Nyyа[\ чЪѓO'ЎЊšѓ}š&v›№0Œœ(/F№ К љt:™ ![•r*ЅиuЃ€TРaэ,Ќ…йвL…П~РЧдyпТ%rВІ>тHŸ­„ Сї8ZГщКFL}NkтДЬn‹YIuRђ„”јћ8Р„"1а^1R›7džњњТz<#ђяг\˜Ÿ№\иfлИ ‹9–EI?џє3~ќћOd- _œnџ8Ž4EыAНЊпгЄ”tОœ!Ѕ ІiШФЅLсџЂЪ‹Y–бbš…Мo^–,ЫШPЫOTз•/В'"_481Э1k"PT‰тŠмЅ”t9Ÿхеu‘,jAaљ —eёD ,>ожї=–eЁКЎЃcцy&fІіав8+х8ѓІ&‘ˆш|9Ё( Њ,ѕПЛ.Ѕ•eIТ№§E\xэRJъК+i­œ,ЄЕ&!9йЖi}—М0ЖЮ‹MZPU•(ŠТпO­5Œ,„k'|ШштГІХ%S]UdKвHAу4BHIuна8СЯГуFМ\ЮT75Й !w]J+*Ћ 0ВјyLљ‰зы•ˆ љ…CхV еuEУ8 –…CY Е6ќ’MKсѓODшћžоОy‹ыљŠLfР‘(юќY*›JЋч(ёУј~ |м0Ъ-ј ЈФТ c€‰5ф4лЊƒx-'ѓ9ŽUGˆдЏŽЏ".ЁXЕVlЉ'ѕ1>cbˆBt$oрћAЭYдЈ>ЩЄиЬyxeQso’кx…%C oЕ У€oПљC?тююEуwгLJЎL;Cжrь™MУrЗё2›јXФЂlЪ•X)…іаrшТІй/"ТхzхЬc­,оb4H†{pšgЖ}Л8pЁ9Ы2г$]kДMы8њќN?Я3Xkg"Љ6ˆфЙ\/(ŠUU"А"ьМ(фYaš~OБђrчЫВЬgl›КёgрF2nmьlЄk-ЎЫхЪeYЁ,K]f7/…#qН+Z\ьтЊ&>ж„acy„ дMЭЁEКЙиФGQфЮeіѓЂ–eaтПюњY „рѓхЬRдUэhЗќ8у8кЯ*XхХ{"mbМм4 [w$ЇRŠЫТФУјІ{fLrCрt>!Я2v–u(‹fЭП}ФЏя?@-ŠЃx`„x 2р№3іH ШЉ,AЩ–jfпа\ЩR8аюuд@Ты-NЌЋДђ‚|Lг)v#jDЛˆы›€ИЇA§@яд—бND/L–„6яŒы+cSLž9ОџюќђЯ_pџjSxютqJ)4M ЅДщщыi‡рџv]‡,ЫaЉŸ6Šm™gQ|1ŒWхyŽЫхbнхCt]ŒЊiZ,ѓ6^хЦыК~эЋе&6ы ЊЊВѕЃиШr:ŸР ДM Sэ†(~Е,3РMнD‰Œ№\Z3КЎ3T§EБ•ИŸyžЃxUbBJ‰гщ­ ЄŠ`žgЏицiŽы}дІoIЕЖ"и#\ШѓEQlцХЩђxz„л ДцшКCйКЊwŽЬЁя,ІEцV–й’ИКў,!е•K™ИjЊЌЂ8В“{Gd™DUV˜->0Ž–eЦ8ŒЈыЕ-Bš›ц oпМУчOR$IИ}@G%ТиРsљ  ?QєђtЧ’єЈ=B…НФ ХЩM š"AM.ЖtXэžмЫW‡!ЩИг|„&їš§љ< огaYр5bcибaЙfnt+˜žч9ўј ?ќх†ъКт`qE;ї<Я37MƒqšТуч9_/gУ2r8@[ ЄЭƒц{\z)g9ȘнbКqQ(Š‚c+‘=љЊ…HY‚xЮѓчѓ‰СЬ‡іZHлВЉpUUь`1щчУ0pi 'Нp§мXа.›xеРaR%Ы2œNŸYСmл&Icє8хWUх;РmdT;ш‰bC2X"Т8MœeЊЊТў%*q:}6–u]Ч1БUyёВ˜fшeUљ9ZЋ6ŒЌг8q]•leс4LD<ŽЃ!U-KgСйѓ !јёёE^8Y8Б-8 нчВ(с:РЕЛG{…кЪ„iSBдЄ!qъпГцбЪЦАbкМэЊХФж›х'јцU_ЊЂЗГСPBˆjЁ› н"DMы™“†Ђ;„ЈnуKB}ї—яёсзпшеыWŽбУЦ}V+мХx”2ё*ВЈ.юЗЬ3ѕу€В,Щюц>оэЕ`jл–Ќђ"Xі™Ых f&GpХ7WYШZЄ†qФјфf—n›Т4"!ЕuKУ0ј8S–e8›L6Y%JA”иуЗ\ŒTkM–інЯK&M“тE C+У |<ёјјЕ(МК)$‰Ыb@Ю-.з‹mgYкЄХШЂMЩосpРЇOŸfМК{IBОЌS+ВЌа6 Ўз ЦyB]ж ЎXЮМЬ‹і=‰?~ќAїwїfNфўУœўЈEЁlJдumZSZkЖБ@ч—ЪЂ,8Dxxx@.sмяОHгўДEЕ,ш‡оlPu‹КЊПH­LFПы;<<< ЬKУHHљЌ, †Z”ѕї}йvИЋJяЄШ"HcžfМ§ч{Мњъ5ŽїшIG„Xз,жNТЩЎщ(§I‰‰YЊvlЮ ЩЮs1нc 2ЗчЮ^ж˜ž.>~т›ЯW3ПЄХх3‘“Љ( \ЏWќєу?!e†усАЋжo§H!Р69ADШѓ ms€zЁт2nЛ6“lAД‡Ж5;БV/ž-iЫЫ„MW•EЖmџ%Y„MZ€€C{@]UP_*‹ZŒ,дe…ІiЭN*х‹яОЁtЪe™%hQ•%”Vxй8R OыЮ`дuэлjfйШЂ5dž–A8ЈŠ/”zY 3c:Х/•ХmRRJ)p<ѕЇ„xё0PЪЪ2hпVѕЅВHiЌЧOŸ>сŸ?НСџљ?ўЛo‘№дк{љ~9нўўљўgokЃз^AиЩ]ћšœ$Z‹Дj}тX‹{nУ]уж9œЦц›лм~Б Х9шРл7oёѓ?~ ТДЬ/Жўœ•жp–ЭМЬPŠОˆЧY^кЦВ­0Mу‹\‘H%`;œ!—Іy† AƒиX-уП"‹6}f•ж™ФlЪџŠ,Ы˜yYœ,њхлЈeX™цЩФi-Мх‹XE\)JPU1/ Фd(И^<–•eœFГœ|_b~љ5!•eОL0„2%{ЎЌoХОT–ЫїЗў†у§џёќ7GЕxb­ARb_‡ЕЌѕ–.!уŠu эX€rИ•™0ж[k˜,дY ‡h;‡чєik=Y_Aи Ѕ@л˜_,1 < ’‚ѓ#њпW˜Xƒ'\% mf ŒУ@ђ№№€?џчзŽ0Ž#йЮh)Чк.йЈыћ:tWвJ#Џr\ЛŽлІСљ}jЭ“'9—…њОccUhŒгDY–Г­џ|ŠЯ;Й”ш/sУЊœЛЎ “1З8рЂыЩl­ё0єЈЪ’•ж4NЌ,{„ЂЛ[’ЛўD™4Vг5–хЙэŠ™™ж„а€КЊyQЪ7žцх– ўк2Уі‚"/,­XЖie>?77Ў­d? fј єп~ј;оМљџёПџ‡С6QХ‹8ЮœЂ”2нЖTU…y™БЬ3 yQ ршО№ћп§ЦiФљrЖСХ‹d)‹ѓ>в›Ÿ~ALГњѓЧIk†ы65уlћЅ%8СЄ+\ЈE’ŠкMWИЕБ{д $ь ЎЕ?‰иш6А’ФfІНз)ТJ+–>їфрЄМ—Y Iw№4nч†п|§-Ц~ Ус`@ГЦ-†ЖxИ`ч" ВEnсe™Ф<ЯдuއƒГVЈАp„aHXqvkFЫ+ )$ŽОЮ7Тq`UšФƒЊ"l+7Z(Я8Ž4є=rЭСЋЊЂ,Яж+РќqtqŽGЮж’R‘(Ы’\мЎ,K,zй”ыYXYМ(ђУаcGяŽ€aЈЎџZ)ыЭ<Иy Є”PЫтЫцЂ’ЪВ4^жбцА‹ЧВИ чёx$6ёЈi2L+c4/~1ѕн™%‰шњЅХЮг)3”EюЋU‚z№xУ юбѕz%Е,t<HkišЈm[0˜Іi")Ѕг6Ў>†‚yAf™Њћa КЊ)Яr[о–С4s_b€9%q7ћОЏЩж&љБ, ŽЩ”ЖНš…4J‰\4.–EJ‰qњџ9{гцЦ‘$m№ём$%eн=г3;{Мfћv?ьпЭv>ЎMWѕ]}TUпq ‚JеdYY*% t‡‡ћsL4иі„}­ёщгgМћ> ь1hUшаі-КgН4Ч †nьш"Й(іhX§}Ѕ>ИЦдEЏл)ЦЂ8ELI Я^с5C)Ъѓ1zљНШ“"Wy пЋHєЛў?R„F ћЏзS­„ќњЇ7ќЗПќ Їу‰“ФйŽgi†"ЫйБ‚м•§b!АіЬmлpf- }хТЦ.ђ№Ќч•Ьаbѓi”‰фišИz>сgƒЃ+eIfAГޘ'р—XXZ uюК–ѓчюѓLгШУ0pUUь€Ч<ЯsˆEЭ.”w4Ўх’RВ=ГыЇКX&>Ž`УPJё <с},у8`šFЎЊ*Ф­ЕfA‚ ‡k4lЂXжытлMгАSоac K)ИяzМљщ-ЗuЫž^y3ЧcŒXumЙƒН”ЏЯWt‡эПЩ‹'0Q ­ђФоБcL7mrѓVFРО\ь—[ДSі­ЊзkЫvрФЋŸЏ+еU3€ЏtАAsяcУzЩВMнрwџѕ„(ЪbѕєVJ9 jр^nžю tš&{ˆљ–1P5т}.ыГм аZЃn”ŽЎЇ:э{3“šVБФщЊ*ћ?Ќњ3–3[ Iв•fиoYІмmkтО*ВI]CЯ…ƒHlз›uщ‡С‚ž‡гФ3[Є”˜œ)дplcБ§C7РYkН…ULБkфЋь= nпїак8&ЬвЇUYQе[Бј!Ь0є!–BЂƒ^ž1ћЪмq,]зСAгбџ™”ЋѓВЖЫd‹WзЫ0 '{ЭХяЅ]ъ™-Б27эєэкЖ €w‹1&`§зq!Ч’&)КОsЏ?Ц<@ŒЇЇ'Мљљ­й›ЎэXкв†5БНЧcˆ4]oW:Sё.0ж‘ZЄPЎH!'эH9ИrCЗ}Шая#ZAБ§Ў:мПLМiлrлkq­ЙРФб№(RЮсэjpј9ћїsхБL$1џсяєюЭ;:КmЋѕДcБiš$ 9у"Z‹Z/л3УuSSYUА^fЕНЇ E^@ ‰iš(т›ЎT5ЦqР<ЯTUUьGBDDу4!KSЪВ“RДНˆ§gšчйjЗŽ!qEœCLjВy•EA$)ЅOи&пу††q$gM.(—Hiж‡Њ‚6šцy&З.WZ]пQ`kИЩОЇ+њ‡TЌє'єОяhRŽЧSає\ыYЯЄЕСЁЊ0;enї€Yi 2ЌЊtЪ2о€оOЂЧiЄwoогљё`Їв‰Y];Ун”%-нЕ›жячЇ9ƒыŸбRЎбˆ|•У;šаР žГЩПЋŸaЧ3фЪръk^Œ…VЭp^Ж›FупџѕЙYm!т јtф™S1zЅ{gŒAн48T‡ Œ‚6а€СmљЌW…ЏvќХаД „А&GKВX4§6‹!wp›x8#Ѕ„­ўк$К~ИŒуˆCeY!6y‰UВh›‰\ЧВ…1Ьѓ )ЄKъsXvаЅКЎЧёx|–эcё‰ЬЧ-Ѕ„ЃmЄIŠ,Ы!„@гдјёЇтЧŸ~Д|U'‰e-F3пј_mЧqt‰ыtџˆПіmc{gзЁЖE–Z О­СSМ.і“™Њ%‰О0ю$Ў8 њ-Ї5Zoa“$БІM]‡,пЦkP:Бо4qЃзывw”ђ‰‹wЉ7к:OAo2>†R ]пy9ВЕ1XДЛбГЦћїёщ§Ї№НиЄ+мЃ[,oѓ жугXщxлћКŽ7ъk-бЕOаjПЫ ё‹˜)ЖLо}5_KiESMІ›ц*ГЃ_'Цоыaо}Р_џќW”k­Гєf1•БІ?вMWEфчб‚фSЈЈh=љvлЅ0Yіе—XяЛ‡ъ@Wе]% *+;AІЩZ A` vж”юiюІvк б6Ыђž‹кV*aыЋœ ™лЖb#iДњ3ŒЊУфEG‰H УŒКЉ‘ЛдC}bCПUR'B^”4kэ%ё}o†q$›D}E'‘$)’$іLchœF:Ž`yTпWmкEЄъУ>K,Б„uBН?MH)1ŒдЄp<ЏЭli[к Њ1AЁЏкЕЁїŸ# Jiкm}ѓМ€š­bђеK KЃ›Ў’Ž'š#Ѓ+/јб =UUv5ё$”7U ”’В<ѓv!–ОяЩу>ЏЁ[з. Bл4xћњu]ЁаXЦ^{ХмВGІ›ŠЪлйСJ/Ю3З`њ;џт=8Эђ— Ћ”ЙŸЈh Мf$<Ч&к+ѕžEdЎЏ†(VџдњЏџяwPгŒƒ›*^™DoОgнлЪPЉј­b?іЈ6jЙёА)О ќvZЪу4YВЛ3&ЯьањI|} я!\9•e/NkИ4Сљ+Ž…wbБb™ePєѕЩсrЙ8C —ЖŽ8млLЈ~МчЁ:@@чh&Е.UY‚\-MЌ—o]зЈЪ*ФІЕЦсpРП§іп№oП§7ЊCЈŽНСF>T“уlNjтI)vRDёq6Ÿ0ƒчq9Ы2ШшѕчЫ™ЫВф4IБ‰eяXlŒ†RЊƒС–W;БšgпьЌKЌШDФ}пѓЛзяјђTГ$V4ЗЅЅЕЇКWsциЯˆbЅчMŒVю‘cэJGбу#‚#{ЇгЊOй#Y(ЭWіСДсщE`Bк’7ъ]^чQ;єeZŸiЩЙ1moЫкu '}NmлсЛџњRHмнXkMœvxЖЁьДк#еuЇЇ' пћЃ XњZЊJkнЈ”BлЕt<œu#ЧБ<лd˜чwwwtЙд8ŸЯBЂr–~ю‘шЈ( фжу‚лЖЅ;Ї$ђ bЁyžq‡K]гљ|^УО%Ђ[<Ѓ BkœыšіЖm‹ЛЛЛ gИ…ІјmєіsjЃqКЎбwв$eW“‰k КБ.ФЦXRш†mзтсў>(z A_ь2љ„§pКiH9?‘ЊЌ‚ъіK(jlЌDл<+Д]‹Оя­4књ<пМNcР`ОППGг4nр“Ѓ,KŠ*1z†+ fC`F‘ч˜чuгbš&<м? ‚‰ћИВиXэŸЮgМ{ѓЧ;‹p(-Œ\оо…лі­AL о9j‡б†ђОISю €vЁсЮA€Мj~HkЮЁ1њhЫ,з‘™yuу‡Фkє7ёЎ:ФU™ўи}PІ З7^і Цq&…ƒ1ўёŸ?ag|ѕезШВм_7ЄФkЯЅРТЮВœГ|Ф0ŽјъеWШѓœ єVтл^ЬlУ­щuХїwїіBиv1wџэa:Y–qšѕ˜І_ѕ5ђ,ƒБ№‚Н'ЫяЪЊ‚Њ/8Ž|wGD|;–URg "ЄYFIпГR п|§MhЮЏЄ€žpБ”ЉљЬЇу wЇЛ Nлт+@€IъФцп|§пBѓ†(ХЗз˜$ИЊ*œ/gмющt:Бgь?Г&ˆЯ‘”Y–SлЕ,ЅФзЏШ‚ž_Ц}і‡B ,+Lъ‚ћћ'G'уg>УZдBЪЉ“{K’ЏœКјMaŒнD(„UёVГТ§§G6wЯ I„m№ўнм=мбo~ћkfУЮК0РI– Ч Oi‡‰і’МPшЖ"^`6[щ+Њqt ђш&оЙіэ{;1„—юЧПLhІ/k(€^ДПуђ=ˆ$‘xz|Ф_џќЦСJ€CП3yzцє“m”[ыHл$ЮЗрgЅь„YЉ Š№rщ-їtвzvюoЎйNќЫcq•ˆRГЧ)†йћ/љ<ГžЁч9€Ж™Э‹EVБ0cVГ@ЋЩmB~A,‚0Яzжn]Дбр_‹Т Ћ4D*1Z‹PМTв'c)”u tБXЊм/ŒE hm нД{œЦџF,$Ў ћ |ў—†Т кРкž П(–@'ФЇŸР0xјъE™ƒя џh%рВ—sжŽn_LЙЕ)ЂыY+­ЈОыќF$VъŠ †qu›aWFвjЃЮWЈП˜б#ІНжђ{„Ечh\);YЌhZ"ўўЛпгљѓйNЄКжz)C9ˆ[`&!$ЦyЄЎm‘eЙ.kЩI’аўвКZr%rš$8?9IRжZSзwШВŒй№Э­УіпB•ЂОы†ЁGЂH!y#pАw,0‚I—ЫYšБв3ѕCЯvлy3–XJ›jšxш{Ъ‹]oс"kƒoTLД$… bF]зœe™54ьаЪ­-ПЄЫ-ЅФ4XЩЌбПџЧПy№;/|ђ НЉм§flћя@Ÿ`кrwc-бЕvAмK ѓтZНПяМй*0qŠkД‡/%—’в~эfСМ-=ŸЉ§˜W&Ё}а…ѓGДљёRР'i‚Ÿўљ§э‡ПsšdxxxРЇGы[Ійž>ям$œe}ќј‘ТWЏ^с|9У0луJЉu ,Ѕфyž1M Uu В(љёќ0(MRŽЊІ›[`фІїоs’$јъсч'O…лош7ЗРRJVгЅ,.э”žјщђDiš"I’ј07ЗРB–Bт§у{*Šœ_=<рёщ1рчќCшK[`)%уЅ&КППчУсˆK}FšЄ^1љй-А?AŸ?ТсpРУ§=ŸэК$W†›лЧD&д+5сеУ+T(ЉnjNЫ”"ѓЅ[ }т—Ы…юяяљt:тёщЩџхrdЮфHЭ _}ѕцЙ@л5ШЪЬ; =ЛffNгF\ъ_ПњŠЪЊД=lXCјHяђЙъ-ьei-ŒSF§аsUVtуAЗMЂaЊюp™єўЭ{~xuO‡у,ˆзru‹F`€ОQ ކЁ‡xwВiЌѓUШ[ДЪ%ўxŒ#™vЈpћЖ˜ Ь 7ПЗќсНФœ ж˜#›Нјї7ЭJбїПћC7RY•Ф`J“”дЌmЧKEЏО'ЅЄОя1Я*(TецYУгž6Пwuп‹<ŸЯ$ЙШ I+d8ФВcѕo!u} c4eyFк:TšІ‰Оn§ю–юtО\(M-­MARHЬ4{+ФŸ‡ˆЈыZb6HгŒŒ1t8iœІ`їИYлЋuv§tЙ\(ЫrH!)‘’ rv–ЋСТЯCDDmзˆHЪ„ ъpР0ŽСжєV ўпО)Йд”‘ JdA‚+c{На6.oЉйv Iii{ЂЊЌаEзмГџKЇ<г45•…ЕЩL“„DjV‹œмўЙ^б ›ЖAš$ !\ЏBпїёкоŠУ?\H)EMгRY–€в$иСЂcмМn},mлPъфц>~јDяп|pУ%hŒq<еёй IDATNqЪ3QŽYх ll.WЏпЫ3KўˆЈЖ›МфщНбkіm1Ÿu…[mщ‰iM qGЕ”Ле;,ƒоы.™Ю~їпђЉЂ‰S’&јыŸР›зя‚’Ш4MжЄ\oHоNзџG@ндHг”‹ЂРшш_ЙeЅ˜ЅМО:'I‚ЎmaиРЋўЊYёщxb‡Еbкиуm'NQuн ЫsЮГœЇibyЭ„XЬл—у„'Ѕ”mл`”eХѓЎКЉQX $ЇЩъ§Ѕщ+rжт˜*%Ѕ@л6LD\–•Е0VmeкЭ‘;WЄŽтOšІхЊ,ƒk*SЄћЙюуGчœуѓм4 МБRŠŒУс`ртЩбЎHЈ”F[w6'šР“УzбSwN–sДО^Ќr mл I.Šг4Е•­щ§vMуЪzžgюК‡зГјЦ RJŽyТб5ч\ˆЦ`еŽ–ыd!ЪВdчЏ)hчкuлyюћ‡cь‡пО~‡ѓгBŠшZйИФEзп"Ая фЭ‰)’= ^ЙТё>вœ,ALFЃˆЈыПДjйQ„“YЫмS|BhqHїиŸ… wеэфѕ[F8УїГќж_HГѕЅц?ўўOlЌ ˆХvУ`pY–АX+УтšЎо/MS4mУЦ0* ЈЕXЏYБw|s2фЋiw„ §АЖm9K3Юѓ…Ю‚SžaŒ‰nШk|—5&ЏыJц zžљx8ђЌ5f=ѓD$рН mлržœe)‚” Џи5dЂѓ%DкX„\”……66‡]‹oŽ ”……ЌЕFзv(ЪišРУѓ<Г”вkџёЮW(!pЙд,“y^3уx8ТbзЬюАЮЧ"…Ф<ЯмїЪВd™HQD"і@фHX`бY’_ъ ЇiЦi–ћЄУТЁЊlRwЭјЙН^”RмїWUхёЊPГbkНšbt 1ЂrЬˆїP›КЎ‘ч…пq8ХkГ:MSИ.l™iі~86 cHш18л3K"Žp„ЩЕпr8LvKN’$@ьžžž№ўѕ{V“‚Туя<o9зё}юАzДsпя€gЂмБрˆiэЦtOL^ч*Dв‚фQљ™ЩэjђѓќТ—‡GПРѓfсѓ "ќўћ?р§ЛИПЛ˜4w![CKђ^п>zЎЉ—ї’ч~ђiиЪ+)‡ВЇx8U?ЩЂ–ЄЁbвzUVэп1ы PењоњОІщ*8T”ѓ‡ ŸƒЎћJ—ЫBˆ фџк1;МWь^,>сLjBo…VсиіfŸHЊjё№E hŠ—чЫ2IPхzmБ,ЧСŽЋЎЋX&Ы„)­ж_фГ!mЕ;ŒУU,ОrЋы:HюћXтJ]k^фЋŸm,BлWe-Оme7Ž#d’ (Ъ8‘^їUыeQXУЇЈšБh‹cэХвД­wdк у€,ЫчyЈЗёza‹Ъ&ЎЋXќѕŸч9ьfоЮ`ЉБrdEqUeДбxџў>}ј|Ѕ>D{юєe4бЦг|~(В›МъЉ,јeоц$/ЊВюџ2V.ъ›ЄDY™UЙО#wХёL&(ŸђъїVR[@gžgјчпўќ№œŽwW‰+,њlЗ3GЗ…нвйкЖНRЫР‹DaM‡жjЙN/-Јх‚vcБU ЛэєДzЏш) щјqВˆзFЭЪiЭePЮзеџЬšљДаѓьbСЎ6ЁПЙmуМКX},irИтV‚RЪIYea+ЗšК3-ПэЧom Њ c”Мтз6Mч[ВЊ№Жъоiš#Mв№љ–!ŒMФ!„ЋXмжVJЛў›!DxŸІiЌWˆ“,л‹eV3ђ,ˆИЎЮƒЏfэћT7eЊBX7ЩЫSлІ]m[oЧbХzc А vtЙ MRф+EКŠ%Ыrфy†awЕ.ЛЎ‹UpЎbёКšo~zƒњRCJЙyPy:oœoещ-ВtKц+щ~ypЦjЌЫ ЪC€ˆ<Š96cлx“VyѓНЙВ'}[ђjbэо;і8hлњуŸ­жмёА;XMо”R "ЎЪ*$/яялі=&ЅиM9J‹o‰ Ч:а\Zх\о\јдrmВXHтቹœ‚‹ЂdwcА|pлЖаZ‡Xі†Bžе лiЊZѕˆи:ХЕœІ)ЛdСoђ№Е,KЮѓ"Ой9MSдuc}(‚№B4щх(Ы2 UV7hзwœf)Ч}еh`ГФЂ&NвдЯ—XмD„(–еt8$ѕIБ ;р‰“qpŠы;ЮѓišџЙœ|–Х@–fШвдЄнu$ЅФХоИ\–х"v@[*™рIйЕ-Š<˜Л~3ї}яExyљL!Že9Ыs$2ѕ“ьЊ?О\.!AcђЭЩv[XNвY–Б›2‡8чyцaАRpIT§EБD˜ E^њЄЮBnmЮ— gYЦyž{Q‹рCХТЬŒЯŸ>ѓћwyžч0й'Šr!7D9'ЪjМЊїx•Khѕ{[!ш­ршFШt-žpCџWQёЪаcƒу‹Ќ+ƒЩ9op€‘§bАб(IњсЏУЯ?ўŒ‡‡‡5AлJ˜|яLkMNšМі™1†,™от•6Јћх яБ€‡ђ@.4MЉыZв^B‘l9mЎ $c •EIn›C!–ОGše”9Ќнњѓ,fdENIAeYк>ž‹ЅibУtt.dи1_№-ПѓБc<. Уа#Я2r•mH№с\ Ч‘ЄLPyИD’$d+.Ђусш{aЋ8ЯЧ!€XoH€]Ч2YИЊЊТ4œ­мйОtП.EžУhƒЇЧ'(kц/‰Х‹Цњ>^?є2AYVП(cŒmшїРучGшYуt:aŒ|#šрŒgЋъ]п­Ж­П4–a№љбвЋВТ—EC—?оВaж3њОї\yy VЯЧЩЋ7Кq№љѓgь@h›фž;Gjžёўэ{|ѓЋЏёэoОНВmsУО(;~sk.g$ўГУƒнўGеWВf[гHЧZ‹ Š#Iџk~pЌѕТ+р,9І39мЉУw/t=іRѓяпр7ЏпслoПЅШsŸсУЎШоI’ Н;с§‡8ŽќъсС?†їИЌWыщœЖ8Iђ†?wwwь+бТ =—”Є”ќсуGœN'<<м“1+СЩ[мXђБ8zІiBžчќ№№€<ЫїИЌЗDТ G ‰?ђн§нпнљ ъGx{“RšІLD4Љ EQ *+/$ЛwSьЦхcBруЧOќ№№ Їг)ё3kŽ‘DІѓUUЁ*+пћѓМгgзЦŸЫЂ(˜ˆ№iшщеW_ССЄ6lžлќgџyŠЂ@нд|<QUћ~л<О+,ыЗЗЮ‹ћa ЏПњšЋЊЂ—< ЂuaKг48Nь-QS“оЁяap‡ƒ§0р›oОEQМ‰х–Œ˜D€ж?џј‡у‘ЇŠg5#0}#ю/­„AЏљЫбюі ŒOA^Я§M‘юдЕиLxo†“Ук‚]nЄсРІкмГЋЙЌ†ЭAд(]/яч8ЪЦи‹hќѕЯE[78N1—ž5†‹кАёžl1БVТє Ъ{=сq•ЄZLy&нHWё6`УаFбO5Эž+ќEEЃ‡Xи@ AžВџћ|3ЛЗ^(n;эxО_ZпИТƘ8УHщћFєм{яg™Кл Щоєў™C€љcШАA*RђУ/œч­ˆkрСJ!‚oД._И'–X|›@Єv ~хйќhЏЧЪjЇјЬ šІЩЂg’ж*ЎyžЁgьМRї беНЯт™Е]c OгDЯTшЛї—ж?ўуGœюŽјџчџAЫ0jеѓЋ‘ТhіJ•ЎЎ„ЈFЛz^™‘­•–нowѕ™™n” ~rУk•+ц­T_P bЊБъƒ§“S„^А9i‹2SUUјѓŸџТџљ?џ“™bžU0ю~сгЯг†xVГg!я–K}a)$%‰dЅfx0ї Ю7ћ!Ь&lbyЩКp–e–}’Є$„рYЯTЅЏ˜nЩm­ЊQ)%ыЩBjЖЮzY–нЊжnЎMšЄxКoL^*‹щ%БŠНБ ›р™›ЅжUьЅБЄiŠa0єЪЂУоTŒЕ‡я‹bi›p§dYŽ$MТЖѓ‹БАЅяћрђn№ШюєЅБ4Mы8бжS љтX|mЛJЉpŽŠМ№vЇЖ†yA,аД–­!…ЅќС!$^ђyB,m‹YЯсмфynеТЕ~q,  i,+G7?НСхщ.ы іœѕ-ЏГоOkйж5Йcй '†АыJОыTўl{ххtцрrЊПѓупџюїШЅgš&dYjе2м…ДЈб,Dыр ГаГЦщxrjŽф­І+Ъйі8Ц$iŠYkДmу]Ш д„,ЯНŸ№ъД‹§Z я{GЏ;:іA‚<ЫЏиёя{zЛdЁf+t„ѓЂ(Ќ+и4† щKБt]0Тd;MSфYЕю%Fqаъ)šІІiDп[ЏaЛЕW^сзњъѓФOb!њЎЌœОя‘eR/ПЫі8жЪб&./6;Q П(aЕ бu]`… ƒUŸNdтЮН(ЫВвlк‹b8›JŠ_ЊТѕК8д…3BQА j+Т`yн{БЌбi–ЁэOцЬlEC+gTe@ўлЯА]KгX† 1И\.xѓг[Ь“ ;fо#ИН Нxы<ѓїѕ1E№0х(‹Ўўхkя{Ъощ§кuyM”Нy§Цтъ•eџєЧ?сќљ’ŽПЪМ€жѓšR†(D_nKыQзЕ—њБ[ЧqBžЙJeмp6WVЌ.ŠОя<С|ЙЙ‰,” т/O№ѕ–(MS(e}_OЧR1MЃU9I’]ў(М›ŒЋB§M!H„*r њ…љn,X—ИF›Иbx‡‡њWЉH!ЏК1'›йиd!$ŠмХЂF$‘~a|Sя­K–І†ƒUFФVССq‘Н>уђ{X}FЯ>щЛ‰LPdEа†L“ФёСзќ^ойЖІЉM\г4сtтПџ—UХЉеДcA‚ЧqDšeШѓУ0p“H’›žИСGл5,„Ml р™dEЮlиыєљоGZ}6ЧОЏїxF.7д&/?!V8FfFзЖьЧд>Я3У6йй]XМJKš“$С4Žмvэ—ЦУ8 АоС< „-/ ІlЯЧhnЛ–“$‰{cA@3ЯsжZУАYvƒŸЊ…U№0 ь’шJwœFTUХR Kму Чtэ юКжŠE,НTžчТ%ucЅЩ"КиЪ—išrзuЧбBgЂ^ъ8Ž8Žьx­ьbbх)Ѕ„žњОѓэ–№J)HЛНgmuyЦТЬœ&)ЗmУГšq<O№Y>€ћZJQA9PЦЌИшФУаy }lVl[ i˜ъЎЇЈЋžзM Ѓ G6Ц0`иzŸ\2‹…ƒ§u'єiНк‘ѕ`vРi'ђСI’ђZ8xaMјѓ™&м.GэkšЏzЫ}7BHщЉtМ_IАИЩ-tкХC|E[uнТёb7КшНNpkJЗьR /(Qщ‹х+…2йnoПџнїPЃZ)oј-ЉWЈ ВHї-Ъ€н&Ь mзсx<Ў^CDЦбъюЙ­гžІџOШї=ВР„0кХТN-CИЊРlПmD7и­тђГёoC‡q“0Ђ^І:mТ4ю­xXŒ%юWQд‹“RbэW\ёКxbћUђŠzSzžбЙ^ІМьХbи„M\јує}Щ PЖЭјСО=)?ЎММЅf’$˜•M\>юxћцAЧЖQЏWБlлОƒšчPхФ,їќhЋРiк‰…Уyњсf,ž•с•VМKаFщMзBƒуhЯЬНъЙбыфЕ‰eLУИв єЏЕЎ‡bЋ4О:†Ѕnl_u Д7Ц`RЪnѓнtxЫ2јшэЃЊ@b-” ЦчOŸёўэ КсЋLџЦкM)Ћ-7РhЇ–[з596mшTсB™&e…6Хвјї72‘ТPхdюc5\џДJ$9yП ,ђ“ьКБНL/* ЌДC‰йіЈЊВСƒŠm•{vњzюw"V?Ш­mžлЪ.’—ВL щ&йmГ[Yћ€ќMхЮ!ЭѓLё9""ъћžРŠй› išЈШKr’N!N?ЩVJЁыЛMeX9”lПLл!„Хд…‡Ob]з@.С™АеWFг4Ё,* лStчиOВЧqФ0 ~ ДЂЕ{ё­Е3нЊ -Pš6БPпїЖЧ[РO~(т‡aVhЖ"rŽзХ‘hš&OЇэ–VbН†QUЭV8˜м†œЊ3u]‡DJyБ<ќЋїГЊч03iНŠ…’ФIЦЭK•П#G№єј„wЏпCЯ‚Ф&ЉGrQhЋПЏv›ДvХхkТxkГI€иPЛx5с`^FзХХs`н\,о‚дTдє§-ё_ўќWўјў#GvўЬјХUJA Сy–ГЖ%|x ƒљrЙ ( ЮkЬЅW™ у€В,йТbFТі „ юћl —e АsdцБШnЭЪz\dЯzŽЅЁиАсКЎ= Š“†kЖї8\Uл„8Вї  "юКŽ ,Ъа‡[ї?эчR`оaМ IDATЪїˆ2žgЕx?HЩZk4MƒУсРЮ{іZrШм!Ь`vћюКŽ‰EС+Ю‚џ„я)В‚MYЭA2‹“$a5+nл–Ч#ЏЗЧМъh33OJёсxdc4+ЅмљБЂEmзB ЩВ*H?цы2Я Y–q’HotЕєUЇ‰н${u>ЌРШl ^1Ч#Яzцйni§uŠЎы8‘‰—†ŠЮ‘-0ќ5Є”т<Ый›EБ№0< NЧGŠ#ЋИ"‹>kLњGзЕœ&)чЖЧыћ-ь6mЁ;Я EžГ—ШђišЂы;žœ­j$њАђ!В}lc v>$б57Ѓя;ЮГ išrd<6~ •Rxїі=?~zєз~dTх-|и{Ж№Юџ+}‡Ћh““˜у\ЕЄ4A{№”mЙ—pL[X`Hy4a6(jіЪ4‹XфггјюHdъŸ~ћлz/В8њ^‹н6$2 ЪНо[СЋ_m\ьž ^ф9Д6аŽцУ~лZUkН4\i;й~е0 *d‰№I’Е\!ХJ-—hу„їeыРћSјъ/†р| ИjџЖВє=КЦКLйюКD‚ЇBИ~еlЗY‰”˜g…ЖkWе_ЄшtеGІ ЧУllEъciл&јVЌ&ю˜‰kќC ЬMЙЕжЧCpV˜›X|Гџx<ј‡mЛYž…P|Žh#:`M”$В4 Б„ъoЂXі”азUгёx‚wьГ mЖm‘ыoьМOlgЗžЉ‹ХWХУN_5Оцтуh­1kTЯ§чQЎЏїxwЬИЭРY!M2$Щт˜&‰ѓЖЧпŠб^н8ŸЯxћѓ{LЃнI]ЃbЉшŽЄЕŽдrі6яrЎН.Н+мО oћ‹Ыњђ^БЉ{0уХыnёxо]&’ |ї_пQгДt:bПн]ŸVз—!­5•EiЫoЃ­7iгPY–Hв”Œ6З|gz*ЫЪ*чЮŠЄLшrЙP"/ѕ^у8~8CХЂБgŸФmч”g$иь~žИзqщPUpл%H!P;_7р јЊ_тzDTEшi­ЉяœђŒфbйѓв Ÿsœ&:TbfGпЊ/5хYю{™{ОРЋ~Ё7Ѕђzі{І>ИбŽ”mСвгЄшhћjAг­Љƒe'}€K.yPЭjvœху0бЖИч ьжжї>НO3Œжд6 ЊВЂ$Iv§„cПх…-(ЯsЈIЙю@ГRіЋѓМZkk.ЅЁgус PГšбvіšѓэgМ|УРЬУЈ|Яwp’Y~sУГ9єВо%Žыaэ”КЎGUUїзї|Ž}лCо їя>ЎкW Qn#l/bЄС2u=Ђ’–9dтаƒёђYWВ6nкТD&‘џяџќOќќуkМzѕ ЋДCП^4Љу€Жmƒ~pwяБlД#гГ+ @дЬOч'КдG7L 6М’Rк<№OwтцІЉiG~xx@šfфЄ”шЫљТОЌШ ўќјйo7i3(и•ьŠ3žЌ”тКЉЁ”ЂWH’tmŒО/2Аz@хYŽŸ?rпїt:р*ю[p[іБLгDујОzѕŠЅа‹і-ЎSЈШЄ”œІ)}ќју4тt:q‘З Ў”<тu™І u}ac Нzѕ DЦ0яьЮvišЄ‰Ф‡xV3юNє|уzЛК^„ H!yœ&Њы  WЏ^1@dLТ№3и/г!>|ј@кhмюeЧCНч‡з…ˆР0ŽTзBаУУЋј7…BЂu тя?МО;нб ИђЫЕыQ"ќЧџіПр§џ€a •ЅТZФЂ]eдZ[У}^ eн6—ЙГ“ь.їMF ЙIЪЕN_ EЏ˜VЖЯГF–х$Ќ!7n\Œ7PK„aШSжв$]љ ?ЫуймЈDDzжlзVlз…ОДОЬlБ‰d%ЗМG№3ЪnlўѕѓЌQф%ЂйmЯ_zfвK_ˆхйЯх“ЗmŸT`Ж›žЃPyiЃ!H T^Хђ%№+ДfЩNоЬ8ш Гй;G7зH)ХBˆРrp'КБЖЛчGk vН>c˜ЊЊ Вb7ДGwэRТ2wєъ Ж=Х~ъёЯќˆъXт_~ћ0ыАЇ |Нџ91oэЈЎr4AцHuЃЙЕїљi‘яЛ.|)плУІХяПћšІХёЬчљ&ђжкeзЄ6ЦчЎ—hЏХƒ˜ІmfДm )ˆ^K| Ы4єЁ1§вm„}л ЈiРлJљRЙU,§аl§_1ПH§УWD„Іm „@]зП8 +`Ѕ^Ah^rO“ъКТЩУ'Iђтыd/–КЉ­ну/ŽХЎcзwHd‚ѓхќ‹cёЧ№\ђK}A5WПшкAђ}п#ISœЯџНX†ap–Ї„ЫхВrб{щqь”]ctXнЇѓ™ќТcицёёwїwјцWп@Jaa}|Kгv'7зТ-ј™іsZТ \&–RиJ(№В^)>G–›tsgCТnE~џншэыwќ›_џšКОcЯƒ|&y­ЖWіЩG4 ='мNЈ›кrэ6›ЛвшГєЛ„ЮOg6FулoПE}ЉСevt[ЮеNЯŽю~шqwгёШЮП•ЄLЫф•ˆШг’Д]тJ“„ŸОўњ+\ЮV-У5ћї4рvЗ‹ўІјъс+ШфР]зQšІ{[ш'ћyЄ”xќќ™Є”ќъЋW8ŸЯBФI}OQ;оъN?єєѕW_3Q…~шУЄЗАБнЂ}њќ Y–уўўOч'/щДнюэн%ф &4Љ‰ћЁЧЗп| ­5MгФyš^щ7юдТG|ќє‰ЊВтуёˆѓхLBJ–$^ды lqР0єјіл_A)Ѕђ<kъ[Т—tЭBс#>}ФщxBY–И\.–R†—d‹{щ: у€_}ћ+ŒуHГž9ЯrzfMУЙђ[q6ѕ#юя(Ы2Ўыš’<ЙѕЛЛч(M­ЦЄ&К<]јёг#О§ЭЗ€1ˆЧWrX{ Џ`qmVP@пІ[2Ін" ZCњh­ЌЪ1ЬачЩ•дњїb_ЄХб+MRњ№с#§№—ПA дzv[„Кgsм’v№`bJ’„.u ”eхYF‚ˆцY­рл7Иyaв4ЯУ8P–Є$…ЄВВ"šк9…EБlёAЅŸ$vzь’ђ,'ШJc… ŸдDnЫНrBšgŒуDiКФЂ”ЂHHѓІ\3“ !]ъ I)ЩЧТцжЯJћ,т’DJM4Z ыЎVVфT€щЦV3vЁ#З5ГSuЫaЅ"ЯЩCЦЊbпкЮG.tDBЇ‰”šэКHIUYaG?ОЕн “w)%pSѕ”’$!Ћ_8“1кIђоцpњ‰Їc‘ž5в$Ё$IЈ, LуГ nўяPФЬT7 ЅYцcЁYЯ1ѓЩВ–Рд=91˜@DD§8Рh/G–Ђ(rЫJњВTŠOФЄЕІІm(ЯrKž{їР›kŸчЫаSА Ш2Ъѓ ЃsщУѓВRфY9ѓЌЈmZ:шКŽоўќŽ†n€2Кv7Оkьў_<Ž,Фач*Žђ™'p”Л–ЏЧ?b aœdй'Ђ€єухH~вМЕТљЪЬпџю{О\jм?мѓ0 Шв,€CGЗ`а4–‚•RЮ…Ь‚•ЧiтуёФnš“љЗВqƒž/—3ЄLИЌ*‡ѓ4c)DpођБX%‘Ќыэєyš—NБzR6etУgЧЏпМцњ‘/ЖсЁRJ>ŸЯHг”‹ЂрqЙШr‚М'ёj-# že}ЅР8ŽЌ”В>Жl0ы™‡#&+эО|žыЯ>‚Яч3EМeѓ,c"№Ќчр:шЌПЫyА“#уYЯ\•ДжЌсусРу8зД0Бdо‚Y{].g.Ы’Ѕ”Lь.Мk\tmФчм—Lј> аЦpUV<+ ZЏЦб/ціwБYffО\.|8@@ьЬХ9ђ†оЛv8šˆИя{fУ\•ЋЩМЋЊbЇЅ<|ыКцŸ~њ‰пМy‚ZД1\_.ЈЕЮxБgіXТ0Ќя{РeYђ4Ž,ЌG<љеКlяЇ$IxV3зMУЇг[‹Уy–CлXBJyuŽќ}аѕ=“ .Ыкh~їі=|џ) \§хт‰!„•Т2 vyk"Зž зvsр€ьы=ѓsюSt`М†жЌрƒШВ џќчOјч?~DщЌЕж4ы4V$яgzu]CT‘T7’4Л- %QпћPjVŽГƒЂ'ЬЉŸ=Pјbžm{RсЛ^ЉьœБ”B}ЉQ_ъ•V"%њЎЗiЮЪбe-tп#ƒvAтu]#K3”EЙv“д ‹bђћ5Је‹xі]cEQ†И­$’еU4FяФW@рЫх‚"/,Р;€‡ГЫњњЂы^ІАк…Oш1f`ЖКŠгˆ§ЮРђЧ'Ых‚В,­€žaДAžцn@ЏC|š%:ДЯѓBHLЎ‡WA”іЙ?цв4 ЊЊВШѓЩгeџУ€Ых‚њR‡ћBаЖ-„”.dЬЊЊ ЂД_К‡"†PИVББД‚ЄэнGОgпvmBѓ:œ‰З;u‰єЙk7PлnСщћo_ПCлД3ўхбaеќл›Sаu~sb Ц9ˆ!„žEWЦЂЯЯлВ8ЂЏЗР2•4Œ#§ёћ?виt:и8ИŠƒ­Pžч4Я*P#d[К2Nг”ћО'ЅUeх!&ФЬЄ”"чГKЮь:1ЏˆиŽ­AI’xя""rMЮГœМ49R(-§r'ŽкЖe+"Pвт'lhžg:Ž<ЯŠДбфќHјзПњ5§њ7ПІЛг9КˆšІІ4MЙШ‹`ž=Я3I))Ы2Vj БА§ЧБ‹ЦЊЪ*^чy&m4ŸGVJХFуЮyйv›ІЁ<Я)Ы2БhM2‘”Ѕ™75wи„`m€­RJjл-ŠТїГТzhvMтеч!!Ш8p{Qф8й>ЛэKRRJХ•z ЮњЕB iBzш !hRждъp8А3šћo ,„ m ЕmKЅ=ƒйлsФišR"šœХrэ2E-BP]з$ЅЄ<Я—XІ‘„t(+ Œ1twКЃпќц7єЋ_§ мюЉkдuE !ffžЕц,ЫHHAŽ:ИКтідuMišRžйXЌщ§Ф2‘T–%урСлЖBhл(E}пгсp№яХ–ЌaлR‚•Rѓѕe…m pг4”eЅЎ'ы[Ÿ>}Т‡ЗXkMBЦ‚n}ЏХќxжХLчЅх{ЫиЁYX8чtпKф€ГБ[ЎЅ)Ќ“т­npaŠЪS‘gYЮ?ќхўљЧз|К;Й ,ПбЙLёЁЊь‰t[XwE7˜Љk[N’„§EсЫc­53€Њ(Y)ХТXылЎ…жš№8мDZkVJбЁЊиЧВJФўщКŽв,ѓfи.ЙТы§QY”<Љ‰ЕжœІ)}ѓЭ7ќы_§š‹Ђ`ЇТuSГѓЁЊ(nh­yžg>X;DіŸеЏGќZ6†ЛЎCžчœfiа]sœM"!Ј( ЇqщІ/kBd:зuM bgњDўјѓ<Гб†Uх9 Ы…КФ"bЃ5[у‚Wж”dyТRJЮѓœ}П*К9ТyHЄДm)Й,J6цЙЃЬŒЊЊи'е№ћ›uqТ \–9#ћ№™•еиЃ,ЫxœЦјfУA‚Є”|Й\8БЈ`юb!qUкk.Nъ]џDфл6l5e Ojт4M)MSЇи=0љ›oОA’$ь:ыšГ,уˆТчщDB.‹’ЗZЗЂ І >–РНwЗЄRŠ3зŸЧq{­О/Од.rkтю“Ÿoї$Rr‘ч4MуЖЕЕœ#"Ч‘ІiтУсРVАЩ^/d†з?ПЅЫЙцФНЇ§нh Ьб˜#|р’ЋЂзEЏ ш<{?эPсxЗйН8…,o~/ўYžgєєјHўуŸ„NIБН‘бšД1t8œ|уѕд`XсІmЁєLЧубcЬТгоJ§Ь(ЪвгtBЕчoTgўM}з#Ы2rЂЋсН\ˆИRй4л vJŒІЎЩАЅљз№ЂBѓlЩР–JWH>–yžiшЪѓœ’є:–yž DЈЊE•Ъj_ž& ]ъ 9f@Ј”ќћyfGYVФ†}…‰(ЉCHЋКт4 )I’ј‘ЏрHЊЊŠЦqмЁuкIіљr!!ЪE nHc›§A^ЪЌŒЫ#92šІ‰ЦqЄЂ,ТzEБ@Y (Eк Z–ќ:Ÿ/g;€БfBЋ*HЭŠœМmШѕZ}%ућЭp7(•eIBŠэК`š&Jв”ђМ ~шCИЄt>ŸэРЮ ƒтъаŸчВ(ЩqАэЮЩJХЅ–a0+ecq[ъX’~GЪВŒœX/m‡gRJ"g+BY–беК(E‚„W=зQLЅsjGЄgM…ЅМХ2bфН)/ $IJ›Xрc39шM Цƒ'fЦљ|СЛзяh'’‰ŒsТеnгх&кЈ}„*‘зЏЁхљˆЋQvрё+ `bZћИЃ’њ9е Щњу_јнлїxИПч­а#С8nb–eѕЋ‚bH"lŸ`љšЈfѕVѕZ›Р“єІжОBI“MгРА‰0y‹1˜ЕЛъ'ОIйїЂ†idп›кФТЎђ3ѓс`}@bŽЋЅЎk€РЧХ‘mеР7Ц@ЯкƒХуA‘яВг Dсhg1kФЏ‹э!‡ъ€aVа r=! Ѓ[G6^ Сjƒ<ѓCˆUЅЛAеЌPкўaјЁF‚Їib!$ЊђК_хB—Ы™г$хВ(Џ†D„йhлЇŒ„`уЗ’ћО‡š—Оj$vР.+!.ŠџŸД7{’$IЮќ>ѕћˆШЬЊъЙЬ…%їOЄџzŠPјFЪrЙ"‹С`f03гнеG~››™ђСLЭЭ="ГВА it‘цюцjЊњ§ОŠж2;јxyDYœaЎжElNЋВл­!Внџ ЦibуCиз[,’эф…Ущ ѕмZоел„v$'ŽЋuёkU–UаŽ^BфыЊŒ˜vtŒeV ЪВD‘<ЭгnF7дUЛ šІFю2ыЃЌ)8•хеUyG€‚ ќО п{]WМ§ъўјю#'BpMXкњ |ьGФћRьO.{TР`гS|>˜М!шлv(mљ5i<Ч QU%Оўњ-ўє‡?Ё*+Nн цЇ$2wЬг8ŸЮЧ#,ЇYŠapCНэЉх!<*УЭY–БxŒD„f^–YМtхop3Ќы Ы–OЇ<ъ'. sп V’{„|д€1Z#Я2Ю>|з=›ч ыЊИЉа>Б х$IиA№щА‘‚Р‰#їr’$м4uмщžGтwvhЈя…аЌЕцкоpќђ з Iy]'IТn#]8n|$IŠЎы9K3hgDхй}І1šЅhл9KSŒУcЌ09юЪЇI ЅNГ­№/tš$I@ Ёы:.ŠUщ|pC0њ^мЪфу~F‚б ШsUеСс&Ж$M}YЮsЗaGi@„u]щЊЫK9Š%МьЌБ\–%Ё!ТО!ФB;:†јаШZ–…НЁOгжЬaк˜ЛОGэ`qЏ Ќ4DЪ` .K%Dќ=W–еЎћˆ…=RŸ‹МрyžЮЬ•x,њОчЦ‘—8z–w%+"зєљњЫЗ‡™}Cd{‰‡ыбR)&Ш>EћН ZˆЪ/QŽЭ7>t‹dptY~їOПЧЧqwОлu“Žnlђ&O—~Kї1`‡дьH"”\uуk­aЌ#ZШgШлoЧxуКљїc4TЙЗ‰Щб4OPЋBлДЛЮсбЫГ4Ю K>CўLя§bE4Оeп9[ЕC ya‡мЋіК”кХт]МšІйХ" У€,Ыр3ЎЋППƒxЄTqE–eшЧ†ЭNI@ ›Б(ЅЄ dІЯm$юNюЧEQЏсЇІJЃШН“œR!›э=Ю]ДдЧЬ ŽeQKР…Эѓ ’,ьc)ƒюMA„†*Ы2tЋХЗы;PBAK}ыяЫПчevY 3Нп\ й[еЂ(іїЫaР`4ЊВ з]$sЂш‰7бЇbYŸo"&гУ4ЂЉd^Ўy+Т &˜Š­*(ŠКО “],7\цVНт›ЗптЛoОƒЕ&рѕAЧ5нюBH№Ўp’Р„32Fyc њ*blƒД eA§ы—јЫŸў‚ЛЛ{PBЯyеQ’$RЏЂІmШЇЭDD4NcМY<9ь)ѕ Сћ4ѓіДфХєtCчИд”ZЁЕMл6:”LЧqЄ4KƒIѕКчpC­ыJišR]W˜ч™ŠМ išШшьАXЗƒЅV(є€1gа8RуЂ'b‰ѕ–Єж•ђLАeQа0 ю…q:эln­tўNS№Юxi,Оk‰В,0M3†qІ‡\шO"Ђd]ŠТeщjUNc 'šO­mˆЅmQ–цeA? ˜ц)иЊО4–б;н­ыъ)8юў—_{i,mг , (Е:LлВрю|žГЯЙw1X–~vЖШ‹PC§ф3m о}џoПњПhjЄYkl\ŽCЄлk5§fџžЈ†ƒиwь#Dоў@H§іh(а™}§—e‰?ќўŸщПќпџ/пяФЕь“d8дёЧЧdŒA–fЮњ№гМДx& yžГбпП8ьPppЛЁ!Нњ5k-KFѓјјЦZЮГœМнр•fє†ž4LxЋM|џю’4СЋ‡Wˆ4ЗДЦ˘ЌЕ,€бЧЫ#YkQф{ѓ<ЁЋН…у<ЯI)ХяоПЃ<Яqџ”rєцѕВжЂЊjЧЂЛ<Т2sY”ЮѓТЩЫžњIЉ$ ђ,УМ,xїўЪВфћЛћ#х)$YˆХ›™sзwФЬ\••Ьw~н% ‹$IШxїс=šІ >СŸБ.\з ІiD?єФЬ\W5U•ЋCо@Н]ЁЩ|,œg9†q ? m[gќnOhkЏт’—@? Чž™AMнћгњ­X8MSЪв }псухчг‰ліпsЯ^YПКЎбѕ&?мо6-ћ.є'ŸgЙFжZ:ŸЯќŸџ—џ™^ПyХr2кKЫЏ$ъ;V4В'T€LFiяwDсЯHC$ќџБœИХ1—eЫх‚Пќщ/\фEш№fђш)ЁЕЇF’RмяТбє ёњ-Ёvр}љК"чYО п`Єс~PлqSBžd8ŸЯwц>‰Ј№ќ68"-ЇY‚ІjрGM˜ž†њьОЇМ§™-(Š4чѓщ ЫVфDO‰жщ(`—OMг”›КІ,ЭБ љ6ѕъо PІ…tељЪX§іgШ &ќF–eмдMh 4 IDATШDЃuС3з™JB—›(‹JŽаьuРќ‚ЕAPjч9еU<ŒщVфЦчКу%NˆZ•5šІaБ9Р•ЌЗ~"П_.\,œ&)~щ(Фхj‘ю›І‘Ўњ-e?—ССEwјqƒЛљdBрbqЧќSгrYV№Ун„лХ[%%†џђЧсВ*аЖ­їЕNЂ<n№./dкР.~ƒt@дЇPX;ˆрQbВI9eSјн?§џѕџљowww/JЗїФŒZЏО6TТXЧќ{Сч№ЁN€yGчDж4/Ž%vлRJ97_€ікє‚Lr?>Ц8ŒNBU7СЃуsbY–ZЏђќ xђЩxм(ƒХ0ŒHўѓХ4тЭ›зy+‹Ÿ„”>ЂqžАГIJоћx@žхЁЖњвkь6O—ЙЙИЎbyёїJ‡œ‡…ыЄ~жgH,у8€м ЬыѓйБhqœœ—uyѕ}žƒІ†цд0№  ˆрЦщх“БЌ>–Њ,_’Йн”ƒ о’Љ§œkqCёП§яџ+~љы_8:v bŠaŽ@VЧѓБД3ќ{6Л^‘эЛU16*р™!".Š‚Оњђ+ў‡џі8Е-eyЦ‹ZЄіЂ#А—vaGN(ЅІipёкп—ђшфPJёууo^Пfc -jEэ;`/9ЇiЪY–‘АљъКцЎя)z›г3й`Ы–ŠМФМLшК_М~ƒU;’яЦНшœЅ)Їi†‹ъPљ#…d9/zЃ{ьуHУ0№oоаМ,аZsѕ—lЂD™я *4UВ*ЙJ“єVfќфИ( є}yžёцЭLгФZыxќф=#йE)>З-ЅyЮг8"MSz‚1yѓ\]К VЕт‹7_РЋ…žкoЦ”8a>”RИЛЛ#JˆчyІ4Mу,§“GрЂ(јёёЦњт‹/а}МЉѓK6Œ$IР–ЁдŠWї,ўФišOROНќ”>|ќс_|цh‹МzќГGр­ЉhЁVзb Ѕ5giJіePQqбїяОgXаћя>№єCЊ›jз_„,Т|ГQŽПoƒnŸKnцфбцЗk›Хњ2Aд.;љ§яў€ОP7ЕїKрpЄљдџќ—„^5цyBQК.aу§dќдgrž'@Q–ЈЪ рm8єSџ“БхdC!#q‰Ч’f,цiƒЁeYТ~n,Ю„ЋЯDЅ Б,K-јt,)иZЬ~ИЕЌ*џ€›нˆЮKжežfНЂ(н(C‘чXдŽW/ЙFж,‹[ЫЊЊPњ,чЅытю—ТŒA^ЈНЃ™њЬXŒбXцХ‰”Oљвu‘X†qkдTЮ‡DЦ?^KžeўєГ KнxNсну>+–,Ч0є y‘OiЪНt]жU…†•ИХэ|w^‹dzCп!M\-\FЅV?іівЯPjбю:ќ№пН§ЮеLгфјVМљж=юd2м клbn:;\уА(4H6-‰˜}љхWќ/њ ююя`œОuн`vsDahђшп)џ!тю~шЙ( dYЮЦ‰М‚tЏЎА:8 zв4eБOМЛЛѓsg)We%SіР…zЫkG1є=Ы|—5eQ0€ŠGšЫЭф%ЪИв4хe^xYмннёМ,оЛЕt˜#з”с§екЃ™В,cmœ1Йаk|ƒт9{Œ%ЮnBA;ЫœBbuі‰г4qY”Шѓѓ2 rщжg„k”ek­1N#WU xРkY:’šнВЩэЧяcIS?Чhp>Ÿ1Ž#Welozдrя>#Ы2Јеuжko”†WX–aтУН"с№‹юx6€™љt:ё8MСИhё^ЭœSМЖБП/–ys"€Ђ(йh#IРnтХ•ЯLв}я†ž›Ісqб4-ЫXŠ(1тOйџЗƒ‹NѓФ‹Rмд5;ЏтТyX‡„$К_љ8Ч+™[пѕШв”ыКцišјдЖсž“Енжцpгј{wš&^ЕцКЊйƒ$IИ( x]љёРзzв(0 е#xB  `mjQЦ~ѓпџ‘РDrЊWrТъœ–eёSzлT@Kпа6MCiš’?B Ў,jСеМа6‹˜жZrгї5Щќ•жšŠВBšІЄ”Š;rФ‡ЯцВ,Єж•М•#ІyЂ,ЫЉЌ*,ЫВгGУЖє˜g9cЈz™Пђ:aCUU"Е.ЂЅ 7X§mЧšЇ™V­…Мсf ‹‚ŠЂ$Ѕ–˜~sА0ѕCZз•њq Ц‘D\,Ц9 n6/‚влСXSšІ)уcLА•œч™ЪВ strмc1­с k­Ѕ"/H-ndEжжZKЦЊъšрщ>;@.cgы(3’г8‚™бЖЮЪs^fЊЊšв4#ПaШыžѓeфŽrNЋ:Я „jbŒёнek-щ(_ќ‰c їю8Ž"MіЂм}С ‚Нg|Я‘Пяђ<Їiš ”Ђ8ЖLЂЎИE”ььєдr# У@IšRэ`ў™j‰@2Нijљ\ф†a Uk4MEь:КЄОвь‹ј(Оїа0 ИZзЄЕіпяDЬ4ћAзfєЖ{žхдcнw№Д˜ї>вл/п’ѕД"`џѓwjИнsС‘~ю оЇS„Ћjv dY 0№/њ ћѕЗhл–Ээ[И.KGёиiHh‡їќјјˆКЎCБж ЮQ–…ЫTšMF4NбИбhš&HЉ”Ÿ+KQv ’СaKžчlŒAзuќ}}#h]ЊВBše<ћi§C,›6!ž'Gю• € >(ђJ­kA›фKМEжuEпї|:$ёГt+зuJR,^}pЬњуcгфfфиgKЁ;ч”юИГџ{єГkТŒуˆгщДыЎЋr‹ )Wloqь%‡ `œœTЌ§КpY”œІ{dUШДтыT9г›Є‡lDы•н1 A•Ї›\ЫU~ІqbJh‹Rў:' дКF:Qо=RЫЧЫВ№љ|цЈуЮZЏh['пUFД.БЄЬЮћ$MSЎЊjЛЮЋr™ПлМиg4Зf9Яs УрВќг)Рb]iС m[a_ђv8^ѕЩ4MœхYЈƒ2;ЃћКЊAN"фzQ,=GшКЦ7Цщэ­5мЖ-VЯŠ”{ї8шCIуhGьJ?[k‘$ +ЕрлЗптућG(Кџщpњињ b,gљНфJVq †@W3eШѓёџ№[WƒЉЋхУеЯ\н+"ЭюdDDJТŽ3Б‡)PЅЏ…ь…еQЃжяћкюЬ€М 5*/яy.–№€њљЅ8w;РЋ9РWwБx“—nшp:vf@В..+LЏ6ѕh `šІ ЏшъkKE^РшuЇC>nJ) У€ѓљ|ѕg”Rhъ6Р5“h”%ІЫ€­Шуp]з€g?BFcјEQ˜—г4с|>пvцщф› q#уЫ4MaИ7ў9ыЊчYИив“}C(Я1N#f5_I5™jU8ЕgџрoБФз;ѕ‰ЕжУOJ$brŒ%dyŽЎЛ8IщЉ УзђRМ\№е—oIќS~Тtm’Ї­Д?sTхуƒйЫО<$J†пџіїш/=Z_hХкRМ•›tWˆГ?cбѕ#д ЋE'щ Ч LЧ,0Кс†a@šЄЎщБе{„–јдVоŒХhƒaЎ6.љœy^‚ |žч>9žGЫŒ{gМЉШЛѕ]еŠaМНq 9ИijЄЉ(Ž7Œ“й8†єV,I’И‘ЃУ &< Y†E-˜Їйm\є”r E’„,№*™yqх5Мj4IЏšЧЭbžg,j cVћА, ЇіРЦЫ5кm\Ц`ђc&Yž]Х"Єё"kcЩВ гшєсЗ†ѕХАоm$МЃžк‘пЈчiBщ ŒC,F Z››ўЬЎёсКзKќГ„К~>ŸЁжзБЄiъxЖЃmКg7њ$ЭЃ›Бф9њЁ{‚wМ.a#ечГЃžkЃЏtуišA- ”RЈЋ^6З5GќЈаїп~wпОwi[B7 vQјА Ъя%Яј–\§š<фY–слoПУџ№GпХ+wМxѓ*ђeYICdЗYœ ЄHЃт‹ПO›-ŠВBХns з8юВПјТЙ cBY•W›з‹sЉЯГыЭBтЖж€=-ƒ™ЁЭ!–$ХКЎ˜|+ўљq8/3*ЏЭŒ7 fFъ?Џя{ИFEўD,6М`фЭz\‘žNЇиzЗ™ЮЫŒІi„З‹%KSk1єCXЛxГXфэь‡\w/Л Бx„гUжpмHAлVюз…д†UЕ!РЎжХ™RЁєвAcї…ЁрЌыЋ,Ў6ўE-hO'0ЖЭ+дUГ z]1y8@МfёІ`Œ№—e‰е[ИЦ?GBЦ8ˆЧ“ЎГZ}„Бf‡ЯЗlC—tžчOЦ"/ЛuНЖˆB!–ыЄFcе+ЮЇ3єжрйІВЬН –%Фr|žхЙIгyQ<KO˜Ж],ўиы"‡IL”АŸ.{Ю2šфдHi ўoxћ#ŽŽx+~ѓџЕЌhкцЊ&!ЁeˆП@ %XЕыф‰`ћЂWЈEŽЂ,рЩЙЁ[%ьЖЂ,бrcqЖЛ‘рЈ3u]žPbёГm.ЋjC'ЅлЖЯHЫP‹3H†уЅ7‹X3-ы#БШ&БP’`YœgЋlЂсЮИк0ЊЊBšЅžŠB;яиЎяPеGёŠЪу0c ?НЎ›Ws"*zuSœAcQюhЎГ—ц~”Ѓы;Фuе[ыbЌБ.ё|‘Ьѓѕ`ŒA ЖЏЫб>џWюъЊУ8 =ЕЛЭт‹М`D[lЌнŽNI‚qaЌнSpbоЅП ќ5GŠЕ`œFДЇvїwЏжХaљв‘млфiG о1 €1Цmњ8qŒežgЬЫМ{AяW–rИ3Qьh.ž0”#ўь7ќ§q]kS{ђ,Яэ%UЦitфЅЖН)`ˆ_пџпН§n'ШИ*ўіГАп1#.ДlЎpnaуo/HlЄiЧЫПё—џў%зu,ЭIЛ‚”LОЌRŠГ,EY”Мъ5јћRBќxЙpсађˆPс2†И“")ЅИЉN]НŠн)ёМЬМЎ+ъКЬїБA!зЩr.ŠЪЧ0п— ЊКBQQ‹> vыБЎ+šІa"';’yМiž Е–ЗпЦТ#ьИƒ"WЪѓ‚ѓ,7’Œ]КŽыІ^к•,(чaХbffЕ*ёпхЩЕ7ustУŽшc)Š’г,хUЋ0dŒсЎяX\ЁиЭ‚и ™—X x9šСЊ•GКƒЧi”Э„їЧp>ЦтНr+NвЋ^йZЫyžѓЊ5њЁG{jљкd}‹ЯBЙm[h­YkЭтя;Œ˜ЊР-Є# з|дUХ”kНТКЭ‚•RЦ‘OЇGc^›є“(4Lќ †EОeŒaџВфqєДЃЊц]ЦF[W->ТжU H дU4aч ЖбЉJ ‚iѓВA03†qиеUЃ{-fMВЕЮВСЏkумі пœZдТ'—9яœёbYЌ€ƒpлДМ, [kНѓсqУЉPј ђѓуІšwўуЗ_ƒЧ'ЉлWіЯJ`Ц7 „c˜ьtЧЂсžр,Ч‡пќїЗZылЌ­]ч№:iъŽ|Y–Й7Ј1hъцf3сИѓЏы ŠъUюg$x|М Ўj”ЙŸм'м|[\еЋ|—YŽ!–­ыrХ šЈp|H“$EQ”Ё†С.—хЉOЇCж7!ф-'ƒЊуш ЋbЗљдКљ…yQ@ы5dЏ]з!2айIИq-‰№нCWЏвўИ4ŒЦYV/Ž%ЯrdyŽuu5=­ йЎЎКщ(ЏжE џrЬ’к8ИєV,єT,yсВ>ЯUTjХ8ށАџхЋ& Сg*gявЧ6јДК:jтpПФ…џ4KУ-‹›W=ŸЮ7›„=ЇOшЪчгЫЂ\FeэЎЦЛ+ <Ыъ›i5г–нп;\#З.&œЕж~ |ŠюEŸA‚Џ.ёП?Мџ€oОўFЄIrхєї”ЄƒШФN“@Ф~ЃмћЙ™З$I`йт7џ№јіэwtwОCфя@ЯР ШПѕЈtƒЗЄ”Тхё‘JЙїНє -+MгˆЊЊœХ2ЛЩtWcяТ†'ћўпжZЪв e^B­Š”ZpЙ<ТЯШ‰Т­vпkš'ђGvV›+ˆ›O{nЂu н\ЏŽИ\.t>Ÿ›o{@ют›ц‰šКСEЏ<Э3–yІ„’РЂ‹-žК+”R$ъЖг4QпuŽщeЯT˜™]MБХхђˆižhš&diЦyЖ‹хцКЪБfQ 9ЂpNЎ‘3`z~xx3H,O нБ‘ˆmгвхrС4O<єђ,'iтн0пЌЕ~Ћє :‹ahGМzѕšи“љšЮyЋgЕмuч‰ћЎ#я-зљЩ{V6 бЅчifƒ~˜0O^П~Џ-=ХXдJMгЂя;šІ‰‡>BЃй'з…уљвeYИШ Ъ| ~zЈeСызoœП лЇю“гВЬ”$5ъКС0іЧ‘Цa@UV,›jtЯ]IіdЃ›ч_§ѕ+К8у‡?ў!ХМӘшЭџФNР8і— Ыюџ№-ЎSžчИ<^№ўџцЩi8_тчяњu]уryєG>~NUn|Нqpsis-ЃУgммHхh^U?~Ф0Шѓ"4P^"в–vЊŽ™ЧyЂiq>п!`с_€H“eUтУ‡ї˜І EQю(/]лВ(§˜ЩШг4б§§Уgф^оxyv2РЯŒ…“$Ё"/0ЭnVož'<<Мњ,0€tHѓ<ЧЛwяdтЖmщЅP ЙЮEžcєБ,ѓ‚зЏ^#ЭвЯŠХГ$xїюзOWŸиЕЭвгЛњn'Яѓ‚г4ЃЛЛЛРЙ я3й[<+IUUБtZн8‹eп1SуgDпœR'UCY–ё~>пИ˜с.њ8M8Юьчл(ŠхfF|ыПsНППП—#є-<аЭ_ѓƒдЈЪу8тюю.о,žњйЗёND”чkm№№№p|)мТ‰бaлХR•4M#пп? u2ЊЇ2H~jѓž"3усс!Hё<qџюNЎŠг8тссъІцУквs‰%IЪѓ‚‰<<<аѕвО`Cі/—AОBфZЧЯм'/mX—,Gšfxxxˆ/ѓ“HЉЃLRN&М~§šЪЊфшдђJlлt’ В,'"т‡‡‡рќ‚{7Ќa–ІШѓŒJјюѕC Ђxщ“ЕЬп|ѓ-UmХПќћ_\эK‘•Š Т03(Ѓ'*Cqэ /rќѕпОФ_ўќЏx||„ё&еŸ r3e‹ZpЙ\0 уg|Ч­ѓЫО(Ќж5l ŸKžgЎіЁ<>>"MГџЁXМСаKВШ›БŒг„U)|ќјq7Ш§9БHБмЩѕgeRI‘zеј№сУџ`,ѓќ/AќЬXу8РZЦћїяџcБ€ќ@Г[[qЧћœXв4ё]hќјЧП|ј№сjиўЅY­(’„№ў§чн'ћX\—њ§ћїŸ…i‹ГєqЁж?ќёqwwки\Щ;ќ|У0Дз‚КbЉZщЗџјOњЏ^a^ЪsŠ‹Яж=Ј€–eС<Эє№№€yžЩZ#іy|ƒš{3Ьђ яоНЃІip*+,Ы‚<ЇЇ№PЗ†(MSЬѓЕ,tџ№€iœ БоШOОСˆˆ’4Сћїяљдžќ|•ыТњuљd PbЇыКтсс•гrњYЫOМIc’0QBx|џШNй2FЫ<уЇ[ЛuЗс<<<ар2FѓЉЁЊЖуёђˆћћ{ёЛe?Яј\]hWK’„ћО#0pџ€ОяХŽєSGЃ  ѕхъњЏxU+Œ1$Г•Я\ЩЌ}іGЭvwjЩЭ"VНž­J,Ћ^Йzzѕ№Š—E‘Е–§щ‡>qЯn™ЈWAфyцjgУ Њ%zЊnwЬhНW†q WЏxžцАЁ}тЖЃW=^QzХ—84ŸђЛсИ/oš'<м?а4MЁ;§ЬšвгЉuСхБЃяПљmлQђ™ЕuB dћOІƒRз‰Ѓћ›птЋП~…S{ТљюъНЋ…МbЙЫўф-.ѕ“yžCНч%ožмƒ}-m{‚ZеsќЖЇ3 ЏJhъЦёг–н(Ъ‹bщњ>Œ#TUэk!/‡r†љЖaD]зhšf'3zЩ›]ОOзuюzŠЂФћїя>kmƒŠХы}eўM2ь—дh%–ЧЫcЈKІM†япYиKb‘,z'мпп‡љ7ЁП8яˆц:йісу{ˆ5чKcхШ›7o>;љcЋЫ”’рђіёё#ŠЂxQvKчyЦљќдu->иЛЮыЇjЋ`žfЇœђCх—ЎУщŸЧВ, –yСУ§C№qиЊќEБȘ-цyімЭD яWѓвXD“­ЕСнЙФл/птўсЏПxлЏюпЖтSЬ7Žј1ьrЙрПћgXуќ Dќ~ІŒЮкЛрdБКЎssiMєА2Ў QХУЌёT~˜ŠgЦ0ŒnдТdЧCГЯЅ№ђ9Y–ycr 7P]зАlЗХ ЈЁлпG:aу8oYc4šL†žњР1/— ’4 –ˆMгьОЯn]ЂЯй­‹WТlШ,s54{@'нD ].dy ~šІAŒQЧп‰oЎ‹жг8…1АЋ“њЙГУК№•N7жzeфbMгьОЯ~]ЎcIќД($мHP•UАнўќѕїŠG[д!зНnънXЬsБШƒІ”Т2Л,ЇЪ Рu<ў]цыk-/…ЫхВkРШ=LУЂ{7~cМ”Јrм wE–9й_<и/“„ЬзпI^ —Ыeз€)Ћ‹ZЎяГпGюЙy^АъMнBGZю%ŽьAbлПchHзїnИ=M№сУG|ћѕЗ;сХўEЕT’ыР6—І)~џOПЧwп}‡‡_KQT•e˜В—F 3ƒХ#$ЖT„1†ІyvjВ™EлЖNоГj-уСA'%Ы9Nю3жUхєЖБj  qоi[p­ЕИ›QžчюЁ2ž–ЁWђђ%Š‡УјpdЬВ ўH$ГZkЊЋŠМЪƒŽѓ‡AŸИХBЋ`VUE‚­ ƒЊЋ‚qГ„‚р‰`a ЫВрЖед IvRћьм#ЁЋбŠx]жu…PМГ,#й$кІн6Џxfюp•(q&щ—G*JGЎVJСКX(й‰д)7уйл;*Ѕ‚•ЃЋ!MSoJ Šnхнеђ Ё$СуХЉrќLl-jпx3ЦKе ФДŸ_џрUЏ~Žе•qв$E];yg8NС~єSJ3—Ыu]“'–3PUi/‘“) ФHHюсЅ@ЫВ6uS€…––ДЬ‹ћсGp:dжЬtщ;jк†|FIЁЊ*Š6u’ЉуН+Zмy™I^~SЅ"wЯЗШ^wГ–t(eeД6 В…lђˆxѓE::Ќ‹?Iк№’ZѕŠЗoПЁпи)bіSŸˆ€Ј_GїOY–јі›oёч?ў‹зЧ H€ЁМŽвXГљеnЭ•ЋЃoпї€h }{Zkgnэgя"‰gvP{U˜—eUzкВ+тъеlbDЈрХГ,Ыаѕnу:љvНг&”Eф‰XшЕ)Ѕ D†ц *Цhџ‘Mш8{<*^К в,  3УDУЁцб$Ž%MSЬЫх1ћrЌгZУz1њgл0зсюb)ђЭ›CeYњЁрˆєrcH>MœlNkЪ@­Ћsk[—эФxЋу“‘P6Ve‰Њ(a-{}БEщСуuЁC,I’`š&kB,ЮЋVќKk‘Э+КBЗLд/— šКvѓ‘~MЌЕa:І”H,ёškXXЖС(œЂ!ќКЎw:љ[ї‹\зЎЛ m?Шс^-Ы –-Œb_ ё'”`G€]&,RКeY‚/Ы‘PD‡}A&nИНu РGмчZcЏFСфЅ)й90Nуnа^€#RSœŽфІC,’§ CѓщМ_є]З_}уŽњiњDБ˜АМj"BГЯкјїПћ?^pЯжnŽ]ŽыŸ9КыЊ".x IDAT™­—ЛxЌЋШОВ,уeYx^fЎъ*‡§Q‚WНђЉ=Б1†6оЫgdбЛОc"pлД0ж„?Їц<Ы9kНЩ 4р,Ыxvя\W5h“ЊБ1†Wmp:x]WЖЦ%Nсs’$сЎыЄ 7uУrг13c8ЯsdyЦыКFвЗ= [hЙzu’ЂH&Чкh6ЦђЉ=A)Х^fxEУ)Pз]gзuЭтdЦЬlДё8ЂTШблЋ3сШВŒЧq€б†ЋКоIф;ДmЫЫЂтŸ,ў3СХR%GŒ,ыRЇiТzНкдУwO㔇a`fцЊЎи%†–ЮНh›ЖсјЈ†CхDЎлЅЛАЏЕ…5єЛ ЂvtяЦЇюћžpYUAšцbQHw§=iќH(b:[kЙы.мд {ж%{DЖжВ?MБPЯБАl\}п3%‰ЃGХŸr]Uь7ЏЇb1§аsг6œe[БРВ‹ХŸІ8zGq4‚УИя{N“”‹Ђм~ˆчyAžчьxWыЎQ’ІМъУаѓЉ=™ЅЌ‹@>рЄža?9ьQ.–Ёч4ЫPxbЛЌ™Б†Пћі{~їн{w%ДQЯ…! FB[|сиZUў§пўJљѓ_„мwоШZKjUtjOАli5:b#R2%IBУ8mД\ "Ц:Ъ-QUUЄєJжкi6ДЗнр(ЕM˜m#љЧCЋV“6†bV‰ХЇэвеЄКЎaЗ#™Œ#ЛИXVE–y7Q€ђ"Ч0 фѕЗžЊЛQДжфЩюџwп'ётяоїНЃх:ушнкiГRšЅ(Ы’”;Sдб"”х У@ОЎКћ|"ЂUЛzYлЖЄж•lDЬ•wЌЃaPљYH„uёз(Ыr*Š‚ќ EЕ""dyFнарЈС”эКЙДЎ+ ІiiQъШнпЈбli*Ы’ŠЂ cЗuТЉH@a †У=‘f)uЎйeпиk`Й, пšhэ\'ћё‘3ŸYKVВ‹E9ъyUз>SFќs˜тUЏМЬ зUЭYšё!–XЪВdOd‰Г&$IТI’8к‘ƒ†№Б€#IЪUYBљXтЌIШшSХ:`угР`ЅVo€”ВkBКЗј>ѓух‘ЫЊdЙЮёКњу4gY†Ђ,yU*>"Š…еЊадMИ—ЃьЅ9*€“eqў,rzё#_ЖЬ‡kВЩїяоѓwoПg‹уmbœт Ф‹МРŸџќgќћПў;юяянЫ͘й_<$^8:+>нžЇ9№ѕv.оP>RіG>cw˜žqpВЁфыЂЌtЄЋВa_нбrѓkZnЈў„ІуфыUё(Kžчшћ>`ОуЧ~]9ЗЎ*АeЧЃі˜аrчйС8#Zю–гуд6m˜не2/РЎЖvЌЗl X kГсДћКŒА —eqZз<Л g ѕЊ$EгдQ,лЄ@їјшHОƒ}KmV0[W#ВfЗЖ‚аR‹CР їPРѕЊ,ЯPWеŽюэB).Шв bDЧФт­^u‹sслзUŸŠЅ(rWЏšЖк™WŽрђётGЄЊ]}.ŽEЎsU–0FУэbбЋЈБ@;КbLКњx‘чЛu ГƒЮХ/бКŽE!ёуKq-<4>fЇПї&щН†vvY–рвзЅїјјРТкk_›–iщ!GgМyž§dJЕ˜ълJ)|ѕхзИ|М MїИ," СVыЂВ,щ§ћїєЯПћ#'IJeYrtМр(ЪЭDЦ:ЮЄVЬRђ<Їqi]W:ŸЯф‡‹ЗQCкŽБЬLFЪѓœ\ŠэŽ|Y–‘жЋdœeХ'о06ф7Rbf:ЮXдBўFЁ,ЫЈzXcЩoЂсъ™8rФdgжbЈ( Ђ„ШзB8MSZз•–e–XŽтїјЎ Лn.шtji^fЖьŽіоk˜С Жmi?tM!ХїB|6ЦЧтз‹RŠ<М@†qigвф  %Ќ”Ђ„jZ‹\Л4K]-“jл–хaTэc!k-•EЩ‹dнёo†ZWЊЊŠќC!Б0Ge•„м‘/MSЎ}эЬ)в‰в4ЅKwЁ4KIŒw€ИћMНьˆ-K6$pvFZi­ЩoФсЦѕкMаЯѓ–-u]GmлКђФ&dп†3гуу…о~§-ЋeЅH0Aq&о=яџќ'ўі›яшўўžЃŠСЮI„ЂЂЕЋ?Nг”‹М`ЅЇiЪZkžІ‰%…Rm)иь џТ};ŸЮМЎŠ1ьŠѓkщt:Qt $ђьТ(oŠЃйуУ=ћЯ™ь,ѓ"щt\ФwŸББњ(I’Pј?З'Vn#Ѕ,ЫxЖ.уbП‰Ц?[B;Љћ9giFމшB‹ZHж%ўѓштёЧ,RJ1љ&ФЬ І,ЭИz&"nš†#^Zм8Š7hжкp‘œ&))ЅXnюU з0<дС+ŠЂcЙCв$ЅЖi8zHЙыЄiъJ.у1(RiˆPQО6Ј(ЯsЇ‘1мдMdcHђ9;6Є…В,ЃКЎižgN’„\CЈч<ЯйзUEPЯбїЁу5*Š’Ў!’ч9y4еuвT‹b!љѕy™ЙШ ЎЪ’чyт$ICЃ@Ž­ђТршњЦ›—oˆ єMЙћЁgTW5EЧYбжЧ›ЊАёЈ*+.Š‚чeGХЮЋhМн@lЙ[Љя{ыUВО,•eї}JHL›Т5u­,ŽЕбLD4Я3зUЭЙk>Вw#фОИnЮsЗq…#ППя"f&ЩЛ,+6жАWˆИ&LšК—`єиyїK!Ќ—+уЌќэзпв‡wфy—`)“”А* њъЫЏёћњъв1"јИhвH­WJBS7ј№ёЌЕСŸ@4}‡AЯнŒžќњЊжˆ%f1M#FйЬSв%љ22јид }|ќШжZњAFШ}тfРёИFBёuЧeЫ8 4Я3кЖefоa‡"“иx]H†НчBlншљ™ЋУр6Ÿђ™bN9œњЁЇХГлФп$–(%K.7 \W5КОЃКЊбw=JPEX—ш:нЄшx lРIЭѓDz]qОЛDеіГo\cР!јлК вЉЊЌЈяz'ЮwžБ‡сз#ЌсхyŽ4KЩq\ШXƒЖ= ъъЉыŠхB/‹’цХ•%њЎG^фb'zDхг Щ]РTЉu…1г4СњŒk‡нbT‰Ы4Mh›Vfѓ\ЩЅ ЖЊaЖєH`ˆŸ)Яu$9Ту,ˆв™OЪоцyІІng9ЩQДя{nл–dњjшћ`Їk­%З…™GhНЂы{Jвхцћr€Kт№ irФј€œ#Т8ŒС+'Š%ЖОМ’^.|љя_гнУЊК‚^Е›КЧа"џЏџхџУ?џўјтЭБTˆŸбRžхЌЭŠKз‘ZŸЯgЉ§б3њо№1Ђ ƒЛюBN`сpŸ”ФpЌнЬГœWНтв] WMчѓ9PEžњёЏYkЙЊ*2ЦЂя;hЃ™-гУУ?У\ЛЂˆ‰ŽV)…Kw1wч;‘Їё tКАж†Ђџ0 фЦˆяяяoБѕј‰в4х,ЭhQ _К Бeмнн=%OЛyНuпЙюw‹yšxšGRыЪY–сю|GGЧВ'О“xS’Є˜— нх‚$I!<Ц—Ў 3sS7ЦЫ2‘RŠ‹ЂЄгщФ‘џ-nшYw/Ћ<Яй“ХЉЛtШВЬС[Т\Ш›:нІiаї=”ZxQŠ*‡ЬтыђT,œч9ЙYНЎыPфŸN'zn=o­M]7шК‹ЋёЊuгpS5$5бчžgЙFE^<Œѕ]ЯeYв 0mЛ5БжЂmZўењ%§єoтждzVQјъЫЏјЛoОЧ~ј#ј–§sRО%‚Я8Cзї|О;усўсˆДЙuёqиH]Б4ۘ’oомнœ?…ќqrЖ”g9в,AзюююјююnW'јФЦfžвдmbišтюююˆbџ$vШВ3I'"Іžpѓщ,Б<З яpAIЄ%­ЊšН#кsыёЄФH†„Яwg:Е'™Љ| “uјPІ4IЙDсоЬM+—ќЬMxѓE•ч9,[ЄiТwwїhы6vуgж˜cХGY:тљtBл:ŸрЂ(žМЯn‰щѓюД @ ОБ&ќдКhНr–gєъе+.‹’^ШЫ qЩК(Ѕ№pџРт["f_Я\ŸЃозY79^Пz|Гcћ‹g6cпЩe‡ЂWЏ^Гї`~џu3œ,Ы0/3eзЏ_ѓѕ,!Цw‘1_§ѕk~x§@їwЌŒrG`k-ђ"G–Ѕјјёe9НHp~ ДЎЋC1№йМ4пхZ”“=>>ю,_ŒщIRPт:ЉвeўЧВ,HSЗ.IBŸ‹ЬЏыъєакю”/§>кh,ў8ќёуЧЯК>q,nФРeЄБ ыs>cе+”rs†џбXфVjХхr'ЕМјГ$ЕЎа^#,АџH,Ц(Ѕ№xyФВ,џБX”ђнSичЦ"ђЬeYpy| ŸѓY"с3ЦvКкЯE4Ь‘gEpйћœXf?Q1ŽуM/lМ`kЯђ jq ˆЏ,t?ѕаыяюЯШѓ ж[Р&в*~ѓц5§њњ5­Z‘5WЧМ[ИŠ.>13eИ1 ЊтiК+яMпс„1–ЪЊЂУјЧ'Н;нБ3ƒБNЈ]UuЈcаm\])EЋ˜f4/ ,XЬнŸ\ƒ'n пЩжИt—08КИуS~}ЛпOгЭDНШ]іEБ2ј‰яЏK,oš%”ШCњмg\­sšЄ4OГЗGЭХЋ™nе o}Ілpœ№~Дэ‰‚RЋЬ~ій}7‘МЅYцќ‘ч‰яПdm"G4Ї‰63єбяS?юф†Xќ4цeЁшФ@xоЋqњGЬЫLчгЦX—О<ь‘ѕ б4MTЅдЃщ‰Я ч6ПaH)EЇг™Д6аFSт‡’Ÿyvq\,eYТњšvВeOyђюjŒЉ›ž€жšNЇєЊC—ћSы*їПЕчѓ?џепQнд0кuд3™Ѓc?џХпс›ЏПСз}‹Ђ,^Ц—ХцЕК, оМyьЭшМ4#X–mлЂЎ*­ЁЕAU•/оэнМžƒЎўр?€ідŠф =рѓС‚ѓщ шЭg!Рrя”Ѕ”Т~ј#,Ы‚Оя‘Іеga‡жuХЊWм7 ъК‚жыg!Р$–q зї?њЦq ™ЯE iol]•.–ЯA€ЙX2єН3Х~ИПGзu^вѕВЕ•ум4nЮб `УxћЪЯС‘Iц&дщ?~dWbO]1w3}Ÿ‹œ~^с§‡ї`k‘eхgХвѕŸstа’>ТЖНфsЄфВ, ŠВФЋWЏ№юћяwЯщKяЙЫх‚$qsŽ\!–ЯХД-оkцеУ+|џ§ї^—ŸТОр3ЌБШŠ ?џхпс‹М '0"кЁеЂp:Ÿ№ыПџ•sЏRыэЪХЁC—јЎPз]Т€pъ™g“Їжю;FМћШm€бБштЩЊ,ЁЭz=\{CъmѕАЎ Уауюtчnф4EYф!ƒкЁzЂ‹c9чЏ„Єi&]4Ќz= @_u‘C,‚єЧwчЛайЫЃсаыNчцП{+>А-=ЎЪЛ§­OФ2ЯІiЦљ|†ZЪЂ@ц3Kё№= k_ч$qqoŸ(А\O7Й‰нК‹d9jYМУйŽчyоe;|œТb1ж!Рђ,wCаiŠМШ6dж lз­u†к)„<:ЋімВ[—У‡ьc1у4ћ<ЫBѓЫк-Ц \VKзw`cЌЁѕРŽЇ‰кМћž,тшфE^„M(M“АЩ?…Ў;чЛюhъ‹›€оТO%5]ђžч-–ТЧВC‰Хf\#лв4ѕ›hАjmлКђV$xИѕ=ф–-ю_ну'?ћ1ВЬСТ‹m‚šеЂјЧ?§1џн/џгCО_ц†5Љы{*‹’фЭe­%k,ylЬ‡аBМ.R]дBѓт„кВ^ЫT–yС~єCќќ—?ПJU%S”pи!СoЛ1XщеKиr,ЫТ;3у#ŒS)я'лˆ`yг}f9ЪЂ ЌЃю/Ци_К ъК†Ч…XWН†кŒ KKЌ0(+Е@Љ…›Іёz.kXдŒ<ЯQ%ЋEэhЕбКАЏrзo&щ1б:тхё!Fєі :PНrг4‰МБ, —e‰ў3‹RЈ›”И5ђhЋнЯ€ižР–бTЭея‰‚AŽƒOеІжuХ0n(їЪСr[iщ1‚Ћ ;ђЃjѕфѕхjŒ!ЯsЬjС4O2ИНT§ f]iмВ{№Чr‰ЭwвoЦ Iэc‰я;k]ChšgЬЫr3c-ДбЛкй1љї4MоŸЃКB]ЩЫ[Р!ЧXbOŒuuајћˆZТƒід†fо“БŒВ,CY”ЛXЌЕXЭŠкЛЧЩцuiЩѓ§р !ЂVŠС ЦїђђuМx зˆм^2Mђ,GY”a“‹с'u]ШЧ1йИќ“с‹~­‰дv6"tTУ„GєМzѕŠўЋŸƒR‚^5я( IТ цKwAнlзЖЏnДŒВ,Й, A§Фgt&"žІ‰­Ѓ„ьА9"Аѕ'хбfЇ}­Ÿe‹ЎыИmкн€dєsрД‰ЎОЭEmšF7ь7Ў+ебЊнM\…S‡DuœА‰ƒоaОш{8%–eсКiІ)ЯЫ:дqнkœF€ˆ…–нH,Ее,ЫЄFtŒ…Н †aрѓљ,@ШЋ>вВ,м6Ыц–%ИqХаЩq9ё0Юx]Ђ ƒГ<у,ЯŽѕ*–ЬB- у4Вд2=-fvПg“RoffŒгШŽRю|EB,zuPк,s№UЦn›eЯЫь†ЇЗkїЄ#>ŸNlхu]9JиЁхŽ,ЯrоХaЫДvЮ|ЂqpВ.у4BЉ…Oчѓеѕёи9зuПE=ќ9Ійaкђ<пзеќГВъ5˜AEзˆїc/=ДбО–Щ1žЪaЯДam Ÿ88жoЄЃyі5ѓ,ЫЎ0m~ѓтвƒ`E'угВsgД›љRJсдžЩњкYјŒдЮ{3+Ъѓœ|,q-,ЌKšf”9yвтЯ™ЇйЯШіѓ‚бџяѕАt:ЩCО.бŽ4Mг„ЊЌ(Я3ыкQэ Y–;пН?СdЮЧ™dF."/ХsŠф6ƒгщLЋѓО БфYŽu]ižgСXб’,зоC^NЋ^З:ИїЪzщ=ЕЇ˜Eл;“Т}v:I)% ф0;+УзuU‡g1Ў9pА6”чyFX3k-в$ЅŸўЭOще›2к„ыlL‹нxєа'Ў_§§/Q{œŸbcї#mрwyyѓэазž–ЂtдИŽ—$‰ѓФHSTeХQк~%{tSмUUŒаŠЇФ1яІС Ігm:\ъ{ёS†y™QљуДZK”КЎCžхьPцНUШ„мQyо_ˆ…ˆѕКђ4M’§нyˆ3/oДЁОјŒ]зЁ,Jиh*рн›л• l0@ђѕРз›g7SwЩn­яМЬhк&цгБИѕ}ЊЌgљо5 ћu‘Q]U,]юДЛЙW IDAT„ЄIŠeYX-ŠлЖН9Ў€ЈлМЈ…›ЖœDЄ$`ЌСаи™awП`• ЃJuU…y<œЄnдFЛю1пcŽі,ЧOЮѓЋYƒЏПЮб5Т1 ф$qGmŸЕ@фjг4Бб­лИvaœeћ#4Ÿ\FОŸ(5ЦiфІnТБ6Ю$ЗЃ=У4IИ*+щ †XЦqdkm{ŠсЂЛk.'2ЯФфXI”ч9/Ы\јŽЧQ`џ|k­яo3@ђuUg žvДЫьтZИЬ ŠOTSЦы7Џљ'?§бQы.Osh]'7ЧБ#АсЯ~іSќьo ЕЈpЬћФи@'>ЧыыК†кœаMVНТcЊ‚хпЁйrKYф(ђ"Р…–›—JyЌхы2В№Mе„T#‹rcЛX(јЅь>GЉUY…YЏЬЯ >vЈЊj_Ы]}ЏYvБxCЄu]нь Z\бк5„ž]™WЋЋА‘†Э!j]­ЫU,ОŽвд›%gšК:ЇGfи­dts]мѕ•IžчN•г_v Ё#мr_;s€вЖnЅи”0֘]цјѓwѕ*GиЩeœЄя{Бyx"–m„фвд›%Ї(>Є ѓВXм3АЂ€ZU№`ОбPлlїќkoJедM˜щ“XќfqЕYэџxу.—иH­(œ‹л4O8NWЯЭ­gQŒаšЊ ЖžN ФйЭ^Х}ŽРWЌo6im‚Зё8џџœНї“%I’іyDЄ~ЏDk53;Л3ГЛ” ŒЦџŸF4FHa8аŒwР-p;ЂЛъЅЮBddОWЂЏжжzІІ+Ы_dІ‡‡ћ': гpЫЅЕѕБXЏх"Pѓ<УЧo>тx}м‹mKZЕ 9cє#›Р?ўПќѕt­eŒуnм%iœ§зш ‰J0/vWПћrgЇЊYЖ1/~ь:У8"/rЬ‹хХ’kК__]U,eQB*№ЛЛ;dЉ­ їђE§ьЊІЂФЉЉ1ЮжўqžgЁXc ]бGbqЛ”жїwїЁЪ‰cyЬяxœьuъІмXНhd‡,–ЬzјрH=јwYмппo6…MѕwсRОй_VNdž1ƒsQ{f,ЦЪKхY€ёг<Ё>еЁšН Бvіх{Ч>–y™бѕ]dШ§X,ыШŠкЎ qг4И:^…„ ~ќйѕВ,№кz'ьŒ‹ОТЛSЮјXњОGлЕИОКо|žЧb™ц e(ЫЅю…щёcБьяГЖzыњBДm‹ОяƒЪЅгЯўjу4"Os”E~ќqRихSkЛк>Hф–Ў !^ПyЗя_‡wuП)xL3 .Rљ"ть“bЖ,3ŽW4u‹гЩ}§ДuЯєxŠŽU9цyFгЖ8T‡0іv,zСѕеъК—њGХ’&(ђмKUсъx&yЯE›зŽVз5’$AUVgUёSБdy†|)А,3КЎХееUH:Я%ŸЃqssS]ЃiЄiz^?‡wєZ–ѓ4…—"MSАa|f,lps{ƒгщфРЪйХЊјЙБLгˆqqss*єчо#уіцЇКЦ<ЯШГ…cŒгWХbuЋдrsѓUд2ЏБ}{{‹кХRфEЈИžsŸ}2АжvвК, nnnžЫ9vўТ‰Lpssb)‹r3UŠњчm4ђТкzv]6–’ш­VŸC•ГіЇЙ=MБС‡OяQT6„PЇ•hоЖДwМбјсјђыќќ?уѕы7ыKёТi’B/у4рЭы7ž9ђUз$Ј_ю>уpЈpsѓвПЯЙŽУЇ%‰…ФLѓˆЗЏо~Џ6ŽE])|љђWWGмммкўsе^œлЙэIŽX–Џ_ПF–fxB/эьf aщ`ŸП|Цѕѕ nЎэCє5БA}ЏЕЦл7oЌ+›ЏŠE )>љŒлл[\_п€\R{~,V]Јя{03^Н|^МчGbћЁ>–—/_тъxu‘:јX,RZЃѕОя!сх Ћ—љ‰EТнн^Нze!+†7^ЩЯŠ…ШQ3МxёђY|ћѓыHYІЪызЏq8СЦ<+ˆРvиV–xqћ"ю?ћK rEв›7o<ОѓљБXй9hmp}{…зo^m‘ ›œЗ]+ЅdЧУА QfŒсУЁЂoОћ„_~љП§іgyf­%щœž{с„Жуmђ"usтqcyKз §uЌЁ…рa б?ЖљOzQф4ќiW•TbѕХЦЩЏ†d№ж‡iѕёўSy#яa%Щ>œlиГ”€ЧtЯ\Ў”B?ZLY}ЊЙW= ›Gu7kфb3Ч!Lи Ÿ8зztц&JYœqјўtrƒЅ‹Кk—щйь61Œ#Oѓё­љёŸКнDТЙ“ L•Xмєјыэтd&5Œ#k€Ф`g ідѓ†д гˆe^јўtO—†IмŸ№ь ) сaЁ5ѓ бс3эB!„”ж№^kмппЧЋ‡є/№}НА5ЋG€Ц<ёГМ‹……ч‰01юн@ш8шТЛгiGеЁЧљШšž]{žОЙЙІпџј{v“{ФдЮH)кƒ™™IС9йЧїŸhэuљнEkoОћћџ§ўў?ўR ЌšgTKB`G4­*ЮГUzёач~%I‚ЛЛ;Єi ы;yёUе›•+ь$//0N#НXАg~&"@ K–gа‹Fзwщscщ‡СRЛђУ8@iѕRѓХў IвЙmх˜Ы+Н ‚§фбІы{єУ€2/бїƒ]чg"tТLг„~(ѕ•Бm7ZРwV8ўpњА@СБ˜Х nj+Ћю№_Ћ%чхѓМ ШrДm ‡c{v,PоД-ЪТоg+Ж!П2BззЖйŸкXђ<Дз|Љ4NњОЗЯœФxю}ŽЋЏКЋС†‘І™E е4OeЌЯ$сї™?K3њѕѓЏ@/n_ njђВCOЈO‡ыXб„zY(-+Ž|ўђЌ‰Ых$Иб sЎuјхз_ „ З/pКw%}тЙЂ—bŸ‹`5ЪЌvЁц,ЩP\јђхГгЩSz˜јk%*СЯПќŒ$Qxqћwww‚WЁЄа7ЅКuD`Дц<ЫquuEww_рдЌiЇЬН'`Ѕ”ИЛПC‘ИННЅ/w_@‚‚ЪЩNgяl=|Ія{mPц%ЋюNї,•ŠЕњЊ ќ‘ˆHљђ…‡nnn№хю H’„žt…ћ,ЅDлЕф=–ЫВФЉ>‘ЪдўЃ‹o–ях2№лчпp{s‹усHwї_ „ {|њ€ђPўtр_юКVћaЏибoМРXдВY—Ak ‚њуO\JЦB Ћ^i3 ˜bЦDФMгВ”eQ№8`€sš5:В єqаЦ“СЪЕlДЦЁ:АЅKЭБдЯ ФŽўŒ@ЭнД •pžч7бЫ)„рyYИГюq’3OH’дš€­ытPйЮ66’˜ђ=дКЉ9Ы2ЮѓŒ‡q`!%l\A}ЅІ‘ЏmVРК’Їyтaи 8`šfЮв JZ)ЏмђyД.ŽТЧMлpžlUзVIТжє~ииНr,ч4ЅќšП~ѓoоПЖ]Ž)ьѕЂztЯmd;—‹K рюYw}rНh\__с‡Ÿў"`чTŠ­А(3#Q‰ЕЩ4‡ъv)ѕ8)/ŠК-0"b8]зA9ь 1‹Y` ЃЌ"’їЎGƒJ• ifx™!_йњ’лs-7ћGДГ !РЦ я:ЄVк*‰@YV˜—s…Š=LAI…КЎADіwЛXєbСОžРФBчШ|Ѓ5њЮ_<МУЊeЪВеёCБX1QX9EQЬvmЕћZ‡>НYOЋІД]­5њоƒФЏўЙыЦЩ‹н+ъ%ХlU#|5 w<ФМЬVа!$oz<bY–Хщ–ЎŠœц R*dyŽб&žэQиn#Hрt:эBџЌz@rЬ>ˆUMТѓтX9ѓ<9}Н2М+г–aТк;П™ИŒБТИe‰ ’qVpDЛ †АёўX—@SзAФ`‹M^=в4E–Іgš™>^­ КЖCY•P‹SƒЯbБк)>|њ€Ћ›Ѓн(Э ЁMХv›ѓ.ГSƒyШиюјЭЅвyž‘Є ~ќуdO ЇŠЎk ЪВмATЂЊIkˆђ<єЋbљsєН5аqІз›~@œH}яЬѕЗвMДm кЊхRŒuєЧVkpгЎ‚#gˆyžƒLљЫњ™]HUYaŽІАёюдЖ-”R˜0сV…уSjШВŒІiкHX)•Pг4AРћ’DЧcјЃ ЈЪ гjЕmKiš’ѓ*  дНXiš&A eЉ К>YЂ~QЎюXнUЄB*ђ‚ЦiмР$lnОnуЄз};M3HЅhœЦ№ќ{xЧ}}яхмуiё&цqЁЄB–e№зи$ЎEЃыZфyфЛіыт­В4 HЎр oЄu:н#Q ђ<ь$hЫ4zеsк{ {ѕ•Оя==r ХЏkKm”Ц…NЇ{—$c“эц1Œƒ•,sЖЖ~oc^ОzїоDС t!‹mёд[ШKDх‹№Ѓ3а#­Y'ьъ/_ПФяќоZ7К,MR'ѓ=…кK…ІoтћуŠ&ФЛ`зuЁЙКйmvЧ• PQU—2Ћ–лb^І3Емј:ОGфћUых^ЅTрQЦkуш^ѕYЂм?]/:ˆNFЭ|>ХWCБьпЭН ™?ž]т;њXжJeK]зь‘}?Xˆc™›#wБФЧ8c zЗыћd_cЏЬэ“­? “eŸрtК :k а4nŸ1r`њyГюNt3 Нщ *сiš"ЭвpќєЁћћћPY‡XvoEЌzže9цiоTmЫВ`Ц lжћЃЉUїЮВ|ЃzюЏsВ ПAэЅёчБЇ ьl]ІЩa H!ЃXpv™ІЩZDЊч~ tКG–ЗіБx+‹h“BМљXЛ€bЧы>W‘šІ EY‚\ыТкŠ<У7п~DyАЖЁ'Н#rlЩt!C„9Pƒ9яњCБЋЇТАw™$JёїјЏa?іь’(пŸюЇкЋЧ:{l7Ь…AФYšј“їхц E^xEяНЮпЪ4Ž#чiЦY–ђ0єьшa|Я‰ƒНxеcБƒсzh ,‹uЦЫRЋzЮЦТDЦqфyЖ§vч‘r&щЋЋУ€НP’Іž0ˆišЈя{ЪГBŠ Шubuž4Ž# ЊЊ0ŒI)Љi2Ц Њ*ђSb4rЅ}pAsНVвZSšІ•3Ž# У@yž{В8–Xсић+а0 $„ 'lkŠM €Q–%љЩ~ќѓ{pВжšŒеl Oњ04N#ђ"ЊУс:и(-лi. ‡JЪ@С’RвЉ>‘‚\,Дћ<Д~“жкh/kEЬ@?єabы€Жgїy3Хhш{JгдOPI Їu)ђ<пФrTЖŸmYВ@і”Œћя}пгВ,”чyxОЅ*ОПЩЉЗLŒр4чД.}|Nб<6oСь>€™В,%vя‹cТ6Хљк†jgш{*ђ*:кŸNЇ_K˜6ЧяPŒ‚HГ” bfДmKЬьев7x`Ьэ;5 }p–ыКŽЎŽWєЭwŸ(Ы2Ћє іЃ‘уРQ>ѓ-^sжvbŽё9Т’лѕb}r§В эЧUјјЭGќхяџ ўс/џ“k№ћ'mЩЦgdidPJсp8Ђы:+Дh˜МБJє{/Šˆ h­)IЖкk §€ЪZvКЭœ%k;ymKu ОјжЉЏјœЫDё@ђ6к I*Š‚ыКЦ4NpcŽŽа‰ЋŽP–ЊЊТ0Œ4M“ €c|$ŠЋA­5В4#Ѓ зѕ ЫМPUрРОєдњКъˆЪВфЊ,i'ŒуШJ*ЊЪŠ=CрJЄ{WZЅaƒћћ;іžКћЁгC|!йu)aAЮ# УР‰JќБ•љ!! ђўЦ*ђ‚СРн§bf/ЦЩЛX.в=§dй9ЗaGt}4KУБu?8яT;ЫGЖгщwww$HpY–Д'?(Рр :\Ю2 њЎGžчp@яГђ(EЭ \{pє§§kЃ”…ѕGш€НоЅБ`д=—EЧQ"сœнМ”˜бoоНЦ‹WЗ0lЂу3“mЁ3!zП &Оp}&Gж Е=3?ів:ЦfрЦ?§љ'|ўэ3в4ХЋзЏёgѓœ0.U8ћ !№тіХ:mТѓЏ“(Ћ§ѓ/ХЭѕ5nnnŸ} /ЏЃT6гdћ<Џ^ОВ7ю+$ќ ЧrЦэээ*љј<гlј^^#MО.0ТDј—_ вP_#Лх?%іAUU8HОV2ЫХ"ЅФoуЏx§ъ‡уWIЃ13в$uНИ;GЊУкoЃЏ‹Хл† ЧєU2mб5юяяps}Cu€Т+}•иЧ oпМ§zЩ8F ‰оŸNИНЙEup иЯХѕЋЊТ§=cœ&М{їЙk]<їpВsž’јђіхjб€Џ€HT‚Њ*ёц§ы jеŸSЗ­>кP}ї*й€љЬiСЮNмhЂЊРЦLLL1͘dŒхіНzѓ п|ћ џспџП8нŸ6іˆ*КИ Бе(cЦiфišbBєCb ёю%1˜Еж4LVСзЫљ§МeD˜ лтьГYямѓМ€ ‚њєѓS…}ёTЂ0Д^аѕ-zБŽfД5x@мТVЗ­Х.Ы)КЎЏ8‘ЧžЕФZжEЃы:žч™\,xфнЁ§Г;/3–eсYЮдvŸЮ?toЮо)Ни*њЇ?џˆ/_Dl-ZЃѕU %љ:Ž%GIќпёчY"SWп‡&f$вE>1†2еЭ)u[KЮRGˆz% IDAT§Ы_ў3ўзџљУ2.ШђьБRћЬ&MS }o}fЋ mз’…3№“G+ iћb<;pЊжкŠи†іƒП?ў^фЕJZk”eЩmз"ŠхaПшь{—ЫВ.ušІДš~?~ізЩВЬ 0(Ыsюњ.ˆF>ёѓиЦЂiY4ƒЭš\ЏЫSыKY–ёщt")в$х~Ш™з<Љђт щRH­=q kйјДдUИ~]мА”ГuјЬ‡юѓцћ †’­йh зУГДЗѓ"хб5JгwwwШhмWыЯi 1I!‚б‹…г4Ѕ nј0XmW’$ИПЛ ж'#,–ГMP AѓВ0m @„дЉ=ёѓЉA)…ЛЛ/ЈЪЪЎГЕРябCЯ љ#Аж>МЧ?љџ WцiоЖЂ|џЩЁšнц*^Н?HэЈpq›щvЄъO;ˆ Q4Šїў-ўєч?тпўЋџI’\ъœU`6qЩрЮ–e™3ЗЖи!7Н}L^*МišЂя;ъNxџГ mZdYF;9Ї‹чќXЮо›/—eEкM,•Rћы№ХѕrБДm‹Ўяшу‡оС EV<$t–p|uY4U…ВЊЁzњєсКоіYѓ<ЇљP<^ŽIkэ9ШѓœДSћёnzЯЙN–eИЛЛГІ[>Rн:Љљтб—”уЯуЭу ВцO)ЧёИbи|?MR|ўђ™цeЦЧїqК‡6e^вIћЪкУ=˜EYB юк8–ч$AЄIBПўі˜япПЧ—Л/КмсрKБа0 EYТъvH’„clь#&`•аќњ IЉ№іэ[|ўќ™Iэ!`МзVшЗяA$P–Дбфu*ˆхьВžШGќсЧпуpДFY‘Шщ:㹕ЦBЋюp,ŠКQЕ[Ёcтяz>/ЖЃ“ђ:2q•Ђ1‰Hш›я>ё_ўг_pwBUUє_Dж$zтВ,а#ЯŸъ'ЦЂ“ЇКсЬтYIEОQНЕ>r”&"n[;=>ЊЄ…PзЖЈЊŠxˆкУопДnЫ&06>BрLЦGс‡ДњЌz=KЅHJ…$Myш;Њ"ќо…ыTНУ—qлЕTkЃЩїQЦqмА;ОШŠI8)#ЅVJQ?єqEzQ]йs”Нx7X+Sm4в$хiœ–эТБќтWmЋbBP’$,Ѕ@пїДgщ\њLЎКBлЕ4Œ#U­—АiEБ<К.^ ЧА Jг„ћžhvU =pпlмuлђ4Я8Є,ЭрЁ`ЛСxnп—,~U’Pзw–JjЋ@~ЂrC’$lѕ:Wa/uЕˆХ3Љ;ТBDrdJ) dI†ЎяxGzŽ…1†й0оО{C/^Н'•pОVaЛ’нVsЉBьƒЃ ?dѕяйЁCAGNfs5І ™отж"0rл™Ц‰_Мxпџј{o§Иёш%"­€ѕr7MУŽЊУу8Вч&К†чЦэ>ўПЧ№ !xzcЋЅiž Hp‘˜І‰уiВ)њш“9э6>TЇZ‘ЅGш4ПДЗ ŒРАмї=У0ЊЊтqYJЩЙ5QК‹wс XГ$Ixž­›™Ÿ’NгШy–AJЛFћXЂЯdЫ‰ˆЛО |мq9IЄi†išТšb• ‹џ™™“$сa0Œ{ЫТyžPфyРOњjп+›ФзŒbAпѕ$Ќ2ђ0АQG йГћ'ЯWІеЗзл`:\i˜vЦї7њœЁ2ыњJIЮѓ‚ћˆƒъ&џ сў9іывѕцХ‚лЕбМЬ ЪВ ўЗ^ бН‰ЇКЎ=ƒОГрі<ЫиzXч,ЅФф› ьŽЅСюшлДM sтС\Й…wЪл=џ[КЇЗwВ4х4Myt‰\Сг2ёю3јЯЕБЖPijёЊ`ЎЊŠчy†ж&`4нt<~ѓб–Р 'vKІ,=нГJ,БZo. kJ)БhКipЈЁjЇi–"Ыв љ}џѓЊуbБ^ЃyˆE§wч+Б‘чС.&Яnлvя№({•(ŒN=ў§N{‘v›Е ”r•†šgHWЧЬ€ ˆ‰­Іїю­џВ‹јЦaгЊˆ…$l,ЦХт”GТчQRС‰"џвФ=P‡У`B1тпsДвЪйЯЦrLZы Цгв”RH,€їQkGKŠЎя1Oއcxš­Еi ‹q‚3й^ТЧВhОы­‘НkIЬЫІџ^фуAœ‡ ёv™GЧђŸщp8€Ќ“KlпBђ`Y–Y”€ГвLг4Д Р;щЎн—rl%c8А•ŒKф^аФ!іŸ#0„ЄФšЏ.ЁXј–X•p-•]ІГXб “F1РпЪnŽKŽhvCŒпџс{фENNc,pЃi,I)IkІiшP|Я‹„ф­YšЁњ звЫьxО#д6 y^eЄPAlqgф№pUjЖ”ЫМPзv8TІq ћ`%^-ƒ‚єбюЅpФsJ’ФїЦh–™‘gЋњB*Rjvдї[Уj"ЂСвšШKс;žd ХC˜Жi)^аšЇжJР­KtДXAЊ‰R4УˆусH{кSY”фРпQТˆю‘шЉkWэТ8"B–fžsЙi7ЩІО06qХ ЮњO[мм4MфiœћkкVЩ„Оы;РЏokЬГе/Ьвд‚ПйФїИпFJ)ълŽ|rˆ€р˜І‰еlѓ<“чЮвKшeNуH§а[Њš{ў]ѕHвѕzз ц\]`$JQлД0касp mж5œч™|ђђ”б],сйЧ!ацао>Лѓ %%ЅIžу№n%RЁЉ[)5?н@‡У–eŽŸЙ˜—Ѓoooёўу{H%Нд[˜іе‚ ї*ЛЉЫ;№9x7ыpœБ…ааЮBnЯ Ё]˜f—]ЇiТЧo>рг71 Уf7о=Јы:HёФ\]_ЖgYШ§—ЬR„Xц]зЁЊЊMу™œSšOУNЙ#Ž…™Q7ЕнЭ“ѓXєЂУ”/VЈи†ЇiТаЈ‡РЅє qЛCЛ*pкЊeФ`F›UID%›жѓ”ƒZ†K ћŠVJ‰a1Nу&‰Цы’;~ш^Й#іЁXєb Єжt%’Љr-‡c†ЕЊй­›ƒ(n3œХ2Я3њЎНДзZmЌ4дCїЈя{,z Cёп™І UyUрЅXЌ•щ„~шƒкQ\,Ы‡Sƒ~$ы‡kИ])хНrl,ЎзыŸЙѓuIаЦqXqvбК,ЫrЇЉE/gЙq,mгР™ђ’6ѓМ ЊbѓP,]пašЇ‹БЬѓAdљгЫ6vуYc в4УЧo>рxu€^єІjЃ-^ЭV.рƒhХ/oгЋкБиЖ8šя…^Р|Ёх№?ЏєЂЕ†’пџ№=ЎoЎЇЊˆ>Ј'GЯісЏЮMЏ}…‘Иф8г†-АI\ѕ y‘o’ш^Й#ЯsHБQЫиФ2zЕмЪЧЮb™F$iъ\ЕЦГXBѕзд(Ы‰kЂw]‡КЎaБ~™nшb,RX5ŒX5;Nш~]̘ъt!oђо6M0Zп Aј•7љіЧЌНКrп[ŸрKБј6ƒеGДЩ?ŽХaзˆ7Ј8ПСxsыM,бFзѕ=Д1^yц,–ižж6Ъ…X”RЮ‡ЂЧёp<{‰Уcl,ўШЗЦМўнЎяР–NИIЂс™›ќ§уMђк Ї\т:ИXН Ўы нЇБЊчETЯЗ УХвu !Ю 7ўwЭЖ/ŠŠKБиЫкWeхbэСВ(ЯЕ7Ѓ{йFm›Э ƒcьzzб‹XЯЄ™oоОЦлїoмЯx5Ч ry№џ-8c3жˆхж&DфІчўvDˆ%Жv'€нї8ЪЭМюЄ3оО{УпўюXЌ•Y›ШС+сžmŸ ž;oŒу„"ЫYJЩЎR $u!уˆeYИ,J?mФ~9M+Ѕ&G:}–ьяšаM}BY–a(sОšJ2jќЧŠу4(l›ћЁЧњЫпѓп§нпђЩњe`š'N“4 9тљ™”’Е1м45ЊЊтX/mƒрišИ,JˆЇiŽca ГсЂ(xзуЈ/iН” Оўя(ЅxYfnЛ†ЋЊтШ—wгfafžц‰+—”Ђфх=1аYГіЩіb,ѓdEo…kCc8Q ѓ4Ёя:>л^№жД‡—yССбѓ–eaЛєЃОя‚Ї{y7ƒЇAЧyžГ‚ЃЁ˜ л њРУ0№ёp<ГМђЯА6VІэPXы%4ў§@(Ј ;#"œN'ќэп§-џ§_ўг4Yс+Юt+ї•h￘І‘=U;a 'Da%фЫ v БЦт\маїk7і‰ІјМшХ>O 8|+Чі}пђbЙїAeХлчrбš™‡В ^1ўй6ЦpUUјј59rеoђ QœЋ|Gэ@œЖ™Бп”щIќЫ8@оUЁ§ТЯ‡k1†Œ6єћОЇзo^RлЕBx™oъњŽŒa*Š"юФйд•№3„”gЙ>d К>Ё(JJ’$`к‚ДИ; {эЕЂ,Ш)m‹Х­РиЃО„m:6л—‘HгŒцe ’ѕB2lШЉх’t}%cѓ4б4OДЌX.ЦЊВ"WY’$i‡гQ$;tйКЅqhёš—™lROhYцфОжMл В}UŠДiЉЇбУ•‚іšџmл‘Тћz>ыКј>ЙЦшљяМЬдѕGкШR€§JЌŸц‰‡64Я3 )ШhЛJIк‰qвюбВЬHвФіњмКЌ}е‘z;„‰Ÿ1ЂШ ЭУ|яŒ<{ЦkКЊž’DQšІЋЈСї›Тѓ—Э’т"Й0Rѓн4/Ёmw€СKЃъyšёняОУ›ЗЏXЂТщt‚Vt’ЃХђŠНš6‹^‚\њВ,`cЅЕtЯFцлїk|OСяь~4/ї‡”VшQm“Хƒ#ўаЏ2кХЮЁ—бѕНu!sIеї_О|‰ЗoоюижЕ,/ "wћг§)HŠŸЏЫy,О_хeёЕбаzСаЏЭљјwю}[жф5Ђ*экjї§S} BёЛџХБјIЅW#ёћq№.dліDХ‹ЎуНЂ#`uїy’ъ?тГX<ЌЪЇэgœ&‹Pиў,6nёАižg ’v›ФЂ4M‹ђТ@(п~]\?3Яs‹Л$ЌFіхv М(ЃЯфХ‡ЕЧкN“ќYœx№Бm lпХщrђњУ0@I…Ÿ>рцХЭv№ASzhЁ‡XёnЫkVU˜ˆ2bЋљ˜CТ>oFЖq^!ˆЦКбgwš,хџјн}ОуЛЛ;šІЩƒ+ iтzЛI!ЅiТЫ<ЃzЈDyL_ …съžчiЂВ,бuзucЕіВв|p=ЧбЪMгˆйQмВ4Aš&ŸЧ"7М"о­х,ЯTVњОG]з˜—™ЏŽW "ŠbЙДп„ыгшСо˜ЦбVENI’Фы‚GˆчЄЕЋTЊŠњa@г4МЬ ЎoЊШ№єƒЦ&вiDžgV…xИn”eЅЄEіŸГhЮ‚sВ8TуˆaЁЕТИ;1‰‹ŸЭУг4QžчVќЕP75ЊВd!э&ПќПж TUEг4aюgfcЈ( іIыБћуЋ};„Ы0Я3w}ІmИ:„MaГ.юОŸ1Јцy†ШRkt5ZсVЃp'šЇћтњ.Ытж3,ЫТ]лЂыZT‡УЅфтЕќ№.Ы2.ЫУ0X };ifЇнЩpУйСхиЪlхh›–оПЧ>НƒЫМС nDOw2Z>qј3€Uˆ/фFЖƒab“Cњ.Н'-Жf‘сР4оНUЋСЏЏ7Є”Rрџњчџџўџљм\п ЌЪЭпyЮ—’–q:0Я3nnn&)k­щ ѕ€n…=юeјђх3ъІСѕе5ђ"N,›k*ЅАЬ3пŸNД, noo‘ЈфљS KJ3ќілЏhЛ7з7ыqў9ыт>Tѓ4уўtЯЦКНН ФscБќтПќђ+†qРЭѕЭзЩ‘9SЅ$ЦqТ}}0p{{{V‰>‹Ѕк)ќќЫЯі>_пАыЗ}еК(eЁ@їЇ{ИНН ъ:Я Х5ї…јыЯ…17з7k%њмX„ѕТЦїЇ””ИНЙЕUязХТD„ПўќW€ыыы&ѕєГkOј‚ …@? 8ЌѓЭЭ ич'g^с†PWпѓ6УRЄW€1gxѓOБЖџlЇsž‹ьwѕѕ/ў5ўЭПќЗHвФ‹i>W.јс a- ЅHГlЏпЦ‘`\ттЖkƒёДBŠНdжƒ/™O~У0X…тqb•(ЄiJBѓЌшyУmзAИщИ’ДO€n‡R` Г'ђєЇЈКx4њ аyЖВ”’ф<эшМk3 ђ€o;mFdyЦJ*6ЯJ€`VбйS Ф*QDч:U— ћ<^\TJСу0R^фˆс*ЅH/§8 QŠсTdžPПйT’ЫВ :Tј'џєПЧЗПћ„y^<э’J9…Нd ƒKкАЌЮrЏрZ№Х'rћlqtdІнПэЯЁјЁN‚?§щGќќзŸёљзЛчjІЙš@‚ад ЊУзWзЈы‰J 3љДьћрišтЫ—/€зЏоР+ydYОZ)>‹,sфЊМТеё Mг U)мУўфq†ˆ&)~ћђ$ М~§к9’)ЄO?Hб * A]788Žhлжjь‰чХтй*П§і+•рХ‹—8юЁ’‰;ж<+eyžѕ\уъx…CuА\й,У…фu1)lЛф—п~E–fИЙЙС§§=в4ƒ’ђйБ(%1NšЙСѕѕk”EфЯ=rJ)!„РЯПќŒВ(q<^…XЄЯ%Бw;wxѓњu8Й|M,ЮX?џђ3އ#ЪВТЩ9Е Яn%%ЮzžgМy§G{цМја—Џ ™?Ÿ~v­’м’žљy\)Š,Mё§яПЧћяМ>dДЧƒ‡~єpН‚бппјnЎ9L=Яј ђё3ЖR?2ХџУOР?џќ/аї§™™іƒ7NкОГAъЄйНс%гшK_žіе;9&сІsuг@ˆ‹H?‹Ÿ?Снƒy3‘=+!ЌЉѕ8XкœŸ6mAєЌž… мп}†Ж2ё}O„7l№œwcŒг„ЋуU˜Жm с˜Яљ<pww%-K:“q/V№Ќ„A@?єN˜ ‚pІр]з–Ч“їYZ‡ћ{ы‡ЋЄ)…ЎырсOOЧВ uh­УчЩѓЬ=?Х9ЌчXДж8ю‘&жфkЭыgmRЋ5k 0JIЫ“я{Яˆ…]ђ›—u}BцаVCГuњž“‘бЖ-рs’('0бсрЧЎca9З/nёўу;H%Ё}цћ}9паГюлвŸp8@Ž\І"gЦ=6 |nДŽvЛП-Лg0ЛiЂ1ŸОљ„ЗяпLгV–'’˜B Т8ч…•=ТlЕг\a/MЩ2…яKЙ:EaІ ”К PсэљmЫ0Ž˜],~Bw<-жjУ“\–—cЙG’ЄШГу8"s.xћXжЕ8Ѕ,ZЃ(Ър­|<схЬWекФK !p<Ыэp@ц*ؘ}€ѕ“lжХo }пЛMnѕ3>Ж'Й[빉ЂыиЪИП?YЦ.ШвЬСPє6:ПGўŸ;зя+Ы2РOЌЙдАƒjmџ\eЊlb:N.‹ hBnЂz .ЧвѕЌяБЇ?К ъХЯГПG~3<еЕSЏєbUз}mџ™тЕяе%t"*bЅЪВtтЅДЙЯ›wб}_Й$к4 ŽлjцеУТі-Ї§Лф”к-ьхі:ђїнwђy›_Ж@Ќ]nк9яђх|ђ3b–7гJ‡лŽšcryнЎе7˜bmЧ<Zџ=DяЄсљOџе9Iv2UiеФcL•Rrнд,эƒУTЪBNгдУ=јlЃ‘тH№ьё„Ю=tБˆ{оŒоi5З_єК>БJ{уr­5ЄЖЙl“W/ƒРnB,J*t]ЧкБћDГ,‹Њ§ГэЮЙ^УыыеЭ‰г4х<Я},Ќdˆ%ЌKФdп– "Ж„ќ–(YѓВА6†‡еЋyЪьІШ#ЙnNШѓ ijЁIкx§K9­mAцўс—BЂm[&"фYЮžrШ`ЊƒѕeсЫHh]РЬм4'eС‰{ВyGБXн8pLоgA‚лЖa!{AŽq™l"уqч"Д?/.qqгжAывсF9uŠщKД.nТшоA{ПЕm%Uhm NLУ%вK~зўљчЕ]аv ЊCх}ГНUCximuБЃрЖm‘8пoђ<ч~ш7zŸ+0о>ƒй0n_ођћOяМ—јоћ;цщ/пѕпЭ6єИ т:ф)"оtЊ"УщюwBЂЕєœ’ѓёуВУc№њѕK|ћ§7фQЂЌvJ§TеЋfк†фН‹Х›ІAšпз‹=~faвјаБDI…ІiX№q0C_hg5iЇЇцМЃї ЃБЖАЖ?*f™uЄѓ›_8ЮјXЌnbfАг–›-НыbЫЏ‹бmлntф|,іШ—`šЇPюЅЁТеді>gyшqкЄgEa=ћ`ЊcбZЃk;”EБ9О/ЫŒдщ§yё‡3ŠAkѓDjG"ф(K[…]zЃV}=›˜КЎпЈрxB@šиз4O‘Sк.!KЌvфевэбоžˆќр{$……[ УVy&ЂкСёнЗBЛЉoQјцлOЈЊЦвJик IDATшГъžеX{ЈSї+эђ* Ќ&;+8ЫfcЯ†‹=5)Ft{^ ЃG%гvяџvЬЫВR §щЯЂуеу4GмRьДЪКЎ#ЅяIрxŸDŽ/ˆВ(ižgК PA IjšкhŠhC›И„фj‚QxЌšРЅ†н§RЪВŒbЁV$Вщ‰\œAзЮ‹*Ѕœ19Ј,ЋM2ђfшeYТ8jЈTh]?!-zЁЎыЅ9ЙщГ…"іMуHБРщЫHЇњ!•EIQ E[оgY–Єэ?ЏыН№BЯїн0G!›fЫхFžчфUŸЃDJqт:NPJ‘cyНGš—  ,Ќ‘ЛЧZЦБxŽЙ>“ЃNR œžц‰ЄВьƒШZбqy7ЧyК?нS’І”eЙ7Œ‡‚ІeˆЈ( š—™V'­%1ІyТ0Tц–ƒХB“Ѕш!MSŠmЏ8AJ ›,uф !р єŽБА[ЛОBšœhDneіЩDš hW…ЋTЂ—4МЌОЧ›$IPїЏ?т13 kEbу0ичЪЕ'bу(AЖjJ”­0‡qимПоDЇг‰В4Cj=0(Žev"yžљоxИF”а1єВxбK’q”&–>щUЯучж?чЇњ„Ђ(Мhmзe&лvША,šтО|‹яёњXтg2hLКwl‡ 4ГA™Ч>~їišn;A”dеБдFЏo“У8я‹Б(Я‰‰7Ž ƒ‹Бэ,Ў$хm72Єfіg'бSIG|Рпўю[~ѕц•э5i|(œ*2†ЁG‘,•5†‰њ…NъG#ъW!–зa+• mvnfQяbп ,ЫТI’ZQы5|(Ќз`)pe)ХкsФкsЛ0ŽњUь[CвоЧт|H™™yб в4c!/ЮŸТ&ЅУРГ“УŠхС|‘.ШљˆИ*+>ыЙXз5KiћЊ1 #)*­5gYЦ.™E}<лWњž—yaяЩБіэя ž!Rpмју‘Br}:БRŠ‹Ђ№вh_Я{Ÿ3'Jы6;Žz™мuЛ#жЙ„­bxGHЅИ(ђГў3@ !pЊkNГ”Г, ыВХА1ЬY–1x}–c™ЖЎkйУEY„Я‰6А тq‘&);гЁѕяE”ЏS]#ЫsvFZq3иŸи0иТȘ]яxKл‚ьщфR, "rњiЪkыc }еКЎЙ(l_еп#Ќыl{ёђ,g6ЬЎЇюiƒ,Ѕрwоё‹—/`и„kИ_ L9)Їr@›io&ўрѕyих$цHKј1qdЙаЧ‹K8Ін”wMy4aА•e*Uь&fГ#ЇџєЧб:ž|Е]У|ІPЫ+’иV“VВл*w8… Ћ—І0 =+љ„XП7Шfrе­ч3Я‘ІkТ†Јя??ЧŸiY4 ЭvfXх^Џ–[nеry'ё:Uy8яWЁnˆK˜;иФВ,жŸТm&ыб7AлvXє‚ВЌ6;§>лчZЇ†[ЁnЈDЁ(Жp&ЦжCc™—рO1GF?R*4mЭfЅG^ˆ…hэWyЊИwT75в4й@Ћx—0ќКЄiѕзЩ|эћЊeyІАВЉМІ‰JТ/ьiеŽ2dv ti '7AѕБјЯ$„tozМg“вP№цRБї KћЋлyQœ;юbбЦXеs‡lXu]C*Й…œ]ˆeG{вRЩF§‰ уњњ?Н‡Jd<hГМыLEљ‰Ђ<Хы”™іВ-!' Кјb№=@ої!W—uк ќЉ:8д11EcgHДИ"{  yїс}ѓн'šІ ЫМPš$4є=Ќeсa3aq{—Ÿэ6†ЊЊ‚яљcАћэ, БzF­§ОyžЩ’ХmПЪУЛЎЃišqЈ‚Znам7›н1‹ЊЊЂyžТ—A1/rmhЛѕаІ2Э3)Ї–ХB]зBkMUu KБD=ЬѓL " ™B `tжЛ–\Ви6>›Ю‰,зJ“&жKxњ`Ьь=2žК†gDфy’ eHŒИНЙ @юЇPћЫДР*G[ПлЎыа4M`|m,узuцeЦэё6ѕŸŠešЇUиг™GХTЙeYь-2Фт6<Я‘e™фŒЃ=eˆх1К›ѓ5†rыВ,“3<<”щЁ5Ј€ОCžхШЌЗ1z+Йєџƒm6gы9 =šІС0 СћФC“ž‹_“išаЖ-ˆDјоSБGћ4Э0#šІои‡>‹ЧњОПwGдMЉ$МъSЯœз†TЅBšЄX–u]cš&\]]m@зO}љъuш |ї§З(Ћ2 >6уSО0^=MNѓxж] gЋ–@сймœw™A­C LŒ—™ЁˆT Ѕl!_DкјY ѕЌ зЉ•xй‹eћќљ3ўітўѓ EYрx<>‡+Мв›™§у_~§…ЄTVщЙЧuЬВ F|љђ† В,EU.iб=*|$ цiТ/Пў‚$Mqs}ѓеБф™M~_юО€™‘чYЏ|l-іпёЛёЏПўŠ<Яq}}§ь8|,E^`GмнпЙ„“ žЁ"ВЦ’bш;ќіљ7eщЕўžuC,EЁpКЗеiQ Я­0ьsрcОa+тŸ? \ъчJTљыyЖmэ1œ€ВЌ6јЮЏ‰ЅЉk|Йћ‚уееІBп—Хв4 ъІ†АZ†Hг”НЌџѓb‘H…г§=юO'\__sY–єlI3ї•І)~ћѕ3оО{њ?§Nj ЇЏZN@Бп&›Б—bŠ›Ћ5Аѕ їт 0GpЭtQДVŠxL,Ф7Зƒdкь>ЇjОаС&Уу0тццпџс{ќ›Лшo_!hх›˜ƒšВД>—v/gдnи#э\,Цэx\kt(џŸћеv-Ћ;VЂ’єКxЛFo”ѓ5_MлX8H!ЁTrШћј цŒП]ЋCРЂуйW_mџб:Ї%JA:™ДЏ‹…нКЌGЬYЯ_Kндт˜N$єйБРХЂ—@›ч9–F{v,^)˜тњ“6`BкеoмоBЇ‰)ўљЕЄ$;о4N8\№уŸ~ФчЯwHЄB’&р‹Ѕуv]јџчьЭšфH’4БOЭoїˆLм(UЈBWГЫR–\’Т?Я‡ЅљАЫYrIŠŒШШМг=ге]@F„_цfІ|АУЭ<<‰FI ‘ъ—Кšъw0Ѓ,KЬѓL_О|цWЏ^“жк:нз mX7; с›є‡ю@g>ЃЊ*Ž€ФєШ’$Ф2Ž#NЇНіH‹Dл\-%ш‘dŽLdњUUЂm;К\.№XИЁQоЅBп_аїz§њ•™W*Ž…Оїі—mл ЊjъћЕcJь}ї^ХUUЮЇ3ІiРызohАсЏIЃ%ЧYˆpбgj8QЯ@}{ф“ Њ­bєТc; 7ЖBo[ )šхŒІjи-ыщkD2T%~§ѕЏ"|їі-Nчg"ЃВ.oЃфx33i­ёђеKќјщ# ЛєЅ8kХ%еŠ€ H7€ WЋS\ЅЉрЋ•ШžК|ц\г]#ouztгZ”УцІЩМЭjьћt HПvŽXHdрˆдЉ‹ё•ћ­ŸѕъE.ќўћwќуЇа}м_к‘ƒ@Bд6к`&Ђ@ж6lk7ае6р|IГ,уišxžf+кZ\UЛF;пš…ocбкАѕ}\ШƒfoФHуyžѓ0,ЅфЂ(Q–%—EЩу8Цœ{о9>ю™ СZ)ЧY–qQ\…7šтщ~3 њОчE-Ш‹UUБu&ƒїюcчЩЪБШу4"ЫrEСEQВВОЙёqй;?!д,ЯалЅEa…мyлžюnЗ@ћ‰ѓ<уТAkДвац*–н}ѓлЙ\Юl s^ф\з5“9s &Оužэ9Ъ0Я3ЯѓЬyžsQШ‹‚ ,6зЫоУНъєљ|b[ оšg9ЧBАќШq;ސ‹фЂШЙ(JЮГJЉИ|ыšc7(сV—он‡sLцЏЊ ?}њЧЛцiоRАЎ>чСЪГœ1Mюююнd-ЗдЉqТ†лЛг'у[шћ•5щ†VкЪ1a…кьЙ^Хёф™`ЫХNЯІiBщ@Г1P5e=W^ѓMЛЩol UU€есыQ 8fdyŽqЁ”Тёx&…eQbžц ЮpыИъGЋЃHАP_хxОoь†ЖwЌГ,У00ЦJv УˆІЎQф–:ѕѕXь9’Rbšз‰­ЙЬnO)qѓ@ћО`ЉУ0'=рн;Ч1MвкLp†]жWƒuU'ЂБпv€<И\.N ЁУ0Œ8ДэЫ5–Ф™oуHWІбъ(ЖM v•ZUVAБшёут(o$pщ/ШђмBЇЦž?‹lЉ]ЩіxѓцоМ{ t5 ПBа&ŽnєИнxaЧ>Ž’I2]СрhХ81„P R<œqCо—CєииЇъ„ИћuŽЈ$д;O3Нљю џє›ŸX.’]_'ъ%fЩє~Йœб4 9ѕR‹ЄЊЌgЙ'œGпП*]`$„ iIЉ…КЎe!OѓDyžSeнЮШ=ЩbГq?‰'c EAJ+ъћ ЕmЫо˜мїЫ<яе‚€§ђ=ВНЗЧ€…4N#iЃЉm[ˆ-hЖ ЊЌh–sМœЃЭ2„},Ы"Љя{ъКŽ…ФЬЄ”тІn@ ’R†з§т„WBНƒ“S№Аpђщ<Э•U‰Ђ(HЮ2,AР•c3kc EAѓ<ѓ8мuG‘2Є”ЂІn ’‹=.Б!ЖЩp™ˆˆ‡Ёg€ьd’ ЯrІЊЎ(Яђ`ююЏЗшZqЧкыf6qзМ(k­б6-ГaZ,pляЂX8o Д'!Й))ЩYR]зД9ЯжЄ;—ћыЎ,K†Ы"Љы:wМ4Дбд6-mX)хЗї<ЭЬсН™ћЁЇ,ЯЈiвZг,gjл6pЬНщН1H .Š‚ћсBJ)юкŽмR–иXС­ЌX‡пЇаo‹ФNД6м4 }џёЕћЗbtpHŽМ=ЂмŠ!0Р[Лq›+ЎХV8‰ѓ ‰ѓвњЏжч.с<;ќJм/=6%j§1%ekД”ѕЅi(O#N0жыЯ5л‰9њœ$фС”џ ЂŸ~ѓ#Нz§’œF\8QўоЉ Ч‘Е6pe?ˆˆхВАcvpЄгчЯGŒ8Ъѓœ•R|>_ИmЛИˆˆg9seMЌyЖфїx у7ЩŽђСУ823sS7~ЎФRJЮВŒЊЊbgdГоMД’™й%Ў…/— КƒгaГчE.’ыІцLdь ™ЖЫf&сcF&€ыКіv…<Ы™ђ"GY•М, %7ЎbсyžЙ:^В‡№"4MЫюХБиэИs)„`x$ШХbїGJ.Ъ‚‹Ђ`ЕЈdЩLщRбѓTiœ&:aЈЦЬМ, З]Ы Р'ѕѕ:\ЕKj< gYFu]ћ%I)QV%хyЮ‹у`Лы?ц’њФХУ0№aYhиАвŠЛЎcc /ЫtєЂыФ2Ц№0ШsлjqЫLH)ЙЊ+ЪВŒ–ˆюt2уDЪEQ№хвгВ(іцєў!hcБтjYТ2д"EвуЂЕІaЙ, *Ы2lG.u]Г xЯжДˆ}тZл?DєнћЗќђѕ+o>яž`їЙЋ7жћž™ьп‘з9ЂмБ]sєКпVšЗ’œоГ …й{фš ЧДлдwWЇ^ђЙѕџt D>џЛxа2xђ$ˆЊ@)%ž=†Ÿ~ѓ#DFД,*‘—BЄЂЕІѓхLнс—ИjžgTuEeQ`šF"+0?…ƒpЦ0 Dю–dŸР„ЊЊHiшm1E yeYЈя/t8Щ- ].АБИ •цyђrGярхŸМ˜UUЋСаВ,” AUYJQ\‘њїцn’= ŽЧDjŒ`™mг’“ц+Œp\|4Ž#хYŽЊЊ(ZzбЂфYFeY’ЇnEФЬЖњ›&šІ ‡унv§BRЎеЯ<Я”eY|nМЋŒ14 Š<шыQD‘Ѓ<ЯЉДBА e-nЮEqi–39EујїС'š™-ЅЬЦ‚M,ЄЕІqЉ,Kr–AfY*Š‚Š<'eU_мgгыЮБrШЖ'Ž)zлсp€бš–eY0О-)ЅШ‰Е‹RŠЪЂАJDіИаіИјkїв_`ŒЁюpˆ 9ч7:Є”"ЅTиО‹…\яŽїwxџУ{*ы‚ДвыѕрЉi‘МU”3€DКЪцџўН<ГцˆjЛЩKH3eDѕ‡ H…лЏ–й‰Р2qЊсЗъЊРј(ф˜ЎжіёжюћуwiЅ№гЇёін[є§%щCzœрдrѓUY†FlЄP6l%‰˜ЁЕђЪIoJiХ§ауpь8Mpлq“=.ђќЊ_ЕV9ŒОяйп qoХ#њнє‘=n,RaA,`Žо(ЌXхŽyЖКnYЧ7В-KЁл#*Х›К№є8•ѕQиSЇКЖ YЮОкAмTзZs?є№`b?+eUCЪВdm—М=зnр‚YЮ6–ав[‡/rЖЂ ыЯтЯ‘?пyž[FЭ8p]зœeйю9"ApJу1}ŒуX<+чp8lTMьпRJ>ŽЌёН3оФТNЭврВ,lУЧтћМEiE{by‘s?єаFлdЙjbњaЕрx8BiŽѕ6–YЮphізOа(мЂ(А‹Трн‡я№ќхѓ+ŒЉБ]Х™’œГВ“№ђŒ(?P$†pY|^Ђ5#њм$IDНС5Qв&эЙDё3aЂ‰жёЫЦЕ}§yN$Eќ‚ŒЗ7NЉлŸ~ћ‰›Ўй“щaЅЦaфЖi9ц;&ЩkžиJ§T)wp$ѓЫљ‚ЊЊИ,Ъh0КоƒЦб€œѓ•_к$CЙHŒгˆЎы˜ˆ‚|—зqPйђD žц)HžћdСЬ|ОœQз5гkŽМRн„XЭMUƒ {{ФђџГœ1Эw]ЧКSјyš'nкBdь’WИš|;_њ 7MУЎЏЪ[NЕжšЕ1мT5Œ6’ш= rїРX–…;Ї<ГЫ<Я^%‡Ѕ”$ќ1фИ†ƒ*r№ГYcQжuUщљhB,ЫxœFhЅб­bД?}ђъК3˜х"й_OЎЏŠeYxœFnл–7Š ІЕƒQWЛ jќрe?вFsЌ0ПЧ›[uŽте’<-.wмсišиЛИтіtGЋїTЯ9VРю‡` m[Фз-3Г€€ЕА– Jih'uх+Q­5^О|Ся?|ЫХ1ЏВныНўІ5—Ф9 ЭfзZЅџC9IkЙцJs"iAђ‚Јќ(ЄgняГžќ‡№d…ыxbЄ…њˆ>~ŸHCЕм‡T-їŠЧъxЫВ Љ›РѕŸїS?З$\0i•мђdіYЎR?БМT&2АБ:“ŠЇd1qнАZ”І2m+Lг&y‚ФOвoЧЛсх‘дUX*jƒѓщчCБMЂЩ$O+ЎБL/4рЫ8ЎЩboЊЛЦ2[#чзWJЫВрrЙ kЛP™юХтЋаЎщB"]ЅЁ„Oi,„Dba—Аmл„Љ}‹чъv]‡LЌеŸз6I*ucа5…Хh˜m ƒХ.њ$ъc‰…МЉ{зvAОо_з^*jGЧф!ИE]h­ftMЕЈD0‚ˆ,їЉdV,kdЗ” ВrI,Ж%€yžq8жcU€~пДЖЂ]гBЪеаЬƒКn№У?$&GaИ-LБCпxjђ‘|EO™л—DДŽXзкМF§_o№Ыж№:HIЫ§ЬNВ:ќоЩ-ЙI”ћy•ЛŠ­ЌŒ6бoџ3н?ЛУ0Œ”хMгdU˜єЮVФRLќВ Я3ђD|пу@QтZСL1O1lд.љZлгs*ХD–ќmДёI6Јйњ}ѓ"Н"ЙHEAEYXп Пl5LЇг‰кЖЕБ№ЊšЌ ЂЩЏ\$КЎѕвLж* aД:ŠmгF§CZЯзZ5‘\$ЪВЄ<ЯНЌ:rзЛК\.дue"‹њzWC?х†эi,‹BžчВцц` iZorП?œШ)-жuœr6cB\§ЅЧсp дƒЖ-r78ј~•_ъУ@жЗЄ^+H;ЧЇD‡аіпЈЊ+BW r“lиОъ!yЄ;jXђјі‹ЕaшШ]‘{P?єШD†ЊЊШS.ЅќмeM^V1iе­d”Ž!фDRcІp/љX”R`cp8XOmŒ—щЧ0 p=о8 Лы–НЅ‚W=ЇКЉ XRР›ЗЏёцн› бїЕ9ф38ЪN2=•УŠюсdЫaEЧ ўњ\сNnђAЋБОŸ9в$кЌЄ7˜wŽWкW*ћ‘ён•БIќtNљ#ˆхkhKЧHqER.xѕњ~ќєVтн8wЖ3ъКAYV7$в'ЮьzDфЄ~М4E’ѕщš*m'}ЇгЇм‘‰pЙ\аЖ]"ЧЖA;Б8‘VПЭ<ЗТуђ IDAT B4usѕДмл†R*\ИЪ- 3.— нi/ЩОХ•Ж\$Ч#ќ2_Xw6g–SG|!Т•РоЊЅˆ"ЯQ”UРj­1 ‡c"FЛ“TЂ*єЋŽЧ`”-„p>,EbМН•uк—Ђ(P”фВ+‚aДНЬыXшJЬк (ŽЧDРul_Е*ЋXhїz)Ы y‘yЉYޘ'лЫМ–ЅК>GЪq{Ч#fgФdŒеQєfVW’q›ыпiTe,ЯЌ”W, RlЯQМO>ЬRB)kі§ябДѕjp~у<%юDOрЖ^/"Зx ˜ЧЦ›DrХq‹­rк›vь )XхэІљњ B‚Џ–Еёч@+I7БkŠтt•Œ6јљw?уOќ|ўыŒŽШ^U%єŠ˜єПЙЋВ„t щгyѕ}/єЧРХУ`Ѕ~Єk{~YO’ЬђKX!ЊвЪуˆѓх‚Лу1сŸ‹“Љђррqœ 2$РО‹7ЌЏ‰P•ŒбЦ—ў‚ћЛ{Л|s‰ўkлЇ mSуa‘˜І §`“hьЛќЕmЬNQИ*‹`Bеї=ž?Ÿrž‰mгтфЄе.— ŠеŠђIБLг4Еб˜{‰a№тљ‹o‹Х-QЯчГЅGžЯ‘™~ђq)ЫEaЯы8Ž˜І /_М€VњI 6ѓ<лXъ}С8 8jхъuќ”XŠЂ@–й‡мляотх›Wv№Ѕ‘јЬщ}ќLIna—ЦЭW­ŽWDN’`"ЪтTЊh8?И…эˆ!0`^aЂјп †рвсM1О"иrD7fЖгА‚ўщџ‰џЫпџп8OxёќEаэ‹`|“Dя№Nu]ѓЏПўŠОП iZДmK™Шxsнт_ЎЪг<Ѓя{Ї‘Ÿн?C]зyk<*œрсUYё_~§‹eYд Zk7H;дЇЋ}ѓЋІЊ,1NњОЧ<Яxіь9uхЧDb5чYŽЂ,щ/љ…чyBгДЁBн/а†wЬёTОt0“~шyž%^Мx67xмW7X‘ШѓўЫŸБH‰Жэ`Ср ˜ЗDќy.Š‚ЧqDп_ —…^ОxЩY–‘Чкн8&и c‘б/Пќ™ЕЕ Іi=є) #Ы28_jє§JkМ|ё‚Ћ0№˜pCˆXdс—_ў•Œы 6mУбБх'Х"ьІя/0ЬєђХKŽ VќЕmy9ВqБш…ўЧџщПч—o^в"—oM7ДŠDšМЭ ‹‚ЯCˆDbеu6В#†АСЦK№+ріuN‹а(ЇS”Рз*Яс%wФ"ЦёžЎD„m3крущOџќ/ж“Ё,рNтšі1ŸŒ1˜чйcєP”…)§Q<о&yряВHTeE.–­pУc„|[UHIBd`cP:oˆихэkэ\fа,%D&  žЭ]Хђ Б2 ,йЊЊ[8=2-cm4Щ…A™€” 5M”ˆo$‹нcЎД‚q •™Qе•Ÿ~г&vДб€5„”‹ЇЙ‘‹хkтс5 E1ж*T1ЊКРOзЦ#Л?ўЩ/Жˆ YGVо ЧХЫВ0 P U]САй^ћЗХ$жі5З•вЂю,.“"Щ8Кё@ x57M ?ўєЯ^>#_ћьВcEљa3уѕ„\оrq#•Hœ™6ДDЂmBцЃU””)@ Xвд Ђn 'NDmПмB‰јЈw{їй54ЩcЂЌЃдЙЦ{„ЋY?ъТє’вОкLњšzeYрќўУџќП`gД]ЫZiJмпПrƒхyСг4бbpMrО‘Œ§~цyСг8К^QI"З*$к„ hЅйXЬ™Г–ЬРkE{kп`m–g‡‘ЕжTVхMnюВХhйXHkE-hъš)ТЙ9Ѓ': т,ЫШO[‹ВрLdФЗ‹‚Ћ*ЮO0н—eђ~C_Ћ˜„ѕжР0є љЅ+ƒoЃЋ˜<4ЦhCJ)жZЙ*ДГк…‘‰ $C?(‹J_9?krxї‹бд4 ;o№[…жеЙV‹Ђяо‡џюјoqwwрeQ”NБWAг5YЦ#‘˜кЂrdщЭQ^sUЄЌ†:ŠW§+Š…œзaŸDх8m2љюБuу’с п|4аЮЕœК+q"ƒ˜66yoЭIщчМ‹жwп}‡O?џ„ќ‡BžхСщў)ђz}”кbF\оk9,JеDНпoЈdWr%3Ёа„Hчк~пк Є€NJљсїZYпгп§сw|И;Аœ%В,  ЬЭџџmAЯфMЦu]sUVь‡Bо|{?чyС§хТl ŠЂDзР›ТK{mўНн6Рр'œumЅЫ]%ч€Рэлd‘ѓх| тЉ]{рˆСР_й—р—k ГgŽдЮ‡V)­7ŽKл !lB?ŸЪВфУсРzIb И>vг-!D‚ЬDЦZ уРeiНyНŸ‰?ђи"Ыхb&U…CзAYіо1хћФЪI‰Uu…ЊЎЋ„лЭы Вм2<њKA‚ЋЊBлupЄ_йFl+sЄЊ-ю5Т€>v­…X,&д`ш{acišж&фѓњШЖOoпОсяоНaf“€Иc3­ВUknp~ŽБД•е0І} tєљ@ЖˆфА(Ќo9DЧ9ƒ)ё‚ š[іnp#іvНЪњmд`ШсpbЏєдаце№Yџ^о5'&$ŠšюŸнгЯП§ Ж=НР9 Иѓ”ъёmЇг ЂІihš'ђј4ЯйєИ6G2@ЄИЇкhЦ‘*зГSZ9L‹ѕrVŽ[†dŸ&7бЄiВ–…[лЖp&ёы6VD;ErUфbŒAз9 ъ‹ƒy†эpX*Š ЇфEЖmй%рА,ђгё„^Еж]Х% х ЙHдMƒ<ЫY.V'ЏУьмптц%oZк~yі№№РeYБwgsБ03ГчgY†qёч_ўŒ_~љУ8Е+?6cQ 7uу*œф щЇ­ч‡;.Б№Ѕэ[P7СХŽ-[Ёі•њЊЎкcœ…ћFПжuгxК еьkZŒЋNerНGз [j#№pz@гЖ\V%–EZК^]Г6iѕФ—9—Йѓ7NћщЋg/ey†КБбд$ž7н5[§­љ|>swшИ( H)ЊЪЎ‚Ђ)p8Яq.0к Ы2|їў;~ёъŒс•ˆ}…рЉeј†P`ЄЖИ1ЖЭ)k~иф&NыДЄ#хЋ5Йп‰-ИpW aЕB;`у- …ЎЧIДћQІІЋэЄ RJ№EJйЩоўэ@‚0MѓЃ\y–[w{!а9О#;Ѓ Ђ(gYЬ“Lv.]ч@ЋѓC­5”ы‹(wуoН_Џz\чђ< R№>?”‰cIˆщQ,“:mKРU18‹„ѕfиЇ!љ^цУщUYЂБ&щ.ZgткCжЛкœбPюБŒ+hv^1‡uчЯžуйГgAШд/…OЇдn‰gБvј^:Qк8–ј\AO!0Žƒsа[c‘@мuІyК:&ёq 4Ыѓ mг t§1cьrоЦ‚ФЏw/"Т8X }]з^Р Ўfшыa}MпЄ”fЩчѓЩRјВ<ФТlP•И:G{q У"Bххо0Љ( 4ЕѕjŽХmвhмннсУЧШ‹м# "OоXк ‘-№f0ыkє˜B –N‡ФбV иP8ЪG„ J—РБ7Ю6яЇЊфМЩщ[ІѓЂgУє;(йжеПWйЙPIyі‹mФкЄёў§;Мћ№.I<[с"/0N^-ЗI. ЅM@Г+•&/lšі‚.ч3ђ!]Љ" Їаьis!ћЬ„@л^'ЏM•c .§mг ˆLвУq‰0ёŽ6Iє|Й ЯrдU…`OAІY†kqšЇЋ ~XZVNЎk‘G !6ьмь*Aѕ|Л­4ЪЂФ‡оуюўh Ї&іЩH•Sp3ЎAw+ђ˜~ЭЭbSk6ЄŸsї;qM.!м~эkаЋЇў!|Ѕ6мљОє5O^_–eUтяўЭяQVф,“Fnx:‘ЕtЬѓ uнЄфіРЈыr‘и“ђД9Ѕ5кUIФ]ь–(ЎДЫт8‘Ц7(3Ѓьр#іŽнJ 9ъ5^ЗS.— и™пlcБU Х'вН” [љџВJWK–e(Ћ2$JИE„<Яq>_šf‹‰дЈ{АЙ“щJдM†~@UG‰+:чЫВ /ЌI–WєIeж y–уt>Cˆ,IЂ>‘.‹ ТRЪфЁ­-Іiš0ObQ ЪЂDn—‘;UО@&2œOgфy8ЖiRŸ‘e"x-Ї6 k,‹R$Ъ@wc†rџмй‚nЏ[_YŸO'…Ѕ№mc™чynщёyŽ † ^М|їпПƒШД6;€nмГOљнз`ИЗr=’S‡yŠ€f=N’ŸЃEt4HќG8‡NіQ"z?і@oо§ьŸ’FМ|ѕ?§ќ#ф"Ё•єBŠТ =jЅб9oƒјB Єu­а6 иpЂDВњ  у€Ђ,dfНHќђг>=лЖ KОј‚є‰ЫїњЖ‰)јƒЖia4.щ§{ЦqDYZo[Б!а4MšМЂžащ|`%єN,J­Щ+ІељdЙъeœFдu•№ уФД8Ё†ЊЎ‚GmL%Ыѓ'лWm›(ŠcБ`тЖnТ2{Ѓ/jС4Э^_яŠncБ№В*БэљіФУУђушЛEtVW8P4№У„uЭmЧсЋп%QВвMfИДўьжж5n_§›ѓVFqєŸГ№ŒeYхњљя!ёИ% +Ѕ0—e‰"/8Œ\ЧфŽ{ЫЬ‡ЎУ<Э.6ŠЂрKoЭЏV›-MѓюЂѓ’Nu]3y6wЖВ,ѓ˜\;y§d™ь­^ˆИэКэ2‹‹ЂРЩ9u]ЬТBАЦЎыšњU.и!Œе&є:„Q,юИ+х/Л%_вщШѓ Їг‰…ь“d<0JDi™й!‚(-<|fžg,rA]5^яqOлr–œч9šІЦ8ŽIтЪm/“ѓ"CгzX"%ODжЁŒс’›‘ЕЏ:Aй!Lpu#ЂЭтƒXЮ3—EЩUUХІ[lёщє`Eь€!мЮБNЅП>ъЊ†IЁKB№4аJЁЎkˆhEУ›^з<[Ы†Ђ(yœЦ qINSёсє%ˆ&јыšгу–ОUUYnБ1Aо|ї†_їŠйiњѓУ‹ oJ *к’h…Yњ­‡Н8я5Џl 8ђbŒ&q‡‹rдš‹ТіТчЬо<ьјЊЩHЗІxB‰J_-_щ‰‹f? ЁЋеїuiЋ…чЯŸуїј-–e U`žлeЋ1fЋ–{еАіjEYђ>`AЌѓlе;ќгА”lФ&uвZЃЊkПд'+v&r-ŒЧXўИЬrF]еT–ЭгL™ШР`ъ‡žЌd\Й]њвќь—Лuн’ГЄВ(шћоухыФЬŽ-ЧїЏЯыК;|%{ёЧџор7Ўp)8ўћкЖ•х%NА‚+ކ]œyЂџВХЎм Œю§ЬЩGcaJЫw3GЎ МM'WўЁМѓyћк<Я8оёћ?ќŽўђчП№ВHr&/АLuEВЛ‚œ[IЇ0Дб8_.4ŽЧ#ДжЗH№ЩAžІ Mг ЎkœN'h­q>f–е№52О“—ЩOP•V˜ЮІiЂЛЛ;Јk А]qбЖ-UUЫхBJ)œЯ'GХк‘нЂНXІiBžчˆфщI.їwЯтžмЃТу8Ђэ:”eщtѕ œO'ЪѓuJќЕG?3cGфy”ЦxЩ*ЌРьѕ9КК‡qDзЖ(Kл ,ŠчѓЩњA;ж=сИŒгDy‘CHсН\HƒЊЊ‚РьзКѓУ0 ы:ђЬŽ,ГhƒЦ)”K)‰ˆpcTJQ,сœЪEвЅяС`x&Э#тq,фГЧiТЋ7Џ№ў‡їШ‹bUћNИNэeУƒ[EbьoђЙјw[ЊЛз  @тЅ$W\cѓbЪэ*К•ЦЃRиŠ!D LЏПg™ЬБЌW7v’ДВЫч+=РЈ=шНИ,ЧcjкpŠ9F•Џє8›7HIGьЦ0ŠЂ Ѕџ—џуџТ?ќ?џ!žн?ЃІЋŸуDЩМ ‘З9гЉpSєНлМDб{|NuюМ”ЇWн„сPHJ{ЌцЈwШ;§:^ KІx-MWяЙ:ЅиnУ[|n>ЧVeYдMO?ТŸўљO“Dхаџйг§Yэ– BюяяЏ _ћухœВ<ƒaыQзЖй-žИ ‘ePZCPф9юююl,Yі7ХТ ДMk—gз‰ы6‘>Ы •BцдjЪЂФнн@ў Б03„K‚Ѓm;ыиїБЈeAžхv QU8ŽСћу[bЩВ,ŸЛЖ npпа}‚ВOYуx8гЂП%"Ў; (JhЃ,bэ‰Ѕчи hлзЖљжXˆЌЬ§лwoТф;Ј%ŠФ›чD2;кмрLЉўщ^^I5uvSџ–МAБКAВРЅMЮŠ*Р5{Eк5ы$ŽVxХЊlГЪeY™пL„зlL1Y†"@ђlцˆ‰Є cІˆWЭ єЁ :hчyŽПџOџџџ3uV`t]Й/љУЬ"ЫHэpp9ЛЪ…#aаэvЖе­B˜чЫ%qмТ ё›4 ‘y˜‰,˜9ђ>оCŒю-е8Ы2B№љr&A"0Pn OwЏI!2(НРh!2и{\<ЖМ]Њхy"рtО Я2Nр&K’­ KmdУ†e 0n—w ЋH]b8_Ю(fя {G@kH/жЏz{Нl…—ЎbЪГ† /T']/U€‹q Зрk,З*ПфzдJS]зјwџўПСЧOсD1Ш—qётЋУ­+КшvВXAхŽжfW*JЁЈ[+FŽгŸ„­ЖeцŽŒ’зТ–ЇGРM"ЏšњQИw*ќъ8ЉъpE_AВинT“œ(‚ТjРм&S8JM\кЉ89цAŽO?џ„§гПтѓ_П экozњ•e‰гщ„Ld8t] Њ“W–RЂzмннC;j›g|K,_чšІ rLп‹w!{vџ ЫЂ@„oŠХу?žPV%ЊВF?єи$Џ'ФRbLгlЅЁЌgю7Чт}‚=XytжпKY–шћЫВрХѓ˜gЄбО5ы}rpSцљлbЕSИ\.ж^ђХ G—ћЖX‚ƒоауxМƒqnˆп‹­ ќјщ#оОЛѕpО_Іmу‹(ЈŸ†ДF%Й”uГіќіŸ/Фœh (|џЕpЊOФis.JкШyЏ€ръя”Еœ629@}іšœ;ŠgДѕИъ^a+[ЙZDn(.я‘y–xљђ%~ѓЛOјп?џНŸ >љІX”Т4ЯИПЛѓdњ`Cљд‹бCb”R8tф,оžИВOО‹GцЯrСГћЕо~ыEэГžЏ<Œ†сщ‰д {Q8.‰eYЋšvnPkjирюxФљrЖOпKU–И ƒ›фVЁЩџ­Б€`M†мя№Х|љІmјst:Ÿaм1ВУxнOŽж(+Яrмя№Yџ ­5ВЌzђyЖЫ0[У{gЧJzЊЦ$Цн§~јёдu)—G;˜К‡•ЇŸЎИ8M)iђEЂWуеЧжЧДѓ§QйИb,x-)уФхAэћѓБ›g М/лАТT"пjGЧЗї„cЈL2FП†>Ag™Ѓ1гЂ ‹љ№—ОOоƒеbœ.зСЬ4є=2ЇoX`Ч„ˆ%pBяƒ#јЗГrДзауюxgMdЪмYŽв]ыCЮС‚тЇЃ€1†њa <Ы(s*'eY’ЇкХБЄNžцу4в4ŽИ;iš'Te‰Ьa ƒїptypМ]з?4Ц`{yюћЁTeBЕ‹х‡ќ6ќkО5 ф,q<ƒЄ&‘ šц‰„/9кN‹ву8 ,Š х`q,ˆЖA1?млmі—ZY‰z;-oІyJФW9jОФБdТкwNуH>–,ЫЈ(r(ЕP|НDв&Бudh œЯg2ЦPзu.–. 'ЖBЯ>6ХЌižlеP–хШВ•ƒ DvДŒ$&РQBKЇу IDAT… їпПУѓ—Я tЪBК†І Eo<6bDWЙn№ЄС”ЪчmsХЃykч}›<QoNщSртЖhЁGљМзлЄ+OMњкc1в’ˆП“Оxtі}‹8јєѓ'Tu™™Ч‘DуR+ПДHŒгˆУс!ІiђˆўН:6›F6Ц0є(Š24ейUUыЩдп6UуББd˜ч ѓ<сx8€H`šF[ёeX"этpмЖѓ,З.oУ€ВДT5 d&дe mVђm,и—qАH‰уёр`GšІрь­|>mcё№™q@]WсgЁ*kmv 6дEŸD•R8:jу4MоѓcїИlc)r ™ІЕ“нZ–‚2Te•˜ЁГ3ЏІэƒ,Яа= [ААжкZЏЖ ЮёЈ;ЧEJiЇSZ–™ШP–”ігWJљў›ћ1Ы2\.pТ Ы"q8tСњt=.М{žй0^МxŽwОCžg0к|u№GДwюgŠkд нHАxZŽ@<ЙVIЗ}п ЯO‹”;?^%iЃѕ—|.§?ШxGmН4п‡пЧє–@є‹ 7ыh{GўГБЫyцпПЧПgG)c7О?@"иУ—ѓ™ЋЊт"H 6ZsmM‚иMУxиsэVћY–ё4O<Ы™‡Cˆwš'.mBфižYѕцhŸСЦv––|Йємд gyЮ ‹1\з3x oќ6–iфeYвXІ UUЃШ ЏqЗ§| AEСЫВpпhš–…Ш|ХlЅ­˜йХBыqёл@DЧZkЋ эп4OvГŒg9s GБРи^&K)1ŽƒUвqД9 s]зьŒб9ђўˆщlV30< ft]ш^г<…эЪE†X8=.`f.‹‚чiтišИm[vУG{ГБh­YiЕЫ ЪЪЎzхОя"nл6М>Я3З­uх“*ФЃ$ЪEa}eф,Йk;Їюnc!зu ЅДжœhL:ugwнЁ,K|џёпнБ(Е'щЗоЏё}НZАGњŒОtY=-ќlЉi}.БЙOђG’'hћ§1€)Ю^Н Ю9ю§ щHю(Bs’АW{Й­doўиПjл3ŽЃЯoЁЗх+ћёЬЊ‹Dэ•“%7‘ж†Š"ЧЇп~Ђу§У03…йЊљТ*їNД,ŠКЖЃxI=Я3хyЊЌШU’A:fж˜Ю—3ЕMч'ЄAнб™‘V%X=Œэ2‡ˆHР4MЄЕ&ЯJˆc)Ъ’ЪЂ Б`ЃњlŒБ€XЕахв“gТЌБ0”’hšš`)sлхgˆ…ˆhG2Vy&QСчuUQžч$ЋX‡эcЈ( ’ѓL§аЃы СЊ /д4-13Щ%Фbˆh~4Ž№*8БK5uMn"K‚Ћ5ЛЅoaЭРiœF:t‡ЄHє\юЮ2[hYBPд"Yї@У8Q[[%oЋЬв*j“ Л юГщuW%Цa YЮ8t‡ˆ5hЃ‰FлЖPZ“RŠ’j2ПA§0 Ы2ЊыкЦBD† ЩERлД№m!џ'r -Уx§цо|їЦОf˜(-шњ~дHућћJ:RxіЬшОЙ7X•"4vЁ) O\Ўт(ŸyЖGy#Ъ"F,1Оі d А7Мn)t6к№Ђ–Xx#УУ4ržчЈЊЪ­!|,ЬrYP—2>–dЙшŽ їCЯ‹эerиw_j­YkCw€ZЛъ˜Ѓ ZknЛяј€ЖkX+НYiёnшячUЗ„8"rЌyДz ѓYіqЁф› mW‹!6кHTЙМB{,GNёєѕјз Эє51BЂќќU1 dЬkpѕоуЊ2^$ЦќќЛпрхЋVшв=IWb8aЧHЧєьЊVЕ /хДUhжZуrО s§УЋЉІ#тЗЅŒ{gЬlЯАд(‘ TeЫV30є7"Ea…PћО‡K\ЛVлЏj!%Н3Žažg:qoаЧо;Я3‡Й)ЙЉ/kЅiЎыšђ<Ї5” \яŒ„ф&юёє–ђ<чqiY$Ч`ŽD”LэШCRJ:Є]ТFБRŠЦqd;ЯЗ"4k­Yd•…ўsфЊ‘чCOZk:ьE’2ю;Нъ9)ЅbЃ+2к№нГ;њ№§;.Ыв^ŸБЋ\Когa6”ДŸќљ№ІDС7<њ,ёUnHХјК­цrФЕBŠІvпŸŠ!јя­ШbсMŠ“вжsCзdў.uзЅ'ХхщjЛДц88УsФƒ r’!†зќw5‰А?LiSбВ7)­A›юЂ|њљ'~ћю Цa &<№щ|ЂТY0†i†џвhйшЬ}ЈЎ›xAB^–…ЧiЂЖm9‚žХЫ?пŸч™kлјЇyž™ˆи\ѓщ|тЊЊЉ(ŠАŽGXОИс šК K.ABžч™ІyІЖыьwŽbIDfЙШЄ%pО\`>yшњ‡ъцИ€™ЙЉkRZБ1†…,„ qyQŠ\,Œ•ЗЪHa*ь—іЬ†Н%ЇбeшЙmЛuHТkS+ŽХ!Рu]ѓЂ–8†ЕбфОc;4ђ­вZгb—іЌЕіrdTфI9г0ŽмЖ]8П7bж‚ˆЋКbi™иУY–бћяјХЋфˆИіЂИ=сˆ§хN…C ёjlфялѕ–ŠЖ}=ŠмУ@ляI–РНђC’ЗЂї!ЩahТLbЃвŸHRm!Ыь%lpжЯ­џЧ–|џ›! aS%юT˜H*МhВ•гкШъ ъ›ƒ™ЩhЋаёѓяFнV4Ž#хyNГœI.MгТЫ§PИŒ|Уи ;цyFY–ЖBZat:ЈЉk”EW/№(ŠHnŠœ4uЎ‘ПЈХJfM-jЁІmФтGмi5IГ”ЈЊŠВ<Г’UЙКœЯgчCQxќXtsЇђPЎ: Ўэ<§В,ЃišHiu5„ёSШMЅB(nсbћ‹ЄІЫљLm*ы€_уЈЗВV#ж†Гk;RкV*Y–б8Žиa(RхЋlЖьjš")eР^њ КaШнЉP2cЋаЎэhQ Œ1$„ qfjš&д„эЌБиAŠЂЖБЧаЩnбВ,4 ь@(’Š л№WŒg7kw,†a>–hP){+W›<ЉuУ&ЅБazљъо}јЮЩАыJ."Ёю­іešd…чw F9&ЩиЌш’їях™hЕˆui^Bš)‰oйb>ъ —h2Sъ?тЖъЊРј‚к1эржLgїЧ§ЗюUђоЈЯЧё7Ž1|KPŽоіђдЂ№ў§;МџўН‡KрЫыˆ–ч9Л%†Ћ"мџЦижДэћБ1г<Ѓu:m–aЁ&UUССbаўoќgƒћ›1†чyЖЂyЦFл юЫУдUDP§SкЦ`ЗууѓЫyžбZ/БP“ИWщ‹ZуїХѕE§пжb1G&2Wё<<<)П?ў3ёЖќw(Ѕ ЅфЖmYk )%†~€aУ•]Z%ŸЗŸeАƒіј}чyQ„ЁФE„ ІpвЛq‰# Š/ў_tзыЭнћ8МЛТё>вац%кЦegїЛсќр(ЬбDг‹DН"ЌPŒћ„_'j№к/ 'pСVˆRгuzм~†60uv>хі{‹Ђ ЯП~цџѕ?ќoјќљ ŠЂ@зЕT%mіфhyћD[5 цyІaИыZДVЄє–Tж†Žhх˜ђ<ч‡‡/ЈM8ШѓŒŒй•@КKпї{|<Н0шcВ]ˆ„иыы=<|a)mуџx<ЦУкqHіGЊЊpщ/$gЩУ0рўўUYТ\ I\Уy]eX„ |љђХё– wwwД’юoŠQ„ѕedмї=f9cFzіьEпсtCD‚},.щаУУVJ[)Бу‘qcАДЧ­-ђ™sр›хŒiš№ќйsј>$—ю ыaO­{xx K“Ыp<ys~Ѕ, dy†_~љ >|џџюпџзT55kЅ‰#aƒЇмЃы5шХ`Vq”јь№ ‘”јхшОнQўDжТчЁh)LAOйƒщззќlLљЊўrcrНЖЅ?™vƒѓєїЏjћŽЊ[ЏxћђеKќі?уяџгџ‰УёˆCзйЪЂxВbрАhо=ьўў<`їЉл чЊV%ЦiЦЫЯCхіd*О;1mлzaMмпцЗЦ’чfЙрХѓчvЪќDjМЖББGЧ'Ч8BРъцX”Т‹—/ƒ;лЗФтMЁњaРн§Чу•eъзbёвђ"Ы@ЦрХЫ—Ш„xњБuБdЮdi<ЛІЬќФ[ЧЧb рХЫ—пrЭEлz§ц5>||кйЛ&‚п ћЖ1cпM%ДУдК1~zЙ•Џш)“cћRО:%љ"‹мS8R сXэ™‚џЩЖъкfv/jž( ˜ЫC^Lб‘ЉƒмMQ'‡9 ѓŸП’Ёёш Эїйž3уЇпќDџјџ„_ўѕŒУА;ЅЏT_–ї)ЫYbQ ‹ƒІьHfэoЫU2жZqЦ4јѕѓчX^ъІpыѓЭ1 |е;nФCq…_фжlš&њѕ3эAyшk6ыŸlСег4c–’“sџФЊЖШsЇRJќѕЏхШ§”лcеф'@JЩRޘЇyEvsŸ,4Щ>†qpJоХfrStu @r‘,П,4NгžМкMNъ6­ѕŠЦнџЬЎфšwEћэяK~x0ОIХцT‘ЃюRИ Нl8*ѓ‚6Uœ_ŠЌk‚t"qуDзе WФ[хА—*шF‡JЭЏ2[Ћ#БУMчБж`"gИ9}­№ЏnЉУCДЙ(ЂьDN#JО/|ЧЮeХˆ]?Ax{ФУёƒЅ”BUWј7џіяp9§Gcь2э‰†7тљђ№mгZЅ ЖKo‘QršvX–ЯюяБ, ‚YQьЩКпŒE‡гКЖsќZ|[,DШ„ЕrдJуй§3‡ХћxЦ<Ž№t>YObЗ”ў–XќўLѓc юяюm/.ЫnтїЊўLd`68_Юж JЮЁЪ§жXЦбJљX чgќTѕki бїЬгlЯѓпЫ0 $а;ЬR:Гt<9cЌПяЇпќˆВRљ7MtЋРЂвlš\.Z-:“[=’ДуЏр+ўbwЮЄ:p‰$ў]ШIю9эM;і…ДЊО^ІyгD‰„62U)И8z”Єьљ%pЌђЯ7кQЁпця0п?љёЇјуџїGќѓџћG{ЃХ“–›о>бhу zZќњљзА 6цбВо?ЭГ<Ч—‡/(ЫїїЯ№№х c–ПЂВМКЭeЧFл$~<ёљЫgЋeO‹Х%КЯŸEлvИПЛЧ—+уџ%с8–С™еUC{Р—‡/Т У>%ЇРƒўWЋЄsww/_>џMБxцІnа4еxt‰єkM+&aеМћЁЧѓgЯ­Lе—Я!‘>%lЇЩ:vM‡КЌqОœƒЃмS“ЈжVитеЋWж?љс @АЪиOјЃ”B]ејјгGМxѕ"ѕ•йјпЦ?ѓAb­пRЪ4‰ёцОŽошв~aЭ[.D hppN33%R5фs"ЪО1ц&ЭjЩП#сОžчюЭo“зW(N&ж АbЁвэl'нЛ8Р3ьэпPJqžчќЛ?ќ–‹2Ч<Эˆqd†f;ё\ћG|>Ÿ9/ryБъ.віSqY–6yАc5FГ‹†§Ж ћ‡X„рѓљЬ B^PZqлЖ,щ=m9šTК 9Чf&зd?ЯЈЪ BXOZЋД2лcшоc8Lй“ЉД?Gчѓ Y–Y<ЂжмЖ-цyN&ЯсsзБ€ˆXkЫхbi€—„3–RКЯ"њŒ=&ёqqH(>ŸOьћn† ЗѕGŽ'ЯбФџjкNDЌмфЗЉ› ˜сЬŽт §zŽнпўu_<œЮЇРШЌ1ЛKЌЩф™ЉŽУДл*<ёпН ГЗИміфRЁ“] 6bыќ)Ю'Ш I.рфžнозё2”6sшрЁ[p…йф„DC'OиСт ЎЂJ_d]щЌ,oˆ!ЌячЇЖQдsИŒˆйыУˆјњ5РaДДжєњЭkњёгPZ9тКУ§­Lд„_8э6Јk;(­АЈ…КЎ#mЁ)”XU§Е‹ч1b}? *+ЊЪв1(ˆšІ&ЇБHы>Л 83чЉKBjл––EAiE]зAk­Э‹[xэ™(h­ijъeQа<Я” AM]“\dАBdŽєи"§D""!2œN'r6™R’њвВЈ кЋ>J ц !ШЦ2RS7Ї™g™ч”БЭ&жѓЮ ! ˆшt:Ё( jš†Є”Фlаu--NК>щ›ХDб9Вl‰кІE–YЌe^фЈЊ’ф"гbХZЎ@o Aч3UUEu]г,gкЖ%)ч+NчzЎУѕтэ@i't]RЮ(ŠN#СCЫЪ58ыMњўу{ъ-m(ШиGз;Ї”:?&•Ъ•'=wџљ•™хt)fŸymТ хЄЙт‹Q l„xеŽYlQ>)pЛщ "F/c‹fоpVR 8ЩH[N—ЛЌв0щФ1FМg1Z:PG<˜нЄцЊХ}/ћ%A–gјэя~F{hYЪХђjТ‰]е.Dжк`ЇФГAkЭЦnлЮњTDOmП<ˆ'ly–ѓљ|†mлњj‘•жhъ†ƒ6^Ф`ЏИыЋ6Ы'дU ЫжАx<0Ѓi;H'VКbЛˆЗцщY–сt:!ЯsяТvŸ šК…ж&Tg 6!=.PJažgnъ†}ŸmБж’мД-{=ЦЄwцАнXeHœN'”eЩž“mENMн&KЖФ&2–—"ЋФ-g‰Іnќ’š)!H nZЬѓ‹?ЌR™‘С=ссt‚їїЌ˜бд дВввˆЋЩпvЎђBА”‹”АlЛшr=MЎъЦz>Џј;дZ‹Šёщ|цЖЕ§f /ИЎkŽћx‰o­№"Т›7ЏљЭwoBEŸІ<іэ(FDйšYPєJFЃa3бћ"q*wO&й5dаыBsKс(GјŸ)m„у(1(офЊ(NБЛ6$кв ржуЎrзE\:"Лх՘ЌИ72зМГнXy'jО1rtг.Е,ИЛПУœѕŸoмя§ЩѓЇѓ $ƒЮ8sk(­QW•cЕKи+хsЩEb–sѕ}ЋeYЌИf{pKXqaшjѓp~@–gжі0‰ХіррžќIт‰_†ЩХЪ§лСE.6aZkЬžˆnbёC˜Ђ(PзuˆEk mЕS€Ib №*ћ}"ГbІ‹Zа4ж$œ ‡-МІƒгuмэ’{“єS”И|,Jk†ХШУ9тДGэ‡0Z+4ЕƒћXf)!В] п2Вчўс|Bг4A’^Иыƒaнь<:ЦяŽƒc4ъК^cqо#ynљžXo|hƒCwР?}Њ.ЁU {‰ituOэg-№…vЭрsсц›ƒј}буНQ9mІ37%РУКТЉј–Џ6ЏхА(ѕюѕт^ЄМ&Ѕ 0šжЪ”ТгžУї…*-ШaA‹bЕэ'аŠшоявUB"‚лЛч5+ЅY?}ФЫз/и%о\Lьeю7ˆ—y ЉV F,rсhЩ#.Ы2>_ЮШ„рЖiЗ ъџoэKЗу6’tПРŽZHQВ,™Ђ,ЗлэЙѓўяqџЬ+Ьёmїщn‰daG"цGn‘  H{ЎЮЁЭЅ …B"#О…•R\в4ЕT‡?АUNšІмiЁSTuхфžL,<Э#˜œŽ'†!vѓp•$хЇчgфyЮu]ѓВxV3eСI’ИОЄˆEcЉД8ЯjFm˜#ВвІGРёpdгgн\D„Їч'”EСeYA)eЅЁxсjQ(Ы‚у …ƒnкjQl’…Х8œъЊлU^ˆХиЭgіќќЬUUY‹1Ќ ХeYАЎ ƒ›Ћўв4…fŸhqT!5fљкКъЎjŽ’—Л„эўљbЈ&‰ZLŠe}”eiw4СЕЦЬМЈ…г4ХЧћИ{w'ћ­с)h€Б(­Жz€ŽOСŽО/”їœ­z|Vі RкJЋdХ‚ЈAЮwГ"AT‹qj ‚<сeПB:†e'ын,_б›ЗR}qŠЖЛнP †"ATг`YѕiO5\'ТрŠ ЊиqЏт”i5ˆuGњџќе‰X yziгъ %iŠƒЬ‹Цђ+‘fх…ц{ ўЌL-аЖ-ЭгL‡У‘МБГWю˜І‰Мь1"Uw"JˆšЫ…ђ,G]е–ФNmЬГBžk5гЏ’Б ЫsДэ…Ѕ`љЁfšцњqг4t<ЅјЊИ‡ш.ЪЅЙPY”(ЊŒŒеђžѓ"Ї4Om…‰ˆ(ЫsК\.Є9Жљžm,Ж Єуёˆa$ы#x­ІЙ Њ*ыу"Х]a?ЃЂ(ђЮ$ŸЋ“h–сry&Q]еAяжЦ2 ЅiJu]г $Є(-‘сq7mCu]SžчКеPЩь Ќ„‹‰%Ё$I o–)UUM1ˆ[€ ”g9UU…aL,>Е,t{w‹‡яЁUh”э ­жe˜СxYUЬYїQХжБџ™AT! *ж;mЂ]Ж(/…k'ьа 0м…ЧHФpіСœ'с1D‘M$WэЪН!1Фїю‰ВP$%x•|4кЎУƒЂШp)љу§xјђ‰‡~p['\ф9šІСРVЕтМ№Ђnл–‹ЂdЫеЅЪˆ,8OћžЭД™Ÿžž&Šя3 )|ŸHЙЎkЖл|љЉYЁmЕХІtR‹9Вг4i О,cЋшуbIR<==Ві<ЎнДY^—Ж LєчŒiœьХœ7]щі]‡КЊ8M3Z"ўњcыd—ІЉбu$‡(ђŸюqО=Л)1\ЧЬ)Fё†žˆ =qр ,…šеКЕZš.„ыxлkm•S|~ˆr‡uZpйˆ|хѓљлЦ„vщ,AЫeƒLX‹!^ГM" $8‚зŽaHR™л>d,ДбЇ Јб™І эЊKјтIDATUYтЏПќŒВ.мBM’Ф`ЖЮфШ6• lТёpФ0ј~UžчxОњN[ZкdCЦ’Л~Uч0\Y–сщщ’$q}UwMб$I0Эњ3ЉL,іѕlœУ НN,mЮn‘уX†q@‘( otecбЬz Є6\бфyЁ„P•%цy њ‡у4bFmИф™=˜ЉЧЖч%c †Є%yžЃЌ*tкbT„ž‘І™mБ‚ѓТ`WљZ`|￘цэXтDh а уKмu]˜И ьg5ћсЂœдкѓbІЙEщ•al,ѓЌтОъzЃъbщ]еЊЏFU–xјrУIOѓхаg]Ф=РрjЦVuqЁ5—"B1іљ [PоН/‚Zk: ‚зNжцkЩZžo;cџњ4^Ј wІB/Х@иЦЙJЕЄ€йbФљ‚7wЗPГ–>яКUY‰ЦГ“c d‹ЌМј0 р…q8м]нВ‡ƒ“HЅm эя”R˜цйј№&`^аuњaаг@„Б,[Б˜ "PW5К^/вччgи-i‹}Oђwjжrьy–;ѓыЎk1ŒЪЊ<Јx?КЎCšЄЈЪЪХђєєdсЏŠe6БYю Mл8Гј8– )2@зuШВ EY8рЧЧGdif*ЉѕgДlФЂ”B‘АœкІi "  Aь;ЧДB‘ШѓмЕ]•/cёзoФВ Ш (ЅaLјˆїпмwЫОн5G;ы№Zžј3{Щs/Gб•œВ—™єя2чкьЈЕPh ш*2A6cЁD-oMіŽРБШlЬЏ~ёвь<$nэН™“Ѕмoы ˜І2оОНУ/џё ўыџўКЖCžхИНН5ўЩUЊM’Dz{Д№‚ІбєАлл[ПU|ЫjSд5Юч3КЎУ4M(ђ7ч№В IВзХ 6њ…6љнооњ­тkbQкЁŒє}oЇсИ9п@)…<ЩvЩe2NМ рљщišтx<О>Р8ЗС у€qqЈЎ*~m,нЊHH'т<Яйыb‰Хš˜уˆqq<]Uœх–kЧZLrВІшOOO(‹ЇѓйQї^ЫЂf'єУ€<Я№љЇЯ(ы Г™TЏžх, КсzbЌЩЖСю ЁŠЩ.ї ^С`K8'sз†jяЃМх“Оьуf"0–hЛ@6ˆ=ƒBhѓ™сrŒц'њ›тA УPР§~dг;‡;w/ћr9§яbн6I2/!4-#СС˜™?ўDПџіwќўїтЛЛ7^Œr?GCFžч”ІџоќŽ››мооJН@ЦО>ž•7G–eHЬ–Ј,J>пœ‘eй5 ИеОУRѕJаЖ Пyѓ†ЮчГЅТэщЦoˆВ,c"ЂОз&оч›3Єƒо З+';_…ѕlZОЛ{ƒуё ЕЈАН}Žн1В,C}Ј1Œкˆќt>йXЖYЖЮЅ!2ысН}ћ‡УJ{љn•!ЋуZƒЃŠО}уѓщŒгљФBD‚ЏьчфRG™ыPзvєюЛяИЊ*У8МтоРI’ Њj<|ОЧ›Л[1†ьyа„P3Oі9‰жн$q‘02Чшїbч^ЪœѕjЏЮ;Эњ5Œ&іњu-rЈ‡мE.–ŸХFpэ З7§Р+Kдых+НrгМ_Ѕя#uшZ|ЛќnOІvMхqФщцŒ/?џ„п~ћ;ўљЏН,”А@ЭВLKCM#ц'-™ѕ‡4эФMгТGцEсўГБшРѕј CД~m,ЬŒqA aRѓŸŠХК—у€ЏпОЁы‡?ЫТЎW:ЮгŸŽeY у€ЏпОЂэ:,J§!:;UЧQcЂщђkcБлнaшёяЏџFQ”(ЫОyїю-О< Ы3чj'YЖ ЮєЇкYrхёjш ‡%kъТ‹+іEBиG№mфу gpE,4ќYъњx%ЫйaWИА­*0ЏџЧТїРПžфђ†~ўітои9:БрKŒЧеЕЉьРлХf}„B™УЫЭѓŒ‡№х/?bш{JЬєЭ|9Э/€ђ4Ѓežбv-UUEiš‘šg$ўy[Яw‰ЁфYсњBUUбаїH“ФрЪ7c П7ќe5ORЁНOАh“@\ƒЈЬднE^Еђ,‹УАŠхк9"+г5#†ОG}8‘‹"–0q^эvP‹ШvHг EžcЄiJ;ЧXŸЫ@у8ѓpНеOќ{к{ОјŸ>NлЕШѓмИjўђЦЙЅЯy–aш{LгH‡УQ_ГЫЂй-bњЛѓeaиTW5§хЏ?бнл7ˆ№ЁXakЏu—xЇYЧ~MsPMGX7ijЦжЌч‡Ф FЎpqЪ\ИТy.3ЄvЉt…Јj›OШB}ОЈЖMmЃ­Ÿ9xa1і,Ѓнj’‰…БЧ,ЂОž_‹}?9 №и,/ˆ;кэљDHD„yвЬ†П§њ §ћŸ_1tЊЊмг NВО›_.є]‡‡Я†mлИqПxУеrL™6*R3ЮЇ3‡ƒ5чAД%пллЪ‚žž& COŸб4-35мЈМhЏ‚œM[ŸjTeEЬ ЂXЎm\•Ѓ'П>уљљ™&гOБаеЊ-Ы1NxaЊыE‘Cїg с{х=Р:‰v”šqџ€ЧЧoPJSЮ6*/к‰…zwЊыiš-`‡Б\эЮчyNmл>ќ№_П~е“\M-$"кэ‚лXЕрўс?> IˆІI щ/Iѓw’ЧжJЮc™=ђ%Zc$ћZ@ƒйB,4Їw*•§УчЩПХ]6`'u rХz›єс(^ЖgѕД Ѓ8B>РиzLђyТ“ЦР#@cр:яОHпеў‡W@k}|ЕЂ‘АAуA7ћWpчмПЊХ‹}џёџјгgЦJH\-Ыb$ГьЯle‡8MSЦ‘/Эк$hц<ЯиР>œ­Ѕ‘иђ‚LМАY2œ*pг6Ь sšІШвŒѓ<ч^Ћ БH%1Iжж”}Яmлтp8ђ<Я\ЙГ‚лААurяЩgб4 л6A–eœe9њa‹srБ)Ѕ8Ы2=Щю{XсˆЂ(˜UЈ8/МЌeФДм”Й8.—Dр4Ig9ЇIЪ&)›%Б@HCqšfмД ЦqфК>№iaДкJ’Кї'Я oФb† Hг•QBЩѓмh)zE+луэyщ:-•>п`су4тp88‡рМHДЇ‰ХbлFCaЌ˜@YЎoP†xБ\LџАiЈYЙ ДХ‚ы],;§д$IА,Zшдž‹aPfЧДгл$!$aяр?|њˆїпПƒ27РЕї†C#дvЂ§ойZ-%d•a­D'PЬћpEhЫ Wц~–:RbнЦs—“6&ц}яшrм‡t§>ЂŠmЫX‡­dяu'м˜„oЇф‹с‘PЮсјlАћ;ћ ЇћwdН ?вжvƒ%н^ЇЯп*4У[\Џг8сэлЗєг_B’m*SАb–Ѕ˜І‘кЖЁѓљьИЕУ0Aќл>Щ„a’11ГёYЈiЄYъ”ƒЕ^a‚В,iV3ИM ћ~+В,ЃaЈя[gN@OqыYš‘Бч$Ћяg*БаВ,дЖ ђ<'+Ш9M’дФ2ЛX<и—7жЈы; У€гщфŠёap8м№)MSячСišвЂfДmƒЂ(м0ESSEA3чЕ §6r‚мЖ цyФщtrqк‰24?œL,$DмqГ,ЃyšЈыZЅ‹… ^’ђ<‡ˆeе ЗŒ,ЫЈiЈХэI[XŽt<žР ›sЎЖіі}-ЫB77gм?мSQ•ДЈe оH‡x_{ь ІЭšѓu‡ї ˆ,QgWѕƒЧа4“т@PšТ<ццR М# z2k›nl0ѓIж$^qЋ|ДЊmNaк™бnwжRТЪRУЩ§]/PqЬ фЋЭЃNШ ўr}ЇiФ—ŸОрУтп7–9Ј,–…qЙ\CР^ДжyЫdЇy  ВњIгУ8Ђ|Хe_Џы5O5KsWЦ0ЛШЅа4 JУcЪВržО+˜„ˆЅz ЃM\‚}`ЉM^A,c™ч mгЂЊЋР—C›ћшXtПS щЎuВэ;LѓŒгщ$э|‚Xdеf1M#кЎC]‚Xьy(ЫГRŽ'{^šЎ…2RSЖЩЬ:‘žvPFŸQ. БŒшj‹н5ЬѓŒEФтКxOїїxѓі ”M{i-~%vƒ6ІД[ОР"pHŸ%)Mяt§Шk№•NрFДŽ“#6q ^  Š+@v`uuhh‹М‰Fфvё†&tt†bеЬ5ЮœБўєъ{v[!ЏœЊіGђ=Wzpbzˆй6‰эДВJUWјып~F}ЈбЕн&U*1лСбU!OиRсђ<Ч8єH( ЁТћсв\p дО†NXЪ›З˜Е„ь‡г4йЊ&фУ=ъЊB–ІЧ!H(lMгрx8К*cQJЁ6тq,іьjc!­Џw:ž‚XlхuЈk$”`Бј­я+ѓпЖ­/GЏcidu}иIъpŸQлѕ`#&aћЅіŸQСПŸ8‰ZЦFпї8žŽюsДЏc™Е‘;л‹…(AлjУ%3рђрhгџг€oџ~тXЅeюxјˆ4KXAя’ 8ЂŒ…• Џж:яŽЗX’8 ЋW8NˆvЮ’ЗRB~КŽ#ѕƒPK”ƒЧћ]fУІ—J%‰jо.ёx-Ѕ%ЄЏyпa…%T&ЃЏуЋˆ9,ЕХЂ^ЉЗчХKŠХLfЦ4Nјєљ}МџшФBХХLišбТ ЯЯOt8MтZVеУ8Јыšˆ zБ;Yxk)9r]зRfЩAЦq@YШ2//%ЯŽёѕРЅЙрx:"MS)х.˜qQ4ќCєё(э{Gс‹’(й„QU%R-\Ц:‰jiАЖmp:ЗИЕД, MѓD‡УH)N<Жњ “„§vпЖІiD]•^”Т MЂУиЃыZ:ЮыыУ$вyž ZЩ$H2юЎku_ЕЊiсE~FdnTзl“EБPпwш‡']хMЭœЁ…УсHѓ<“в I–eIŸюqssrBЇЖmaa‹!‰П…Охыk,Dr\ŸSМЦv сAі’вYejјХ-оЦужЭБ„V)s*Lытю*Ÿw}L?[Їз‰6tiщ*њъж—їA; љzVЭїзџѓ nnoа\š  !њЎwа…ИЪБ lš&m†ю8žЁќљЌžŸ/8OС–(nhыой”аz k;Ц^%ЯsWщЩПYkхИ'  бŽЇуЊ"Е‹ЕыZPBЎ—ЙKQШRН]–Uqnp~]лъ–РъЎ.ЊІirЂГїGv1u]‡Tї.БЅЏg+ИВ,‘ЄЉБcщћ>mЃm"=ЮЋ*аЊ]w]‹ЬHmХЂщŒВЌ@F}GЦТ у§‡я№сў88ЏЏБO–Ф ф™Ўтж6оыоіyї7+ђХЋW›Ћ7јџr)Žб>>a7U‰е^БТ8:™б@ыБC›џr›uсE+ся№DNейm№Х,‘ …fƒёZ@ ”5ДШБе’M2К#2^ў)і8ܘgХяпПЧч/Ь`ЖЕі Qќ|yЦёxdCUcOЩ cэћ‡[•bщ§бЖ-›оœяЛJп>nžgN’E^№<Я,еЂнЖѕxт„’(– $хaq:žrAIТИiNгŒЫЂффр4€gЅ8I2фyncЮвŒЧqфЖэј|::СЊ˜™ЧqфгщФЫТ,ь?#ЁвŠкѓЬY–sšfђ3т<ЫЙы;Цчг9€pNдл…yžљ|:ё<ߘеььёtФЇ‡{>*^”г4А’ыЯ7№Ь5o­Kk{Wš8r…c+ƒьж’[GюlеœYњ‘TzŽzdИGВєf#њёŒ„BHmcLф0›)>Ќb9py“Јmкu…лQ„ŽЫWvъіћŠаrЫ->’еBБжNЂЅв, U~3I4 ƒ”RјлЏПалwwdФM‘бЅЙ†—”вщ вСЪo1ƒ*-§NJ)ЪвŒцyІЖmщxtлVG&zЭцkzGJˆh2t=К\.Ш‹EQј-ДяMћІ1)НИЉ*KвNrŠВ4ЅiЉя{:N+о†є"13КёoО‘ч91˜.Э…ŠВ ,ЫНЂqhшр••RD в“хЩ—aьє8оЖЎЮ‹Zбщx/ Mгd™qi.d`D$ЖњЎOœ—YQ’$T%ЭЦЅ/Ы2Д™X(~.‘RŠцiЂѓщDJЭZ;ЯiVњsЖРiыpgс 2xЮ™в$%K•S‹о ќсо|OЪLŽс=Ђѕn2Fр ‡ѕМтК"tTnјu&™BсйУ}нcШЧ@ёš_љЎEŠафюaцœ1‡ылБ‡eМRš%ТЬ<:юZrя†§‘ьЄ9( ‘"рДxy/ Є•8^žeˆб†ьlr^ХV@СпЭТ‘“ИC…у%ї{ИуиSЈН8Oќѓп~v>!ГRмЕ›‰(‘КфиЪ—ћОчЊ*‘$ФJ)&"~||фЂ(и№SYЂ=qY–œe™…g#†ЅЫїc†|Јkї§0 <#ѕЪTPЫВ№0|ЈkgЌХOOOкДЩT`bbюп—?/˜ч™^Pз5—>юћг4q}ЈWОЪrFg=Ћ™ЧQЧnњцyцчЫ3КzчѕgфЃБзчЄff0зUЭг423k•yцКЎWчб|авуѓ<ѓ4O|4Z‡г8сііїŸя‘ч™IЧ;/Пў„е6‘#*PА[т`Н‡ oV€ТgТ x"‡Я „е›їф-p яV€яН'pы{Ћ‚e‡ьЫ^П™аL/kHМэWья7Eш:Ѓh“ьMс7ЋЮЁРс­КvљbfЬjЦ—Ÿ~Фoџ§~ћяџ‡Ояu/-Щ0Mѓz’ОйїUш{BUжhк_‡ЏЇQK]1CЭ/‹0]зЗАmлjиKY8ет—tkЌPЊiшЃэZ-Н5Oz2)l1Џ‡ЫЂE8 =šІСхrqк…ЖѕR,‹ZМ~aзЁiєЖяTœЁ”жТ{щŸeIyЂ—Ы—ЫErЏŽХ˜ъJOў'5 Э=^јхXŒMi–х@?рљљ—ЫЇуМ0f~],У0A0>оРнЛ;-tёMioЏЈcБтѓŸыŠЮгz.›ы”h„ѓŠЖ …ЅEыќFd:cГ’У0Q =І(еЏFЋr йjaљчBЯб ш>Ч5d;ЙrZ,&ПцЦGMІШ§?Ž›пƒy / уeЙШW–Ф ^дByžузџќ•ŸŸž™@єў§{[ЁФю|Л?EAV3№сўЪЊѓТрMЉЌЭЛL–eШѓпПђЭљ†юооqšЄИ‹ДffSB ђМрхŸŠКОЧУЇM ^!и–Ю"пKгfэY–ёЗЧoєці ююю лaС•љр c—eўёп јё§œXПŸ'Аu^Шє)Ы2|{ќ†Зoпђн›;+3FWЮ K~m–eH’ПџуwЂ„№сћœ›эќkџiеъ6–wяосюЭ[шба K$Bžч\%0Мџр&яVЪ B˜D*E ЌйbЩQuэuэQМt§Кх`Gmx?:[шќрTќ"$'Ч~Н…?ћf…G %j˜c“d§{ђ†ы7Вw 79‚кчGЖі•ь‘хdЃг№ВђффO ЫУ?oЛ*йžm-ЎЫСќ\<жiЈВМMY* q 5ЖZ Pentobi User Manual

Previous | Next

Classic Rules

There are four players, Blue, Yellow, Red and Green, and a board consisting of 20×20 squares.

Each player has a set of 21 pieces of his color shaped like the polyominoes up to size five. (A polyomino is a figure built by a number of squares connected along the edges.)

Pieces for game variant Classic

The 21 pieces.

The players alternate in placing one of their pieces on the board. Blue starts, followed by Yellow, then Red, then Green.

Each player has a starting square. Blue's starting square is in the top left corner, Yellow's in the top right corner, Red's in the bottom right corner, and Green's in the bottom left corner. The first piece of a player must cover its starting square.

Board for game variant Classic

The 20×20 board with the starting
squares marked with colored dots.

The following pieces must be placed on empty squares such that the new piece touches at least one piece of its own color corner by corner but does not touch a piece of its own color along the edges. The new piece may touch edges of pieces of the opponent colors.

Example position for game variant Classic

An example position after a few moves.

When the player of a color cannot place any more pieces, the player passes and the next color continues.

When no player can place any more pieces, the player with the highest score wins. The score of a color is the number of squares on the board occupied by the color, plus a bonus of 15 points if the color could place all of its pieces, plus an additional bonus of 5 points if the color could place all pieces and the last piece played was the one-square piece.

Classic Rules for Two Players

The game can be played with two players. The first player plays both Blue and Red, the second player Yellow and Green. The points of both colors that a player plays are added.

Colorless starting points

Note that the original Blokus Classic rules use colorless starting points. This means that each color may freely choose which of the remaining unoccupied starting points to use for its first move. Pentobi currently only supports the rule variant with colored starting points because this rule variant is used on the Blokus online server at blokus.com and also was used in most of the past Blokus tournaments.

Previous | Next

pentobi-7.2/src/pentobi/manual/en/duo_rules.html000066400000000000000000000025641227240712600220310ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

Duo Rules

The game variant Duo is another game variant for two players. The game is played on a smaller board with 14×14 squares. There is only one color per player (Blue and Green) and the starting squares are not in the corners, but on the square with the coordinates (5,10) for Blue, and on (10,5) for Green.

Board for game variant Duo

The 14×14 board used in game variant Duo with
the starting squares marked with colored dots.

Example position for game variant Duo

An example position in game variant Duo.

Previous | Next

pentobi-7.2/src/pentobi/manual/en/index.html000066400000000000000000000024471227240712600211370ustar00rootroot00000000000000 Pentobi User Manual

Next

Pentobi User Manual

Pentobi is a computer opponent for the board game Blokus. In this game, four players place pieces similar to the pieces of the computer game Tetris on a 20×20 board. Pentobi also supports the game variant for two players and the game variants Duo, Trigon and Junior.

Classic Rules
Duo Rules
Trigon Rules
Junior Rules
How to Use Pentobi
Become a Stronger Player
The Window Menu Explained
Keyboard Shortcuts
System Requirements
License

pentobi-7.2/src/pentobi/manual/en/junior_rules.html000066400000000000000000000020351227240712600225410ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

Junior Rules

Junior is a simplified game variant for two players. It is played on the same 14×14 board as game variant Duo but uses only the pentominoes that have relatively simple shapes.

Pieces for game variant Junior

The 24 pieces used in Junior.

Bonus points are not used in game variant Junior.

Previous | Next

pentobi-7.2/src/pentobi/manual/en/license.html000066400000000000000000000025031227240712600214430ustar00rootroot00000000000000 Pentobi User Manual

Previous

License

Copyright © 2011т€“2013 Markus Enzenberger

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

Trademark Disclaimer

The trademark Blokus and other trademarks referred to are property of their respective trademark holders. The trademark holders are not affiliated with the author of the program Pentobi.

Previous

pentobi-7.2/src/pentobi/manual/en/pieces.png000066400000000000000000000154051227240712600211160ustar00rootroot00000000000000‰PNG  IHDRP7 #ЬsRGBЎЮщbKGDџџџ НЇ“ pHYs  šœtIMEл 0”Н]tEXtCommentCreated with GIMPW`IDATxкэ{ЌeW}п?kя}ЮНч>gцЮУуЧ<ќc{ќЂЦФ@*+ Ц„4Њ*AH­”ЖjЄ””ж(%ЂjЅDBЄIј…Zhƒ ’Z1ЦlАчСx^ї}яyючZЋœ;fм0w§іѕЯƒпGВЅб]gЕ~Пѕћ§жоgЏя6ƒ^ЧЃ(ŠЂ(5‰дŠЂ(ŠEQE ˆЂ(ŠЂDQEљЩ* ~уПѕ~ƒїОNпuОЃю8ќњЎ;yћчbк„-і}q­­ыйяGыo4~Mпr?жГѕ–=zбуёђТ_r?^)$њУŽ'Мч3  ї2XфЫя›сЭЗь 6‹=яљпcќљЗVУ§:‡!У7ЖAU€йЌБГ'9§б›й>‘Лžhо§—–Я~Ї ЯбФpњИѕЇЁГЦl>Ž|@s"ІpрЋ@ ‰–П‹§Яo"MmpмЙѕњdХвj^Д~ўšS|юзnЃА—nб6bУЧžp|јЋ•Ь+?„}їBo%`k ДмфžхШGš4Ћ~ї˜пiBЕ*ђcc"Іњ‘3пƒїCж ŒТ@оУLLрm$№ЃчWіЯѓGя=Dх6okŒсЇжтВ\Лы05жќ8Kdr\"ŒЧSЧш~т0qdЎЈЄxЌqјПV`R‘™˜kТ~Д–Оn…O[Ь‡л’щёˆОБиA/œL:G0ўрfгzХ@ІŠE(ГЭћŽbX{ž‰јЇєЯ џНйЂъ.0;{ KэА›FZ”0Нў"˜„ ˆІЃœЅС‚(ёДzЇСм~iw„Z C\­ќh }„‰јv­ДЫxЋ[oЛ'Ђq>pkЪО$ёŒ (пLвtыIќСmA[[7Х]vRVlПF+fњр‚ЕъИгсmЖЈмxИуј ФІ­ТыЩD34›9y6)ѓнћчpN–"_1Л}ŽdrGАmYdќъС.{я”ХokВIкЫС„ч˜VїˆќDУ№ЁЏWќб3–FŽ™bх$ѓоBš9.6л%џсЇRAю+ќ6>3?Cвš ЧЃw|f9чs˜‰єАЦ}JЗЈSЬ}О№KмЕЗlлŒ ŸzЦђл[LpcbшЎ,0џЁ}ќИхwСl{гЎ >ќЮ;Ф•ЏŠЛXчљЙ;wёŽЛw‡убР|зѓ™ЃK”i/|;уЅчёі oт­чО&xуўЛ/ўe]У№Ѕ?Щi_РˆьЉ'!: Ђў;eФккŠ@СЗ_Тх >ћ]ьž ;И'5c ]&œЌП№Љy3шu~ьjџт#љгep­ЃЂC<Л›ІсЖ§Ÿ|W‹ђР~AА{’ШФ‘јnVЪ4‘Z Мї‹–O?Б6”NиЌУ‹OрџьHгJ4юfbˆЃX–д€ЌЈDs,ЌчќЗ‚у mТЇq=ПМћ$Ÿў—рIаEiEg|Нї4cC чИqтU*с&ѕc§тi5"БТ­uŽЂ’IˆЬїсЦїСЖУ;бо"џщэ-ўХлей;ˆxќŒчџ{ŸN7МipэОјЯvђрыv‹’хяќубЧкP6ЦРйя~;vёФPtS‡wйЙk;kUs(2Й™љЂ{фqќЇtP^ђ"2–Œp=yя7n–Y[ф yeHlбŒр#;>њ.ОмМ€a?Cі‡o­Ї…†ХУ†‹Bгі‰)IЋ№ю’j@фdG/ЫžTn$ŽoDfcŽYИ€mёeГ1†вBiхIАŽL…w%.ы!‘2Љв.Ж&С‹{ЇСP:(]DyШd•.Оo†Э Ц!*ЛDŒlžЎЬqi7ƒ6иYqПIdˆ|†ГipŽ”ЦŒc`рВ‹a›їЁєХа[ЙlnПх5згхqЇа`ѓСP +ДшЏ^и(ŠЂ(ЪаЂ(ŠЂhQEQД€(ŠЂ(Z@EQ- ŠЂ(ŠЂDQEЙФфМуљ"щ„ЫЭWщ˜•Kшѓъў~љOPмФlЉЯ+MЪфЊ^ЬlIЪ$ё%уй<Њќ‚fь‰‹e’4,P”d›xјэмsД-[Šжyюн=<ь$!СW=b›mоП1SгHТр‡ЯRjD2) уИgw„єЕд;шА˜žО/9@k,NШ*Я‹ыP:‰§6оћ.8ыЉиrh‡LQ 6№ф‚О<€ѓАГYqуŽфЂŸш6РёЖg9“љq=uрRšUќФ8UNxДuбYoшЦbјѕџSёј M Яя{6 ož“|OМ5т‘ћїGc№ќХё˜џх6‘рфњИыcЧf)Ыp^Ќ/ѓЕО‹зяЊ xO‹_КЁьˆф8ПСу+ая„ŸuЮЛЕЦaМ…ў2TЁwЂGPІЕžЕ6Ў„оxЛЙyLŒБ%˜с‰Y‰­+oЈЌ кЯ{ˆ#O#’љБY{h“а\г6y>хpпЖ"ЇЈу|1к/UЪ˜1БЬ~ЦDЄЅПчМ( h/§ИщV,Цg9>•иЯ@жЁЬCћљ№IєВЊDуѕо™ЁМ$. P8.К ЬVЈЌккх›=Š!ЫфyЧ€Б9є— x=ТWeo=МЖС:XПWМЎЋЊ„ю*и"ИVЫ†СФыа_ФуКX)c˜ћмp†о‰Žй8lXГ€<ЙѓбoF4ЂА FdKLaMXІ,Kўе]%oй?. GszŽИ–[ШMkИИЄxO<Л›Ф’ƒ1фp5Є2LDМc/‰qЎ#ђfФ{ОSс Bc-ПrУ"?їю`ауљ›31к˜№тъ |c’Бз…k>ЧoНeŠзяй{ЌyЈЮйКИСиЎыƒЇНѓДЧ-|>Тк(шЧоъ"_ўЇ;IЫА=r Пџ`ЬЪЛD’>ыХCМџ2ћХv7яП5ч­яС Вї sSXXš1†яЏ8>і­„\ `“e9П§€ч№žц%-Eџю~УћЎ›iМYћ3TYY#c’{‰M0hи9лр“яnбя_#Аїяп%ЪнxЧAЧЭян9мМј^{œпџvL66^ЋcØ1Ё:…n4š3Л0~[Аqюѕ ШтРѓ?Пљв†ижц_а,ж‰чn эvТƒяЗydЧ ьПEšI(­ЇLУ‚‡,Ÿw+ві>JА•УVYxЧsцy0oB$excѓ+Nрдs|ўЛЗQЌž"$+`:gИџэл1їьэ8ЮісЯлlПAдvќЄЧ>ЕLž ЎЖж—№Х`JЖЂ˜Т:ШУТ•,ю­W@^–иЈ+бnF!ХђцˆhїšБс7Ою˜TО8ŠАYЪЬЬ$сxœнuW=й"!ЦўяiЯ_Н„Ш& н’Щ†!јq0ж ŸeмЬЖš˜ёpпыз/ Ю4ИЙЫ‚W bxz–Ќ K`иС€ЂПlM4'ЁЯe•†Џ„V–№ю&Б4щљqHи,JАЇAt лєЃчЦ‰R&&n`OalюЪ*,amвЎГO=Ю4щЛqbО:,{gјќoтžsТ•ќ‚т+'сїўІƒЭУыЩž}–ЩлпFV­ ˆС–)зЇ/№ТЧ~ŽLЈ4b1Х:ьœŒ9њ‘ћФ[@8‘ЈЈчйЕ„п§ъБЯБiЛ0Й‹Ђ ;ШжјГч,E1L$о–ЬŒyњб ЎШ‚kе{šGпєЖ‘vO"ЯЧŸ0Фщ|PщС V™›лIпoК›>ы3OЙKыт‚чЉ•„п§ъ"‘пбs=ќФN СЭлYІЪS@І‡•б ЧФPH6рGN‡ы‡М‡Еi8AЬФ6lоy‘uСЭж0:иb€MЛс]Oж­ug0У9–5о(†МWoЕxYыK‚њLХWхaћƒщ­‚йQуRеCоЧ ДА\кХћбlа1~ь…Ѕ]В.оYlкhauЁ5Дtмf„Rum'‹пˆGVqѕЖoN‹ублR_Uq Ы'єуVдКхЗ ‹с7лdн˜л‰ЭК‚у‹ttr'оуѓ6tkл{<)Ж15ŒIюѓЎf<і!ы ќиЋђВFŠH ЫМђ3С’PПvЫЦ2ЊО_– ;шџKhЌ[ЫрЇЭЋмƒ‰l}ОŽSPш'ш9‰=ќљэŒ\›N_чыЛ"fъЎЇZ1#бТz-Ц,Œ5_УяfDЙяZ}?ІоЂ(ŠЂ([@ ˆЂ(ŠЂDQEбЂ(ŠЂhQEQД€(ŠЂ(Ъ|ŒwјіЯˆрcm/?.ЩЉ4[|ж 4[|ФU2ю­<*jLОыДЖ6GЉG‰‰d~Ф ЯG„ЦmЬіzвWeМ~ъЬQjkp6sœTСШЋыK/њєŒцXгOиckжњиџ9жЫг{ё*™oZ@Œ(Ыс{B"ъ§!mr.Kш4“ъ„#‚ЊђЭУИа  I†\<бёWDшч“Б“ Wp˜ўіKrЌ™ѓ†Ѕv3–ˆ0(”яЎ ув›­С9Œ.ФY›c“жФг|c[Ы}G-‡цš^э9U_‡еБhOС9$IМbLЖХEPпШeiЙїщ˜} L€D”?Кb0AОЄ$8,Iаи“œћO5БЮя›cхБuСЉпотJT ˆёfCMЧмНдТxtgDиЪ”яЌ™0_rŽФVУlr@FЧХг ~п>єЪх+k~ЭМуФ _ќЅ7Sx2w"№јšуo}Ж„b *ƒB`ИNkщ,YЏчy(s~тдU~япТhlї1IСг›ЪOџ~Щsз{x­к_Ѕuт,YД?hй5ВЙГѕ=s4Аљј‘“М§ž%ЏЎ Ÿќу‚/=6JmУсл`ф‘C!G(_+Ы|ц.ё“o:ЖяЋЏV ѓЗF+“„\%GжЃГИШphA]Е>œх-Ы<ќ/d4*=‰ОuCxп`‡cЊšЃUшM&rxbыэЇ6јм?ОH+­.|ЉяЌ8~ќwKФVя:sgюdДБ5™„g}ёFѓпўЗoc4,ї/ЙЯїр#_ЬyьйС$ѓTщЎЗЭ/ќUЫЇп{ŽЖЉжG+Я?щјФ—3Ћѕ!–Ÿц>qž/ЦК§н3?з‡wf Ўч-4#mw(s<~*АЙЪЏНЗУ/ОэьMѓAZЅў†8А=(ће?ЈЬIuєЖрIwLйFu‚ƒ5[дZu #Џ{з@џ нгgШЦk`ŒЗ€Єйцф{и}uЁ-йTЎZ§+Ь''Щ|КPa.QК6[ї8y§+ˆœ –ЛiЪ-АžF™гШ6HєуlУ“\эDЁ!ŒђauУeЛ0Ѓ“M}ЩTћв`…ЮБC Г П8KSnxŠвЎбаv#>н!$йЙуТ+=2Џ“”сОl:ІђЅч˜oмЩ( ˆ- е5fё+YZšCБ Zz ъ*Љ>2FhЙYЖYm“Bя2Тљ™јшDk9O•эЙ”^>Е}\')ŽVл-0УyFІgŒR7szО3љЙ{:šёЏw>ї>;y~ПHўs­ "y™ЬAЧXSлјц8ЕБьUnŸМDзUЯъЎCd6ўну+Џ№О$/>ЋО9КщчС'ю/е[Хи=KАЬЕ}Zъи%0ЖTgjCy!Vќ~Z'‡ЩnіэШŒaЦ^ k‡h?5^}Ф[X{B, Б€DDDDDФ HDDDDФ_Ю"QIЗ*ў:ЇяЖOдпHwрЙЦЋ8мХз Х;‡SE5МAЦMПчэqWЛёFЇwр=зЮ1 §U'sœtGfŸ>ЪЉЬ6(A87эЇЊSUЇд™ЂгЙЗЗУщЄWVВsшNЕ:OШєgЯд.јэВлЮNюз{ 'Ъ MZ‚:7љžјžГ(:§žОWжŠY˜АHxcKЬTжЇЛщЧЂ3Ыс/Ш\i—Љ kшC‘‰њ|Ztšk˜­ŸЊЃ’Ў@u’ v|кy l—VЩгВCкй2MлЏф’Бжrrбаn†Е•cXШЎВ/{тRЛ!g/лUPІ 9з№оqs„ƒљ5Ъlущ–›!YЄЁнvтх]R5Ь>хщпЄ–ЉCкЉЗ]Т$Iš) QЏdrЙЎ ц‰2ъшHѕЦ– ‡г(ћt;Э ^“nчTиBn/БeJцк ‰gž#] бnна’NтhЬ5М;ŸСБ3^z”Н"Ёd._І‘]ЏєSEi6šЄЃИV:YаU`АpˆFч`eо4у—оuОС—ўМ— HКэЗщ%ЊќЦ{2Юю Zхž8дЁЌБ БЩЖQVЏ№D0eЦп;7ряўд§dЅпyœ#ЫЫ™8MšиДыuФё(чЧOlѓоwЏ7ьU•FГMYdС+ЊЛOвujр“uљй‹w№(СŸ^oђЯўЗ#mЬ{Wой№Ÿ~[‡ЗмqЦЛыГю:q`fd6iC:яiœuœЄиFР"C-ЖXЏБгŠЄMй№m@„Є,€лhNv-2—vHiЗыД3NcЋ№Ц–އXгЂLчƒ H‘??“fBЌ™Ф–RНЗcGУ‡`“9Ъдsf Њ+5w{с8=oјђЯž=юЭ —ћ Ÿќša#KЊЋ*ЮКJГЄUЩчШ|ƒ#ѓ‡ko}+L€ЛOЮѓћ6юю$шJЫxаѓvЫ&[Ћ›?Щч2ЪУ(Ќг}яHW„Т*усР„ЕЙТBыїŸ;Rу'ЬяЋ 'ъNjqњPЫПBXV(ГЪЬЯ…хњ[мqє(їпqdпyƒъЂpP њеt"0XХ:ТxИэЁˆ˜aхv=8a:ВЂ$єНGXщh‹Дб™•Va‰РіхbРгŽiDШ­›шУзСМywіѕdƒ^•IЎ›Ьц K(­’†~ъŽо6яьФХд.U$…ѕkЈОn&Чs­†сўл-ˆкы`u›l8хѓщ#3о“ˆ›&ˆ=™+ ёьeьZпб€˜и9ПŸЅГXOщK ёLVоћ^ kPЎыфiейЮq_ЅпэKСїS#/ГПњії;лќЃ€Ш№2YB~ЦТЮоxпЋMТ}Z^хXеђ††ћ’ёoDDDDФž HDDDDD, Б€DDDDDФ HDDDDDФщхЌL2љ#oїЛ f"Гzњ@Œџ-aЗ,vцшiККЅж5b&їј}Оd’щПщєЄч­‹Еl(/ЪQљиЎ74šФЫђ€$@Эюo ˆ-cІŸдgR_ŒНХVˆ>ЈgтгЩ-фг;>В?љБЂ‘Z‰’4KXbШ3Gщќ„"Т\3№5–hТp\нyЖNсй'aѓЩj‡Аљ6уђDАъчR р0@…,з ЦCЇТЦЕgсЉЧНдl­3п?зR…NЛG“™ѕvЂфyЯ>§kеŽЉŽœœТпQЄGwаmjѕЛ _–‡Ѓ0_BЎ>ЯЋ:y й8p žzdjCЉœуцМ€Бж2X~Ў]і~ЇЄ Ь6Џƒ-Њeш;мў "иыпƒеЇ§ЩgИЦpћ^xђ›L“H•#БyмЮцЕЖКЕ WЋn$ пƒђ сCЋУ]} ЖV№uцГ}%8ЎT•NSР„ЦЂa”ЙА`Вѕ<\yТ?lo ь§•qxгЈhхЗOљ•?vЬ‰ѕё {…пzџЏ?QнѕlDАЮёжпiqiЭOБ‘[хнЧљ/џр/Х†Uxр„сЪППˆшнЪWцšiPzV*Ÿў:ќЇПHhIщ-bohЏёыяъpсј|e|Z…‹Gрб_Йgя№;JЛ1›Ѓeщз ЅWs‚ун Я№›џuŒ2чѕw_hpхзо‚„4 t[iyeЗЅМџП7љЦѓ~_*юЪх+Н'HЃBYўзЗ;фK’ЄЈ=4vb`<№Ыь~єЌpѕSoDн}†4AМRŠвHšiX!Sчhп~?юдн^й~п&ћ'?СИpњ }д_)щБ;hо{ущМ3’…~№и˜Л№V’"ї.ЎЧ›kh`}l$ТпђЅчRтЯMgИЦWц(i’єO ЩБЛhЖ}UТИп#YL*9лвЊеFV”l-_eЋTЧOY`є:eљІаЅ ›Н!зž}Ц[Yщ­ВyЎ ^œЄFXьЄ39fЫЫPЋѕQdЌuз)ѕўE4‰ƒэHxu!lFdзž™U™sV—–СмtўаH„ХNc" ƒqЮЕч.yШ ђ ц pO№№‡цък%ќйаОчд sЗРiГХp›bѓЊ‡Ъ$еЇhМё‹н&ЃТэЋ>ъyДPŽфЋ7•Щц*іŽyрx№Ž,ы­‘ѕ6ЋѓIсв#ˆО-XюAюXЛrьиC§sйџEЬћƒm8бЧ*^*“ЕЋиэЃ ‡ыя@^zf&‡‘щ ­цдЛ…пuІ{‹†щу’y‰Тn-Џ2­ЫюЌ#/њŠЯп$ОїabрЕѓћEй‘G‡Гj/9, ьќЎdЏqX9ЖжгЕNiѓE@=rќО5оТŠˆˆˆˆиb‰ˆˆˆˆˆ$"""""ˆˆˆˆˆX@"""""b‰ˆˆˆˆˆ˜ТЧАыŠšчšкžш5|нrы]ЛœЉ>nЁ9Доr2ЋЯ$ё я>(њЕщџ"еыey2ћО+Џ ?zЧо‹_\{—АЏ, ŠТИљfЕe6ВZPUnј 3кBЕ53Пjжh 0/шcЋњKEЭl&ђЊ*ЭD‚™;j“`дZl@тiˆsљLоDЊЊЁvIAPnњ ‹Ь•ДjєŽKIП‹h˜pœ†б5L8Г‹U(]Јm[0мђ$уAH3ќ HвP}(фŽ ъд•0кЌ~G<у-дЕУ}uЧЇўЦЪ|XыЅЖЊ уo ЈЫ ъXžцT-ѓIМј G렇蕉MJц—–˜KUч–’mfrЪ|Rrьі;НЯY™IВЪ­ђС/”\кoуЕЈ#8zђ8"Kеѓspxa@bdплk;Mсу_*љгЋa~ШmБe^l Њ т2gЉU0Оуѕоф-Њ;НЮюыќ#|wЭёЏОцxЎч/"ЉЫXщ[Žнvояw Э֘љlДШБƒ-љљУdћ;ET•?ЙтјиW ›VлEЮЖЦќ›‡ їœшр*Љв„Т*ПќП”o\sо"’—ЪOнЖХ':BšˆЗамy蹓‹пЧ№3C”чŽ№ЃQX вЧo?сјw+s^BT9зЩјеK8Н8‡‡нƒх@Ыpфє9o/п8?GwБ.ъ-r/ЄŠŒѕэUЌН/xaОtUYkђШуO%Сyнfа5ш`нпuz§qпјN6VŸёыУцм(/ƒЙ“§ЄR`y |уђ€+еЪЋпЇsюM ‡У*“>КИШњъ*Уj&)<ў'˜~Ќнwп[ п{fJЯюоY.—WyюѕИxВ[Щ&РЈ„Чз-ўНЋђ@оKkПќжERЯŽг:хD~їуo овцЙЅА.hї+ŽЧŸй„В‡ˆђFy™<Р™У|+K‡ЁWІЌmЎњѕБЙТрьЪб ?ЭДЩњhH6иФЫЦ{щлЈ{(8ЖДЭжі ИБЏЂв>Š1я Ыy$ld†ЕѕѕъИеe†k‹ˆмУžЈLЌ0}dЫЩГ5ЖњЂЌg%(f2іŒhhˆNЫуˆТdEЇnGnЉдтftц.ЄFЋƒaїГ‰`)еVЫьtъ$гљЉ ЋФ˜йœsDмD–JЛшtŸх&ЧЮЖФeхЊjЂ6дй`тvjЧЊ„iIQc‚з! ;>эс~r–ЦD!ђ ŒВВц<У|$5“Йт<ЌМъHM=fQ7ѕiOБЖжooDДєч,BЧемkЫщБ›aЗŽŸюŠq_ 8]HkzќЭ3Бю9Šn јdб—MиЫ і*Ыћ™ХџЫЖ>“А_ЬЭxŠВ[–PDіO‡?(пєъZ^С§”€qoЕ{>}ш+ѓ(oл›РГ‰ Ш5{=лпЩйЏ0т5оˆˆˆˆˆНDDDDDDФ HDDDDD, Б€DDDDDDLPMe2НП<щkИйS“ћѕ*ѕЎ“Љ›оЭі5Š9;iыŸtчŽИЇNеЂш„ЁRLяoыl^ђќ™=?@5Лeіа-8їbŸ‹Џ)Я–ЬЬ,КЛпЦљ§cЧ—|2O}IН>=uўмЁеК­)2э]Бў{ўЮеПЊдї0бЊЕnѓ: ЃvQ#D:"“/„шCнд'Є^Ўq…ПФYj­+=)Lњ‰\=™бн} ГЇмыё=œhљkѓЯа*Ж+“ГŽЃ ]КэFи $сЭныя>э}ДH ўЪ™s^Ъ‚=mПDљБC+Ќm<…ISвƒЧ(GOЁвѓъуТ‰:­tџ‹ˆ їЯobtЇЮб]КƒбрIT7§їКOЬЯп зyёt&‹ТНЗŸbП ЃРсЖуСю2ЋхIГVеѓ1­ХmrwЩS@@[‡–йОz M3O< йљv0Ÿ“ˆ№gз”Q6ЫЧЎє`ћ2fМZiU0]3)№ХЩ з`уFЋЙŸ\žбглљ?W M|vШќѓнрињкВPњЧђjжŸЦ”л~},˜zЩxx6žЦx+нhŒшрaE-Ќ_Тє§œ~NѓZЉР”X§>Fќe„7™vЪ[\чъм%я+рЧIЦЉ…Г•!~гЌYZх}їtљРЬпи3mJЩЦжUЏPœ*Ыgњ$|]њ“Ÿ]Z†YЙяЄvЭDјo?w$wЌцyщ щ}њpJ–•^}дХ0sќЧПНяћ;a1ф\ЋёŽЎm9БK•>vцX:†уb_чgђІЅ„Я}є~0o“йY‹aОdK0ЏѓыnњќpЯяCџЃфйчЗ&,Tг“0МBћТ[(‡лžЗbьѕЩŽ%pї‘,œЦœ;€ёЌ0eмуЛщоѓ™ џ.Gxю›Œўѓ;ƒ6D­оѓye|уFPлxл­бКыAьИясS„ЋоХТKьт9ЬЙ% žвпFZсіŽ„єьибШгГ™р–ŸЌЕSpЭƒpсЏc\сѕk)žœшУУиЁЊœ[0ќЮ‡/Br_џлМ$+ьMc ­ZQх—T/љ^ШчЃмQ‡CIfд<ЪЯьˆlПхF…BQ0;фЏъќЌТhl:TužпЭЇ х6ˆŸžЄ™oPЗ)ЗoTОГ”і:Њ‡B%FЫз[УyЙАV0КPnљ ˆIЁXЋБ“6,уbгЯь Є:d4иФњ˜mХъUгсВф#\oЕZl­рђљОСФ†=Яюо$Аі ТѕŽ!{k”6ѓ,ў;О‚ШљВSeѕbЋ*R"""іЉњэњOх.uз{vМЛУ=О'ˆЪФМ ДоыЌvПњт№A”;5Й“vПSCіљНЕlЈћ?ЖŽdЦˆЗА"""""b‰ˆˆˆˆˆ$"""""ˆˆˆˆˆX@""""""b‰ˆ˜dЯЫkp^uoI§ц*r‹ыјЕ‡›^уUU#Є23FŽ`ЗЕ e@CоDгМК2я„Oщ^”Щ'sj^{.ьjиХLчјZCnУAUlЦфŽ}uˆ:‡ГЪ_гЁгzД1NвCƒ!e1al)sМ-їІ zХщK#WЁЬРtбEm9yоGe"ўзМюŽCчl Їœ;œJ˜ Mжж{ёЌe1ѕ'O'Ёs4H%ŒЂд‰NіЃ‡ыІФˆ№ФКђѕЋBšјŒ$4ДРapої— ‰–X• UЋ†{ Иџєœз"B?W~яIh$ртBC32iНW8еœ‚fРЕvсxЛр­Ї`Ў™zeЪWŸƒ­ТЯЅ$@ъ гЁэFŒЅtU\PŒZЌ4М+HюYШxѓЉЅѓлхJ2Gџ{д…TЫЩ››Нє2Ё“PEM CлШЄЕʘb#П+9эЙЉЗНJi5™+ŸЅа ЊЌюpœ>(,t[AЋЂFb8І+,ЙЋ˜ЊnmmŒhЫ€Вн№ђЭЉмБ3ЕVfб™kaB:бM‚ЄŠЮ5+ІЂtлKС‹­N ЇЬWьU юцпSІ#Ž8Š ЇpВc9YЖP@R\>ЊеУWа$O;$ЦŸХE{Mœ—кЅ,6 й'(pМ ПњюЃєпvаЋч”“‹ ХgnсЗ>ї3чQќ4A8ЌLW’P$P?•ЩЕЭыќгЏцdлЋоbг)жљяшђЁ‡Ю“[7Л"@Љ‚БЁм\0Š Ѓm “еf ІЗAcБ zžо›‹EpГeю ”ƒA5E„lmа:.Œ|ЯЂ%Њ#Ц’сFƒjC}нІДp…FЙe8z ШDцЙу:y6­$cDA9шSљ+/c`sІ…QП?йvW:GFoЎмkђqNž ќЧ;‰‚6 Gў‚КЕŽ,že0VУѕЖ› Fc4UЫ‘ЄАqЃV—oЁ v”aХzш8Jšэ>ЏПэ0Ѓa8mLиq(м~Д јIчš0їC6ћWЦ’BoГж!n!)хhH@eвp9EYbЧ#/•IІН`}ЅF8ЈzЧЗЪ|гpёЬbНЃэ@6cK‚Ы32•‰*ІПф–Qп—Ч;оb”5іэМ<ѕiМ$Ћ‚3єй—?RчйКpg"з [v§йO™wгSHиДіЄPйevyЙ)*ѕЁѕlјТpњ ЋŽвЋžЊw†Ъ37шЌ‚яe‹н7УЬR{[fM;[ћ(BМ… HDDDDD, Б€DDDDDФ HDDDDФ+ЏMzђ*KѕtжJ2Йl’Щџ+Ÿ5Лў„<› д|}ЋIІВxњ@vф0ЉЗЃрфХБЋюТЩєѓ:7wєчы{енЮй‘9­–йь;@’дяd•нAХОјЌOf— ѓћ2гНr$ЕпєІ;c‹Џ5џV EwєтЅ2IkъCУcKЛc—ды8yѕЙŠfn™мыЁmйyƒЃ19OQГПы кGсњге\M"иN@Џ\‚|ф/шЋўЮон({+№§oLш*uЇєnK^еW‘юЬбŽЖбЇП жњ#§­:ƒ3^{ž|xв”щAџШЌ| kе:WЅП Јќ№ž`Gл№Н‡мыƒЎ0zюqX~Т[Ќ3Sb9КokіЗošСГRцАдc&Е@луMLЮжHК­АзЗ—NщЫ`9$I'$nf#“Žн}$ЦаiЅA4жС /qжВ<—Ф$S™ї[ѕьRXe…йEІA Ё2›u5tчl UaqООШЖ л-ЇЦp`ЎёЊ/Є‡…Nш‡%9д зЧіитjљЉ В‹ЁлnХжkƒмQe ]d_uУ?IDAT’ђЉˆ0зLhІЩОШYY@""""""nZЬЃ """""b‰ˆˆˆˆˆ$"""""ˆˆˆˆˆBќ?<]юvгћ,~IENDЎB`‚pentobi-7.2/src/pentobi/manual/en/pieces_trigon.png000066400000000000000000001171631227240712600225040ustar00rootroot00000000000000‰PNG  IHDR^ *BМsRGBЎЮщbKGDџџџ НЇ“ pHYs  šœtIMEл  €‘ћtEXtCommentCreated with GIMPW IDATxкьНyИdgUяџY{ЌёдКЯщЙ;‘0У хъяЊ~оz•GЙ‚ЬSˆW&DИ**"AТ$2Щ$d ™“NвIz,љ[UET‘tjCNоlŒЧЦxlŒЧЦxlŒџлЦ0XуKзљЗ meыџ§8_ёиёиёівi6tp+бЁ+Orлм‡џN^ыњs”qˆђŸ?Ѓџћ‘ЋXŸX•јdVА?ВŽГжВбѓљљЋъЊЕкdлWAeе”ЦхЧЩŒЁ$sUUNz ‡yј“Ї#]ѓ“U$ІьŽЪАъ№,6Ѓ#[ƒGЩNDЎој'МVуЬ_N@?xdфXЅ/ŽђгСсЯдGB†?†xptXšЪ‘Њ""(ЊЂ"љ J’KQrв’#`ШzVyMялаgЁ†‚F:р^2F"Й;еР'"ЂЉВjy0‡5ЦXг˜ОŸ>ЃqnЕN”CˆUЛJКyЌZЋ щшŽhп f;~œЬЉ hЩ0’чз[УuvмdЊA DqBџO’MЄИ`Ё†%c№|-:4N^ ЦоDvscˆЁžХЉ\2>’C‚#0$?'B‡рћ1arš:!ЙвєEЩB1e žЏЅ™U=Q№_cшњбР0ЉЧБl9йy_EЛ~œгЭ§беССўЪ€-Š!'ЈƒЙSЃЛнИќH$1Ž…ž0VŸUdѕччЗeI.:Ъ?ў8”OпŒx№‰›эE€^ГнjђО‹&˜ЎИxaœэ!"ˆъ*&ї§wЈЊЩ‰—_њrDЯБMƒ5n-Aт-…uŒв$э@з)mLGŒˆзn№{чкМфё5К~œЗHуб1ЄьЋуђуdЦcЅZ>ѓž|ђ.G{ˆЌЙ†kœ—u‰Ѓˆ ­ѓ/™•Њ#ъ…ŒьХ.–!„БrёЪU‡BЉ:й1шPALЕ§І\‡$ ѓ›Шњђ“| “FГЭлЯCž{ZngЖg,:bUJ–№§cўјъˆЂ';ћЦcїяˆAХВЫ\жїџм6)Z‚Ц'$W%[Иg9ц’Х,tb)Z’7]CdеЙYLJaqJД" ƒ8ЧлщT#1ЁлреOЕхg?ЉЭf –ybњQВ…?Н6фыї);Й[o(Лƒћ+"†јОЏЯлжцѕ?Н^/BB:ич#rуQхэ? p eФ–nJ‡ЊRЁ+ЁUвn˜пƒид& HЈрDЙј™†>yя$эnˆaШиtX’ћьМќЪ*{šLЖ` П§/ tV–Р(‚пepЪъE3lш,By+цќ|ќе!эю№еi–t›-:SNФп~я(˜=`Ьё1 h!е*–й$-шvРчљЬћPАЙђ–&7Пf?ћvlЅу…Ш†КГљ—Аm|~œЬ™внПЏџЎЧЁCG Vˆќ1y˜~MpkTzЧљЫW<…Ищcd*ѕ #R(`kYјЮ ѓр7С4ЦјЬФјтїРUŒъVтАЭzz›•ёd(Š)9ћ%ЯBхФщ„PсЩsW?PЇёРaАO`§у№!v;-ођ‹gBŸ\yœ6eађ#Оyљƒр†уЩ€*&єV0&*`КФъBЛžМ?–А ГЅюYр‡Џ;—’kХёињ‘­ыf-^їХyш-$Ÿ;Ўг&шBбхkнežЙMxжуцhїB ]йЭгЧpЪЄpМrнѕ‡РŽNа–.рЬю шBэЉфAj‹k;8rЧэќш/ UlN‡Ё}WпЇ,š|LНO_Љ*‘ŠЎђюk,щ,ЌPђŽ‰;QззШKю{№^С†ЈNunЋ01Сп^qˆЋo{PŠe7IђвБ!йтUг=Q“Й *яКР–йSІгGX5оЊз&X1…B,хйSŽо%ЕŠ Ѕ‚ЄЯoL‡#рšт˜mІіœNѓX]о§хлР6QеСZ%џmHЧЈћБџћqљqcЈЊ˜†`š1Мб‘CЎрЖяЃДeJАwецџ­Ё`xLLе-;јЋoў˜Ћn=JЅd‹ў„шHiЁг^ѕd‘ѓŸИ%Qšѕљ?<ЧРu}™и}6ёќjвИЌхeHжџ,S(ка:ЮqŸЬЭNт1ЉŸcl:DTТ™.Х|ш…U(зФptйЭОEpM0Љm™‚-;х’Я\Щсу-ŠŽ™gSЙŠтФXМџЙŽ”vЯ$gА5y>2‡ЂfHeВ(ю–SDю–j!†rйD p-0#)M8Tvю‘лю^сc_ПYмR!9AЉ"‚"/и/ќЦ…“`–R966Ё#бuУ˜œнR•7|ъ С4S?иЃ-Лƒ1КЁ2Y„^Tsn Ьqt0ЕЅq‹‰­3ˆSТjІ\2„’+cђ@ KyЊŒ;=У•wЕфяПs;хj9Ѓ{,: QПЏЪ?Fњ;oЕ њЭЛ ўўњКт:tМ@ ў bЙŒDе5ї=x/ pmAЊГJаCээМљв” ФъЛЁ$sЈљЄ‡/vB7PнY‹ѕ~zBq*ЩV>Uем вmе@‚ЅйSД]_„ъvѕпAБZUт(Ѕa:TСД WзЪь^šѕиК[?ёƒ‡јб-‡(—leШМ У1*ЭћДЧхЧ‰ŒЁŠŠˆКЖrљƒ&НКЉ81^ЄXЭУj”jЩM$П†:)ьџДеGІvЁ­(юсŸЙ†(B]ЫPэG“:п‚ЂX6њ' 6 QšVЁš—‚є[3Pz мЩ9эtкP˜Ђsќ EзЬYU‡žяы{:зz‹gэW^љќ34іТ4Њ'H‡ЈЊjТЫ/\єф)C+‰ЩŒЪ1CQE•Ђј•ч [WпиЩ>uтиљ“уXrе •3ца?xж˜eDЃžчuIћOšAsjЇіZ+йSЊ+сЫ #zЫGСЖaЫ)МџЋЗq|ЁEЉ`\ъ’ьц?б…@yї6х-SйA.їwyгО=Jгtќ:Ы0Е‹цёУ”cрж–cˆ|Ъхuаж"цЬ,G–>ђѕ[РЖR=–Эup$6ИъГжА•/мkѓэ[ы˜F;™ЄiК“Аx7Vu&QОбхЮЄ4Ž(ZJTйFT?–œц5‚Й3xыgЏ'є",CF6кœўЌЃˆХŠmС{.tЁ:™*џ”JТQ+ъ`NяЅз\LќнCyёA Ж9ь{де9™FдСйEЇеLцG0ЛянбуsWмU($vЊ/V›гБЪНО ?ђЇŸ#ŒС-РЅЗ —пе‚А™ьЌОКHуfЉšаДІУг€0 Tp “„­%0ŒDйчNсm—§ПгЭ%4<:t0t.PЂ8љЧыŸсPоZ# aЭsMbu(Nэ йЈЇ< Ё:KgežВ˜vjзXг„FƒŸ;гс…OлKЛх'Šv’tHš?кь ЯнЇќв'!Жx-x-Jх žкDFђОcCy'_vŽeŽ-W"B7„ЉBЬ›Ÿ]ЋLВЈКv‰*–і ЖƒођёфP0Бoў^Ъ#p„bbј+и3{iЏЬƒhрСЖ}ќѓеЧљўMP.;kО‰ЁSzј§s…НћІ!аѕїbA{‘тЬ:nћгkrŠƒ >єеџЫЪe„=:ВЫHœЅТіjЬлžSJiŒk§”Ћ=ДЖ“ ГЅq„Nю!8t+…‰iƒ5&žmЌNиҘо‡З| ‹(№`їМїЋwrџЁ%ŠŽIыІtљДˆќеQД8WгVкЂЏџ^"Mг5†тŒіZ -ˆva$1:лbmФ[СнК_[+ УР‰IюZ(ъЛ?w-nЉ 2HЪ&+LRD“d/щЯ,‰™і|xў~xйЙ5РХ N$ЩŽ~K‹Е)КЁЊікЩ{qnI=)aЗЈY(kr“Jгѓd8p^tm<ГLм^Lj0Г›Э–SxЭЅџAЋбRл4ћWгœИmHЧAй„iіgњќxcЈ*Ў),6•ї\-P?ž% (‘“;ш.ЇlEI`-KШ—д­›ž&АЅідv:Э†ЂQтFŠeb о}йX›0Šћія‘ЄcѕZ%МэєTŸА-цmЯ™ØАдMлП~‰@аЃ\,р™еі–І)UDХ-НЗZЫє$ЫмH2FcpўќWŸa?щуaб`ŠBі|['ЖЯЄѓЂ6,ŽеŒ}ьЉtš+Љ/ˆ•Й]zХmўсћwЊ•оцЧ‘Ћl#юxЂПљDјйsЖ(Ё™Пp ЎA†ЉxMЊ[vjЇчCи$Ÿ8%МШТЈ?„Y™дОЉŸДЄ`˜JЏAufNЛЁBЏ5CЫ€Ъ>}ЫЅзЈяХИ–1lK6ЂC“ЌЖjIљРEeЅPC4%kzZ,КЊЅ‚ц’fЗН0№aзщќёWютЎƒѓИ‰7сQ”нYVќю9&O:cЩЙ­dpЃ2m•^віSЕеXNlАЩOЗL'ьоFibpJЭ5ЁWЇ8Л›vЇ­„AЊч ŽƒZ;љŸŸИRБLтфЖ!Fп9ЈI`rpѓбдЧ(т•ї_oЪбУ+‰ы*9Љ'зЫ‘а,‹.?ˆ],Щрњ˜w{4(W'ёАEЛЭфНОpФ0ЗO>ќэ{9№р‚KюРЌкш *ЉАЅФ ФЪ;ŸэPй2IБzq„ƒ/ЦфNМ•у2фЊ }ЁЖцТ!),ALа|е]rr5ƒіьЉt“S—єŸЈЭptйсЯОњcБ 6QKоА9ƒЉnФ,Щ@њѕ8›‘ф™ Žѓ‘›mn?А†ŸЉAQq сќН&І Ž%59:=JЅ’v•ИН,ˆ1и""ЖŸЦŸыnю<ИDЕ`хxјШаБоZeg ЏПїсŒ}гЉ(ŸV fџкЉЯKjИв{z•щxnаDœвj—Лa ѓЧx§sgйПsšžіч—Žь:[UyгГ* %LЂ++{.іЅ\Љв‰mш6Hx№QУ@и~oљьMвhєА Щѓ`CЙтtŽяx–#LN\yєѕIˆB)9‚яNKи˜Onk9cb;оЪq І †™ ƒ“o‹kЦhiЋxХСц#"Фl•ЋDђ‰ям‚U(ЌЪjоˆŽдЋУ/œ&ђтs&бР–Ё›\ЊгvФš•VГ q }Џƒ*иwѓЦO]…с:DБ>ЊВ;JG;Pj%хэ” ъВ;ˆыŠ"tК№+g ž5 ‘™]ћvЪR{jЇt“ш@~S[JqBzЁˆу-aИЅэš:ў#ТЬvкэЖƒи`КQ`™ТФnоqйMвi{sC: ‰ћф^Ж QЄњОыLЭ+F”їЭц3”кnК‡nгвФT It)VЊДC^S|DшћЪЮSљдх‡єŠ?HСщ_Сu8 ”\Ѕt$ЈЋТ›žnpжi[”@ЇžаЇфјЮ”FЭE0­QЏf2FБІЎЏŽП‚ЪкgJшуšŠ–gё ЩЉIGѓД бЌОѕЏгRЋ_S0.Ус›Q~ˆц ЎЖŒбџi oљЁа8Ж„ФAОцEs+hy;Св!J…BцКR М•щmtќHё;Щ•9Ћлгь3"иЖ‡O^БР7Ў9€SH3‚њьфщиl­5Dј•3…в@ЊcQлЗ2?p х\ Ф‘RЈтЉг9ŠQЌаЯЬУ€ю‚Оу…ЛйВЅšTMчЪЃ>‰1ъЊг•˜‹/,CЕšЦ"гVz+”gїiЇе$ єШ№иiq-Гћє}џvя:Ф =^Ч’+U…HѕнИЬюšJ< йё8ьiЙRQп,ЕW4g”гHнЁХЃZ” s‡ІSˆ)8Ђay–`хИі!yKЊTЇѕXЃШЛ.Л^ХБњ‰u›щGvгixшsї+ПwўŒ‚;pŠЅfPЧкrŠv–eуDЪ5'эстЯнHЛхiб1ћP#†ьббП­)~AQ™šFуxрЪ {”ЪэЦ6кЉ+bdѓ|xшСдZGP.К9I\T\Яž n.&.Х!QˆCŒ­;єЦ!ѓэлеpЌ щH\XЩJFWЖ­|ћ!S.НЁ…а”‘щGГ5œžQГ~PŒJЖ‰.Lэ%X> –#C&:ЋŸк/oјде`щэWSэVЩЄЫЎю§№"ЅRTоyaQ(NЄЪg`†-œ-{ш6—Я/йюLyNТХƒ\Wњ^іЈKaj;npЫgѕхe)ސ{ф3з,№§џИ_мRђXtd™хkёCХGЗЬp’іЊ1ТXЅфŠ|ы^јТ­]VаЌаJG"yqХФ8šэc"…Jr‚Œb –—Зр7ђWfAsњЧˆaСфоњй!Ž1’xжУІcœЕ№#РTоёь21‘ž@‚Ž”ЇЖвъt ђ’шАwяШф~И8f­1lCˆУXџзЕ,Ян7GNŽY.єar7НХ‡ЈФ‡ъз)LnЃлэ 2;ђ‡Џd§4є`f7ођзпМЗT ˆт‡MЧ8kЅщ4К=xі.хЗž1 ъ&™cХ­D…4Ыj$=Vru ІMрNЁ ї`WІP5 s”ўђYdЄѕ‘ЇЃŸП)јijЩAЬˆКІwаiЕ’ДнA9ѓHЊЇљ=иqŠ~сšyОwуAм’žт7—Ћ VЉч Џ~ I074Сk31=K'T№к™ыGѓѓюы2 …*н РъCЪSаkQœЄ‡‹vызбЈ-‘С-РЗwђ‡ŸЛZБьD†’жЇCD№cegMЙф90*љИ– еYМЦRšЉйЭшHэC†Аћ .љТ-4ъAzўЃ$ЛЃv7Œ•X•з>Эbћю)­$}~ыnк­&}з“Žш }—4jt:ь  Х‰фљщ9К~HъEyž![FБL3œцŸНЋP$_a™ЇУXUz™Іv‹Ъ‡o4ѕ†mˆги†a*ІІЉVђяфg2Ёт$О1šGUDЕ\ЎhшNuл`:‰;${оД|†™|†mУ–гє_Кљ…І– v.ёyUf^–ŒнпzУKљг‹Š“jФіь)єЫЩ<ЭєлАVгaІE\гћhН—RЅ^C ;ЮаvcEЯškаa‚iiЧ0Л‡ыќЭЗяTЇhFq†tЁ2kёЃŸЙЁЙc€Ј бЙ1ВLЖ‚ѓБ[О[C1|ХДгЕЮбо_‡”&Л ;CМx?vЙJСЖ4,ЯЖ–ЫN\€йg§5IзХN\)sgёю/нЪб…–m3Ћ%вЁќл!x~DQ‡џvœФ&k•mЦМыY–жvЭAwgыЉtšѕlо:рЃЕšeb‡Жлm-˜дМтМ Н№‰;№ƒЈ?ЗЭј1тъШZбмз N5{-xњ”э№{чз АДTt№ьIтNLwdўy:ЬФmd˜0sІОњЎSПjІфуЩ•ЦЊ•"\raI)UqДЇбФNќц2X.Cђo˜šгЃС|ЊЛWŽkС11Е‡1ЙSЛ­Ц`~ІЕЖ-1­фцЗ}/—]пс ?МSЂЃ)тѓІњб?Ѓ†№Š'њдГЇЁггтфVZ~œd™vЮ˜:2; хI]Ж№ЦO_Up5ŒєЄd7WЭо/РлHЯГ‹L+€Sf”ЗMЁ*ж—нŒУLŠГФ–гOпЯ7~mVOй6%н$ѓj,~Єэ*‚mIз~ЭMem~и" M‹ ўњНЋщJ|єŽьфЙЙ&й#pј>єŠЇђћџЯЙвiuѓю‚ щ5q –ОшяЩ7юŠ”жqaхXgic:,яWwїЉRмvнvSНcї –НЙ†i/Яы™3зПяПтZ&~хцЛО~dАфЅњЅЛMyљЅїЋ'E нЂ9=о„Qќ@јЭЪ* IDATœі§\~ЩбѓПMТёdwшїBЁAхН~cщЙi†apЮ_.шmu„іЂв\4vББf<˜ПW­гЯJ“„ѕКВќЄxa›гaйТб‡xцJ|я]П€)ЊAЄC:(эfCѓ—!ДX0хw7љГoмЉ:"УBГЁ№*Ђ Й5мУС4QkY’ŒOcIв c)EM§бяь“SgKк тњх‰іSЬsйц*EK8мŽѕ}пzHŽ~Ч2NˆщQЌдŠЖ^ЭнG›ИFШкџЏ“њЪВОыљ“rё wjЯ%9ЛЈШиt јA?DŠ(нїИdI!Й‰m“Ћш№оН]'Э.:›мœ1DC{r;_žпЉakA гT”Б6‘ Š˜дІžyцщG§“ћ˜ˆh'P9Ѓмц}UйVsёB{­Ёh‰ЉїфЉџаЃгѕ!є4M=о|AСtXYZп=ošОtЗЎд{тZF–x0?TJЖш—я‰х]Q2Ѓё o?їЭВЬ/7єЎvI№šШ cS:D/‚m,ђнW%{Ї:^”­я†r•йс’#zЯЂ/яљк}Ев,#›бК/ruxžЇF>mv3[Ђ1†эъвБу\їънœЛo’Žeе?cшGrюД ф%Ÿ­ыЗx2aњ:H?пlQУ”vЗЧйГŽЮЭЮJ/ˆV9Ў6ІChі^КПЋoxўн^œл@6жѓ4ƒŽ’kr§!Oџфw‹х­ŒЪ№ІtЈЊ+bКeš ‡е0ŒёqЉ єBх#/?—-UW§0вСЌЁTšщhAЌˆcЖkŒ.ј7бŽgrжGŽqф№!ЂТ”hR0ДљЂ€aЉб]’И4ЋП0}|щЭ/аfЃ+І!Cт%УHЫ2HЋN~с˜Ђ–-iаWtФƒД‰№ ˜шgoxйЇпHвл6Лdт”SЃ^KЎ~х6}ђY{Єгі’ЃбP зЦtdќшЧтг–ЙЊь3ћ†7sдЊ*–!8 7?ёŽОNйўГŸjъ7Џ="тФЊŒЗ "fиТ-зд7 лоpЧ­„W eiE~э1Ÿ|ѕГщЕ}т,Яs“ЕJлoR.њњoіфO.ŸЧЕLМN[гВё7%…НщbZ†LКЪз~uFŸ~і.щtМЗ}~ЈŠ-єPл§]&8z$E g%  (UJjUfЄQ_aDў6ЅУ‰ЛјБУЋЮ ф/~§IєКQ_?6“ЋьГ]KФД3;0„,О1 ˜ІОтK-љЛмS™RПЙ,˜cl †i7ZњМ]MОљЯ1 cK?4нЧKnЌuГ+Џќф ъNяoe1Н…Žy )8‚ˆhзЌЭжHw:bZ№ЕWЅ/њЉ=tzС sеИtE[T,#б‹с@єцtˆhПЎЫ0t$vЌ …n7VцtаXНСљЩ!ˆЏ DЌь;њMс2&+Ъ./Шƒ,уіŽтLnЃуuV#ЄЌѕк0 W—ђDvЁ&_ОažЏ]}@^ќŒSщДzЙтђaмєЬХиOŒСTМPQbd p“ѕшШНєq†|ьь)~pC!FWї:э‘b`ћ+і<]–nњ!яјТЂќЫліє“Ф‡;˜mLGЦЪVТЌ$6KPяFŒѓ;BД}›ђЃьТ%х›wеажBЊќ›іЌ€ЈGЉ`жvH|л7Љ~>ѕЅ%CжF†)ЬеШZЕЮ^іl‚n`ЄРZ•с–ЃШ_мZ„ЃwRиy О[ѕЛЌЮ€X5AСб.хOfё–јУ/> _9k'Žeр‡ёXќQМHdзdЬ_Pуї?лZ`šы=Tw"^‡кьMO$>xХНOЇЛt,ЩОйPv3мЖŽ'*Dю,§—ЯѓЪ wђ„}[Єн Вxё†td7I/T4ˆD‡эЌЁФ@Е_Н;’OоІАtBбп-BаcЄяСj:$ЭОѕцЙјчЯЛT гђђ,кD?„’­Ў‹Мџš-–l cеЌsп8tdќШЕЮе >a›§Нhі3ыŽ' џ0 Q#П)ok’Ќ•‘[+№BбѓvЉОс‚IаB’2@Њемџ3ЙQФT+lcЬьЇЛtTувœGnЧ­mQdqщPa/9шгQш{~~ЏЮn­ЊŸu;s­,C ŽyЧ•†z‡+ЕэtŽоЃЅR)*œGе>ZovђєъNЬhЛуСє џz]]Пpх=jЙvRЙ*›ѓDcU%DыIшyŸРдв‘еаДЊ[‹–83DІіpдъЧ(е”8ZЕŒрЖCЎЦ;4jЮ+е§ќЯO\ р96•+MњёЉaєэ@_?Œ5ф*­MеŠ-къ)|­hДДхiТ•CZ(†y0(ЩљXYжW=ГЪsЯйЃНŽŸЩіxњ! ІшŸоhъНї-AБЌ^ЛЁ%ёЫ{Е6'ЉгZ)—Д‹ *кё< Q+…kŠsлyвJжжD2дbбь§фЇˆˆc ~ ђы…`iIˆЛТє^iЛŸJЙдo!Žф’4ЋDM. aWJЕiѕ"шеХиЖSnО/”O|їN1\[в –„'ЋмЧзИtŒ–э{>rЮЌЪ+ŸQЂ>Ъh:п\XѕxRА„а” Е,˜Sћх­џt­xНH,ƒЁ1~tœьDБJфЋМњЇLіь™‚4$ƒj^СД„іЂ”gvащѕ’Њ§ђŒxžы/'ю \љ o]ћŸ †)qГ-чюUљ іIь‡2Јк˜ŽєЃФ-(_ОптK?n qCДнŠЫХ,з щ<ИšŽьИGтšБ“;Х_>š“mЇЩы.Н^д0${s~€H;P):ШлŸ]€ъ„іс)Є_…згЦш-‰;w•љrИ<+ўтC,IВЪэLЇђkhк‚ЗDqnПt[M!є…­;јо]>ћƒ;Є:QJѕш‘—ЋtcБU>uЇЭЗ­ˆI*[щtКтњ+‚SLDr•ш ѓ#PІэEyгKЮ&ђ‰уЄк}й5‰оz љ№ЕОр/‚]’Р,IМќ 8Ѕ @єшp=PПSЎ‰wԘй+Н•у`šЖрЯпKБR•~oЖю§кє3 KX8*oyўvЖЮжЄ—Ды§OЉчw #=sѕs⧆ћ>]эgСh?х:{_EаXU-GљЪ§Іўы-mDI*™ˆFЅYЂ#ЗсдЖ(a CепЯŸЂ1#аИКCУ•c`кВыL}ччџC[Ž:iSњбj†Рh.ЙДlMб~5ŠUпјtSwю™†zE UCŠJиеB‚ђЉDAp6ГEo>dшпќлЭZ,—ДП“ЫOˆŽ‡9F'DwO+Џ}fYБЪšVwы MД_„јZ,и—fK‰Яе04r' WЋ[(бЏpЯ*Ї“ќѕ,ЅЛ o~С-WKкѓ#MэЬXtИІЊпў№G1Д–E#(oеn}!щoкй r˜е$VфЗД4Г[›†‡)рчŒоП`ыћПќИЅb?ЅsS~jˆhЛ‡ўмiЪЯ>ЁІФ’?к/Ч0•nƒђфŒv#AНvђ~ЁЊ^laїеtKƒѕtЄ ј-ŠјfIУіrђ;S`j/—|ё­з;§~Д\% ЊЭЎЁјЃ‚%TВъhэПWЋ•2§›ЌєЋ ЛЈe+KGyл wыюэ“tSv5БsІђюk, ‰!“дьЦŠщfЗ<Шр€ifarVЛН@ zЩ Б4I7TьЮqЗ’“нбъsCitє ;C§‹Ю@§HrџsыљЩŽa Š@ће@CžЦД:%wFыНШ iSЄн…з_ЎBѓ8š/h)Nбыѕ(„ pЫ’ЂнфЖHowЫ)вЉ/%‹I8_(UXъNЫ>}ЕXw8ž1ZXё0шH[‘Ш(ОN'vO)ozv iуЊ>\pކ(Єф˜т;“DЭyЩсьРм~yѓчярУKRt-ѕ%>кt<œ1ЌДћыkЮ59їqгBl2м%@…mмЉ9:эvк"W’u+Nз_ЇЋ‚Я0xROkГХ/?б’_Ор4šЭžфвЗ7Є#9y‚щљcƒыяnBд`žYŽ„і:Л<1Јг\orƒЄTА№эj‚•дї•ЧАѕTyџзюро‡Љ­\z3~$zH ОШwыtВЉЎ.ІBБ$РЈnЇ7Рƒа“žт^),r€ƒ[\КŽFиgzэF=bI0’0gцИуAјш7nl3WŒјШШ•Hвfиp„З^aШ‘‡“ЊџlЎІƒoVDяУЎЮHЎ<Э*Юn кюЪwGќоEЇIф…˜ЦЎњ5d7УЇЊT.ЛгфŸojacP0ЇЁшФNzGю”RuJRЄZ*,N`\лȘKВ\)ЁК“юќAJEЗ_9ŸKЉLљ р-ШХ/к+ЕЩ?”~œџФz~ВcЋš/ CF’of3TЦ ™+Q(””пdsџ+ =†.M“Ш™$\<ˆS, Ѓфf“НХR™Р,uъЙ*ЩДB}n{Х!ЎЛу0ХJЎaЮh2ХIвБ #,їQ™}ѕS„ГO™†ШшGЌЭЊ,Œо2ЮмЉt–1„ѓ…•-ЏТщ&R˜фuЇћhбqВc€)`ЦМѓ‚8еиd^+тˆ’kИ3DЭХсОЬAІіб=v€jж|lД_„ч’_8ќ0ёгŽIGж№ь№ МяšКiŸŠўмB˜˜ЃнjPЄЖЛкx‹‰ЖБgібi,­B ZaЉ7Х{ПxbйXFц лœ"‚ТS1Џ=ттpгЅl =JЕ™ДтЛ3’ЏMь":v…žbјї)’@ЅRЁ'.tWˆQšn–ёРoSœœЃйJA†бяg„эXŒхћАJЕІЅяд$ЭКетyћ…_ЙрT:-oƒМЕџ\z~Вcфo УП•юЬN:e†КЧ%@џP*sМћВ@uаДhэні$ш`U OrБё${>ц§Я/@Б†аz%щ5ЈЬЬвёуa˜€t тРƒЇѓ—п?Шя9FЙh3‚‚№ЈгqВcdАў?П_yщгІаРЮѕЌШš-coйG{љи0џВ/г"АkшќнX3CЋLл†…cМі9Г<ўд-tƒ8щ=JWoк1ИСт№ЁEh-8Cл­юР?zХruЄa€зЄX™РS э6ѓPъЩw"ЛіѓёЫтњ;уЄ]ыЦсGвјJ #ј§sMіŸ2 оШњЈR ‹Nь X9 –=|SW…ТЎ‡хЏ@!GCк$ШШє^zKI“ ОqЬžw‹2ЫХŸЙУ-Ѕ`WЎTЧL6км`Q?rLом4г&0'ањaœb9З‰ІыXo№_ЮЖјЏчэЧыxƒЌя1dWQ >yЛЩ5w504Џ‡9Hi+ўЪqJnZС‡›BJЎIPœ&nц`oTЖЌ2GЇБDЩŠСtЭЧDиџ8яћхЧAЄ§0Яџ z~Вckє$зЦЄ%§Kš^tD­ЄКOпЁG\ tЭ ГШ‡ЩНДпЉ•ъˆр‡=ЪхŠvb Ксš…ЬO*;іъWЎ]ф_Џ: Жk­nлљ0шшѕх|ЂЙbПФ€†№sћсeO›‚абЄo•@`K”œЎ‹кяч:’ˆmГ[_їW**иІЌ…z№Јбё№Ц€ ЖеЗžяPл6Ѕd…}†ЉєъTЇчшE’oDёv€•жnP$з|L КћЖxМёEЇAиoЋ>&BСUНтA“?ПІ СЪыHrё™8†B•^lтtŽiŠЖлЯШБ%PkfOвeOX}Кз8xЈьхw>qU.ats~dqЏNˆnЋ)oЛАЅО;41TНeŠлNг7.ЅКПVZмJМєзN e}оWД<Л‡NЗЇ„ў0œxў6?ЛWџюŠc|ї†Ldpхi9єЩЪ•ˆ`YЪXќѓѕu miПчЮPЁЏдЖгЊ/jA; ,JЦыX žзїќвй Iќq|йS8ВЂ\|EЈtц‰1tR=kКЄžк*+b+:ИY˜~{Ы>:+ ЁKŽbУ$rІ4œПЇ29ш1.&,Юыk/œсigmSЯњrќШшрOBЯO| #Ќ/ƒN€dXїЉњ !P’єpRqlхš#Іќн =!^4ZZЅ| –EшLŠ,мƒUMQ:1pт.2НWМЅ>Тd~Њ}|caю оtщuB”œPћЭ^dЈAР б1’ї,ˆЌоЧEЂDођL›км”$m3ЂхЉ­t§hау`ДљJвkцvШЗnыЪчЏИ ЇX\нbјQІусŒ!"4=•'э„W>cB%4ŽХ5­lЅW_Ь#ХцЎ Љйr Œ2КrЛXNc†P_фѕЯлЦЖЙI:Н #к˜I\ŒФ"tФ+ЫФљоюУ=3тЪst’RБŠП!ј-ЪSsвъє ьхњDфћ:ˆhРЬvЎ=аугпН“rЅ@мя В9?lщvс7Ÿчž1 a‚”‹зІ\*усJмЉ‰ ыA х):]ŸBдDœT†_\лЈ0Eа\”XрˆƒыЅМћ 7с{!Žeір'#WЊIŒžXИјŠКKФНeЈ0k>6ЙGМЃїPžHмH†e ЫѓМц‚­ђФSЗJ;)ИKvГохЖЋМїz›хУK‚„ЉW_Wз9D0ЙKкЫЧ(лЄЏ’.}Ѕ‰I<5б^kг hе­вэzТКрЄQ]Ÿ]ЕЖќСЯžAфGХњˆырOBЯOt cИЕХ№kY•"=@ U"xнхРЪё\р[Wg>ав4нж2E Р)Aw‰т–„Щќ>7њ|BmŠлŽкМчВы)” ЌUTpbtЌN_ГtКZMžДCљнgTС*@Q0BТвlWoЌбS=G‡˜Lяхuџt­F з6Wm:Nv S ю)o=ЯbЯО™ЄвжoRœ˜ІыЅ(ŸШкќыпDЗгЊ/QŠЛЩЩЛгх‚}ЏњйГ’bбЁymРФ~ЧЯн%|§Ч-VˆХ\[ГMЬ)тdљ~Ќr ТЂЉ„Х­„уУн2з’cCaj?яМьF—;”глpоЕ>?вC(ў3˜œBЃ3іHZЏЄЗ Ц=˜оGыЁ;(—wАФ=Jг;iЗZ Ђђz

Ж яј=‹хФ‘гYфЯпЮЎэSДНАяˆx$u№'Ёч':†‘ЋПЭ+љмЖіCчU‘JQљ›л-љсN87н еQFОSЌ!8eBk’xх†P(ИDХ-vš‰?q№ќ?эФ@o??ћжНrпЁ%)•œ~žђ б1huмЯq^№Ѓ2EвФ€FђіѓMvю™…іВ”ЖŸNЛUOn_†GЗ\E‡ЊТ–\pјиПнŽщZIŽћpЭща>J0!’ћ)§я!OК$ЅйГIљС:эVr#YKvѓЏaЫ,їЮ—љШзnl+YOY+Vг‘Џ>rў•џёдIСƒbЉDGJ №ЇхАС&rfЙ„ЅmТвAЬRMŠємiтіJœ^OSФ1АѓlyуgoІйьa2Јmas:2~ЈЊTl‘{о$а[Ы]y[;Хo.c›"дЙј…;eлмdr“g}Ьы‡ˆ`›*DТћЏЗ–‚%фєpm[‰;5\oI0 )TЇЅhrC27фA2b "NїИ OšэЪЋ_t^Ч“~ŒVЧЃclЛЛŠв/ЬиLЯў’ kйŠaPџёtiЗњ`ŠЙЫnџъІЙŽ}ЯtЩA,+~КУс›œe^Щ:[• MУД…Х{е>ы<‰­бтМв^У–‘№ПЎy Аmхр=ќП/иХЅЏ§|?$TЭƒ“mJGЌP0ULЫдŽУ*ƒог(PL?ь_,s}(ПuйМw…cгю^::ї5ш№<1ќєЖНŒSgЫd—ƒд„MшH‘w‹Ф*"†Ќu`눉п\4&TФc•AЉщš<Яrw“\!РрХџИ,пЛгSЅ=КЭбЅ]‡ ёXщƒт6љяnгOџSщyˆЎAХzќ‚ЋМ§ЂєЙЛ yH0Mн`ьСkУ‚і"8rЪга^G8z@GІ­O‡a ~ъѕРџ~)ћЗеЄу…кOITжЁ#[У’ wЎX<чЏcmŽмЎУяѕшH}QЄt`чYШєєл №RМ$нœБ”‡ю’пџogѓЁпОPЛэоК<_–€eФМт[џХ[„рx ОЯGщАX|@˜мЅЯ:ЛЦw^ѕ8"5ШuЎЉƒCњЁ*%Gєв;DўћЧ‚•;ЕHиP“тz ХŠqv?пЈРƒЗ$ДъњВ›ŸF*aЖœ*_yљŒўќгіаэ†Љ[M6Д%'bЏ6фG к“я5Тц:˜їMkЌ"F–r<Ж*ˆеЧ?Э4шВ6РŠ4{=žWО‰ѓ b*Œџ%h|:хšбЅЛвХ0'NшљшмГЈšз6ђ‚MщPUЪЎСU}:^‡ і— у$ёu f2NFќъй&KЯЗЙѓоCXЇ•‘u!ЂVs#ІDзxhЉУ™лЋQ4оD(кЪ›Пуёo‡lœ8—:=ж ^ЏЧieПћЅ­иІыpвЦњ`UB/RЪnЬІЬ_йѓФ­г:€Xїрк6oў…S 2ˆуh5\›†@л3Јy‡јџЮ^ЁlW6€щZУ+!;0Ъ3>ѕљyŒSOьy1Š4КU\ёи7Ћа!"4}хЬ-1{q/_s€Т^7I==ˆу2еš‹Сп*fљ„xаєЮfЫT•nлУ4dЈ}ь8t†АиvјљŸjуXыЃ4­)ЇЉђЛ/и[phuLCrЈ‚ыцM0Шб3h­,ђkgІhUЧч!€ЬЂ‘21-ДZ‡‰w•Nа *<§є ^є”нјо`ѓиHvЧеѓѕt0яТ+9ТзD\ќя†ш‰,т‚ŒG=оћlИ№ЬкН(зА“MщNЋЏKнšFTEAmCp ц‰Ў|є%3šk3L6t~‹@гэ}‘ Й”[pлl#цe_7х+пј–.}№9ˆх&˜K›вп‚ыH Ž'ыkљњAB/Ру16ŸС|ЂXЉ„Ы€чўн1шЕ„ШP"/ч‘маюЄО№&ИгМэм:яyљ3Љз[иІБц\ВЯшFZАУ9IHщ {!~”%}ˆŽУЧџJŽAR'уYнQ~ФQ*ƒrRrt‚œьŒЎеfђx‚ѓ_MG–БuВzЦtzAc[чkб‘ЫЂc€ežŒ БцRЗKѓkXД ФYw 7Ж%fУqђrХвѓ’ŠѓењѓџГїоq’œеНїїПѓћщ ФvхyЗw F№uc ч~|і=еѕPŸЃЯIКП-у@k†7qЁu77Нѓ%вNу@пЭжЎŽD„о’Јј]4#x")>8*_˜^Ѕг№яћeŽњщ~є‚АЄчАЬБ+3…IЂЙн§f+љ‘ЌїNNљюKџэnц\оїЅЛфэПvБЖчти=DЧК@х• IDATфил BЈЗBЊŸ”Ez'ЋјбeJжь YЭ@!Tо}KІŽk%п$;3в|Y‘ц9КC1Bk–‘›uЎeфу&~эЧwщ9[G%žHњЦМЇKtлі•аѓYZ8e|дoтv!Э]a,2к)}тџu7Tuƒфоv5Э‘ь5^ІпљНАЭ§ˆОlиЇ­–ѓ#™|PB7LJЂW\ы§Ž*iПыљГС4Gт;u#q@uљ˜їѕ#іЛо_X9}ќш*ˆщŠA 6ГBLM/ŒйjOd іЮ%™Џ8 юOŠЏ6HBP(iЛž€ЮЛI<’л­BQљР†ŽЖБм)-ЎлH]‹рh5?Ф(^KЪЃC#умrЛЋџЦнќ—_HГоŒЄАЊ‘HЪN/1vОK­ЕMОт]\<І—(Le!ШŸЈ=rzсЇ•ля;"ДцЕZЛ››оѕRЮо4Lгѕ3р‰цG48‹NШпоeЫџјќМтNУд>оzЖЮwbMѓ4§ечR”6іњГЕ6u>$ПvЁбOџЏŸ”Žя„НyЈю˜?eу)O.IжЄ”‡ћЇ…џ—P&R‡*хi•7уЕШш8)=яЯЈЗz5†6э–ЙZSuцcэхŽ+_Њ[ЦЋ=Їв•ќ0‹ЂGxiq=CїПџKFƒ+~Н‡}їёЗс…P(Т'ю1мўhC№ыPЉHЭ[Ы{?w‹`,lЫЄЕ%O0?T•Ђ­2н2МџЦмPOйŽ{|П”Ѓъjщ{œp>‰к фЧЖPŸŸ'[vђ™ы&флwHЊЋŸ41ЪЦS6žl6D+ОЖјЫ[ “GІA›0М™…й )хH›VЪ< ~GЪе!О…6fБFЦdІQтƒ_КCˆjƒd1@??LrDJ(“/‘ЪRВ”П]aћX:5:vіRўЦз9’Њ,<о6­8"“ѓЪo…к„bYј*[ЖЫ?]\ЏЛч ф ŽЄyž'˜фD?x‹ШўGчА]_8кjЋЉSV?ЅіN;žbŒвiiuhзбж|Њ2Зf‡ўйgn&%U§сљS6žВёdДЊJоAoŸАфуЗКqЗ-„ oUї№=ZгЈPК'›„=8цюЪ… ed‹tцƒmKаq•-gђ‘o>Ђї?:%ХМ­AИК&У—!JvеЩ”Еg–жфПГT  №ђКe‹ ЫFB§рm6ї?8­HRД‚XЪ№ў№oR‚р‰щPВaя1хУ7yJ{ KЛє•ѕИГGДdС(Л7Ј’–ъЋр„MЬШfZsSt+BFзsы#!ћђ”*Х8A!?М1ЪЦS6žЄ6@”PљŸпF™žаЪž\‰Ж:иу˜ђHЪ7—JЄUїkЗбl4пKч лќVўфЎSŒI$lWєУd4‘$Ѓœ-М‰‹ЭпяХI$щJ{ЋJ Фуh%я(їЮиђсл s’eЫеРFзsч!_ўў›{ЅT)ЊŠъУё%vШ;orhŸЌ єLœ<)‰дŽт”‡3хЄTщю<Х‘ЕД=…Hк5sB aЭNоЭ^ŽNж(SёЎژ?eу)OBЊP.Љ|ъ>‹я=XД!1т*ЕQ\CgцPФralК7I‰ЁзІЗёsCјЕY‰Љ‡ЊXЛ‘oмз”/^?Бј˜d}[ь‡щuє]!cНŸ^tr–[%›пяW…ђИйєКюuпQ:SSБF‚щсHcЛИтѓwqlЊFЙ`“*Ÿ>О~ˆљœђяXќыm  ѕETDйDcњ(Eг‰(Bz>7$oЊpч&2ѕЊЄ,ЅcЃ˜)ёЁ/п –•Hџ№Хќ)OйxВйшBжсШ,МѓК&#jŒ,5  PХ œж$VОШЂrD$hQлбо„~:$Ћ–cСаvо№ow2;з ГКЌТ§ќ0й#RЏZžd`DГХ™™мBJ3#}ЋЁиыcnCU UЕ\@Пђ№ѕћšр/ЄеЦВHяad„CГ%>ќхллY4>~~фŒњFп§}Uj3SZ4ža< Ћ›ёЇіSЈCрk,вэy k6SЏз!єЂ|H&{г•+[wщОњ0ї=pЃСŒXЦрt%Šь„V7‰lЏф‡зH$>f‰эЮŠЕv—Дf –%‹опBЯ…ЭgЩ_М‡‰Љ:Ч’0VЦCЖPE2*‰ ]ВŒIB…фѕ OЅ,A:k T{Œm$ХQЖђёЛmnМwZ‘юН†nRSтЩ0@ryp6№‡џxЙŸ>Ž~D"=Чцаw?€цdTќ“нюd§ˆЖJyоєAЪ…ˆƒеYРнЊ­љщDzŒдїЬsˆ‘ЧЗшo›чГпЛŸJЕ%дEžј1ЪЦS6ž„6ЂЄ(FyэwD[Ч'"їЌFIЪxБP) бjЛф:ѓJišГ”жlвІыХ9PДїШ”rGRфEmњуМўSпЧфьmLvяъгƒR†ЅU›)ДЉЛѕ‚тc]„WЮj w?ЙGЇ! —ЪТКŒ”KПCэ­^JЮ™jXМѓz‚СŠй|•ояŸНЫ XПƒkюО•ЏмИŸ—?{Ї4шјЈ‹ОРЉhЋХЮЇѓЙ'фЪkѓrјаБHс-a]j3§З0DЛ9CЕ5Бђ‡FqMAдLиdћ}пєЙm Ѓ;Йђ‹wђSЯмJЕ`IЋdѕіуmЋшшоЋkА|Ьћї+эЖH/~ЈblТцz*y‹Ž]bh|bйЧРЃѓеM|у`ƒ?iЙ#с‰ћq2m•lЕKyблЛќСЗ ­ŽшJƒeЩsCобbe„љРŽЫdљ˜/њ ХТЏЯщпМДЬЅЛ‡hza†ЗьдЧ|q[‰ ŽWнБРeЛЋZЭE;йSic5?ŠЖ№ш<њ‡пєyxІЃŽp ЦЯ,Œ;Їяљ‰2?ѓŒM?Њ/x.Ђ-Міл>_yАMйюбЙ]ЕЋu)лАUC՘/y?ЂЖzTs! aIƒРЯ’й­>—ˆ0пь№ъЇЙ\ёВMДZA 0 д;ЯЏтВи cЋін †еЁPВеф|Г-ŒзЅRЗ+ј!OУ  ођ-;=KjЧBAёmJ<и%ѕЪƒIzSБeHПКы–ь†УЪ7Ў;*8^rПњ DlЅ5E~§VД~ЏtrуJ}V"ж[–s<3v,Ё5Kn|ƒцќ)i;ЃјѓГŠБe™†[ЄUaС|WžеаKwŸCЧ SМv†шьTД•,КPKlЈЂ"Џ8гцCыђzрбY‰ЯŠЋ“ХIDƘзфЫјѕ mz^Ќ—R&,{СВ…Ѓћљ§—ь"WЪkЋб9)?NІ­DDьјжрђяњмxяtД/k7cц]§"м†кE[LIшј šbЄЬЗЇД<ЖЫEл.У IJŒO[Ь{єTЕTDn=–у—>=ЉяКјАМхЯЃ^ы`ЬncаxИ>lAЖ§ъЕ‚ieйГW&l464І”ђИ\ўЙGљёmшhЕ MзOхˆ№C,xўv‹џч4tf$ЖПњ\"к5ЕЊULи&Јl’pzBБЌХ€дeќХя9Ш;ŽњЙ! j5њаф-?—ђќŸxКЄьХ'–ЇlЊZњ‘_=+{ƒА"iъ?4ŒQцŒ Эм„ЊвnzФКA=§Ъ^m+‹Idхs“€@Е Му’"пйПwf2ТЏЖd Z ЏХuЪhэ^†Жm`!,‚ЫsыgqežKЙZ€сѕ4юлЫаЖaцs%мшš(к3МїUbŒр{СŠtы?H[-я†аьРkB^ѓМ*Џ9Zƒ ЮЂEt™7Є=Mqѓnцчf)дQкpЭЙN$ЂБjZ07ЯЋ.(ёѓЯй…зђi юЧЩДU JБ |ѕAјк#9Ьф] qsЂHшвxhЈ%Фйv w§#ЗГ04Dиiа‡@,‡pц0ћ­ЇSЌ”h5;]Ъъгѓь_;–@ЈМы†ТўЪ ќЮѓwВaЄ+і§ 6‹G ŠчУЛ/БјзГџСCHоЌnL ъЛTЦ†`|'wнА—O|ы^ўьUЯAКŠƒКj[Љ*ž'Мr—№ъKз№ЩoЗСю аЎ+CyнVfоGua?юкнtц'ВЇёхп/KkŒl|гЧQš}ˆpУЙДчŽGяз冑M89Хя<{”Ы.иAНжТZEkЄ_Kф9чŒGЛ™~L9ы‡чR*W№Ќ"ЊBЋхbЗЇE ЅО˘dMГЯМPJжo}Хй рajxr~œX[Y"4š*ЏџЎТьТс­ДнKih ѕ§о˜геЇItЩE1H{–мј6iЬLТа&ЧЂT* at[ƒя$ЋјЌ]м‹‘pzF~с™yХmЧsНn=§1‰lф+Оx{r™і6№ІЯм€8–‘!zњуˆРXEх-—Ё0„йёЖd ‚hbyuЌъiЬOУІМёГћфР‘) ŽеПЋЕ•ˆр…QUїыŸэШјЦ№т8Ч Т%sI‚…rЋЃRw}0yi7jфi Nе0•‹_<—hМАЙ )•+4ќHсАэљф;Гр”Dƒ0ЃЎ’іНЄ? aЧcД0Ч~щщрљ‘“ŠGЫ€eDlKА-лЖ‰hš,KА-ПnФЖŒиЦ?Ф2ЋGЦяГ$ў,“МЫˆXF–Е‘Й§эёУ$GжюЩ*Щ‚Їм.к= ~–ј!ёƒЌЈЎЈ-Њ~ }УE†бcбж’ЎiЭШjbт5Е4МVk-мš2ВY sZ2žbr1яKїxžЄs†VFFЕс­Ѕ“ КžЎ/7xУЫЮRэјX&C–9оŸЪЖъ!'ыБб-Tвw_šƒвІE„єњЕЁПЎЮш&mЬNAаQFЗуN<ЌхB!У‘•ё —ŽeУм”ўёѓЦѕЛзSkv2ˆФ“ѕc№ЖBD‹ХP?v—­ї>2ЋhKЩWшˆЃvэ І2Ђ„I(‰gЄ­И ŠХ"О]б 1Ћ8y§ЩІэњЩяOђ§ЛjО”зє*эєЦCDд2Ђž‹ўжyFŸ{іˆи‘(ДHяЬ^ћЎ–ЊCtЌ’j}JUBkОэЊ[Tr9‚(;`[Ё .њє ЊxбH1IїЦ-3‰ЏЦŠV саfѕцŽAОЄžUбpццJЭ~zч’n>BphЋнЎэљ 0–†…5Иг(U*ЯY)l6;žDБ”ЉуzљOmftlHлŠёqœKNŸюy4ГыX’зю[_Œ$ˆy_ш-‰ŒvАЖЊМч'Ъ`U1,НЩТ­@‚Ъz‚љ‰jњЂеMИGю“таˆФф`‹ЉŽу2ўо,љЩhAQЊ›hпOБЯРьњР№дsЧхН?ГъpYк^ШвФ§щiЋхlˆ^ИCјХ †СЯCШ8ЁjЄ/кiRY#­Рмz„уБ\S„…CX•QК,==юW;>kЫ5о№вГХowА“эюшЧ mUЪ)NљаžИГбжG-‹;?IбБAьЬж1уG++l“пJc~&о-…P—VmŽ’‚ ГEщžТŒ 3ќю№Ь3зS‹ЏЎтbџгѓИЎU хУЗYмё№рFз–-”7ё†ЙE‚ьUШiGrђŒжаPоvIAЈ Jzš_СРіы"ЃлhЭ6%~жэрS7—ямё(еjAД/ŒvЉ"‚cл‚?ЛШАu{ jщ…яІі#4ЇЩ­й*…™ш;†> o”FmŽbиь|\€лk 5GaxЭЖ+јnєсХ!кžbзcŠхД€w‰šРBC~tЋ/Пйг]_BеЧ}.9]6LЖ {Ј;V‚‰/sЭЖфыЋЂќЦЯ;w”аЗS Юžoa#yђыЮ Б0“…ЏаЦСiХ”†R†ЩЬИЗFql­V;*чœЛŠЕp;ћўХў-4Иє х7ŸП› э/Йї—оz„SоV§l@ЄYŒ(oЛ$ЧиІ‘HјmёрБ‘y\ТъмЙубšЄU7аš=NбxЩкЛјF‡Q˜9Т_МrkЦ‡№=e~ вV&>ПяfУБCГэФœ_Qp}ƒY8ŒU,wбЪН›&хj•f`Ck!оD(XО3D0ЕŸ\yˆžыЬ„#H:ыЫ МяЯ#p§шшЎœў˜ЧЉуR^й?-|pЭщЈQT §ЌнТЕ{|ц;ї“/9KєЫOu<ш…втvрХЛ”пМh ‚|!бhгšЅВv ­f<7§ЧF [yлПэСmћ‹дžщWKQ-)я{arCˆ}ђХмхс!|ЇBи˜OЉ~†Зв:z?хсбЬ<АЈЁ||Ю еѕx SщћУ†Жаœ:щ.jЗn2рNђЎWьЂR-аŠsЈї\rКlЭ чтURВu–JЯџ„Ьыf?{^ъ1)""M*хЭунЫbдЏк5JЅ Щ6$& ŒЗ~ЁPЇ=s„b>%yЛ4$бY ПCСАИFМљ)К •"QЧЉЌ“ЦЬqJ9‰*Cщf U!С›ф]?ї4ŒmсњнФyВ,KІXѓДДеr6DDš.œЛNљнg є оxуЩдзl’vЫ…аЫЈ K‚мЈшЬЩWFх=то5_чЧЯ~хygHнћŸR?VjЋPU 9ИёАШ'nmC0+a‚я‰6c[iЬ•Jо’ˆENS*‡xpфД-ŒэˆаnнOВˆЎ“fЃэ@|к?т‡Б˜>Ц;_Ж]ŠC%щјaT<јФЌ{oЛњVЉз]rvїŽѓ”ЧcБ‘„Гzpљs еcжFI7ОG1o‹_Х‹И–вœ[ш‹Е~#пНЏУUзюУЪч“šДкJU%шР/œ /~цЈ„^w3к#˜dЉ‹3Д‘Цќ,]Ъ‘ш$ъЄ#yЬЬУX•1‚їœЎyuJcЉзj‘ š™Жя ‹NяЧ.wOѓщ.оВ`a^~њ,›—?g'†›еyмч’гaУєб‚Ю’ЃФчМю+šХmgбЛ‹‹’‡ д[ТЫЮT§Еg=wёЊjЋ‡3Ж•цмtry“сА•|™Žц0ѓ‡тhцо_ŒЏAqнъ ГŠЉжEїXkiP'˜x€\y8nСxЕ,ЈЭщЏž_цвglЁбђВ~к“k8ЭmЕœ TщИpХХFwю‡ ыяi1Ÿ#(ŒiwздГ3 ”Ёuк\˜гМЗNЁз|(Š7Щ;іL хМЖ{gЇЮ~mщlŠj МёZQІ'вуeЖѓЊ–зkчШНZYЃј~Ўo =Gal“6 "юŸLсHмZнLыш§Z*UwЅ[Љ`ОЦ Ю~їВ35t}~›˜Ћ*y нsФтoonA0Ћ]ЎЃ.3RУ#ьŸ.ы•ŸН…\БІвxЌфGЫ‡нkсѕWРЎ"dr ^ќШЦ(A6Qж? 6ьтOџхNццjjYR;Бœ"Ђm?’fxѓХy-Œ$›б8.рЗЕTЎаж<ДцHICу6TUЪkqkSmє/ЂдaфƒчjЅУu† ыгJ‚жŠЫ |ЈЌеV}’qСЮgђAЂBŽ)§Ш/Ÿ &U OЈЙфTлшХфѕ­’I›@шlж!”Ѕч# Мѓbgl ‚0=rz-Jе*M_С­гeЫЭNЁ#›iLЅ”ГR8Ў1аiQ)i‘G›stƒогlTЦi4›ТфŠ)фгWŠ2Ы{~ўœnа—9EsњлЊПф*ЋWоvIœ*щ ЃS#Пf3ѕк<dрЪЖоаG‡Зв™кOЉ\‰ЎїDcСќПђЃe~тќm4jnЗуŸj?–ДU|7\)*џї>›яь[šКzM}( уњ!Йж$RЌv}Рs)цlДДП6Sж,к:…!фJt$‡]?œ^‡jн6С4o}љnŒcг]@‹˜']Qоx­Рмdяе\OО0€Mg№ўo<ЬОGŽS,:imСЉˆЧ*~ŠК№ЇЯВ8gзъ[щuhрS*№ђУ„ѕщtї0НzPeІVф/Оx;Йb/{№У+љ!@­-\r†ђъMЎcФ [ШшvкsGcIIѓeЩ•xЎHGѓ0w;FwЦиmь 5ОƒцќTZя’}yЁэУk”7]ZJXD/OFЗг™›HwKюїcRБЪz‚ућШWЧЂн€ vиBЦЖгNЏЎ–тЃ(T7в™xbЉн†щIоєЂѕьи4JЛэЇ:M}ѓQЇП­–Г!I˜џъй†œ;‚њvє ПCЕZСЕЪhc6+"““GhОLлЋ~ S†0@CА˜Ž`Л^€1ЇдN‹OТ%&р§7а˜‰иЇЛ СEЉ@˜Ѓ3s„BЁ˜"§…БЭдk Бо‹,НŸOдЫыhЯЅTpЂšc`a_<'ЯelЅоpггзiŽyЬиBЙЈ|zЏХЗX€А™цnz„ІPТ—5МѕЊ[Ѓ fRЙ~ тБšэЊЙ7_\†|%КЪƒИ иkЖб˜ +иЖx‹ vёбџкЯ=ћ'*хяW–(A.ŽMy§šшb,hLSXГ™VГ{њa‰цFrЅ=К…цмdЊ0vєz{žbuзh7Шћ-эC•qšЎKС›M7ЃЭ;з4yэ‹ЯЦsНžеm.9•6L†яPГ“ЇtEэmЏЄaфbQ‹џfi›‘‰A њЧч[œГ{Œ p 5Ka|њМx,Upъ2ЦF№ЭТ-ЗЃo е(шCkЂт*З™•,†щJОB;А4зœР+Po№є m}нKЮІуњj‚яяЧcбVЫй@ЃЭœъл/-‘V|Сё‘-Дf'brхž­Зv™:Ѓ{hЈnЦ=хƒЌМ2}ŒwПxчьЇyN?Вm%Ђ`‹ўял-ю{pЃЭŠлU\^D PЅЙHЯ IDATнёqšЧ1љx.хbNлV­OG'афЏнжK^s tЄЄ2§vyX5@Ыі4љеѓ/ЬоYŸж˜'‹GВ€ОѓћЬM*fёЩC3WЎhшЕaу\}§qОuы-l‚POIuˆbЙŠёыXkЯжќTzm!&yШЂЃEhdГдf(и"ЬLЪ/лШњѕ#уѕOШ sШЊ1Яі›ŒЅќѕ<<18†fёЃз#0Ж›7~цF ќМ…иqŒ`Ф6АфauŸwŸГЫЄпw О›РBCИќyŒ c№+ыqkГ`œj–їC‘Э;ИњЦYО~ЫC8EЇ›P_u.Iі”феO.aI[Бфѕnй…(…МбŽ‡xAЗa*Зo<аH'фp]9џog˜oТФУёБн,—оЛPљ#Аѕ<ўщGљѕчŸСТBЧ2}r–њ‘ KŽ Z^ YšчAќBЅZ~юs>_ќкр%Ї‡e#— „Ёц,Š”~фRtсэйcQ>gcЁЭ9”Я8{пўŒ WhК~ŒI“е§ в'ЫйBлз('Ÿс”•>іlПRUђЖАоpс'цЉИ;CеЕ:Ў2Tрј~ўіїŸЭџщ‰РКLЬ—§!СЈзCГБrпе.KnЉdЩkП2ЧGїtАН9м™Ѓб„@DѕЬNpоУMяz)‚„šRЦЌ2—„Њ” №ЏїПў/|GюI7јŽл[5Зѕ\щHо_†аПыwёЭп\Я ŸЙ™VІ`?ŸwћЧ#оŒс‡ЂŽmф„йJD (х,šЎп—Ўd%?ЄY[HЋ2Ю~MSі™7u)†E„О}^/оbЩюѕUm{AМЉ’ј`ЎВ$kг­KR)9†ћg|>uэД=ЄєФ+Q waщaЈЫeС*аšŸФXКњрIЈ?U@<,ў№…gВm4ЏЭN ’ђа/ыGВ7(хDя› фжCM^ѕє2*†P{ПЏfXЕЃ6LйšƒЬДB§ђО&ПrnI,ЫФ  žlеВёPEJyбЏю]ям}˜œК(fI[-i§. Хрz>;6­б?МlЇ4[^OЮЦ|ЉH$lјм]M9g­­gЎq№У>њ+јa@›‘јоA=61)Хœ.ѓЅ~D]6ŸsјFm'7{wC_}СДМP-“tœЅ1_мЏ‚*сЗzќозъTЅƒ1fхОЛˆ,аѓ}]c2М~ЋКN—ёh€—$oІщѓ’MuўўU›Pа \НяFКЂ‚RЬЙсб6/њчŽб4џДкŒлв79:sЧићšКmMQВО•Ц`\ЈŠJЙ`ё7ЮАїСGШл&Щ 8—ˆ†ЁR]#­кЁПdЖъ\тz*чяч—žГп4u Йdq>Ш\RА‘ƒ ЊЏќМGЭѕФVпDf^їC•<§ЫKyё36PoХ +єнЌвЈЭwoФ^їфэЉrЊЦЛЅœюЫѓєwоЫŸŸ?С•ПѓBк&aВъ&dв ŸЄfkNЅ{ё–ГЛ`@pИм0щR˜œмрЕ|:ёЈ‘}њў~ФЪ| №†яфјР?|“оѕtvoл@Гещ„AJМЏм]qPЁ\ўњ6‡?њиќзŒrйEчаЌЗКЛ–ет‘|v)g gЖуъїДZ~ЗЄ{ТЬФ|‰Њ”rТсКpопЮq‘оЭзпќдkэ(а'ц§ќHиХ‚=Iф21Мъ.Ÿ_њєQh/DчђAOЁЕ&Ял8Ыuo}>ЭVиEDЏдwгыЁ\R>v‹ХтО№CМђтЇбЈЗ3\iЬћѕ+ешыЊЮ§x<ЙAу%дщ`лyДPЅ]o-ІЌ_БнР‚ЉC|ќз7ђ{/9—fнЭЈ$їяЛЉб/‹‹чќн,{<†UСЋЯeйzW=шд џэх“џуЧhЛazyГТь‰G<Н– и6'§ЦШE9ЩqфДм оЉЫВ1_Б_­0яЎKлРџњ–ЯџЙц˜ірsЂБ =C[8ЯП[о§R‚0Œ6Уњag{ЛGЇd"щс…V%TKyыu!8ќЏќц‹Žsж–1š­YКыфИЅ™]Еjz\sЅUыpв3G—ЪУ;щАt“ІНЙˆў~Ј*Хœ№аŒсЃЗt@”зќѓэ|љђ—fHžeб^GКƒNђ–0W‡їэ№ѕѕWн#ЗœП Ч2QељёHŽ™u7$lЛЋM}wЩчи&!аS–p/ђˆкЫVўп;-fgCўу‘C|эІGє%эFнrР‹cоЧ‰7^ЕІЊ7ШДз{|ŽЏCџŸГ Ÿ|цОz}ll :“ќе/>д &Ь+їнјеr&цсЃЗp=оћ…лyЩ;)чlšIЬћі+”@…b.ф/ВЬЯF‚ЙˆП|fZTжn`СГБоHuїХдІGЈВUЎАФa­Эsw Пї“чаЈGoКdŸоп/†*ЊŸИCхЦЃ9˜|ˆЪЖsёr…ˆЖD`ЭіCЪЙоєђ "і^‚хлj•ЙЄо е?йm”f.“Nj1"˜ "ю„§Xqо]9О*Ж?ŽЃ_Кo\ь?Š8ƒАn кiS&хЎл>ње;xэ/<›VНй‹AXС“-Йы/F+b,TKЊŸЛЯ№љ;ц+Ф7ыѕѕŸй–єLl€qњўЎЎ.gФiŒк–СJ˜!QЫЖНn[FЛЌ“=Ь“Fm#§лežT+ynu?ЇЏ лHFД•%ъE~б*ЊЏљџ }єŒoбЏм6У57юЇ\IюКm—’ЅtЃ‚э„Мѕ‹ƒ‚ёuмКПЃџЯНъ ТЎ ТŠёHОД%ЂіРmezкЪ1F­tм,БA?T•‚­zЯ1У{ЎkЮCe'oЛњ O‹ŽЅ‰tпj§*YЌl#бw9A?c"кsаПxAЦЦ"Є]6I“у%йWc)S“ќбХ#њьs6рv‚Œ\ь*}WˆŽїFљаmћюŸPkэ({„ПљњнГЛњён˜/гЏЂЋHUпƒŸй?сЈЊч$uJџџO‰”mдЭ4цдЭСььЪј‹1CšaБVB,hбќЪгјžьџВ}7уFЩЃГ№ў›T™?ЎT7Љ?ѓ(ХR9*ЌЫЦ@3hцŽfnRпіSы8{Ч:ђŒСeц#рќ@s‰,ƒЮ%Ž1jЄ—ќёФ§| і‹GнƒЭ#Ъх—V”BY5{cЎ‹Ÿ+†j…mь‘Дj30О]пёяїѓШсi-sKQЛЫјa4C“ЅНйўюu“jдiц№7)дІ!taнvО|ыџБчQЪх\ХГИpeФEe*‹~Я ђ/‘$•’ђХ,ОКЏщsи†v№іЋoЅнђ)хЌnqœЪRdRБ м|ФтяhCX‹ŽЮыЯтŸЛ“щйЧєLfOЄЖJ$Šqр­7˜ ƒНn7яW>ѕ­{0љ|ЗяБˆЙСѕрмѕ№šч AXŒъ‹Вї‘йbDhzlj№њ—<а ТpIЩРВmЅJоVžЕјЫ;Ю„!l=“З}сjѓHz5CžЗ’ $UDyЫХ9*ы"КђояОигž%Зю кѓгбkХ1кsЧЩ‹vaiPвЇЩ‚ЩI~џycќиюutтФщ ё‘шЪФљыЛюhЄЅš­Ž;е†$Й] ЇВхqЮx‹?xсYДэ4щќ8ѓVЖ@ЛП{žpоЎ1№MЗxО'Щs1рЙT†Giњ@kЋ:B­3Ф{Оp;Xf зВcА}(НwsšР ,хїZьЙo‹ft ` TЯр­џЖ‡v; žЅ­ИВ К_VВмжKСC}‹eNЏЄЁфР\ЎИA f&РŠѓ0уыЙeШп}у.$—[R„–|%л№ЖяCуј˜ њЃj•ЃaоuеMи…|OТЅ­Ÿ*хЊ{ _ИЋ^DVlмХыЏО‡‰ЩЙфT№˜Ф<.-‚PyЭГlЖm%Ш’Mv’’O7Іyн жБ}ѓѕЖз_gЇO[Ѕ'Pјгoƒ75•iЅ юЏ§д˜Мƒ."т\бЁцТљ›•пПhL!њЬОз@:uЪ#kh†АU‹^vђxіfсNБШВЩьvЖеa]ЅЦŸПьi€рсРё€hНуИХ•зЕ ˜юбC†7гž|”J1ŸюКdёћУЈxЗ~”ї§мnЊУEМ0|BŒѓf‘Љb,хЃ?U€ъHš#^\о%^mmІ3{ŒMрЙАљLўўлВgяŠ9‹0дўн0ѓ‘ІWРЅ—о>ЙwŒptAИтZ:StyzТЦзВчaјћџМЋPX[LxмЯ†єќ.K”)˜ШђэCмЖя…rЎKіDi+еВ;п€ПКU`~*Z@ }(бш sХgoХ.фёƒE2ѕЇеІ [з„Мс’!pJ)ХIfЅp;œ5отu/;Зщb[2˜№‘J Оќс+ї7СŸM”МоАƒO~џ7э=ЪPУ.хл*6,‰єožХКMcMZ)”žžТ+t1еѕДfв !єah#Е™у”ь0‚Дk*пžb дІy§OЌgЧ–qц›t^Y%\TРоrНЬLGr ‰ЬАS C™?„]Ѕ_!ЁБlЈ-№тЩёŠ‹Жс6;иЦј3’н‰јPЎRї†хŠЯнŽфКTл‰jšX"„Ъ;nЈgЈ"ˆ˜\^Ш­—З^u[4атcШЁ­$њGхŸіймxпœXІ•ЂчzŠѕ;хяЏ=({і‘j9зГI:~H„ЦЄй~ї\•ѓwЏ‰(яЛpУxYŽ0{”ї§м™‚эfiЋВ#4šЪ•7‰„31„*Ѓ№˜Яс9›фWп *’‹aљ!ЉBоё‚2˜ŠtщЪЕЛ%…РЅ2:.Mш4$fІŽЅ;0ВЮс{) ХтуWrŠt=й>врO~њlкЖ81jm x ” №х}ТWюk‚ПЦN Э$жм 5w\ŠЦKzјФCшLёж—юc':OˆqўCoC/PTўќ99к0]‡іФР^‹b!‡чTХЏЯЅaAп п}ах_ПsŸTЊЅь[_?L7Љ–ЛЋtтЊCyеяь7ќЭž†тЮiLT—ЊКОJeD'k.џз[Tr6a,Ÿ$ŸWЖ%ЅG%+љ{щ2qvЉ–*ђVОŠ[ѕѕп5Ьž‚АЋ f./6ьв}ћнГїеR.љLDD 9еїнlщmїЯ(кŽ)G2ožЋЌпЁзм:Э—ОПŸB”PBД•ЊЊcршœб7ЏЃИSЈIл IŽђкБ7ыWпЌA9лD8ЋЧ цQ_VђyєЏ^T€ЪhТЖ7Ѓ:3ЋЏК Ш+/кЎ~'bлШ†FDБTџaЏХїя™UKкi4Qs 5Зш5З.№йыP;яєјВ’ TUU}єЗЯ5\rо˜vЕsRеL кжАВ!RйГэnIfъ\‰VhЋе8†)j *а6Ымa§иЏœ­љr0;ЌтdЩFk­Pп{“РдDDЙвeтMUA§меЙƒщЎt™jЃtыьДОцЧ‡yоy[ДньhЗАр 0ЮŸ 6DD:шЙUџєЙУ`gдуДК кжlЁY›SB_3xсxЗk”БњњЋюдЉщК`Ш2~˜˜ ^Ђ’њЎ>pRЋ!Ў‡|рСŸ™ЌPRА8нK:ѕ;ТЦ]|ќЛфі&ЈьD'ЙЛкіЕ‘BЊF+nTP#1Csц3"ъŒЄЄ>Г ž>ёюhЈ rнA#џrOМљDу@КlѕЩ}oЮ–Рй"яИjŠHЮ6С~Užљш-ОtUіz€Щ „5ЛyгПмLЋх‹mвЬуйVОŠих]7ЉŸдыНœMv8/fУfЙцЖš|ё†А ЙЄЄћ1‰ЙСs‘lƒ_ОpќœQэ„RЖчфЭ/;щјaВ:ЎnC•œЅ2г0ђіkНјjRХMњ@(ЊА~Џћч[%ьxbu§_нiњ*љœrљХБ†2к9–#Дg)Ў?Cšѕyщъ`h‡@хЕв™9BСщžŒM8;ЧЯ<Ѓ ?§Ь-тЗНьЎvХxˆ FDФQљП{Йaя,X­TГ'хlNЂеqiЬЯI>h€SˆПЋB'uХ9љГ—ƒпі$ŒћЭу>ЮŸd6lAќ6ђЦ‹„M[Ц шъ&п‘rЉH“кœLOТRт+i1#kфаЄ#џч?юЩйНšя‹ќˆЯйiš>a†АsЊ_yФвkюЊaЈGZПi-UV“\Щйtr›ѕ]WнŽ­ŽeztћлˆЫIE’еЗЫ/Ќв}=^ЅSосЬ•\QЬЉДЗDНђfЃЭ‰IА4ГыЪдЇGїгjmмЌ_НЋЮWn|PэB^Fє§З=vhF-ёвн’е*P4TFЧИw"Чп|эv-”K=;“ЧЃ­@tЈ zн#†КГ­ИГQІЎ‡шPbђК0šжяд7ўлЈл‰+Z›˜Ћ*^ Уз=;Oa|Xё4:э-ЬъЋ/ЈшљgmdЁй9! &‡ОуFЃГЧч’h SЭhskр)Cc˜ЩыћПt‡цJyѕЬP#ЂѕќєЊ?{о„NdУmjЙTІcŠдчSвЬХc0b\VWьцЄк…JєКbщ —ПьLЕ[;~Hу•уЁЊšЗTgFпѓ}Oёg#ВФо$vЏ3У@ЕКYНЩ‡)U†Ђяd˜9І—џєVнД~˜ЖhЗZћqчO6"Ђ^ˆ–ђЪ•—•СЉЊб@ƒу-ЈYГ7JœЇ'“lFUCПЃlyš^љ•Нzшшœ–rјq&#­f%‚Hж›ТkПэ ѕIBD%Хв< ы6ЩчoЉѓйяюУ)цz*Г—иˆџS$­N?‰а)y[?ЉŽ>rФЇТ†Ф{gЫVЎzРpЭэ Еєо1Б˜*hнЋэф?}›Д- yУžУ№77З† nэž@tb_€u;Йќ №аСI)цэ МёБmЋЈшщt”+їѕc“`eДФЛmцдї`hœ‡ŽYМчѓЗ‘+цё‚p‘ЎњщѕЃйVžЕ%фЯ.Ћ$t|жUцхПє зУ6'bCЉр†Т'nk ­щ^ЊѕeЧф$Рк3xџ5ћфСƒ3T‹Ж$њіЋљЄBја ѓзEЊy&hŠ3Ж‘Fm.КЇNћ]я№]ad ѕуPЪMоѕYўчs‡yіlaЁс.цЙ_1"’.ПNфјЁi!pГ9а>ЬС!*віЋ~,RўЋ7јбэ!єТнтЗ§H}БЫђјѓ'ЅrяУ/Ÿ /|ЦaZs”ЦЗаjЛрЕ{чБ””1}=ŸЇУyэЇО/8fXЁЩСr”/<ьpЭ5Ф4"цJп_L„кА›Пњx№Р4C%›œбbоѓК&sЭ ~$]Мг‚з^(lоБŽт/нNiЈLМѓЈ­КпSоЛЧа<>СжYэЁP)3ыЎс}ŸПБ-l+†Зр‡ФЕ-лF^wёxЅR™fXˆuоW‰QR4кŠќ~Є4LY'xЫЯž‹пvБ36ЎаЏT•j^Иў€с“їаY+GЖыЬC‰єЧgE$ѕ ЎxљXХ"?XК[.q šc gѕЮЯ%vŸ~eI4/‰оŸyєГсœLпMlX 0ЋіЊU 0_u+ЩP 8Ю[О’Г•ЗўxЋRˆіЅЕtЦфUcЈА~;Wп6Я7nоOЁ\ш[ЂбЅ2Щ~ƒR^Их ђБыgaњ>,;wŽ 'Ч§‡šќхзїёк—?ƒšяŸЫhВЇ5XЁOЅ`бp§ЩВИ~+њт–ЬвЄX‡XlW‘cD?B)oљnШУ>Šдbl{`Жƒ Zхгпй‡Ќ;‹ыіЮA§a,Ы0(OQ0\тПїПyй—>m 7ˆѓю'цЧIЗ•*–ТРpХwъ0ЛДч-3†йКЭ‡ПzљэKh…в[d:P+D4 Eр'я_Œьу‡HЄо8Zоx|ЦпЪЋŸЗаѕ{8…Vm+ь<\ѓ€сЫ{‘o?ŒЏ2X E0У9>qэAўрХ\xц:ќfg xЈjЄXtОЭеЗиWХ>|;–† Ъ{І(A{Ќ|ф7.`lЄ@ЋІEƒŒœ%ДНw|ЏCѓі[!˜5[С8јѓGЁiѓKЯпТ+.мŠлtSЮЕИ—ыЛImCйІ/x!HјG27}œзUђў0­':aЊ„фНљ*zVЬGcБхљШ}7‘‰ЊЕсљлB~ѕќўщ{ ћя…љD­qкPы№ЇŸК‰›ЮйLбБшјaOД3Д9щ]“-ь›і96лdhу6cЄOфНЃCИˆрnрCїўmВAЃйŠTювП]™ЅS„NЧcиrљ›—”єТ#4м@‘dлЂ§?dЉšIQаЅMг8MщДшЁK&ВчP,›сMЛ2ЇХеќPAŒnmфƒззЩ9†ТЦOО§f—}БX(­гя>T“KŸО–А`Є'гV^ˆ–Ъ"W~kAїM†TЧжFўО1_*c&bЦ6sе:з}В­^Л%ЫhІ-гЏ? •і,~љyёYEmЙaц4К‚qЋИЎђ[чZњЪ3wJБшh+Ы’AкJБаn…\ў…ЂCЁИ]Е?зi?Б]ЈnфЯП>ЉџЙ{œnntРx4:Ъ†с+^4ЦћїК:kЖˆmYŒСTmFPЊЭ ^pікЭЋzbуC г-бK+‡ИрEŽкіtiн~џе •@GБŒ№л/мADсjЬЄДjпUUЭYТ‘ZШЏБСЁ–MN=Э +ЦC18кІd ”T/[ОЗЊŠЈЅž 9! ” |/[„Нњœ(Fцъm§эГ]ољГ[i5ƒšЏ‘œmє3ЗжиUѕyЮ™c4л~/Кk™qЎJЄош zQAwŠ‘ЮB€eoZЂгизЈC‘5tBЄнёЕ`/]x:їьидœAš>Мј*WЏПх P`1Mѓ2”Щ XъxѓЦ6аžŸТЫ ЭЙˆƒ(§лхžG :wV(oаŸЌю•oМ§eкЈЕЃ;ф4ЇЂK.rSтЪT|+BЇd(Ъ2­uхLБ@Э0T*yбЏ=,Мє““а˜л^эЛk—ЈмoS.:*–#žZъКžDдKЖ0‹'пшыxЊЃ§ђр{žOЅTP?Ю#œЈ'еVŠ–•}Г†|r†c“3Šx)nžIDATSкuQ§юbЉeБќХсЕкё}щјИu%€W кŽ`:uahŒ38Ъ=oўQr…ЂК^x~ЈБ x&фUл РOCИђ{*oўвA хМЖkѕЄ$}љОлНиХmIЎ`kчџoяLƒ-9Ыћў{КћtŸюГмsЗYяЬh4Z` b‹CpЧ!Буr9Љ")Їьr…ХQ"@ CЪиЌU˜„Šmaт %"f‹e €‚vДЮ f4š}ю~і^Ÿ|шюsњм{ч.Ѓ} цVšsчžгo?Ыћіћ>ЫџпфŽфё/ўю SД^йЄ=ВНЈ[2ѕхwєфЁЧN€38"Ќ3‡ЎQŠл„ЅП~е’|щЗ_AПQП6ž І!тИVбv№н Я‰ЈФ:ЪГžяЊi иј;пўјЋ`ьиKВp^Rфfй`-ЩцњM-еkBиUъЛ%\˜KжК 9% ФАS,-Ъu Z]ж˜ЧА‡Bl нgИї=?ЏЏМnН &46XЏTU\K˜mЖ}єИўгёCђ•[оЊa?0УŒпpžgе%І8^‰Д h`ъѕрюWј•вяiщїш&ЦKіО+5>ќњ24YgяH №к/г‚ўоєnк~‚tfЉx8v?ошU2!nS›˜ФЈ7јлЧšмёЗORЉ–Y/xPlЩ`їEF?eиZЙ‚lp%г€noЛоёв1';ЦъЦ/;naN }ўхhГь жћ>˜% OђЙ_ЛŽ‰ЩQœ ”З&ЧVu•юМг ќё‡ Юžэa/ЁъкУŽу _@дУѓ\BwŠ№дудЋ”J›єˆЈVJTЇіrєаi>іЕƒУЭЭй\„MŽ aѕt•ЯуК OŸƒO=jСТIJЭX•њ&хWH,МЉH~џЮ‡™[шPqЌОб†rфЬ{F’ТS4Ц”ФnшƒA‡ЪиF};_Оч п}ь4х”ўvеaс‚~%BЌJЏбэєщuњt/Ÿ^сеэјtл§є}лЯ>ягkћЩъa#пЭ›CъŽђнЃТ ъPщœТŸL6є# Хv„ЪєТ0ІДp˜вд6 оœZ&ІєлО 11чžІ<1•>Л6ё}qlш.№žзMђЊЯЄЁјТю`Ѓѕ*VС0•[ПŸ@lqчНчИчСc”œвŠ0ф:ѓ<{:Х‰аэ„{фЖЕijЗсџџю“Аzџ›aŒЬЌAoSЗЇМљфзnЕ‘ДЉiДT чх”,є№МВФЅšhKbДOЇмеŒўŒ№Ёl#clIgI˜МŠм§„,/їpЋ0ёtEіzфj9 чIтшч]Ъi5š@#†ћ‘ь3БЪGпрˆ;9>dњp‘ЎŠd fъ4•эћщ6…њZчŸ•JЙ”vŽЁд”W ёR“7^_’_ЙiБ ъѕ/NŽ­щ*mgQљю)“?} –O`ж„Ѕ”jуУўƒ‘‘FЛSClэcL]ПxFДЖ›шьЪcSiзОќgˆбP№+ +j‹9•вж2ѓ>ё­ŸpфФЂxЖ)АпKnѓДžо2DаDў№aSfŸ…ёmt–Х3уdH4?/д&ХoЯ‹k&9ѓЈЌqђњ‘v№Ц&ЅнѓСr%шwqЃЖ`{У‡ьZs0ЫссwЄR#‹:I?ьўтVe€UЖЪwsЛЈВЭksћ?~D’uloЌЋЌЏЃ^V~pвр/ыBвІЎц=_zPHтьnхyБЧFc#ˆYпSŽJ њОз”дžh qоюВЊў{АЅ2b_ЦNэДšJиQЦїigю Еr)эDљўА#>}TvД2БSл.5&Жщбs–~њ›O(ЅRЁуХј2b‘ctл?ьЎŒ=ќ›ц= й9OћzнxЌяНЙЎ$nzШ[”#;Ь=u]‡аЊjд^RDHjЛ5:{PЫc“J uзяkљЃТy>№їЏVЧsдcђnг‹‘cKКЪѓ†шmїЂtц!‰”њNэ-ЭQЦWL{ѕqkиЃ˜%Ѕ3Їюд V т@ёкCьўЌŠ['cЋYCв0goIнЦДіƒHёЛŠчб‘њсЏмЇ†уЈiЄб—кцˆкЖъНg-§/uUX1HМ)fЋSЉЇёR№н‘1вїЅЄЃЦфUъ/œQ Cйs@?і­#œ<=ЏeлJяО+"DI:яnyЅХдюq%р[Ќ>zˆ€X*§ЕЇЎв~kY‰#eršO(њэ'дЋzyФѓуW[#QUЫ†Џ5ѕ›ЗU’eЅ\б@ЅuЫ­цю+ц фGO%Ip-еИЖCЃЅs`ЛYu—NЉSЉjbdцї€mJcF§хY0LUw’`ё$ЎыjкƒЃя#ПžЉ,žгОu†jЃЊ§0о‚ЎвўГ0D?zПрЯЭ)I LlчЩ“‰~ўožвВWж0Nž{l4†ЎБ•ЮtгрчЖСэol€d ukUгˆ@фSqmњFэфчJRйNpъIЪугEЃe{RЈŽˆBШёг‹)eЅъКH—+х`ЄТzэ эъњŸєžUЂ~я&ƒ^; ЁЌ‰+d­$ътŒяЂгmЇхЌ(”ыtћ!Vqkщ:Ъ?1LЕ›МѓяxМщЦЋhЖњ—DŽЭъJЗ џщƒlBиNwr†Л“$‹ЯтЄ№`ЖLx~­xФхQ{! { ‰7MИtЗь u%ЂФ!NЩРЈmЧoЮ С&Зяс/ўп<пКџŽчЌ(kО46Яr–h$Мы; ,ЭЁI’ЪыNаѕ#Ъб28•BHǘŽеє„в_ІВэЊьІ•‰ЖKlnчwяИ,cuEк…ц`іPoњpнДђозd`‘z!$IwоЕ:сw—3Ф;`r?ЗоuSч–№Вў"žПкЊ=Lк]х–я%а>ŸЦC"3tцЯр™Qv бЕЯ!†…јKиS{щ,-d!Хъ;шД›8aJЮjЄк№ ў2хЦК§>„~њ9wŒ~Vћ4FЙВ"ПыМйцuW'ќі[Ў'іЃ5Š?жз•i)w1ј_Зme ƒ L]УGю~œгч›T2ъˆт‘рrЏ‰kтCaј ЁbMT х_ННсР+ъЖ p&VдVcњНХГi.$эN†r]§8QЛ;›- ж$–˜˜aSэmћщ./r7 e‡ииС­_њiwЗЌЈЇ­Ќ-+‚ќ…Xїк…{Ђx{ŒyЪ­7ЛŠ›uжvэй‰BI"*^пЌЊЖ gо4ёТ ЪŽSgъЊјDА“ѓњс_yБ&YУл вт9ЪБ‘Ў4Ѕ*ж3ЫЊŸy$Ю˜ЦчЋ2IЇеТ—ЁфрЦGх0тіиv:­–vъIю˜іќЋ{^Хё :,4!іpЧІhїњщЂa$jXŒэсЖ;(шцRйEУ\ю8h№РБNЪїb˜YЗn •эєЯSЏRы‡ѓ@ѓЈыXDЅ*Q{qрАmŸ~ѕбyўцуTЋIіѕЭјЎe@аGпћrƒћŠhН+ћ9TА‹е˜Ёлœg%al\чZe>ѕѕЧ diЮ0w9§j+іШеYv•OќИЄЧ/ў`BЁѕ ЮVЗ1™nF 1э њ-*еК†ІGвkСcћшŸ=LЕо’70q х’ЁZ™&jЮ‘6тPiьеЮмi<з^Й›/аw ГмўЏCJVEи”Ў4-Zаn Мя н9е6GЈз9гЊѓёЏ>Œщиi=€ъeБЧfmnш•tH„•aЎˆН™Ў!Змь N}• Kш.Š;ОCzНОљУjК3•Ф—pщхВ+#y”Дчњ-)WjвWKДп,ьpEHbи6#џѕЁљ??#хJy-zVYOŽbчfŽ1SpмœLвЁJŽK“Oо~~у…"ЏО~LˆM tаšъзЫ_Фœк/НХГ‚iђ:x щ… vИ,тИB’ŒФ)Э’ ГЇљ№лїЪЎэcтЇ o—TŽ щ*-6АTО№dI§tŒ CqU4жwЬ=#•j-Хѕф"4ћуЙ6]G;ѓЮNfУШЦїб}Vj^ЙаЙ]ј~SБ!Њl“Иy>EЯЭ0ž’аІvђаOCўфл)Й6Q’\2›RБDf›ЪЧPX>І‘#сf ёЕ$Vы4FЅ>Мя<лŒŠїpЦwгmЗAc)фЬR&ІкUђЁ;$єRxн”яŠˆФŠ”J‰ќЧ7Й`зS(ѕСМWЩVЅж˜”^"аo 71Њ}aцZ>qЯyњйyёв„њeѓЋ­лC№Ъ*Oљу‡!X” ИuX}iWХБZgJЃ€†0шЦ"ЋБSКЫѓ2…Џ*X6YцŠY›LБв†чќЫŸ}уK& Бђ†њЌkЫзВcЊдІ š ZXќГuеIz~DЉ7Ћ†эŽў], Е4Б‡ўв\ќ>—!AGqwыјЋ‡ дЖFyЃ ™ˆ Ъ!YуПbІZЈ=ЯёСВ5oЄK'б”PыгЯХhŒ“флЧМйІЗDuzН~Ј„>|ЄqБЏ4іб9§ДV=wHВAŠ`ЗЛМx&цпМщDЩ ^џвЪБЖЎHij9tор#пяAĘюК†ЙЎ,0[С 5šЇ0+ E -Гщ sънљГO†ŒVЩŠIфŒŸ?Œ=6•ЃхfНVFАDij?Ѕй•Й•єЎ“Ж_Ыmw§˜Й…Ži[ž“ЎRŠгD?§c‹C‡чС‚ЯаvЉяЄ;w2]ќ s4P…ъ–|ГBвžтE ђ„1ждvНяpЬОч ФNЋiЄ’НY5&qˆОуZсМlь!}l/г'л­9IаŠЙ%J3њю;юУaМд~ЕU{XЉ9ѕ4єќЩ9PGWѓуZeбђY\лЬС‡)aТ^ЕЎнФ„^S 6b€ћфMЉп^PЯˆРr†ѓX‚>UЯЅoTH:‹yыСа—“МIњнŽКIJЎъIœhЅДШ'ѕEJ<ФњйxІяН2щž;FЕZݘiђj‰žTЦ&шј1„]‘…н…ЦВm<нчЯПwЋl‘ф<Х:ˆuх9юЏ@ФеU8/ъсlŒР‡WьHxчM !Мйю:Р1!vДцSRебЊŒ,XЙлHf`з'3кORДЬоœ|шmћdЌQЅыGХZєK.GQWiзГ€-ђЁФ  ‘Œvœч|БPпПxZм’f ЈЄсЪю•‰выBи—U˜ai.CВЩ‡Е„|#a˜а[–J­Џ–hЏ=ФZ*z|’@Е&gк љи]ˆэЙdсЬчЄЋœІіаЂЩ§Ш‡dQ0вйHЕ&`ZDЮИ0”’WO97ВЋ6)m? НХs`šЃЖЯР8Žaї5МџП?!ЭЅŽ8–!Ъц}зУTyџЭоф˜ јФ€о"хщНєђќ[^UTДA лvѓЭЧ[|эGGЄVwѓѕщ’њеVэ‘ЎЩїOšќљУ=H– шBХZCЙF/TБ:ч1œJЁ*Mp„ёН‹gG+ци,‰P*тЂЫЇ(Й•aЄC Б’Ў˜{щ-Я1ш~VVqjФЮбќ3”ЋЕ!_‹XТќyоћ гВf‚~?ZЁВ ыJDHЋџ”?zШ`ёЬМ`EŒє—gў”†CїШї6Йћ‡GЉdсаKi­и<чDзV8HgzMЙюU фЕЛUые ˆœДщ%ŽдГа’Єy>‡:­Hœя Ъ“Ÿ=HilJ‰BPХ‘@“к. —a‹!Bdжј…Њh ЛЎчƒw>"НN@Щ”С1`‰НCxфє„Хџ%уЊнЖŸ3h^ZПк‚яІuЃщNрїŠаœЮ!UFЊE ъУФ>эЬžjйЪюС„юЮфnэЖ[2x€ЎЕ–Ф0О[:‹sxF(щƒF”^Sнњ„іЃќn^ZhЭOЃБR”Ўрєч;­jЃгзыІ{ђЎ_|\}†_ЕЁЎвЈъ§gLюјqt9mмnBFзQeќ€ќўIПRЩ з/…=Жjs)ЅŠР*RH ЄјBŠ*ЗМвbЧž ˆM5‚eьmзhgёœŽ$‡зDъLР›а~ЏK9nCЙ§EЪгћшЖ–5C]™\–,jЂTj_ЎщћП|ЖыВ}јњrА:ћы/IЃY†Јt/—ƒЬ9ž…Ж;№Бћ…h~6=KD Нv:H0RпIwiV=|Хt hkЙ1MЇлO'7+ЋЫŠ c!дІщЖš”УeХvгвэњ4n/F!ФWМM)]ШмЮ-_~Dsљ‹в•‚V\єы‡…ЛŸь)ўт(кюŠu>DтNœ§ хъ„Ё:–’xS6ч TЧ+Ъmѓ ž„Аm?ŸџЮqrП s’вА №сєР  fo)™RпEЛЄ'” њnі^œ2aiЗ}хЂœН1#лHСaЬMја*BтP­VЅ+žhЗ FщТs0‡’D!ЬмРя~Š3ч[и)Z№sіЋ­њЎЊтйЪёC>ё аЫHŽRЌГ–оЄєњfwJ%ЌЦNкЭV_gbЄ!Ї†ЏЖА| Ќvc;Эvтx­yАJ‡X.ЁUY~bƒзюMјчЏ; §N€e[а•ŠчСžВљбБ.$НŒг^.,‡fEL3зЪэ}„3gЄl›Ѓ{…чiM”Nk9ZJ9HCF2xBIŸЬC_ё' ђшё%eс”Ž ЕЎ‘cкАpLЋзПJbЛBямIMЧк& `лpтY}ыkІљЦ­o#ŠbЂXїО9tˆv1ŠСDq“3Š-“U=ЂЎ‹|уPТ/џеМF­yaюDњA7Ц–IQ‰МьFНч7ЇйГk’n7ЄЗ.З ”-бЙОШОOŸЃє"с&1rDChžСкu жіkш?ЈЂЌ&ІвЉ5(яП‰оЩЇ•Ю‚`ZУаЯzx[† ‹rЭЮЧ?љЋšdе4[б•€–ЫШgю‹xз?…юёД3јN†)ј]HКZОњ&ёеDO>• уTIJУУГGјoz‹ќГз_GЏуoСц*^IД—XђЖ?;Љп;ag†Ѓ… ыЩaXЪ‰gф7щjОјЎ7kПуч!ŠчфW[ё]ЄlЃяўv(ŸОы'JtZРкЇ*-МК HЅЊоюыЄуЇ*VisјNYй5FЌЕН7ав2œ|2Ѓ_иw‹я“ЂЖz;їЫwџѕŒоt§vщvУ ЇJ7Ѕ+З„™Ox§_4ѕЬ љ)|c9,NуŸМyw§о›Ег ђљEйуbln/Ѓ}R€­ЭIq"зRОјK._}рvRCe жGЧЉ†PЧ­8kHаvcзzпЙ–ыŽcбщ8І™б 9Ÿ фШuW,р(@•dёвUН’ƒ€bш oоgшo58r<д’Йƒ˜дыЪЁ AМK~ёЅлиГs<{x№МЩЁšbэДћ!ЗмА Ю 2ІAжpк5G“d‚ъиИ†СЂзVЃЖоФ_е}™ш$^ЅЂKвЛІ„;зQзъћˆ’IuьЇЛь™pГŽэ-шЪ?0иi,sлњZ.экиw‹rШ4ЈRЗi--(/л!›}јУ0hћ“T=W3Šй-йМ*žѓйЗO№?я?†Ф УимЬ~яEуКwЊFЛэcыsŸ[’C„^hpCyžлпaлЛW oД–L)(е†Ѓэ…ѓ№ђн›žƒщZbHЌ е1—^k }щЮбЪУ јn Ф•ŸПjŠЏо†Ÿ%Ю‡ДыJMєЗіŸЧозPЅLиPŽ ЙQ'ke‰ƒxe_юѓВ–HЇед!žЏ€К†Щџсљcˆ§шZFЙД–|›1^Zгœw~n§GAЅз рkХОаЭЪЌŽъ PJŠ-(3'vЗlЅUH\ЄAЄ?–AС‡ђМШ‘Ы`™‚6E]ЄъE]DЛСM)ŒUэМA/$Š“AЫХfu•џю•-АЌ­љnё–’$mxмќзFЧЃМђŽ!ЃСЦrф|Ў%хU6мМIBЏfў"Я}~lеwЯЭч‘^œIДIž’5MЁh,ƒ†Х‹љ‰"КйУЃи4И]%ЄФeЅч4:@Тљсy[KFBXЂй)Ћњ Ћ)VўІqи"wРœЛЌш7жћ\уЪWЦИ2Ц•1~–ЦАFK•G*ˆYcЧ'+@VдыЏ`@=ј]уЪWЦИ2Ц•1~†Ц0VЧїFйŠGЏXРZуяŒDбVЁŒ]уЪWЦИ2Ц•1~†ЦјџЩ9›Š"G ћIENDЎB`‚pentobi-7.2/src/pentobi/manual/en/position_classic.png000066400000000000000000000140421227240712600232070ustar00rootroot00000000000000‰PNG  IHDR@@ЭЅЊsRGBЎЮщbKGDџџџ НЇ“ pHYsФФ•+tIMEл 8HЖЂIDATxкэœ[l[їa‡ч‡RdйOгISђŠ+в— qЗhб Šl@bЖ ’иCmлCА‡М {vV4}Jг‡ЂE‹ѕЁ—дч(‘/ƒnX›Фs]лq,S‘xHI^Њ8ˆй’HŠ<{ тЄ[ѕќџЊЮїA.Шgо~тЩЯйљT?Šz]ХЦЭщЦТkњЪшдЫ Чжћ§ОJХ’ТfЈBЁ€Ы+ыаŸ<Ћ^'Šэч\Wѕз•z]н/HŽ3јы7ХqЅ‹“Ъ?8Ќ‰н5ѕЃxE‘‚R жBK{Њјјјјё|пЏHыzы‹rфЦи.ЩuЅщПЁќр?8ƒAћџ€Эџ?ч ж8ŠХО}ѕћ=||||CП/ЩŒ_Œ§Мжsфхнї рћџў[&ю•ХЧЧЧџџ|Ч`П\d@€`@€H/љ[џДпсъјјјЗнwЬч+/77јѓХ уJrдяїE‘ЂЈѓЪўояїёёёё|ЙЎ\7ўзрмС|)пi“.ќЋ”‹ŸЄбz[AiЏ‚Rpы‹ЭqШхђ J>>>~ќWŒ*шЪLЈ“гзхyё›зюHЮЎЇ#ГWžЎДвšб—‡ОЋЎcжѓфpšЪхђјјјјБ|пЏшШЃ“RЯрќзuеЈЗ”яw;V=РТƒ#ЊYє—Ў.щО}ћёёёёcљО_‘к?ађUГрЩiz€јјјєщтууЇлЇР0€ РР0€™€ >>~К}z€јјјYєщтуугЄˆŸ%Ÿ >>>=@z€јјјйѓщтуууoЮ=@€`@@€ШєёёёгэгФЧЧЯЂŸlАнVЕxЏЪE_Н^?ІЉ=4|ќ ћО?.пЏl† тсцFUŸ u"Љ`Їy^§р€6кНј_Dv]ѕ›ЏъЩ]ЯЈCO?sОяыШc?”жzƒ—c1їЃ>Њvп§Rд5иЏw{€КбјЙY№ТQэ heсœ"џвЄМGTЅ'ˆŸ9пї+вZЯИчwbњКjћїjЙѕ’‘?ѕoя(ЏЈoб,ќъПЧіщ тугѓ3ьљmžіšњC…ќѕщ туу[њŽх~а`@€`€`@€LАu=@z‚јјјFлa9Щѕ7_CвФЧЧ7№­z~ЖО#хo,М&]œМ&ˆ5Лыm­ДfЬ§еUU‹qOаѓM}Mа#ФЧ7ђmz|‘ ђ§ŠUЯЏ9?ЇЉщЗфхsБO8зл]-ЮЯkђ”™ПЖж“ѓ•Пћл(_Ž}№яіРѓГ* љеЂЏ‡о9,Їkи ЯЪЋдЦ:=B|ќИОmяЪLЈ=їя‘кЋF~s~Nпœ<ЄбŽбё—KъssЙЫlПŠ,*пЫ kТЂЧWo4U3єЫE_Ю™ž"уžр1 OЄGˆŸ@яфєuэйПзиŸš~KЃ;э­Ž_џfkжиПчї оIЊжыѕх8Юцxб#ФЧПН~В=>/Ÿ“zЩ^џјЏѕ[xЗˆ!>~т~=ОЄЏ?Ÿ€ЬТР0€ Р0€щхWО ’ш'дщту'у'иуKњњч“эEƒoEг#ФЧOФOВЧч8Щ_џ|ЉXJЌGVШK§цЋв%Уž`Л­NѓМtсш{a„8ЌЗ”ів“УЯЄЉ +3ЁNN_П6ˆУеОччŒ{|7Wo*/'v§Ч•ѓѓї‘ыЦПђ[е3{sЉ!ЯЂ'ј№УŠ_Мr\iЭшЫCпU— ~}пЏшШЃ“RЯрќеuЕ8?ЇЏ{дИч”ЪšŸMљ|2зПT,*_(ДЇš\яєе%у_ЙшkуLO+ чоmьФ;О8ЉТƒ#Ц=CztјЉяЖ`мѓ›=РЌ]џ|К{fЖ=Az€јєгмѓKuажїz‚јПюnHw @†`€`@€`@€єB№Nёщ fЖчgэ[?=@znєЙџŒ|ыžŸ…я8–Ч єщСeГ'ИоVPкKЯЯЂчзœŸгдє[ƒ0AЬьэvWЏЯ„zс”YАнюЪЬ?—ехАЅ)z€є“ђ“ь ЎДfєхЁяЊ›б mЯЏ9?ЇoNвшЧјђ}ў{RЯрWёЎЋзgBMь+Iэ5ЃуЏ7Њ§ЫG$ЗC\{‚'UxpDЕŒіm{~Sгoit‡ЃНеqѓЫoЏ_ў ЇЎы/ke‹слЊѕат…ГєщСeБ'˜ѕ eЯoKz| іНмцЯBz€Бзгќ;Ќ'ШэПyѓЅЛЧ—іa>Ÿ€ЬТР0€ Р0€щ…рvёщ&зѓKњђ-}z€єр2кЄ˜hЯђђЗФwшвѓЫjOpuUеb rбWЏ7iЩѓM}MњOV=ОHљ~ХЊчWŸ ub:™ŸmOpНнетќМ&Oі;е› šЄH0­ОmO№ЁwЫщітоuеЯЪЋдЦКЁ?ћšЂњ+щf$“žн•™P{юп#ЕWќњLЈк}їKQ7ўљжєјl{‚хR њмœFю6ђ§`\‡Юќhѓ:юzаЄчЗ z‚Ю™žЂ…s›I­˜=СKЧ4хњ†ўBЈшњ_KŠ?^r\Э6[ЊŽп+ѕЛF~НЙ кюJыkЧяъrивОZеиŸo„њія?І9G‘С§_.šЛrYљ|:џєё­{‚gzZY8їnу)о)№…Ѓк|X+Џџ—БПуоаJУаѕiwO‹чnжУ[КІ'v—ДxсЌ‘?ЙtMOь.їјІ–Ўi_­fю/.kчgЋž_sn6Еz€јV~ђ=С­ёЭ{xЙС ЉѕгоѓЃˆ'јIїщn‰ŸХЧ/Ÿ€ЬТР0€ Р0€щ… ўжјIі-}л^к§,?~щтЇМ'(ћЁqo№чЄзw2џјЇ˜qП:VUulBНи=9iWоQП>+]:mиtЕвК,]4эЎЋ;J?3МќОЃ_ЮќЗžŸћЅђЙ\мK—лwє‹+ц~ЎяЈоlirщк l“›НžZaЈч2кѓЃˆoхWЧЊzфё/Hk›ЯШX>I/Kњ”$УŸ.JO?ѓЯк‘ёё?tфOЭ/џŒЄZœs•tаТMŠž:,ЙыёoЧU+ ѕOќ‘vnV]то~iяљбФЗђЋcƒёћOХ{;ЬйќџKњИЄ §“вЦŽ!яЎe+Žџw-ŽџИЄў)I§ЖqO№ЙХeэќєУ™эљбФЗђ{§ў{OF“'pс}ЏІ,ќДП­OЯ ўрлО“x{rtwюё[П‘MЯяvћ|2  Р0€ РЄz€ј›A9‹ Жѕг~ќ–>=Пф|z€Yі# Њ.qПЫњўsK?эЧoэЛєќшт'гѓѓvIЏhP51ШщiUвyI'Э§кюšЪcA:ПmщwЄхFЈЃ=??—Tд}ћEІ†2§ќЁ˜ѕžп+’>)iУрЩ›<љŸўўSкн0:ўкXM№˜ку‚@сlCоHСШ/—ЪКА0ЋУУF=??зЁOъХђЫu5пѕЯ|^ƒё HOo{єќ>&Лžпш†ЦЋf=ПђXъуJъЭКЊеЊБЅuХИччiКЇХ™ЫF=СЉХe|vШЊ'HŸž_FџNшйIєщтПїФЖ!ы=П”пџєућ|2  Р0€ РЄz€лСЇч—ьё'ьг4їщвѓЫtЯ/нїDO`v}OžєВэљ•ї•эŽ?ЩŸхё'}џGCCКЖ4ЕtM^.џцыttwPБъ њ–>=@|‹р„yќБФzxхRY—ц/)Ÿ7Пўo6п”w—gцoAЯіј“Мџ§ Ђ#чNN‚уž;ЎъЭ–jAQZ_‹џјq]][кWЋћIїщfНИ=М+­+VзџєегЊV'эёЅѕўїƒŠєгcЦ=РЩЅkz"(™ї—Ўi_­–к!=@z€мўЪr07xё˜˜Ÿtріђ3оУЫКoлLкOтіуs€Y@`@€`@ Нам~Ц{xYїm{€IћIо~є3офіЯrP’“ЄяаЬК_ЋЊ:61ФКЅ]љ]™юсeн††4п5ЕИ,/ї' ЃvЇЃVъ9 ПоliвЂGXІ˜хž_U<ў…Aв*ю)ФfЯяkGПЊюhзшђгоУУдxу  )2КџЭЮЯidxиШП;Јш‹?yVІ=ТVъ[§œ 1_НmеэGpєќКЃнD{~мџЩћ6=НцмЌБяizЭИјмтВF>§0=@z~Ђч‡ŸBпЖGHџлm€лџЗсгH 0€ Р0€ ^шо >=?|eЗG˜фёгЄч‡ŸP`Њ{|ž%sџIŸ”Дa0~›=Р'Ÿљ’:…n&я?ќtї§`\‡~ќ=ЉМфКšo„њЮg>Џ‚СјбмŠпqIЗє?&щEїНUЋUzzј‰љv=СžqOpjqY#Ÿ2ОќЬїЗЊЧ—ДOЯŸž =@{піЌЄ§ЌпјЉі“ш ђ9@Ш, 0€ Р0€ ^шJі=ОЄ§ЌпјЉі“ь цщёЩЊЧ—ДOЯŸž =@#п“7HZ™іјV%—tRЦ=?+U*я+гГУOmO№rивдв5yЙ\ќЇOЇЃЛƒŠqOаЩчГоœа#?fеу{њћOictУьјƒ@сlCоHСШ/—ЪК4Iљ<=;ќєљ~Pб‘s''СqOWѕfKЕ (­ЏХўКЎъ­Ez€V=Р“вЦш†ЦЋууЏ7ыV=П+­+єь№SщћAEњщ1урфв5=”Œ§гПИNp+z€єр№ё“шц/ §|ЮЅј†э6РЛ…јјПn˜ьž€&>Ÿ€ЬТР0€ Р0€щ… D_щэкјєщёсуЇД(Щ1їmƒ`uЌЊъиФ lыvхwбуУЧOА8п5ЕИ,/їˆЃvЇЃzГЅIУž`'ŠвнЌŽUѕШу_$­тОоьљ}эшWеэ]>=>||;ПёЦRdрпTєХŸ<+гžрмBЪ{€[бѓыŽv­z~єј№ёэќ=ууц=Сщ5ур‰ДїщљсугЄјўaЛ №n>ўчг`@€`€`@€LА=z€єќ№ёщ š =?znјјYь :’ђњїє%ѓŸ­џŠЄOJк0ПЭр“Я|I‚Yž>~rОTtфмIYѕuSвK2ЫI—єq™їјЖТџ˜ЄeмєvT­VщЙсуЇЬїƒŠєгcі=@лž^к}zlјјєэпIMЛуЇ>>ўхг`@€`€`@€L0ј&ˆmO/эў&|Т?}ОUPyYѕєl{|Iћєи№ё3мд9IЇ$yѓй•t6ХўКTЊ•tЯюЙѕХъX?yWЅb? ?ŸWНЕЈгПИЎ|.о+ GвZ?вџЙQ?ЛЈ шIENDЎB`‚pentobi-7.2/src/pentobi/manual/en/position_duo.png000066400000000000000000000221741227240712600223620ustar00rootroot00000000000000‰PNG  IHDRрр-jсsRGBЎЮщbKGDџџџ НЇ“ pHYsФФ•+tIMEл cЁќ| IDATxкэkŒ]YvзћœSЗžї§<їYžn…™dЁ Ёљ€"QЄ"ЂABё!aff№|$Ÿˆ@€Тdf˜г“iЛЇнЖлѕpЙm—эnWЛ]еu]хЧН{ѓсмSuЫщяukК\юѕkuWwЫыь}ікkŸНO­џYцЋ_ќŠЫLMЌГФ˜wЎ_cvf_ŽBЁШінїШdІEіѓsѓєћ}‚ №ЗwŽ…ь§~уyящ§—Keо›0ŒDіЭИЩ;зпafzFdпiwXпXgnvNdЏў;]џ5ъ ЂЬднNу9CЋецЦыєz=$TЪvvюГДИˆuЮлљ\žѕuт8ЦZыm_*–X[_Ѕ^oˆьуFLПпЇеjс<ћАи[dыж–hќŒ1,ійИКAЗлХЃў;Cў ‚€z­N„a(X…,CоР9‡scFэћЎ Ѓw8QћУсp4шцp~жkŒ;ЫЙЄ§є^|ьгŸЦџЇ€gнЁ­ь)Ђў;5џЅmЃL†б!PџЩбT @EбTEPQ4EбT @EQ4EPQ Р“Ч'ъиŸu?мЖ”лuV”M~иAыјЄљŒтAwВ1ad>б§Krјч'ш?fВёWџЂџF6QмˆiЕкIbЎ'SQDЛеІеlygЃЇЮяіКФXd? шѕzдЊ5YћжвmwЈT*Ђ‰ю?ЎЧ8dіЭf“fмєoп@†Дš-ZMѕп™є_bОќы_t @ пnЕйМЖЩєєДhеЉ”+МћоЛDQ$ВЯfГьээ‰Wў|>OПп9пZKН^чюнЛЂаZKЏлcѓк&SSSўіЮБд[dm}•щЬДњя њЏнjЭЮЬбыѕDrŠVГХ­лЗxххWDr–JЙТооKKKЂШчђЌ­ЏвlњыЙЌЕ‹%жжVi48ыМ2уs4ъ іїїщД;щ^Ш‹nЇЫэ;ЗYY^џbo‘kя\ух—_QџAџ5ъ ЂI–щЪcŒ‘лљкЙdу =ƒИБў;уЇKл?Дg2=™ї=8АC{hЋў;cўŸОU”SDPQ4EPQ @EбTEPQ4EбT @EQ4O€Ї2‡&哉эOЈџъП3цПQ*ӘD&Вu#—›рЙН;. p‚т"Љ3&ефФј§Ч рПЈP(R)ћыЉвŽWЪ*хŠЗ$Ф`0ЁZ­R*–d’g)6KdkYЌuо3ШG!.­.рmŽc8=$WЯ‘ЋчpћЧ™G”Š%JЅ2О0€uCJХв™і_­VЃX(Šь*•ŠиоCЉ\ІT,‰ШZKЉ$ї€љъo~йMe2ўr›ˆ1Џ\ЙТќмМџ–ШAЕZeыж–HцЌУДОіѓ_gчЩŽhѓэЎ9шŒдхоKpмO8Ьајк „СјзпўWиYы=ўжZ^Zy‰З.ОХммм™є_ОgggG\YЈP(ВЛsп[ ™Ž_Ѓsчі-‚0џвтЋkЋLOO‹ф\нN—(“™fiqбП> 1Ф˜ –––DћтRБФіћлЌ,ЏˆЖA+`а№шт#Мъc9` 8ќ №xзз €oЏ—Їi М 3ѓ34z б':э—пОЬђтВї"ђ<ј/—ЭБњh•vЛэ§ГжR,Y]{LГйеlдмџрКнЎш иiwЙКy•хЅe‘џЊ•jЂДЮyЯ?чœxл№ДНфц{Ь(žѕ2fЬ&˜Р>|ъ:Ю3€ЃФЦЇ6нБћэ›ЮћЛ(ЯƒџЌЕ8dчWЧёі%ch=ДѕЕOlŽк—жWŒx‘0к™Б§IЯ§ŽO6мџБ€7кOˆўPQN @EбT @EQ4EPQ @EбTEPQ^<^œLwЖ>з0кOŠ–<>žzОУla}Р“Ъ†‰в\МIђ­Е|пп‰єl‡6INІяRr”сŸŒmЦ‚`J6ѕП|ќS-ч$О;MџЅ ЙџqЋШ~ЌПвœжёвђnбќм<љ\^Д Вй,љ\^TЭ:KО'—Э‰n`Яэс6\DЄf0CУмх9Ь=@пР*№šѓ@ч`№`€}ХДBќkГ8юЭо#7—#ŸЯ{ЏфЯƒџŽ\>Ov!+ДЗфryг}dsYrйœш‰6 Ш.$іўё“~дяїYпXї/Pш зыБЗЗЧкњ*=*ЕZV­ŠкZ!|ˆO ц.ЭёїОћwЉЬTМшЌ#~%ццЋ7Щœё/Џѕй•ЯђѓŸџ›ф>Ьљo…8јЮП§шыЌЏЏ‰ЦџД§—ЫчЙџіііМ Œ:ч(ŠььмgwwЧЛ}gqмdwg—ƒ§o{k+ЫЫьѕїDёуœЃзщA@ЧоЂHc Еj €fГ%кТ EnnнЄнn‹џІi0vєђ}‚E`ЮAaІРЇW>-кТдЊ5њл}КнЎџъm/g_цЩ“'мЛ|яpKњьЫ7pЂщj\=“ўЫ.$:;= sŽBОРоо­VKД…­UjмО}Kд€z­ЮЅK—h6›оу˜€rЙ<в Ю'Ѕ“žaЌЕ.8:ЯIЮ€arГжzСјо_Ђ's.љ†sюИžа— йЪžUџYkq.§щз‡сpШp8\šWїЬ71*"вB=U.­р“PюpgŽj,’Б1GzЖфќО-яхb:‡ѓЕOе‰žP"щ3GzТЁŠ&сiњ/Е—ЬПЪчсF5"ЌЌМЃ\\яђpAшВ ЂњhЦ€Г–|>OБXM@O'…BQ\пmЯьс6МСaЁŸЖн“€йjЬt1і—#сxh2d ufŠБЗœШЧ77sЧ'ѕ3РТFШlsFTЂЋ?нЇ+P,§ѕt'р?GRсЈ/x`: …љ\^Ў'Ьч(ф тњ€й\ŽbЁ(ъЛУ%zРЕѕU|ГRаmwшїћЌ­­ŠŸJЅТюЮ}VзћоЈ<?дщоžупoќUИœѓžР&y|угнŸС]zыщL0<џ6ќрЏЩњЯџ„і џѓОOєаўwјЗПЦъкЊwuЅ“№_.—ggч>{{{"ћBЁР§ћїйннOЧ|ИЛЫкСšШ~ii‰ўH(щЇг!2ъѕ†Пž CЅR:lˆѕd7nо йlЪє€ “Д{‘Щ]0мГOxјюU<їА&€ѕѓЬUœ§w74„ЕЗ’~_Ќ^`юEїрŠgќЙб–ї"˜(ЄбЈ˜рcїпТТЛЛ;2=ŸsфsyvwwХіЕj­­-т8=@Њ•*8DѓпCБP<вzяЧъК9ыМсщўy\Oцу@cЬ‘0­гњ`Ђ4I№…‚oZЃ&„Рћ›GпЄёЏА}МNЁѓџєœГkьЧю?gєt’уэKЙOы9eGH'вcІ;ŽшDо ™Щп,о›)wvމ„eюєoљД§wўїдnЙЬўЄњЏП†P”SDPQ4EPQ @EбTEPQ4EбT”УLпTЂЇ™дочЯ‡žы k Яџ>ЮіMh?aџ#ыЌМЖxW?ЕOЏ!бRЫпЅczЧƒIўa‚pЄ„№М@zПA$fр•S–&?г?Бw №зŽj#ZgБXЭPZaRџ‡љ “Ю?‡ѓRt$ZОБ\P_{duAвЖ I>mT.•‰Вl№(ŠЈзы4ъ бъaŒЁбˆiдXчЏчк™о!<НљoІ АђфўD7n$ еžhюёјжEи|э( ž OТDЩ№YйЪ§X=€o  єЁЙS­жdў ёўФq“ZEжО#‘е$§wŽЉL†zНN­VхуFaHЕZЅбˆeЖ ~Ьo}щЋnffіЈRЧcЛнjseэ оЮ7ЦP*—Й}k iћ Й,лляNM%‚`ЯUЈPШГ{џCŒ EѕсZџшСчyј№Ёрєђh§UfєsиƒОР>рЩљužќчПw„;€яТєЯepяЫЖБс…ЏМњ/g"П|єбPgsYvwv УаЯџЃkфђ9>мн՘Р{ў8чЈзымЙs›(šйїК=VзW™›MтЯр5џк­6QFДZ-o= @\Y]_ЅгюˆЪc•Š%о}їнnз{Гж’ЫцxИ@[X_.Ÿ+Аіx•8ާлwŽf­‚}sРў{›ўћHчрњїqŸљiіяnužіCИіVђяkž[аДЉs№шg'†}ѕ„Џ„SнŽ}ФдBџYg)ф ЌЌ‰ќчœЃVЋБННMЇгэ[ЭыыДѓпCЅ\I^ТHєPЉ&№pEРџšюЅzЌtя.БOkгMv€wGЊРѓщžzbћклс‘НС >фјg0$яУЧќ(}q!ѕ_њ-ЉџвoиŒ[Fr„rИфsОучžz zжє€OиЧ­';ў:qB=рЧћSўŸЯвœmџЈаЈPQЮ$€ŠЂЈ(€ŠЂh*Š Ђ(€ŠЂЈ(Š ЂМxМ0zРIД\'yO2gвЇ ƒH"#з?‰ЪB=ЅчђНуіВA›ЄџЧїa’ъ;Žn”РњЫYœ=š=iБп‰ч8ЊЉс™ žкXg“:…gЬуѕ eхВкЩ‚6jЦM{‹ЂŽDQDЏлЃлщŠыЋ--.бiw%F?kчƒ+ЫЫдkuQ6|ZbЊZЉ ІЇ зў\§ž >Ÿƒ§moнKЏћы ƒ{гIeЈoр_œЦРFі‘џdШђgWhЖ›о ШщћЯ…!НnVГ%ЪežžІгщˆд 1˜ЏќЦ—\E’%„fГЩцЕMfffD(•JмН{Qћ@v!Ы^/Q{WШ„l.GППч­цHчНRфŸђЫ<ЄвЏбg№ж§Ч<žољo8S‚Ї—'7оbfхG>‘йsw“_›ўL˜AЂh=]џ9Ње*ллл&№ЗЗI}ПЭkзШdІD @Ћй"š™žЁзы‰єLЭИЩЭ­›Ќ,ЏˆВСKЅ2ЛЛЛ,/-‹VР\6ЧњЦ:ЭfSTоЊX(ВОБNЃбш ЁUЏ2МачСэ AйфЩїxMА…ДРЗР–aџЦœяœ…ЕџEцS?Ъƒ[oŠ"0иќГ?9KЛГ(š€Їы?GЃѓ пЇню$[IЯ)мэtйКЕ%šџЦjекбЦC"ШMƒ&™et–Lџіu@К—7Цxї<р}ћpЄžœЌMО уСщЖгЪт7е;ЄZF3вњ}M€УћO4Œgб֘б7aFчЩ@.G’є|#Ѓˆ9Ѓ_FћЈнЦDo!КPњOƒHQ4EPQ @EбTEPQ4EбT @EQ4O•чЃ>Ёђ"ј/ВЮŽђтњa­Kђ п˜Q._€ЕCЏtFc‚УўуWX%I…Lo"H ЅиЁ_їнH˜жі“$cЇKЈ&љЈЯојЁб˜БОјЭb`М6оiљ/™Пƒ,%жЭїЌ=7‡sј№3ї’јСuкDш+,4†!KНE{‹иЁ@˜k‡ќФb…n3Гі™яСЄsжZВK5Ње/ag:]ААTЃ\šїзУsг№фм\}SІќ пўВт`яУmxы ˆМ§GџзОoў>„AрpŸ?Зјgˆ[9АюcїŸ%Р­МDБTЦ_Эф’љЛИDЋйof:“шaЛ]сњc0_њЕ/:I6{Њgzчњ5ІІ2ЂЧzЎTчИ_‚G=Ро{ЏТај€1Аsђ5ЫХzmо мКџnEОџИќић—Ы№ЛџщkєKў‹'жБ—љмюп!+Ља оПЩAЖ‡‚шќwџ*џи§.Ѕiй“ЏRЉpяо=ба[kiЗкмИyCЄguЮбŒ›DsГstЛ]‘œЂеlsчЮm^~љY}Рj“щ <о^ѕ€0‚­з™љєOspяіhщi§ЕФ~ч=Œo…мРр6О w Ќ9YV§ОœCЌмЫцШзrџŠЭ мвпОфџEc`ѓUцъyА§Жlќ'єпќЕ?Є№™~фSKоz>ы,q=цќчiЕZўeІq,vЙЛ}—•хяГЅ1†z­žшЅz&7в‘Iѕ€ntq‡z4ѓЌЦ`-Ц–s;Ф…žрьQy<ыpЁ‰уCMœЄ>œхш3CA№ŒKє˜a"pрhќ‡Ѓ’cуŸžсFџэŒ§и§—і9,жKЯŽ\чAxрxїЩтЧ=GoAЯкЩ^ЈЈ™OюxЈPQ>Йh*Š Ђh*ŠЂЈ(€ŠЂh*Š Ђ(€Šђт=WН™фыЬ9ƒјцЃ3(&Э9Еl7љјŸВџ|ѓ0DFїQі“ uіHл&З :bщщ<ђЖGуч,8y.!XA}“Д™jkЕѕЅTŠыћ&е7є 3szўЎ9сWIo@Ђ:,а7bZ­Жw}>€Љ(ЂнjгjЖD…с4ЎОз_#txДўxoеПОg7Пя]ѕЗ7ьУy ч‘х>ОќodzРАђђ2ХJIД3\{ољC=!yќіEИњšРор"xИёЧ№юjRшд“§с/u–шеŠўѕ qLM‡Длmтz,*pEЭf“fмЈI BЬ—§‹.0ЗЫ9GЛеfѓк&гггЂUЇR.ѓxї.г3ѓŒRуНќяr1_2ŸчУ'Sо Ј10ЕНЩ?qПGeоПО›ЕŽz­ЦЛя< Ьј/C7фГ…у/}с/Ыž !№-рЏк”дї{~чќф†ўы‡…lœхўіx,|ўdзІјѕџђЋІJЂW”љеG‹‚0сж~mії˜ЭHќoщu{l^лdjJ`?ŠŸhvfŽ^Џ'дЖИuћЏѕ€•r…яŸыЗНW0ыSХƒе}іnnј?СТˆљЭoђфЧІ(З–q‚њrеFЬѕЛЏгnзќйЦQ*”ax™№їGј:‰<Щ<ћтРЗсСь<ХF‘РлPˆѓ˜ МёШ?"pЏfxlчEуo­cЎв"ЛКЧЮ{зќЫУ™€шъ7Шўф/-/<шvКмОs›•хQќ4ъЃ—0’}lКэ”(ъS{c’RНоŠhkRN&ˆMћƒд—;Ќ8ЊАъ[_№и‹пƒŒІž (чРбHO(ЌЯ—–Й—LоdрГРœwћvTЃ~ьќъѓQ}ЦбЇ!М§7іs’љџ|Нь§мУЧdы~H§6Їдўžњ{@EбT @EQ4EPQ @EбTEPQ4E9A^œL˜Sж“yл?н3Ћ%DžJ7 ц#.шU"ЯœŒџ' Рt"LrA‘mZcЯMv$™/Uз1ђ{Яѕн•–8Ќ(нЛx”еј“і.љKК€Ѕ§—Ж/ѕџијж1В<‰`’^#*ŠTЪя ЄЏRЎP)WМѕ€ƒ еj•RБ$вюƒ[рњžЯРр!•ZƒbЁ(jпCЉ\ІTєзу9“L|.C№jш?ƒG`Ўмw‡ чА{–…—s‘˜нЭ q—\Ђiє—cтBiБDЖ–ѕЊ˜FюУpШ“лрњEЁџQЈ6)”ыЂћd(•J”JeяиaњьWѓЫn*у_пЯYGЗзхЪ•+ЬЯЭ &TЋUЖnm‰є„Ю:ТR›ЏЛŸcїЁСy+šЂэЫќ}ѓп)ЭgМлЗжвhФмЙ}‹ НЧЯZЫвтWWЏ23;у/(wŽ••.ќр йOћDХўЉкЫќѕ_ќY2Л‘žа]r<ўѓ=Ђo2x\‡L7ƒБ‚$ъxЛFјН?[ё~› фЩ7ZŸХYAyЊ dА~ŽЏХР0šЩёК.Q&3Эвт"ЦSbŒ!nФlllАДД$’c”Š%ЖппfeyEДŒ m†WѓрЦКПЂ:™Пі-f~|š—VVМŸ€Ю9Еї?ј€nЗ+к‚tк]Ўn^Ѕнk‹>‰Зb.\zSlп,6qћŽGo<ђ“Ѕє}рг$zDп'`\€Чу1ЌyОpЩŸ7п}Тє№€‡w/њћпАіmцj/Бџо5џ#ŒГАљ*гЫs4кKЂёЏVЊЩm[ч№§ €sNДmћ({ЩфuЮaгЯh“2}Ў˜ ‡Е НŸbЮкzxtюиg@$ Xrn4bћT—ш}†4O§A$Њўq{9_joGСфуџєЬDG7c<ч FіCбмIчЬ‹ѓє#оl§а^њ;ЪLhџ<Мѕ4ј?ŸщЭфЩў‡хћz ЊПT”SDPQ4EPQ @EбTEPQ4EбT”еŽ8sѕхN2щGђiћGw}јИцРGљлЙЙУ#=рЄyIйŒpі(—вoЌгšТ)9JС($ї?о_iNыxЩјЇZЮI|‡!Љ1с;їЦѓ@#С^*ЛF(xЄЩЫ&€0:Ќѓ№ьўŽ1х„zMa;š?јЯ9ЂљЙyђЙМh dГYђЙМЈ>šu–|!O.›нР>ŽщЛoБp§œ ОœanАKБXgaag§WДсp@6—%—Э‰žhƒС€ьBb/ œС`РТТ‚м Џ„ЬО:ы_ŽDЯїрњИ0‡30Ь]žУœ іfyђш:цж ™а†;[˜­7d9Н&`aa\.ч§@4fTœЅпяГОБю_ аAЏзcooЕѕUџВƒZ­ЦЮЮЋVEэg ~iц]‚qю‰їъS,7ьяБЖЖцнОГŽ8nВЛГЫСўЗНЕŽ•хeіњ{ЂёwЮёЉ•O1‰џ{‹ќъџ§ђГy‘žГДXтv%‘џšЛ<Ч^ћe гя/8ы˜kЗјэƒ?ЫћЙЯјяР‚'7^'*Д>SѕOфBЎМЦою›єDўыuzDAЧБwy*c Еj €fГ%к‚ EnnнЄнn‹Жoй…,§н]:Žh ™ЯхY][ЅеђЏ№ыpд*5nпО%ъ?@НVчвЅK4›M™žЌZћЏZ­’ђќШвˆќ—­e1эšl jЮAaКРgV>у=ўж9fŠ1юmЧУЛзEѕЙyЉо_рр§ˆJlПћ6СЇ3Фџ& \.є€‚3м‰шљFі’эЇЕk-Žф:О}‡ЧкїƒёГ[z>šАcіЃў{зчГюpл,=COю?wє*/№ZН›(9JIќg‡vtџ&9~„ўG‚(љ.‘ §ийбЖw(ђпа%:B§5ФЄ‚|)ј‰CPQ4EPQ @EбTEPQ4EбT @EQ4Oc4§хš#&Jѓ‡CП 1&H&ЁH 5VЬё0—вГРтx]CIџ?*ŸS:љЌГоrІCQЎ“хB0ЎхєЖ?џЙЩє€цH*ѓŸУ˜ Щ•gI†‘ 8K"grV:ўAЂ\Ш.ˆълЮZђљ<Хb 'q"P(Хѕљ–bЁH!_№v`z…B|./n?—ЯQШD ЭжZВЙХBQ№NьsймщљЯ8ц/Я1ѕGSјыAч>œЇи.‰ЧџБшюEоy=IЈіŠC`їю­В№ЮkH’zУ,фЋ‹E‘ас0_ћ—ПхцчцМ;р€nЛУХЫE‚P€JЅТз™››їŸќЮ‘Ыхййљ€0ŒDі…Bћїя{gВЇСЧ17oо “™й/--qёвEВ Y{gyiх%ЮПqžB>:ўЋVxcу<Г™yя}ЈsŽbБˆ{р˜ŠІDўЫŠlnнЦ`МwСЮYq‹­[[dТPdп[\aћцѓѓ Ђўw:"ƒЁ^oјыЩ0T*•QЁЪ†Xxуц šЭІHNГААРююŽLЯ7вюююŠэkе[[[Фq,šРеJЂё3ЦP.•БжžЊџЪ7*МДђ’иkkkљяУ ќ_ЋжxИГMЗлљЏеlёЭыыbџ Х#= їљeЌЎИГчёIƒєќ3ЎЧѓq 1&ББGz:oAч˜Ф>НџёПeGH'вcЬб9к:Ќ‡Є\§їœјБ/qLєfЯLўf№Дэ}Ўсœ;РёŸЏ wЂўЛѕпЧяПњkE9E4EPQ4EбT @EQ4EPQ @Eyё8Ь„ёM%zšIэ}Ўqкэ7šаў9ѕпщј/АЮŠiS‰ъЃь}Ў1ўчг|Bщ`ЖЌ>сa.ЁЏ=ђ<ФДНC Eќм№IDAT= ГъП3ц?FuЃrЉLмeѓGQDН^ЇQoˆVc FLЃж№žDдч‹у&ЕJMV`„DRTЋ ьc*“Ё^ЏSЋеDЊ№( ЉVЋ4Б…_ C*• q]§w§Eбћяoгяї“0Я|чД[mюоНЫўўОїcпCЉ\цЮэ[мџрQћй\–н]nпОхg?КF.ŸуУн]ЖЖЖМлwЮQЏзЙsч6ллл"ћ^ЗЧіі6њ§ФЦo]Z\тоН{œуМњя њЏнj…aDЋе‰RуzЬъњ*vGTоЌT,ёюЛwшvЛўхЉFj№ƒ§кэЖ ОœЅ+АvАFЧЂmD­Vc{{›NЇ#zЕš-ж7жi ЦЯCЋйттЅ‹ъП3ъПJЙ’М„‘ьcSMйсŠ€џ!4­Љ'еcž…$ћp{мЦлžЃ}МєЈЫГЮџ}Дуиwuдgаœa=ргvІѕdf2= 1F§wжќ—ОEQ”SCPQ4EPQ @EбTEPQ4EбT”OЄ№OуЌшЩршгцъП3ъПбЩгzЌ“Аїе“йЫ-i]Рёњ„ВŒr!­м>эџЄz:ѕп)јoдvдŒ›,іE‰Ђˆ^ЗGЗгзЧ[Z\ЂгютЂвС`РЪђ2ѕZ]” Ÿ–ЋVЊВС Czн­fK” 9==MЇгЁл‘UчЩLghЗк,vеgб1˜ЏќЦ—\E’%„fГЩцЕMfffD(•JмН{Qћ@v!Ы^oTЮyї?›ЫбяяyЋRTЋUЖЗЗ FеjНьmRnѓк52Y}М^ЗЧкњšlќеЇюПVГE43=CЏзщ™šq“›[7YY^eƒ—JevwwY^Z­€ЙlŽѕušЭІЈМUБPd}#Љя&Љ/зhФ<шїiЗ;ЩVФsК.[ЗЖDуg0tЛ]6ЏmЪьеЇы?cЈUkG/a$‚Юtаƒ ЩQFz2‰œц№ьAr пўOп>Є:Иф›"ЃѓH —ГHњ?ЎTџAџЅ~C™ ѓtСѕУѕŸ Ђh*Š Ђ(€ŠЂЈ(Š Ђh*ŠЂЈ(€ŠЂhž.šyЂў;БДЮŠђиŒ1`РN b4Œхх‰2БŒ џAphoŒљзџпзG№БЯ[”оП ЬЁ&MdЏў;]џlЂFН!вc„AHЛеІQoˆ…‘нN—jЅ*жƒѕ:=ЪхВИШeЇгЁX(zїл9GEД[m*хŠШ>U8ЊUk‚‘s‡ŠѕпYє_RпёџКкOfœ‰'IENDЎB`‚pentobi-7.2/src/pentobi/manual/en/position_trigon.png000066400000000000000000003241671227240712600231040ustar00rootroot00000000000000‰PNG  IHDR@цќ•sRGBЎЮщbKGDџџџ НЇ“ pHYs  šœtIMEл 2–ŸуtEXtCommentCreated with GIMPW IDATxкьНIА,[v%Діё>њ№hюo2ŠLe*Ѕ*u5a‚™F€І5fT ЈЦ Ѓ1 Ьа0РЌ`PШЪ ЄUVЂ( ™”њ™љ3•}пќюu7кїнfр~мЧНяОx9сП&ž]‹xБN?ючј>{ЏН6оБЃ|f0@DФ,п@ђ?ИузјзјзјзјЏз@љ ц?Є>ЗОƒіу5ў5ў5ў5ўФыˆ™+lЕ^rљu3@’ы'7Пѕџџџџ"тu0@чІ$pыџ‚jS’bNОЦПЦПЦПЦПˆxQ.‰хПк„Ќ>•Іc‰сЦr$ЅэзјзјзјзјOw(Юмƒ`нZOЉYS &jЛ_Q<™IлБхy ;юGчяб™ћ,=И‚ЈДыщЙ№Е ˜A$ˆЯzp^ўŸЙ`BE<Ом™ П0ОЕНЙ=&сЋq˜‰„рЇЯНјъ#тђDуŽs|О|ЏрЂlуТЫЏЅiFišr5ч_Яп_Џпі(*eеPѕŒлЎFКћDНBxff]з†!ўфŸўаuю˜єOsлЖ&Кe[‚шx8СЖ-_Œ—mКNІiтp<С0єчТW BЇгХ~З#г2™љ9№r1gFПпЇ§сРš uL.Т—mдqКœ9љОг4х‚xОђoГadNЇ# У3“тП_9•HЧqpи`˜1у9№•ТР`аЧЭЭ † €Љ G^€ЏЧ†nвЏќъ№ЫПђ9JтЄМ-НžПП^—пЌп–хѓKОхu”~FхГWЯЬа џъOў_јЧBгаыuQХыЮг г0qГОqC@kКsСCўRœ'иnn  6-MУѓ<€iјш§1 q OшvЛ( ОЯЬ0- >~г0Qфњ§>ђ"П|L˜aй6–Ы%4вf)DOмщъЙЏ/BxЁ‡,Я‘ezнюSB‰O?7`š>њрcєћ}DQŒŽнAСЯ1&`˜І…оџNЗƒ(8Ёзы!ЯГ‹њBŠЂ€у8Xzˆ‚ X–EХэ•єN‚‚‚4]Рѓ<њхЯў2ќРGœФдэtЙМЙм—ь†ŽгЁхz‰ЋљzН- vЧ.ЮЦфОзdл6Ўзшt:4Ÿ_ёЃЧЁњЙ5њT<шКŽ( )Ы3ўєлŸТƒ‡Њ…еЌYїсх˜0˜“4~ќŸџтч(oі|ЏчяsтEiФ0Њ}SлЗЈИPИіЊgц•ХГœш)ПћЅw1›ЯqђŽ<ŸЯ!@`*В,ˆˆ˜ˆJ{Мќ?˜™MУРіf ЧБ™Й@ЏлCСЬyž—ЮЇцћ-<ЈЙLгФсp``[6˜СНN^рБB^)Зё•ЏЄКˆ˜™q809IŒ†#‚UwуЉС€ašXЎV|5ПТсxрљ|ЯїP:^žŽ—§Ћкрѕz~ПЯqУ‘$ З(wсAu_,ЫТnУ†a@7 ЁБi™ЃJ?юРЃ>V!'I‚0 быѕ8Я2 њCјAMk‰Š'yўЋ15tЫеŠчѓ9Nо гЩ~рƒЊ-№]xѕœЫ1Y­WЧHгз8ьЌiкыљћ т…21ИёЫ‹LNЈŸЃr^АМр_E<6M“Пѓэясt№x8pQH’”GУ!|Я+ЉHЬ ЮŸ˜™™ˆЬЧУ‘'юY–q˜ЭІ8y'yq+aЎOЈ4ж4З7[ИЎЫF!Цу1Чq‚Ђ(ю8~’ "Юѓ‚;‹х‚GУt]‡яћ<шїQpСY–БtЈмТ—Ч&bоэn`š&,Ыт,Ы€Лн.QXбјЉxff!в$A<QaШГщ ‡уšІГвF _5_/n7ћмБЫEžspЧ.Т0фъЇюФWЉ(Š‚mЫЦbЙ`w4f]зс>FУ!gYЪYVШ1Й…on“€у8М\ЏЙзэТ0 $IТ†iВЎœЄ Јt%пТЫk†™Y‡,Э0 ˜™љшјбЧQgdЏчяsрE5РФЕaH-6Ет`Ї3ЯЉŸНjx""Яѓ№о—пУ›oОYуЃ(Ф`0@–gШе­кY A~пВ-,—з4IZIšРа XІ‰( ЅѕEgЎЉ:и ы:эw7dlлFQШѓEQ`ая# У*7ˆяФ33†Nž MR фy!NОљќ žч‘Дt˜Йu<еБд‹нЭюи­НЯОяcтN( CuЇГОд§“[ЮЩdZЗЧ1Y–Cз'QГя‘јГё5 лЭЛг4AD”І) tКЂ("!DГUёе1ІУщ0ЃзыS–e "„QˆщdBžЊЌM:ћ}RЃзHГ Очбh8FQ "xžGѓљ žчATмН“Ўd–I‹х Гй yžƒˆ(/2,žЌАYmI+#M• ьѕќН/юћЂ|]?ЋяѓэF_М\0œn_њЫwЩ4LX–U_иЬLQСu'№МUм9'…ˆšІQрHг Н^_Nv""aзRF( Ejї‘š~ЖЛ=І“)В,Ћпѓ=FЃ1’$!Ѕuдv ]ЧfГЦxFхіЊt@ВъX.є F'фК.<пЋћ!ћВZ.0›N)/ЧЄўЌ<Івз&„ Я;сj~Eеж‘ф˜X–…еzMУСDщ™iс•cB…дыѕPф9Iџ^Д0iи‘Іip:Њ8” Эq' t]'лЖ) #Йа‘Ёыј>’4ЁС /лЇjŽQГiђ"ЇЊNЇƒo|§HтДЪŽ(ƒJЭ“3т8.­•гЉіСхEŽгё€Б;ЎВ#Ю№2QДВцЂ8‚;žРїНњю.*_йd2‘‹_IœPCМеkš/№K HзV—išиl6шњP"Ђ-МЬv!”AˆNЇгј(+фfГе8шU7дј&!чy–c4С+ЉBа„†(ŽU[љ‚ …+йьI .ŠbL&ЅOЋВ]ˆ€эv‹щtŠ,K[…І!JZLuПuУРzГСh8nё [xЅ/žяaа"Э2y’|э`Е^ЁзэBзѕіuЂŽk,ЩВ ŽгA†Фщущшv:m+єŽqaтСеэрыљћ М@•@_EБXЩMdѕЏўœ[m~U№Bl6ўЦзП…Ћ7ЎЄOЎЭ/"IШЈ§3\љ›˜Q Žcvз‹kžИSщ bхТn№UлDeІiТВmŽу–eёЭю–eГeYЌLд6ОсЗ1HВ”]wŒгЩ“љЫœФ1†ЕsНќю^њ%…ьћ>ЎцW|<ЁыgYЮЧгюи­љ†tŽWЦT@…< f)gYЦІebЙ\b:™АтlЛ…—c-„р( a[6ыКСqУqЌWkюїЌišznZПЏ№Рч9ˆћН>‚0€a|ђN\ц1їd_юФЫў!8\ЭЎp8и2MŽ’Иф0ŽЧœч9+dц6О_„a€‰;сАђїъКЦ›Э†'юД5&чx9&•Ш›еšŸ<К†Ўы5зёѕќ}:^WNФН)ѓ:c:пz§тЅЕф8џйџѕчАL †n ЫВЛœ­wёG1ЦУ1НŽЧ#ђ›ІЩY–сp<ръъ tК]$QФКІ=_П&‚Ўыиэoаэv№ЦьŠ}ЯƒІ]ˆ/ЧNЋѕ Жmѓд"ŠBhКС–ћёеёXІ‰гщФ`>Ah‚ѓŠљ,|н†eq‡№=oПѕ6›І‰$‰Ёык3ёђНr‘bŽ †˜N\AРZ9ЎЯФK~gЧБyГZЂгщ`6™Бяћ0t§"ММжlлТnЗƒc;М|ВРЭz'ƒBЏмќНW*œ"ЏпŽО5пЗ‘/Оt\wz]ўкWО0Бaš%%Сщ ƒV&C%аŒjcйqЩп‹9 Bє{=$IwфђЉЄoдR>wсeІ†T^­WИšЯсy''№ƒр™ПЏ>†еzёиeЯ;СЛХq“qІ&rŽoekXˆˆѕJj*I’КЯwсхq23 У`?№‘х9wœв4Хp8„wђъ1Й [џ!ОЙЙСd2E†%ЕЦїљ)yžD‚АйЌIЩж 'q™9a˜uz›Š—ы_ЉcаjНФh8*%бQ„4MрŽ]:žŽ$ЪT,ЩУn,9Вm›эЖm‘š­QY4q]xўЉе>*§iE !Ф1EQ„сpXЗЁiљО‡сpTЩnе ­”д*–`Лн`6VќЖFъЊзщ“Мб(&)Y ЕЌеzГ;ЗLЋЂ(ЈрƒС NзSЯЙклvАZ-бяѕIзt™}BJ– Ы\^ѕ`HeгъšпїЉ`FЕэ%HŘ  ёh„(ŽoKоSЭJ‚ Ay‘cП?`2™P–e2C„М“‡сhDi–JЂ:IZ 5J""lЖkšИ“–ЌкnnАxМЌ’яшхПП^pх*”щ2§Њ~ЗўПt•ЙŽъї_|•ф_њ‹/a>›5V€ВїˆуwŒ“чЕ’г+ЧBi-щ:іЛ=,гjRФЊь\AЇггi%ˆ)х”ЊMM#Se ˆТ–HЉ №K9ЅnЗ €ъд4USZ\8Ž—йЄІгUj1plb­05жpžчА, O– LЇГњ† hр!Яs јbеЂIї’жвfГЁ^ПпkЂЦвѕМf“ŽЧcЫ‚cДЄЛЊ4ЗвхТ†!§в4­Ян™Ѕ@RŒ!ŒF#ЈZЅœYЧВ`˜FY JŽIЭ}.­YУдБZ­0›N+ݘ t;N{\!ѕЫпГ, ‹хcw|ОЧ…`6)•АЋ1Ё&•d%@'шšлЖ[7И2ѕ2УУС;љѕMюЅœПП^ЈђKj§)7ЅЮещY5Л/лŽƒя~ћ{№N>WE‰XоUdZQЧpœ4M Эвжoж‹Ѕ Ойя0™LXeуKiЌЪп„сpШ2O(зPy§†ŽеjХгщTn[ЋДЌfлœ$ CxQЩЂЫ_Йх4xНй пыAг4щ‘Wuќиѓ<Œ+й-9љT9vл.)†ЎГmлмV>)Ч C–JдђЊЋїB O3і-…d9иОяc>сt<‘"SEŠ '‡ЃђгpY[жWEєћRИкЮ”3 ƒћ=4M'ЫВЕ`j|je!ЂnЗ MhˆуXŠЙTzIRЊНTbšФŠXнjsQ`аPuTєnnn0q'ŠВt5†еЖJ!№šЯц8іаu­NC/ŠЖeуЩђšм‰{ž"&•M@D!FЃв$­8rЬ,гЄЭfƒNЇУ0ъ„a5бNQJLѕЪэt’&•КL УI–J ERd ЅJ В,#Mзси…a-H[ƒŠ"/EJ]WU‹Ў“ЏKЅBF4›LЅ -Љ;г4БX.iвт’Ќ6 \w„ІcA’ Ж?бЖmZ. †Š;€фнЖ“$MА\ЌhЕXуV@фžџЗ‚ `z–‘дœ—_,лТзОќ„um&Љ-Й^ iVжŠп8џ У@HгДЊ…[дQ>5J+3Э2 јОп dhBУцf‹йtŠ[Е<”e]гЪ*cгiнeСРѕтгщyQ“aЉ jЋ>Ц#ЄiRз.˜anv7АzбЁZsъmEЉиœ%аuЖэ B!ƒANЇ#FЗг­бf\­ОМ(-Ш~ПЏ_m–ŽЧЧcХ‚dulk7Fјpн |ЯoЊ•”‹-”`аypBu<žМfгi­„-}je0hƒ~ЏпЈlWЬQj:RЋXлŽ Уа'q5~%ufЗлCзu8еЕVпџМЬП–лi?№IЋЎ5MгЧ1Ђ8Rq›й­З“Ч?І4NыW{ў "А`х>Th… IDAT" œЎ~/:^оG УР“Чзќ§я§WWWЕиAЭ%j’Ѕ"‰є!q’&ѕЖOг4ЌзkИcЗсЁQXVЫPVЮrєЫЌљ›–]V"єа4­мržу7/чyЁлэА_*%Cзu>yG;NЇй‚ЉxДЫ.†aШгщ ЧуЁ$XW•б‡Іe ‡›mZ{ЋQ-Ў†!Fу1Ђ8ЊsJ%5&ю„‹Ђ`5ЋоЊTїQYзУСˆѓВ˜9susК^.x4Бъ“ЋёѕЙЎ ЖLІiВЄЕL‹Зл-[Vу—•Ѕ(•Љz [R…bžИЎДрXqšЅ№+KЕЅн:7Е;„У0Фxф"№Љ№Алm1LY)žеЦWЖŠЌe2ЛœЄ …Œ†—хG%эЅЊXуЯcж9МYoyёdЉд'yЕчПPšjШАDЗYАjЏ:зNЕ,_LМмJjš†wџъ] њƒ–ђrM7PjvШ?Y}mа/eбMг„wђ:NK-њ.М\HKKc†гщTE†ув™п>nуС-:…_щ§ХI\Y†Ѕƒ~:™Жœтwс[ЕLL †Љ#Š"8Žƒ'‹ЦU}ŽV;*^9išBŽнA„0MЛ§ІaТ2эжxЊxеЁ]ЋXO&8y'˜І…0 KЋКзŒЩнјЦ"=z'Ьц%UЈЬ†vћ=\зmOnсы6ЪZ&нnЗz]Ъn]?yщ—UЄєoсыќо*аdйхqX–…r+пU‹!нЦЋж €ДЊ-rђOА-'я.­ъіur†WgI”Dxєёc„U­чЦ=ѓjЮёДL٘VŸU&5јЌ/(^жФјё~‚?|€Б;цђТ.Р\№йsы}€љt:ЁвЖCžИ^.x:™ M“gтхs’& QZ~QётzсpPЯgу™Ы…8Šc њŽ“Ыѕ ЖэАЎkШѓь™јЂ('Гј<ސЄ ‡Їi‚nЇУyžн‹—DрУс€‰ы"ŠKџfГfw2AšЅЯФЫїЂ(‚eX\њ1ОООЦxT Ш^‚чRbžг$EПзч(ŠpН\ зыBтЂнЮxцœ‹"G<Ѕ)жл-HЖLГВˆяУ—Џ‰Ј“ёa"Žc>ьFUат~ыKЬ‚ЇCOЎŸ`4q…Tљџ.ТЫЯ-гФіц†MЫ"C7Ё›:[т–•СїЩ+ Aœe9ХA лqXAІaВ’п{/^ІeшКŽХbСгЩŒ<пу~EеЙЏPјб“Ч4Юиї<–„у‹№Њ%КZ­Игщ0MƒoЅ•=/_kšЦA Ы4и0–пэYx9&šІaЕ^ёxф"ŒЊJn_„ЏЎB˜–ЩOž<Се|Ž“яa8Ъ1ЁKЌЗЂ(ИлэтЩ“' њУЖm)ёоg[Y6ўѕЧ?Цƒ?ўCќф‹›ѕпљ{{GѓjЬT<Р;n?wkВЉЗ“ѓ"3/^F~пћђ{(ŠюШ…ЈhDЫ§•йA€0ŒрŽЧ0-Aх`ОєQц„2ŽЧКFЃ?xЎ~EлrА\.aл6І“v–ЩЅг4Боn`У 0щўg>$шф‘e)мёКa ŸoLЪBы)<я„~ПЎЬ‰}Ž1)]&6›-lлЦl6Чёtxю1‘йЖec8–VdёZŽE9&‡УЬŒбx\‚Š@ty_ ЃМжЂ(Фh4†m[Ђшту!9 LЙРь›iЧЦїџсјf Эq^Љљ/ёBTъ“аŽє)ЌМˆx.ЗExќј1~јƒуъъ ОяcтNjЮ—JUЉџЉџЏ^ЫЌ‚ЋyYЛwтN i1їтбXІ…еz]G};$$џ‹Tж~Џьl,ЫТvЗ…i™0MY–b4AЩ2Й~ЌY–!№= ‡C„aˆЩh\GQ•,‘і1)§ѕѕf‹ЋљЧ#Іг)Ђ(Њ‚$wŒЩйјд"ы%FЃ1dўqžч­ДЕ;ёhдUlлЦjГBЗгЉ#žнNЏОAнзyЌRС'Ы2tЛ]ФqзuQe™ДЧ„nk§Й lonЪЯЧc$Š"%Fљ”kEC7А^Џ0™Lсіаt–e ФqФЃсXЊš0K!Ц3М A—rJЂ0ФpXnOž‡љlŽ“w’bІ|veн?5 ћ§уб˜Ы„ўƒaŸѓЇ1ОўŸќ‡а мвc|Йцџ]xСJIюšZгŽpЉ%нсС~a№2—7M3Мї•Џa>›/.”ЅFУ!<ЯЃ–@(5ЙМEQ гщрzЙФ`0 27ДЈлї|ѓйœNо КІ“rSЅЖџPЏ#­NY#„” *sbRФЈEf’rJyяфa$yAПфŽ|хЏ№ёПњ—d\œ1^šљ^дѕжChДіыgѕжФhпЎ^|EГ, _§ђ{ˆЃНRLS• g™‘хЫh05ъ+5q:Š"щ›ЊRбъ(*В,ƒІi\Y+х6Ж nЖ‚ЋuЉіЂЖ/З_yžЃ?Аtў+рzиЖХзЋ%\wŒF$Љ<ў2ЫФуЋљNЇSнП:гЁк/ъКŽнnУ0йЖm\д}%"і“‰‹8Šк[ёJSˆ™aъ:<ЯуМШЫкЖE.З§rЛгДX7 Hы†­аЮммяіp]З&Ыmz–І  ˆ! :wdC–eуzБ(ЧЕJUЋ•SЊ,“щd)7пвi\КiђfГAЇл)%ъeк\uƒ:yцѓ|пЏе‚Є],Я•a8LDшuЛ+Ђ:ЧqЃлщ03ЄПW-EWOГž`8ВЊ)ЗсКІ•љзaШДZЅQQіGФјмwПЬћ›}%кJЌщјЕމoџоЩйщЁыъМyIцџнx3Вм+RЫ‡Q>З —ЙvЊрТ'/'њv{ƒя}ћ{|uuХYкфћЪ‹›™9 Увбьy\Q&XNžЪЭыЭšGУa#йЄф^ЪдДёxТЅѓŸ[9РEQАm;М\­ИуtaWД™ж„їŸGУв4хЂš<•ЕТUŽ({ž"ЯЙлэе –ъƒЪВ ‚лŽЭЅгН6лъBт§~ЧгIY$]­Љ Ч$Šcvн25­ЊлQЌщЏЗ›кYŸ двN&Ў‹0”пЏ%›ИДtV\n[5Сšу…xpн1ЧIЬEСuџфеnš&яїЛRqкЖЙhЎК_I’АmйЌ Сiš–Т)ŠПŒˆPф9Nо‰+™{VC‹e['IЪЃёЈКND=ўrqв„Ц›эSwТ- Pu<ЬЬQСИ,хПъsR]'–eaЕZёx8R)@мпчЩdТQ6’ZЅUЪ€Тю№Џ}ј}N>ќВ"ЏЧГЌ IШ™љjхYF?ŽЊН2URѕж‘Й nЏKaжxЉ,e)љОGЃбˆ*ŸUЌ&Њ,$Ш <КККЂущDŠœ13™ІIыѕŠ&юфжЩу!"ђŸІю„‚В59ŽC‹е‚†УЁ”&Љ-ЧPAqSЇуHЪZ&ѕиšAЧуž4ЁQЇгЉ§›ЅяTю‚‰’4!Mг`й6EQDе–*™*ŠKХiЊјqеИB=."”SгЩ„<ядєЙ$шfwззЧ/ё"2‘я{4›ЯщфyT)UзпsOžќˆњуŸa6Ÿ+4•лQЛj›Фгвб­V4cнаБ\-1Эe2|K „i'і}ПФLbŠuf6л5њ§~#иYmСX)DFDH“”u]‡išˆKAL\ rnЗ˜ІХRК эќpЭЌDBsŒG#>UєZq8 8Nb њƒкzPKЊ–SE<O(u;˜™5ЁaГнb:В …ƒ Шч—бмJbJsг4БоЌy4Љ)u­ЈІl(Žc8v RAGњemЫтѕfЧq`šп9&ЈЋЇUˆ}V :Бa8+ХщЦE"ЧTс^&IТуё~ДЦŠ™yЗпaтNY‘UcUZMމяћ˜Яf№|_U bнаБZЏy2™œплю*п&K%ь’Ѓ_РpКќЗє‚GЫѓ{–:&_wLƒ?Н[сkПї_Сv:LBМѓџ>М(еЙы02ЕѓЂeJЕЄѕ вя,еН?ЙxiuщКŽ/§хЛ4Mk\Еq"Щ*яUКP– лВI’ЃmлІэЭ лYљ…ъ~жwнкЧ#џ4›ЮQ9КIЊЎDa„с`XІCкјњ.UЖф(VтЃEQ`8дКў…‚ЏVŸІђZПOE)1UE9MЌзkrGnK№ѓ/'MЧdšІЌLш8-жK њ§ІІШ^ r@œ$p]Їгe–…/№‘ч9ѕ:=%C­: -„AЩЉ+-уВxTžбЩѓ0Лuu9/ЯЏ ””•熔ЄMŠЎыиl7pЧn=ўчј:UЖAN ­ЂўЇƒХbAЃбИЌЯнјэjМR|ŠЄт з#?t]ЇнnC7ШЖЌі^Wlse9€€ІГЧtMЃдДёЏ­PяGп…†Uд[ЮY;Іц€2шsoЬБўу?Фђп!НЊsђ"Яџgсш"ЁGn<ВмЮUE ?Ёx9™Žƒo§ЭЗАлю0Œj:ƒJ2•w^PC і}ГЩA„(ЇУО”‡*јЉx%ТŠ4MaY&єRў–ecQжжPш#t Џі/MS€€nЏ №aл6л zН ]o‘™[x еП(jHМІib8€@шu{-ті]xI і<Wг+xžWƒ’a`054™3<eНDQT§&#MS˜Іѕr‰йt†МШ[Ь§s|йFYNnЇƒ0 си–ЋuY<ŠЮоwœ_iСeY†с фmV5afЋЖЦ9ЪИhšпѓ1ŸЯрNЅ$Zр!Ћ„nеђ­ђЇ­0+•к’уqIlЎЎ­›н & Хъ.Мкvh2aY&М$AŸŸџб7pX<ZoŠ@Јb ЬАLŸг€Џџчџ I(У‹9џ/С дœжvрG-,"СjЎ˜O.žˆJЊЩбУп|§˜Эf”ˆsmОЛДњJЊBŒЩиE’$XЏзшїа„@СХ3л§ ƒуёy^рfЗ­T“mЅhЯ§šІСї}ŒЫ€‚0@рŠ<_t.nЃ( ЄyŠсh„$JБ[эрі\ЌCфЂИр/Hќѓё2vыzVЖf_м†Бc:œ"Orœ|IR–ЩМt\eiƒ~П‡"/PФN7fу8сЫŽЅрŒЁх:В(Х~НЧd0Ш4жž‰зXƒШВ0У|4C‘XЏзЈqŸыxЂ0„;ž Шr|a§њП‰Є`hTnїžѕ—ц~Щ"§кЛј№OџŒўЕUџ‚ЭџK№xЧ зы­ЕlЈaW(ћi.…UЅЊо'_–?Д№оWПŽ~ђНѕЉOqофЕžнGZЮRr” ‘†ѕvMY–ё›oМY–У]†—>;ЫІеzХQc>kЩ)=/ЩаuŽЂOž<СoМIВ@б%ј*(Сš(гЗ=~HНо€ЧЅHСEј:…\kєpџ€5hxћэЗ%џ№"|Е’гБљс‡РгеќъЙ№Uа‚-лТ~НЇэbƒ7пz‹ г@%1ѕLМє3ъšNШя?њ€чŸšЃзэQq;йїNМ ќ ЁЁШs^,Wа…FoМuХY–_ˆoŠ ІŽХћ "<O%qќ"Мt“Z–MЛ› џі?§Ÿ№ƒ‘’зјl|e‘w<В§…/тпњЧЖ%пё…šџ—рuЅЌLй[шЌ^.Вjeе}ёЬ MгАЛйуƒŸ}н4p8ьIQiЙELЦнRBB@7 фYN‡УK9ЅЫёЬŒ$ŽС“iьље$р №J?H€и0 ŠЪZЯ…—‹†mYdŠ<Ѓ§a[ukяСЫ…ХVWkњGяќ/Аж&`?р=SяЗzˆŠy”пЊ‹|/ОœЉ„WŒџ њї‘„ ЩрЪEјъйЪ,ќtњSќўяўc2wВ(+лО_‹Qф6с @>Ь‘­s"ћr|%OLqуз?ћыјЛ?§Лф}dœ]†WТpћœёыBZс‹У~Щіmі†]џFЯІo™~іѓёХпќuЄxБцџЅxНšдDфjGmљх3ŸE\šaќdт‰ˆ-лЂёЇџ’Й`ŠуКІѓЪO•#bf2 ƒ}ЯЇ4OЁ ЅŸхR9Ѕ*JJyžcПпГ,^дыѕ€ЂqkпгV-1eш?|єѓљЧУЎ;y.9Ѕ<ЯЙзысёуGшї№§ƒAŸ‰еj<Яг4Mћ§я<ќ4ŽŸ=ђЃє!DЙЧК@Ž  )M Ф`ЌA˜8€•…чйrb рЦ@џЭ_§wјЯЎўSц“`q@Ю9wь§зПѕ{Иўц‚ёnhЪЮщYrd„€wРи`ШСˆЅжrf ЃњЁѕ#\]Яљw;ПKЙШ/–C3'†MŸ9mёц7ўšПw шяє оoЖDЅpУErd&Ђ”њoўй‹ю !‘e‰иdў_ŠПН0žЂЉўЇŒц'РВ-ќєЧ?У‡я„‰;Сh8jW^Sы%№?Іјп6л КNГй 'яtї%ФwМЎІibЙZСЖ+9Ѕ<+§:Jэ†Л№ЊЯТ4Mьv;"ŒC8Жƒ Є^4mмƒ—mx'aa8Ё?ш—Ђ ‚ю >Ц7ћЦК‹џxѓsEP@ў‰ГgљКњг| ЦЪв–є1СX­ЯяХ €иПd№­љЗёз›waГнR§ЉћЯЗSс .0ЫІјƒ/ў!ЎБ–€НАхЭIмѓћъћEЉдЂПЃ'РјБ1її_œНЇzЈУјeШдћ}ь;Јe4ЯЏЗж9PСд4ќъOП Ќž :юАБ{0- , i€ІхŸ8џП€аu„яќцПюtŽ,ёбћ ыšr-}Тчџsр…Ьъ$5‘ИЂ‡дЩед”Ѓmтвињ„с+&?}љЏП‚йtJaЂлщ5eЄ)*Рe5kBk VT„иѕzENBгч9 УжBz'žСMЃ Rxюїћf“)ŽЧЃ”SКпP6j!ƒн~GГYY~ху/аяZџ6рMёЛvp[™Ихkа&2Ъ€#ˆп`№wтMAH”явлjyчЯS7‘М•xј#њпщp{ЂrѓGАRSMWoђЉЙ~Ыv d†IŸл/аљС7f9}JxН%Ы€•\f‚Z’‰Є8щqˆfŸЦЭ~ƒцƒќ  хѕ Л›tC!цџѓр)a”:Н„ъrsВв&зљurёTПѓ ТƒСЖmулпјяoі<{ОЋйWњxсЗLЊ‹,Ы<Œrст$Mјфy<ЅœF#$iТВОн‰ЏЗ‹l/— žMЇ "Žу†iБišˆуˆ…wу‰ъуЗ,‹—Ћ†ƒ! !f)ƒ€~ПЧ2зX!кжxYЇЛL4p<tЛ]P坘ђЩ;ефлЛ№2пWз Уaёx4BЦт#џ§ў=hoh@qЇ-ЋфЧ”Щ—ZЈ1О№GU4HOŒР`tС(юРЋ\ {ўUцтaС`@t>јєќяџœЫт8ЈSОZrеVоџЏПі|мЈџbЪ\|П€ИŒЂюћ­§AхГcœРжФBвI€MJ§ ~7ecn0ВЛї­1*јгЬxXЕ=џйєЯљ‡ыБEƒюј§В$(ƒ}0ўл_т§fЂ(xоqЧ=?Ц–у€ыРЉrdeю33Ÿ˜ј›_ј;˜Ољ&k%I‡?њј И%Q?ЙѓџyёŠšB•/\sјхЁ!ЕЖka4Ц§'_љьшt:сkя§ Нљц›uŠR–eƒКнn-Uя0s+•iГнв ?ЈSа˜™’8СФuщx:ЩHn /ЙГЬLЖmгnwC†a’eYЕŒQ…фКcA(Б…‡"_ЃiХQHqb0( р!Шѓ<ŒF.Хqм"кrЃhRЇ MXoV4›Ю‘fi%Ї”n˜d™І$6+щBMЊ”,]7hГYc<‘TЂ‰EL“Эџn№їЄe)ж(5Ыze-Щ)†­ IDATm§Г:%ЛˆAаЊўЮ ?-HИ‚ЏXƒWmЪЄudfFи ERoƒў‰ѕGДїіuўzŽыc“•[xџїщŸџ”№€ aJЗ)Œи XŠђЁjС*ЯZЌQёйќ „  (:‰ї‰0ЋоЃЛёаKџЁіy щ*%фЕN;EŸŠшŸЅLЉ—’ 5ЭБNwЌюRЙгХЏ>љ9ё‡?Ѓ$Ы@BP”чєљŽE,–dMІ .jy-Х,_AЯњОћ6сз~ ]Џг<ѓ"Їеb‰›ѕ iКІжи }§ЗS…ˆЋ™jљVyЙЛ=^џ?у‰ˆ5]чЏМћФŽуА|ШєЖбxTэЎ“Ъ‚,33 !Х1GaФ•˜f%В)8ŒBtœюџGн{Y–žчaЯћxsюžИ3ЛГЛ‹MX A0@–)‚, ”HЪЄ)1Й\"-[ЂƒќЫПH—\,9IEћ‡Ыƒh[фRІD‘XaѓЮ†Iнг}ћцprz§уФІЇgьЗЊЇ{Кя{ю9пљОяМщy’ˆ 1IpЁС–‡ BУˆЇгw; –•2xHBFЉ\bзЕ3†–ДŸ_[EЌ( їюt:E =\Чц^w‡‹EюС!RюAщšЦУсеj“\NsГL“лnжgЄEћŒЄд49CдЊЕlќ$’xъMёЩ§ПŠѓsЪaблZѕАЂ)Nœl:ёпC]РЗ|Vg*Ѓ‚Ђ7™лsъA І|ƒyMч­{mўЇ“пфKa@lO Б,@БІhјGџcЦФ›ЂЌeŒљ<#|!dyWоюСЅчcƒхžЬ>ћР"їЈ8n†Ќ**CС†šy6X­Ћp›.а'c`jЙћ,}ђu!v kрPHиu œћцx>Ÿ3zн.%иV*"lЧІNЇг4SЦЁ”q'KИ)Š‚ЩdL•J’$Q= „ ЅБDЇеЁe’ck3šІб|“”–ŠтзI^аѕ<*щZжиœ^hТ]*Яhi˜h6šoaњо0 ˆQKФнѓ1YсЧ‹‰[Gи‰1а+ž„/љаЧ:~yёЫ@ ”TЗБт9 qI < iC­кpрНф‘жет"ђp 0”Г*ЙЖ Бї—)а}vїsxљшehЄ%з*Рає›єХ{Оˆчљ…Дт[( ƒP н”эфwДж*й–)К/Ў%чV”оQА]Сњ˜ЌŒ‹dIРНяs|…-=&мќnјћZ!$’VicrЪT*у=o>ћ№&Т„„6НП>+eіс5Z1яM™LPqDˆ,/^|кй TQеѕ="ЂˆуёGHВD+ќ”пУћ‡XЏf+ѕRЈђЖšњЛeŸРд„$с/>§94 lQƒ$LгDНVGœbЎРХTUСl6ƒ’ 5ŠЧHк\Я…*Ћа4 ЎыdpЋДЊ+ˆ„–Цэv;ыф/nЂЬ ЯѓаM "љypy9žtнnQВљ­€й™aй6:6 г@&eЉ(Аn\эV+“"ВCг2c’а˜>rЅы:†У!JЅ”]HЫŽ0РxэG№tэ}q8Й~#=@))№ъp”T>злpe ЊEрЫ к! ТJ P„‚рBмˆ7™J<сљП§3и $VahФГnтн§ыЗh0 œТЋ!дВšoщ{€1 ю№~ќ~БхwюM:єxƒ\П^[2<Щf[Z“уДЎо}4јcЈЁ’ЯУdЊŽ г>кЏ|ЫХRAџЅˆ9/Exe8A­лЫDЎR<В…ИQmу№‰aЇVЏЭєЎчтЦе=˜KsЛ8еїрў!VќШ ЅЅ5Ёƒ"ВР=§Ўл3Ѓ\.уѕзоРбс Оk›WъС9hЕbLl‘\2}Д'у ƒЙ!nC€ ЅБDмгgAШ2Ь ­Єу№р ;ЦЖ†8ЬvP*•@BЌLЪдƒœNЇ(щ:YЭЯЅpн ѓ1$IFЉT‚эи+›œЂ(0La W|WRiErж єбhЌVИS֘0ŒАXЬбjЕVА­љ˜!clŽёŸќp  €ђЋв\?ЪрЋœ{KыqшˆАB!%_*жhш"!ё{q‹ЭЋММћ2О<}JЈФm>h‡mќбŒБ9-зСŠ^žJКA@Ш<И8”‡Ђ+;aМ™Ыkіщ9y.СЗШgті–lLd@^Ър9і Хš=VЧф™кПЦсИYШЙУK€/ѓ›ћ ” 5 G’zQ„]CА˜a Ћ(i*8y(“pЯ?№jнIкК‰ЄXxУXтцƒŒе;ƒ•}юbуЦmЁЪD|6Ї7иK’зѕ№ьП‚nЏ[ *Цўyyжu]жK:dYцTy-Ё/Чp4фJЙY–‘цўЈРІцрb НЯЭV ‹”в<юљухb €P)U8Ъˆ%WвЖ™(ыКhЕšБWbŠл^ц R‘LйЅЄЙ9г4бnЖaЛvJv“Ш‡t;1MUNvмўŒ_•`šъе:œН7Š"VУбз\*21Ёt|svi›мнП›?Щ\у8'˜€ЅГ<лЌ$їХkї?п8l†РѕˆcBœWS$A/“у26хч БЭ]РПЄЩЦв€L  =дБпЙ‰?8ѕŒыЋМН18ё6qь]ё iZLœє’I,н/!< ЗeТŠзФЈAАt(ќ1ЄK{3p‘†ПX;#ŠYЁgчfјCѓ йR2~Œ@QљоYъЋп‚Fa\Jё`œ„ИИ ЎЇP›,Јx.оhТђўGб­”Є•bЮ—eBSБіШQШ“ёВ,ѓїњў!тaІFMœѕ-ЅdЂёїЄœBy*]ywэ“Ъ/ОѕoaЙ0Ј^Ћ9ѓ*PB“b†nЗGK#ІSŠѓh-Mƒк­VŠM5‹Є1і0ІBrЈVЉчкЙ$бp8DЛгЁD/ƒ 4lYѕ.ёЎШqв5ЊЊІа6ŠейЉнjeDk9—тё(Š"„Q„F­N†e$[™ІгЉŠJКЎгŠЮCNћG1)pьщ†AНю%*c$K29žKЖэPГеDE)Ž(Гчœv ав^вЯюџЧhєёm A2KФї0АŸфьы•тќ_„S€7іH3UB9Ў”Љ=JнˆPTЎФjoW^qVAз.\Їg&B’/HА€~чсп…еЗ)йXWTДчјмљSј­Є3Rќ;аъ9šŒѓ)бš= ЧfЮЂ+”šл)Њ‚рt@8e$жьгћD„]рџkў)Н4y 2Ыh аН_ћ Эf“”‡€Mrое@€ЖЎM'8€DšЎAŠBšCа‹~НSЇ‹э[жOžK_Ьgtѓњќ ˆЩdљ{wџYTк{–]NZaЇМЊH1”t№нГƒ…l&žћЪзpцЬiі}?—K‚И\.УВ,жt•ћмэt9 Ѓ‚бv{lƒw;ј9ћg]0M‰•K*ћS№‘їѓ­4šЄ”$qўŒУзЃИ—n VvИp‹Фƒ:Ю^ФUTœџaэљp|Ш•А‚7ЯПЩŸЊ| тHp^пк^$ЉЂ&8tC–&PKK с{BNrœ]ЫК}zlЃ іeт†`єšKїI‡aкkИнОHгб=џ+џкЃкР{ЎМШсС‡О)–ХdAФ"щHЩzЂЄкщFПЇ^Ц•›7Yщэ‚l‹ŸяžeОяд!›kШЄ6цoрўсгб”sBпяН§ƒrYЬЕмC–/+4Эа1ЩХwС>VUŸљwŸЊj F'z ˜–z­ž‹Nсћ>jЕ:ЂшЄЧ X–jЅ ! І‰Щt‚nЗЛ‘ƒ<ю&љ˜ZЕУ\ЂпяЃнърN_ŽmЃгюРq]$Х iЃtЬСВm4M„a„бh BЉT:ё1$’а№7Ўџ ЮWЯ› Їчћ…0я$_еX(7Љ%!К?Š iЕї_œ{\ќѓw @Сo=ійэŽ‘юЇx‹!C‚ИGРЗ|РФЩсФ!Й-€RW‚Sw€УBёфv_@UТWO=‡П˜~wЯц8џъ_b6‚’{|ћЏ‚€3‘‹ЫгŒі.Ў<ѕьT+w>з\WпИЯѓŠфЌп3ћGfj-ч|:zЫ"сLъ–Х–яД}’oУh4Тч>§yььь‚ŽЅЭУ3ЊЂ`i˜L'иээB–ЅлmЂЇ'!bЦўўzнєRЉHctЂЫ#H GCШBЮ w6< YVp4`є:нфtтсefшšŠсlki`ggвъ&ЪЗ™Ž€ЊјBѕ‹јЏšџ №5ЄЙЋлЧn:Ca•ЇЊѕGqЋˆ|rћДb[•ёЃOќ(žQŸїšЯзфУoї\Pмgд?мC(9lОАЄ;<‹јН яъЌ<.“ѓЌ ђщЮŽР}оwгŸ]ћqЬ>ћo1#l!ј8~XЃˆЄ(‚њƒ‡ѕбŸ@;б>Љ} ѓD8sзм}яХŒ!œОGіdT`SХY”ыn•Q•Vt6 џ7ь”љѓЯТ4-:ИyГШџ;>ИŒЉEI–Ш4 †rmЗЕGЎ$F•JІes4Є}'ЖOй*4Mу 05ІdйGaˆ5:љ[ичš›В$ƒ‘э8ьиЮЊ˜љmьгPG’dbDl[ŠtтЗЕЯЇёCxПџ1|§Щo Ђ;ВOqўвЧpѕcјЉ›ќš_ˆТOШчФi‚?ђш#ƒsЉZ‰!a„ѓ9Т‰ Ю—щ}ђ,КqіNZi81Ÿ#"з˜[v_њћќц“o1м ЙФИєм”яНzьsЇйГˆДзч„|ŠJшѓ[r‰^{ф§ьАU…’ЙvтѕУ‘рЃсzЇzT.—A.iї]ОЄџ—‹`љЂ“sЧd;:e“МEС;mЯЬЄщ^yё F8ЕГ лБP.WЈ№$Л-Ÿ›‚$IТўўЮž;‡бhL5ьуьгDЕZС[WЏтмщГ4›ЯQЉTVšЇГЯzэ4 §Ѓ#ЊV+ЈTЫPd…фЭаѓX>7Y’ЩВ-ј^ˆZ­Na T*amLŽхƒKpияг™гЇ1MSэ’йg^dIЇWі№ЋУ_Х/о§Kр79†Т„ŽH №сSѓOс#тУxoчНфАУ+Dль ?+ВŒљrrT†и$HАЊЊД&ў}K{№ -ИыЕ—H/ ИёгП@ZЌуq"ћ„ћЊЊвс•Cў{ЃПK{чз€ы VјЖіщ˜„27СПѓЕ ЗobgЇAвrŽt{ћ<а`Чuш/џ(v:Лф/Іejьž˜1жЦЖi2™тpЏK\Ь6ў.п?6Ёpyлo”ѓ ХyЂ оiћTрШѓ||хЫ_E­ZхZЃзq†сŠ‚лкї•ŸЃ(‚,I Ќi:ЪЅ TUeЫВАR0И…}z UUy2‚аK:WЋхД-цЖіХФВчљXš—JeдkL!ь$ійѕ(2†1эVЛйbгВVTЭŽГЯо$ GGG(•t.—ЫDьyо‰эгцыёxЬ‘сЁљCќзНŸL)Іxƒ6‹6PУqЈьхЊ Њƒ=сс_…{сpФ‚((~ё­~іC1GЈКŠfЋЩscУйQ—#V#^yСrЪ/‡ЏН_а‰ьƒ(`/є Щ§aŸЙс‰ёјaњapН VqЋБHП'lTџхkувс#ьЙЪЭ6GЬБц‡œ|/ўœ§.+Qˆ—ъ]xОjрЁйjГaрш–suыќ“„„ЃС­V‹їїnb>ЫєЂПЋїu{БBїУXйXWWOЏ6Оcіi[ŠЂ(јкГЯСuGR( ЩэєѓхЏь_Nк‰њG}Њ7bЉЭ0 Љ^Ћ‘ie2˜лэ‘ŽРŠB]єъ7a…НGTљвЇ1ЗHЋѓdеОрхJ’„ a, jw:4[Ь№K‡ПˆJЛЌг…аX…Жі4њЭЫббxы іoТЈдH–В-eЬBIž<'Ч §^њcшЖкЃ’TвKpbDzƒЖЎЄ**ІГ tUƒІjdK\ыz =]їFП‹іmіЗP…у GИ№w^Я~МSі)ЩР|ОР7ПёЃЌ|šЊAQUИЎЛ‘P)p:eWЉTТСб!К"fјAмV)WPX№+іХцЪi1žN —tЈJ SrЭfk3pгОx У0‡вU0GX,иййAЊW›ѕй?Пт "paЙX йŒей\зEЕR’k+єъ­ич a ],БxT?№ „@Љ“ЏІкЙыpЂT 9EА †‰ц1Ip%еA?gџа*xЗ В$ @?ЋУ =`Daмќnє{p–чЪnД:Ж…q•% žЫжkЕ6hYh5šь{>"ŽВhiн>сCDBS"\xўYЬ–˜JЙФїэНѓХП„/+л aФ+ЈžŽиjЕтV!ЩРЙУ3јkс':rT mOШD€ќЯ/ОЅс”]пƒТCe/ѕЈіvР‰sЮ–6№*ЂŽ…—яzщќнЈ%Ќцq?l–cхфЋЬ ЋУK‚0žNбюtт^э(Тh4С№hI‘VarпEћЧ6{OоLEdЎЂ№8"ЌЖЊ>јБOб*>ћчŸEЙT†Ђ(Y~ЫВmД[эLТ2E'dЈ BJNVЬ €rЌ}K>š:э<пЫ1БШэsМu~–ГйvarЎыЂ\.A’<пЫ`7Eћь^A’d ‡#tкБјuŠчѕ}ЭF3ЇIЋћйё9‚ЅдGЃбDъ13lЧFЗг…aЋ•Тœ%-{iš†ёx Uг kZц˜І‰VЛ зsWapLыЁD"сi[&šЭ&"Ž@ yˆŸЙђгИдМ…Іпэ­ ! Л2М ~мі"чуѕK7№Ч“g ‡њжkЩчUМ'!кЪCФёД;m,c:ГИBЬs@/сНЏ~ юЕЗ>„$р3pEGѓЋХhШђ 5{ЈЄЇ-IА, A$8† ~p„_ОіЫшuz›8тЕq dЦ“ЏuёѓзvбŸB‘d05EЖ˜a?"”ЫхBїuВ‘QN)G!і% зџ v›M0G+˜єv3жiNЧЄ@Œž/Š"”J:ћ‡hжsХКX“иЦѕ+7ркnaќnк?Жл *4RЁ.E…†ђ<†Іеёє}я€}тЁ№е+зpук:nJfР ‚я{%™u]…mлY.™ ƒP|,E’1 аnЕСE˜;ЎƒvГ У0˜вЄ#ЅВ<R.—qpxˆvЋЭEˆaБ4АглeciффЋр<‘0ШШВŒљ|ЪŠ"C‹ХЏГ"šcл\ЋзEaАэK’cАу8№=ѕz=ƒБ !иu]ЈšЦŠЊРqђ1‰э9Ѓ№JЏ:›rЋ™с}гм"GA€ZЕЫЖ9Ч Џк‡aН\тƒўaЊyœQwIBТt9ХqєwЛ€№Хj…ŽHwKЬ€сr`!€ј™кŸрк№TЁЦ<=—јv)’ Ы49 #$ЭфYєр8.*х „рy^aLrшX 3;kNбyэ[М4ЭДзŽ†аKxxrї…чрBАШшЬC/ИЄІjмєбэt‹cwътзцПЪшf^ Џсbm’№[o<і9\(,w—uОrиЕ:@ЎВІ”тB tМјрћИzњ.Ј…‡d<&*•*˜‘ACГЕšУ6“ЙцТs]дыдIHšЃC,ц Оyу gрwеўq+{Бe,‡а:ђЂиЩб2яŒНQт‹ŸћЭ&K’(nц$ Kc‰^g‡'kџр|0boIе4ŒЦCж4ЉЌфšv.WЋU@™}ёќEaУ4зЊеuёkŽ’p$E™PЮ’$ёx? ГNјДh1Q)Ч… b"›ˆ`ZЗZM—zм‰_@йтаДXГD"СiхЙ8ў6лx|яqќˆєУˆЊoр^“џ)ЁТўиC*L”mB,..№Œћ'š!HЄЪЂЋc++2њƒzн'tfœ>t-`юЖлАl‹7МG]UT<ќђslь_G”lђiDс3ѓљVЇОќg<;м‹*2рyВЦtf<ЁЉЇ:а”qе–б’?rѕ‡№tљ}€Ж œ0hџѕ—яц^-сРHdqШЬ%EBзwpеvQ‰%NЙфРчзk] |нZQсс”†ЭЎы еjСВ-.DP\\‡ВЂ`0Яg™'™ЮЛМмЪ^luК в’ХяХ4РБЅђoЛ}ЬGїЪЫЏb<œ нjQ‘зЎ8‰]ЯЅf3&HpИ Sr цц(Т|Б vЋ V’Nи4АгыaБ\Є цTi.оe…†ƒ!к­vЮЕОТvEА, fƒ‚ @EDЙ—ˆ(ŠЈ\.у№№€F"ДЮ+зcmЊ–+ Aфљ^‘т‹@’š/’<gd‚љЙЄ|†•rvžHјрbLs0L­VkuLђ™І…^w‹ХВ,gm$YAˆгЩэv;gЈFŽНdСX8 ќ­ƒПНЋYŸ3Ц:"ˆGE7Ѓе™ŒIфG@јГњЇшѕљeH‘D+љб8пFуёКЎAQ*ВЖп'YQ ( ЅH†3љЊ†{G{а_‘,з ‘%Югё @xL&ŠО№)X$ХЙ@ЮЕ`’{EГХэV›VAIO(BВ, џщрW€нФ ЄB‡ЂТјпоxœІГНДbS\7ф…ю*ы8шїсVj… "ЗЅФ +щх‡žЦЮ™Г)уjџЧAЙT‚ Ђд ,ОO–dXІ(bT+Uт@8cZ„Х|N‡7ує‡ JлOон§ужіb=oА’/,АЉаŠЬ&­h|ЇэАу8јвчПŒгgN#ХЛ7ўЂWЏе…Qі4LŸЊІa0 ^Џsњ„Za)Hмф  $ Ѕr™Э8љŸ=EСt6eI(—+)гsž{MžКQ!№CЎзА,‹‹ъ^В,Гcлp=ыѕzJфЃРЎ>^švК=6LЃ˜XOк$ix•ЊŸ­v‰†epЇн†у:+(bНєњшД;иh(xЉЈЂ(А“P8=Ž"ЧХ MгЁiZaLr{"‚!L\8И #јF 0yZ & эh№ $ЅЋZ„˜юъ~№?ЕЎхВ MaУљ|†vЋЫ8Ў„sйCŽ—K;Н6-#gЩaF(з9ТН/}ѓAŸ)ЮršиKzЪ8dFЃЄѓoМ€йЫпBЈЈйН‰ЃЃбЋх*Eс"Y>€ С Zр‘НG№cј+@ iŒ›р™ѓХЇА{гaг1 ЊЊЂg“nZ­ИUAзuЈ7$ЄЂ`ѕЊ§jЎ'ір:эLг ]з1 Iе4Є њu8[:ОщїДUЈ›h-K’DЖmСѓ\Tkе•s^З/ЮШyД _љЋ=FаХUмФѕънUИXНM†—†єЬт_CёЈЊŠСpˆVГYh|]§ќbn,Љp“ыКhЕ[0—KH•:}хЋdя_Cј1ЙcA[v§МˆˆМ0Ф}Е Ÿy‹щЊЊв|>HЁ™Д†žЁт:€I4СOПѕS8п>Oа€Ї_нСru‡і‡"бр еюЙчDqч:NђŠ’[­ж`к6‹9zн^њ№1эOЗЗcЙŒ7еNBˆЄgяіT$IА;нކŒЦc”ЫшБLц‰Ž"xž‹FНcЙФb1G†hХUП_OЈT*аK%˜– У4pjїєЩЧ5еЁ№t:V€хФРйцY”ИtbF#4#ќз‡ПŽŸг~ў%$љ= '{y:РџSљчxџќi\Є‹‚PKМП“IЌsбl411-œ9И‚кхч1˜ЯAВ‚08!Ѕ <т.ё_њ ?љГєащэ ”dDтd!‚mлќ{јяДП‹џѕЅ'Бьп@FˆNШFРЎLxхрЇwкxщЎKшмs?4"кр‹Пх=v]zKУРb1Gр‡инiнйњ#Ц|6Cџ`€{юПˆaўёяРўqR{yЅŠSїI!м”ЛPЅ oЬХšђЗе^0Š№Тѓ/сТ]т*чЩ){ВЇ]ЛеAПˆ^Ї—mwђ УНn ЕFћзіБглA†wФb‡JšЫБqўьYЌW/O4ЉBFЏзУССюКы"YКуcpaЇЛƒ§ƒ›8}ъ4jЕZ&ИtтMДpЕ|fЫD}З†E0ПЃC,yŽhууъЧ№ќѕР/D™ІХœЧмŸуыѕЏуœwяКАU ыЖїŒГэ:іi$й€ЌlBѕŽV‚aŸПј„ЬЧqѕјЧDчЙH$ож1<ЯУ+/О‚яџ№suФBa‰ох§GоHфпЂќЖ§-„oЗ}ZЩ4–ўќп§.нs†Ѓ!R„Т,ŽRЉŒ§›{hЗZАXО2Э#žєЅ( &г)(ъ‚ DЙTКЃsIu{-лЦnoГљќŽX– =nИv§:.^ИˆЃAнvaоСfЁZЉткѕkиннM{њŽЙлWjMЎсђџР[Џ\№"ХХ щф&„Р^љ>\P.@яъ0-3ƒ%оЩf.„wю`їќ.†ƒсVEРлIЅRС›зЎb№єGqOџЌЫЏ€ЌьIS%ЯGэв§XммU*ЈOGp“~К“D‚k,№­GСН—№ˆє§ўјїслnН"r|ѓєНXмћ(.дЊ†h–šіљЄkGзu\Нz.\Фh4:Ž ї–/]/сЦыЇ8:`чtaњёnя?тV&З›ПDЗ& {Ліi’ИT)у+_њ Y‚"Ыhд›Xly нъ٘ТЬLЫ„чyаѕDŠ!џV{^=a>›ЁZЋЂеhСJкFŽ/IёЦЙ GC”єЪ•*"ŽВv‘EVАOЯ/SЌQвuЈŠŠZЅ†ЅБм oЖиgh UУl>CФtMCГй„‘Ж€d@ч-і…яjЈтЕГ—ёo” „НJ;rМљ bэЋј;ЈOыјqѓu jUУв5р„\vсF.<ірђъїт—:€BиьC.)аUšЎСВЌЌEg§ќЗ>$ѕ†УtIРЌдpєСЃ\)ЧШ4I Hвж/,IСp[]œжшЦo. ЈЛЇAŠ hњ‰ОdAИrъ.LпћДТЏ>ќ}а/м'ї9‡тЙHD8"o=ђ4u КІB<пл:зЖKEqлЫp€RЉUеP­UБ4Œ;Z’$aБ\ тŠ"cџк>Ђ0BЮ,ѓэп?оŽ}ŽВЯмTlI+р†WT‡‹Ыхлg@з5>илЧхW_ЧЮЮ.І‰jЕТBјљ•‹Px Q E–1ИгюРu]VВ"У-р?oeŸўЎ\*sџЈFНС@ЌQбnЕБ,т„З}~>X–e,%@ЕZ…i,БЛГЫЩ†~kћЌ0Cц<ЯƒihЗл0Ь%'­ œWї6эзЁLBЄšЧ]6- хR‰IЪ0Бy“єК=ˆŒCTх*~ћпfь(УRiOŠЕsS<яЖhЃH…ъ?zѕ#ИЏsГТ0M;НЬsШB.4Ї>ѕЁТ !'ЛЎƒfЃ‹а7лАlЛ@ŠДн9„‹У(Фb9GЋгƒ7ŸткХ‡˜ю{Rеžжцo.ЎЅсаpсЬiLoюЁ]*qЩXр&$ЈК^кbŸ@в3Ќ Р —Cїє–Сћ!Ž~ш\VdpЎ5Ћціiї„ЖmМpї#\Кx Ea˜мэvaK^ЃЭп2џž?YцИХЪDЋеfУXЂ^ЏsјХ6šлЏ?EЦx4фNЛƒ x2™тцо!dYGќй?о†}FŠVиRЉ€ІІ|-Ќ•сW}аo‹=‰6ѓЅЯ?‹JЅšAЎ\зЃvГЫ2s(Wj•sуХЅmM‹=!$*—Ы+№6г0ђ6‚"—[]xmЖm‘ы:ЈзcЉMЫЖPЏе†…aооАbŸЩD&evYЦp8Є^L?&p JЅ ЫВЈРKWvEIшKƒСݘN QQh6›”RfхЃлЇуEiš†ёt UUЁkZвЊ`SЏлХ Ъ„6эгЖvаЦŸоџЇx1x‰0IюпyР{гƒVбVВ1HFьмиЁџ?Q!fОяCQhЊлБ‹-§ЉvKyKлXTUХббuн“ Р`4ъu2:ГМ%‰i-L"ц˜Ёy4ЁRЎ@‘ec/ §џJѕ[л`в‘–ˆhj;Јž> нЖрY&"КЗRТ[{{Pvv)ЁЉкДЇ\œYМи;рё žЬљаЖ№ж™K„Чо9№)%lиАOJшуjЅ…ƒї~€zѕЂx\‰ˆPЉT“y’ѓnЎŸц)гh4BНV‡H`uP+‡—ЛўЂ("]з1Ёi%РВ{X. Iа фЛИџˆЭVСЭў;k5|{іЩ„Ф[o^СўўMtЛёФBРql”J%(рз„ Š Ї„ёd‚n™РЬ‚ѕF}…ЋяV.|Šжh6[EUœЧлйСb1ƒœ$ЙЗ‡9BЙTТp8@Й\Юб1г šЭVšЖjЛ ›ƒe›УеJ5k65-ѕj afэ4Yh_ ёJпEfГ9К9‹ќР‡ BЙT‚ L8QKXйНˆ G2–Е%ўЩљпI‰ bоe№‹ qFФэ)лВб„$€9№“GŸDЙU,Щ:%эV'aзюoё<€˜Ј`6›A’Є ”вN5ъuўŠЗВ5еc § Д™K\mB№фїCЮ *Ж†Y…А4Їк-G‡ ТѕœЃ/M–hЖлрЕœdQЬX&Šбџ :%=g™!E!ОНЇ?‚rsѕTуРБjІуzјжЅGб>sJ2~щ˜4-xž‹( зђэ›aЋуКpѕz=ЛЫВPЉФœŠщИоj§ЅY‹Х­oŸ}цl:CџцHаZоюнлФцпЗГ)lђvоћяiŸЮ1п №ЙЯ|žwwvвŠQКБaиэюbчЌИШjТЋ‹ƒGуЪхJІ\ŸЧN$=? yc­2ГЊЈ0–KDsЕZЭА)B‘ж5Жeё:Я^*Ѕ*I2\ЯcУ0бjЖr|,ќ0`f V­sJ4ЪЙ‹ЮШљЌШ ŽњюvЛEˆ XЖХНn‹х2ЪSююЇуЏiЁйhl„€†iЂнюРЖЌœЉ$Л–и>DШuЎуŸ?є/0X ‘c1CmР7|(ІTСˆжюsBiEžи{пЯЅzЉˆе†я{‚PЉTйЖьdLвћГТ’ ŽCљЮ›‹ы{мщД‘В№ФЙ@*І%€}и?фuД† bл\тЭЇ?†ЪЉ3œАєЌGУ,ˆАB.Ÿ:y6†чfŸАёиXŠЂ№p4DЅZCЊœ'}мИЖ‡йdЮЩœчw{џY Qш"мцB|lsAпЎ=bМя7ўђы№<*• "–uмGQˆŒz­г4)g5YѕТ(Єхrv\Е*авЧO-зqбэthbЦ!l!BD$I†ЃzнnЪтБТцbлЕкm˜ЖE+йŒ$lщ”dŒЧcЊзуP"=ПOІi йlч{й“–‘‡' Ж•Ц“4]#UUsR‚xОOŠЊ@UИЎ›.№"_ХOu‡<Яۘ‘3ŠŽ„-Чї}Длm,bBЬ<мb&C 4кыэсŸѕўи"5ЂЬ…Iяu‡НAДEQ22gs‘y.у“CЙ[ЁУ•ћ’ЂLZ­&йЎƒ"лHzНq‚О„сhH•јGйиЅdЖCЅRLIЫLT€љ)ŠBЫХ`ЂrЉœуЈ“С'пG_б1ћЁ18™G)к >™ЫR•юЊV0 ђ–Jћ>O#ЄЗFSшЭqц}eЪнŸЋ-ь?є$vыqŸ\zбЬ !Щd/fИіФRЉЗ ќ4пF У9Щ’„‰эрѕЧ­J "Й)b#ЁџЂzНŽ„™(SB†”‰ЁЂ“IŒdI;%Šы/уЃJЅ ЫЖѓ?e}uБWэ{Y–…VГЙe§ЎcуЦе=J„ь(k‹y—іq\р-9БЗџњmлЫВŒбp„Џэ›инйENЉ№д&иЖ…fГзu7B“0 Ёыz,*оnЏV{sv8Ў]K›“ф?Š9EСd:ІыЋЭŸ9пМ„*ОZЎРIкkŠч*Klз…э:h4yџVI`лNLPА\ф!ШC‰ b’V{kх™ˆ`™КLЫЬШвsMЙўŽŽbОРЩ‘Ќты8Ј”+ИXhŠЧ,а%џч{ў/р1ZcГ“шОэCЊ@%Га5>ўжЧ№pѕ=JЋ}iEТИадJ=И•{(%4ї†БI2PWг4БГГ“uЄSž з<O2ЌnёQЬ3[ЛІ+<љО‡!ЅOЪГ`шG8sіЌУ§­­~сLЅ:СP‰ЋЉ\`J`јњЅGбИыnh”ы#'|ћZГ|š,e$HЮ32 МxЯЃрЛ@Г\٘ЩSш\–КIЦd>ŸCJВ‹уЇHІй\[Ÿг„˜uНеlСqllаAзtіћЋьDXZxОб`„УНC(ŠŒw{џйШ)ѕЉ УЛТњ=о7cь;ЕЂŠЊр+_ў*TE…ЊЊ™Јј:ј<І˜ђбjЕ`ЫњwMг`K„Qœ++nДЦАт8N‚‰57n˜ЮІшДѓ‰@Д6)I`Й\ЂнщТЖmНЛИhЁрЈпЧNŒїн †OУ>Mгb•ЛЄUЁИЧcT*еь:iEъ>ёxУЬъе:–†‘Н7nuб1›MОРrОaPўрL5KМ„e%?J@9(уЅ /уsъчbИšt‹™чИ№^і ѕдŒхXbдћuќїGЁwu D1\YdЖэ ZЉ€9Ъ7yzC‰ћкђP~ыИІyЇj­ +@Jхt]pšMг6,‰аЄРЧ2ќ8Jх28› лїС­ZТZ. њ1T№Fœ(Т}%—Qюэ)љ.jрѕZЦ{?€ЖЊФз%PЦв˜сЭKя…vщAPЦ@OЅ*Ў>јv9Ъ‰А™Уѕ|Š$CзѕьЁЎ)UU1žФЭтCŸЖŒ+3PЏж3НšЂžЬl>ƒФКыŠѓ%„€e[8ияУЖ рнйЖфWEђXћФ1јIэу‚ЊЊИqmЏПњvwwqe^Б/ЖqXЖ…jЕЪ‰ЗPр3# G#tкœvœђЯO:СѕуЖMг8Q^C…а4G§>7MЌф(жsP šы:мщvБX,Š8<›Э!Ч4ї+ЬЛлB–ыv˜†™ъp:aMгфvМѓf?Щ 'џu1эVœЗ! ˜'“ :.GIУtБ!_AлБQ*•!IRF‹qФT&ќУ{џ'ЦB r{+0 ЊGЬЏДCЙ’ ў‰ŸРЙі9@Т]zъсЇСŸэ8шvzX.œ X’$иЎЯѓPЏзЙШk—Еad5$JhЯšьЦHЮ™JBЬчsN dМо‘о!А˜ё›}ŠЅ$‡)˜‘рЛNТђ№&ƒж%ИРЙ€5ИЌШh9Пюx('Lу2˜gAˆ—ћЗлэѕ\6џ3О>f‡Œ›њQT*„Iž#№}ўЫЛBэв§(‰|nЎЏŸtNYŽfЋЗ dPƒР‡БŒ[ЌŠcВОўв4EЃйр ц|‹“ЩV›sЧam§хьK<бп?b!6Вaяаў“ф9™)cWШZт6‚ф;ЁШtB)їчлЖ'$ /~ю‹iиZфч‰Э‰}Ё|яи6uZІAHDšчЫdI&]зsІ–‚=ъу‚-– t;]2’|ЂЊЈpl›<ЯЅ„N)х“ЪьsV‰8ЗтК.UJЅŒU9UFMЦшД;”ф`ЖкЇу ЈT*0m“ˆ(–Щ<:ЄnЗKQmџ|dуIi{MГй г4cApEІщtBzЉLJвZA™ŒкЊ}Кр cIН^–Ц nаЅ?НїпаОЗO˜7 Г(2Е„ ь€Т~E( tёЦњ˜јD]dLощчЏфqc†jђ<В"“^вЩЖmJл^њ§>ѕz;E {$ЉЋф=TЋжШВ,""hЊJУбЕZНиЕaŸЕŠAЦrIз?ј#TЉж 1гТuЁэž"Х\ТЕmB*ђБцф<(`аХŠNG§#„­QAИНМ{|пУдPеœ-:љќ•љŸœ›Kм8}7}”0 % ёІ^ЅљS?HMA”ВД№цњIзяћ$ JЙL‰xiЊFGƒкэжЪ|8n§ЙŽ“JЩЎШdЊšJЊІЛўRЉMпїАу&™K“„$rі wjџIоГ!Š„ˆŠ—оЖ}\ФЋЏ\цс`Ћˆ<T­ •кдsp\ЊІB–xžЧ’$0Œаэvйї§\ўrнОр|p"йjЖxБ\ЦЙВсЗђ|лŠРFЮИ“ЪIЦO4ЫБЙеncΘЃ\*ѓpx„J9іЄŠ•ь[й l˜&ЧeEQАXЬA жu!‡лэ Ў`JџU­Ф@a!Š"žЭfшДл„aоГf_V„aШ1Oa с<фIs‚пН№{qгГŠXEZl л–д†ЗД_зц•6ь~ ŠяВЁъИаjУBUˆЄ™V Ў "H„јчd]*’„ГёЦtЮ­v‡уЪуBЗRЮ›œ їgeўЇњܘл6я?љ!дк1‰ыЫ|ѕF“%!esvУОаи/„рхrчё\В$УА ‚хrхФыЯNщѓ%ЯD˜MЇшД;ьћ~>~ЧЌПˆ#,–Kь]?`A"gг~ЇіŸфKоьž&.ДдІq#gtрШžoХ†Š;ВO\(XІ…/|і 8{іK’TЄЇув˜BЖmЛн а?ьsЗн†ІШБ[~ћєџяЁYЏТsL†P$ zУ  b6ЗЁˆ’Wрhж+\-—1`›Ÿ;žЂ0„вБŸ_мˆe0KžL'tњдi AЗЕлb$и–^w‡'г MЦcьєvX" $hЋ}J<гњ М€;ЭЭ—sќў#Руƒ10C9ўѓW~Ў‚Мз<рјщЩћ№ˆєW›•˜І*‘"8ж (Š Ш зы5Ьf3ž/ц8sц, LЗЕIHьЙ.К.†й|†нннBеёіqњ’сЛ_{яqїѓ_EGXGЁ}BФŠ,бб|ФЭZƒ"Ž˜6UD иl#@Ађw“Іh84ћќ Е_$ёЂ`&&аёŸПўsЌЉ§ƒоoђSђћШ–ь;ВO.‡„,ѓh0 rЅ‚rЉЬЩ<9™}2ўD‚І‹)d!qНZ qщфіˆ"ŠЊ5œџЪŸГѓЉ?ТеЅEтNь“ь‹\ЎW§вЏ“ж; чeOhŸчЮeз&уъы=-Юцн}ŽVšЭІe•kЕ*Ђž“Џ?EСpMSqxиЧх—/cБXчКЃыЄю[KЦn1ЃмxxЈ IDATЊ9*§–§УИўЭ! Ђэ0Ќ-іYgRUЏƒЊ,_u[“нЦ>§]ЖƒџхЧ_ІПsяЙIФ‰э‰€ 03JtКмФХГ„йє&IB9йч'wB–@оTСєL?кz–0CN‹vћьg Р аЮkQї.!Xщы;о>oЁР№ќѕшŒqЎцо‘='›ДЊ*dй6ц‹9*•*ёfAˆnѓ3‰DuojYd&ЏвКпо>Š"(Ѕ9эГє‰ћрSoМ Ъ=ЎлкЇџИ r5•Ÿ{ѓ2МP77ѓcч?1“Їjќ№рŒЋoсfћ4*YoЛ~8) я Ѓб˜,Гvчы/Š j:LЫ BмfC›|ћЧЮ_f†ЂЊtэњ5ДкMм}пErИ!ў;ГџlкЫ”ŠП#SQЂшЬu*QQи;'LЋV'ЕЇЌЫ§Ћ_zQЁгщpрTЎTLюл>ArЌЎƒн№ˆюь€~cwСТ’[=вц%ЈС‚Йї^r/•KпЯцp@ˆhnoBaiОGећ?„џі_МС?љŸЅšx nЈqFm}œ}LіA“y€zы"†7щђ•*Юѕˆmл "q[ћt‘/цCН—Оп№ёѓ:?г^С,но>›7H…OwYzN&љ)™ƒН€2•ЖуьгŸ %ц0§п/ўПќ!џCдjЗђ]Зѓр˜™KЅ]ПqOяž"г2!IЫšv"ћдPU•Чу1iЊŠFЃЩОяЁ\YЁн:жKа8d;6І!ѓбћ?ŠЮхc\zЮы1@‚#H:?qњОіщgрџвЏЃY.#ZІ9vў‡ nЊ2ПњО№еч`Оfіўко4HВь:ћЮ}kю{uWялLЯ ` ‚‚@SAŠ š!йцb‹ZlKІ%QЖl…УAгŠ№K" -EHAJ2iJИС $Р`0Р`6 fэщЅКЊВВrЯЗ/їјЧ[ђeVVwѕPЎˆŠZКЯЋЬћюЙяоsОХ?ё0ƒŒ ZЉЂоh0Ї6­МљЕlЬ?]зSєƒрF­FЉœgрч“фOŠбdЫВiАˆSл[Ќzж(ћџa§9/V[ЏЋ/rеVn3]ѕAтГ…ыіЭл|ѓЦMдыuЎзы№|qхЭ‹Ь$<3p–Ь)Ѕ'4e!‡ŽKќXљџ1ЎЗZc "СDD‚sС‡Dl3…$зc†іаmIдKcюїЇ`ъBbЪ^я†xЄё‰YvŒ™SCЗ]cбщучїЈ—Lf­x:кИрuЙ*сгЃМ"'beЌѕДА@ЧФeА40І€і˜9”М8НРПВƒ)е8`ЩRІї“Y&о`™4/Є”œй,цsŽe EеИQoцИM)хr^ЌХs6Wвy"ЅФl>cУ4бЈзрљ^ЯЧФgѓ—Ъ<<ѓяр‘Шр(й{3g4Бхћ”’ЅdАЊсблЏ#Мё&Ÿѕ,TŸџ Ol;БI)Мцуђ'[єУ0dЫЖa–Lnжыp=яDёйgR­‘G\2Mдju„a”a^ѓFзІПŸНЧ8ŽЁЊ*яїћhЕš˜NЇиЛЛPСЊі?№њГ)^l*‹Єц{”СяŸu>IОќЅgАuъТ0K™Vн% ž‹<Ќ;уššˆ”†‰’i`ЖXрЏœяхHЦKyкДчєшыЂZ)УЅиKpG;(щJжАп_ьЦ4јрк6мQше№›ЏОўЮ5ДЋЩrEќБШЫКє‡ŒK—Юa>нAЃ^B՘bgŸašU0ЫgЕЪ?[–х1ц ‰vч ћ№B‰яЛ уgоЌЇ)ajмы#ƒФЛ€б6р ИФЏЧаZZfк}ќG6V. щ‚VьиОдљ^=|Œ%тшФЭ:uрЄ‚Сс!z<п…nаužяхмMёE{г4бппGЛеNœгТzіRеdc|‘7ЌЊ*fѓMEЉ\†3ужг?ˆjЋQзв7В|nfiЦ;~Д:Јq„Хt‚3:.|яŒnпRсем-Žб?шЏH”"!а‹œyљYŒ,­V—оz і›Џ!H:а9Ÿ|=В#Ђ”1LГ„Н§=єКн$џЈTЪpXЬНѓ/WUг0Q)—Ёыzf”…љ|Ѕ пtЮ§ЕuиЖ)cдk5a€нл{АGИёџAжŸутШa%е{ЫЩф8>*GГ)>ЫеrЙŒ—Оѓ lЫІj Ѕ…mЁеl# Уўo:‘ ~хT$УSвvo“”NDxŒv№cWF‰–ЌЪю,Ы$Т”юђІ§D]бщ-№шЉЕGXќЎ $„ x3ЊœК ЧZ28ЊщПє4 Ѓ•тT6h=ߘ†Ђ€f ењh4EИBаУW+иЙЛ‹ˆz š˜yM;c‰gRЦXxMє:ж|HЬЂ ќyЏLWЌP>š_rt +‘jЋŽЗP3 PTŠ@7Aи*ар6ХgЅ~‡H<$ ћ2љw D#њ]љћA&уП2.Щn–Wф”FуLг$]з!Hаb1GЇг†ыИyтSмWЂЂу8Ц*• 13\зEНVG,уœуWШ ёV EСh4Є^З—А5ТЛхЭ?њC(qLKz[žљц$СЯIЬ!hћє6мў Xешz`A§ЮзiFЙбѓљŒvvv0—“>ŽI”Jxќ{п„нпKIОdїˆWT‹ХЯуylѕЖђС$р86ЖК=ЬцГЬ‘~žŸсдdŒRњkЖšK*–ытПмКƒzЏ ФМЖƒ[Н•fnL`пIўoГpЦ˜ьЊ‘ъzPsЫтЈш Ѓƒx>DЪkЈјїoПљЪаЌ(Ы]ЦКj3טYelѕъ№Ќ=Ѕ46 \Н ёН7ІЈ7ЖРНщ.Wз§!утХ p;9Ѓ"`рњEѕFшˆхтEЧTаf€rUA4–’V!€3@МCg0юS ЃjР/ћР0}МЦ€Ј <ЗѕО1y"Eхœф §ЦKОoKLg3ДкKОЏ”qЃ^ЏУZ‘3ЫФ0—5яl< гюЌp^mЧFІ Ѓ,Eѓј -лAж|‡E\{[O~к•ыaАvЖЪЩ ы(ŠЁєNЁтXp B(НF_~жлo JяYbуPBН^GЅКЌS†F —GћhОѕ*Ќљ"ч7ЋКŽї w`?џ BЁ"7*~vnŠFGЦФMйHѓљ ŠЂЎ!, љ—ђэwћћhЇзШ^ЃуКhЕкЙ_4ў~іZVљі#†‘P_3ЅЅ0РсС‡§сQž№q§9.^акЖ oTЌЁWLцв35хВ їŽ—RВaјцГЯƒ@0SЅм"эKзuКЯѓŽШй WyжрzТ0фZЕ–гОˆN,pVіёŸ]pjФёQЪLКщUЃ9Ѓ}Сh—!ФђX,#p§ЫСл0ыэМЩДOImм1Дэ‡й$‹_†uЦ…Sјя?ї4ЄКЭЬQў>WpP‚АpbnД.@сCdRЂdSбmiќŒЪ0 э/™аФ–эУ(Ÿ…Ёк|+WЛŽc†Ќџ„fр#ƒ*Ѓƒт"ИJe“€*UDЇ#`œѓ}гкaм’LЗ Ђ%Žяz<%aeЂ ~,fме d\ўўoИ wIёЂdлХД.g6@НVƒBJЎМал4ы DQ˜Ы™хkзR“uнРl6ƒЊЈ\2 S šЊГЁыp\—‹b ШНф“4№і<ѕZeОS$(aР}ХРфщ?С%C_хНІ"‚ цаpЉзхI"Хк§Xт‰Z…ѕЏ§[иQ”k6šf‰Џ\О‚FН‘д(IР$ц‡пzГНbуРgulП№ ћЛ).1oxцѓ$уЩІaBзu.Ъ]aг4ЁЈ*ќР+дрVѓЯаuXж,™+х2d †!"”J%v]7opфзYе!фЩ8Nе|K‹wяь"№ƒ\1§Кўм+^ЌPщж0+ јњ&†”N6Цgђи§Н>ОћЪkп7w‡**MД[8…ZФ:с=Ћ Е[э#Ч|M­?гЙ л0+ЋOBf@бgˆк™‡с,fЩБuE‡JŽeAї'€^:ђLЩ.*<жРо"SXў?’DФџњп‡­^ЬётžеЙ8Цмiтд–ŽХќ”QГн.ŸcмК3†Ђѕђ]`q\$ט,tlmuрлwZu%ŠЦ™‹*~юN†Љ%ЧкuНH РPWїуЃЕ> `№ї|шБ–0B6Qf€zEEш†€ЛaЇ)€Ћwё™ёoCќНЇŒAРq\4#ŸL)‡зїаiw3щЎхё3cж"`4Ёлэц;ШbЭqtкmxU!Z™ЯЩУЖ?шЃеn­xadт ёlŠЗћФ#я‡8"œšИ§Л/РтЅЂO #!№ОајњБ27љ)Ъм !Р`LІгФЯ9 .^Ыu]Д[эAкѕќЉL\'U[ЪФC–ѓuЄкxG@pй$S’X -ЃжY-Б8!ГD’Є€э!ўтЅ`4€Dћlљx(™:­x15 ZŽPН‡рн§.*ЕfZ фх"G Ѕwоh/Y@‹шЂфшмЊтяћCxsџ"%Y(KŠТ8Хи>{sДЄхЛЁ(bДšp0Ta–ЪЙЂo2Љ ЎЃR=S#ќ•›oЦŸlыјБнаFЖƒ[6>Рlš№L/9Жj€ €Ћ@ќR u[MŽЦ:$ „ тГ1p7Н.ЏMT2ыїъПw7`#Ÿ‰T8^эя'ЮRЦk‰‘$Вяћ(™&TE…яћ+‚TŒУс•J%ЏЈчHyлхR žчх DёP5XіЬ@ЕREМЎЄœтшІѓ9v?іITЪ%p-}DЃ^ЅŽЖЎТ A,LCЮе*ињж3rћJ)%Р‰жl]SpљхЏcššWбRј6™'RЂ]ЉрвпСьэзСЉЫ_Qц>ƒНДš­ЃЙ›ўПTш†n,ЧЄКІ%ЛjUЭUИГЭKё}~€fj@VЌћe>=žы"№дjЕеюЌtC$р>vюм…чњЙнУ{ZNА~mицXС•"тктКЂИsl<3ЉšŠЗоx‹юооЩѕз(s0Sё ВX,аэtШ|ШЄћ™“о5UУ`0@‘ _pНЩ3ъƒ>YК\2h9ѓ ˜Сш^„3/ >Y<_ф$AMIп Еоb™l?АGЈm_!Чq€Ш#QpšЯ† вbxЪ#є‹_њ0ŒRƒВУї#"ѕ4šŸB25 *uЮ“#uБфКTЭЧ‰Ў‰пxёiњЦыЈ— q,IQ€СHрЬ™SАчw—ЯЅLЭb•ЕUQшtЧСЛ;Ъх!a|`0 qњЬE„ю-еЫјь{fІ ПЊуgПW#œ!P^нД-"Š__ZщЏЂ$€3 љЎ„ZR—˜@d–Lxuшƒ œс#(IB јУц—ёнйkPЄ‰6Œ Тd<ІЬ––(WТђ6%&JQEQ :|пЯд_P*•hЏП—љѓIŸѕЃ–‚ДКнЭЙp*щК†бp„’YЂT/пš3ЏЂЯВшіS?€ђіYp’A5тжlFk$OШb5%ŽщT­†3/}ГлФ)'>дtкvgиzуEš/Yэ6є&H(WkИ~ы{dНѕ]DщЂ‘ˆ”ДаG')Нgў  аl4aйVžІiтр zНNХлІќCжhкъQкhЪЛђ–mЈЄ@єL­;{-Хћ†!ѕw0Ÿ-HQ•LіфыЯ з/A4є7Њ)ЌќћŠ‘љ& ~рRЩФѓпxžзCц­‘еЙ и".lЃйѕ\дk5H–œn‘9Œ"иЖfЃЩщЮ№HќрHp"рaьђŸЙJ-хOŽ zГ;К Е ‚DKР`Z”Oы…mаєkхZrœіflv/ТБцРВ3{4>Ї2 €г—љП§ЬG шЇahЬГyˆzы eŠ pABY*XаъX&> рKчJ8иП 'ьА †Dткu з3 %эзlіC`ЩPk„?Oe~r\7Sи‹Ѕ"z(bОЩ™Џ/o№qуь˜ •"hwЕф8b.8~8яsŒжэ1 Х30"џ2ј—pm‚kš–Р^J%6ємИžWРЌ…1!"X–…­юlзa"‚Ўы˜Эg $ &Ѕ\YЎŠEёьсћЪf™‰Т0Ш гљэЅЎнбјТ5(№yЇвФєCŸ@Iг˜ЅФЮmmёbBQŠs~“•.ЧŠ‚їSёьиі*ІЎjќјып‚}ч]ŽГ oŽgfqŒ‡un}ёї0›N)йHЌЉZ&Rzџќs]ды DQФqbJЦA$њŠЦRkѓ˜ќi9KU46MЖы2Aе4 œžрŠ5Y.6'ђq%bЫВpћЦmNСз'^d§JhKDТ:otѕЉЕд[9ЌЧЇDišLІxщ;/гжжVVxЅLZМИSф5)\лБаыіh:›ЂR.г`аЯъ‡KПƒЕјЬ#!‘˜"L,—~К{э^“`MQйКЯ Q˜BVh)џ[ќ>aX2&4Яa1: В"Ј”4 J]Hk”,Ђ8.žRy”@/OŸЦПxс 4ыL–[ЦVЏ зоƒЊj9‘@$Ё$DR_ЪK=bОLxуэ е›Їа?Œpfћ4|g7нAmŽЯдў… „ЬИ|^ЅП|Їj(РPScBŒlЄ|gGМuЦ AИИ7\шzЂњЌЕ5ŠŒ˜PюŸќ>E(Џ_|_? ƒ ’,1›'ИВ‚œR6—ђЇvqУЬ№|V‹‹TUУh8ЂVГ•ueЯ$ГRЋSъt:˜/Ј”+ДЛПfЃЙR~йŸћё цSм|ђЃа.\!зїPн>ƒВ=ЇШїоž%‘'НW"џJ$Јзъxфэ—hqу-z —'}ЊПљ2œ $UQ@иŸЬДtˆ5•žŠ,xЯ‘a ЩtJ­VыФљ—р%tЛ]šЯf(—ЪдяяЃеj%œф_VІh7лp]‡t=Їо‘ЂˆѕкюjќRЮŒТ8Ф№pL‡§!д•нјНзŸYПдД@Вф‚ЎЈЉЄ›Tф•>‚|=&^гT~с[/RЙTсFНžOяЫ%€djJ^Џеi8БЂh `2Ž‹В?їфrЦЌё6Цєг—Я№?ъwhЎvРћЗ9йЪ„'т’Ё|žg7^&эдeˆъY^ьМDўoџ\N"FUЁПѕћуG.ПЬеš‰ХlжмOVО“pIЄЉР|кчлwЯує™‹PбЧtк‡PдsI=ј“ˆ№э |ёr пєЗРШюЯ vG№J\ќ3>у Bё еDрп§U|xў.%КжИŠŠЌАЬD_яЧ%MY'еъ)о[ьгd2FЅZхFНAQbП/•ФRrЅTІZ­†бxЬšЊRЇгŒ%ЇАїхbы š’‚йЧ„љЮ-:­7odМиqЙ}"\PРo}ў_Spэ\ќюs|kч.…’‹ІЦїЭMjћЋ=є‡gЮQГбрJЙBБ|€ќ“ŒZ­ЦЖэасhШІaR+aОpVD> —[зuj7лlYEQ„эгЇ™@ыли{Юп( љpя:нUхdЙџњsвѕ‹lkЦ‡…V›ŸХmdОЃяЁ( &“)~яГП‡sgЮ!am$Ÿп“ЅЉююэЂлщ@SетбшОёй.ЁЄ+ј?пicч­W ЌаNПь *pњоšФ‡o2)1N|(№ь ўч~ћŸхЏМИ EаЩ_D~ЅЭf5ѓ}ѓ…ФQ~kOx‚f]7шз>`њќbЊ*Ÿь™ц3)ІŠњЋaК3охЭ№˜уo-A$цRŸМў'a\6бkt—А—“ФgOr•яKœys[лНcНžIИќЊІaoэfzб ыё$EеuєžћоњюЋиs|ЈŠ8q|jHQ№ƒ=‚кы/сѕйЊЂnђє9~ъAЗp>ў)ŒŸќN7›я)џ’AQTш†>йфЮo‘P)XXshЊ -EŒ?ш5Р€mDLЈ7лq|ђkdјC’ИэјЦџ1ьЩa2БрЕАЂ!žЂнЌтŸќя№№щ}8^Q7ђўB–Ыpƒ*Оќ‚TžBйŒ!љфГ‰С €`А•T”ЋЅм„ъФУ",‘СvmДz-ШX>а5$K4Д^ _У/ў№/aМ3~€д*pІкРџђПƒЧХерAŸ*щгВ-ЈŠ’Юз›kŠЂ@ВD „ОюškнI>"к†ŽG>§OQ‹‹)$=иcRcЦ8№Х С•ыPЂhен№оƒa-ЌФИKSшЬ€šŽ 3УБmTkЕю†ї~цf%ћ‰?MSS6ŽЌ?яe§RqD.'їаL1іФ…/ЁюљE9Гdв+/НŠ§=ОvэѕИšr1qBALf†i˜ќЮЗщ‘Gс§§}Jl@P“СfЩ лЗoѓіі6ЙЎ‹0ŠYгєеЮя}ф” УфƒУCК ;ј™эCў'ћ!тhFLЪ Р‚слT­–0vkќ7?ї4>ѓ_ќЙж‰ХЮЩŽРŠТищ—љ§OTQт_ЇipZ­™ŠHžLSJцJЙ„яо Ы—.c2C’dETРну›ю†Aћ§>j•ъе:ЛK3'єd–T2JL8kхŸыџUњ%ёП'Ъг NrN9В€ЉŽшWчџПвњeЎT*TPЙОЏ -3sЩ4щэярњCзyЏП—ЬЕхўD‚Д†aрюнЛмщu)RDОЯFтq2A[)!ЊuўаkЯвpчо.WјaŽЩžЯY(Ђ8&ЧЮXё=zцђ“pЎ>Ц—::8шsЕR}рќ3toмМA^”яюэQЕR-˜YLж4 Кyћ6Ÿ?w–DтЛУjтŸ|тќ3 “ћƒ>iІ[ямц‡ПD)Зxm§y/ы—ШˆЪ99=Э^zQRdЌљэPŒ'ф{>ОѕoЃйjQКћ#зu2€gбД9‘Зт•пQ'|п§ў>%2;15[-ЬfГМЕlМLŠАRJEаt6O_Ÿ‚fГ……e“dFKФ’I‰HJŠ%#–Œ8fŠуфћ(–$№ƒ€‹9иЌуЯ4oгіЉZЂŸ5;’ЎCВEДФІр!3 IDATЄТ‡в:ULщїпќ0Оzћ14JЉ“)DPR™-…–šU )вu‡1ЮžНHŽнЧЎл№fЧ$% a –’XЪD,c’ƒYsђ;Уашp4HžЂ2FЋеТl>#& ’b))–‘Œۘ"ŽГЬО"’1ЎчРvlшІ†j­JЎя"ˆBФ,)љЧХЧЩŒXЦOЦˆZ}ъцуЁж5РHб/JкPЩО `х{@hF ТpxљџясчШˆЄi-%ЅЛьыВ‰QŸ2R˜HЅRE‡дmw1N!‘ЭЕcуГ9MDА-Q‘ЎjhTыАm‹dCЦqrфђўdŸRІџЧ№…Š3“}ЊМёДтЃС!E­.TU%H`0B!RфŸB)Щя 4AtW1АћОяGSЄ(ЊІ‘—bOšІibwѕ&Т(Ђі{Ш?EQh’XВ‘@Ћй‚e-(c„/WЧе|Вm•jƒў€ЦУIa7ККўМ—ѕK–TБA”~Пвк]Ё~RŽšрœцbОѕЭo'Њѕ:ц‹9zнь”^SДlЯхnx•ЖЃЊ*\Я…ыЙh6›p]%ГЁ*‚ Ј­П4ПЂ*‡шvК Ф!…Gd<ХMёХК˜FbДоjЕ1AuёзЎNRc)ŠА|Ckѕ‡LvЫBЅй…2"{œОˆ?ї[ƒbžIH›Б ХЃЏуPГЈW#8жНNœ“бїŽ˜ulЊЁПŸІt(?№ (*Ь’Й“MЏ…—№РLLsПпGЗлEEˆЂѕzcENщHХŠБЪжа4 GC”Ыe˜К‰ХbџЉџЗ-€bкLЕ+–Б€`œ1ˆ8 ќVѓ_уЦ№4R ЙЕљV!‚ €у8h5[p]†™ї26Ф‘љZќKqаў пO9еJ nvут3ё0ТcoП ыіM„$№АЎрЅў!ЊнNю'Lыї• лEч8xљ}E§ь9”t‹ХН^ЖcЏввю“Žу C4ъuИЎ‹’Y(1­?iў %NwЛ]xžEе`šЅ9ГћхŸaшњ9ѕn>Ÿую­]D)wš–­ўїМ~‰5Ђ?чR‰'/Пbщ4МФ`rцљЉЊ*&у1Пє—y{{a23sр{мiwxΘC( QД$IЏc5MуУбzƒГw]—Л­6lЧЮЩёGтSœ”’Kf УУ!—L“u]g"bлЖбjЕ9хцdќb|qEWU•-{С’%W+ffŒ|тыят/3X]6зѓЇJ6~йX›XЖ.q0оMTu‰н№§ќ+Я<†г]slVNю.‚ы™Щ3KхNЗЯОЫ Rj|§rˆОРгЙЫ9Д јD"bBђўЄ”\*•08pЕR…ЊЈL 8ЎЭэV›=п[bВ ї:Eрd —0 ƒ'гi*2`лŽХz=“3лŸсї2œfЧl-hЗл,ЅdG8И|ї2~TљQ–Y06[љ\т%X 5D#Ц]0дa\љгюg˜mDjA—ў§"Ж1шКЮ‡‡‡мЈ7в {2з:э.lЧцьc=>эdf6M“‡C”Ьt-™k–mЁнnВяyЧЦчѓ—™#UХ•ЩЪЏ~‹(f&BCзXŸOИ/ 6t}퇙 –@ Ю.№ёnч Џ=ЩJQ":ЫQrЋбф…eAQ”ћцŸЊj|8ЂеlqfJч>wкч_ЙTЦAџ€ЋЕ:+ŠТBЖm v‡л9qўЭчsƒЫхr*Ќ*љ№`€сСˆSž0џQз/БJІ[-ўy л+H€ЈЯ~э9”Ьвs>„№|•r‚ќ.>Aжу)Єфщ!ъѕzNЈ‚ЪOqSk-“S Ђ–•=чџЧї=ДЭ59ЅЃo0[а‡CДлэ|їЂanЛјљѓwZШ‹мДJФІ”хТиК g6ЪК €КбЦ/}эћБ??Šoчи з ЁшgP+-р{s‰Z­Šы^kН0ЙЧж "й{ё<Ў“ьЊЃ8!aOШXЂZ­ЎьрhУ§ЩvT“ЩЖF"gfликкТ|>_•3[ћHйшЇrJљЮƒ‹`ПАѓГ0zЦёžqYЕЧ”mШ‹rс1аОв~ЏO_‡ЮъБЏ#SВa”ЬЕŒ—†!Ei–VИЦwч”h1Z‹EfыZ`™xшvЛX,ЧО$jЩЈъ:zхы˜Ьf žРESЧ;{ћаК=p­ЕГ @(,aЧЏ\{?zЇЗ! ;\ЯѓвцCœr ЯП„o?˜sЖFv ]7…œ0иx .фя{pНUћQfFЧGxТЧц_zJшД;+Ї?№БsыnО–№Fсу“Џ_bIЪЋмЙ rСx{EД%f&Mгpѓц-М§ц;дыі(чІџю>ЅJ™0"-щZЫКŠЎЋtpаЇ^Џ—[ EQШЖЌ”Лщ П|AЦ2ћYг4†Јеk”ЏgЯѓ<Њ%“Ё(ˆЙ2t’%™†‰ёxDšІQЩ,QV;!yуявŸНЂJ1­Œ|Ц;Ž|*—LŠЭХж$Џ‚dJЧOрћтгT)UIЪ8яO\­HU˜ЦГ2]8пЂљє.H(љћCЦ#W4Њ‹ЯeYIŒVДЛ2џqв4ж9˜)™žlЧІVГ?йЏФgЦмRJ2 ЃёLГЃ`~MD„!TE…ЎTФЬуГ{lЭg30ƒRЖFnкэ’‹гƒгє_y‰аD$ЈАч[ЁеiRЃшr мхръܘ§ шŸћПлvI!%ћлХљ–дe5‡ƒъvКTшж‘e-аыvЩq*Œу Н/™Џ ‡#*WЪ(ац‚  ’Y"Da­Ь{aуr•{їUрн7њ^ўЗbfЊj*5nx!UjuШ”[›KLІХ-%Š№ђж%№ћ?DuM)ф_2ŸУ  vВ№м#џEQixxxLў-аыіШZX)}sўЉЊFƒс!šц‘ќs›ъѕ:Ђ(Ь\шŽцŸ”dš% Щ4M2 ЃDБ”ЧД{kŠЊPXяызКnЏ‚Ј€/ym›’x(_ћЪзащt8#Fg{эЬCд4KŠр`m˜mMгФp8fУ43‘D.˜Ћ$žЕQ”xC8+’ц\аcпїсКѕЦ*•)нrлŽ­оц‹9дЅљQ†Dg…]ЖщlЦvQq&IŸђ€`9.~ВЛƒFЗФtt|„ђflєЎТЌJ1ЃДUСЏО№4о_сŠžьŠуЋ(рУQ€vї"ЧA,c‰]@Ё˜јР#}^LОŠ RsZT‘"•хmH)QIђKˆ@2&ŽуЂзыa6ŸБКФЬЙ…ˆЂѓХ‚;щЎК є‘hѕy.кэЌ ђЃl.Ї$ЦЩ2ЇT%И,hЄa љ'vўl5{ЊdШЕљЇ1шQBДeŠ6МBк37.Мч†ЯБk+єЇь{]г1™LЁi: УHOЪДђZ?рvЛљbС…Gбd~Рvn5[й’sРЎы люВe-VєёВќ‰=wЮgПїmЬІsЂr‘"H‰‹%Шf *Џрпˆ ёAсцSCл4)Wž—jьИ.Ъх2ˆˆзwyў•L ‡.W*PЬэJўХЉ7IЃбШkŠыљЇ(*ЛŽ(ŒPЋеrЭЦ‚: {ž›в.ХW9C033'‚И‹9Зš­#љ—щюнн‡5З (тНЏ_XWƒ9‚,eЛ ЫТЗž[[ЇВХi!9q”bдЊu8Ž“?]’юБ‰сpˆZЕ–j3kЛЂ0Рp}Нnч‹ХЪџ5 “ЩЊЊqІWЖ,А;‘ЎчrЋе‚чЙЋ;*!Fл–fRчтД{ЭEГ…ˆћЖФзnђйгѕTp"э KЩ{Ы№'ћœhІs€ ХZf JќеСїсГЏ<ŒN} MSТdЦшneпйЭЌ—[Д/%Гd_uСоРvЫ‰ ŠP*•x8кэ6,лттюNUИžЫA О”SЪу—OџDГеlБ—њKаrЧТD„ёd‚^в=f.>‘ Mє!Fќб[пжŸJv|Y[‰саU‚мM…ж[жiƒ˜_žугуOГЙђt,“фрpРеjŠЂ,=‚—–љ{ђ}Ÿлэщ.0+Mгx:ŸDœŠ ,ѕ•Йг›Ђ(ьик­6{й-0sЄjИАqћЕocюИKqW^JЃG œЋ–йРЎЅвќщЎI‹BО[kcяњSиЊVђ]шzўGqВcЎV*yўeЭ г4p88@в Чц3# BДк-^ђ/?С†0 ƒ 2їyўi]ЯхvГзqХЮБBŸ=зAЃби˜йuУ(Тр`ШЃС8‡Х<Шњ•Ё…-‡Yk˜ ›Ш\ЯmE"“jвTњі7_@фЈV+yЅHt.~uЭf~рчDsUUсљnE(hˆ­з*rAL]гIг4јОOљгŒ“Щэд(i=~эiG ,ІJžчA@ЧЩr:NV`>Ÿ]D!Ж'є?^;ЪэdEхжзB?гЄ•JV~Нˆа:ƒŸћмG€Гˆ (‚\7+лhжrœI‚\…хЕЌ8–шДKxєќ ˜M_ГJDEQ†!Эк­х˜џ~Ёо?№Ё* JЅ1Iеuєѓš|ЇSМ/kЅљ ЈгnУВЌ|.”L“ƒ4\vj=>›‡,˜lлТ/ьќM`‰PƒСДŽ–8жMrЭAкPрNbzРч:ŸЇ7FoС€NЙЅ‚ыСuJO+t*”двт?™†Эа(kˆЄJJН5h}чW”t@БŒ!!ЉVЏУqœЄDDІ"№иыпЄљўюъЎ=ејKVтdоGЬtЭTёЪо>ъЇN!C"м0ЄњZg/@/9mЪПЄыя еjТѓН4џ$iЊлЖиKЃqпќs=•R™H$y”х_KЬцГх_Є**JЅ Йž—;&љЗOнхiec|Vƒ\X кНГ‹0SИŸx§Ъє›6)_•W5ѕWŽ›Ќ( ЦУ1ПјтЫи>s&пђЂhЩVlлgO0DЋеЮлђšІap0@ЇнЩН\i-ОјTЮž v Žы$pMЧh2†Y*Aзѕ%Еo-ОX‹В, ­V‹]ЯЭ pцѓ9@Фх„еАмМJ‹яg‚ŸР|т’А0`АЯмМ„pВeй’_]@3№$ иїŸФпћђ“шДL„QШг…†3л[А;,DVЧ*Tz)ё5]Jf)xјr€ ў-ЖЧDZвEуZЕš?е‹?ѓІШо–Ђ(АЌЊзMгфЩd UUѓЮ‘xЌˆ‰Тu].—ЫЩб1ŽYžчsR—Э;ћGтГ!$и!чЮс?•–QOa/ž†јz м`ф­‘Ѓ•›зо;яёПq?РYMU1 аlЖ ѓ+П8зEaлЖбmuйvldЇ•бxФ%У„‘zkфiГПЌZ–ЭЭ*Ф`DКЩз†Лапx…н0Z)%3)ѕƒ–Ь\з4T\ЗB ZЉАxГyŠн'žFЫдЧК•:збќ‹ЅDI4MЖl ŠЂВЂЊ8<%rJШLШў§ќk2чцЮ?ЕѓSдш4€)ˆЎЂE”ˆљY"JЮIД””р €КDЯn?‹WfЏ Ž›|Ће*ЅѓuупgЮЧ„Т(РT­Tт8Тl6ЇVЛЯ5фѓ•Wц? нZЧqбэv0ЯбLз_њ*Ќщ”TE’BЌвOREс{"Ј"еКlЈшFаš-šD1ОїфGЉгj&k*ЃЯл#ПЯ2=•‚–Н@ЕR%N4іhaЭ(ЅeЃ{х_rŸОч“Њыа4Є”№}ЖcЃйhцjбїЫП$ЮЃNЋƒ…e„Рx2AЋйZvхя“Шѓ=мКq žч-зЌћЌ_KФ йжœW‰ŠЋŒtк@а+•Jєц›oу ŸџЎ\Йђ€$if]з(Š%fг)ІГ)Юž=›Ј^œь:ЉšЁZЉ`wяnвѕm4!L–ї(Jі RЉт`АЯѕ@BСЉ­­ћ‘иW(’ŒГ5я!ќЦ;tM и}#Q‹оќ~ŽRў…Œ<ќчџјЧП„ZЕŽ;ЗпШˆ>'T!тй+эџГХЇЖNAгД“ХcI­T*илпƒяљаtнNЇ8&ї•,RЂV­b4cБ˜ƒ™БН}IДћ2ќcŽБMg№џ\ћMќНр@i+ˆПgЂЋ'SHНH.МzџшЁˆНбЖЯlГЂ(ЄF"TЫeьюэ".•+”эfф:ЕrwІsўи­WЉєхЯЁ?cƒМлБ†"№цдт лЇixц2Ої'~WOuG$КРКЎS†XLטN'8юТ=ёŽ›ђH TJ<\\зEЋеJš[ ЦУ`TЪU р8tM[1В:IўI)aш.\:k^…яљЙцњњ•щШsм]—Ук@IІ‚Ю-Ўвўр‹р0ЬTЮHѓ,щLY ЈdGN:a|zјB`сЬС1Ѓ^kМ9ЅфQb:Ѓлi'вA„п€DяSТ†_zI`Жwˆ§{хшЦФЅŽПѓУ/Bк7`лсq.0Ч. ˆCДJЧvѓCа eгИо{I•@<пƒэиh7лыуzЂˆвJёp4DЇеYСЙž4>т5д№ГП€;;w 9кƒ-€$$"Š№7№ѓјбЪ"2c–R>˜Y‚‘ƒэZУЕЩT7ЪыW,Йїыџ€яю€5ІУЈћQDѕџ)ˆKзЁЎг0O№jˆˆKмŽ%”ReУ|OљЇ( fѓ)ЕDХ§ѓ/!.˜ЭfшЖЛ'yGђ/–1tMУ#O^GЛ“’6Ќ_)+8хТ$yЎRA-!їYГ ЩL2JеЋЏМЪяО{“jЕ:—Jf~lYы?ЧшLjRRR$уЄукlRёЬПјЅR‹СГљ ŠPШѓќb§сОёйЯКІca/(Žcьяћl$ѕ‡ЧЇЕ"`ќЕж!WЮUQ*5(‘dЇХƒ™‚ШФксч~њ­Oь ЁŸ,>ЛЁBe œЎљєЗЊШз.^€Щ“ХАƒ†iаt2eEUАяяУаѕ196>ћYQTŽуГљaТаˆэўё цVиФя^џ]МcОёA…‚~РPNŸЏЇокТвMћWj•ЂjЭ§у‘›Єгx2f]гр:kšJдQю9uEсI,qёкЃќ}Ѓ}ђРL§Ь6Ч "vƒѓV‹л/~•ъ_ўЖ&cP(џєРхg›ghёч~ž§ƒšѕкК`ё‰ђOзužNЇаtRŽяƒчŸЎaБHSЃ0т ќФљЇЊ*9Ž Щ?№‰eЭЗ#ыWСƒ‰3э,uУўvйЯOыЙ€ Ђ(Аm_§ЪГtхтeLg"kњŠоmxб++A"ЇДZЕNнn—-лBНZGЊZ{пјtёУt:%–рNЏ‹УУAж:Пo|іГЂ($eЬ‹……‡Џ]УЮю.*ЊZь о3>E™Гi–№ілoс‘GУўў>I RГа=уp, І œ>§2>ю|–>^њž ? њ‰œж}т“G…ДFшnŸЇўŒ№Лc_8ч“nT™WЧdc|Ž5V5r=a`ћєEъєЙRЎЌw;щ>‰MгpуЦzт‰'yчюUЫеЂ{и}уѕX‡лішзџŒвNЉ№ЭА д{Ф'cBФSцŸМѓ“dœ3aш%,sд— хїŒЯюЏa˜Д˜ЯкэƒU*х{=(ЬПФJS`опAџ>EЇn|СЮm"U]ƒ?џ3FP№№Љ-zўЛЏсТЅЫTѕ<c>Q<ƒЁА3НёЁТ%ГDeMaЯѕ3•чŸiš8’ІkмЌ71™N2њъх_ElYКv wїvQЉTЈPZ8QўшКŽЛЛЛшvЛ†д=еEFYЛ"9ВЏЏq)ю]ф ТМЫГ*\Тd4]У7Оў$ШNЛ‹љ|ОзЧEЦт* ’žяСs]˜ІJЅ‚8ŠFс}тWž%3&г *•24U…Њ*ЙGЎдБ_ќ7)%TEХ~ПfЃ‘дhj5иЖs,7Х:|Hu q08@Н^CиъuБXЬЁ*"чv1qъу€єћфп1Р:НxZќ"uП|њџB­Q“†HОоуS†!L]…gіСјЪь,^мƒRщбћГт БМ?8<ф@TГdТѕмЕklŽЯЦе4 ьяяЁнюРї|сt>]oŽЯš І4ёйGћг>PфM ЕЎ œKg:QјО№{fЦS;OсƒъS`“R KdО#ЋuяMфу "1PЉT ы:4M+няБL V{ћ{шЖš@Хр]ЫсSErР‘Яє­-ЂЕ3gA“1ЮpŒwћ(uzQ˜*žШхœ[ћDj&ЦŽƒ—.> этCPYЂVЉ!ŒТЕКлНѓOabОXР4L”Ых–Њ.8џT§ƒƒ””Ј5йЖНWŸ џ У@џ`FОрж;wб(јnf (,эjr9™t/СЋоWЩ~WUUёкЋЏуєі6a&p5]4 7щœ` :WЫћћ}юіЖE‡aˆfsE рH<хЕhЪЈУс!*• ыКЫЖащtгkˆu•b|пФКЎcnЭY@ЕZС|Б@ЋбD,—0н#ёйЦ Хк%Žu–…fГ… №Ѕ”RŽќOœGуSKbŽcBГ.qљд3˜ЭІˆДџрХз№ЇЋП ш[ -a3МЮ†Я•/@sыœљ˜@ч~уmА \(B№:д)Ћg6 Љ: ІГ9Ћš†RЙŒљbЮvnъьЕ|а­ЦsARIгtЮф”jЕЇrJœ™ЁЋёЫУМ+˜є&јЕоЏ3nh€CBйU27КU ЏWЇ’ЎА:WљO~­SM–Б„јh5šАэЅЊЩЪп_цyЁaЇ'А—R)‡JѕК=Nц|оŸЩqQƒч‹ynДЭЇ|ѓТCO|•uq^О Ѕj1XbQЊр\Ѕ„бс€лeъbЦЗ‰jЅ™™ŒoˆЯ0-ZуЎYХўцSѕ‚8ц(ŽQЋер8Ю‰ѓOе4†ЈзъЌiV"Лe-Ќ|Юп+џ$KN| ЇЌ* Jх2/ч0ŽЬђ{хŸяћp=zQb2™pw51ˆ/$Й(ї›Kъп•9™Ѕs27 GN&ŠР~с+дЈзЁ( 0зujж$RHк ѕЧT =жш:M&hšJІa9ŽCЕjDŒ(!ЏК'sДрТGˆЂˆЌ%Р—2Ѓ”knЕ IDATZЕJŽcoŒЇ “(• ‡CъvЛЇrюЎчRЏгЃй|†ДS˜Љ3“к€,Ѕ„ІitB<,‘ыЙдlДр'`г”›мцdј”€N “žИђmDо›ˆуˆЂˆЉvњ4ў›SПBлѕCДNЏyѕfя1Q6TјZƒфb”мгЄзх5њќывхфѕœŒŸЊ‘ƒ\™™FуѕК=Ф œ‚  NЋCѓљ )Й\ˆЧ№WQ †‡hЗ;Й€ыКдюДaФ0вdЭЧ6Ы–L5QУ/?ё { D „ \Т›!К\5šѓ9VОK!єƒя|‚žЌ=IвHЦЯu]*W*‰уXВKШяЭВNДiХqLГй,3х")%Ђ8ІFГ‰ХТЂ ”[ŒЯэ( УснN‡Є”PЄЄI(БѓсЂJЋErЉ˜B( y&№ вбaуќ…‹dяяЬф3№Pй С€dГ %ѓ…NMззH„$˜сG}чкSЈПHzтICЖmSГ‘PXу8Оgў1sт{ьфИ.ZIэž2}ОrЙLŽым7џЁ˜LЦдщtHЦ2eоxдjЗi>ŸŸ,џT•}jЗZ9@кѓ=КskжТЪХ­J+nži]ЕИЬ—ШŒš™Q*™xыЗбп?X‘˜Jшmѕе'ШЕnЙ•‚0žŒW$n„XXzнSШ Њs$|՘™_›І™%КнЂЗ(lлЪX&~b†ЂЬ 1йЃёхR)#~ч,У4 k:МљO…xvAšІСuDQ”tРRє{†‚P.ЏŠЏr‘Џ˜^'Œtlїf8еx–eƒ(!ВћсЉЋ>~ВєЫ@ѕ<™ка”3ё/†тЁ}ю№. ЄМcэsјЬ~ЃёŠљN-гТв уџуьНƒ|лВћЎяк'џrью›ѓЛwо}afоŒ&jфђйBи6bDйВD–БРџ@EсЁ)—ЫHPЖ‘Р XТ#5’&X“г›—юЛяІПNN‹?іIПюОїvЋЋК:юійkŸНзZпЯв ŒЦ#tZ­jТ*|пG­VƒШjчVшЛВ_ЈŠSвбBPЋеŠg†!4Uƒiš›}BењмђYvуОzсЋјœі9`”хіхлы^ ~!Ж„Ь$lІБцŸ*`<2№cс'!КbsЌ­зиккТКЎЄXмПišиппЫ‹Š—.œЌvu’&…цœЇUSЫхКЎС/} gћ;—Мј>h„шУлЖМ`М§!ъqwЕ’В9fшЊŠ~ртЎ жjVъŸaУQЪ hi‚зЌьЗП=Н”E !`л6†УсэЏЊЗплпУpАЉїuН^ЏHC9‰§еkѕ ћ“cЭ’‘сА2жŽБ?]гБЖэbU-ы„Шулы5=и+ЦхЯŽљШJф]О7[ђb"|ёs_Фp0(Q<йлŽNЛ8Ž‘"ъъХіj2™ЂnеIUеbрH%C Eд­њцDЪGaЋѕšˆ…Гв)™ЪЄJz9т’Ёl“ДX.аыUР ЙЪDОйрeВЅУ$с<QЫќyоR^ЯTQЌзktЛ=Ѕ&Жz-YџЅPUЗ/}юњrпqAjБКјЗ.§&ž3ПЈНjLВьXЁюѕС9x^„~іNЫ|>ЊРTЛŠџчUZъ#[Р6•И9/Š]cф?a€NЗЧq‹zЙе‰'џYQUŒ&c ‡Уь%Йй'§~ОяSщ‡ЁIGА@jџг3П<8яKlб("-вdКу>№—іНvЙхK”Р@Ѓб(Иt‡НwљФх8.т$AНVg>4dXј(Ь\7іz#—ЫbіˆLgsd+ШRVІ(p мзїСlq\P•7 œb-Tœнк†{АЪjc k=Зилл…пъVsC6ќ^ЯХьBžLЦмыіŠѓs™ZЯѓИaеX@љuдм$IиД,он{ФНN/ђUЄ;ФaВІы0Lƒ}пЏ#ї)АЎi<›ЯЁы:КФ))3!№sі:#"г33Ч‰‰ычpгј{Ў"Qм3#L_ПЄђЯv‰Qk3Š4!*бIЬFћ‚х"еђљ%1ЃГУџяtР/?œBY_R4I№5 {ћшїњљ3Ћxž‰}пgЫЊAе4Уp“њœ}oЦуnды9R‹Хп™™‘Ф1Зк-ЎЪgƒЄЮŸОўi~5z b.8Ыљ+)л ˜/0ЇпJYйQ€pc#\…Џ<М‚ї…яхzЗž5Ў Лиѓ=юД;ˆТГ”˜1š_–ІЊ|0кчAРUŸ,„`зsбЈ7˜Hp†yIЊф€MгтНн]юv:Ÿcйg" јAНУыїџL)Б‘$my,"іу„е­3lК+јŽУ…†!ЛЉ„™ŸбО3š НЕЭqхїУЊŠЩuј;žwљw5NВ{Юэ™BЋпэБяљЯ&ЗПJ0™NИп;оўзсvЋХ‰„Џnи_ bг4ywo—3Я†§р и0tшšЪAkšІёl6‡aZЌi—к6*ЎuЕZёУ{8I.вs*œќЊŽ “т*fŸIAЎывŸ~ёЫtюь™jQIнЯќ$qCгT˜–ЙФЬЁŒКЎгССzнNљ7Йк,|(iš"MjЕ;”‘&ŠaІLКІb>Ÿ“Ўы0MГ(x“ћёsпžјдыvсКNK, ˆаuЧA’Ідh4ђТ+йZ„Š{^Џ%ќ1?Цa ІPЭf3є{}Ъ‹З6МQТ€j–EŠ ’№*$9IЊPЭnœџзД^ю‘Rја(УЇ SЊ5шG.‘>\ћM‚qHуТ‡ЁMѕсr\ˆУrпДVL(Ј_Їпz3Fи”ћњrŸŽЎы$#љ@­V+ю'8РЄ( ­V+ ћВ”IГ(ї/ 4IaЏз”чtц@мм“Щ Љгъ Nтˆ щЗ‚’јЭ€~ѕђЏоR-ЭЫ†_ J#Rі BR&еB€р€~lџ“loQ‚„JПt)ЂO’„в$EГе"зuIжМ•gЩсЏГХ‚4M'г4Ы>Љј3э4 њВГШe~ЎlеKAрSдl6KHiп@D/чtчіїРИrˆЃт^‰ˆTf .їЛДип‡Ш|яХХQТLC– ьВ КeW`ЃjšаD5шЮГяЦ™~?ї7БПЬ­Eѕzз‘~КТўRв5 “й”,Ы‚žIрŽЕ?пЇžд Б?C7hmЏСЬTГjGь/ѓЭгjЕЦpИEŽыcmг‡ ЂљbО/” АšrJ{4›ЮЉPў№> T [\ цB{У4№‡№Y6tšІ—xšœёUЉ ЗŽ]Ў еѓ$av3_YЃбфMFX .BРqnЗZ`N b‘Ф#ѓљŒ}YŒ'/(CYЉЦт 0LЊІsцG(ЂRЊЂ№xNŽ“№Ъљіp6Ÿё /И•АaЕ~т8– гШгbЇЈiя лыm>пŠK'лЖё8eь№‡аАLNВбЈ< #œЙt‰ЃёОŒђ БХ@ŽсŠЎроtЦдщПЯрЛќ•KЗ`^Л…š(сР‡эOQиЎЭНnQ!I‹qЩB$iŠе*ƒчxўЧи_ЭЊA…Г@SaŠЂ№xXюшv]tк]„Q˜•5”ЧpqЃйllєIuЈ•}@зЭ"—N#6АЛН‡_яќАŸJ~ŒŠ˜@ЄЦPї5 #ѕОЌ20>Бњ8jУтШ8Ћ ’ос {]D4MУd2FЋQqЇсVŸЖmc0JИnFзU]жPЏURTŽkB ulМuё&’л/A‹#€QУЏЗаг8ГЈxБапxТŒ–a@_-АЯ+“–œт.iП§ƒ˜в'и_ўё_–Аtь юA–ЏїЄ|Ч|.XлЅ§)ŠRФц‹9tMкУэ~VY2MФYАEг4ИЎ‹0Œаl4žh9dyЕZсб§=h93№PlсШXТ?њУ?сVЋ Mг6&кВrUбAАНnŸЃP&Vš†ЩгЩІnˆ›|v>м>ПЊ И^ЋK8i$1FОpјhЕZGAЇ‡ѓB2Шc'шД;œЅa5ЃНdб+Ўчpћ*ГлэТvmVЊІa4>@Зл=bYTŠ‹œ•Їдыѕ8ЄiЂ^Иі'ЙwЦ‰‚IEЅŸЃ№c' `ѕwјчЯќ=œkю0€ШуzГ&ЇюЊ |Tr ‹…~žІЉИЃмФo}wƒЂТO8›K}… },PГbшu{М^ЏЁ* +ЊŠбx‚~vŒ27ёш8ЩІы:мыѕсљЧЃЎжё?Мэ—]@Ф‚+Ѓђp5ЕŒ@ЦРХgнвeр#>qџуИеКХТ'yx.‹NГЎP%[Bnйvt:‡€И›ёйbќШФъvGпT•…"0–С –2o?0VpŠЙрб{? Г?d%I0Kч/œч`Зz– X2№’7[5МўрЃз‡Ъ Чїљ[яјк;gYЫГПŒыsЃйˆу˜EчyE!šц‰ь/IЄ`4 Ж3p*ˆ0Ю0A‹Їк_ЪŒ09+ЂФB(Њ‚бx„мYХЁgЙЏvяс./—ыЂX[žфH9‹Њ‰вв/„W^~{дэv ФMžі’ћг4ЅЪяРЬ№}Ÿкэ<ЯƒiΘЃгщPЧЈcЃ§Ё•!­ж+YЃbЙ$ЫЊaoКn5ѕцHћ”Ыkзsбl43џЄFћћh4љVі‰эѓЈЁчyd& ŠЂЎу ‰cдыuJRб;Ж}х>у8F’$TЋеА\ЇИtvŒ­і7)№жаu•P@UŠ( HUЫяeЦгѓ7M|Њё_­+PмвvžЗUЎEі•” С•}/џ&Ÿnї~kдЧ§§1u5<кнЅ\SмЧЁыЏ~%"ШњuщgIRZ.PCзQ?кОњŒУ("!†Ў“Ож№…Ћ_РWЭЏ3 5Sк@[хи+›Ш+ј ^phоoтЃоG@mЂ<=у№љЋу7їAкЖAПеjЋVУЃG(gS>Љ}ЕxЗыzШ|Ъ "šLe™L)ЕL‹Шяqэ‹нcуnџЂпMqAнкF')АзаT•((RхїЄ  ‚& мLЏЏtuМвађъГшХЩЩьС”з^,—d™&ііїЈпя?жўЕ?ЧEЛеІ ЁjэяэЁнjsЬIьЯѕ\Њ[50I;Гз6РВ,љŒгЇлЬц к{АWнБE‘Ш]Џв* BšЄєџєџцfЃЫД№ѓёbz0ZЭ=|єу(†Іiшv вѓSлƒ€4MЉQo№|1—R;AиокІ$Nžо>ћ9eщ/|ѓХAа™Њ'цDbxUQ1™MЩЖm>ГГ“Л>Б˜>х­V‡оКїфЅ?‚=§=јAL‚.“хŸ$ЦLщў}›џЮ§П—гяEшЮ)žэ2­Кh{˜^U9N№ёэ‡јљящ“уzМГ#ƒ[tB1=аф›”і–.O—6Ю=GЂтї}š^::AнF—_п}џХџKz§;Џ^сІ~š^ўЌ€Х›щ{Sњ[Џ§>Ђ˜еŽ NјdэГ’ FƒцЫVЋ%Ж‡л'1BЦ}/яDкbrbМ>Н}іu:›Т0L„aDVћФэРw]UЃgFwБPƒёQ^рЦЏLOЃ8Тє™№ебŒіЕ”Эи'џœ$Р•+ФчЏBKbИQxzћ›NHзuA€fЃ™л№ЉэOз DQЫДрўŸйўbщRЃPІ.вў ‹A}Gа{Пї=ФAЁHRi“A„0q§™kјъWО†хr EUpxZЪ)jVM–д5Œ'#Љ HOM…š9з‰ ѓхЕCХgNt U…ы{ˆуnш8СG–6'1л"ФI ЋSWšV И8ј<ТРФїс{ўуxЧ~h"AшЉј;Ÿ§$T„рЫяAтЏГJp'Л'Eb/ХOЕО„TќуWZРz$їVЇљHb@Ж”mќнѓ_Cвєt§šІ)„.`|QЧЭOмФЋѕWeEЗг|(lрОќ§P“щІežjœЄY53лЖ‘І ЋjVэTЯ8Ы„ЛX mдqс_ў3LЇгЂабЩŽ!k@S\ЙљїќnнИ…4 N>NВxЊeеАwА%№0™E0 уДаbф_X,—0 ує}ЂipВ`Ъb1GЪщЉцjТ\’&p]ЏHКІSиN^цњ3W7'DЕъ4€$I`YНяяхЯџбчбэnWQžŸtгžчRЪЬgwЮbwя,гТУGG"щКЮoьясљлЯусюCК™+p‹'lюОu/\‚эк F.G:QћЌВэ>BЃоФ… ]LЇ3t:]N“фIрFЊ€$Є5МчЙoРФwёэ7RДъ=ŽМ тЉэЅ‘кš№ѕРя-џд—…­gkwЙЙ~ќv-;Ž‚A№об|ƒ?Иmт+ё%ќёЫ @СбТBЧѕCFœН1wЯМ ЃНПћV‚Ÿy‚H˜`NŸмОœќ8ЋЮ†VиТпИџзё ц$#Р*oo№˜x0 јњћё^BџjюC/k'jŸO КЎcЗoпЦУ‡`‰#‰'_У01ЛЃѓ1ња’ілџ'D2Ъ‚іЊИяzиЙuжУ{дќќЇйо9‡Ж* …еЩьO‡=<‡Я_ИˆнН]XжЩэ/ЯчSU…їіёмsЯуў§{0-ksKѓфw>$ыo2уђ•ЋŠE(аM§TіWЋеqџС}єК]hZ­аl699Y’§13ЎпИ†ўж ŸЫИІRе)sEУ ФgЎSЇз%л– ™уГH„N9нјЬ…џB'h6Ф`ВЌ9ŽS$T>­}’ІTГ,<кнЅ~ЏOŽыа зЇеj‘'V>О}*‘ЁЎh6Ÿ“Њj”І ѕК=r2PBцh=О}Z:hЁчЙ!™ІA†aЪ$чР'FvЎcкЫКђwQЌаАcгvы Єˆ€ъЦ Гžе”чМШZsЙj[dЄы эњхщNXпAмЙFЩўwHяlIБyхйїy’ЪпTBвжпФх чhцЦєsчКCЇtшџ7лчЧPB йn“/j!шw&zewBК(Ÿa~џyП§œ9И…E­m›ИЫєž{яЁw›я"д@‚eˆ+кјЄт{й=:#аЙŸ t@q”PГбЬЧвcЮ_ПљЇišДЛПGнn‡\зЅ­с6Э х§гО8v6N–Ы4€о|лЛ`\{†Dх…[ђч+ВЧ,?d} уˆЈЗEu€Ќ$ЦезОIі[oPžСvrћ#šL&h5[™–EЎч=еў ›HS2M=Ђ~@žуа ?Єх"ЗПєщіЧ)TUЅЩtF†aPЧдяѕЩЖэgѓTћSTВ›’8&UUЩВ,ŠуQ іOГП8ŽЉQoа…Ыч7Ÿ6п‰ЃИЎЂ`6ПћНя‚ј\Yr–‰В•6”с” Урщl ]зQГjМ^Џ1шие‰=д%N)УыиHтˆ›Э&Т0dUгЁщ|пч#9aХѕs™Сёl6Х`ач8Ž‘Ф1кэЏЋкЭCэѓD№<D3tŒFмыїРЬ№<{Ноa№У‘і”Ї*p ЁжјіЕ?EшОЧIјжЕ^ПsPЗСHsyTЕg7rе…ысяўŒћЫѓŒ`4†pW+‰ ш&obЖ шТ!иžРѕєuўЁ dДрХŒ‹ё.~ъJРP[@Z!nŸ'_3Б­ њWсMwMEа|†уѕОГ†”ЉђFвiu|хo^]г1бj6aЈЯ~qїo[(фŒУЪѕВОrІї§7§.Е.ГaR“ощpXњ# ъхЦXЉЌќD–тZ­6Т0dEЈYЯaЂулчуЗЈЌ*2эe0dіуї}?[Еz™\ŽM|з‚›3&lэls8кCРРmKeу~Жу0ЪBцOД?г0y<!CwёjНТА7`пѓ6вWŽГПќrtM—еnды№ƒ€ ]‡ЊЊ‚€7„ЦЧи_5ЏfɘЃпp†`š­&;Ž#эŸlbMS1pі}ŸeэjчDіЧЬP•Џ\П‚fЋQ$ЕчЙ”Ь2#Ж’ЁЩ”зg У.\83чЮPх„Tжц.—ЛH)-Kєe=ЩфY‡ЖЖ†X.3œR–sSmOœ’ЊЊ˜L'шvЛ…|Шs њ}И%NщHћLЕ@išЂVЗppp@­f ŠШа]ž‡vГEi’–тщ,šTвЫИPНЉЊJЋхB(”oсƒ г0ЁЈ ‚ŒVqЄ}_d —ЖїЉg}Y2…R&\Ϙр;ЏЌбjРœ:FЕєЏьзšтбWіЎтз&џрм%!Kvu."zє-XЭ!‰Ћ‘ТBYUkгVлWЁЕšEћЃДMHй–@К^рЭkЯ“Иq JšP —и zфЈш0ŽЁєЗа‰Вm ‚jжш…й.–пќ2ХЊ^тВž`qгzm#O]ЫJXв`аЯI/”г€gŠ*}ЉНnЏД?ЯCџ„іЧiŠZЭ’ЉkВ_Iзuаmw(ŒТТї$ћгt•Ы4UЃœЂућ>YІ•ч?бўђtЌNЇCч/žEХЈЂЫф4ФЧХQrlRјРїОOж3-пьTЮ!œвСZ­V‘UŸ+DLн„ЂHbѓFМКP–ˆЉеj™U&k”ˆЉLњжhдсКђ R[|с<№сК№}?/є\МjќРGЏзƒуVо Д)ЩWВLц)к.’Нзы5vЖЖaлkЈФi,, IDATJЅ5#Ij&сжХ/Т^оЯaHS`ЋЏУsї1_7ЁчUл]eŠиs№_нћE,з&,хЂ=MЋ?U Л ЋЏр ЧqЋ#|Јљ&^м2`дšEс?hћј™+6`ЕPЌєpL‚ {с|OЂ­™СIl=ƒјэŽcŠžjснФ0LYIlи MИx‹xOНљг G“Њ+’нџЮmC*xЛЅщаtžя•ѕv+8ЅœЎЉы:–ѓ9 I%ЯŒ2J$-К‘!оЪcф՘ЫЫR0ˆрК.Zэ’Ђ#ˆ0w\<ј№'aЕк@Q3Л$*фу^p@ .Xь>,ълЦ.жk8ѓЙпУlД/}‰OВ?9™Ѓнmo8љƒ0€eе Вў)•›і—rV_{6ƒЎщЈ Т(„Ђ(А2:И?ošІСЖDq‚F†‰Ы—ia!г ?еў„˜NЇШ  %КЫББНuwиўђРЧ3Зo@ееMх •[-‘™Z>@*E•)sBжј…oc6›)Аo1DіVїŸГКхv`Я—oЧuЊКШbƒRlI„рёt‚Сp€(ЃZ`EQ$-Ігу м|ƒTВъ‘‘"0™NбщД‹-P~Щ9е DqTtнГeе0И^ЏCUUЎЊ$)Ђ8FЇнСк^Wю“7 ЧЉСз/м‡E_b?H*Q}љ0/#МvgЭpšЦ9ІЛ*Šф†цр_м{џюњ'ч @heчЧ!аЙwї5Yˆœ”RS˜“/˜VИ–Œё§1jэ!ч№зМЯ<№ЧwpэLрLkZнТ2Ы\Cg„кжUЖ—s9йцЮ}ЙЯРCѓY|њЕ [Jъc:$м2 ƒgѓTM•ЦiЁџŒ)†КPљoЌ~"y(ЄВ%†мКд?jPБХЬЗxОяЁзэТsН’Sйn–“”Рt6CПпчjЩOAвШznІоIЙВъ)ЦZО‚GмnЕшRЩwљ­цёKяg-›T+ ‰r0ѕžПZЬЧQЁEOхлЯХ_ќW№dр#і—ы—}пCFмjД6мM fпїбы=йў2Д/ єz§ЪvQкŸm;шїњьyўыА§ЉŠŠЩd"еRЬх˜Сѓ=Ўзр”‘ыЏГПZ­†§§nЩВ \AчI•IšЂеlхщБі—$ Ÿ=wНAѓВЁерЧІncндіХqŒч^|ѕІ|вЁаK’$—Пo;*ЖгPU–aТї}*@”•“™†ёxŒfНYВќ­Nƒ0@Зл#gУЉНВU›эиHтЭ†,г—ЏZKјуšЖЗЖА.єŸР'3TEAp™Р]М)3-&љО”хEqTж†­ ,f лJqуЬgАZ-rвEбЉQЬі (<ХxnТ0,№ЁкК’`ЇхƒЩнФЁ‡zНШш ]OЄњЄдюœЭmќ“G}м;˜BS*лМъC&Т|>Ї^З_1"i )X$ ќрНяЧГжГHЭTn…+ƒ’І ŸќE4њuˆLг\*ФфXS2НЗј9_Б2Іщ˜ЮЇАjВ*dG :žяa8уЄв6ЇЌ@SeъLЧheК№ќйžkуw}жЙ‹27•Ђc`ЄI‚ бТVЭТz< Ѕт4ХVЭТѕя| ЋЗю UЕ#іЧœТШЂиUН}n‚$шWзЄо;‚ЧиŸ‰ƒб-I|?šЩЪ)Т8BЇг9ЦўЈXq-VKu”‹и•§ъ86moocЕ\mьЋFпїс•\Хўˆˆ|ЯCЛнA>ЮўdНяЋ7.ЫДЕ4­кeш?pS+Uяeщ†ŽЗПѓE8Ž“WY*nк4MZ.–ЛXђVа3rbrа Шѕ\оxƒdћђ0Ža;6u:ЏŸЇЂSЄfНС)ЇH2ПWzЭqWубУсI’”иљ Ў'зч@L!ˆ*@ЊІc2Ђнj‘Ш'Ў„ВЗT†шїњXлы0™GЩnсіхЏSмAEDn)лАœЬз/Ћxэѕ=RHбI@ KњЕ?ŽЏЎпKх+.кpZЇ1а=OЋб}4L]"sЪ­1TlЅїщЯPъ]ЄвХU)‘Я№}ќрeA)›љsp*‰h­Юb’WЉЋдЩ`€c]Ѕп~нCИ%И3ѓЫšІ‰ЩtL–eБЎ—RцВY0EЋџоьч€~ю$+§~H@Оџчјvѓ9K)“Џ+‹ЂœpмяѕЩuНLПЁfJ9СrЙDЏл-фRљXЩ'… Щ2 ‰3ЋJИXBTL&cєћ}*WT Ђ(ФОfбђНЅЄ2^:чa%œ9Kбx?OuЁJ BЂjt+ !>їiЌ}Ÿ(л…•іgбt:ƒІщ0 у іgcиуи\шѓђaРї=jЗкЧкŸ„•zдjЖ8ŽcЉІ8dŸB4N0шЧ1UŸo>rЪSНVЯ] і'aДлэ2йљ§%ЉДџNЛ W"Г6ь@Иxщ<Е;­ь:2wM–žSu.‹ЪЋМZ§dуmц{>nZэж‘"Эљ5g№Uд, ЙъШB8гŸ`Лѓ%иы9„Ђ!_”o]ƒюwю1,ЋQ8Йt•qwlсџ'€§њё>БќЅ аЙ„№оз`tЗr. м{6>жИ‹~Џ!”ЪJg“œСp=?Н§VЏ$\ю "ѕzеo™­јŽвUв$кCќюt€З&K(••Ђ(ˆЂY—"UkyТљLЬ№Т§№aуЯfЩ"ПѕQ?ъќдŽRYнSщŽЉ‡ƒ0Р /Б[ЙЏ6IЙНклЯЦZI"ІC#ˆзuх*А‚ЯЯ§нГхЊІ•dфУЕ:™!„‚а^уюЭwBНt"–ОtA„0ŠСН>њ АžЯ!В—(*sОdGІhЖ[ИљжwБКѓ*!6Ў“™БX-б•“љcэ™Ї)Zэv…„ЭE™‰ƒ§єКНТ—WЕПj…:Яs1Б\-7ьO–Щœ йhBQJсqічy>:нќРпXЕ)Š 7' 5JџсqічКš&фd˜K’f.ŸG’Є›% оA!)нMЙћ7*)QI„Mгјаћй–Юž<›Яйа| ”XеЪЛ=kКЎЧ­f QЩ}=˜u]чѕz-З­ЭfAшЅЪљГwˆˆЃ8‚B‚kЕз)дq’`ЙZrЗлхТ‰тz "sN†N’эV›з-vКЎѓўС‡CЄIšгЊДЯ§žЎчёжpШЖНFv…Ќi*пОђ ЮkœЄb“ЈK›$_>oђўоФМГPыСџэнПЭ{ы!#X2„Т~Й œ2Ь6ќ8†сߘЌƒS !~NМСО АVkqЖТЈœQr‰NLȘюђO]ѕZ Ф CЈЌFKл7й›>Hp>ЫЯj\2atnёџќ­ЁяВA[зYОр:7шнЪe"тЙ?Ч/мћ›Œ32SH`xРнџ$_h^€fh\gŠд…D™Œ*Tcs”Dœ?пЕНFœ&œеqсЪЕф~ЫbЌ…QФ$ˆыЕ:g8Г\JЮѓЬXЄЙTл—БIЬPxќ}?Ћ^gЄ)ˆ™Ї,pfч ;rюЬЇ a;я 8JR~Іf ёПЩkзЭж)ђ~&“ з, КЎ?еў<зхNЛƒLN†4e6 “‹@@Н^?жўИи‡a]ги0 xО_и_EАэ5Злэ’єRБпMћ“ўХfЃЩnеў4їG#”Ф&ЮsиўˆЄ§ ^en Ќ…ЏнИŠzЃИ—Ќ}І@пŒ!Б@Б…,Bъ%|(ЏШD 8Ž1єщњ3WБX, В№bБ ~ПŸћ@ЈЭ€ЪS~&"Яї0iЕZAS5R„ Щt‚ž“WЙ\?ƒррu2juЉ€7ЅП8< НбЯؘ›ЩЫћ‘}Ђ)„Бг_ънЃўА)KhЏGT^$зБ I‘ЛKЊзAež`JЈзшЩ5|х­ $“Y]зЅHq‹mёЦљЫм""ŠD„іЈE=јk„(х”.ь]Р‡вя%Ѓg"г‚цБб>cх)7~рг KРU„€ЂЈ4Nаыt+„цЃэszЗЂ(фК.uЛ]ђ}qS­VУh|@ЭfйXЃ џ P еЪбМ^вs7 nН@JУbвЖЖб№lђ=JжŸДП2€ ЊВЂыxб™бђ ŸEЄщЄEVGДздыѕNdВO ZЏза4„Эf3t{ЇГПnЏ зs)IdтєСшv‡ђ-эуь/7лЖбщt(ŒBJг–ia:eYЄЫм ь/$UеH7Єf9MS њtіќDQTT”“ефŽО˜™„м‹юC‰%Щ™Ч1Пы{^gЩЌ{{Лшt:\С)1чXN™SFšІ\рВРь>TU…Њjˆу‹е’E‘ZУЪџVлsЪртјŒ0 хRЗ^GGьћОLEшД8Oй‘—Вй>MгьZхm:Ўƒ­Сзі)$ИлщцХkђkйh_ХРzНц^ЗлБQЏЉќќх…Р;€Ў5M@гDўš*XU š& fП#ŸлжЁђ KЛЦIЌр—юџ"VЖЦ ЊŒТ*Њќ^Ј(~V4љ;@s€€u6§)6_х›КYу ŠЌ|Љдerб')тЌ9#ќЭыs@1Yза>ЯбzЈFхZ_~ЭЎ…€ў5ќън:Яsш†Сћћ{qњиѓWžyё[†+ўф›ŸРNo‡сЛџУиъoqŠЄD7г>иѓ<ЈšЮЊІIЂёzЩЬ€eYШЃ‰kŸ?џ$I8t„QШžчСs]Дš-d§К9f+у?+лР `с8ИѓСсfЏ‹БЂсњАч`šЊАJMЈDЌ MT\§ K–СзПљЬ=€еh`woнnЏˆŒ>Эўˆˆ=з…ЎŠ@’Є˜ЭчЌiZŽ3;™§! XІ…4MиЖзˆЃFуЉіW­ЂчК.§!Џз+ЄœbБ\r' |œФў˜ŽуpП'SkLУтыЗЎAедљ ељэйQнL>#WqйхWЪ"ОЕz /НћјмŸ|5гФіжі‘Н:ž"ЂTIСжpˆщ| ЧЖqщТ%Й‡зO'иоокСƒ‡рИ \МpIŠОOyŒzНЧs1`kА…vЋJZФ‰рКЎЁнОŒ3/b2ў&=\CQДCРB|їх‡˜ѕ?O|Дћћ`в€S@ТтЏть­wтGЬ5ЖЮнDЬ€qтk т#ѕ=ќѓ‹зё§3Xпћ&АЫ юЄз!TŒmŸ~sVvбы ёiњu №яЎ?…цќ:>к§(jУкЉЦZОdиокТx<†у8ИrщJсЧ;Э8†xИћ‹хgЯžG­fJиŸXtЮbќЮяХХя~wюнУ|щ@Ј'&ЈŠeЖњг?Т^З‡КUУp0<Ѕ§1!Є§Mg№}/\ƒOб'ђ…yfч ю?|€8 qсТEШ•Nu-uЋпїq0:РЮіЖ,1›œќ~в4…eXh5[hЖ”zп’ ]!xчЫtЮЩ|С‡Q;@ŠѕjfЋƒ4M@ЧЊŽiŸ—Є„š™тlѓa5{ qЂ=щ^GPW€ ,Zу—ў‡ј_Оѕ41“rЂі ьј™ы~ъ†@Є˜HSЦ1•щёаPЏ:5ќ_iB_яЪz#'l_рЁR}ссяО—pЙЋУ‹72­NєЄ“4AMЉсaјC1ЉN™Oƒ'b–;ї № Ур,ЕяT Ђ(ˆ‚aq­VЇ јсd#ŒX7аяђ™пў5zt0щ:N3c0Рџеv'ЏFwzћ2рGDа §ИqђћЫ‚†Ў‹ј3лŸ"d@kН^i/Oy8Gэ/I`˜ž{ё6zНЎЌtЬќ•ьЩ~Сй pЃ˜9•гc™\•Й…9OМ„Ђ*Иz§*>ћ™?FЇл=еCЬQˆ›NЗ[)Ь~ђAЉыfГ)tC‡яy}ЪeђЊЊТ ^k MOУeb$мФэ KŒpЖз@ЬGyёž!MЛјqњCќъјпЦry ѕ @ШРР~?:˜уўHE­BœЦвГ>I ЭХЋјшЭсw^nKс­82‹рЏАWЛ‚џ§еoтчЏŒ‘Z]№iV*Ьа ЃеZЈb/й“5‹OЛi’ CQˆVГ…tS“v‚е(У2MŒЧ#˜V žчУA‡“ƒzуЪ7О€(Š№BMGрК Sє+ dЙРgоѓC№XРаh6Oн'ŠЂ" |ЄRпŽ<ѕьДуDг4ЬfSК… 3яŸСўІгЂКiZЇОŸ4e\О| Ус ˜д›ПђZЎlnU•9•Њл•я TŒdH?рk7Ўbчь6‡AРЊЊBQ(ŠТйgѕћп !`Я— hšЮ[[[ьи64yŒЇЖWBnдыX,fЌ( zјžMUYёдіљWSз9 і=gvЮ I&Т‰л+Šр”MОАГФхѓпСw_y‹gы&ƒТ(х(ојФq_У(х8I0[ЊhіЮѓЛZЬ?Њў  я0Ÿ‘Dљ'ѓ•‘„H{Ь?йЛЫаjийuжu§ зПqoP…k–ЩŽу Šbў+;Йж­1ЂЇ?Bёs1B5ђЙЙu‘џNРwg>ы‚ ЊъSЯŸџЌЉЋŠЪуЩˆ[§67ъ Da˜ЕЇЖW…‰ˆk–Хѓ…єsm ЗиvlВOžк>ЏмЈзyБ˜CQюѕzƒрФэEaU†iскjФ;ї^СЗ>тАгCЬ)‡iЪQš"ћфЈќ™7>™‘њwxЛ—oђЅ-hšЪi’<ѕќ‡эЯ4Ѕ§™†Щ§С€з…ІiЇВПfНщtТІaqЗзчЙЇЗ?CжоgЯœCХЌТўф3юuЛИtэ"Т0фcvмќ•ыNЊ‘њM%m€**u A…1‹bFQŒї}рНdлN™ЕT$s>ыfч,ЂŸPUЖуPV)ŠLгШ“MЫФьЭіеќ$EQ( #иЖCѕzDЫЊСЋ$VзОZУ(MS(ЊJЃєz}DQ„NЛ л^‰ГЧЖчМO$%вug/§kигзqыšŽW_{HV§˜уА_&–х9Uе€˜"Ў—@5ЯТP–0D?wўW0Ј-дЈ’ˆUyNtшїiМCяиRHhTU…fш”Ћw u@v§\y.9L4™NaЖ‡И˜ьвЇЎ€о,#НЧžŸ*:U•„П€Оs іјЁПђrBIрlЄTVЯЯ•2љіJеTЬц32M qSЇ#Ќ’|l{цr{ЅiйіIœnаЅ‘#‚ аТ‘іE[XЎVЈеPHРЊе6њѕиі\д@EТLM0žљЪg1›LpM0}{2GЛ?$* yЕTЩY+Ч ЇАIСЗnН„­ГфЌзшїe6EžFrћг4 ЫхŠEUЈfZr{ЧЇВ?зwсћ>†Mе`hF^хяфіЇ(4Ъь/Ž#Д[MиВї‰ь/MS$p§ц5ІсD3бця6 2R%{БК<ђї$Iащu№ьѓЗh6Ÿх‰Ђ›Hъ, ЎRы–UХht€с`ˆ(Š>ѕЛ}иЖ]vо1эСхhаT“щ­V ЊЊ’у:шv;№‚€*а†#эѓlЂ<љz6›Т0 2 ЖmЃбlQšrю‡8О}Ёх`Ф‰+gGдаОз` ;О{'@Ће–Yщ96ЄЈИМЁТ€)ЭV.žя`ЕxD‰вЦї\оУ_nќC‚Й$m€& FёI€ -™ууНjѕЖ2Сѓ\ъwzp]Зьгъѕ—ŸeŸMЇcдkuшКNж ўBы.эьtВї(=џ†м— Ћй‚Я*БЗL‹О‹ыј§W`)Ѕъbуќш€*зЪДX,0шї)Š#DQŒNЇKыѕ!œYЕ=Jf‘Њ(ЧEš–дэtсTqfЧДЏЉЎы4OаjЖ`ш:­э5ВЌ\Nјиіy2БxлƒW‘мПCAcP3љ Њ]ЫЉ5T№ЬЈbk2• F!О18GtыEДT%‹@ЇhЗлdлvо'OЕ?!&г1†CiaRЛ•'GŸаў4 “ёvЊЊ’mлшѕхX+‚ OБ?ЫЊa2ЃfеHзuЌ5Z­6%qМЉ№yЌ§IппЮ™mк>Л•—Ў%ЎJF7U jГ0zFaЪ-ЯнN—3@{ž‹FНС FE\Iс+Ёp”™‹мi№|Б`Uгй4Э"9к0LшšЦAT“х7лgcMUUі}Ÿ}ЯхvЇƒМ(P†мыuБ^Џ‹$їjћŒŽТDрXQyЧ[ёљЏџ Vѓ“$ёCС›#6†л’.žн<х™нХиQ‰x*оzїŸчAЃЮЖyžчqЋет8‰ѓ—іБі— УрЩt‚Z­Юu†=ЯЫЪаЂ(,m§1іЇJY+'I‚fЋYиX&6;ЎѓTћS•У0ФjНцnЗЫI’@€ыК< yЙ\BUд2 ћXћKa™_Й~™U)в|N3”myƒECеТѓ9хU˜%PГйФѓ/мЦjН*˜oх{И`Сe Ё‹хНn/gИIЂmcаРqнъВПh’ф У0АЗ@УсVЁїЭ%r­fQmЌ"P2#‹ш’Іi4™Nбl4Ћ:^DqEQЈfеrшiv-LUС=sJqкФ­Kп!Šю ŠТBjІы* к>юмs`Z]Jгl5ЙI€$f ŽrУ=Ў=Э'LAмМќежGh Ѕ§pSv_ ѕш!~tлоN`Е^g ух@ЮЗyB}б':FуКN!DД —ДћєС+РњцsЉђphєЯэx@Ш№kšBш*аEќ‹;.(ђ€bЅСTЮŸ\шЙ]зAЧh4EYЩэ$WpХ6‰Ња*l@Рt>ЅС ŸW$„Ђ(X­–†$uр‚Њ„Mў! kŒая(-V€јTЏеHРЁEхлYNS†‰Знљ:ЙЛdkФ8ejш:LЯІЗќH–дЬЃŸ[“\nBтиєЭkЯУи9 KъЈ‹-`'ХyЌ§х.—4MА^­быv7ьomЏ1laэТždЊІa4: С`€$.эЯvtК]DaДС=оўTšL'hЕкTQСф832M+Чn=ЦўЄ§œ=†[}ФQЅdЧ чЏќЕ+NПЧБх!ВСрК.ž{с6ZfО ,^AљU—№Щ:yшОЂM’DОAšЭ|<6ТІыfѓДЊ3'ud5k‡}ЉSЬЗIМIП-&:ЧБѓтз:Eзѓаэv b *оGŒ8бБн_рт№ѓX/ВТDђžУ0Хх 5ЌWћXК’ЌСЙnЕB;еT`КЮŸПˆРyАA$aRНŸКђЛјОкџh§тќЁc(@штЧ[oтќVЏаW :є{}dЋкђљTњ$Ч)…a„fГР)Im%"wOэ<‚ші*YЃz-qCIЖЮ!Z  ЦHушŸУ??шуюhR‹C^EŒЧcєz§ЃЉ5ОЋVƒЂЈ“ Я €§ƒ}tZ(BйKђљХhwкЄ—B“Zск­жRG\Ће6Ц3УѓŒЕАŒ IDAT}є{}Щ…тЖ0ы;нРхХzпў*жЖ-‹†Rя{Шў@QA ,ЋшзТўЪc.^ОˆFГŽ$IЉТX<нќ%+яlР`ђeј!(QQЊ0YBХИxщ"._ЛŒѕjНAo "ЈЊŠбh„^ЏWЁВ–бУм‚0Р [­(вDцŒЦx{ЏНЦoј§зkhB”2›ЈёЪŠЙнn1  ЛдCсœЮЁуњЅ&?‚ыЕн_еNR2†}ІvŒћO4XyЄ­ищ0gXmMь]š"p‚ђ*‹К›VJFFў­;ятяДўg uНЂ>3Ћффh‰ї1њУIЩCkPЊђE*Ь{„]w)(]\ f)ŸXtˆќШЂЗ!ўою} Ї§|Y[UeŠ–ФійfЎ'wЋ,Aн>Оь^ЧŸО?‡yjї%a1Ÿa:™efw\]Є”шѕzp]З1™Лййb†сpxF‡­>/Žc }44bЪ~bc6ŸЃню@WЂте5jЛfЯѓ0ŽTš*ОMixaёцЗО†0“PѕО…'L]#eЦЭnГ'Oрwйˆ тпž\…ћвыЗd˜ЗtэЬNMзU&Dœš&0_,ŠrФ3ЧŸ”ЌM§>6лЭЉёgYfГњн^щюyкј GJOќФЮЬВL,s nq‚kŽ?Хьмl7˜ŒTNпI‚K‰ЩdŒwn UŠ9јцЏ†іг :ћ"|ђ‹I’рOq7ЖЋ…ˆšG‰ZЅtmгjAзuФqAЂМFЊx ‡eШўф5 *…Ўыpь п]~ž8:>Т89ЊпGЪ;DFи4ŽI†iтЅ_‚ЛyRЅЕ№щ]wšwo˜x№№ТиЭгbThЬ%ьЮ5Xњ’$w€OэжГ4Cg<Рпнћ'Иф<Ј†­Ÿ5пЧ‹C †SЁƒ*ШKсЧЫw+– г0$4o“B№]Q€G5œ7л@ ^tˆПлє^жLыЪЫ№Ћ‰™шŒ€Ћ\Сбmќпˆ}…їч fc>_ гэ–ƒяd?ЉВ^Џ4KG(Mгс>d&бiwžйз‚ @Ле†FQ gІы:в,ыn1,€ИuћкceYBЋ]›xX%Nї5[_ћ2V›Э‰$ђ*х…™‘0уKЧЗgsєwv‘& ˜:KЌ!№нW?‹ЩИ‘9€3кФї}ŒFУZ\Ж‰Ўых„fлі™эZЕ‰nЇ•‹сiв3Ц(ZŒ У0šj сxЙTрdљœё—ЫWБ… ­Іуж›аuбФЉ}ФљKPПGЕ•P[хk|04ђЄИœuг4Хh<ТЧ^ўŽ—KшКžktЬђU=w$WyB\бˆ*V_ нž_оАiY8<<РH­ъЕ-oe_ьx„иl6ЧУ \е гТђјЖe“iXЈ0ї\ЅеVЌ8Žси„ІXŒ$kсоеШпDF gњˆ™dи–ŽQЯЧЛ„шv‡2…L‹ЅƒЋ{lVOНFAЊ3ŸО9Чпщ§я€=ЭœЬlŽŸЮаŸ\R•+Еr]zЌ\„Ж.&“ |?hф„-ŽŽаnw>n’ЅЪ]с"`ќ;ш…Ћ=@ 5й+ДG;№у ˆќjїG'|0ХNЯ2щ}О…п§о ’r"NвD-p§AuМЊ~58Hц@Ь^Ї‡BeLP bК3E–я ~(”Ф#Tо)|Я-[н0 Ьr_ѕ‰рHЭОzЯ[w‹Щx‚Аpн0CЖ:јј{o#yќiš–“kSЙ%3zІБ]у tZ %(Š№іЮuhЗ?†NќЌ>П6fŠ1KFЗзE--&зЖY`2™”GЮК§Щё„ah*Ÿ]7 Ьцљ NJBuLoŒПт]nЖььь”™тwtt„vЋMКЎ#Eœ(ДZ­6Hд`ИДЗK;{г’ѕїƒЮ_ЂБЇb%ѕoНƒ—/РšЩ%>пК)&yф—‹cŒ”’{н.ЛžЁ ЄiЯѕИ€im[З/RVT›–ўŠџгћ@gЄtВ^GДšB№Љ•\“-$†Lƒkјѕ§>ЮŽЁƒЙє G\ ЌFЈК^§ЩBxž‡О"т$IsО`ЇхРа pƒGuЛВX5ЖmA@г4Уqœ зы•š6ЇэQŸѓ~2ЦfНfщДpй=ЦЅя|•7ЧGšж Сœ&r3ИejxИ8b Ц0Рx(t~єйП‰iЇУЈIЕ–Ÿ_ЙmИиСyžЧƒўi–"Iv‡мяѕЁ‘VІй4эЉОнч(Šс84Mу\Ъ‚н­т]Ж[mц2ЕуŒё“З3#‰иnЗl’4ХvЛхсhXjАœ5ўPэŽ9 L†c.D”Z­6пОw‹5MфtЉ*ФќQч/Q*:О‰2gЇ™IШѕ™КО8з"g9"Ÿ_§ф+№<QВчК(0кХ,^ЫнЋсmИЬхYЏз˜Œ'иК[6MOžь—к OЗЏ~WDщZ­V4ф§ƒ}є{Н еsuђкЙO’лbзЯ№Щ{п‚–Н ЫW`У œјЊџ›††—#~єФGЦm>Хx 9M–0MuЯД7 YЪw_тњџаНgћџь%вюsД8ЃmэЎыЂпы)­eнРЃЧx8Ђ†SzЊН”‚$V‘ФЇДќ“зHРщUxQІќ“ •UCf•?з‘Y€m№Т|ПѓaЬ&lн-в4EЋеB’&ќь~"KЁЂэvУу‘B‘I)БZ/1шъmђTћ™Енl С ˜Іj“ЩXU&<ЯОhз0 `й6Hз!г”_ћрˆ|MзY'Р А!ЕЏтяЌчѕЈCлBлн`.С_с XУ ›†^РAŸлџЅ]=ь!Т0bпїбщt8Эž=ўX‘X№fГСt:СfГaУ0qxx˜ЗIzЎё~рЃгi+/ˆії1 3yЎё—ЇБnш0 ƒ?Р•Ћ{N†H“Œ+bџ6‘яn$Wк­љj“3+›i/'ўЉˆзз…Зˆ–mбo§Цoѓёb‰AПnЗ[DујD.лIёХђzЖэАыʘ- `<Q‘ˆњ<ћ<ЦІi!NbЬsАэ]кeйдН}Њ=ъ )d!‹ПKoо§?јУП‚v>ћ2}Gр[яјєњыЏsл‘X-я#|$ЮeЯLД]ѓѓС/сW№ГW}2:Ѓ"‰і™іee3лЖ 1›’aМГГYFJŸm_tИЎ–сCу&§gџ*`73яGщƒžk_Uф„§я№џє3LК ‡0tљєЛСSЎI ц–гЦСьђЩћUфїЙі€‚Ы8ŽC[зХ|6уvЋбXѕ5:‡}БХ0udv>јЎџЦ/㇋‹&Жц™§_Qџ™? b|т­Ѓ?ўФOё} ”fѕ№иsћ?ƒбiuщᓇF1§>кэvёŽŸ?~TZN‹зы• ї њ$/8ў,Ы†X­!3I—.эхIгч‘€ŽŽє3ыѓlF‘Жsjўљ(ѓ—ŽГиLЭX;p’žQуi5r3Њэ/о|ыMќ‹_џM0€ухъТ ЗЭf ЫЖeЊJуЃ\CЪ ь– ‚вgиlЖШ.HšPW=GтџљНЅЏт‚€•|СбБ BќщЗXЎ>~! HбцІ–рч/%H“B9€{МТEА&`Го еnФ$А^m yЪs_cЭŒѓјќ­W№Ћп№avFЯO)=уO|ѓЧё/–ќƒ#AяЂяРfуBгвT“Ž—ИШ *њšгr ЁЂНЋеК<ЙŸїœeањм{ђ>ќ$Ak0ЪS,.FJњd+РAoŒю•kp—+Ш аUŠOлn\hКдѓ ЅФђ#Œэж…e™epэЃ\cЕофй M3.мзŠё—І^љфЧaYVƒѕwjўљѓзщ`ŽфЮu+ +*KЏ GpYйZ§Оnя8сїОHїпПЯ§~Ÿ игAщЎ,…4 ƒ7› iBуLfдщД‘eВЬєyІ}ю™4MƒЖЎЫ”gьЗл"сљіЪЗЋPЅ…@*uњмЅgйWЛ аdHxѕњџТџјŸясњэŸ#AqщC|ž}ёoКiђvЙЂ„т$+vжu>о3эY*лvЛ!@UДZэ\њфљі…ПWгtJ"аmќтёЯђќр еЫpŸi_Fx4б1И5тџ$ўUњќ'ю ШˆkяцйіХюZ7јшј˜ЧA’&мiw(SщBЯЕЯe иВ,ZmVPŸЏhВТШ=уѓ‹x†aибЯўЫ_ХцбC&!ЈдЖ{†=$аXђТlQвnуЗ_x‹/Пі!‰OОпЇї•CІiaБ˜ГгjSšФмjЕЯ=ў(gmšІСЋѕš„L9NЋh“sŽ?fгДhГYГa˜ф>zно…Ч_šІМЗw‰оњ‰Я”%ЌT@§ž1џœwўв‘Ўz‘г‰јqCнЉV5DgйГr0ъЭзq№d_Auѓ|<ѕy›ЭЏНі <~ќЬЈr‡Юy У0Ай<Сэ[Зсљ>в$QрЦs^CJ‰vЋћ>Фp8BЗлСссcŒFуsгU|ТЦЧoўкє—И1љŽŽ^ХюЮЄLЮн&BdбыЭ1^ўјЫИџ№>@lšЁcЕорЅ?†ХёЂЬ<я58gу}А˜сіЎџјъўћ}ˆŽO‚SŸё0:рЂsѕ–^†пœ№9o‹і`IšтМДR=O{Щd†НK{xјј„Жѓѕ5ТvНХЋЏОŠG•UGйНY–§ƒ}ДnнХ“Яќ4:їџ7ј ЮНг7ссЦЫw~№oџ ‚—?‘m_рдТ0 ЫеBhинйС“''ѓм.!Яd†­ытЕW^Хƒ‡ „(еЯлЎšІaЛuqїю]hkuMЧv.дзlЫЦ ЛУ0šе?Яš.0 дu7ЙЖŽŸЬžсВІЎ,еІ5ЂВ/ƒУюНxЏŽь!дpEЉ–˜[jШ>йпЇ]ЌзUo=эшЄ=Ђ' н0hqД@ЛеІ8Ž1R2|•@бгьЋ &™†ЭvMj@˜а…FІa(ЄZ]я ћšpP’ъtygqы+ˆ"аЫЗ ёŸР JAїЇкчС%R‰Ј&ііi:н!знb2УsН2уџiіѕі2-‹ц‡3єћ№|гЩžя5ЕhЯА/ƒRJ2 ƒ–ЋtЁ!ў ћCzэš HГжgžŠЬRГMТБ-ФFH=Клјƒ}hYH(в‚N|>зJє‹Х)OБЂёh\фЈQ-йћ™іЕфx:88ШЅ7tщв%lЖ[ˆŠ${І}1ˆЅ”Jh§hлВШH|џіЋа^њi*Ч“Ъ|ЗъO}ђ%A?Žaя^"3 ЁЫ />ў>љп~БаЈ–PќдёSцOƒБZ-быѕеЉЇг!ПЂпD%|eoРW‡ПЫюцCHйAКГdЮ§DхgзњX]*šІБыn™™бnЕЅ)g™ф^З Яu9?>i_в‰H0XbЕ^a<žАŒ#>NRьџичЙеp8(„љjP;ЊЅХH,Ёaw:eџpŸюЖ-ю}љwy}4gшњ™ŸOљм\фк9ŽУћ‡ћ<шѕa:{ЎЫУў кѓŒ?У0љxЙ„i˜lY6'IТšlYТ(8зјSyС ћžЧƒСг4х8Š1ŽxГ=ЧјSš#мj9ќТKwИbgœoўЙШќ%š›ЦšЬa R­€а Эп5эѓХDoОѕiZЎ– *I%аВXJ№ЃІббё1FУQ) ш{ЎZхУрЉі%ш“™ZN ћOh4— ЏJlnQQ[^Їn_Ыˆ2 ƒŽ–Чdš™ІIЕЋе„лЖЉ UT;И’-Wrм’дЁ{WПOŽіM ƒšІSœŸ|IТпFЊѕП<+sеђКP""’ЬДйЌ1ш(Gw‘ЛѕА;н!зuЉ(ЦЏЩ;(@E єўС>Ч“Вў3ŒBjw:ФR")VXœЖ/ЫВ,šЯцhЗЛЄ ‚@ۘшгњCќдUШ&bIЕѓI“x !ђбэ („k‚а”СяН„пјžKЧTлСQуАTMЃ$сћ> њЪВ BИю–vvviГн$‘SіЕ(%LгЄУйŒЦуq‘“GAа ?@œ$”SWЮД/ОлŽMћ‡EщњLпУ‡—oQњЪыdАЄвGUOLЫЃ•‚ˆќ$EћђUXл EaЈFeг'мE_ћSŠ Ј^w[яџe›ш:‚  8JЈ›GТIr=—vІ;ДQ0ŒчŽ?!ˆŽ—GжЧ_ргxJ:в4c" нjСќ:ЊЮJ-ИѓХŽэАЅ$YU_“’4A0рМЪ„kДšzЂ;4Mƒјœ$ КЊєЎLпlЗ|џsџ6Ќщ.#MђВ­Й(cцЅeуrЏ‹ѕс>kК!e№^Џ‹+_љ}^>yTђbOŽŸBжXз Ьf3Ўе@‚хl: гD…Я6Яц3tкm˜ІQŽП,Ыeњ§^б&ЇЦ_э‡ѕz A‚s{9ўТ0р"чђiуЏ˜LƒпКs,ЋЛшќsžљK˜ђm5тЩдaН”sZO>Oй3CiЋІПљж„aЙOЮQЗœ—ЉА‚ЅЬАйnx4сnЮЫл8 Cюхѕ9S}•їЃNгu^,Ž0RBK8р8Ža˜&tSч(Šђ{цR? яь8-ььcаА(^TЖm< йuЗUЕA“AЩ^МљkщлˆХ8т<ћ?ЩпЙnёРњ^Ў6BpйQ(#)Х,ŽуОяѓpPeаэ„їzЄIТiš–ЯJUЉVyœ\.W<Ш+>ŠЃ]оЎpœ Aœз%—Ж8ždYЦŽу№ўС>FЃQUвџЧˆУ}ўљл1 ке„WЙу)\У™\AЧŒ4Ў@—ЬLœ0Fwё+п“œ….HмзŽJљБUеŒsЧмыѕЪ„цт]AРУСP)rсЉь‹ЃЏЬ$ч@NфmWіЕ аыt!ЅЊ2$ЊQœЗЋ”’-гфйс ЃсˆkнZѕУ(ФCЛЫс›? ЋСVЯУЬ,Р|ХИtѕ:ЇѓУкГфи.Пj Ш/џћ$ИVR—okЈŒКо–A@ЛнцЮŒUpР“б˜kОРвОšІБТФy< O?_э#I“ –{жјг4^- №IcќEQлВTПЮЧp1ў aюVРэ{З`йVо&є‘цŸѓЬ_ЂиЖGŽ2YДбЩя…3Q}1=Я%IBЛ{—шю‹З)'Н[їђ˜bYЮfдяѕIЈœ2ѕ$ъЮ a@Ѓб~рSпІк1€ УЄеjIКЎЃеj7Ž09ќ‘Жл-vЇЛф+ЈfuLЪŸAзuђ}i’PЗлЅLЪмй‚ђ(EЕлm’Ь”ІiЅ^ŸпЇ”„щ0Є“/вfуR~єЯћ!ۘ,ЫІзюнЇићŠ"ABˆњ} j“Чх§Žsѕ9’в4ЁA@A­MŠT…<CJ[У&ЫВЊЃ\~ Mrн-эюь’ы–mRЬиъІI›ЭšPОЋІш—Їб*ЬшчпЇы—ћY"ЩkG`I1Ё ёё>AR№Ъќ§I&8-њVz‡ўе{KHЫwS?Іткбd:ЅˆKѕшB–e$%ЃЋœџTИ)ъGyх ŸQЗнЁЂ/ж№79ЪЇщtJЎчц…™ІEЧЫ%iКF­V‹r,CDo7єЮ?Iж­Л„,#pу(MY&)эhЌkф-‰Ъ{х"qњЖM/}џ›Дњжл”щF§ZŽKнаi6›бtRЖIОПP7“$ DV‡ќ сК)ŸЫ0LšЭf4 ыСЊ,)Š" Gp=їдјSX5‹G В-–e нхaww‡\…ю:ѕŽЅ”ДГ;Хо•K”eY9n>ъќѓМљKдfqдђХЯTлљPщрцZ—yО}^'Œз?§“ 2™Би=)L€8ŠИзыAВЌnЙr†QФЖmƒ„@œ‡ФQ-К>>^b<#IЎчЎ•Е”Ђ8FП?((5\ѓАЎ˜/Ž0P8vдјхŽŠ™!І“ ЖЎЫUZэx™КxхжŸqш=„”)™E[)f ФѕНvкПЧыЭ>НкБ)Vo6р–гjЗQƒžњОЯн^YІvк5R2ЏзŠсVVв%љБ"Ы2HfДлm.#мЕе[з4a˜Ыд|–љщ…1‚%џУЧ€3ЩД sC3€э ­Н{ьЎ%ЁШЭ*PUЊNј*~эО…ХrХ†&PпyY–ХGЧЧА, –i•m^c€qС–є‡'qY6Wл9#ŽуК$Ÿф5ЈїГЁы0 “Ѓ8ЊUђrи^Ў–FѕкжZ8–XШ G‰фХч~Д6t IDAT-Cƒ”ећ1a–dИ|љ2{ћGмr' епбіWОˆѕvУЂF0gлсйс yН}D=[Dг4Хл9Њ\7хn0?Ъ#Mюt:ОF•ApЋе*ыЈыуO)3оЌ7FHгєдјSc-CšfшѕћЇЦ_QНuыю-Ѕx'eƒїQчŸgй‹њ™јtўЬй ж'ћ,Ыаэv№к'_ыКЗm‡‡‡ШєO%Ќ(VŸ‹Kгн’Ф[tnЫВp8ŸЁ[У)&2ЉыFa€žJ"FэX ]зБнn54№C'sђЂ8VjX–‰ ˆ Є™[Wbмў3xоBшЕgj>“‰з_ZA_„dMŒ‘ІcБ˜+tаmR‰* ИЎ‹]фЮџђžѓРFУQУцЌыxža!іSЃЈшКŽхj]7риvI1iдЙфзu$ёSЮ}МyЭ“АT"‘NЏ‡Xы@КЯШd иіѕ{ј§я{р$8ЅЏМ^Џ0ЌŽђЇrŒ‹э>І“)\w[ЧхУВ,ьŸбзjйeљ5\ЯUЉBЎзxfг4pt|Чi)№BЃв цfЉясН›/_~z^/ˆрЦ Z{—бŽ|л-D=wБ‚G “ŒVЇ‹—їпG№­ЗInА+“4Ю'ќA­MЮЃŒ8—(ёљEz”ibvЈ„Ъd9xrќ‘€чzийнС6яkU›˜˜ЭбєЋwvцј#„aˆ~~œ>ЉG|хъІЛ$IњCžі{qтSoWƒё6ё‚E…"Єm_ќ2№zѕЕW`;–BL)|9) ЃэPm‡QЫb,*I™!ЭRЅƒ V*є? ГMѕž@Эћ“ЪљбhЯѓJ]]гЈPЬ*Ž'эЋIрљ>ЦЃ1љ_JJк–†ЏўмЭcbжjY˜дl?(fрdдЇлЛˆењ˜UМпЖ,Žцd;Nq”h~~•ƒ˜SЌSAА›”ŸJ­ЫїЩŒj“yУОТГDЧ4р*HhЁ­AЧЧЧN‰NV“6О$(є6ј…Ћ3ˆNŸ# EК…6К5ЇВdŽыъ]ЈъCГэсџzиЅƒу ˆЊмаљ|Fэˆ[CиSэfЪћ‹“˜lЫ‚}ЭВ,Z­Wа4 e0ˆаАЯ;[‰нR’œУ1EХЂНйn1RёiћМџ2MfДŠьџип„гВ ЭФt8Фі`ŸD•Ј\ cVЪU 8MqГпХр‹мэ&ПМ„Ѕ СдыіDъ“уЇРЪaHŽТъ+ †mлtĘУR˜{тJHуЬё—f)Hэv1Н dGQLНnЅтгЦŸ”J†v0„ыЙЄd $лЁцў‡6џ<Ы^œ|жpr9?Ё‚=›ѕљU„яœіХУПѕЙЗxЙZТ0 $жJ( ™ЬЧФBЭŠЊЂчœrПзGЂœџj9;,RИІ%аА/vXBЃЖэА”ГЧZ˜хŠYzЎ­QmŸ‰ЯZPВ,c0аnw†>2щрюЕћьˆЏ"Š’uE Љ*ђЦЬ'ŒW^0`Фџ Ў'agYRPЉЋœЖ3>Пp‚—$оСAр33УаuЬчѓ"*xВАЕz.ЊДТ0@ЇнbfХClЗZМИ~ПЯU§%5э›CЋD№Kє?sCШfФ>кнGА€p‹ђXLtbЛ„Š№Щ)0ў8~х›[жГ†apЧ№|ul->g–jP™уЯїy2™РѕмB ЫхУсˆ.…“…л@ЃŸєКŠІЧ1кэ6y\ыkq=в 0„€№Жќсф*вз> ƒ%м4хіюЌэš•[=ЪIћb:вt|R†pПќФšЮКnТЮб]ХиСЩ`K=V яwІ;Мйn ы:K™aНнЊШО”6yкј Тƒ>Ђ(т,Ы”оШсAqZ9зјѓƒ– ˜ХI 0сжн[hwк\ё ИѓЯYік?њGџэWтМˆjsvЕуv­qJ gТEь™Ѓё‹Хm7.ЖюD‚:N™ВRhлебэuzD–e AT”ЧФqLОчc4qN‹&œaЯ ^Q’$шѕњXЏWdш:fГ9O'SH•ЫvІ§‰П#Ž" š/ŽА3mё[/§6Bї{$„`]а4@г]iqёwMщš‚Јtкйц я?so№fѓ}В-›mл~ючЏ#Ы2hК^DЩѓn\ПqЪ№l.Šp-ЧСУЧФ vvv щкyЪ7Й^Юуи6ŽŽqМ<Цx4F>ŸЫО,13-Ќ7Œ›гп™§žь/ŸUЪ'JЉ.Фћ.ЁЕћ_#It\НrE‘žЯa_Џ;nйžЦгщEЦN!)%‹9Ђ8СеЋW Г‹?ЧiссCEНонн=яГTу€cлxєш1>§цыИuя–R–TœњЩќsЪоw7ВщIfЪХшыъˆUщk}wŽГ ЯЗWМ?пјњ7№o|—лне">чІ!f)gIFІeсЊѓгDЩšADЎчЂгnsхг9„@œfDƒn„{ЛПЬоц!Iхћ;—}ё{!drЂПEУСUdЉЯ•Зт|4s’ІєНЧЏСЛlYб&ЯЄy0ƒ M %uy—Ld,›„–sб@cCщzŸЩ1Iв/d_ќœh6џє5AC[ƒІœSMЮm%ЌЎ|ЧiЪ†šИ.dЯ вu Y–qЧАЋtЂ б|Xv˜Бўу/R&Ѕ: \Р^•:I8šFсЫoРjwŠвsлqMгf)В4cг4ыĘѓѕt]CХ,™Щ4‹ПœP4™Nшг?і†‚3ц™Э?ЪљЇnЏŸ’P…пЁ*ЙїФ'ёгƒ,чГЂw_И‹ЏўыЗa˜f#ЩѓŒ;§y A8^Ѓгns 3˜Ц™“р3яWзuььsЗгСjЙBЏзуь4 єЯ+С№kЗ~›х>іvMЮтщ8#ФxfPJbз‹у(z›Gжз)–‚Ё1У^ѕ#‚cEјвW/ѓџ&йІ ‚Уeяsь?kmюCЖ_сќчР.7…}žcOС?vn№OЄ№ ЏSgЈvt{•ЧqxЙ:FЄМкЄшчšХчБ/~B ˆƒ"%ВЏЕ цѓ9zН<з+нчЖ`š-рМљ“ЬЬшtКХ.џ\іRJДZ-,sІ$AИ^Cј\і%yЦ0xЛu(-п~аr8џјa,W+Д[-–YVЇЮm/Ѕ„eZИћТ愆$KŠT•љќS7jXЯ†ЩдJŒЋOЌGў/d_д5:ŽƒЯќј›X­V JFЃTЈЪЌы“Тi94›Т4-Œ'ђ=я$eуЉіХGКЭv‹4Эh:нЁBнюyіD• Cš9И}хMЦпЂ'т№иІЗ‘eЩZљ•IAYё3kRPўяДіКtyo Сs|элNч:$;ШИ ‰2nS†ŽњШќяЗ‘q›4­Cg]МГџshЗLL'; ъ’Jrv{”ajг4qМ\‚Iряэ<Ре=@з€IъЫ•?ЋпхПДZЃ]ќбТоїD1љрХВј"?aј У‹%y1Sў3ќ„JТ&ˆё№№ƒснvE5ЉGќЊћЇЦћ-њ‰iYX-`[6эLvА)$WkzУOГ/ўиЖйlF†ab8!ŒТZDёщі•ј; k:yО‡аpy4„ }Jм …@‡!х?ƒУ јїђяЄЅ Тѕ ЫУCьэю’eš…ђьпOыП †&4‘у8˜LЇиl6чХЧqhџрэV Убˆ<п+ Ю=ўрЦЭыOGEAС_ЩќSЗ@Br2Ћ†k зџ­МИ}Qšvя…ЛŒњьћ>уЌm^•юQ:U У@рћХ1:э’$х^Рžч– agйŸ№uБаѓ9&“1‡AРУбЈЪ‘b~І=œI–#ёТЕЭый}ўиоћўCші˜ГFžYс”­yas" aЕIЙнЛТœЮ0Y<Уrл‡Ўkѕ}ЪОщ A,ЅЫoПїI˜іиA2ащvрy^‘"С'|c%ѓИ,%Ъг+Vыœўh†џ№жАћщЅжшФЊ[е„ъЉ }rўь>Аћ"ўЯwЁяБFСqтѓбˆ†ЃЃ#t;]DQ„AРqŸpyžЖЏ_УВ,Ќr…BУ0XгlлQy›ЕЄѓ“і'sOу8‚мэД‘ЅJ;зѓ=ж*ЕЖ3эKф3tCчйl†K;Лp]Ѓбˆk}эlћZn#6 ѓХЃс[зхБТ™•яяTЎш‰ў+Ѕ„m98V:аJОТДЁщ… ЖоуЏ85ЙžЫR2,гмiЕйѓН†огЦOёЬNзo_/4OИЎП§Ѓž {Qц ~„ЦЅ €jэї%­ПFYјіyžоќь›фКn3o№4гЋ\СtMЃљbAУС„І!|ъuК”Іђђ™ГїЮqšЭЉгщТа ђƒ€лVЄ‘ИЌ29mЯU9Ё”нЛўЂH’а4миKщ/ПэЂз,ѓЛВ2ЛЬБ*Ъd–ТћДЗл"wsM3щЦUяО{ЇЕCY–д+ЛЋ­|еc =Ѕ‡‡Cztќ9ьLbf„a€Ao@Qе}œlз†Єr–eЄTФfдяѕaш:<ІГu/^iX'АЌч36ьЫїЙдъOІ Ф>Саёи~‘ўПoЯЩЂДSwnHIJБnFУС€4M‡ч{шvК”eЅRж)-ЇAMљ§9ŽCѓ…Ъaд a‘eXdEq„В„ё)іЬLšІ“јP™ =’RRХ4БЩЫЪЪёwrќe_B ЫRЌ6kЧ”І))§ы1ЙžЂ q5ŽŸ1ўtZ,4‰„€я{д (Žу’4єЌё'Ѕ$бЭ;7Љнm!ŸўJчŸТ^ RjЎЋхMз(>y‡jўТGЗW4ŽcКzэ nнОэfKUŸJigf2tƒЖЎRыЊˆ­:КІ †У!еVе3эѓz_DQп0žР)MwАQU&ЇЙя„,Fƒ wwŸЖы%ˆдQvoWGшbэ HзEЙЈцUCT“я…Œу5h:Н†Ф0(Iулиа§} NЋM,Ггіe УўЮgбэ]!гд‹ bfєЛ=ђъ@Ь“ y:‰iš№|т8*ФЏI„Ш]бyѓ а’‹њнњB[гYdršмBtќDхјpFшэсзg;xx0'MkEЫNZІjЈлўсM'К+Š"XІE†nRXnЋX|;Ќ$шxyЧqШ4Эв“‚к­6ќР?<\Ј+цmeš&Жл ЩЌ” "Ђ0Š1ŽсnЗЭsaЉЮXT[„”&1Ж[УсPсЁHРuЗE•‰Т™IхЅ=S=ХШ0 аŽЊ.ХУл­vоЭqfu{4‚a™&ЭUтtоЎdh:ZJДDЋ5ўЄ”d™­жŠ8нjЕJ`o–e њфcќI)1ŽqѕњeШL6Вeѓњ•Јз†žЄQ§{‘ЛJ8э…ўь…H“Џњu$YY&tRУeYnП5чѓІS…OЯГісћ>Z­Р@Vе)6ЪЕЪz_УРтhŽ~ПŸы…R)f.ЅDПзk…j8М™%$Кxщцз„eI)p.„Ре=Ц;я-`ЗІШwpеQИр!‰SФr‚б ƒяЏ bФ pыЊ†‡чњDЭ#м_•ЯcъОїр жбЇ09HSYўЮѕЖшH’ДoцBVтзšEб5jEнTрсoн!€4_t=GУќZ;7сЎW€Ls‘t вž} Пін"іЙPЂ“яІiaГYƒeЕF-Ус N5Љо 7KћВ,УzЃъR ™L%†юђhXвbЊыTŒЄЂ.BЬkЂтEџ‰ЂЖcCгѕ го\P9љMЬsдЊ5Tэt.ю>шАнnЁ э$ПT­gšf#П.ŠbUоцКMvІfЩ—ˆтAp?Џ–Ъщ-*Џq4BЕ#їщёW$'/1Lчќ•уЏлU йВ‹8cќ13ыКŽЗoРДЬЊ]џКцŸв=XъlвЩвй"oјTF5WХ“?}šІ˜L'xљЕѓfГ-}t8ёЌ–iётшNЋ УаK_TбY=ЯуннЌ7[!*Šp­ё5udFЇШС ЕЬupшїћHтИ,ЦЎ)АкmкИЖ;Уор+pн#T–2q’0.M- [ђСТ‚mйœ—5ќКЬ–nоИТюњ~1•(ЄvЫрawƒw?LйВЛdeŸO šXoП§§ŸРt| ‚š…уDОяѓюtšO*ЂЊш(Мhy‡мК[€%ese"+ˆ]/Р0Нg:bd5Нщт§тŽmrъŒ‘mч€ІsщыЬ ?СЖWјэ‡Ча)Ÿ`б„]}i~t„ёhвЈж "DqФІaРЖm„aXAъuЊМ ‡ћ<ŽЊjкŒуЃс(OТЏ‘ W:3 нреr г4йВlдEвUэД‡iЎ]]эš*і`бЎ~pЦ'Р jЁ ‚н^—%Ы?UнhЃHГ ыЭЃFQмƒAАу(1јёVt‚2ƒ@Ъ –iсрр€Ї“щЩкђR›ЗзSМПY‡š0МЊЗчХ|ŽvЇSЯs­Е‰ЯгЩыэѕЙ>ўŠЁПwe{WїЊzпЏёЏzўiю š`]XX9ВЪ:ntод€яћјФ'_ƒfhœЅYqVo”ђH)ѓŽ0Ќ!ОЋе)_‘Игm#п%p-цЁœыІ‰ƒƒоQѕО\ RЅzœ&je.JЈ \Aр0L_КўUј›,Ѕ–ƒЩИ„vF1уЮ ї>œБщь€ ѓюT€ˆWыэю5жАD’Bk@3у„qчF‹g‡‘bЪ„ZЇЭ7šˆјkяныŸ@Џ+ тF-а' LгdгДчT“ZРЂ@Ууp>ЧtКsВMcO О”Э№woz€б‡рЌRV(H<ЉkzKaюkЃ8Яrd їnсзО$ЁЧš FЛц@\,—ЧьXv!ЩШuІІiиlǘŒ'ь~EчЉŽа0M“нэ2“шt:Їњ  ЈBэvЊ‹eWЎЖъGЋ%OЦдѕЈ‹IYѕ5цnЗ[45†a№сс!&“I)жNЅ\CВъk§^uV_­Mи2-,ŽŽИгю (бЌUєАІip]“ёЎч–‹j ЖeйМ^Ћф|лЖ+BNФтyН’$a.Є+Ћк1Юsn‘qЯICзt7'ІWц\С(nпНUфsrЙћћkšЮІСа3h tvтгb_ЫOТg?їŽWKшšоЙTT+Ў„&“ 6›mЃ’)Ы2х—нпЧh45]:ЇЧ_€0ŒNбv˜ЗюмФ`д‡Ьd#;љЏkў ЁБ/угЅgЇў0ьsЂЫзqщђnУЇaќ €*ќюž’ЄфŠ€Œ4Gѕ:еЪ\яјЫх уFGЈюѕI0|ŒFЃ‚8ЃpuhЗїЎќ6Ћ„Ј:иЩ”Я$юнr№dџЄЖ +Хœ]Ÿбэ]…ШKHхЛЉZ%Э“‘’K<™›Аm3Ÿ,YЖХзп{†u э–еl>Qf—$шД;(RŠвЊ,UЋњ`PУ)Б:Ѕа` ќƒ›+РъВhtzМ‚БїТЃ‡€ІЃv$iМ$™ЦРє&ўщ‡|w]ь˜Kппl>У ?hм{гЭSуЃгю‚%—Я-Y–“ŽЎщpœFЂrЃЊ —К[9џ›}- ЧQ‰L;i__˜e&бэv‘яHЫЯB`qДРTеQŸYrЈо†­ыb2žb{ёfšяB›% '|žъŽЧqЕљЮŸgЙZТЖ˜Іyf{длDm DQTЖ‰eYиlЗ`ftкS2еС$яkB еЎкЕ№Щ\Нy2k"Зў:чQJ/TdїRŒKЅžхпЉц™­R“ш‡fŸS\…&шSoОŽ(ŽJЂЌІi4Ÿb<QMЂЂeЉ­†Šњьmбя(Ы$I)IВ$3GхМР†Ј UЫх):ˆу˜,г"г2)ŽcRПВшуЗо–~ƒU4Тыj—ЭЙl#“‚ЎэЅєяЎЈз›BЪŒ˜%6о€іvLlз3"вЮД/r—СD7.ƒ<<†аЇHшZLя?Й„‡GoбЅнAY”ŸЋŠ }EL”4M#зs1 )MU6ЂvмŠТ]эtіŒŠІKц!бпpоЧпt”Є4 ‚L­ёUИ~$•~‚šF]Љct GЮ єOПЙ†ЩIA F…ˆТˆњ§~!вS}~љŽQІDQˆбhDЎЛ%!D€=::ЂщtŠ4M+0ъЪъ{ ЅсpH…ОKезц GU$€pЪОxAЎчв ?@–Ї`13YЖй|Fv‡4]ЯгНšіEџ-pfš&аrZ>хЙvДнnˆAдjЕђ6С){TmЂpfУ!m”ЎдZ­V4jˆ7nŒŸbwFDј ‡CaGLеŸЃХуᘠдм™у ЈЯл-ЦУ1EqTвИѓРЕ;-ШLў№чh/Jн†Кl6B5–g“H œЖљСьСQсЪе+ИyыЛЎ г0инnAB еj1—‚>хоЗє/>AAрѓx4ТfЛaC7Ч1ќРчс`Pэ–NкЂBЙŸgыn1йѕ\0tŒћ _Ÿ|›ЭDZЭŸVŒ’Ъ!LDœЄ{;&'с1цЋК“gGзЎ_Gр>ЉеБ„6эsђ†‡6Žёx&иЖm„Q†ЏОћiŒЦWЁk•\з’ƒЙ$ёVўЯ$IЙп`ыmЁы‚ DœФмЯqJxŠ}UŒ^o}ќУ+оHB˜"c .#YЯЭЅцаЎeзUm%S Зƒ/,w№юу9 bЖ,ћ‡˜L&\D}ЙёBъыjhл6 Ур0 сиfѓ9їzнШЩхА/оwоИгR>cУ0иuЗ›‹њєЇич;'Vрд 6› [–…$Žсy.‡УвxкОŒЎrсƒ †FQхљB%щзtOйз‰ЬApЛе."Оьи6іYщs –pzќ”Х Iњ5-ЃІa№zН†Ў‚OЕєйуЏ˜]Т фёhŒЭvУ0™ŒqэЦN’…тўИИНЈ9Hjчg:Чи`žе7ˆіХрќŸњЬHв ЦСь“Б ЛЫ&тчЉ_aСВ-К,ЫААсАJ‰8ЯW–)|wПзыexуХ?G}ЂКFа?ѓKзMn^Щ№шЩAмх\BП M7аuMуч~ХI†з>fуўƒ}и­1ОўЮnєi zтмЯžяЂгщ”БА'O0)Ыч_ƒј у–|ŒїvЄ­НЛиn\хmвч IDAT@Яћ€ЎСsюрŸ}РаdŒЃу%4ЁСВЌs?)fтp„0†>\ЯEЏз‡”чo“эv‹~žfЮ0žL•_я\§DБ5]‡эиˆЂ‡‡‡шїчюgЬŒ4M‘Ъ эvI’`БXРЖьВџ>ЯО8fћОA€8ŠБuЗˆ“­VыBэЊ4Ž•z›”ŒХbIю—=яѓ‘Z rYмК{KM|Хтё#˜?>ŠНЂСp­x˜™Иртц\.uЂz9I-Ѓƒј‡nЏœѓќня|—ўјK_сн]ъѕz™<7M„СЌы:I™ёСсišщ”sьЯљh ъІd’ШОŽыУ_сћюCQw”=—цaš‚о§ОŸјёи2€ƒƒї).A˜чЃhaЖHx<с(њEJщEєЛЦI<дГi$Ьlв,ЅЧOЃнjc4q-ѕ\4GdД4ЇјХ?>\eЬ‡я4­xЉчЃh:вй‡јЅŸю№ ;=ъ ЧŠЬsšlђTšˆBwй"Ѕ{2ц–г:Iz6„™MгЄ( ёxџ1#ъu{ШЄd:'Э‡в5E‹Y/ˆ%cwїK™Q.M‡ХЖ-zќф1ВLђююЅ*йём4ІvЋ…'ћ>ЧЖ-Ло&ЯэџЙп]ЯЅƒУ}žŒ'дiwЋ4žѓŒ?f6Lƒ<ЯуvЇE?ў“ŸE’Є…JпvўИ€Н^uSЅ-sЪ[Ш…sˆЈІ‰НZUяоЛ‹я~ћИЎ‹( /@SW–Rцк@Ч8Z, њЙџЈэ} ЛЙ…ЉИ|i'?ўђюE`oЧЧ7>x Gы1тјsа„vЁ+(И0!иіБv z[Ьц)шЃДIЇ@С\‹ЪД‡s^Cу†хaw№*ОП`NoФХл$э]Уoz.ўЋьlжYvёwГBSЅsaфAЋ‹Е 3ЃеnAJ‰0дn№‚oGJХЧ“ЬШвG‹љћšr M+ЧGGън\АMVы%Œ$IсyЖ›ЭЧŽКЧБ‘eŒ0јСХЧ_–Сv|цЕO#Ыd3ёќG:œп^ч #Ў-x|jЎWšœeѓCДЯѓТt]Ч'^ _ј‰щtчBрGlt0_,`™&’,…Щ&шJх‹Hт Оњнлјї>џ) м/"“Цљtу›яэрƒƒ7АwiF еj_Ј ІaрЩСєЛ6›ƒсЉˆјГЉТ)Эf‡plaЃcЇ„‡ž7З5р7Гз№Чя,анйУv+Hœ{„’a‰-ў$М†ўѕ?ЦпўЬ]|БwC„<ЏS ?­7 zƒ:Мѕ\mти6іїїбiwрЊЂЁ)0tŽОfлXЎ–0 IУ4/оз(OŠТšІ!“йГРКg/+‚ ifГC ‡*‹Ёлэ^xьX–…'ћћшuћ№|џ#]C ЗяоBаЯЃгѕЃщpўИ€НNЕњщRrД˜Ў(ЊЕcvў ЅѕО}О ф[wnсъѕЋМ8r‘є'<]Ёwї?{уП€З^s^|ўL{ЌыŒ•Ыќчп{‹њУ†2œ<œљlbs™MЏы:yо Yтbgz•яЧ”$ ŸA&>“ЧІt“•?Щѓ<Оvх-WKŽуџŸМ7kВ$IЏУЮ{м}ЯZККЊkщuІн= f( A€$2ˆЯxаі"3™ёoшMo2Š2Щ(ššбHH’d7 I ‰сƒщщюъЎ%ѓю[ьлЇхцЭЌ,HгS…IГkЙмџќ;чDh9є‹–фЕП)œ"ВЧќ?=МAРŒуЯО ыіW,OR{№BМЅcFі/pИї№П/'јіzўшbaэy9>ПУ0xЛн@!Усa ЭVUэ™ј|ЉЧ†a Šb„A€{wяуlњ”у8ІZС№јъ§WUЛнЏнЙЫЭFƒЖЛкэ6W|6.ХЇY†fГ>ў˜'у uЛ]WЋ% У„e)xјtˆMќЋАЬŠ@Хaэ˜žœB ўшw‘* гЕŽ§~ЮџЌHwе>џрZ8WпЭц˜œœ`яь1ьсyоqQцЃХцКЎc6› ˜ЛЧh8BЎрQvŽтqt рЏoоСvБЬ‚8†юЭ@VѓААѓтWьЃбj!$ ЌŒзёwў4Х>8чTseћ™ЋчФuœeюXчy.zН~^œ^_fё‘Т79"5MУt>Хdr‚эvƒ“ё5xОWГџ<ŠЯЯВLдыЭgшД;H’­І№жMвоЩў›_ЋiX­жа5]‹fŒfSШ™D+§ПЪfJгЮ~nЗ‹ бэі…{лA›ŽŸJѕHQ1›Яqrэ›эубвљ№ВёWюVgа5ЏнПЋaЁ`v}ŽѓЧUёЪA)сбъщšЌъEљ‹@ю70СНћЏaПпK9Ѕ"а-WєT Н…†›јшuЛ`fьуёЙVХ™”ЪcРz#ы8K†{<œ~š§„ЈœьŽ\ДЉ"œЭS|4ћ‹lYЊ€VГ !ŠтJЧЄ‹jзa[ІГZэ4UEEа4 –iJAЬ#ЫщRHВ <ЯEœ+›€Яї0 АлэъDњ#xf ­&ј“ь&~oбм™ибэнF<§VЃ)v‚щ‚{YxнЊP‚Œ“№WЇ@Н ~{бЧžЎ WФDЙ–ч)sHlл;ћН~!”`л6дМ}ЊmBчLёФЙЈЊ&хЕ žЃ(4ШМž™тх{ЊЊ"Žу|%VŽч`<зr|4Ьч“eЕ^a0b~рЃзы,\€GEќСЖm<=}ŠсpXДZMс ЧPЈЊеW?rqhш:Vы%9ПXj Л З\НhќЩ6Щ2`8тњ­ыˆЃИ’­ћ|чЋрЯq/<ТБїы;џПухxє<|х}€јx‚ЪЪw 7Ÿža4#Э—vqч“†пїыЕH0–‹9šЭ†˜0‰ R„г…Ѕї А,Ѓ8>BЩQ Ыќё'п€nоBУ.•/\ЯУp8„чЛЅЧo5; цХI зuбЭЭЏEСойa<žHAЬк€ЏKFІ‰ГГ3фш‚ycй6”ЪЄqˆ—ƒ]CQ5ќ/ЛЗр-Іт‰šЅ€йDРД§S(ЭP‹zPп,W4РпЂ3О /LШЬ$HКЏуя}” ќ#“q…Т”[~њО8Š‹м”X‚ю0™Lр0*ŸR…љМ&dюGЃqБ2№_иL&ѕеТEŒYБ„”ї&ŽЈŠ*X&Т;їј˜Э7Ѕ ]ЧjYN:ЬШW+ Zэ6<џ|T{x=ІibЗлCиД6‹6q“ёŽыЋ”sзSЁљeЬиnЗєEZФu] ЁёўMёЪ`шюПy5YРŸРќqМ>z…0c^UЭсAˆV…ЌxeYјр+ябrЕ„ІkХ-cЎ—къКNыѕЊЊ‘mйФЅј(|?Џrї§ќсЩžЪЊIЪŸЈДwє$5AЅё>љСј%ЃКЄЌ<'hjDŸ<бУйtmв•eBЛ-ŽШ4 hКFaR…K9[€Œ5M“ЮІSъu{PЅТ’Т0Є~@2‚+Ÿy`—ю‘e™4›Яaй Ъ}SŠћсћ>CI‘+„9sюx. В”пЩюг>UЁDkipNH"ТрМГa›*AбrНР‚o^2AВ„ ‘ЖoRД9DЎŽOИƒМЛIџтс’TNЅd5еюQоаšЎгl6CПпЏщИgYFišRЛгE.А[УЫы–v ggЇдщtHFiђџsN,э=ЉЊv/k/4UЃНГ#0 )ЖŠpЈчy4шї"O9—bЃЂ,#)Ѕ4ЫrщЎ! ?q.žыRПзЃ$‰(-)T ўD(ЊŠХrNƒœ-%eђ“$!•В…vq§Јє!ЭRX–Egг3Ъ9а…h/3S† †”GЕЧЧ3e)г­л71і)MЅащOzўИЏдkЈb›N•п…ЖpОёЧˆ—мƒ7 ?ъУs*OU*ЙФ2Н\-!ЊпыK !l  бhР ЪcP%—"wŸ>}‚бpT+Э}ЎБоИxК§5иЖ pRI3”Щи8ŽёG~ЃсhZ]qFёNF“_Й Јˆt\зEš&RГ4—&A н%-uб†ВM…ЈЛнЖj~_“R,ЇMг(И›Ј–‰ЇКf6№7цї€§dЄœЫыЅат#шэ~с…[aЪ‹#[иул№}WшVžрY“ћј›*н*м7TDJЁkv{!™&#Њѕ—яћшwЛHr Фj‘б’ІiЃ~ лщжлUQ…Lг„ЎiУJ›TЙЩC7uЬfs ‡УsжВ№ИбlТќƒhДю7r6=Cа?wяHQђюЛ\ДЁhTлDЧf#ђ‡U/щ‚'ьЙ G№}я Ÿеsцž'ЇЯѕ5EA†h6šХЪЁ–ћFЉшгnЗqћюэZЄ§“ž?.У+ЈшyЂоѕ$ƒ$сЏі^\ўИёœk–išЦяљ=јЁ_Ш‘№Ѓ€№;0y6ŸsЃб€iœяžЧTU•GШ)…AޘЋђQО Мнn"n4œяJ ’ ХВЫ?ztЁђmжДR>JЦ’ššр{?КЩЛ№ымыj\Ш:•ЎЭœe’$сnЇЫ{Їˆр$ЁDФšЎѓl6убpФеBжвПƒ9ŒBюїћ2Y.С€ј 6 “gѓwкmшКЮЎjЌЊЊ r Ч№}Пєf *Hd]=уџе§>КŒиђV\YфrtoРY<х†sщџ[%LeАtцЄ1AВ™ AIR’O ]чЧњ›ќ[?XУDR’:+žкŠЎbΘѓИ”3“ХЕВ]9пhbЧuYUUЮw4ŠvгuчГ)zн+ŠRj,–$RxžЧУсˆ=Џв&Ѕ›[–…љlЮ ЛС†aЉФ*tќ\зхaШAїДJSU•]Ях$IаnЕeхЎМCLDЧ1ыšЮКa"‚Ъи(Фm™ˆАZЎ08IŠbc–^!Y–qœФшіњМwіPUЕі ‚Ўk˜Яg<шj ЧМݘОЯƒAОчZ›rќхe/|ћюЋмщЖPzЭќ„чЫ№Чs€Dџпжр?МLtПvїNЎklђ)' œ§§^П.-tАSцAЎєВ?ЗkЋj*ЫК2rE_€Pzї§Nџ"ьжu0KЏ РаыMˆяіѓ˜Œz…Rt]й$ЊШ7Ѕ… fЉаЌiиl7аuІiž@*ЧАэUDsBŸkrJОяЃлэUUkЭ+9;э*ђљ "˜”`І ёwцР КлБ]ч4†>њ7Аzc ‰Ымj€œ3Xзп†З:›'t„ž”%@чПuжУgг%L­žї2 ‹йMЛQHwсH›јA€fЃYDЦ2‚+6ƒ\IОTЋїЃњц›B ,Л_­Жk–Ѕи;Л|у#)"UъУTlDLЦТЫЄЊL$-!Їг)†ƒс9Ъ!*Sžяa4ЂЎ„oй6NЇЇBнќ Куj›ј>к­6ВƒОЦЬаsEЙ::ьїE˜†EгjMђеэvpыЮ+H’ДЎ*єЬЯ™ф"ŸuбšЊџџyрХЄ(Œ№Ÿћ…BмГШ™І‰й|FН^ЏтхСљ"Ќžƒ BŸZЭ&8Я‘ШIР0 Z­Vd,Ы*дh$О\Ё!MћД~™,SЭЅќ™˜}њ7џ  НKЖ!•@j1ИŸЃнъP­ОЎъ9‘чЯТ8$Ы4IS5„aXфрLгФrЕ$C7HJџ2^\bЧ­V“<БЉBљљOaЂгщHџ”ŸЋЭШq† Иž[Œщ‘3ŸЭ1~#Ež‘фј‘ў&ЊJ{gk“ЪхъdŽšІуѕЗю“a•|ї 4\€WЄšI‘>ЇJсLѕoT.Нъ;HŸ/>—юЦ[яМ‰ѕz …ˆUE vЙнnj.Ч*ьфSвѓ<ŒGcьї{VИ"ШЩG*ИJCeEEnёёй7Y1@ЁšЪ|:з№бщз1кRYї(ОˆF}лЭ6XDaŒМфрlzЦNЊЊreчћ тYдѓІi dŒVГ!”’шšЮЙЗW9ЯULKK|врёx”ћ kм пч›ќЛГ6ЬEѓAЋЌ2P€,eto"œ~ЫВX*ejЉ Еї ќѕL$Qљ‚z€‘Ф@Œп>№'ѓ-dТЮбВq6=у^Џw`НHGyQqГЊi0 “Ѓ8`™&/WKщ­ЊкtЕRBќYЋ‚ фбh„­ШСБ(БŠђЈК[хЧV№Еe{ОСpЯїŠЖf›ЭƒС PЅ>‡/гp=§ўЅаЏмљЭІ,KJЕ—:^ў7lB’ˆђ”†mѓйl†VЛ MUK_ѓ#у'Ї+#NbtЛ]ьЄ(œe&'cŒOFœЫм—ЫЬlў8Ф+ЕЂ‡\ˆ œMЋпхючOSй2Ÿ/ž™)IМїС—`й&ќ $Ы4…bЬhD•х МŠLюŒ)Є ŠchКЫЖШї}4lГйŒ:нTБT)"ЉЊтM1]ЈД№~ІнF;єнП…Fы>Ж^ьЦ]„'"§B'зЎcПп“ЎыB?зЦыIЫРsц•0ŒEчy†!2fЈšJ‹ХусИж&‡xЊ| D КЁ#=R џУњMтљ“"нЮ„=fљЊќžŒ"˜аїIЕ;@ИЃf7N`Ÿўј\›IŒоЂџўпњˆ|—LУ€ч9H“”КU—ђtоЕЏY\зСd<.ЪРДйnЈfќt/o‚0 Ј‘'џƒ $[ь”b<ž TЂ №т<Ђ8‚ЎjАL‹ќ бпbfГ]зыыUќŒg†4шhяьa†eƒB•Ѕ†} a”!Mљи‹џE1В”aY DQ„й|ž/i.џќъ+Щ2ј~€^OHДЯчsX–US‘Іщ3ёY–xž‹AЏ„;ќAњ&ўхTš Ј& šœ?ў‚LоТnњ,л†щртэ0›€j0T—Пt еСїшќѓOжhшРt&дЂe’џї8gbЄˆТvqc6›ЃйlБ”пж1фЫѓ|є{Єi‚хzЭŠЂ@в Џ‚'v{сPјGQŒн~‡nЗ{•ўZ\ыЙЙ&&#Э2Ьч3є}DQtхkI’˜г$EГйDF˜ЯЯаЮэЎx N’AуЏОі*К§n-ЧќЂЮ‡x!‡U kБP–ќЙŠœ еј$• ЇР|оx)ї§лПѕ;МпьшЦ‡œЫ+Щ)†С›э–ЫЎMЎБe™t„яxБœ˜‰lКжџ=ўcvтПBу‘)ЄЛшŠrJ ‰ѕ˜OЯDˆqэф„ѓфsШ!1+ЊAŸ,Ё*!ŸLЦE#^ /6і,гР|Нхџтoвьп}‡‘xaлxE9$‚=ын15пљ%v~ј)ѕж Йуz92R8‹#:б=ўЏПн#л019З\Мšœ™H ВЂщ|†4MјЦѕвпїjx€3ЮШ2mœžr†8™LЈи„ЙЂœа4=пС“ЇOщцЕТ H$!Џ,gІi:т4ЁЧЁлэJƒЂч’3#E!Ю2Ь p–ёѕыз)oз+Й4_,ј/§ъ/RЃaЫ•ќлwў(Ф‡уHНвogЅ 5IЯŸ/~і_ХяўŸџ‹Хђ@iій_Ь UQЁъ*ˆьНА‡|ЮЏ,c,Vз№ХЛOqНїп" ЯЃЩ$MfRbїN—`Бœ#Ыžѓ<А-wnў)~јЩ5,Wыч”К*™1+јMѕџFчЋ:@цsЊC1ˆzhк ўњfŽŒо@SMŸЛ]SRЁd„йGаЃѓљќЙtТћDЫ)kѓ\žуў03 ] #єШч§’u—Іe@UT8Ў›яМвsоу –iAгT~€yВxў6a@SE›јжvegŒ/Нџ.кэ&Т0ЊŒе—`ўШёксЙњЏ5[ЙbK%Кљ с‰т8цёdŒ“k,І !cФйсѕ№Щ&GЈ+еu<Ю‹žїћ=§'irl#§ˆˆЅи”i4јєбž.nрюјw8q?"љl|ўОЂЛ]2юblџ)фпЄNч6 чЁѓdсК€ Qlуэ{glD‹ўhѓkшtў=же(ЏзК\MЄjЅЉk-OOёѓЦь::N5Ъ8ŠЏ СĘ?yШџщ ўšї;›Ч„Š—,žЁ&вoЫЕ§ЯpЋu“Яіu4­т/ЫЯРWМy]—“,#R„[^y§гADœЗ‰ЊЊДœЎdЪ„›Э&eYЦє |A˜`&Ы4ёјЩ і<—Кн>ЇirШВ;ЎІCDišЂгьтщщn5›фћл ЬЕaБO^Ж"V=ѕ€"’+мяžЅц“Зk–1 ‡Оsя6ЂЈV„џ’ЬЏ\ќм1Оёs_GGШ8+ˆк…ъvўЪjх@"‡ЄЉVы5Rаыі Њ ќРЏбXу,ЯCišЧй#ї№Ђ70ї~†йEЦ&@VэХ•Wё7˜ ь‚ n\?СIџ jјwсИaС=њљеѓc‘ol73мџ>zMз;ыЭВж–сГjєЇы˜-цаuvГ0NсЂ”Х+пCљћсЯIV4Ьз,р›н ~хN ш­œЌˆ)хЯ‡П“А‚ЗДёэWTнAЇн|VфnlнF!e/љАѓхЖea<cЛнƒ5ЫћЪ!^Š?dUѕє –eЂлэ"ŒТRbў|q~™pЌ[mжШ8У п‡Ђj3ƒŸ…—}MзБwі№}§о†]pСГŠWШ!^ЃЪTYЏVА-убЛнюOњЂё#sšЊтС›ї щZ)qџRЭе]`)'sX3SхгязЈщє“ФВ,ЃfГ‰wц]ЌV+в4-чэ ‡5Йs,ЙЖв}MТo6k’ўƒС<Я+kЄ.РЃLЋ‘Іi4_,0N† њшь›ЄйwЁPZ`‹:Ом] •ущšBѓUŒбјBџ йЖM_МџlVпЃ,г .цсчWЙУЬ@š5ёжэPў Ѕ)сKoю)мџ# "”МVы8žЉZ˜Є 9Ž‹nЗKОяг`8$зuЈp;‚G9Иr—7ЫХ’ЎGДиющ?9yDVПdTх —ЖI•]<0‹|ЃЗЂ_ž’н‘ыћдiЗ‘ЅЉм>їљЙ‹IЪp–edл6-sjитќ[­ф=–мяC|БК""UUЩѓ=ŠуЭf‹‚0Рp0ЄНуHwСЃјZпЩЏqЕZбx4ЦnПЇС Яї*}у]дj*-— Cr\§ў€‚ ЄВpљ8^Іѓ6Сt6Ѕ–`žІjЄыd]эExTъHг4Ѕ7Џc0PšЄTІп^ЎљуМ+хGЪћхПSОsRЉ#“М ўIухЎ№;_| ­N“ƒ Јx –t C ™mл8›žrЏзc…„'ЊІщlšAћSдш@%>7ивuЗЛ-ЋŠ0ПV(уљ2хЇћп€iй dЕЯGх;Pb?Œ8Ѓ †}РsœБЮї_е0Дў.ЏЗ[Іœ^G*WЅv‰РiІткh[Ѓпч§~“џn№ƒыџ„7ЋqНvўUЁHљЗ4Ka[ŸžђHШ)qlš&kšЮЙOlaжvˆ—mnш/ иЖЭšЎГŸ*<‰žђv?є6ЃЈНу uЉT–)Œ0ХЯ7~ФяN,6э33Лž‡Щф„w‚QQоуђžШк=ЮєЧ1я‡{bЃžчqЏзGEЩм!ОJ€ШВŒ нрщtЪУ№, ‚ лfUдВ<—CМЄ`ЫВx6зш;• IDATЂлщr.ŸХЊЂЂбhp9žЯсeџ•KљнnЯBЋбbfF<xЛлЁь|ŸwyhšЦA №}ЮЅЛиѕ\†p]Џ8gyЌcу‡9CГйтзюн‘5Њ\JЦО|ѓЧ9=@1л[йT~Џ™Tj/>MSX–…/Нї.Ч)vxЅЧ.—LЙЬ#зѕЧ)ЕZэBzмqі†фznЩ'ыAВš€ЂЊД\,0JщЄlO=О6О U‰Јтй^Н)†DлНŠёј:BяˆTЄ)“І›xїўою(Šu( бA’КЈ1Ї jт­;џ їœe2˜xѓ5‡Дф`ЗHUTдj$eФPršиюvШ}hхђ‘\зХh4"Чu JbЕ]Њ=СжШh›—|ШКВ™—б_i„ыз{@FeіКZХ_Pу4jё П™§e4š#p>1ж;9З9Lв К-чЪ}$ уЦ5З€эц €~МЈ‰QjуеыЇ4ў%а>§E&gY^Pф„ kР|mтю э6ЅI:ъŠE_МП@цџ9^Œ"W™IгLAУЬш­WПwї€Bеј>Ž3М§@Ѓ&§m8n@ЁzЉПhн0АнlЁkY–Ul­I&‹чzєBOЎ|ЊжЂУ0с:.ЅI!RZцYˆА3|MџŒОyGЃ VЮќ(nLžfзh}ŒпxЁи]ЊГqЈнjСѓмВMЪф}С ?;;Ѓбh ЉхX Ъ(  УЂŸTsR›б0 ZЎ–Аэ ‘RЙ:MшьїЧp]‡RŠЖ”TсYbbЗл oГЁѕЩ+F†h4›$хг–QZGp]НnЏШчЩWEЄЉЊxѓ<‘ыЌblЮс+z†H@hSњЏlЃ'”`’ ?Џ§ оНбtЛhП*ОС пѓ1h—ЋiЋЊŠ$‰Бпя)—3+wЛыm*М žяSЇнE'…™КЂ(ДX.Є ю9|5WЈ( yО‡fЃR$q\ДЩйй)Нў…јъuЅi b гjУѓМbЧvГ]“ІъА-Ћnl[СWWžяc4гvЗ…ЊЊPU•Т(‚чК$њeП8kђ!ч{дяїbмdY†VЋEЏнЛ]ˆДОАуџЯ€WpдЊў?њ~ЙѓќТс™…aЭћ_yлэŽt]‡ую‘$ Е[­К7p™,Ю–ЛŽ эі{ *+і3Zo7шчŽYт‹П8sщгГ›щчаА"šЏ€;ЗoСл?‚Њib["KBn •ЊHƒLУЦ›w#ёџ\0t oпљзнOРP С­sx"(RB’Њxч^D&џ}И~ гДs9%PЃй”mrpўѕЅ( эі;Œ†c8ЮКЎ#Š"r<]сЛ\”„УЫM‚Uть‡xћV `›ьр ўЃєіˆгк†Ц!^д(Š ’‚FЃIЎчТкxдЭmPq|WќxГ,CšЅ$Ѓ@ЫДh]zkPA^ˆЯwQ)ЗYРnП'г4EŠ$ŠЈеj!KГЃјZ›aя9шіКђ E!Z.V†Х&лEјв 6"У0`š‚Р'гД0Mi8Jц ю‰L"чiЃfKДЋЂЈєЪэ›шКHтфЅџЯƒWŽФ?|јѓf‘VЁ#ћа/ > CмЛoоКЧuy:›a4ЁЂА‚KИЄХЯQЁпpрћ˜žMбэt‡’љJx•2ь˜?A:AГsƒ,mХaр@Ќ.2МЊcц A˜№­mмќжы)нНЕтAѓŸ`яј аЅxљ=I2ДZ-Мsћ_гvљЏ8ŒГљœЧУQ‘+=vў‡Ы2І4KЙлэСs]>›MбыѕЋyДKё(f…еpƒџќжŒЁšј?РЭaЊЊ ХЭgрѓЭ,ИžЧ§^qcЛл!ŠctлнcВч№ВЏИЎX:r–С|ЌW+Š6Й/ПЇi EQаl5рЙ.Яf3 У+уEУqЁпыs†8;›ЂгщpОl}&О’Кс~w€8ŠБ^ЏXфy|  yaџW…їЙ­D†мэДqїСk‹шE ш >ўЏŠ'ЯйrFMEБОД\ЈЏJNёї‹GNђvџЗ­F §оY–Ut!.џ*!Њ‚хr‰4Ip29с4MщЊj/"Ч’€д!ю_џпрЎ~Ÿ=ZRrрЯёЌ/EЂаСдљўЙ/Ћtіє;pНч;†Xк‡ь'”6ўZЭN^œ›тŠM€Y!…РbŘ1OЅйUЙšLЅœЁeлјя>nсјЛxѕжm$_х\Љz…aXo7иn7˜Œ&0t#ЯбГёљ1TMEœФxњф)FЃd!јЧ8тb/"АŒ3,+hЊ‚сpxQ›№Б 9 Ё:szv €0] _zqœСЖФЦ–ј˜Œ&а4 •ч ?ћf чМЭfƒзюнЦ[_x aV'•~ќ_Џ *IсчШeH,uЖђя\Ѕ’UА_P<qšІдяїјСƒє§?ў>’$х#Цъ—ыЙ1ГЊi”r† x>Ÿ‰СёzrЬLŠъbЗyРп|ћ;0UO”}\/ѓjIкф;ёcŒCд‘ze|оnlъ~єЄЮR€wˆТ€Гrt5=8fh†Ю2~Рѓљ,Џ™МВ#РŒІуUŸSжшt6ЛњчW'/UхŒ™Ђ ТvЛeЮ2љpz6Ођ9Жm#Šcоэvф:Ž,ѓО"^єOгВ8Ыrќ(žMщH›\6ЁГЊi”Є)8Эx>ŸU†ё№y_г5 QsEДоЌЙЂ”ќLМќ9ŽbОyы&н{§KO’—iќ_Џ]њ\њОBшєЦ‹ˆ'ХыoНŽў№GH’КЎ_9b’ВЎы‚se‘8Maˆ +E_RŽˆёщcМ§ыў„a&jџЎt"€Њ2žЬTмЙгХїОџоџBЛЭ#ЈЊve• MUАојјоЧПŽбЕ>|wУ0EЮ№Š‘Є,Яиn6аtŠ(@‡ЎщШ8Лr›ЈЊŠ4Kсћ!к‚|ƒхЊэ*яЏЊЊxzњзЎ]ЧzН.МЏњ•Ѕ)ьF?ТЩx‚эn'Н>žЋMD‰еЖнЂџYUеžЋЏЉЊŠ0‘$1tMG’eWюkе]eА]l0OАлэЄaвел$ЫаnЕ№њ[ jЊиi ЧџU№ЪбŒ"Ы#R­DPRu^T<чюnя|ёmYlŠчљв Ые vЃёh ЯuЁ(ъ•CDА,ѓљ§ЎŠ>zЌЬвLЇ]іJSHСf— бКCй!gјш1УnД'й3‘eТЪ#M=ќб‡o#ц;шЖMДZ-јО_” ]хZˆЊЂbГнЂйh` эzЎ‰Ы4MLЯІАlН^Q\B­:ўeшжы У@Їе†iшuгю+|™–%Ъ^2FЛг•ŒŠчjEQ‘ЅЧAЋеBЏз‡я{Янз УР|9ƒmй†ЂЏЉWыkВnO№}—А,нN'gHЅЯ-Мz§•M†E™бЫ8ўЏ‚WŽЬžоLLЙtЩ&Т …qЎяљќ3я}^aе’Ѓe­YQ$ЫВЮЩВ,žЯІh5›ТG"^ВЎыTеHЮу+IXUUсy кV›Ÿ.…Э~с(&ыЫ…]Бвd" KоymœLиnžтСm“OOˆyШЊBЕ Š—э#)5№ЩьлИ~вgЧйЁлэršfЕ"ЯПкђЛА„c]З‹Œ3ж„й‚|p_{Јш:яЖ[h‚­!|š‡УмN­%БЋјЊш&!Щоэїє‡ШхЁрљ>зЎхьК–Ћ%Цу1vЛ-ƒЧI,#Р ёђј’я{6%VQБŒЧ9Ыф2|1›Ы%lЛСšІ!ItЛ]Ю9щ\-т>жх$E!pЌs=‡ƒ!зa9‰^†ЯЃ?nЗкИџцНЊунK:ўŸWŠŽъДЩ|ОŽыРбБъШє"уЅjH’ЄјЪЯ~YGWНƒ+Љš 34ME’$ф8К]СKѕ<§^q<с!ПТM”њk†a`:=ЃбpАЮ|zv‡Мь[0є”X.kЮUх8WЦf—бhќ*Вш)8,KЇ^sЯžІdйmpeЙVХK(‘BiрЛ} Эц+0 •‚ыК4 с‡wКЊІ“—Zhš†0 „!u;dгоu0рчІнEvх_РTEЅљbСpH…!vЊЊ"Š"*­#ыјъNІeY8}zJУс‹ВM…e™фћў!Oја žrщZЏзАLa“™W4ћ9Z/šпу‚G­ы№<I’Ђеш§~Щш{gOљФsЎŽ3ьЖ[є{}ЪЅЎащtHˆнІхѕрЋ…н–iтєєŒЦу В,C†d&LУ$)"gЪv)EђѓЃлЏНŠFУB–fUmЬ—rќ? ЏTž\&‹$к9ЮgљFЉљ‚уsЭ@~ѕеWpїўmlЗЛ\!Qp& ~fЉQ]г0ŸЯИлэBUUЩ‰E‰В˜НS№„Kо+•O o6k ‡CNSСї ‚€[­6@\ј4з5‹‚‰ˆ5MХ|>убpШЙд‹њС эV›sq ё9[ƒЇѓYсЭЬШ8CE,"9їОvMЬlY&ЏжkhКЮVNНSU•їŽƒС`Р9OXъВU№eщf–eŽFќън[Hг U^љЫ<ў/УK’+еЋг‹p„п/Ÿ<(”M^tМTq‰у?ѓў{HГ„2ЮЊjМT}Њ žg€0ŒЈгщJE’œиfГIŠЂREЅRrE}BІ23mv[ њ}’Oq"@U|zt6 ]ђm2 BUѕЗ|j†Nt:gмО}“мнcЪ'k03E1гw‰ўнзші&Фœh†šюЉJзЯш{Ÿ|ƒ&“џ]–tЙЎ‹бpHžязRЮ„eћЉЊJžч!Ы24›ЭBщXQјОGэV‹Œ$IŽFpљ1Є іŽC§~ŸЄžQEd™&tMЃŠФTЕM ж.TИi8PЅшYD“QDнnљФSFpŒR9™™lЫЂггSъ2"ЄЊŸ№x<СnПЃМ †sU кmwP5LгЊ kУѓ=єК]ŠуDжžж”JЄвЗІiЃ? j1ЙhWŸZЭDqS-‚уњ9)ДhЊlрP–Ѕ3й&љО_ЖkŽ—Š?Й:4^Л›t]Ћ“Пьуџ2МR ?.йFЉџ]ђо‹‰;Т †Ѓ!^ы6›m‘ш–!Д|рY–…щlŠq.tЪeH Љœ1шсКюљ}%*ђ\X,чh5š…№‚ш|brHу%>|њ-ші}€“sЛtЊ ьі!Zн[а•-ЂHlОHi'!† щOg&lЫ:’!hjŒя}xП‡NЋ$ЩW—N Й&Gˆ„ж–є’ё`ЮfSŒЧувЃТ+ТНnЏ№Ј8& `fГzН^%Uю€nw;ŒGуќ ŽфЇ„ащzг0`šж9rшЖ:ШђмfyИЖ‹эyžXЖ6[ЕcШ~Ђ’Ін€чžпЬ":ЊЂbБXфіi­=В,CœЄшv:№< )uЭР2Šщt*ъ+ЛзВMіћ=Цу1ммE*ыц вE›, 4ѓОVН7DЧѕr3Ї№ш9HVЬфк'зЧ§Хѓ%џс•д&Ž2dЋфіѓ˜/‹UЃ0ФћМг2нЌŠ]пеj]гaцe9ЏЌhЛ8Žaъ УЈ$џы“d’$pЗ^(Wrр3Kрщюзбhш`NkF–ІиК6FЃ|їЉЌљЋЉ›0ЏнR№№баЦвšКАЎNg ўєєW0uJATдUM<ЯE_˜ŽW6DЪvБ, ѓйMЛНZ–Qi:?аl6QбЖЋ r* ?@F…№BБ'WQQIвнNŽЛЏ‹ фQ5Ќз ‡ЄKѕК=пУh,\ўЊЛЙ…Ш€ЎbƘc0шзyReЯїа ЦсyfиVЇг3ДЛuAмЪfМ’"ˆ$Mj“ЌЬЉюї{€Жmу`Ѓ@Ю2!–е@Е>&кDEš&и9;єsсжъD)ћfсTБбЄе>K8кИџЦ=q-џЙџс•Ђ6ŸЋ>#|~Й–›?˜I_|%šљв{_ФvЗ='ЇЄ( Vы%9ЅЪКЅшр;gЩdзu‹y|лЖqzz*ЉwuЩєJm`š:xxњѕgЁ ћЪтЙ^ŠvїUЪIhг”бiАД5NgЫnхЌ@QYЖЧї>љTэš Г2ЙqїцŸЦ:"пTЈŠаЦУЮйЁ—зиU'ќЂ4&V&уkиэїЕ6٘aYNЯžж#ШЪTžыё!№}tК]$IR’їeiš˜Эgшvк R*яе'Щ(Ž`шtУ(дСх@зuН{jи Є‡ѕ~љ)Ѕi*›MШЅcЕ64ŒB„ŽЯ=8}ЩqLЦcHЭРъqtMЧl>Чh4ЎGд+зs1 …–ъ—%Ф_1ш*JвѕО–ѓ„бАmЙS\;0pуц єпЗђЮЦџМrЎLІš+<(ЃЁ‹"Ь—ŸШ0 ёіпЦ`4(:w– УщйlŠVГ ]з+K’ƒ:"Ђ\йEШчя*’ц†a`Лн„RЮуХnhŠѕ6ЦЃе/Сjœœkў!УЦэ𜉉нцDj}вЉ\zex§5Ÿ<|Š ЃbIЉЉž№ЩєЋИvв/&Цњѕ”Gђ}Ёф,ЂˆМ˜з0ХВЕлщЫЋcm"' 0Ѓнl ]Кќ:-гФ\lкxѕ(ГŒz2Ю!†§!іNС)Š‚8ŽрyКноСУ‰ЮEpЎыa4 ќкrPQЫХRјИ$щЅѕŽЎуbа"ŠЃZaДЎщ˜ч@=FЧа4 Іiœ,'šЮЇh6а5­"ЯV7і–}3Žcєћ§"ЊїX…ыКHг ­fыHС4е"WЯї0 сzkfД;м{уВ4+_ў\Žџ#xЅЈC+;іQ2њ EMqыeТч7ž“8ЦзОў–бŠЊ >ЈчКмыѕЄР%ћъ\Ы™p.fЩэvЛФЬ+ёyYЪ)хjбuМ<"…8xxv>џ,tPU№й<С›ЗК9Я…qнrн*Й ІржЕ?z еъ3!Cœ$јю‡_цСш.t=;WG'uЛф’МˆV&оюЖPЕp УЅ g՘Ѕ$зЩј§Ђ(*Ў оn7UЦsx”ъ*„ ŠЂфЪЯЬ–eущSUW‘Ћјъ=OХ’“-л†t ;а 4›MжMИЋKŽrаЛЅЄ;ЎƒёhЬЛмyMе4ИОЫЉ‰тbbЌтЫЈ–Eчёp8*&UU9IRИЎЫ§^ПAV№rs_Ибљh‹2ФqЬтakђй|ŠбhX kјjeAEа †.\ўф8К{џ6,ЫЈэœџyџ‡xх8Mф Uу|й/6^RфnмИŽћЏпУfГЩ•‘gшхц5‡OвъЖК|ˆЂƒ^{Чm Н@г4aлv=Z:Ђl Єh№Н5>:ћ5˜Wсћ!HП†v#‚чmРЌ ЫrЃюŒЫWў;3ЄŒ'–‹'иљ}X&уп~t‚m№M z:ŽВ ЈЩ%Oў{,гТt:Хh4:OЅЊт+yЋ4KЅѓZб&ѓ ƒ IDATХ­f F•†x€GЅЭsЅŒ†‚yc&6л5ЄїE-Q_gg(p\усA˜Gљ9[Ѓп ЈПМ_mг4ЁыBм4 ЬfsŒG“Кащ9|ЅMвœ1:эЧ)˜AнNЊЂUН–mS}/ У<‚saYЫlг‚iZ5h":Кj‘iŠ““AяdЦфd‚Зnц}‰*ЦЏдђ†…(15еЕкj ._V|’$єо_"USБ^Џ‘$1Еš­Bюаиќ№;ђ}Ÿ&TEДлmi0"ŽуgтхvМB1žЮtlЃ_І0жшж+dбЇdLІ‘iЄd).јN†žBUS|ё ІO?›’—t№НOОEЃaŸDвш‚ѓ Ъ‘YЖMеЩ|lЗ[ъїњ$UИ/УWЫZЂ0ЄnЏ‹0щоƒЛdzEђЇgќ ђœ]Vf…r‚,bЭ;tБ’Љ„—9ЉК№ђс…џƒХпћуягwўщwpћелЌЉЊЌПL „ъE•ЂžусЇy<SKHЉ_/ъЕЖЅ№Єљ7шёЃ?С>^ГЎkWЦƒŠ ŽЃ€юНўЫxДўЫ|ыЦˆВ4сJbщb|Y-BЊІbГн№vПЃ›зnrХ—јйјќg…NВ”>{єпМ~ƒ УЈък=_˜б*рГГ3В,ƒў€г4ЙОrџˆš/цР'ТЪ &Т+ЈљфžКDЎMЎчYО^–>“ЊpExђє НrѓшЊ–ЛП\MЭ &Eъё“'шїњhЗZЙъЬ№eБ=EqЫ6№П№uNтЄ2>~zЦ?РШ'РzЃUr— РReщeХЫd‰aєЗўЧџI”АІkеС~… ˆШШЖиnЗЌЉЉZ!byх Œ(ЊЮƒœ§нnѓЂи+у`га(ŒQАЎ=ЧZцJШа €Рžч‘ІiЙўпѓM€Иa7hЕYБЁҘќ\xdЇiJžчСЖm>Д*НЪЄЉЉЊ‚ГgлД‘qі\xf&г0‘e)aUеH!хљ&Р|Pк–еfE–iт@ЖыЪ˜išфy>В,…i˜\Йž+сГ,#Ы4ёэ_њ GCvЃєS7ўrXЕaЧєЧјРџЂfИѕђтs{ТŒёo}№§!FУ—Žї5вѕБAУR:шЃO>ЦЕ“kиэімэv#І ёe9ƒ(Оn6-6є!КMгј`2ОУwзsYЅ˜Ђd…-1рЏ„Яѓo €ЮЮЮp§њ оlзшvК\њќ^Ž—mнАјш“јж+Џ`ЕZѓ`0 JЮьRМьФЭFƒ?yŒnЗЧЊЂ’mлЈ($_Š—?‹В— DƒnЄ(еIу*њxP•г,ЁХr‰ЩјŽыpЛеІŠXРЅx1ƒЩ0 |њйЇИuѓ&Џжъїћ|Ф0щи€ЭW Z­&>|Шуё эї;юt:WЦЛѕIЪo}с-Œ\5‡џщџХ&UЂьRiКBЄ ЪчPzйё2I§кн;_Ÿ`Гнч6ƒi–‘( Щ(MГТ~0KхпSвuГХœ‰ZЗvЛEлэN–…\ˆЯ'љЗ єБпя`™ ъvП|ŸI&Љѓ ›ђЭŸІ)UСl>'гВаыѕАп;”І)В,“пkŸŸзQ9†Ђайt ЫВаА…Oђz/ФЫї’$!Сn8ƒ&(bh4Днn‹Rq>Ÿ_žЩ]бн~OžРВ,jЗлияїђ5K[Е§a[6Z'ˆ’˜nV7И\‚Vb‰+3 §yefЌnWxѓюŠВщЅЯбО tjЁІсьф2мЫ2єR\ŸОе џсTЬЈJ]WлѓЂk›mWuгщ„…п5ПЮѓŽэР0Ъ2-gaRPя7:NiхeŽљмo]ЌЏё*‰ЁT›эоФг)$‰дap5ЩдvК“ЁЙŽ‹§сЧqЕ†lЧRЯœeiЇуuЩU–чHтž&›‹у“ёU]ЗBћыуїдD8X.(šЈБЊ+д5c2юЋLtМгКЎaй6vлМщЌwЮižaЙX!hŒЎMXћŒс`ˆѕvсhгДкыI’ў\ЊˆУk ( Ѕж(KLЦуvœЂ(ТЬ›ЁШѓžжИУsћм’\аЫщŒХТo#щ"/`ЧA–gWЧяJNъГmлЦЗпг2ћm^№ќj?ТtхЁ…~[ЙоЌ5jќШ>МЬOе˜ŒЧќлп§чЫYSl€Љы[fцСpˆћћ{ічѓ^V•ˆ8 ­4[Чkч]n>з4M>žNА,Ћщ‡б%n“4Хd<сЊЊZ_:еЪњ-Š"фyoцAљк !eл– зv8б 1%o K( кщt‚?ŸГ&ћ’[ЌВфщдCœ$]YдlAЉ'яКЛџ‘ЕFЋ„Bp…xs{ƒрr†‚ЕЊcЋЏn"HоZg“і: „$‰1›ѕЖŽЌ ЮЕћУ–e!TuХ“ЩuUqЛ@Ї)ЎУ†дЧЊћЃ3…ИQє Ы3Ѓѓйœu]rYJЇчсhФYšі"@н}XЫfГцХrЉ/ђ, ^­V‚‹F`ю‡pMе—зл5Мљ Нq#т(ŽБ\.v/Э[­ђПяоНХЭ+”Eйѓ~|Щѓ_4ёБДHх~>C dыьыЯЭNщЏёWŒ(ЗnиEъbYТ0@UV5ђ$}+X5IxЯѓ”Г№ЃMiHE:э–n0Р5#K3јўМщC"Ř2- лнО…Њ{mF#Є_"IтžZ€Љ‹ лСnПЧhиЙНш‹zЧ№ІЪЂhл+rз4Oё)q<aƒСрј”e Р`и™ а•HSЄJ—@—Эѕ"ЛЂ(1ŸЭ[ы.f–їF3v0MГЕЉЊЊ ZŽПqzIАXјˆтЈЕќв/-Гlьі;L'“vьЛs‚‹Й8Mz8§\Ў+З­Ž ЗБювЧ$/2иЖг”НLњ4j-I’ OsLЦ“Ћѓ UјРlъѕЦЄед‘T мОљўkЉї}@фzЙѓ_([UeмиЋW{ћ­ЫЃЙKЕвч„GЃš "ќнќN'2 Ѓѕ№U›І4фФ|>Ї–­^)MЙ=IšN'T”Eћ™zюnшА^ЏiъMIЃ!Ъз!(IИC‚ *ЪBˆ–бпјкб%ИBй)imЋЅщeЃО ЩxŒ8Iкм™T(KЃгЂШIч`R†ІЭuЕ_qcuГЂЫхŒfLкЃFіx<’?o 9хА\IуњмŒgуQUƒ­язZЃ5ь$"mL’убИЩm–эѕЊёu‡‡=л%лЖЉ5›е|$ŠЂ гДр8eyJктеЗfYBYšбЬ›ѕюŸКOD„4MhЙ\QашЩu%…•U-•A‚EскЯQ'ЄфmBˆЮ/Zв^ШqкlжДXјз Д*“$Цt2EY—$IодKcžњЭЯОСx2nяЭч8 ^h‘fЋЇŠЦ§Ц#Є…їjxЉ_Qљ<№”e‰ї_НчwпМУхДаКЎйqœЮ'!uЉ\зЬ=!~ырСEQa6›Б–ќgeƒ' в,U;=НЈS–šиˆo–7УАЗНafиmЗН-ЇfЬi&Gq„љlŽ<Ыј:ul›я7kЬЅ§—Ж ЇіJDRHoкь8.В,ƒhHЬЬЖmуx8ТvVn/ђХллЎЁ,KцšсMЇcеЪУ:ЖрrсЊЎ0žŒл†Ы]“Ѕ,Я`Y,Ыl›(ЉrГн`:Жбrg‘оП FQф№ч ‚жЮŒlлЦQі<&Wš fEжЋš7уJ‹ЙЏ|!лЯ/Њ’Ђ0”њхюїoEqŒЙ7C–eЄЅ)ŽЧЉє†Av1O3Tqc4ЩhZоЙ[1LњњЛЏ0КЈКш№ГžПЯС7zž6гB@+вuЏŒŽy­вfŸ#^Vsљ‹/ПРЯџъ/XхёЧсн~‡ёhЌ; ы|Ь&ћЂGp1пЪў@гX(CдUЩdмF…:ОyЛ+[&СЭ}ФIЬЊrœІ)Ђ&AЏ+,zјцr…“‰У†i o*†M“#^-‰ ‰јJ?ŠЂ) ‡CЄMЯq^oж˜ЯgZI}<ї#А,ЫАXЪъДRk\‚3 Урс`€+йkЅ’6‚‹тK3 ‚mйˆу˜Г,УЬыŠAвzЋ|ЙeHгЃё›^&э1eoE;ўšYЧАmђsY–БeYp]ЗЕнr]їwwМаЌЛЎёњZX7еёйЬу(ŽTž™їЧмС€ѕCМЌfЉBЂ(фле ЮrAgfЦЭ7ќіЋ/Qнѕ}іѓїxq-Ђ!­Ћѕ†ZлhCѕŽН’}Fx"BХќлп§Іэ#VЊ§lІююBk'ФZBž‹Ђ ƒWbЪEgЭЫхeQВоЃс1|“у"ЯЄ’!р:.яv[LЇг>kьпœИ:HšІ№g>Т0dз`ПпЉЎpZ‚ў!^mГ„ЩџљlЮqšТ0 NтYžЗ^uP 1Џ№аŠI–ђРq!„R,!АлЪЂEQ–м9#?ФЋП—e‰Њ,eзД0„mKweНbЋЮћЏ\ѓъКц<Ы1ѓцИ†У!6л C4QѕGёЪRі$ž#Jbи–Э—Ы шкхx­escрa2ž ,‹цм*œŽ'іч~Ыx ‚S–%ФЃбaТБўюћo;т~+ŸјќчяSёfOQйcаЃъбЖЖ—ўlё§0~їw?р_ўљ_' VЋ г`Њ?hHvMЬE–ЅX-VиЌ7иnЗ4иuн‡Vъј2 yQ`:™ к\VОєвУOы{ёБЏВ,1i4ёх|Т%№юэЛіOМЊъŠО(Žq<qЛКaOК$˜9M3м,oАнo„fѓ,Ыzژ–c’У›zH’Л§†№ІS”UѕI|GuЩ1›ЭFЄI‚їяПBUU“тКЎ!H`юyТћУ_м~$щр5кnVЗ8OwвJп0MдuЅ;EHУмјSf№ч>ўјЧ?т—ћ ,o|”eЅѕyAѓї xщУš Е…dюKж™дя›WuЂЛЯЏQўЧћ':э|{ѓ*љ@>:є=Є5зА-›ЇчYŽ…П й7ђ)xuО†08ЯslvмЎnЩ0 ОrЩчLRYТы;ђІЫB?ЏЉЇќљœКHчiјЦbŠЗ‡-ИbZј‹kЛ,ўФТAŠgF!OGмЎnYЋЦ~ЏmяЉцwїwМ\,сиёCовёш\eyПпСЖ,ђМїШЦOР3˜MУФvП!‚р…ПPЯкгŽпD”–eбёxрјOџ€бЈ1•+ТЫšПOР›зфA}L™”ЁšrДшыщЎ‰ЙŸ^&h`л~ёЫПцџђŸџ+ cї Ђ‡јгЖ23Ыц §aAB7щ~J8Щ†a тi’тx:ВшіOТЗ•_ЧE–І|f(5УГ№В>!ИЌKЄI цњzВЏfЊmлЊэ$яvЛOљђƒПГ,(eYЦEžуx<Š—yєцљШ ’Эœ†фM=eќ№IМ–Q ‡УоtŠ4KIЃ_}ЯЬА “ОљюkH›ћжюъХЮпOсКІfоЭЊ(ЌЙќ Ы^ёKР+ЊТbЕР/ѕ78ŸЯмГuъV]Јб^&ЦЃТ0РЭъ†/С†0ИчhжУwчg2їХ| и›z(Ъ‚ЏхU:^йeЕvJ ьv{,—+N’Ус€-гBžemє№^mёЊЊ‚ыКјёўGі}eC5ЦzямGŽпUU[IлтУсзvйq\йМhyƒГT™pзмч!^з$K9M%э%ЬцsЄiЂS3Х+-ЎДюЊp:ŸБX,†!І“ ‰жvыcx"тЊЎр8южВc]ЋПv:Z №зvfЖ› &“ 7:dјОЯAшЧ{^cљ/О|УoоПii=MјЂчяЧ№т‘шё;Ю`K%ђН|#=JтПњѕЏ`к&EбЪ%њ ­I:™І…ѕfCЋ•l~]”%‡cDqHBђhј.N’OЖmлДйЎUЋMpЭдh)ŠЂ^ЕRЧГA:6ŽЧз…cлвN)ŠШїукЂ§ЏФŽуHЃжŠi$ћаR†№ч>В<НŠКЈoHйяšFЧу‹Х‚ИfeлВркnKіж –­мЉЃ|mYXпЏiЕ\6.3%Рd2Ѕ( {_ѕJTзcZэїћ–зIDH“”ќйЂ‚ѓˆxUШq]чѓ ‚:9т%И`Й\ Nb"ббЏЏ]ŸlV^J‡˜Ygg6ŒZ2|GsъуеjЎс8.ОћљЗФЪИскіьЅЮпрХУјМsdэЯGвцЪKС+KЁ?ќ§яq<ašІV ыr\w€ЭfЩx M­8I0›ЯчХЕw{.­АПБSЊЕцзЪNi:™ЂqюlЭ{лЮІЊЎYЉ5Z;ЅВ,aCй;З=oК*рtмЖЛ-‹E#{‘?Oг‹ХкжБoма|bUWИмннСѓfђМИ'Б4~HэZшКП4Ѓ8Я0MЎыЖFŸqТѓf(ЅƒNпЅEзO4cR*“вЙп,в,Хp0„бшїхAЖЉљн~ПЧrБшйZхEљlŽр<вfЕ—КЎaл6жы{јОпо{fFš$№§ЮэqљCWјјњлЏ0›{нyМЮпOтzЊ:U>юŒ‚Л?Љнk_ _ ^хПћю[О}s‹ X3(PЙeuEUYpEфy­о—ˆˆ”Оt<sЧд:НАЂЊ’˜БeлДоЌyЙZЁцZхкИYY‰ё[љЕ›xу8|ПОoЬzєТ‹тЄЭYЉ75Ъ‹–nrКœЩ4Lr]З% !(Ы3 м arБnМTЅІ$M(ЯsеhUŠKЦ†Cn ­ƒЛЪ… !xЗпaБXpп•(ЫRžys„Qдu“~!RзŒŸу8ИџёŽ—‹UuЫ“#!Ÿƒ3nW+Rї—К@‹є{l[OGr˜–нRSН2MЦcfЎIЉRфёЛУЬl[6…QˆškŒЦ#ЊНo3Ўlл6йŽЭI’Ј1!шЋћ#w3|ѕэ{вШлЏѓї ј~єАёz ТJuмюД5ўвЫР7O> Ђ~џфyAmŽњсŽiZXo74ѓІ0 ƒєІ3$ˆЄЂbFEQtol]ГХ зqhЛлСuфXЖњwњЂAЖmУБI€Ўљ‘ŸeišRžхвqZ‰[5w›"ЯсЯч:aƒt§™šLBЉ^­И,ЫжщZ-ўi’Р—:сžЛŠ–Ф‡ešиl7<ї}К ЂкІнўЬЇ<Я;=pПЧqxЛнвx<išвнDг0ІiJуёBt[GєЩ1lšСDУсА‹6›1iњiР›zъхЂЉЌЙ]tkf:žNXњKдЭ" -’f9Эч>ТЈ-VuЯ_3.Т lЗ[,ќ…ВЊjуУ0( #,§ЅiŠ~З3ЭШ4ёѕw_a4ЂЊъзљћ М@зŸЁх ЁMмjОJšУBЗќv~Ђ/ ŸЅо}ѕяП~‡0”ІЇ Ыэ˜@–Ѕœх9ІWn/jltТX.—ВoGcœЊŒ„\sЫхЬОlTў€ FDУ—Ы•ДS"ЉџT.0u]УvmО[пcЙ\ъвВvСBp’І& "n’џКЫ ЛЎ‹ѕzщdкsCбнbВ<‡э8lйв,mЕЈZDŒ(Ž˜k`ЄвŠdDš9f3AВю Ѓњ”e)ЅgВбzwыХ‰0 БZ6…&Уш‰\X6ЭЂгuЩёЦК‹НЉдзВD9ЌШ~#r+Ϙћ­єOOѕ !8M ‡#6„!tК1iЭ(NЇГ,šИn;–њГRеrЗ0Z-xЇъ7дї}Мџњ-ŠЂd!шuў>/dXи˜ЪАbџЊDБќыПз4uцхрU>'‰cќ‡Пџ=ŠВ э”E ЧСн§LаK?О~ъF9†Ч!гДTз>§ЖeбvЛУd2!•WыТн_v8›ŽЇТ€Д5vJd2WVеїlшЅУJšb6›QtеЗCAyQ NbLЇS§Zхž”ѕ{\hЕ\IЗД‘Pу]hт~НЁ›е UUjжл(ISŒЧS@n{—mл6­7ЬМ™VѕnЂT™яTE†#Dqдіэt"‡Уw 4аЄ є~ u]SQ№<Т†ˆЮ‡qЁЊ*ŒF#j[ОWУ0†-—KФQЄ‰ёяс‘6IDAT\$ УaOЪWыAгоˆˆТ(†яћШГœЎ[3XІI?ћљwFw~ЏѓїщxЦ>АяРб РUYY+4§s/Џ&ьh<ТЏј5я,Жm ЧуЖmscЇєQХHЧМь 1[гМ(8ŽcЬIе#ъ“6вˆуžŒˆк>ЏM_і›=>мW%џYˆŽЂЖœыћ;јsПС\4›JЄ4)№ІТ&‚SфfГХx4bг2ЏœyIЗХg0#Ž#,—+nЖфmT$)—…ьXЇЉцГ@Gœ$<ŸЭ‘чy;~BЎычѓЙэСєЂbжщDqc:™2Р(ŠЂSЫ2Бйnрћ~7ІкЦŠЕs)Ы’ †C$IТ]ЬХнн=_Ю­RЅЎkМ}ћ–—_,QIНяыќ}&О_n[^љы7М#Э6МЛ9/ Ш ў›_ќ%ІžTwШDНрУaЏ(+ЌѓЖtщŽzр‹Ђ€0†ƒЁ”…‘`ЩЕЛУrЙT“їž4э' ›1M=q3@0M ћУž‡У!ЌІЗi}^Ћ ЖaИмомpЪЂŠeYjfFКQA‹‡VЕ&I’№дѓP–%ъКf!ъЊт0 рym…ђ]u–‹Ђ€eZp‡“$,ZИИЛџЋеfRЪќH…V9ш ЎkžHŽ" !`;жл5Ÿй0ЭжэхК'ˆ.Ь•N/!/ќ’Dі1M“г4Eže<‘Х ш}<Дt27M‘8Я3ИЎ г0‘хŒсpФп|џM[эWœХзљћtМа#Т†ђІЕэ˜жНvѓмYJПXМœddл~ѓЛпH;%w€ѕfMуЩDѕжаžхЋ%ЌсмЩmRп_ Ы3ІAчѓ†ф•Ел k|ѓїnKžФ˜N&ЈЙІК*С\сrОаТ_ Ўы‡mщŠGаpЩъКІЩx‚8ŽaZmw;,—KКšшWxв;ЏQžІXј \‚ мЏзфy^Kz ŽаK6ќХ’’4…у8ДлярК.oМ~ЛPъ9Шп B蹋ЂhŠ=в4mкdђёшh+T–%,У ЗqТ6 Л§žš~#]†Œm[<Љ™)/rјОѓхзqiГн`>Ÿ“xH‘бяЕfoпtЃѓ}DQDD_ћžІѓ 4?ШзљћLМ‰+о˜F'ыХŠд.Нч˜ђbёФišтgёўїџњ?XЏзˆТˆпМyCišBkPЪxдœЇcВЇI‚сpˆЫхТ‡§Ž–ЋEС\ѓ'ёъыtїљ|:бfЛ• Щ‹\ѓ“№ ЦДйmpЧ†dpY”ŸФЋп…QDодƒ тѕz4Mйї”Ѕй“№Јц–iёРqq<y8аЗЗВyЏі@Чг žчqХt:0Ÿћ(Šœœт›lЮч {žGЛэw?ўзq™ki і)МкZ‡AHгщ'ЎqЯeY’у8œgљЇёЭЯђ<‡75Љ›Оўю+Ў+•фзљћ№‡—ZkАньЖWХ&Uаš=А*иѓKЦƒ™Mг 8N№пџёђлЗ_’m;`ЩчЂ<аєш„e№vЗ%wрbюЭИЊЊчсDtПYУЖl^ј>UuЭєD|SE#г0А?8Mњті 7<“'с;ЇрЂШщџн§ШяоО“Х­—ЅBQ]X1њ ФВ(б_$язw4™x˜N&Ъˆі“Чзё аvЛ…эиьЯ}Й=ЯLІibГ]3зРjuCšѕ№'ёњ˜$iŒЭvKяпНя„ OФЋEAУЩпџхїШГ\ЇеМЮпчту№Тз‚fžѕrбўфё‘wзKУЋТ…у8PЄк|ёЧŒ- !лж\џ$|ћ`|РЂъ“x%ЯRњпŸ‚W… У0šж”c“‚Ђрg0DUф a4Ъ”УбUUіЏЧ0Рu­Zr>љј“Ÿ„WŸ€ЋКњIx5I !Јmзљќ/INЏeaІ—л}ПЯЦ›:+•ЧчulIZ|љШУѓтё0зHвєADZu§Žv5ё рц3~:ўO=~_№Іa"МœAТ@–Ф† adI w0A‘-Б‹Eљ'Ÿ‰тOОўхПџј} O§Рўuў>o’2GеФ$нцЎ/œвфu=зфWќ+ўљxю;b3z~ЏуїŠџїР›}*i™ Къ+їaqњ+ўџ|U•№f УФ`8n#гВPy/В~ПWќŸovњЭ`с*ЇаYm=РПт_ёЯУ33 г–eЏёыt›зё{Хџ™ёЂeс+S~\LLше{ьЇWќ+ў9xљnжмŸ5"ЁЧzПWќŸџџа8№в†лIENDЎB`‚pentobi-7.2/src/pentobi/manual/en/rating.png000066400000000000000000001222501227240712600211270ustar00rootroot00000000000000‰PNG  IHDR№ њr;DbKGDџџџ НЇ“ pHYs  šœtIMEн.5љдa IDATxкьНWŒdI–%vЎйЎU(-3RWf–ЎъЊж3­3Фюў .@ў40 ШO’‰]р7фЧ‚Фp;рLГgf{Ї9нгЂЊЋ*Ћ++uЅ ­Еђˆpѕ„йхЧs№№єHQ•=еSхШ ЯЯГ'ьšйНчžK?њбџ—X,іŸ‘@А1Њќ=nЋџžќ–šxџ4№˜Ь "5Џпя'^ѕ^щ|>џгггџ“144єПž9sц,ЫJ)<Рув№€MМgРc†f‘ ЂJЧ ˆžOk}АkЎюPСOƒN+HkХŠŸ|2ЭћћљсI)…ыКџ2"‘ШŸ˜ІIB!ИюЧдР:аQoUšxO‹Ч і3жЬ$ЊЃaаБ‰„`ˆ™™ˆРЬЕ–™jFаЃЧ`цjПd€ќ|C`fЭI“LTGуУ^0B‹SвМПџјxŸ™І‹D"b пї!„`пївЙШІЈћMя xBИN‰K%‡Ђ‰lS \*`Џрp:“&SжЪƒЯ‚MCB)!%ƒ5‚О`пїСЌЉTиЧ^ОЬ‘xŠ2Щ(X)”ГђЉXvйА,XІСkз№юнeœyљk|О/""hХЎч$a™#ДЇtѓў~>xУ0Њ}UZkдўknŸзF0M ?П‰_§§Я9ћ=ќрbќќ'иŠžФН}Лљ"Ђ‰L]ЦіО ЫВрЛ.Hš0 э­„CW{…м~єч…Ще-Є:№цлпФычњАПО‚М'азнŽз?ТƒЙ-|ѕпХоЦ&–V–бГПђОЦђv­mэHDB+l­ЏЂр*Єв„-ЌѕSЯ›лѓнЊ}•™!џєOџєпЄR)H)ЁЕn4Є7šPныуІ MМЇЦc@л‚Ц?ЙŽ_]ОŽ•…Iњш“9œЁЅ;ПСŸџЭ?P‘ЂЉ}њшу[‘(юј[ZкоЅйёлјёџŽœp'юЂэХ1ќпіs\|ыerЖІ№з?ћoМё"•Wр/ђ.yЖЅ7шЯџќЧpуJ…#ˆFЮ.НџыŸбп§ђ=,фѕіtвЮдЧєgџсЏ№о•O э4 ѕvТ2Оj>/Ÿž‚”RиййЁЕffІъЇ9…љќ№<Ї N№З~№Ч4џП§ЯјПўђ:ў‹џіПGЋЗШ§ю- З%љњ‡яbГГƒwЖЖбuњ4цЧЧЙ”XУЦт<э9 їDк/cqeуб MƒKћxxћ*ўпПќ Џ•\:зцюўxѕх ,цnтСи,ђy}s‰dœnНџ.ЇЃРЮƒБя™dщМљЕзёў_ќˆж—Б+#йб›%ђЌIi pѓyљМ№j—ЛFu.ЭЬ\3…nрxJї7cišxOРcх`$:†љ• ћd)Ї.^Т Ч№ёэIœэыјоЗ^Цћ?лхn| aФщЅбФ`Ё-“€ иƒыЮОј6ўЫoН‚Хё{XzРЉЮКј T{#C'pщlЋгујщO єк'Ž! !лг‡–Ё“И8’СЏ~}Зц—0ђъ Є @ыцѓђ9сUЂATрЦ<44ЫВjНао?k›xaР4іr9ой)Pkgщx[Ћ ˜\ЪЁw`ˆ{;ДЕЖФ““3PЁ іfQий…‰ЁЕ5br1ЕЕ]tѕУRћx№pŽ“нdњ{XXЫЁ%лƒўŽ–чЇy)чSЖ5ЭІ hжDB"‰ ”/"‹`ткпупўЛџ€Хr џе§псOўрeКЬeO5яяч€'Ѕ„чy˜››]Лv­ксћўЇБЯК5ёч‹&‚ж У4`Ўу@ƒ`Y6 I№==_‘a˜0M` Яѓ!Є„ж žыDBТВ xž") JiiР4|Яƒчk˜–Х‚љškCаPJУG н"Ў№s|xkЉоГјƒoНŽИ Яu Йy?<У0рК.цччAзЏ_зdš&j˜Xќ„Eјg]Є7ёžЏ:UB…{МхšОNŸƒˆЈіK§AѕuЕ“2РФ ЎщДtШѓ`0Ж/MР6ŒТў>ЛО:`4яя?>^eІљљyBˆєS:%ž–i‚Ї\Є7ёŽС;z?Žt*Њљў‘зuПЏ§О~ПjЇЏ!W=кОb~%: tU_д>mЭћћˆWлWкŽ№цжм1Ээїф^tркЮ{ЬMЂч|ќ&^Џ‰ї№jћЋQГ`bz4aЅщlт5ё~Я№jЛЉ8nhЎНnєїi>kт5ёšxŸяР#]o Њ^ЪЪhЬебЙvЄЎqx1Љ6бФkт5ё~ЇxU QІЕ†4 В- `M$ X–E’9˜u^LzZЯ[Рп„AXЫ2Щ‚tХ;њ,xЕИ5ыSЗягр9И†aТ4$˜BH …B‡CdRСћPЈš–G†i! ‘)!ў'qОMМп/М##03CJ‰BЁШ`ІX,Ъь;Шя{ 3‚DФ‚ђ=і|)‰)Ab—Ы;‚ †чyLТ C€]зƒийPn Йм.V‰X„ˆ˜}Я…ы)d[1{ОOZѓA @\MF?ЮbU’л,V5йНоVbЃGжO‹f48‰iХ’ Ў\ЪrЉHŽуВ0 €‹E г2ƒ\\Д[ІВbG#0‰рКс9Зšx_XМ*†Qf‡У˜~ј€ІWёѕќ•gяђй-:џ*ЃМ и1Š„,xЎ[жZ3ыk›№™(‘LТР~nG!“rKPТІL*Ьѓ33”/1t‡qчс ЕДvcИЫСfЎ@щ–V„-…эЭu8 ”J&@ЌСDi™€tP=ЉъпcЌз‘EэїGGбgФЋАšцЧ?СƒёiD;Oт+—†17~—оyџ:Šб…—/pд№щжЕ;\T2]'шѕѓC˜ŸИ‹‡‹;4pт.E2bРїuC §ЉкїМЯЗ‰ї{ƒWЛЩў№‡џ:‘H”Ьš1гсЅ•uкЬxs{>йH™Ў^Л…э#3АДАJLЧ#РƒБyl,Эа§ёqЌl—y` ›МТ>јЭ{4ПБX"ЩЅК3ОШсЈРјН;xџђ]tvЄтЦ цч'щюФ<\ %Рўњ,]Н~гKkМ_а$<v*§s„€RЏШиРъ™О4№>#АCBШЎњЁˆЩ@:ЁжL’7fЇЈ "8qі bԘ™УхЎby_гщгЃhIФр{Aбѓo_я‹Œ'јУў›D"QеФ:@в0LЉDщl7E Цњв"ZzGqюд(В)›ыXXЫуФЉ“ъA"ƒ)X3H˜H$тˆDЃHЦЃКŒ]ш8… ЪяяТ7bшюь@kkmm-А V8Œж–Pi—?xwІVаж=Š“YDcaŠ 5‹џc-^M,Ў6#ч‘}>^Х3IЄбгн…L* _)(Ц›oпќЪ‹аЎGщіNŒі"b>3\_Ѓ%ля~ял8бгхКеќ|лїМЯЗ‰їЙуUжРилл њ{zzѓ‰@`†‡˜5IhЅ@ТUЯ”R№•†e™ "(пЏ‘ m!пзR€Р№5CЩ’˜”жPŠaš$”ЏРša…C№J{јфњMЬ.ЏСŠЅpўХзалћ.”>єИзЄг9сК‹ёШ…Ћ§ЌС…}&МŠ~2 гtp=„ "†kХŽЇШ2 АVp}!d У )<з…ЏєяЄ}MМ/^5xqqёˆ+№Žсp)v@пв ‚ УkEОЏƒ$qУ@Ш4zд™бЊDˆФo…”АL :НЄ0˜  5q5ОЫ№LЮПќ:.J !ЬЪї˜57œZдФзч'uЃй1ћгО*<€Ё•bp0f0Є№}J)”RЬЌФП*Г%fИŽ[б7њнЕЏ‰їХУ;–JYљ%у0W4p}3Уѓ<вɘ”яГЊŸЧU2УƒјpЕ1Z)въPuO)Пf]ЮЈа:‰ЄЌ”бš•VЄ5?rђјUkb| €Њ"cœЛJd3˜5+uр@ёТ'UХ X)GкW7етЪё˜Ž˜иъе;ЄкеXэOqО\Iѓ=Ќк€ ыџ‘і}жы‡&оsСЋ#ёс3El CB+žчзћHkуBmд†г‰р{‚4$ 5”я3зЈ>дЉOи™ъЁ1оѓnпЃяЅ”0MГzuY+Ў:!€_SsъYкGBВaHhпƒврZ1ГGYyФ–e@љ><ХЁщg?пЫnZLЌрy~ѕёњ\П&оГуеюoд?3Ь„dл Z_žх‡г‹HЖuaАПS‚QЋъ@,DэoЙкУЦ3iжъ#QЅњ 0Bˆƒ )‰•тнЭ P(‚X4J’5ИкiƒЮ@Ќ5HJ–R’іmmчиc-ЉXMжA $!ˆРAT,И^Jka„рЖhqufлїЖФI’fпїсИ^Е Y%†L№м}›ицt:ƒЖ–|_UŠЃЪ№ъ+едQ&жЬLxі+Т†!Ё|“юR$гЦй,ќr Ušžгѕ{оїуЫŠgд™Р DLЦьд8ю<œ…i[0 E”]Dи ж~Х№ЊЕЄ`d№}i0f|зЇ)xЏ„Њ˜a™V€Х€4L(Ї„н­uPЊёdЖvЯїƒ™€ЬА‰ЭЅylэBDJiшƒQŽ>s№МСzфиї†eЃДЗ‹хеe$Кбj‡`[,xиннAIG‘mKЃZRLљЊТ9щДа ’ЮP.k„,`jrwЦWаЏmЬпžХŽk``hЇЛ@8СР Ы’№ЪyмЙ7гЇЯЂЗЛ eЧƒ!<_ЕAЪР28ž_™U†„чљRТ0-hQ‚_иР­•]Ё:%Wƒž’mђ,зЏ‰їŸП#q`0 +„ТЦ,MLL"д}–Оv~Ќ<a`vђ!Ц'&‘щ?ƒЄщcqa–rљ2,лC ’ЬтЅГДП1;csd„S8їђkH:k4НБƒфа T^˜†Ї]˜†ФјНћHєžDKXZ^Рnй [1”nGn}wюŒСHЖб‰Ў$ЦLаЮю>кћO .нПu‰Э}ъF‹йhээ…ЅИ:п;zЋїђQэБn?:n SW5}'NPд$\ЛъбљoО;ПŸ§Э_SЂ=…’oQ[Z`~ъю>˜Ѓ$FЯžE{Lу“OюaпгХKчќ:ЦІцPв™ЖnzщТYPa“6ж7ю9ƒž›VK Lп"зѓqтд0 ѓуИ3>Ђжl/Fzcˆ„CdH‰НЕ9\О= _њлЂ”|TЮгюњ<"ё(І'giЯ7pђф™ю.юоŸB[п Є_FnkmЇа?rя_ЅЕ•EєДNЬСкјy\Пч}?ОLxѕјШдл4 ,-o@iчЯŸF4фёњц )x{їю\#Љ„›‘юŒТŽss›hЭД@9s˜Š+мКќ!–зV™Ciђ"нјо kkЋиІxk‰‹JPwZ№§{зQ^,PoHfˆІZQ˜]Eйua![WЏЂdFA_Л„™х}юŠP9ПЙ•"ТЁ(2щ$Ђ–ЩvШ ТсТ№Ифчkjp‘QDxžђ<ИЎХ`ReZ\šЧЬb_;бв–ƒ™Onѓ^6FfK7'юbyyš–Ш€я0Кк Зn=€ЗЙ€xoЖ’˜Л‡gFЁіJ\м/RЯ™6ДvЖЂПЗЌ[~ˆM­ЬЮђцЮ.ZFi}j&Z!ТQ膄SмФэызАГя@Нѕj-!ХуgюЌюbc}ХR-™$oюИtњb”o~p yњц‰‹АBIЄ Х.JZ@з.Ÿžлѕkт}6<уШш РW‰dДфcqq§CЦ'ЇА:ЕHmН-xѓћ?РУOАНВ…7ў№+ho‹рн_сD/М­{˜Яm#O|яћЏQ4…&ЌL’щuмПќSdFЯAА‰э\‘^џюї1~ ›s+xщ_Са№nюЬcgkЋeТЙs—P.ЌЂш+dКFшЛп=…ЉљY|ђбЏqіТ Юœ9… )ИЎ пѓщYj+6Ъьx–яІ3hж№}EJy€0бт<^{щ>Кv~ДŒTїМєЕЗhЯ*cй-`*'№кЩѓИt:„џ§п_Гысэљ$№сп§ ђN†&вАLььту›WБ”.^: S+И>Swџ^}ћM\л\DБƒт,ііжБVмУЉб3pŠ9$:ЛХTŠвниžнУ o}YШ$|LЎ0|ъ.œ9MQRИwч6зжMЇ`СRЇбэy\?4ё>3žqdMDОу г;Ш]л9њј7?х•O,l— NCcmeЅ­ %ТQ›œr™ % х{pœ"9ž@І­ƒOzДДМТЩЖNь€Ћ,Ю&щуbž‹ЛРwЯЦљђ•q8;1hGУp2 %†Hk‹ѓTЮO7 *)—ї %*–КЛ2ьэЏв/u™‡’1$тw ТR~эК^*љHиЇ&4S›!rрЦo0…iˆѕэЉЊн пsЙT,“уxм3:ŠАpљ—?њ+ЌONaрвœЬ†pуЪ;tыn‚лЛ{ УŒн]2У!0йW L:ЬБЈIkыЛРвџъЩjщхОюndлNВmзo\СфТ"l8;0LЮФOю ”іїр–]"3‚[lЖ†щ§Љ=ўWџтUЪE=мœžfФ3”ЮДБ- pн2ЛŽ зqБК8ƒНœžокѓСэ FЁТ-xозЏ‰їй№Žф;њ№D„Oœ…iЧ(я1ЦRдо’фто6rћŽЅ1$’щ8™вР‹O"Всw$`F"фgXXй™a˜†€ч)’nУ7џйП$WF8лЃзШтм>Bб$EM‰x*Kћ4pўD(ŒСі,ж7rА"QjmKУяаАаšщРWП"эцБуHŠл6‡Т ­"J5‹§FФjрxb…єу№\ЇLбL .Мњ2т2„PЖЉЄ&Ч30а?H}н}\ЪчˆХo/‡aШ0Нpц тІ‚oR7ШэƒG2$ЮОњ™ЖD(žAЂЅ їюп@Ы Їщ[пљHJŠEТЬОOОbNІ3шІl"…іж4в!‹ О„)vwї+‰ДЩДŽ``hЩx Щ‘гp1K%%!d”FG[,Љь8lиQ\zы{”liCaхŠFœRmн€чБjzўзЏ‰їщ№~уЦ нггCекH6 УЄp8Ь€ж>\Я‡4 ˜•š™<зc­Іe@)E$$X)&i’m› ­ШqxОЯL’"Ё і‘w|„C!˜RTБрЙ)ЅиАm@)HУ$г4 ЕbЯѕˆьИ>žRЩ IiіЕ†V пѓЯИЮbеёKЙ6qКjы’Јё8 X‹ЧЬ,Є$г4иs<R@JbЧёЩ…и+lсо§XЭ•‰gpbhэR’ fЧq’аЪ#ЅІmГrЪУцвю:ЭЯ- =pއ:гЄ}—=ЯƒЏ4цЧB‡c9sF)’ы‘aй,‰ƒR,vˆ„*уоЭk<ГщаљKЙ%j„B!&вф:WpŽыУй…B№оЙIˆЕq__„W‚Ў$, pDхZWŸ’ƒЦэ ФЉы*ˆ|€жаJПЬhЈЄћЬ2Ёејїс„‚BьЛœЫэP._ц–ЖЄ“qRžЎ\ q O‚HƒQv4 ypX‘ђ}жЬRсH–C†іQ,с‘…L"Т&Ѕ+šТ•cНПКZ§Оl3ИіЊТЈЋЃ=ЗызФ{6<)х.tCjлjW#ЄFA ђ> ыА’QРЎЊЎ C3#``=Т89VuЏ^•ё% Ѓ–ъџŽжŒiЈтї kЇkп[ŸШВХIг[%HJYж4Ъхээя#шD‚‚X*Њ1еЃэЃjDЌšІЁt@/ЏUi­‘ПРЮRu6V‹Gш•яИuXнљRu(&ЅtѕЁ 0Ÿїѕkт}&М†Щ eQТ/x-ч’ŽqЬ–(цїYŽ„!XГж€D bЫ4!№}MRJvxms“Жw5N uC@ЁTv™„$;bв>зelЫДЯŽыBCm[, p\†i’ЃT*ГQ(‚і\ИО‚i…X@“ыКь3`Y6‡-…ёЩEцžю \W‘eлlŽS†вLv(bЭЅВC$$ТЖ­|hІѕЎыАЏЫВи2ЧЇ˜э гф:.ыЪšQ3ˆ„Дтмц•} ‘J#dZ‚X+яРeдЃЃй5Щ"ЕКfєШДŠЋu’ •(!еВIk-ОЈреЎF=эJ=yОv †эЋЮА‡WSPэ‰члФ{:МF^h3ЌPЋscє‹ї.Ѓут7ш;Џ_рхзhнcœyѕ ФЕQ@_4эlmТc‰XHЂф(ДЕ'Љ”пЦЕЋГDФшыыB("пuББ­э–qђТŠJb­5Xу вз €зВxj<‘„К4Д&о—ЏaИШt\pˆэlЬѓјB'{N“ГѓШкaєїDqчў"9{[М“Яa'я" Qйzћ8a–hceŽЏ|TЂэ§sxсЬ .чжщсј9‚Гg†ЙИБ€[wЦб1|уIфWЇщЪ1жv ^Йt лЋ‹ДQ№‘IЗёЬэДnуз­™Q…‰Щ о. :qъ wХїюђТк2ц–fBЋ IDATЗН'aH‰•ЙIК{џЏяЛш<ѓ#нДН2Е‚цЂ3DЮжr~ ѓАНacun нaŒa™.#оwЩXХЂ i‰Z1Тб( Ф‘лйЦХ!­КћGqм ѓHяЙЎ2kD|?юѓ&о—я +ЎQж+…RйCџЩ3шЮ„№ріmЌя+8Ѕ\ЯƒŽ@+ЅЂƒX2‰юСAФ"1dтq$- ЄбTN і"6АВИ„…љy,­чавж†žЮv(DЂIЄ“1HЧG<™B{KKїoрЮє"ђl МЛbFЊ5 ‹4Vц0ЗМ‰x*ƒЁоvьячЁECCH'bPž ­Ыи)јHЅл1изщ;˜МyїжP„B.ЯёаогlW7ЂЁк1D,…љ•ULN-РA нйœПt=m)Ь|rK[{HЗІaJUтЇŽ‡DK7.]< гЫУŒФ14<€ˆhŒ}му4“>эжФћттeb!ШW5l‹BЖЭ&№'эqк+›мгСжк§іЪ>Д4боеNЄЬтбЄBБХ + ‹xїƒaтх7_Ёђц<і‰R--œЩDikWБiŽуDуqX†$ЇX‚ы3"б8"ё(†-Jмњј*Nf38sёЬ-EЉt Щ–NPŠЇІщН>ц\бСpg”HЦpj(‹ќю2]НБ‚\бA{X !,MrВ‰ИпU0­"!4+”ЂCmœ$ƒТ‘8ЗЖd ‡(–Ь $VппІеѕ0тЖ…tи†f†›пЃ{ŸLcuk­А W 2-тРёР)‘qdMSЏ<ишžз9–ЈЎфFяK„w$&|D•ВRяGЉ"Ж гŽ Ѕ­•:лл1впǘЃйЅ ŽŒрдшБ,УF,F2C$‘D&•wАYbъ9ƒs'бжš•vАВ•G$‘ІЮŽvЄQXІAZkи‘0тё"с0R-”їrШ|œЙp=mи^]…ъЂб‘A„д>–7r0Т œ=9 [ј45ГŒЖЎ~œю%K xХ\џј#\ЙѕщоГxћ+/ƒ ;и/jŒž>‰О.DBВ ёx ёDсhУCHGLZ^^ь(b!I›ыkˆtєatАRkXЖг4@B@ћцІ&Q€‰.НH- F,™B,Ќ8}ыьфгZщуjУ–hт}ёёŽUЅT*H6RBTU&‰и4MBEI<а†€ (/К *…FBPЅ@q=г(pш0I|ЎTРМ’2вбZCЈТм’RŒ&P.…”р I$ uAЄ”†ЈŠ(їяaz~ 0У9{УНYпbTлЪS ! Ј*T№ШHзте$ЅЌHШЧ­ї.*œпѓL! ”_е˜zd†єИ5R#еТL&о—Џž‰ѕш‚YkшЊv,3”я€!aHЭьW(PEOЃЪŠ„€ы\aѓ!!…„вZщGŽYЅјiЅ D HЉ” д$„i­XHЌ1ƒŽ&д)|хƒ™0rіNН№ДісЛeh!!*ђИ”жа<Јb„X…ЫXCFтРИ4к„”ЬЎЏЊД™cї=nЭгH…сYжEMМ/^эјЏFгZ€R>T№ƒЖКŸЏБ Jљаѕœнъ_gДV5:W ПІCАRаGŽ­ ПДіг:˜YTU4ЈfХœлQ+WЇъ:cНe­ИкЯъ5Waƒ]“ЯАoя‹їфLУ0ѕжAЅuHаЉpŸЌЪЎІ(Tїy’5Њ}шkG4h§HЇ~<­˜+‚@E‹+˜ P…Œђєэ;Ю њTtPП8˜Ъе№Џ”ђp–}ЃUГеШљ,эkт}БёŽ9˜‰„”УГгijaƒл{1:дЯІРa}о`ŠH‚і/CH“ˆ5|хГжL€`У^˜f­™„\yАЙкщЋŒYWЈУЙ5P8F‰X433+gAЌUіt н(9kS)EЕф 1РTIŽ`Y– жяэюв~™Й5“"ЫСД&ЭЬ•ut5I"˜YT‹FеPймЪ 6LƒЄь–Єa2Б&ж“4`HƒY+ЈJ‡`=э“V\Аoш lдБxšx_<4Jf3LЫТЮЪ2MOMУŒЧi~rЗям'и6,У„id˜eA$Y!›”уадН;Xнн™Vо&ЊЅYН У0)ж`jN†aТ0$Lг$!$ в (ЗНAћ%в4+дџ@еRтрd*ыi’Её@YIBU}G†aR4b‘SмЦэЛ“(—R~ л{ HA<!D%Ћ$d0ЊњЖPћКкy лДЕ<‡wџс—и(yˆ„ ,ŒпІ_Оџ6 švW№›_§Œ~іы0ПК вК~х}њ№ж2D%Y a_Ў•№EЃПmЊйъТ MМ/ ^ЃИтM–Шяю3ЫНіі›ќбпў љѕ7Юcьк5мŸZ`3™Х…3CД3?Ю‹;edЛњБxы*э<œР —орGВДБ:‡>К‚Ћа98ЪЇњлhfь./ф4Юfщ№`|ЇЙ;ЪШ œЄt{7Янј‹kы8љZ'Э<ИŽ;ї&9мвC'{QкнDчЉГ№жV9ЗŸ#OJž}№Бь Ÿ9u ЦЧ]ц™Х žО@] С“г(›Q wкєю/>ЦтЉa>{a˜6WVqc}9зEб<|ђ<n7љцOАИчpЂ )+ŒžО6nщъ†сЙЄƒмуƒл`лJљПїП ЉЕ<пњ:жVWpчЮ}NžЃd"ЦScзБИВЪУ^C<Т›Зyiu#GШ8вmЁееЭ ј‘єЮКзЧъ7ёОИxFНЫZ3УY”[_ФЯџПŸбўЦЇzFiюю]ўјъLl”‰МiФЂ›33Ду†00| ЁpээH'тd;;Xођёт™VмНѓЮЪћ)ZлtјєЩ.К§p•uИ чR ЊЉ s$0Џ•ЄIн}Cасb6c~qЇVшTВ ‹Ы”ьhх–ЭUЪJЌйСФф ­яyHu­!2ЈЇЃЪёj S5‰ MМЇ'rѕ DAш†„D"нŽSйЭmњјјђ-ŠЗvулЏ_@‹З‡DЖ§-qLON`jzЬ6NŽŒ ЇГЄwСdP{їоxuІfqэЃ\ИаЗ^ПDыўŒpšОў§WбЁ ˆЗЗрюѕ›И?svЯ%Д™ђЛл(r/ŒТѕrИvu‘Ђ)BОPDйqсИŒ—. …­­/cuЛ—тэ v,ƒSCg1к—РќO7анэЩE”бжNŒŽ РёwQ*:”ъHрх7^G.Цдд'XмЕ‘HuбЫЏœЧціG$Ь42щ4ђљbЕ>асЩ зѕMЄqцќь^ўˆмr†mЁ­ГЮіЦю{јъ7о 3 \ў№=д9иmhyЌN<€e›шющ9nEвOКбЧMГžЪСйФћтс‰њ_2WB0vwЖ0;ПЪŽЇpюWбžŒ`yь.жіHiТЩяbvf[ћ%$cЎ~№>Ўмž™!vpџЦ{јwџў'˜\)свХaHvv мв?„ЎіVЦяb1W„4Ђ8еХФzЁH=i„ТiєФŠјёпў~њ›1Мѕѕ—1а“Ц;?љ ~yљcь8Bћи\Yф™љEьН`L€ы–с8іЖжБА0}e#KЃИ6Žw?И‚­‚‡X< ) љ"ЪŽm$1меww†џт/~„[c3˜Ÿ™Уьм<|г‚ЈxВ}”Jх€дТZг­шHyŸKЅАЗОЦ3г а†…pЌƒ)˜ТAY„‹Ц j*О=хіiшеMМ/(о#šXBHјn›и+ЙPОцL[ѕtЗёцђ"VзЗIXДЗgріQ,yHЗЖСт2VЗvaЧZqЂ/{wnђo~;K—.žBwObaNйA8‘фˆ%hk}WзЖРFˆВйv$УРьjщD 6ља† xy,­lЌF†{иЭявТт*в•RTЅRкvœ@DьЙ.•Ы[Ё0lЫ$ж>ЧƒЈЈTzž л4рЛ..жжжysшТ™!HR(9„№]—ƒ,;Ф!;Ј7\v(Mˆ†LИži BЁ ~RЁP ”2CvPЬЫs+ХХmfэ“у8ьkBШ2HiЯSАC!X– hЧqaXAM^У2™ф–K i"6Б85Ž{'Yaъъф‘Ё~иЄШuНš)єЃ—…”0 ƒ•ы&;†%€ВS†bСЁPˆˆ}.—h2‚sа>ŠŽo,lЧѕ7Џ^еАvєЅ›x_Му:0|пЏapŸЋw‡DJ<јP92`lщЪы*ЉAщрсдО=Pf<Д€СўЁH§щV+8ŽУЎЇ!DEOъИЫсqj№j5ъ ФE=zѕŒЖš)зcod§ыIPя‹‰'ŽY4зW‚ч:шкxеСћЊ~s№WЃЂlј™№№haуч‹Ч|аfЅ|.—Š(–Ъh!(Š}~эkт5ёŽСk~ŒШжcуRЕЂ]J*6ёšxMМч‡з0ŒдР2яыЎлЏСА‰зФkт§Ž№ŽэРЕыБу~TПnЋїАе7Ж‰зФkт=?Му˜XќЈІ*Ъ#жу8Хњ”Їк§%2з{Зo/=ярЄпyћšxMМп7МЦLЌƒyЙ€e‡‹†aђˆерЇШб­+>V ­pУiEРќВ)жAМ•ŽЧЋ:ШЄaТВэ CъhRўГДЏaюeЭЋк"kѕ‰њOwДТgo_яKзp>Œщ*ЌЬOa-ч ЇЏэЉ0Ч;vЈo4Д~ЦаLАLЂR6ЅКЉЦM7—СvЩTBљнт5Ч–†‰ќЮŠŽƒD[l4юФOгОcЇ-ћfлДRp=…кkєѕќqZР›&5ёšxOы—ЊKf`–…Ѕ<Мw[.А“/тєйsшЩD‚\Y­р8˜dPлˆ+Ќ,iЌ'V№<$M2DP2”ДƒХ•uh ЁЋ= ЧuС,(Р”„ћО‡rВ —КњSэћŽЇ‚A ДЏBсюоТфЪЮГ ЩА !iхУu=Hг‚iuŒ=з„„e•™•кFЪ'ЧU0L –)Ё}ž FvIAэ'ЗМљХ $)d’јJUtИє‘щNхТ?RЬљИ,”zfMуYPЏ‰їd<ЃоH†mА™РwОqз>МƒБЩ%и=&OЮ,б їѕ"фЏскј,\‘РљSC€ЛЉщ%РŒЂЏЏ…­e^ои"—C8uВЫгqхњў№пРа`Lx<;~3sЫ4qCЏДЂ”[СУQf­йNИŽТp6зw1ЕсqˆђДКБ…d[/\— ЄDи",Lalb†#э}ьЂэ‰ћ˜]к€IbєфЈМƒ‰™8ž X+ йкХЇћ[iee3 ›HДdббšФњт46їJ0"i>9˜ТЭ+W(WВ№§яПŽН=ЩX ™LžЇъ/ш‘"q ІH\ h4 hт5ёž„WпШX2ЖmђЮњ"}јБfiФT;ќў;Зёёи2”VЄ”т* В&vїЃІž^WыцињБMМ&огтсИ„ўЪ;2$aђс4uіœрlˆЉЬ!~ћџщђХSw'ЪИxё zёт ,ЬмСм†7Пњm ївќф œ§ојжwрLO`ђсЪ…zpс•зщв™av}EЫг3,Ђ-јƒo‹6~ѓїXAїзЫtўв8u~[y„є.п[Є_@gKkfп§оЋЇ24w ѕ…%жfŒў№ПЯs?џOимZ$™Ф7.ѕтє€OџЧŸŒrЎŒ?ўWпyE\НБA'ВIs“˜^[GќёПNF4…љ{oэц7~№=šўхЯxНАЄЛшѕгню@>S"ЭЎыа>Ћ7ЃЮЪR эєиŠыЕdіњЯšxMМу№j7љУў№_' ’RBkM–ec}ažЫєкзПТ{+K(”ЪHІт(ФIdГ№wWi|bœ7іЪˆDЃ~‘ЪžB$бТQЫ ќюZ‡OкоDno}УЄЉЛwйŒ&бжолPX˜ŸУн;уŸ˜FМЃ›:Rv EХРа ЕФ€wпy—Ž3єђЩ^Ић›Tжбd+З'$-ЬЮВ#lUЦн[їБЖ[Fw_6ччpїюккЭГЇfщюvФXZихtд B~Ё–ВtОœШД‘tKь(йСи™DYиппЧТд ­qZ[нaЅ ЩDJщjБэƒdкQЙЪІЁЃќы#lЗ‹{@Фiт5ёž„WЉЬ@{{{GKЋ=œ!ЅЄH,Žžо> ›&щ4uДeрьяCK‹вщъщHC)MZ„ЈЛ3Kй–J%Š%’id;;N&Ш”бD ЕЗdБEi$уQŠ&’0ˆр9eJg{hxxIr\—\ХMЖ KЂ=Ђб3шlM#j1хЫH˜д‘э@<ІdkйЖ4yљ<:G0млBScгфУFпР їSOw+E"q„­bбeвq„)te;(1Б_rHš2™VЪvЖ# “iHЖt 5ЃeR"•APШВ)6Љšѕ„ЃВ)дЈтzУтHХѕКїMМ&оё„t|i­!M †pІm•ЙdYƒмZ„mЙЖeЧ&lгk?BЇ бЄш"3УВ-xЎУŽуHРЖ+ё_0ЧfлЖhxŽŸP( э–рјІ‚eHhхУѓЄaZБf"ЫВ@а(жqэЮКЛћprИ{{y4 }šг * ! •:№X+п *4hfЯqHк6ˆ)Є!с:NP.ІЮ }œ‹П>Aћq4ЙЧy›xMМGWOJшЏKУ;vN^st4(=РUтvЃЯqшsМіЯ‘ƒbэ#"€5•BJXІQчс;вL>циеvsО„cєнХ{тѕCЏ‰їxМjn;Uъtец7b­5ЉJ9†:язe3ссxсъч†WЁcВe`ЭфКnУ щŸWћšxMМgХЋ$RЪКqЅš™!ЅDЁP ­­- Jт§qцq™>Ѓ'XЌч€ЧT=Gj\ хsn_Џ‰їєxZk€––DЃQRJKф8˜‹{žлЖ‘Эf.knЭ­Й}.[Еg§˜d4В D„X,†d2љ$ЫёиЕћgќО‰зФkтUЖBЁ€bБxP(ЏQ>27їkJfVFрчЕHoДVhт5ёОаxUДZѕIд”4ЊўH+Tч<єы8БŽЋNиИћз…Ђž%ƒт8ЁЖ*‰ћY32šxMМŠx њРŒrЉ€’уAТ‘L 8х2ШАa’ }”І‚!o_ŒЇвЋЂЌс жЇыOЊ‰зФћRрHЄТЧкв’А\I:ЭV‹њUФХf+Œ‡чсyxGУ;•ИNрЦ!зzxž‡їР;бмд­Ћ’x™Ž N?2žkХђ№<М/"оaИa=mїџv)vДj1W•‹ЙЫРеЌѓт9Њ}wУzxž‡WћЬю |Є€ў&Т51@`&Мћ.hjЪ9_н‰їf;8’Ї‰[э^ŸЪФУѓ№Ошxxе€~зZ‘BИ{јйЯУ€зМцЕ?L;ŠЫ]AЭzЯ9iYРФpё"ак ЌЎю2ё•ыШЖн;rcМцъtИVГFВ„‡чс}a№NЂФтŠУ5s}Ѕ0œЯ33РkЏпќ&pћ6P,:ќПeB€+й=Њ2@CМ&ъtїЉњŠ€ž‡їEУ;hТ1Sу’šЯžНН@w7авЄг@6ы\'IРж–sM•хv­:‡•ш<Ц`xxочяT&p#  ˜šœя~?pюјЩРВœы~љKр?џgGNЎ—g3ъЕuG^l<<‡wŠ IDATяѓ‚їЪxпUVЯЯƒЂQ ‘pŽ+ 04zњдљОЙ в4ај8pчŽ[5зє7uЊо^жЬUЭУѓ№>ЏxЭš|д­ОЪ ЎўаэлрБ1Pk+`л`!@Щ$04LMЫe ЏhkІЇ]Љ*И–Бо%|W@Л!\зn2ёZ*уАž‡їiФ;оМ[\ѕ Ћ=Лo&X;Ч+šfј|РеЋРпў-09 \КфќE"ŽЉЉўw˜ЙЪZWхфЌvэЛћ\ѕZ.5КЗzѓюoCў?ЭцсyxЇеіM`ЎNвК2†ћЎЙ}лQ^9у”Єн]ЕЕеQj1€ X ˜Ÿп?™uœsLгœ…`ЏKfУ†ъзynД tЯ!ƒоLйpа=ž‡ЗOмЭ[WcŸ]l4UŠБgћ~Щя-.‚юнл=ОВ|є‘ЃaFХіЛЖцLT"Р4їюˆВ ќйŸОї=gBРЙsЮ.ќрAcyZг€Пўын]ККЃЛ/Еm@ •–—w‚bбЁЇXt xўиоvЎ7M`iЩЙЯЕxœ–RТЛЮЛЎбuЕ‰L{UРдX%м№ы‰”XЌЊрщiрёcчЋНРмœsўо=gg­юОBьgЕc1p_ŸsЮВ€XЬ™РUW§Ž~џОУ–?}ъhЌ%iwђзЩоx№ј/џиммЅя?ќ‡]њVV€ŸќdwБ$рЇ?uŽЙ0OCня]ч]wаuе]6Н>‡'/ц л`aњб}<~:OЏуцЏ‹'/uciП§еo1ЗВqЌ L •JРШИЕеQМ{їŸ,‚u …Э9LЏdHD#‹ЅЙYфŒ6З6№ќй4­ЌЎЁl•hqq‰жВжЁxOфˆMcŒб(у?§'&гdњ‹П`Ъх§зŒЎ.ЦшhE|Ѕ=б кМt—\d0ƒZ[‘гЬ РTЌИ{—‘H.0=ۘ›cфrЮЮ pEЩхрЭЮ‚ќ~ЦїПВmЦ?ўЃCпПћw}џ№ Yf|ы[@,ЦєруНї_џ:№У~ШЄiћщл§ЯеP.WпF—ИŽyx^C<,ћмб7€жD є\šgž=CЊTfг,ух‹eЪ‚ПѓНяcА7Щ>ИЯr0Юпћюї9,ƒ7—7ŽD”ЫD##€, `|мЙІЋ X^&ФbDё8РLтРrŒфкнЮŸ„ М|Й{ЩмQk+ Њ„B*Eииpые]|cДЖ\МHќ~ТЛяБ!ЂQрзП&ƒDƒƒРљѓ„>"Єг„бQGщЖЕEикк•ПїFˆ4K№WяŸкМПž‡чТc€|бN:?вП ŠYЖ‚Оž(ђйM̘Y3SЩ.@зMђќ€mQоЪТВ’Њ9ЉноV,ууРр cЫœ€…drзqу$ъђŽ‡эžž†‡@ˆxsЮƒŽйizкбdЕЅ‡VW{kћњ€џиЁ pЬWЖНKoG№•ЏьzŠƒР•+РЃGЮ9Y†зМіqЖъ б4†a" Ѓg “/—ЁIF‡ ˜)ќъoX{n|уKи\œЦЯџћ/б54Š‘О“T'д4s9DњћСБ2Љ "˜ВŒl$Br8 -ŸGОX„"Ы(‹(•JeЙ\†a@’$dГYВ,Ї0SЖтm+ ŠЋЋ$•ЫаККћч&Й­ киђ;;”™[З ‘ ЁPЖЗ‘Пy“Ь‹AээШf2ŽЂld]'С гчCЖЃƒфxZБˆ|ЁхќyУa”r9H~?Ъ…ф{їРccH AvЙМ‡>fF&“!!LгD6›%Y–Ёiђљ<E9V=<Џ\*BгmД&d‚тѓ–€$Ћ48:„h$ŒbЎ„юО^єѕtЁ\*Q0Aw_|Т&IиЖ г4ADMЋюйNMЫ‚Y(Р*• •JА˜Щд4вЖЖ`—Ы0lZЙ лЖЁы:4M3CзuшКf&Mг`F­т›iYА5 Хѓчaъ:Yџўп“РƒQ.Cг4и Eнй Ќ­AЯfЁKhcƒxgfw7l"”K%˜–Kг хrАl›Ьr™ДlЖeСА,OзЁчѓNU8z(ЃЇідiЉ `—>г„UЙЯВ,2M“4Mƒmл0 еЯGюЏ‡чсй6Jš†ŒŽіLг„ Ц/]ЄБбA@+СˆсќЕЫшяiCЙXD0–ФезЏRG"JХ\еEЃЁчжФФїііЂЎ:!lлЦ УЊВiš$I33lл&I’Pљ\нЙСЬe–eU,7›ІIB,Ы‚$›D$§њзŒџњ_aџх_’єц›`г„Э I–WЫ…аЛяТќёAс0фŸџœэСAтЋWAЌJ­T"bЫВvщГ,’dЙ!}’,У&=} qы›ўч$ТaeС $ dлl™&IВ|:§­ЇЯУћ‚сЩАmЫyџ$Љ2"С–eQЕоou—BT‡AЖm’e…BхrЎъ„XZZj^I–хš\Q”кqI’мŸ+VЂн\vфJЊПЯ} 0Ў\B!HCC\ЙRНЌ,ЫP&'л&NЅ ўјwё.ь#г' ™$DЃPR)F8 Hв.+"„ЊžjCŸ‡їyФ“Qwе_ыѓљvч‡ы§Cхšf6hTNЋћЬ'№i~m2ЩH&MЖ›ИЊbЬчsЬ>х2№o€…іјІ5№9”>fЧF<2|ј!ЁЇЧбp/-9ітkзѓUƒLћЏдпг?я wXe†њи}3Й@іWq5,`ьJвхЖ›э<Ў8МWМЌ*ž Јž#$“Р[o1[Q0ИЕTз уMщГ,f!ˆ №ЃG@: J$KK„џё?DРѕыю ŠWяяqшѓ№<Мц У>МWЯJщЪКзЬcЅZ/Е–uoз.\ЕWwс]МЊ–ЯG ТНѓюУ;}’фќђР€cŽzјМДD\,‚ЧЦ‹‹}ЛмРЋїїДЧЯУћТтсUГRV‹ЛйьƒJDœЏ:y\чNОj№рclm9ўзх2№Їъdйu2љУєзУѓ№NиŽ”иЛ‰Жъ3аз\ЩъиO/Р5пьбQ'-аЭ›ŒG.—$рю]ЎњЋ}цћыс}ІёpHbwљ,tх=Ў§0ЙV‘}IЊЋчыиwтъOOi:žXУУ@.Gtс‚У:їє0T•Ш0ŽІћГп_я3wвK>ŠFЬэџй(]HЃњЇи_|_ўлO $чІ7€7œQcЮŸwтwхѕЯ~=МЯ,оaэHОыъЖћќАKм$: CŸ,“mWиirпуdсJVBƒ‡ё™ыЏ‡ї™Ф;l"ПZqГ&сW vpnєџЧЂšКg/оўы?§ѕ№>ѓxЩРдЬŒt[]ЊВ‚4*шєщЦл_уёѓн_я3ƒwИ Э.сК)‹а Y57*P\бШyxž‡w МƒXhcіИт4вЌ5аТyxž‡їŠxЧ‘щИŒыГђе0і№<<я„xЩРŒЦЉBіlщюmп§й]Ќи%ˆяq Ћњ‡zxž‡w4МуицŸыJB`ПBЂїйС<<ЯУ;оЉ›‘\Ўa ефnа:™‡чсyxЧФУГR65#UsоК"+F[43AеGYxxž‡w2<Мj4’[јnvМбіP†‡чсyx'‹R ицf207‰ЄhШгзу5рџ=<ЯУ{М#яРЭ„яfЋIГыц=<ЯУћ |eRŸєЯУћ<у9Л-5)WMеЧЧШЊuф€ўz­X]А17c ЊЧъЮyxочЏтСD`АYЭMЮl[Шe2HЇ30,лЇЗwP,i L]у­h†y$-єЉэРћъЂƒ-№№<МЯ^ЕД(kY<К{ПК5 лЖпYРЛПќ=A6Гхй'јчwпХэћQаЫxљф~љ‹_тС“i”ŽАfFЂFjmЎOBзроЊЋNMюсyx_<”лYЃЅrТдШвѓарЃŽю>јЌ2ЖЖВtщЭз(єбЏ?Є’%бЏНNV)OK‹й#›‘šiМИkАgeruЬ}Ќыс>юсyxŸs<чDДkзЏ]B{,€’NˆЕіbИЧЧ;[Ыxњr+8л?ŒЖDk ЋЌјCюс "qБRƒщиJЌУ4i^ѓšзŽкDC~˜†УdР@ow‹Yф ŒDT`ђЩCš‘‹#АЕ<ю?К‡’aЃЇЏэdи4MиЖ "ЊеmЫВ Ы2lл†mлЕЯюк/€SVТ4MTkϘІ I’@DЕЯž‡їЙЧSTВPжээIР6АЙК‰й… hPqўЕKХMмќн]tуKoО†™‡xјhУWЎЂ-щЋ3h8OнхEЩЖm!P.—‘Яч‡aл6ЖЗЗIQ2 ƒRЉ|>JЅrЙ|>rЙŠХ"|>вщ4t]‡Ђ(ДГГƒ*цЮЮNЭa{ggЧУѓ№О x*2™ “0аз‰BБR|шІp(ˆHа‡p4‰xЂ}Н% $ПpžZ[Ђ”KmAЎдгuНЖ€TЫ‹К'0зъ—Ы`f$ T*­‘пяЏЦ1RЕСчѓеV+UUСЬPŠЂ03“ЊЊ+е}>Ÿ‡чс}1ёd€ЖmCѕљ YD‚dE…ЊЊ№ћ}`›!„Dбh„Жm“Яя‡eY ыгФФ„нллKюђЂЉTŠ…дпп+WУZ-и[WЉб1ЯУѓ№Ž‰—ЫхHг4v•ЅjyQдkзЊМНmлћДnѕљ}Žъ„нь>ЯУѓ№ЦЈЗEз+БіHЧЖmУяїфѓyjА:˜!GЌєрсyxобёœТрtpFŽъЬЏjдt]ї,^ѓкЇ U=ЛъwтKЋдgб;юoОтyЯУѓ№ibЏјЛзХ=‰ŠOlтGzрЕž‡чсЏ‘|”тftЪ+Œ‡чсyxЇ„'aЕиЁXŸЏй*усyxоЧƒз…ЎgŸы§ЗыЏiTyмuЬУѓ№<М ЯНзfМ+C†Ў›U’pВў0ƒ˜mšTUCзu0 ЈŠJЖeРf†eйА- LЊѓNs 5рFJ†ЊЌ{xо'ŠчfЁЬЄЈ*VŸЏp*Ћгјx//LУЄ_Ž!“ЪВ>EА4кLяАabб0T2(Ю!q р#Ѓх|О@Ж№Ё-счйщgДМЎѓ—n\@ рcY(ВШч Х`аO23™HR JD… ЪКЩB–‰M“I’)жвСgS;Ф’ТЊЂ€lwВи dU†ЂЊDЖЭ\I(цА=ОЈ.VЦ]в‘А5фfy<<я“ФЋN№=,t–i’nXhiMаіFž [+tg}‚oM<…хKвЗОёZhм"%6€Џ\?‡™Иy{Hв…‹з@щ9ЌJШ.^";П…n=ЅX2ЦЏНvЈАХ“Зёl~“Д’зоyc*§ьў;СЗоЧгћSииI“Eb!’?†KЏ}RvŽоћр.ЖЫ2}хэЏpЬJбGS<њклH ‹ТЩ: ыqeМ\+елж*ЅjƒTЯТИWЭЌ‡чс§Aё4#9aR*ђЉќЭ[ТрШ%Œ5м{0ЉЅj.‹tz YgFЎтЭ7/cmщ%nў~ ’Я‡Dk‹Г ˆRзОљMфцYŸ…ыФkoМ‰+уCСxљ№>r–Šw~єЏБvыаKlb$ЁДО€ХЭ8вƒЫэнX[[Т№ka”uLўю&„Й 3к†‘F)›‚/$р„„апгП"CпкСFŸЄyxо'‰ЗO ‚ЅR™Ѓ‰vŠFЙЮ`‡dŽЗї ЏНp0ЂрЃ;STжЪ\(aX6"Щ6ФијЏЭ,ать4’=ˆ— (юЬpЮДЩвŠЌќb!ЩАm ХBI0Э<нмф@И gћ †Ё‘ь@w{€b†:ћћPмйA)›Ak{œЧF/S—8СЏ‚Kы И7qBz“/ DaUжНjзм,LНќq У‡БDž‡ї‡ЦЋЮYщЏўъЏўŸh4J’$л6KВŒb6[ђсЦ›_†–NС‚„іЖ(-NПЄЌЦhmыFGШТГЯ1НœEOO/кУŒЙХeЄ ќ љU‰Z::й.ф`ВŒP8€•™А}ttДЂ%ЁьЮ=ќЇfяюCkXСњЪ*р ЃЇЗ~9€Ј_‚išiM’^(Б?Go+Vц^b3]DД%шEЬЯЭ“i'Х0!+@0‡d[рIЕ+lI§€4ђˆЁ#ЎЌž‡їУkа_‹јgЇ‘ЂЊlYЬ,Ј\*С&‰UеŸBT*•йА€`РY0Jeи6XQU’%свš˜ Cg&‰|>•­rž––WА•Ю№ТтЮ^МDWЮŸс\:Њ"эњщB’ЖЙ\,’ЩФЊЂГmƒUmЬ HВ\+ђXo{sЫЎUЎІXр§Л‹/{xо'Š'Ыђž€ў=[;3CHIBР4 ’d™e"bЧZ`ЖЩ4LиGcл‚a˜`Ё ѓƒm›lЖ›†A•>|~?YІЩ–Эd:g3)ьdJtскk8гзXLбXУ4,1,fB‚ešLB"A†D‘X ˆ˜LУdHV†mС–%В,'Ёя–Gu{КдuвыБоVчЪьсyxpМF;Вмd3CV”=.\ІiЂъшAЌЪw!IЮЄЋ|Џnœ’,яaЊч%I Mтђk-ИФ€ЂЊГ“Uб•AD€+FйYБЫ2k_мДTY !N\tТk^ћдЕj–Ыz–[Ўз’ !`ђљ|-ЅцikЮЖТЩэ#СЖ,XUyе{V^ћЖъмsoTеуЬŒ`0ŸЯЗ'ЭUУXRЉ„l6‹H$rЌМ>GmеСЖ-Tщ!офѕкЖI’„rЙŒBЁАgУДmЊЊ"4мHї™‘Њќy2™ЄОООfезмќz=оЈ@b}žЯУѓ№ъ№RЉ/--A’$VЅšJ–уё8Y–ЕOnTГMЖыђНYЧN„gГ Aтд№N›О“тйЖ]е)|*шГй5Ž:ѓшћ˜ёЊl4л6жзз Њ*bБ8\JЏ}ї6ŒFrkЮАYЪаЃG9gqюмЕХ‚Gц–ыљyьњ€)кЂšожВ,EP6—CЉd Ѓ=@G:•g[‰P"Ќ№ў>`П И2aЦќГ‡4Л‘GярFњкїЌ†›‹ѓ\" ]ННЄ,МРmЊj@Уш’Њhbй6XІLNC$‚"KMЕŽ 05ШZИ23 Dтшlk…ЈаT?ŽGЁЯЖœфўD„RfžО…кqэтH‡<_—‚ƒggW тдгп7V••WЂЏœлЦд“чH—сГушˆNпžн“Qѕ`8@asянц€дТ,?x№КЌь+АмŒ>‚mНˆ'яcjqКaЁ/ь+hО6;3Г0СУ1 Єзх:bч\Ьoтю§gШєёЈёNРѓOa~uЗв>,žД)}ЬЬ$NgцЩoЄЖxk}š?šœ…~Ќўšxўт%ццyeu7wђ€cЅфњё8)}г&y5•e!йј„ I сђ—пЂ(2ИћбЖu УcуИ2к[2зъУ\ћ4qА ККл№|~nпƒЂgaШa–žряNQАѓ ЎžычдVбx+:띘|8 +П…Э|№Етл_НŽLf “Я–щЦ7О‹3qи6PЮnaъё$^Ќф1~хl5H!Cјёэ[˜š_CЯиe\<гŽЉ;a%Ѓумезqn ЃšжЄixXнjЛ7э‚]5б•Б•Ъ@wЊВУжKєђХcLM/#ййƒ@ЌaEТйс8цRШd5В ЫXк*qп™sаЄ "Вhј(ышбW=oл6Wcg;‡б+—авёюяб№#Г:ч+9 ž=OУm~LоНŠї" Сяc ŒŽ!НИ€ЂБMŠ Јš|ы7ПТZZр/ўђO‘РŽŸРЋгЗ•AпЅзqyА/|ˆ›'1д“DФNс7=ЇОс>PqџјГIќрњ!.ŽЖтщУxЙИ‰ЮГtљђ8BqЃоЧПуМЯЭ‚lл†išшьшBoOйЖгДИj jдфЦl“Г9”жСёd 0Š˜B"]+тХ“GИ7НMСа7АБYi6ђ[Kш EюоњОт<юЬo`}5Mлœэ%`Ч1kЗ?nCuu`ъ}>NrkЩŸФеѓcєгŸќваuz} ЬЋѓsюE~q3JŠVR*Ю…бйцЃЩ‡OQZ_@яk—ажоІ\ЉtрL…Е437‡щХ =гТ+KѓДНДƒ‹WЯ`uю ­ЄѓhщЦіьšІ-<[NЁ%–D25bsw=mЊо3{ФwdŠуєт˜еœјh…`k}?§лBV+"к5L’ˆрЪј%|E^YZІЉЙ6ŸнTzБ’GЗ_Bч95?QŽDŸЋйplћ2tы7ПAЌН }=}XzП{єœц—3XлЪ‚nœСНSИ№Е3ЄeЖА’ЫаРшgзVhЉАн?Y0#Qtј‚њк}Єєjєœ‚|І>w33KШ—Вd”Жqћця№bЎ7оИ„Žю^ъыŠсљЃћєїї ЖdІ'K„=И4mјlъпПу>пур †S8œЌ(29~ YІХЫЭEW @БMdŠ[€“їQPr†Œюў ˆчЁi;Юрj[‹бџц№‘…Пљ‚€Њсѕoџ К$>UХIQЬWH<жб†ŽЁ3HIQФЌ–Ѕ ОєњeЬ•Ж‘*цa ?LЃ шxd‘—Џп@O8\-ad‘Ъа А‘.hД ръыч№ћ&1ЕКГzБКАƒP[Ў]Нˆ‡џ4‹\.Hз0оМ2ŽжxАNЎ;IпЊџmhe Ѕ’Ф§иЩх!BнјЗњ%ј#IpzщbџпЛыxу|‰ |ЃзёямРжNs&+”aС HЉfB9.Y{v#!рјБЕЖіXЬb CзоЦ7П›DXXHtwBz;Тжѓ|~B€aл„KзP$†?šФPїјe–eCH'7В#$ XІ ­R|`g{9›нй@>ЋуЭ/ПгиBо$єŽœE[ЂЯ–шЧ}џЫШ•mсO‘ГWеnUТс3Ц]юђЂD@,Cдט9EЗПФгЇгЕЗЃœMaaf&IXЖ(dwhk‡^, 5pцLЖжз‘+шˆ%ла™ˆэ[IšфкЋyЌАŠЂ8ЋђХ"иђуђЅ>,ЯЯа{7oc­ŒП†`i““ї1ЗВŠэŒˆJшьC,†јЁЅ—pћСc<˜z ƒTє$Xž~€'С6К2жЙ™Y(-I”SЋјнЭ;(*Q v#ГGK<Žx,шd§hЮ&5dПмџ н€Њ*0Ыiќю_>ФЪЪ"ВЅ"lVQtle аYТје+Ш.>Ц§ЇѓИђЮб™А­ulчLФIDDS/lяCkи_гр9ŽlћУиъщЋбc$‚m,ЬЬaфњ œщУГ‡OажнНЧіv‘d’бfпС§щetі#ФY|pї ^<{IЁі.ј``scйдf_ЮЁkш ЂAу?еНЌЧ O–!„РњмSLНœХєќ"žП˜Gп…ышkёус;XпЩСK Н˜v‡ IDATНO?њR(ŽžŽ8Š™ ЌЇ‹фEбенŸ,}џŽBпqочzМЊЂ6ŸЯCзudГYš_X„ІiƒP*^‘ЧИЬ 6Уу]g№•ЗT,oЄЁ\К„ЮюN(ЌaЕ&ЉшшJ‚ Р/ tv'р‹DСf_§Ц›(Œитђ#ђ74€ЙeŠf Mf†Уjж‚148ŒSќ\Кvёжјшыю%ƒˆЕvA‡ŒЋ—c(„p<цhь|!\Оў„WЁ™ŒDЂ=m!ЈŠ‚ЅЭ"К{ћа™ йоŽ@,ж шьиBЌЋё:%CсІќP§Nжlr;f Жm!ыЦ~єh†5AВ%бпŽх4„€a џ2ўїОKHv‡0FqCbЇ`  qщ:b=DrѕGj зfz…ft›ІIѕaќњыP#Q„|>ˆЗdDbatmmb+]D,aрќМFWgДмb+›р чаой •tф :ˆ-”JŠфЂmїљž˜Оo!Б“FI3Ё(~œ‚JРЗОMXOŽЗЂ#FG 5в‚žі!a-U@8nЄUoјў‡ОЃМЯMёа4 +ЋkX^Yпя‡ЊЊhokkў[млл‹j4’,ЫHЇгB ЏЏЏ™сљ™…ус™І‰bЉˆh$z*xЇMпЋрesYј}Юƒњ4а—Яч!ЩўРЇrќђљƒЁ(  ‡гсЃуЩxyЇ2ƒ Y’уRгс$ВСA+Ьб№ў.ЃчЩ№лY^>ЯУ;ž$„#­ŒзT Нwш$JwфР†Эoо$!uc›bѓьєhў@­`5ˆп‘Я’дxUо‡ЗwтЛЅЋRљ^ГЫОТTŽ№t$ЭѓQћы>VsЂ&ћ4›WПCщk`зn†љq<пЃрUŸ™]­jКfи•k…`Жaль$œ`;YZ@$ „‹>л†эТЉ%БЈ|wяњЛеїWQM"qвўк•В'Ч‰Š?Р•ВЖP%ؘР6@еlUЖЭb/‘.тФr4+е€U""‚ehикм@С”айй… J”Ых’)дŒсхYmb8Ъ‚ S/a'k’UЮAјƒшhMTюБр\J{ш+d2€Њ" ‚ъ^№гщ/ э ц>Р)ўDуwdњЈš~Ь†iZB‚$‰ŠХюЉИ"СfWp‚mWД•єёбЗ›ifЯ$Ќ.ШеД-‹„$СISБ‰WпуКё%!і˜”йmСL$DУum-wБmЛlяЇгпуLрН(B,ыыkМОЃD[z:[AVk›YЃ DƒjCЃt#9ЯѕПQZЭІ+еVVg`жVfёлпо…ЁчЬ9М~Љ—O="“’xћKc•• Е‡ЛГ”Хтє|И›ЋЗФ1vс .œщ‚в.kSs„ ,=™D!кŠЫЮCЉИ)žf‰Ы(aцх^Fww/:лЃ•Efя‹юž0Њ/wе ёёачтšжчА™Ю#˜шТ@gZz [9„]шk‹fЫ+hяя‡UШbiqšpWW'%"СMZn‹Ћы"шkaky™2"ЩNєw&! $P.цАМ0"ћаен щXнLqБXІ–dНнЕйq=Z_Х,еаŒфиL$‚QцЭЕUМxљœ™\ЙtиО‡ŸннТwП§=К4вЮ 2ЮЛУЋNEэЮ9j‹RйbН—љћз‚єюяŸ№]Y‚mйlЁŒB!г–‹(ииЪR$рr.ЭtСx+ѕЖХ‘)˜МДИN-Бv–[ЎQЇœУєф]юэ§.‰b†W7вˆ&кИГ5Žєж—жpђ)КЎОNРЎQ:еўцЖ_вЯ5žў^Ч„–№щп шcЋH3ѓЫŒ$ай…(ЗНŒ—‹›Ќ[шьHkЙš_йF8чdKˆ6W–иttvs4Ј|ЌєL›Ћ‹<љ| ešЦ;o]Ѓ­—“x8ГС-}чЉ§лзљїџјSz0ЛПјПџVЪZY˜у' лш9Ыєѕ/з&гдƒ;Иџl–аr†ПѕЦ%ЗжpїС щфУџеИ3$Ќ2473g‹ю +ч‡yўЩ=|јhžЧЎП]›РЏв_Ў3Гђ!xw`"и–јЃ8щ єьЌ—Q*dСЖф$лlЎ^;хZЛhи2АКИ‚ЊkлyФ>ŸВ]Т{“G{p§rЗя>D‹l`aiЗ&чkТџѕўЏP„†Мкю‚'/f`†d$;ZБ6џя§г?схЪ’gЎу{o]GakгkyЌoЇбЮTйwљcщ/›”@‘x]нmЬгŒп щsюsюеЪY|єћАК†я„Ў—АѕќCќ~бТПњг?BіЩўю~ƒЫ_ћ!кЛњ>^њ˜ИєЅЏbdd П}џ66SШЩ]xћЦ0ЖЗ–0ПЖŽэL‘x †$“]И~§ Ічо…ЎkиNя`maэ=Ќi~\КњUшЉylfsИўклшъшЦЏ?|ˆlvХUшapє ОѕGНшМ§>žЏайнП–ћcyэъЉѕ—+ЂJUl9Ј‰&<7UЕ:BёCUU$(коOзПќ&їЗ’aZе{Ш>Г.EfmCwŸЋ|пѓWМ1#ŸоІ<Ђˆ‡Ѓ$•WQ`‰TE%f&IQ ,Я'ю=єЭќоО4 =ŸСкмKj…?ЇЭ™‡˜|ўя ќіrzˆ~ќ'ŒыGhёўВ‚mєgўЏщG_НNё  Ѓц—xЧ­ŒaМ}o\юB~g†>И7“wNsќХs%WT•BЁ R9•bё?џњс[g0ѕd…’]УИ6>X%:zёкеK)0-‹мяЦЉвЧLЛ.џ6­nlPџ…Ћ4деЖ’‰$)–ŽH[}§;пЦ`G –nјќR$ ПBЙLŠ–—)“Я+>ŠХ“ˆTŽьEыл)Œ]Н-Z™›Хђк6Љ€Жƒ’PщЪЗ €iqЇK $дZ‰Žз_З\юїљ †Ь ) mлю{і$|ošVV( lНˆ…љЪлП€^64MCeябЖEѓVЯп7ЛЗўXM;ЮNFH4‚єж6J%“ 3…ЙŒŸlЏØРz*žсaˆHТЦШйa€™љоњ~щэzЮ]ЧP›РцкюM-тѕЏ}ƒ.ВJ,>z’2›Po™Ё:с}ЯН–ms( д“u,-y0>DГ3гЌzшЌQфTЮ€ЯчC6BСoТШg),d–Tщ•U-І@(6K>БeГnIЈ>XКЮКnP4`r*›!Э?˜˜РFžpёт9(О0Œr‘6sEж-B,Y,nZUћ$AЖЉq*—#I–сѓ)\ЬІЈX,3)> ƒЌ ‚UщЭЎS1 №ЖVgшя~Ё–Оxљ" ѕtАD гВNeќŽњ<@‰Fjžž•5†ЏкьДžНD ю|xŸћЦЮPЧрooЈT(Aё8 БUПsž:}@’eАЅгєУ žš]…ыЦ[ЏŸЇдТ$юОXчЖСЫєƒЗ/ёГлаƒЫјіП§7ЬщUКw‚W LУЃgљ7ЎBг0ёtт6ю>~Nдv–Пzuы/юуоєљbэјЮ7отОЮ$™—fžб>ТbкцБ i Eс‡“ШєЗ№иш(F‡@І]fN’$Џ~ЇХA;Ал•’\Ў”dЂБ †ТŒв-m—)ГОIОHˆњGКis-йЅЎd€tУІ&6к}Ч\Ѕ(ъџарћюeл$љ"Ь"ђщUК?Н *ѕД‡јƒ_џ’~ѕо-ZNФ6Q,"ЫД‰E)(›tы7џBї_,`{sХ­*D ђыєб{яBєŒЃАОD/Ічру"eЫ›HгuЛё6]ъЁH0@Г“тЗЗ'h-­QyњржMќќWЗАА]Іюж0žLмЄјљ/ёliКfгЬд<š™ЇЧOfи>шm'Ж,1Б+) ™TЩЂєЦ,&_а?8DуЃНP$A.ЅЦ+пБž9тBчвѓ3M?|ŸЖЬ к>мЙї’b~R-њ—_}€йOБИБCD7’Uм>VњœB,бJ­ё(m./’Ѕ;9 -#шYOЂ‰›ПGJГ1zѕ Е†Tš›žg“$ъюnЇЬж™v‘=_ЄpЄŠž%ђhќвeŠШ„еЅeФ;к –‰L:Kj0ŒО˜љ4хŠeиdгЮжœЛDНэmPЅJрЛПBшКNхrЙКhеўќ~?U\)Љr-U])E^лљb[ќ!ДЖ&!!Ș/q,й7пМЩв‘Og!Щrн^л^ИСБ=r]“Jm ќ{ ‚ˆЫЅ2КЯŒЃ+Ў`іХ4КFЮ"ЛБ„Їгkшьhсb>Ч;лј~ј|~maюё#Ф‡/сЋ_џќХ5lы:fŸ=C1НШ“Гыxјбmф2[аDKE&Ё#ФЊŸ;:c,Щ&fч№єЩњЧЦёЦЫиx:‰ХLСXj9…йх9|x–ЃсB3Яf`којцwpЖ#†ьжr6Бhа_fІ%ŽwcД?„дж"fW3А!XЊKJpђё;њѓ`л„€кz”пByэЈу6ч—‘y9‰Dп„,!›ЯВъФwП§К[§xўtжq­­Є•љИшЋІЦ!!ЃЃГ-б †ЮE2фЦGЯЁEв`œЏПљFћлaъ:Ђё$-1иІ`ШЕ…—ќЛїочљх%j ƒgЮcАХƒ‰xЩxc—Џ"цюмМ‰ппyФJ$ЎŽк:ка?|Ѓƒ=шыiУм‹—Xоиa!K'ъo“{Gˆ'mR^”™„Г”уЕЕu(‘(§*~щ­uОљояй’TDZbl™f%оОёBPwŒ<Ќ=х^Ыљ л?…yЈЋНƒlБŒОЁ~у+_УзОђњZU^Y^хT:…MЮфKlл—‹9Xj]§c\šљwŸ-aшЪWАјб/№№х Ÿщ†Щ6›j+Цлй(ц№б­{˜y1ХмфЅѕ,::;ИЇЋƒ wѕСляМƒo~эЫ iiУеыoрыoП…‘ž’‚žоnюNFKgmw3нл_0ГMaŒ v!Ќ–yъй<Ъš yЗN№+пqžРl3БЊ„‘9~т GћFИ3dтwwŸЃ­;Щ‘€@йАЧ14:ŒD<Т…tUmЯЧIБ$Щ,‘‰;7У?љХ-&_„лZа3Ыxќќ)Џф­БДЗЖР64€Р;[имЩs4›Зѕуk_ƒН}Ќhž™~„ЙŒЩќбћПтŸўњ.dеdВЏ}що|уЬд~ђп~‚‰чЋмкB0сЎž!Xйm^Zšƒ.dGHzЅў6ИОiyQ†yЁABH0ЫyZ˜Цг™ ŠЦZ№ЅЏОЁiтїSдогОю8išQг€ззБЉW“ЃA>ЂjМdUшчк;Аˆ`шќс8ѕіѕBb‰Ю ЂЃEІ‰ћББ“Eg_™Х"1šnRЌЋ—ЪыГєћ›ПZЯр|_ tbгjСз/ `pЈz љ ”‹yъшCИgŒ;XњП§№ еOCЃg L“tгІ‘kз)jх0§јІ—Гдожkч:№rfЯ^ЮC G(‘L@+”IјƒH$“$Г]IчБЗП$ 0eЗWёlzy]Ібс^ј|2Ќ]MєЉŒпQž2-‹dbœ={†ЌHѕФКzБхpz[ўџіОГЙБь<ѓyяЙ ™цаьœІЇG3#MRйVY+KЕіVй_ьѕч§Y[[ЕЛЕ[іЎЕвЪВЅ4…ЩЛ™š™Apя=чМћ Р&йь‘Vƒ[ееЭаоsЮ=щ ЯЃсь’ёвIЛЅ*,'JCƒ§@КЕцѕФэc€„0HЋ*Э.RЕ\Імк:љкЦxЦФТЪ23уHZ&Xk$њw,ккXХэ›ЗШ.G(chd‰D†Іc”пXРЎнGщDœf<ІjЕŠмъ JžЂЁб1Œ`+ЗŽљХ5Шj…rkXпмЄ‡ї‚ЃIƒ­%tёћШэ}ЪuЂы‘МЃДJ>ŸGЅRСШ№‚ `/PфК."еђ.Њ^€h, aP“БВ#d›GЎ%Dащ˜qН™НпЋqS™‚иї=ЊxLг‚уиPAPЏй„eBћќ@ТŽЦс ‚в •€‘p xRУr]мћьЌцw№жлпd D*№P­”hŽcУѕЕUТ€W.УѓАИ (ь–ЋЌZиЉak[q#Šjі“’>Њž„!LDЃ5qВ&Kх ѕпQЧУ  hи&A„ŠЏaз9м ƒZы_бНЊшdь ‘юyž_WЁ ˜–AU/€iЛАей&kќhJJxžУВйБm2АGШGPЈz>ШДa‹z9­a€0-ТЈIЕAP'њ3ъŒ•€яљlX69Ж ЃЮикX{л^B T*ЁP(pSѕЃžрёДjЄnщ! УD4AТ h%ј>мH ё„Рї!C!Єn9œ]ю3xк]с I,DMN>4ˆDˆХjњСŠNSgˆЁ•ХH” рIгtая0ЊО‚уКpMє№(bщaXJBj†!,$ћ3uzСZ2C-?\B'oє‹TТA:…VЊ.MШ†’dд)mїЊЊі^T‚хD™ дТNВMСя$њяаубЈ„6RqОяCC 3рK †Q‹?У„šтфЯл>ЎSъ$SЉк&"%ЄT aЃ/ƒV<_Т0lS@љ>LлF$ƒ@ЪдM,УД‘LE%сKТU‚ NoиŽ‹xТXCšh,Ек@nœŽВА&ЋЃ[›вZqрkјЁO№}uIжІЬЭM<‹– Ѕ$+UџžV№<е‚ЇjC{xRТсЩРЋyћђН М*s&“!ƒˆ%С f@еХЃб–ТЕЯlБOkŸƒ`ПЭJJЈfЛZTЁBэe()ЉnwЈНм(c;бў;,ž’>—›љšЪЊёЛŠTЗёРОмљчc3yеj+žібWЌЕ"эзУpJ‘ЇTGЬыгiAр.—џ.xЬOёжqH`К+wioУжж>lћЬжџ"э џŸ9Сў;Є}ьщђ~Џі=ЭqЫ­ГЁыЫrpн.~њЏ=§§ыжоЇрCeb™Мн&яў7Tœаъњoœхrэ*sмЊXЖo"Е‡ТЅXхbwnоЦЖrpщЪUŒІЃ˜_˜gІ(NMdїЂ ]u–<ѓыє`Х‡_Xe;•ЦХK—3i_{qЭЕЙG"qŒ A„OЂНOYЙ}…>nџХОšЯђhз IDAT зGОPF4EФнЇEэŸнжпŽуžЈ}‡“ЋШў}>фxя0э}ћ:—2У0-Р/тц›|o~•ЦЯ\Ц•3Ѓ˜Нw їч7ётWП†s“C0B‰ mƒIwЄуаѕ›СђђЬчqj,†;З>_>Эѓѓѓ$i 1|ќb sw?Х§b”PђаПSBЉTХ7^ дХОљћї ŽchhиhŸЅН~Ћ–ж№ЮЛŸNЏ\ХщБ:`':ZџУ>ьрГ›38sњNMdj‹жсŽŒЯзОfBŒЦƒ ыˆ ЦзЎХЮ“лјxf йЉ№Ц SДЕ4‹їчёЪЗП•[ХчŸ|Š-эђЙѓшвєhrsў.>ОѓfіОvy ‹w>Т…RУЇ№ц+/ bзT ‹љU|ђсGXїL\Кђ†ЃŸі98NWЏ\Ц…Љсnў‰Н/н[†ІЖ $гYLT XX˜ЧъƒФR@ЌпЦ§{3ˆFR˜ШК`ЭrєЖЏ@O!ђ>`‰SЈјњGЯуы6~єо >zА‹—.a ІёйЧŸ"2xWЏ\Ц@Т~Ўі@пР JfнСУАЕД П\Хкт#ЌE№‹џ EЦхo Љh йС<јшЄ&ьM`;чБSєЁ*№$э";q §љ нЙ3ч&1Mзю›ТЦшФ$6n>РН[7yыLLžТђ‡70c›Э |ді>ЫcvžљЕ #XQL>‡lв?ЮqD+lюфHЫ2—J8}І\›РЁ…аб'ЈniP˜я6L„нщBO!‚@Є‡ЛŸ|Е•ТZ‘1zNBJ6$ЯЮ/"•9ESc)ЬЮЮc6ПŒ Џˆ;syDя-сЪ…1DdHŸAмєјWПј)нЅp§ЕЏ`sў~єўЪdЬхEA‚'Ю]"ЗP„єv$sgЇ№ўЏoТ‰8X˜Ÿсл7уь уTВгxщ•—сЏм@Щ—ИxњОў2AФR0dяПћ.?ћрRУгИўкзpсьYPnN"ŽxКЏў tЌВУю$дъ7ƒe˘ʹ"&2}ЈфцQ№T} CњeU()БЕБ‰ЁГз№7ћёї§]XZaцю Nƒ“LС€ %_aЛXХРиEќэп§=ўъЛo#тo!0cИўтE\:5 G2dЬIЗзIMтЕыЇ ‚5МїСmTƒьПcйЧ№*ь–Ъ€эт6J:†W_љ .OЅБЙ­№ТЋoс…ё8юоК‹Бѓз№Ї_ПŽЕE<й,z—=В}uЁv(ЌЮтџўт7HЦЉбaј;›XЯm"_єФ‹/О€ЈPMzфВЇ‘ŒG XТMe0}z §Щ(Ј`+ЗŽBEСРVn ?§р3d'Ю`rhУУӘ˜ƒаюіkм|RРеkз]С‡яџ ЋUKЯЕИ†žЕНGй;вЪZŽCСжПџо/q[plhSY*(ѓЎ‘Ц+зЏвTПС`н^eСЁKzћjгUY ‘эе‰Щ>|h5M­ЊМЖВHЙв.'GN#QЩѓтJ }1 ~ЭOЃ(z1zщ_ч­эM<ОsжљГdT=,ˆЏЅl*(/Мыcю.Ў№ЎЂБA›oмzˆс‰3|љзpыѓO№?ўЫуткЎП=NV›>№IДЗёЭмђš]XA% ЮŒ$Щ4їy$пGДЏ“,n.ё{яЎ`cу,"бDwёпџч?0"izё+ЏУ1ђxpџ!Dz‚_Кv†6Wyf9џЪsЗ№шНwпуГ9X‰!dЏœЂЄ‘Ч=цфшyъАU.A“‰ˆkrnu‘>ћьW ƒ‘іѕcАЏxФНƒOoМObр+bу—џђЏИ1ЗEv"‹Sc_уГ—.ЌЮнЦџљ=}c|ёТ2­?Zтw~љ2чxmjЩЉбЯѕ№эХ3$†tжFкк‚А,ŒЄ“XнЬѓnŠE\HЇА“п Э]рєдЫhъ#u:"<‹6R§ &"*— XZZ‡bУ‰cttŒ…W \ЩчЈcaЗИE%‘ьKcxАљ•'XпЎ"ЕPоYТн| ікDMŸ7‹š2qbБˆРpР•жЖJ‰ѕбФј0WЗ7АДКI0] Ѓ?­‡’іњђYллМooтЩъ&Шt115…ˆIс\шgюПЃиW{m,-Ў XЎРv#H$Rp Ыы[nгC\кк ЕBй l’иXп„“шуt_š\лxЮіIЬЯ-" N4LІ\нТђfёє0Fвq–^•ŠЛe$гiјЛ;X^Z‚4c<<4LЉ˜ЭZ3 ƒШпнТвкD|У§QЌ..Тз 'Ч@vЛvЅ)noбъъ:LлA<•ДФv~‹MлЅXВГщфБлжF2 ЃЉФЬj#u,', €ЉЉЉНbѕи'—и)0}сO“^ЌЂFAЧік­ЭмB11‰ЩЁ$ьƒяёlд!Ої,ээtЌъTžyьў;Š}{лŽyаc ріEйWWТлЛwЕщ.щ^ЃЇ‹›1w|ї:њ*беЃЖ7<Ух†Ч*'lPЃ ЅЙ^КWЃH†бlPЛнМ‡Oћ~ЗЮlШ‹FН35‡юЂF=Опо)T[\ДЎe0™Кˆ!ЫDЇЮnIдhkkc№š/ФSМІЧjo]•Ш0КЧБгGЕЏЙ@ыцЛпШюkєU­^–AwT[а;йџмэ3уЅыi д|ї&яйWпі$žЦ;ГзюкзЭаFvШ_РЁЏ[лyќіЖ/|Ч№BЃe"дЙ™š^ЪC^ТOФЫжŽGD AћІxˆЖЅЕ0Ѓ6ыk‹и[‘У^@ЂіуоняqxЖіЖНTЯЋџŽ‚gь[АZ"лп…Жъ ЗШиЇ`ЙguЕЏ}ђjBД~ІЯмоN;ђaёЬЮGуšЌJЕVй“Vщ=Нч9?BA№дшТгМаЁТ}•J5E§…Ѕžѕ№zx_FМ№Ц1S)ЮI•KжаNG‡cр5МВTПЛЖ(ЇsШ‡и˜[B?nНš^Р#ЌŠ'моп/^ˆЮыKбоп'^› їЁёКj#…„ЭМЎIУЁ›7&RшВбQыЯX]sœ У”bХК.DFPR6љ–уд˜г‰ˆЕf)eCŽЃЋ}hЊАkЖАчљАŽerM% цжИоIЖЗNЉВяnй).zдў;’}ЁЬ‚5kh§|ЧїxxLЌCžšКPSЭIдˆŽдЎŒeMюЕЅХЕ…Пі>7@BПKћ&Y§УјЁ_krяІНэѓ 'ЁTЯ…&зuи2 |~ aйlS №=xОOšї… Кт5vJЩќЕнЏЉCшЁ‘:GЪїрI ‰“%Lxе2)А‰RЛл›аТ+ УД)žˆRжtp:R•R}ggaQ~s“ЅfЮ"ЗЖŒхmє "г—I‡ђОУ Wsж†Её2б>gOkIb3SEᘠb&ЯѓрђФњяHубd}T(W|І Ы4š\fІН6Жj7…тЕЯЯОsе‰DЩЖXЈT}–зЄЄ‡нŠOІeУqlTww‰„ 'т’Гя{фљroГИŽ V>UМ–уТБ” PЉxЄ™FM6'ILОWE ‰\зeMžчСk’њ5#‡noл$ЅУž:{Ё™aЛx4їЫ›;чбС><™{Dkљ ŒŒcd0 ‹T3ќб6`2Ма…*ЄЅ‘м [ЈвЬьЪі(ОymŸо{„|EрoО [,w~ѓ>•Ѓ)Шн""§\}щe$m ˆк€(&шГѲe“ђ*€0!„ Ыдј№СxŠUс[7nЃшKєОє2q 2рІeСRњ№| aкьи&i%ЂA-‹§ЉШzM$m1BЫ2P-ярЃ›ŸУgсБq Ѕ“lH…4cŸЉџŽ0B№ЋлИ}o ЃЃу˜KЃR `Л.\л+‰JеcУДЩЕkw7ЉŽы€•„я{ŠŸ}DdР snb-_D4=ŠгcЈЎ/`n}‰Ь.NpЙАIГз0rњ дNwoЮЃjD0:2ŠЁt‚kRB‚JЙ%м^^…‘ЦєpЫ?ЧJЁŒxfЇЧa ЃІLВГEїn~‚’Ж16>‰ИQХэљxfуcc<”ŽC)M >V{eшШШQ[S @љШч6iuiKЙ-Й лы(xR[U'…гYeOсКЙЭяАŒMЯИ”MєУаїБ:?ƒ™DЫ++pГgшЩуЛXЯяRfhО6 ЕЊбŒмњє#lV52й!Є„‚38+(гЮж<ЃЇ!ЗЗPЎT1œbh€ЄdмОu—Dі,ўќв(ќ€QкXРƒ›kP"†3Ї†Б“Ы!ЗГ‹Df“й$ŠљZXй„Oc(л‡ќъlŠ€Ѓ‰ё Œdт$•ояб'лdlnЭу§поСјЉ d‡‡Л^пл‡К3ы ––з‘HРЖmxеЙ'3xИИ+–ЦЅsSTЭ­`v)W[мR.Vn-BYqCм&­љ(ŒCкЗЃ/moaa~Ѕ™%аЋW‘_И‡‹9$‡KJ;єЛŸўw—w№зџщюЏl@D’аЙ9МѓЋУЛяŒЅѕm Цёлїос_ќђW4ГВ…ТVЙЅ‡(ЭБЏ@ЇNO2i†№е^?0 ђі*-я"Ф…ѓЇрX‚•R'вG"adPТу…<ВйAŒeАНЕп§іCllnЂфyЈV~|узєС§5œ=…'ї№уŸМ7;Щ“ЃCБыаёњФьc&­5ЇЮ`rЈл[9h‹А+“ИvzТл€ŠФy}q™ 7ŠЩ 0–Э Е№рўcv“)JИрлŸпƒD…ц7Њ8=} i]€gЧ№езпФhТD~{Щў–ЯbюЩg'ІщњЕЫ0ƒ]фK>Ў|хU\П4Э•BžЄˆ№фф(Ј&6Єі6bЯОяЃ!nNu]uqГцяv7kœПЙqUГхТю?оФиєy:?=D‘h‰dОч3+йОqs7Мю№vе6юp„иcb0ˆЏТЩс)NEcАU…Ї/žХкъ&(:‚џ№—? —.ž"!Ћ€АL‹uрЩЃYL^пџ‹яѓЋY'АzчClч—hЫЪђН_ў+o6б?~Šѓe_к83`Т4)EdZ>nмК‹™ЙПќњјі[Џ ДДˆШШYўюїРпўЪiloчА˜§ЩŸ~‡Пѓ­зиQ>ЂЉўГяџ{Мyy№ЖАS;Єьk/ГF  $2ЇљЋWGИКН€п|:ЯзdзФВNЄџŽ3†0с8•ъox ўЫ|ђђffs4gћІ” xМ№ЃчЎрдp–u 8‚ƒ‰z§ыoђh:Ъ*`СТВ`›[ Z­вЮЮyО6LИN”]ЛІТАЛ[Рмђ:O_НЮcщvr›и)UШВ ^{2Ч…€pѕЅW8mi<žyыЧ•ЋWШR>i4ƒN|<:с™н2C„х—_ЦЛ?џ)юхЏ%ЧиЬ/Ё\­  XмЅВЇ >&lХВbдуЇк ˜ˆIj(†‰б!ъЯ$QHc(_BХ<<<ˆЄкІG‚2lщ„ƒТЬ ЬоўŒ РЫЇЮтЦуФЛЙ~~эЋЏаЧ?ќЯјўцТEšпђ`іŸУфД™ејч?ќGЬ КѕpЊЋк]zdњVW*$ЄЧ`‰гcЫhƒbё> gwпй"Ѕ4Рšš;ЗзЈХйБS(’дІа(W<(ЭLdœHџu<Ш0 ЂЭЅy|АU@ЉxО/)FEМћ‹_B’Риє9Ю‚>ўј<\мР_џхŸ’-w№сЧ+ЩqL ФX) >iћˆШ2-@—щљg|ќ` o|у›ШNg‰ЪO№ўяц "}x9сrЮЇJеƒi Ќ.ЬгьуYŽ00~ц"_8{RVhѕёЯёљЇГ„h_~ёЃТЧїзшѕЗЂ8=vЏП§6lK№“GЗщџ№Їм!|oънН§?ўЩ{HžХдФЫTdнБ{rуqPџuдЎT*PZУ!…]ˆїPЬЁсБQЌБSєqютELŒJЖ.+{Юъ№НА'Ў]єЉхˆб!Ощ`бD‰d™˜p@љb™l7JУУYшOї#‘Hbd|ЖђPЉx˜8Ц2ˆФ"Pбz§Т"Щњ'. ZоРZЎ€sз^@*йGЎyЛђиСијЮ™Ф@&X,ŠdЊAiЅb lF196ŒtТЄ\ЁХR}i ™HB€OѕQ_2I‚5mэ5j^­Jq +˜‘~\Нr§q‡”T|Р“ыПЇс…%e=еD*5€‰СЖJUи‰!МtyŠМв|У•ЫЗ€b9Рјщѓ8=>D[n‹ЁŸ”}d” А™лA?’ЩњњгHЧ T4aќєLdSЄeMk` Y.bmu N*‹3gЮR&nS %1Ђ6рI‰єиiLdP,ь Щ ™L ‘JТЖLаTиоч3†‡ВH$cPZУГrЃ1JЅDZЗpІІНmGшFКrs>шн^Nи7ЃrЙЬcуф:ФKЭАL‹„0 dMе^3кГGТёЎpшІл —‡… ыBџюˆз8r г"S< LЖm‹ŒКЬ…b"гЭ,­ ! ƒUM3‡,л…m0Wќ–э"ъєсGŸraЇB_ћъKЌ•&!LЖ,ГƒAј>I)AІЧЖи @)IA `Z6›Т€Vu…:&ќ* ЫfƒˆЄяГnѕЪЖTйгbл2AЩР‡/U˜XяDњя(у28q‰PRеВё Sж№}Ÿa˜plZ*ђЅbлБ‰Xsрћ(ню…>Yћ@ьF"dŠšюTH–]FWЯcC˜АLсїD8ђйќ|гВa[&Г–ф –эРФ2P{ЧkёeгВрК љ~Р ƒlЧfVŠ‚Рч І&ЂМ:\{™™„\*•($nж^NШZы4|пчіjЄ}[4k…jЕвP$f@зй РМu9+А/ЦЩнВМК~}^cgh8x $МЊЊџМV-ЃdKМдаJЊЧ`}ПŠ kTе216>N#š e-,ІЕOВMЉчхкg69№˜пї(~j1ркЫu˜і+PЕІГг[=бў;,XSЅRi§9IсЏ•_зЊЕЗZQ]?џФэ“W­Р §\y‚Нг)iЅрзЋб $еп“}Ÿ/ПЎTK]Уm/$%”we‹=wЄЕ}h:+лоУ~нQмЌ‘ŒгL3 ЏŽ!о„ч!ЮbДр§„Л-xmШѕХ‡“Zjp4%"АЌ­ЂмЁ:d_цT‹}tЕgho‡TКчз‡РkызЦи‚Жљ8т\'jпžжо‘эужїЉЋ}эcцы:n{Утf †A†бЌџ=r&‘išp‡ъХ G‰e=-гфЈББ/OѓкОо'ž5Я8И5ёЃУ$~z&Сu]ФbБкaЊvі>ЉњЮу&ї№zxДx†a ZЉB“WV—Љ\охH$Šx,N™Lцh™Xb4h€ЃЇuгУыс=W<2jЄОяcцбA_џ…]яРmЛюrЇщсѕ№ОЬxZkі<ƒйAŽИ.lЧeYd.ЕIБPћмњщЯ™QОїєžогњ†!\зE:RAР4[yмкwр–KЕжŽу€кЅК‡;СKџ‘ЪЅzx=М/^­*ЭЂёёё†GК™вHмР~ЪЗV'Q}vwwл/н'qщясѕ№ў №šI#м`ьџ§кзиы3Е‰Ї”ъ 2{N„оѓРcf†tи’Т/ж>]ЋjjќІ бмOЙ“cУ?д}ЂЫОVдZж[§sЈ1ЉYs7цЪж8T{й’ПЂuAіќN є Ш{xмxdАјRRšжпЫМGЂ`q8Кт>”VB  e|gћшˆіёбкШ€LaТЖlhж‡л;O4јœ_эПпУћђр|пЇвNm˜жavaтA?:С?ћ\чoѓ<бH”’ё$ŸЙНГ ?№a™VМН я-м*5лR€диЉ дбšЊРЎLсЅlЫF*™‚eYћŽвf7яsЛ$DЇЊŠіU­§ы№ї{x_.МbЉЧv0˜lЊ>хШ C˜ˆИД’hдBШРуф""Blф6­ђdlЫ†ыИ.6‰+L ŽcC>t={Њak3с4мTŸЬЭ/лОпсё<}§}ЈT+(WЪшwњ;NрŽqЉ0UkXЎmEьШвиУћrуЁ–Н!La6=ЈшЄЎWЛŽ’cлTwшіУћбїE]b(ŽѕЅa™CezЭ›С!‚С&П ФтЌ„Z; 2и сЬnƒ ђЄЧёL‘HdЏXf_.4Сu,онЩгмтGRФmС†0L@АB+c+™]ЃЮ Ю€й1Ўм`ЩхrB ž}И\шF­тAЌ€сšЦЋqяKŽ€ЩјžяСї§іџл|Дfr [Й%КљљmЌц HM“U)У—КќѕoѓАk’iY`V№<’ Ч†ALUOТВm˜‚рWЋ№ЅЄ:qfЧАЁ VJQЇPJIВVщ>5PhС€уšиX] ›7юbkЗLуg˜ѓ„Ј…Ћo|YaС0TрУѓВl–iРїЊšсИ.™AU=ПЙQЗ-šDDPJ!‚кщ„:gCv=B?-‘уА[=М/'е Д •>Ј0,шЪ6юпќ Ѕоzы›HХpыЗbЛъС+ XxВDR˜AмМПŠ*\LŽєaуЩlЋ™˜Ци`?1”в]Рьi|_J‰Ц$nї)‘pржqѓГOАA|ыЏРБL|ўЋ%”Ћ€яоСмђ:њGІ152€ТућXЫ—006…С>Гїя`Ћ"‘Тф`ЊЭžFќWJй +э;Пt›РнЄ ЛPcvМѕ№zx КЦ–’оmЧХцв6ж ИјЦW‘Эє#bˆG,фЋ6Њ›ЫјфнХЏяЬЃт[јю~€7/Є№ы_ОƒЛЫUќрћo`юў<”6б?4Љ4Шxњ6 ЃЮ№Лзž№фmЗ™YУuЂXž_BБфуЪ7Џ!•А! бЈљХмЙw`eГ–SИёoяa#ˆу{УXМ§~ђўЧ˜[нЦшдќ§п§у.‚JАеІeзŽјOу ЏbЗэ6ШДo{x_Мц„жмe7л{_‚ †iЈ”§&ЖR †0Б1?‡BEухЗџх-dуŒЛѓы;A и.nСЪLсsг8=EБTmљw‰џB+]›дъUжJ7'Џ”ВЭqЇыi5™лJйЅ1ДV€abyfvџ(ОїпТ§Wl 8§ed6 ШЏ­bуўc œЛ†‹зЃАЕ„’Њ^-ЫЊгnиbЃQэмqwtJДЉЂ*YЛЌЋ‡ї%Ц#4яMˆ6'Wѓ>W­”9жŸІё‰QўјуhэЎЬа;’)ПЖФЛ^щСAЎVwЯ  ?У‡мФVQRФВ™Е†є}2Јg,ЉІrI'ЧCkЭэ“ЁQPЏuЭf­~ТііљIDATuK{™™Ћ• ѕ `hЛР7џэZJИœ1ёк“$в§PЅ5ќь‡џЏ(щЪt–cК€ЯоР\. ЫЃ“аžfQvˆЩ hˆЖ8 ЄRЊЮ›F]XЃQV_jїXtzšШXяЏ.!­4Є”|аЬЌс;dЧЯа…J€ЕТ.|ЯЧа№0&Yc)$,Цќь"mяшЯЄљЪ•K˜_X†6ŒCI‘ˆ…нн*ъŒ*]ЃЋPZСbЋk­vДnќЉ/:­qjY…ŒФ1~ъ<џ>ђхRJŒŽOB‚œє ЏФsѓK:ƒщr‹›˜<џ"†ІЮa8iaісl‹иХH*“Ая„вpјеmсNЇЁ№мОJ!$Одp…SЇL6вЎw~8SЇ‡їхТЋЛuЙ~Ѕгhп[hYmх6qчоf0ЙЖ‹нТ:хlрђ‹/‘–’•fŸ˜fА†H2в}˜8u“ ˆTЈTН6ь/ MšЅ”Є-нЂ‹V_lHI>5„Нэlл6­./уўУ2-ІiQac‰ю$НјЪЋь{U=ZIјЄЩѓWqІю•4уЪKЏ’ ‚R’}? Я“эzIM"{Yчr{"тtьLЌіŸ”Љг‰&Д‡їхСcfX–E•ržчСfзb)%@„ˆыP %Ш XVЭ9T)—›;Ѓч…_оžз=бтi™[ѕL+j8БUŽу`ЗМ‹вnЉ.Y–…Тv•Ъ.ИЬ0 RJфѓ9,Ю[$•кч ь$Mл-Єж~аЉO`0Іiv-'ь=НчDfFФ@+JЕюaЄ@p]/]ПжВpH)сљ~KівIйЧ‹Х yOЋ*•ЊЅ*V+ецю^twњь@cЃЃ{›<?ЈЅaЧžэа•Ђƒ“Ё{л˜‘H–m!‹З№Bw›Р-Щеэ Ё,‘У hї№О„x!ђqŽЧуp‡4ыЎщ‚{`!I0lGРqміЯ;ЬЮебОачР2­†XГЩ7ЫВL$k@ˆ+б–>Л4ВМc_ћЉХ ѕМ№цКг]ј@oSА™іzяK‚зX Дж "иЖ}(ї.“хи‚№с5МЭ Ї]ƒ@н4L†ј§лзŽзАяH”:ЯzL9IJžоџ?xс{_HбўюйГfЎ•ы§ašЛ?QІБ†AB„<]@gЪNG„ƒ(Czx=МоsРkЬWУ0`*ЅXЪšI(ѕ…zx=Мосё„Е—R0 …ъпЄ“ЮлDЦГыђч=М^яx ’ЛBЁ€џoкMвjёEЃIENDЎB`‚pentobi-7.2/src/pentobi/manual/en/shortcuts.html000066400000000000000000000035641227240712600220670ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

Keyboard Shortcuts

In addition to the menu item shortcut keys, which are shown in the window menu, the following shortcut keys are supported by Pentobi. Note that these shortcuts are not active when the comment text field is shown and has the focus. In this case, the focus can be switched away from the comment text with the Tab key.

Plus

Select next piece

Minus

Select previous piece

0

Clear selected piece

Space

Next orientation of the selected piece

Shift+Space

Previous orientation of the selected piece

Left, Right, Up, Down

Move the selected piece.

Enter

Play the selected piece.

1, 2, A, C, F, G, I, L, N, O, P, S, T, U, V, W, X, Y, Z

Select piece according to commonly used piece names. If there are multiple pieces with the letter (e.g. I3, I4, I5), pressing the key several times cycles between them. Some letters are used only in certain game variants. For example, A is used only in Trigon for the pieces A6 and A4 (also known as "lobster" and "triangle").

Previous | Next

pentobi-7.2/src/pentobi/manual/en/stylesheet.css000066400000000000000000000003301227240712600220320ustar00rootroot00000000000000body { color: black; background-color: white; font-family: sans-serif; line-height:1.3em; margin-left: 0.5em; margin-right: 0.5em; max-width: 60em; } :link { text-decoration: none; } div.caption { }pentobi-7.2/src/pentobi/manual/en/system.html000066400000000000000000000017331227240712600213510ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

System Requirements

Minimum: 512 MB RAM, 1 GHz CPU
Recommended for playing level 8: 2 GB RAM, dual-core 2 GHz CPU

Pentobi will also work on systems that do not meet the minimum requirements but the highest playing level will be very slow on those systems (if the CPU is too slow) or have a reduced playing strength (if there is not enough memory).

Previous | Next

pentobi-7.2/src/pentobi/manual/en/trigon_rules.html000066400000000000000000000047251227240712600225450ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

Trigon Rules

Trigon is another game variant. The rules a similar to game variant Classic but it uses a differently shaped board and set of pieces. Each color uses 22 pieces that are shaped like the polyiamonds up to size six. (A polyiamond is a figure built by a number of equilateral triangles connected along the edges.)

Pieces for game variant Trigon

The 22 Trigon pieces.

The board also consists of triangles and is shaped like a hexagon with nine triangles building an edge.

Board for game variant Trigon

The board with the starting
fields marked with gray dots.

There are six starting points on the board, each located four rows from the middle of an edge. The starting points are not colored and the players may freely choose a starting point for the first piece of a color.

Example position for game variant Trigon

An example position after a few moves.

Trigon Rules for Two Players

Like in game variant Classic, Trigon can be played with two players by having one player play Blue and Red and the other player Yellow and Green.

Trigon Rules for Three Players

Trigon can be played with three players using the same rules as for the four-player variant. The three-player variant is played on a smaller board with an edge size of eight triangles. The six starting points are located three rows from the middle of an edge.

Previous | Next

pentobi-7.2/src/pentobi/manual/en/user_interface.html000066400000000000000000000111001227240712600230100ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

How to Use Pentobi

Board

Pentobi's main window shows the board on the left side. The played pieces on the board can have numbers on them that indicate the move number in which the piece was played. An letter after the move number indicates that there exists a variation to this move (see below).

Pieces can be played by moving them to a place that corresponds to a legal move with the mouse or arrow keys and pressing the left mouse button or the Enter key.

Pieces and Score

On the right side, the remaining pieces are shown. Above the remaining pieces is an orientation selector that shows the currently selected piece and allows the player to change its orientation. If no piece is selected and the game has not yet ended, a colored dot in the orientation selector shows the color to play.

Pieces can be selected by clicking on one of the remaining pieces shown, by using the left/right arrow buttons in the orientation selector or by using shortcut keys.

Below the orientation selector is a score display, which displays the current points for each color or player. The points are the sum of on-board points and bonus points. Points are underlined if they are final (because the color cannot play more pieces).

Playing Against the Computer

The board can be used for entering moves played by humans or for playing games against the computer. In games against the computer, the computer can play any (or several) of the colors.

When you start a new game, the human will play the color(s) of the first player by default and the computer all other colors. To change this, use Computer Colors from the Game menu or toolbar and select the colors the computer should play.

The exception is that the computer will play no color by default, if it played no color in the previous game. This prevents the computer from automatically starting to play if the user mainly wants to use the board for entering move sequences or similar editing tasks. So if you want to use the board without playing against the computer, you need to disable the computer colors in the Computer Colors dialog only once and it will stay that way. After loading a saved game, the computer also plays no color by default.

Selecting Play from the Computer menu or the toolbar always makes the computer play a move for the current color. If the computer did not already play this color before, it will also make the computer play this color (and only this color) from now on.

Move Variations and the Game Tree

When you play a game, Pentobi will store the sequence of moves and it is always possible to go back to a previous position and play differently. If you do this, the new sequence is stored as an alternative sequence (called a variation). Variations can also be used by annotators for commenting on existing games. Variations can exist at any board position and can have subvariations themselves. The game can therefore become a game tree, in which each node represents a board position. You can navigate in the game tree with the items in the Go menu or in the toolbar.

The main variation is the sequence of moves that starts at the start position and always selects the first child node in each position (e.g. by selecting Forward in the Go menu or toolbar). The main variation is supposed to represent the real game played. If you want a side variation to become the main variation, select Make Main Variation from the Edit menu.

Previous | Next

pentobi-7.2/src/pentobi/manual/en/window_menu.html000066400000000000000000000240571227240712600223640ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

The Window Menu Explained

Game

New
Start a new game.
New Rated Game
Start a new rated game against the computer.
Game Variant
Select a game variant and start a new game of this game variant.
Computer Colors
Select which colors are played by the computer.
Game Info
Display or edit additional information about the game like the name of the players or the date when the game was played.
Undo Move
Undo the last move played and remove it from the game tree. Undoing a move is only possible if it is the last move in the current variation (i.e. a leaf node in the game tree; use Edit/Truncate to remove inner nodes of the game tree).
Find Move
Find a legal move for the current color and display it for a few seconds on the board. Selecting this item repeatedly will show all legal moves.
Open
Load a saved game. The board position after loading will be the last position in the main variation unless the game starts with a setup position. If the game starts with a setup, the board position will be the first position instead. This avoids that solutions are immediately shown if the file contains a Blokus puzzle as a setup with the solution as the main variation.
Open Recent
Load a recently used saved game.
Save
Save the current game.
Save As
Save the current game under a new file name.
Export/Image
Save the current position as an image file. Several image file formats are supported, the file format is derived from the file name ending (e.g. ".png" for the PNG format).
Export/ASCII Art
Save the current position as a text diagram. The text diagram should be viewed using a monospace font.
Quit
Quit Pentobi.

Go

Beginning
Go to the beginning of the game.
10 Backward
Go ten moves backward in the current variation.
Backward
Go one move backward in the current variation.
Forward
Go one move forward in the current variation. If the current position has several follow-up variations (i.e. the current node in the game tree has several child nodes), the first variation will be used.
10 Forward
Go ten moves forward in the current variation. Like Forward, this also uses the first variation in positions with several follow-up variations.
End
Go to the end of the current variation. Like Forward, this also uses the first variation in positions with several follow-up variations.
Next Variation
Go to the next variation to the last move played (i.e. the next sibling node of the current node in the game tree).
Previous Variation
Go to the previous variation to the last move played (i.e. the previous sibling node of the current node in the game tree).
Go to Move
Go to the move with a given move number in the current variation.
Back to Main Variation
Return to the last move in the main variation that had a variation.
Beginning of Branch
Go back to the last position in the current variation that had an alternative move.
Find Next Comment
Go to the next position that has a comment. If the comment text field was not visible, it will become visible. Selecting this item repeatedly will show all positions with comments in the game tree.

Edit

Move Annotation
Add a chess-style move annotation symbol (e.g. !!) to the current move. Annotation symbols are shown after the move numbers on the board and only visible if move numbers are shown.
Make Main Variation
Make the current variation the main variation of the game. This reorders the nodes in the game tree such that the current variation becomes the main variation.
Move Variation Up
Changes the order of variations such that the current position will appear earlier when iterating over the variations with Next/Previous Variation.
Move Variation Down
Changes the order of variations such that the current position will appear later when iterating over the variations with Next/Previous Variation.
Delete All Variations
Delete all variations but the main variation. If the current position is not on the main variation, it will be changed to a position in the main variation as in Back to Main Variation.
Truncate
Remove the node with the current position, including any subtree, from the game tree.
Truncate Children
Remove all children nodes of the node with the current position from the game tree.
Keep Only Position
Delete all moves and keep only the current position as a setup. This can be used to create files that start with a given fixed position.
Keep Only Subtree
Like Keep Only Position but does not delete the moves after the current position.
Setup Mode
Enter or leave setup mode. In setup mode, pieces can be placed anywhere on the board, even in violation of the game rules. Existing pieces can be removed from the board by clicking on them. The currently selected color also determines the color to play after the setup is finished. It can be changed with Select Next Color or by clicking on the orientation selector while no piece is selected. Setup mode can only be used if no moves have been played.
Select Next Color
Choose the next color for selecting pieces. This can be used for example to enter game records in which moves of a color were skipped because the color ran out of time.

View

Toolbar
Show or hide the toolbar.
Toolbar Text
Configure the appearance of the toolbar.
Comment
Show or hide a text field to display or edit comments on the current position.
Move Numbers
Change the way move numbers are displayed on the board. The options are to show only the number of the last move or of all moves or to show no numbers at all.
Coordinates
Display coordinates for the fields on the board around the board. The convention for the coordinates is the same as in the Blokus SGF file format used by Pentobi.
Show Variations
Appends a letter to the move number on the board if the moves has a variation.
Fullscreen
Make the main window full screen or leave full screen mode. It is platform-dependent if the window menu is shown in full screen mode. To leave full screen mode without using the window menu, press the F11 key.

Computer

Play
Make the computer play a move for the current color. This can be used to change the color the computer plays or to resume playing after navigating in the game tree. If the computer did not already play the current color, it will play this color (and only this color) from now on.
Play Single Move
Make the computer play a single move for the current color without changing the colors played by the computer.
Stop
Abort the current move generation. You can make the computer continue to play by selecting Play.
Level
Change the playing strength of the computer. Higher levels are stronger but can make the computer take a long time for playing moves on slow computers.

Tools

Your Rating
Show a dialog window with the rating of the user in the current game variant.
Analyze Game
Perform a game analysis.

Help

Contents
Show a window to browse the Pentobi user manual.
About
Show an info dialog with information about this version of Pentobi.

Previous | Next

pentobi-7.2/src/pentobi/manual/en_CA/000077500000000000000000000000001227240712600174765ustar00rootroot00000000000000pentobi-7.2/src/pentobi/manual/en_CA/become_stronger.html000066400000000000000000000075241227240712600235510ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

Become a Stronger Player

Pentobi has functionality that can help you to become a stronger Blokus player.

Game Analysis

A game can be analyzed by selecting Analyze Game from the Tools menu. This will make the computer player evaluate each position in the main variation. The result is displayed in a window with a diagram of coloured dots.

Game analysis window

A game analysis of a game of variant Classic (2 players).

Each dot represents a game position in which the colour of the dot is to play. The dots are ordered horizontally by move number. The vertical axis represents the estimated probability of winning the game for the colour to play. Mouse clicks in the diagram will go to the corresponding position.

The position values are only estimates and the computer will sometimes evaluate positions incorrectly. But sudden drops in the value can help you to find moves that were potentially bad. You can go back to the position before the move and try to find a better move or ask the computer what it would have played by selecting Play Single Move from the Computer menu.

Determine Your Rating

You can track your progress by playing rated games against the computer. The game results are used to determine your current rating. The rating is a number that represents your playing strength.

A rated game is started with New Rated Game from the Game menu. If you have not played any rated games in the current game variant, you will be asked to choose a start value, which can reduce the number of games needed for determining your real rating. If you are a beginner, leave the start value at 1000.

For each rated game, the computer will choose a playing level of the computer opponent according to your current rating. The colour you play will be randomly chosen in each game. To get an accurate rating, you should always play rated games until the end and not change the computer level or the colours played by the computer during the game. After the game has ended, your rating will be updated depending on the game result and the computer level you played against. For the game result, it only matters if the game was won, lost or a tie. The exact number of score points does not matter.

Rating window

Window with the rating graph.

You can always see your current rating by selecting Your Rating from the Tools menu. This will open a window that shows the development of your rating during the last 100 games as a graph. The last 100 games are automatically saved and can be loaded by double-clicking on the rows in the game table below the graph.

Previous | Next

pentobi-7.2/src/pentobi/manual/en_CA/classic_rules.html000066400000000000000000000070401227240712600232200ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

Classic Rules

There are four players, Blue, Yellow, Red and Green, and a board consisting of 20×20 squares.

Each player has a set of 21 pieces of his colour shaped like the polyominoes up to size five. (A polyomino is a figure built by a number of squares connected along the edges.)

Pieces for game variant Classic

The 21 pieces.

The players alternate in placing one of their pieces on the board. Blue starts, followed by Yellow, then Red, then Green.

Each player has a starting square. Blue's starting square is in the top left corner, Yellow's in the top right corner, Red's in the bottom right corner, and Green's in the bottom left corner. The first piece of a player must cover its starting square.

Board for game variant Classic

The 20×20 board with the starting
squares marked with coloured dots.

The following pieces must be placed on empty squares such that the new piece touches at least one piece of its own colour corner by corner but does not touch a piece of its own colour along the edges. The new piece may touch edges of pieces of the opponent colours.

Example position for game variant Classic

An example position after a few moves.

When the player of a colour cannot place any more pieces, the player passes and the next colour continues.

When no player can place any more pieces, the player with the highest score wins. The score of a colour is the number of squares on the board occupied by the colour, plus a bonus of 15 points if the colour could place all of its pieces, plus an additional bonus of 5 points if the colour could place all pieces and the last piece played was the one-square piece.

Classic Rules for Two Players

The game can be played with two players. The first player plays both Blue and Red, the second player Yellow and Green. The points of both colours that a player plays are added.

Colourless starting points

Note that the original Blokus Classic rules use colourless starting points. This means that each colour may freely choose which of the remaining unoccupied starting points to use for its first move. Pentobi currently only supports the rule variant with coloured starting points because this rule variant is used on the Blokus online server at blokus.com and also was used in most of the past Blokus tournaments.

Previous | Next

pentobi-7.2/src/pentobi/manual/en_CA/duo_rules.html000066400000000000000000000026101227240712600223640ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

Duo Rules

The game variant Duo is another game variant for two players. The game is played on a smaller board with 14×14 squares. There is only one colour per player (Blue and Green) and the starting squares are not in the corners, but on the square with the coordinates (5,10) for Blue, and on (10,5) for Green.

Board for game variant Duo

The 14×14 board used in game variant Duo with
the starting squares marked with coloured dots.

Example position for game variant Duo

An example position in game variant Duo.

Previous | Next

pentobi-7.2/src/pentobi/manual/en_CA/index.html000066400000000000000000000024551227240712600215010ustar00rootroot00000000000000 Pentobi User Manual

Next

Pentobi User Manual

Pentobi is a computer opponent for the board game Blokus. In this game, four players place pieces similar to the pieces of the computer game Tetris on a 20×20 board. Pentobi also supports the game variant for two players and the game variants Duo, Trigon and Junior.

Classic Rules
Duo Rules
Trigon Rules
Junior Rules
How to Use Pentobi
Become a Stronger Player
The Window Menu Explained
Keyboard Shortcuts
System Requirements
License

pentobi-7.2/src/pentobi/manual/en_CA/junior_rules.html000066400000000000000000000020511227240712600231020ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

Junior Rules

Junior is a simplified game variant for two players. It is played on the same 14×14 board as game variant Duo but uses only the pentominoes that have relatively simple shapes.

Pieces for game variant Junior

The 24 pieces used in Junior.

Bonus points are not used in game variant Junior.

Previous | Next

pentobi-7.2/src/pentobi/manual/en_CA/license.html000066400000000000000000000025111227240712600220050ustar00rootroot00000000000000 Pentobi User Manual

Previous

License

Copyright © 2011т€“2013 Markus Enzenberger

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

Trademark Disclaimer

The trademark Blokus and other trademarks referred to are property of their respective trademark holders. The trademark holders are not affiliated with the author of the program Pentobi.

Previous

pentobi-7.2/src/pentobi/manual/en_CA/shortcuts.html000066400000000000000000000035721227240712600224310ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

Keyboard Shortcuts

In addition to the menu item shortcut keys, which are shown in the window menu, the following shortcut keys are supported by Pentobi. Note that these shortcuts are not active when the comment text field is shown and has the focus. In this case, the focus can be switched away from the comment text with the Tab key.

Plus

Select next piece

Minus

Select previous piece

0

Clear selected piece

Space

Next orientation of the selected piece

Shift+Space

Previous orientation of the selected piece

Left, Right, Up, Down

Move the selected piece.

Enter

Play the selected piece.

1, 2, A, C, F, G, I, L, N, O, P, S, T, U, V, W, X, Y, Z

Select piece according to commonly used piece names. If there are multiple pieces with the letter (e.g. I3, I4, I5), pressing the key several times cycles between them. Some letters are used only in certain game variants. For example, A is used only in Trigon for the pieces A6 and A4 (also known as "lobster" and "triangle").

Previous | Next

pentobi-7.2/src/pentobi/manual/en_CA/system.html000066400000000000000000000017411227240712600217130ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

System Requirements

Minimum: 512 MB RAM, 1 GHz CPU
Recommended for playing level 8: 2 GB RAM, dual-core 2 GHz CPU

Pentobi will also work on systems that do not meet the minimum requirements but the highest playing level will be very slow on those systems (if the CPU is too slow) or have a reduced playing strength (if there is not enough memory).

Previous | Next

pentobi-7.2/src/pentobi/manual/en_CA/trigon_rules.html000066400000000000000000000047601227240712600231070ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

Trigon Rules

Trigon is another game variant. The rules a similar to game variant Classic but it uses a differently shaped board and set of pieces. Each colour uses 22 pieces that are shaped like the polyiamonds up to size six. (A polyiamond is a figure built by a number of equilateral triangles connected along the edges.)

Pieces for game variant Trigon

The 22 Trigon pieces.

The board also consists of triangles and is shaped like a hexagon with nine triangles building an edge.

Board for game variant Trigon

The board with the starting
fields marked with gray dots.

There are six starting points on the board, each located four rows from the middle of an edge. The starting points are not coloured and the players may freely choose a starting point for the first piece of a colour.

Example position for game variant Trigon

An example position after a few moves.

Trigon Rules for Two Players

Like in game variant Classic, Trigon can be played with two players by having one player play Blue and Red and the other player Yellow and Green.

Trigon Rules for Three Players

Trigon can be played with three players using the same rules as for the four-player variant. The three-player variant is played on a smaller board with an edge size of eight triangles. The six starting points are located three rows from the middle of an edge.

Previous | Next

pentobi-7.2/src/pentobi/manual/en_CA/user_interface.html000066400000000000000000000111301227240712600233560ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

How to Use Pentobi

Board

Pentobi's main window shows the board on the left side. The played pieces on the board can have numbers on them that indicate the move number in which the piece was played. An letter after the move number indicates that there exists a variation to this move (see below).

Pieces can be played by moving them to a place that corresponds to a legal move with the mouse or arrow keys and pressing the left mouse button or the Enter key.

Pieces and Score

On the right side, the remaining pieces are shown. Above the remaining pieces is an orientation selector that shows the currently selected piece and allows the player to change its orientation. If no piece is selected and the game has not yet ended, a coloured dot in the orientation selector shows the colour to play.

Pieces can be selected by clicking on one of the remaining pieces shown, by using the left/right arrow buttons in the orientation selector or by using shortcut keys.

Below the orientation selector is a score display, which displays the current points for each colour or player. The points are the sum of on-board points and bonus points. Points are underlined if they are final (because the colour cannot play more pieces).

Playing Against the Computer

The board can be used for entering moves played by humans or for playing games against the computer. In games against the computer, the computer can play any (or several) of the colours.

When you start a new game, the human will play the colour(s) of the first player by default and the computer all other colours. To change this, use Computer Colours from the Game menu or toolbar and select the colours the computer should play.

The exception is that the computer will play no colour by default, if it played no colour in the previous game. This prevents the computer from automatically starting to play if the user mainly wants to use the board for entering move sequences or similar editing tasks. So if you want to use the board without playing against the computer, you need to disable the computer colours in the Computer Colours dialog only once and it will stay that way. After loading a saved game, the computer also plays no colour by default.

Selecting Play from the Computer menu or the toolbar always makes the computer play a move for the current colour. If the computer did not already play this colour before, it will also make the computer play this colour (and only this colour) from now on.

Move Variations and the Game Tree

When you play a game, Pentobi will store the sequence of moves and it is always possible to go back to a previous position and play differently. If you do this, the new sequence is stored as an alternative sequence (called a variation). Variations can also be used by annotators for commenting on existing games. Variations can exist at any board position and can have subvariations themselves. The game can therefore become a game tree, in which each node represents a board position. You can navigate in the game tree with the items in the Go menu or in the toolbar.

The main variation is the sequence of moves that starts at the start position and always selects the first child node in each position (e.g. by selecting Forward in the Go menu or toolbar). The main variation is supposed to represent the real game played. If you want a side variation to become the main variation, select Make Main Variation from the Edit menu.

Previous | Next

pentobi-7.2/src/pentobi/manual/en_CA/window_menu.html000066400000000000000000000241061227240712600227220ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

The Window Menu Explained

Game

New
Start a new game.
New Rated Game
Start a new rated game against the computer.
Game Variant
Select a game variant and start a new game of this game variant.
Computer Colours
Select which colours are played by the computer.
Game Info
Display or edit additional information about the game like the name of the players or the date when the game was played.
Undo Move
Undo the last move played and remove it from the game tree. Undoing a move is only possible if it is the last move in the current variation (i.e. a leaf node in the game tree; use Edit/Truncate to remove inner nodes of the game tree).
Find Move
Find a legal move for the current colour and display it for a few seconds on the board. Selecting this item repeatedly will show all legal moves.
Open
Load a saved game. The board position after loading will be the last position in the main variation unless the game starts with a setup position. If the game starts with a setup, the board position will be the first position instead. This avoids that solutions are immediately shown if the file contains a Blokus puzzle as a setup with the solution as the main variation.
Open Recent
Load a recently used saved game.
Save
Save the current game.
Save As
Save the current game under a new file name.
Export/Image
Save the current position as an image file. Several image file formats are supported, the file format is derived from the file name ending (e.g. ".png" for the PNG format).
Export/ASCII Art
Save the current position as a text diagram. The text diagram should be viewed using a monospace font.
Quit
Quit Pentobi.

Go

Beginning
Go to the beginning of the game.
10 Backward
Go ten moves backward in the current variation.
Backward
Go one move backward in the current variation.
Forward
Go one move forward in the current variation. If the current position has several follow-up variations (i.e. the current node in the game tree has several child nodes), the first variation will be used.
10 Forward
Go ten moves forward in the current variation. Like Forward, this also uses the first variation in positions with several follow-up variations.
End
Go to the end of the current variation. Like Forward, this also uses the first variation in positions with several follow-up variations.
Next Variation
Go to the next variation to the last move played (i.e. the next sibling node of the current node in the game tree).
Previous Variation
Go to the previous variation to the last move played (i.e. the previous sibling node of the current node in the game tree).
Go to Move
Go to the move with a given move number in the current variation.
Back to Main Variation
Return to the last move in the main variation that had a variation.
Beginning of Branch
Go back to the last position in the current variation that had an alternative move.
Find Next Comment
Go to the next position that has a comment. If the comment text field was not visible, it will become visible. Selecting this item repeatedly will show all positions with comments in the game tree.

Edit

Move Annotation
Add a chess-style move annotation symbol (e.g. !!) to the current move. Annotation symbols are shown after the move numbers on the board and only visible if move numbers are shown.
Make Main Variation
Make the current variation the main variation of the game. This reorders the nodes in the game tree such that the current variation becomes the main variation.
Move Variation Up
Changes the order of variations such that the current position will appear earlier when iterating over the variations with Next/Previous Variation.
Move Variation Down
Changes the order of variations such that the current position will appear later when iterating over the variations with Next/Previous Variation.
Delete All Variations
Delete all variations but the main variation. If the current position is not on the main variation, it will be changed to a position in the main variation as in Back to Main Variation.
Truncate
Remove the node with the current position, including any subtree, from the game tree.
Truncate Children
Remove all children nodes of the node with the current position from the game tree.
Keep Only Position
Delete all moves and keep only the current position as a setup. This can be used to create files that start with a given fixed position.
Keep Only Subtree
Like Keep Only Position but does not delete the moves after the current position.
Setup Mode
Enter or leave setup mode. In setup mode, pieces can be placed anywhere on the board, even in violation of the game rules. Existing pieces can be removed from the board by clicking on them. The currently selected colour also determines the colour to play after the setup is finished. It can be changed with Select Next Colour or by clicking on the orientation selector while no piece is selected. Setup mode can only be used if no moves have been played.
Select Next Colour
Choose the next colour for selecting pieces. This can be used for example to enter game records in which moves of a colour were skipped because the colour ran out of time.

View

Toolbar
Show or hide the toolbar.
Toolbar Text
Configure the appearance of the toolbar.
Comment
Show or hide a text field to display or edit comments on the current position.
Move Numbers
Change the way move numbers are displayed on the board. The options are to show only the number of the last move or of all moves or to show no numbers at all.
Coordinates
Display coordinates for the fields on the board around the board. The convention for the coordinates is the same as in the Blokus SGF file format used by Pentobi.
Show Variations
Appends a letter to the move number on the board if the moves has a variation.
Fullscreen
Make the main window full screen or leave full screen mode. It is platform-dependent if the window menu is shown in full screen mode. To leave full screen mode without using the window menu, press the F11 key.

Computer

Play
Make the computer play a move for the current colour. This can be used to change the colour the computer plays or to resume playing after navigating in the game tree. If the computer did not already play the current colour, it will play this colour (and only this colour) from now on.
Play Single Move
Make the computer play a single move for the current colour without changing the colours played by the computer.
Stop
Abort the current move generation. You can make the computer continue to play by selecting Play.
Level
Change the playing strength of the computer. Higher levels are stronger but can make the computer take a long time for playing moves on slow computers.

Tools

Your Rating
Show a dialog window with the rating of the user in the current game variant.
Analyze Game
Perform a game analysis.

Help

Contents
Show a window to browse the Pentobi user manual.
About
Show an info dialog with information about this version of Pentobi.

Previous | Next

pentobi-7.2/src/pentobi/manual/en_GB/000077500000000000000000000000001227240712600175035ustar00rootroot00000000000000pentobi-7.2/src/pentobi/manual/en_GB/become_stronger.html000066400000000000000000000075241227240712600235560ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

Become a Stronger Player

Pentobi has functionality that can help you to become a stronger Blokus player.

Game Analysis

A game can be analysed by selecting Analyse Game from the Tools menu. This will make the computer player evaluate each position in the main variation. The result is displayed in a window with a diagram of coloured dots.

Game analysis window

A game analysis of a game of variant Classic (2 players).

Each dot represents a game position in which the colour of the dot is to play. The dots are ordered horizontally by move number. The vertical axis represents the estimated probability of winning the game for the colour to play. Mouse clicks in the diagram will go to the corresponding position.

The position values are only estimates and the computer will sometimes evaluate positions incorrectly. But sudden drops in the value can help you to find moves that were potentially bad. You can go back to the position before the move and try to find a better move or ask the computer what it would have played by selecting Play Single Move from the Computer menu.

Determine Your Rating

You can track your progress by playing rated games against the computer. The game results are used to determine your current rating. The rating is a number that represents your playing strength.

A rated game is started with New Rated Game from the Game menu. If you have not played any rated games in the current game variant, you will be asked to choose a start value, which can reduce the number of games needed for determining your real rating. If you are a beginner, leave the start value at 1000.

For each rated game, the computer will choose a playing level of the computer opponent according to your current rating. The colour you play will be randomly chosen in each game. To get an accurate rating, you should always play rated games until the end and not change the computer level or the colours played by the computer during the game. After the game has ended, your rating will be updated depending on the game result and the computer level you played against. For the game result, it only matters if the game was won, lost or a tie. The exact number of score points does not matter.

Rating window

Window with the rating graph.

You can always see your current rating by selecting Your Rating from the Tools menu. This will open a window that shows the development of your rating during the last 100 games as a graph. The last 100 games are automatically saved and can be loaded by double-clicking on the rows in the game table below the graph.

Previous | Next

pentobi-7.2/src/pentobi/manual/en_GB/classic_rules.html000066400000000000000000000070401227240712600232250ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

Classic Rules

There are four players, Blue, Yellow, Red and Green, and a board consisting of 20×20 squares.

Each player has a set of 21 pieces of his colour shaped like the polyominoes up to size five. (A polyomino is a figure built by a number of squares connected along the edges.)

Pieces for game variant Classic

The 21 pieces.

The players alternate in placing one of their pieces on the board. Blue starts, followed by Yellow, then Red, then Green.

Each player has a starting square. Blue's starting square is in the top left corner, Yellow's in the top right corner, Red's in the bottom right corner, and Green's in the bottom left corner. The first piece of a player must cover its starting square.

Board for game variant Classic

The 20×20 board with the starting
squares marked with coloured dots.

The following pieces must be placed on empty squares such that the new piece touches at least one piece of its own colour corner by corner but does not touch a piece of its own colour along the edges. The new piece may touch edges of pieces of the opponent colours.

Example position for game variant Classic

An example position after a few moves.

When the player of a colour cannot place any more pieces, the player passes and the next colour continues.

When no player can place any more pieces, the player with the highest score wins. The score of a colour is the number of squares on the board occupied by the colour, plus a bonus of 15 points if the colour could place all of its pieces, plus an additional bonus of 5 points if the colour could place all pieces and the last piece played was the one-square piece.

Classic Rules for Two Players

The game can be played with two players. The first player plays both Blue and Red, the second player Yellow and Green. The points of both colours that a player plays are added.

Colourless starting points

Note that the original Blokus Classic rules use colourless starting points. This means that each colour may freely choose which of the remaining unoccupied starting points to use for its first move. Pentobi currently only supports the rule variant with coloured starting points because this rule variant is used on the Blokus online server at blokus.com and also was used in most of the past Blokus tournaments.

Previous | Next

pentobi-7.2/src/pentobi/manual/en_GB/duo_rules.html000066400000000000000000000026101227240712600223710ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

Duo Rules

The game variant Duo is another game variant for two players. The game is played on a smaller board with 14×14 squares. There is only one colour per player (Blue and Green) and the starting squares are not in the corners, but on the square with the coordinates (5,10) for Blue, and on (10,5) for Green.

Board for game variant Duo

The 14×14 board used in game variant Duo with
the starting squares marked with coloured dots.

Example position for game variant Duo

An example position in game variant Duo.

Previous | Next

pentobi-7.2/src/pentobi/manual/en_GB/index.html000066400000000000000000000024551227240712600215060ustar00rootroot00000000000000 Pentobi User Manual

Next

Pentobi User Manual

Pentobi is a computer opponent for the board game Blokus. In this game, four players place pieces similar to the pieces of the computer game Tetris on a 20×20 board. Pentobi also supports the game variant for two players and the game variants Duo, Trigon and Junior.

Classic Rules
Duo Rules
Trigon Rules
Junior Rules
How to Use Pentobi
Become a Stronger Player
The Window Menu Explained
Keyboard Shortcuts
System Requirements
License

pentobi-7.2/src/pentobi/manual/en_GB/junior_rules.html000066400000000000000000000020511227240712600231070ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

Junior Rules

Junior is a simplified game variant for two players. It is played on the same 14×14 board as game variant Duo but uses only the pentominoes that have relatively simple shapes.

Pieces for game variant Junior

The 24 pieces used in Junior.

Bonus points are not used in game variant Junior.

Previous | Next

pentobi-7.2/src/pentobi/manual/en_GB/license.html000066400000000000000000000025111227240712600220120ustar00rootroot00000000000000 Pentobi User Manual

Previous

License

Copyright © 2011т€“2013 Markus Enzenberger

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

Trademark Disclaimer

The trademark Blokus and other trademarks referred to are property of their respective trademark holders. The trademark holders are not affiliated with the author of the program Pentobi.

Previous

pentobi-7.2/src/pentobi/manual/en_GB/shortcuts.html000066400000000000000000000035721227240712600224360ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

Keyboard Shortcuts

In addition to the menu item shortcut keys, which are shown in the window menu, the following shortcut keys are supported by Pentobi. Note that these shortcuts are not active when the comment text field is shown and has the focus. In this case, the focus can be switched away from the comment text with the Tab key.

Plus

Select next piece

Minus

Select previous piece

0

Clear selected piece

Space

Next orientation of the selected piece

Shift+Space

Previous orientation of the selected piece

Left, Right, Up, Down

Move the selected piece.

Enter

Play the selected piece.

1, 2, A, C, F, G, I, L, N, O, P, S, T, U, V, W, X, Y, Z

Select piece according to commonly used piece names. If there are multiple pieces with the letter (e.g. I3, I4, I5), pressing the key several times cycles between them. Some letters are used only in certain game variants. For example, A is used only in Trigon for the pieces A6 and A4 (also known as "lobster" and "triangle").

Previous | Next

pentobi-7.2/src/pentobi/manual/en_GB/system.html000066400000000000000000000017411227240712600217200ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

System Requirements

Minimum: 512 MB RAM, 1 GHz CPU
Recommended for playing level 8: 2 GB RAM, dual-core 2 GHz CPU

Pentobi will also work on systems that do not meet the minimum requirements but the highest playing level will be very slow on those systems (if the CPU is too slow) or have a reduced playing strength (if there is not enough memory).

Previous | Next

pentobi-7.2/src/pentobi/manual/en_GB/trigon_rules.html000066400000000000000000000047601227240712600231140ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

Trigon Rules

Trigon is another game variant. The rules a similar to game variant Classic but it uses a differently shaped board and set of pieces. Each colour uses 22 pieces that are shaped like the polyiamonds up to size six. (A polyiamond is a figure built by a number of equilateral triangles connected along the edges.)

Pieces for game variant Trigon

The 22 Trigon pieces.

The board also consists of triangles and is shaped like a hexagon with nine triangles building an edge.

Board for game variant Trigon

The board with the starting
fields marked with gray dots.

There are six starting points on the board, each located four rows from the middle of an edge. The starting points are not coloured and the players may freely choose a starting point for the first piece of a colour.

Example position for game variant Trigon

An example position after a few moves.

Trigon Rules for Two Players

Like in game variant Classic, Trigon can be played with two players by having one player play Blue and Red and the other player Yellow and Green.

Trigon Rules for Three Players

Trigon can be played with three players using the same rules as for the four-player variant. The three-player variant is played on a smaller board with an edge size of eight triangles. The six starting points are located three rows from the middle of an edge.

Previous | Next

pentobi-7.2/src/pentobi/manual/en_GB/user_interface.html000066400000000000000000000111301227240712600233630ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

How to Use Pentobi

Board

Pentobi's main window shows the board on the left side. The played pieces on the board can have numbers on them that indicate the move number in which the piece was played. An letter after the move number indicates that there exists a variation to this move (see below).

Pieces can be played by moving them to a place that corresponds to a legal move with the mouse or arrow keys and pressing the left mouse button or the Enter key.

Pieces and Score

On the right side, the remaining pieces are shown. Above the remaining pieces is an orientation selector that shows the currently selected piece and allows the player to change its orientation. If no piece is selected and the game has not yet ended, a coloured dot in the orientation selector shows the colour to play.

Pieces can be selected by clicking on one of the remaining pieces shown, by using the left/right arrow buttons in the orientation selector or by using shortcut keys.

Below the orientation selector is a score display, which displays the current points for each colour or player. The points are the sum of on-board points and bonus points. Points are underlined if they are final (because the colour cannot play more pieces).

Playing Against the Computer

The board can be used for entering moves played by humans or for playing games against the computer. In games against the computer, the computer can play any (or several) of the colours.

When you start a new game, the human will play the colour(s) of the first player by default and the computer all other colours. To change this, use Computer Colours from the Game menu or toolbar and select the colours the computer should play.

The exception is that the computer will play no colour by default, if it played no colour in the previous game. This prevents the computer from automatically starting to play if the user mainly wants to use the board for entering move sequences or similar editing tasks. So if you want to use the board without playing against the computer, you need to disable the computer colours in the Computer Colours dialog only once and it will stay that way. After loading a saved game, the computer also plays no colour by default.

Selecting Play from the Computer menu or the toolbar always makes the computer play a move for the current colour. If the computer did not already play this colour before, it will also make the computer play this colour (and only this colour) from now on.

Move Variations and the Game Tree

When you play a game, Pentobi will store the sequence of moves and it is always possible to go back to a previous position and play differently. If you do this, the new sequence is stored as an alternative sequence (called a variation). Variations can also be used by annotators for commenting on existing games. Variations can exist at any board position and can have subvariations themselves. The game can therefore become a game tree, in which each node represents a board position. You can navigate in the game tree with the items in the Go menu or in the toolbar.

The main variation is the sequence of moves that starts at the start position and always selects the first child node in each position (e.g. by selecting Forward in the Go menu or toolbar). The main variation is supposed to represent the real game played. If you want a side variation to become the main variation, select Make Main Variation from the Edit menu.

Previous | Next

pentobi-7.2/src/pentobi/manual/en_GB/window_menu.html000066400000000000000000000241061227240712600227270ustar00rootroot00000000000000 Pentobi User Manual

Previous | Next

The Window Menu Explained

Game

New
Start a new game.
New Rated Game
Start a new rated game against the computer.
Game Variant
Select a game variant and start a new game of this game variant.
Computer Colours
Select which colours are played by the computer.
Game Info
Display or edit additional information about the game like the name of the players or the date when the game was played.
Undo Move
Undo the last move played and remove it from the game tree. Undoing a move is only possible if it is the last move in the current variation (i.e. a leaf node in the game tree; use Edit/Truncate to remove inner nodes of the game tree).
Find Move
Find a legal move for the current colour and display it for a few seconds on the board. Selecting this item repeatedly will show all legal moves.
Open
Load a saved game. The board position after loading will be the last position in the main variation unless the game starts with a setup position. If the game starts with a setup, the board position will be the first position instead. This avoids that solutions are immediately shown if the file contains a Blokus puzzle as a setup with the solution as the main variation.
Open Recent
Load a recently used saved game.
Save
Save the current game.
Save As
Save the current game under a new file name.
Export/Image
Save the current position as an image file. Several image file formats are supported, the file format is derived from the file name ending (e.g. ".png" for the PNG format).
Export/ASCII Art
Save the current position as a text diagram. The text diagram should be viewed using a monospace font.
Quit
Quit Pentobi.

Go

Beginning
Go to the beginning of the game.
10 Backward
Go ten moves backward in the current variation.
Backward
Go one move backward in the current variation.
Forward
Go one move forward in the current variation. If the current position has several follow-up variations (i.e. the current node in the game tree has several child nodes), the first variation will be used.
10 Forward
Go ten moves forward in the current variation. Like Forward, this also uses the first variation in positions with several follow-up variations.
End
Go to the end of the current variation. Like Forward, this also uses the first variation in positions with several follow-up variations.
Next Variation
Go to the next variation to the last move played (i.e. the next sibling node of the current node in the game tree).
Previous Variation
Go to the previous variation to the last move played (i.e. the previous sibling node of the current node in the game tree).
Go to Move
Go to the move with a given move number in the current variation.
Back to Main Variation
Return to the last move in the main variation that had a variation.
Beginning of Branch
Go back to the last position in the current variation that had an alternative move.
Find Next Comment
Go to the next position that has a comment. If the comment text field was not visible, it will become visible. Selecting this item repeatedly will show all positions with comments in the game tree.

Edit

Move Annotation
Add a chess-style move annotation symbol (e.g. !!) to the current move. Annotation symbols are shown after the move numbers on the board and only visible if move numbers are shown.
Make Main Variation
Make the current variation the main variation of the game. This reorders the nodes in the game tree such that the current variation becomes the main variation.
Move Variation Up
Changes the order of variations such that the current position will appear earlier when iterating over the variations with Next/Previous Variation.
Move Variation Down
Changes the order of variations such that the current position will appear later when iterating over the variations with Next/Previous Variation.
Delete All Variations
Delete all variations but the main variation. If the current position is not on the main variation, it will be changed to a position in the main variation as in Back to Main Variation.
Truncate
Remove the node with the current position, including any subtree, from the game tree.
Truncate Children
Remove all children nodes of the node with the current position from the game tree.
Keep Only Position
Delete all moves and keep only the current position as a setup. This can be used to create files that start with a given fixed position.
Keep Only Subtree
Like Keep Only Position but does not delete the moves after the current position.
Setup Mode
Enter or leave setup mode. In setup mode, pieces can be placed anywhere on the board, even in violation of the game rules. Existing pieces can be removed from the board by clicking on them. The currently selected colour also determines the colour to play after the setup is finished. It can be changed with Select Next Colour or by clicking on the orientation selector while no piece is selected. Setup mode can only be used if no moves have been played.
Select Next Colour
Choose the next colour for selecting pieces. This can be used for example to enter game records in which moves of a colour were skipped because the colour ran out of time.

View

Toolbar
Show or hide the toolbar.
Toolbar Text
Configure the appearance of the toolbar.
Comment
Show or hide a text field to display or edit comments on the current position.
Move Numbers
Change the way move numbers are displayed on the board. The options are to show only the number of the last move or of all moves or to show no numbers at all.
Coordinates
Display coordinates for the fields on the board around the board. The convention for the coordinates is the same as in the Blokus SGF file format used by Pentobi.
Show Variations
Appends a letter to the move number on the board if the moves has a variation.
Fullscreen
Make the main window full screen or leave full screen mode. It is platform-dependent if the window menu is shown in full screen mode. To leave full screen mode without using the window menu, press the F11 key.

Computer

Play
Make the computer play a move for the current colour. This can be used to change the colour the computer plays or to resume playing after navigating in the game tree. If the computer did not already play the current colour, it will play this colour (and only this colour) from now on.
Play Single Move
Make the computer play a single move for the current colour without changing the colours played by the computer.
Stop
Abort the current move generation. You can make the computer continue to play by selecting Play.
Level
Change the playing strength of the computer. Higher levels are stronger but can make the computer take a long time for playing moves on slow computers.

Tools

Your Rating
Show a dialog window with the rating of the user in the current game variant.
Analyse Game
Perform a game analysis.

Help

Contents
Show a window to browse the Pentobi user manual.
About
Show an info dialog with information about this version of Pentobi.

Previous | Next

pentobi-7.2/src/pentobi/pentobi.conf.in000066400000000000000000000004751227240712600201760ustar00rootroot00000000000000# Config file to override installation settings such that the executable # can be tested without installation BooksDir=@CMAKE_SOURCE_DIR@/src/books ManualDir=@CMAKE_SOURCE_DIR@/src/pentobi/manual TranslationsPentobiDir=@CMAKE_BINARY_DIR@/src/pentobi TranslationsLibPentobiGuiDir=@CMAKE_BINARY_DIR@/src/libpentobi_gui pentobi-7.2/src/pentobi/pentobi.ico000066400000000000000000003025361227240712600174210ustar00rootroot00000000000000€€ (V@@ (B~00 Ј%ІJ  ЈNp hі€(€ џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџEHG)EHGrEHGŸEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGЈEHGŸEHGrEHG)џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџEHGEHGЄEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGЄEHGџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџEHGEHGзEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGзEHGџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџEHGЊEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGЊџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџEHG-EHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHG-џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџEHGtEHGџEHGџEHGџEHGџEHGџ=XZѓ4joц5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5fkн4kpч4hmс5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк5dhк6bgж\OAч\OAчXMAнVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLBи\OAч\NAхVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкVLAкMJDэEHGџEHGџEHGџEHGџEHGџEHGtџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџEHG EHGџEHGџEHGџEHGџ>XZёбчџЯхџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЦмњбшџ ХлџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЊk:џЉk:џ‹V-џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џŒW-ѓЊk:џЄg8џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џKIEхEHGџEHGџEHGџEHGџEHG џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџEHGЈEHGџEHGџEHGџEHGџ7inтдщџбчџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџЖЬџЖЬџ Чојгщџ ШоџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџКаџЙЯџЖЬџЖЬџЌm<џЋl<џX/џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ…R+џƒQ*џŒW.ђЌm<џЇi9џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џ‡S+џƒQ*џƒQ*џRJBЭEHGџEHGџEHGџEHGџEHGЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџEHGЈEHGџEHGџEHGџEHGџ;fkж!жыџеъџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџКаџЖЬџ Ъся еыџеыџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЭцџЖЬџЖЬџ­o>џ­n>џІg6џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ‘Z.џƒQ*џŒX.э­o>џЌm<џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ†S+џƒQ*џRJBЭEHGџEHGџEHGџEHGџEHGЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџFIHЈFIHџFIHџFIHџFIHџџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ†S+џƒQ*џRJBЭEHGџEHGџEHGџEHGџEHGЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџFIHЈFIHџFIHџFIHџFIHџ=hkж.йэџ'йэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџКаџЖЬџЫтя-йэџжэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЭцџЖЬџЖЬџБrBџБrAџЇh7џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ‘Z.џƒQ*џX.эБrBџЏp?џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ†S+џƒQ*џRJBЭEHGџEHGџEHGџEHGџEHGЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџGJIЈGJIџGJIџFJIџFJIџ>ilж5ляџ-йюџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџКаџЖЬџЭуя4ляџзюџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЭцџЖЬџЖЬџГtDџВtCџЇh7џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ‘Z.џƒQ*џX.эГtDџАqAџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ†S+џƒQ*џRJBЭEHGџEHGџEHGџEHGџEHGЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџGJIЈGJIџGJIџGJIџGJIџ?imж;н№џ3л№џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЛбџЗЭџЭуя:н№џ йюџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЮцџЗЭџЗЭџЕvFџДuEџЈi8џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ‘Z.џƒQ*џX.эДvFџВsCџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ†S+џƒQ*џRJBЭEHGџEHGџEHGџEHGџEHGЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџGJIЈGJIџGJIџGJIџGJIџ@imжBпђџ9нёџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџНдџЙаџЯхяAпёџ#кяџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЮчџЙаџЙЯџЖxHџЖwGџЈi8џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ”[0џ†T-џY0эЖxHџДuEџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ‰V.џ†T,џRKBЭEHGџEHGџEHGџEHGџEHGЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџHKJЈHKJџHKJџHKJџHKJџBjnжHсѓџ@пђџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџПжџМвџбцяGсѓџ'л№џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЯчџМгџМвџИzJџИyIџЈi8џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ–^2џŠX1џ“]2эИzJџЕwFџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џY1џŠX1џSKCЭEHGџEHGџEHGџEHGџEHGЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџHKJЈHKJџHKJџHKJџHKJџCknжOуєџEсѓџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџСиџОеџвшяNуєџ*м№џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЯшџОеџОеџК|LџЙ{KџЉj9џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ˜`5џ\5џ–`5эК{KџЖxHџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ‘]5џ\5џSLDЭEHGџEHGџEHGџEHGџEHGЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџHLKЈHLJџHLJџHLJџHLJџDknжUхіџLуєџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџУлџСиџ!дщяTфѕџ.нђџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџашџСиџРзџМ}NџЛ}MџЉj9џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ›c7џ“`9џ™c9эМ}MџИzJџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ•a9џ“`9џUMEЭFIHџFIHџFIHџFIHџFIHЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџILKЈILKџILKџILKџILKџFloж\чїџQфѕџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџХнџУкџ#еыя[цїџ2ођџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџащџУлџУкџОOџНOџЉj:џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џe9џ˜d=џœf;эНOџК{LџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ™e<џ˜d<џUNEЭFIHџFIHџFIHџFIHџFIHЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџILKЈILKџILKџILKџILKџFloжbщјџWцїџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџШпџХнџ&зэяaшјџ6пѓџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџбъџЦнџХнџПQџП€QџЊk:џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ g;џœiAџ i>эПQџМ}MџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џži@џœh@џVOFЭFIHџFIHџFIHџFIHџFIHЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџJMLЈJMLџIMLџIMLџIMLџGmpжiъњџ]шјџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЩтџШрџ(йэяhъљџ9рєџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџвъџШрџШпџСƒSџС‚SџЊk;џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЂj>џЁmEџЂlBэСƒSџН~NџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЁlCџЁmEџWPGЭGJIџGJIџGJIџGJIџGJIЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџJMLЈJMLџJMLџJMLџJMLџImqжpьћџcъљџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЬфџЪтџ*кяяoьћџ=сєџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџвыџЫуџЪтџУ…UџТ„UџЋl;џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЅl@џЅqIџЇoDэУ„UџО€PџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џІpGџЅqHџXPHЭGJIџGJIџGJIџGJIџGJIЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџJNLЈJNLџJNLџJMLџJMLџJnqжvюќџiыћџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЮцџЭхџ-м№яuюќџ@тѕџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџвыџЭхџЭхџХ‡WџФ†VџЋl;џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЇoBџЊuMџЉrGэХ†WџР‚RџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЉtKџЉuLџYRIЭGKJџGKJџGKJџGKIџGKIЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџKNMЈKNMџKNMџKNMџKNMџKoqж}№ўџoэќџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџащџЯшџ/пђя|№§џDуіџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџгьџЯшџЯчџЧˆYџЦˆXџЋl<џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЊqDџЎyQџ­uKэЦˆYџТ„TџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ­xNџЎyPџZRJЭHKJџHKJџHKJџHKJџHKJЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџKNMЈKNMџKNMџKNMџKNMџKorжƒђџџuюќџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџвыџвъџ1пѓя‚ђџџHфїџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџвыџвъџШŠ[џШŠZџЌm<џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЌsGџГ}UџАyMэШŠ[џФ…UџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џВ|RџВ}Tџ[SJЭHKJџHKJџHKJџHKJџHKJЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџKONЈKONџKONџKONџKOMџKorжƒђџџwя§џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџ1рєяƒђџџJхїџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЪŒ]џЪ‹\џЌm=џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЏuIџЗ‚YџГ{PэЪŒ]џХ†WџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЕVџЗXџ\TLЭILKџILKџILKџILKџILKЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџLONЈLONџLONџLONџLONџLorжƒђџџwя§џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџз№џз№џ1сѕяƒђџџJхїџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџеюџз№џжяџЬŽ_џЫ^џ­n=џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џВxKџЛ†]џЖ~SэЬŽ_џЦˆYџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џКƒYџЛ†\џ]ULЭILKџILKџILKџILKџILKЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџLPNЈLPNџLONџLONџLONџLorжƒђџџwя§џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџиђџйђџ1тіяƒђџџJхїџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџеюџйѓџйђџЭ`џЭ`џ­n=џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џДzMџРŠaџКVэЭ`џШŠ[џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џН‡]џРŠ`џ]VNЭIMLџIMKџIMKџIMKџIMKЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџMPOЈLPOџLPOџLPOџLPOџLpsжƒђџџwя§џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџлєџлѕџ1уїяƒђџџJхїџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџжяџмѕџлѕџЭ`џЭ`џ­n>џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЗ}PџФŽeџН…YэЭ`џШŠ[џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џТŠaџФŽdџ^WNЭJMLџJMLџJMLџJMLџJMLЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџMPOЈMPOџMPOџMPOџMPOџMpsжƒђџџwя§џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџнїџојџ1ујяƒђџџJхїџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџжяџојџоїџЭ`џЭ`џ­n>џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЙRџЩ’iџРˆ[эЭ`џШŠ[џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЦdџЩ’hџ_XOЭJMLџJMLџJMLџJMLџJMLЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџMQOЈMQOџMQOџMQOџMQOџMqsжƒђџџwя§џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџпјџрњџ1фљяƒђџџJхїџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџз№џрњџрњџЭ`џЭ`џ­n>џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џМTџЭ–mџУŠ^эЭ`џШŠ[џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЪ’hџЭ–lџ`YPЭJNMџJNMџJNMџJNMџJNMЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџNQPЈNQPџNQPџNQPџNQPџNqtжƒђџџ‚ђџџsю§џsю§џsю§џsю§џsю§џsю§џsю§џsю§џsю§џsю§џsю§џsю§џsю§џsю§џsю§џsю§џqю§џpэќџmэќџkьћџiыњџfыњџdыћџbыћџDцљєƒђџџ|№ўџsю§џsю§џsю§џsю§џsю§џsю§џsю§џsю§џsю§џsю§џsю§џsю§џsю§џsю§џsю§џsю§џqэќџoэќџlэћџjьћџhыњџeъћџcыћџaыћџЭ`џЭ`џЩ‹\џШŠ[џШŠ[џШŠ[џШŠ[џШŠ[џШŠ[џШŠ[џШŠ[џШŠ[џШŠ[џШŠ[џШŠ[џЧ‰ZџЦˆYџЦˆYџХ‡XџХ‡WџХ†WџХ†VџФ†VџУ…UџЦˆYџЧŠ\џЦ‰[їЭ`џЭ_џШŠ[џШŠ[џШŠ[џШŠ[џШŠ[џШŠ[џШŠ[џШŠ[џШŠ[џШŠ[џШŠ[џШŠ[џЧ‰ZџЧ‰ZџЦˆYџЦˆYџХ‡XџХ†WџХ†WџФ†VџФ†UџУ…UџШ‰[џЧŠ\џg\PкKNMџKNMџKNMџKNMџKNMЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџNRPЈNQPџNQPџNQPџNQPџNqtжƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџ€ёўџ}№ўџ{№§џxя§џvюќџIчњѕƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџ‚ђџџёўџ|№ўџzя§џwя§џuюќџЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЬŽ_џЬ^џЫ^џЪŒ]џЪŒ]џЩ‹\џЩŠ[џШŠ[џЦ‹\љЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЬŽ_џЬŽ_џЫ^џЫ^џЪŒ]џЪ‹\џЩ‹\џШŠ[џШŠZџh]QмKOMџKOMџKOMџKOMџKNMЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџNRQЈNRQџNRQџNRQџNRQџAnsк Ысњ ЪсљСзяСзяСзяСзяСзяСзяСзяСзяСзяСзяСзяСзяСзяСзяСзяСзяСзяСзяСзяСзяСзяСзяТиєТиѕJББяЄ“њ|Ѓ“ѕvЁ“яvЁ“яvЁ“яvЁ“яvЁ“яvЁ“яvЁ“яvЁ“яvЁ“яvЁ“яvЁ“яvЁ“яvЁ“яvЁ“яvЁ“яvЁ“яvЁ“яvЁ“яvЁ“яvЁ“яvЁ“яoІ›№YИЙѕYИИё e6њ d5њ’[0ёY/яY/яY/яY/яY/яY/яY/яY/яY/яY/яY/яY/яY/яY/яY/яY/яY/яY/яY/яY/яY/яZ/ђZ0ѕQ—VъМlњЙiјЊ_яЊ_яЊ_яЊ_яЊ_яЊ_яЊ_яЊ_яЊ_яЊ_яЊ_яЊ_яЊ_яЊ_яЊ_яЊ_яЊ_яЊ_яЊ_яЊ_яЊ_яЊ_я Њ_є!Њ_ѕM^QЯLONџLONџLONџLONџLONЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџORQЈORQџORQџORQџORQџ>qvувщџЯхџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџT‚њЊk;џ™`3џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џЋl;џЊk:џ‹W.џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џ6Ѓ]ѓ$Чtџ Сpџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ XџA`QЭLONџLONџLONџLONџLONЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџOSQЈOSQџOSQџOSQџOSQџ@quндъџвшџУкџУкџУкџУкџУкџУкџУкџУкџУкџУкџУкџУкџУкџУкџУкџУкџУкџУкџУкџУкџУкџУкџИЮџЖЬџU“…ѕЌm=џ e6џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џŽX-џƒQ*џƒQ*џЌm=џЌm<џ—^1џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‘Z.џ‰U,џƒQ*џ?Ђ\№'Щwџ#ЦtџЖdџЖdџЖdџЖdџЖdџЖdџЖdџЖdџЖdџЖdџЖdџЖdџЖdџЖdџЖdџЖdџЖdџЖdџЖdџЖdџЖdџЖdџЂYџ XџAaRЭLPOџLPOџLPNџLPNџLPNЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџOSRЈOSRџOSRџOSRџOSRџCosж#жыџеыџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџКаџЖЬџV–‰яЎo>џЉj:џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џa2џƒQ*џƒQ*џЎo?џ­o>џІg6џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ‘Z.џƒQ*џJ \э)Ьzџ&ЬwџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЅZџ XџBaRЭMPOџMPOџMPOџMPOџMPOЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџPSRЈPSRџPSRџPSRџPSRџDosж*иэџ#зьџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџКаџЖЬџW–‰яАq@џЊk;џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џa2џƒQ*џƒQ*џАqAџЏq@џІg6џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ‘Z.џƒQ*џKЁ]э,Ю|џ(ЮzџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЅZџ XџBbRЭMQOџMQOџMPOџMPOџMPOЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџPTSЈPTSџPTSџPTRџPTRџEqsж0кюџ)йэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџКаџЖЬџX—ŠяБsBџЋl<џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џa2џƒQ*џƒQ*џВsCџБrBџЇh7џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ‘Z.џƒQ*џLЂ]э/бџ+Я}џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЅZџ XџBbSЭMQPџMQPџMQPџMQPџMQPЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџQTSЈQTSџQTSџQTSџPTSџFqtж7мяџ/кяџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџКаџЖЬџX˜‹яГtDџЌm=џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џa2џƒQ*џƒQ*џГuEџГtDџЇh7џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ‘Z.џƒQ*џMЂ^э2гџ.в~џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџІ[џЁYџCbSЭNQPџNQPџNQPџNQPџNQPЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџQUSЈQUSџQUSџQUSџQUSџHrtж=оёџ6м№џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЛвџИЮџY˜ŒяЕvFџ­o>џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џa2џƒQ*џƒQ*џЕwFџЕvFџЈi8џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ’Z.џƒQ*џNЃ_э5е„џ/дџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЈ\џЄZџCcTЭNRPџNRPџNRPџNRPџNRPЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџQUTЈQUTџQUTџQUTџQUTџIrvжDрђџ;оёџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџНдџКаџY›ŽяЗxHџЎp?џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џžb3џˆU.џ‡U.џЗyHџЖxHџЈi8џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ”]1џ‡U.џQЅaэ8и‡џ2жƒџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЊ]џІ[џDcTЭORQџORQџORQџORQџNRQЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџRUTЈRUTџRUTџRUTџRUTџJrvжJтѓџBрђџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџРзџМгџZœяИzJџЏp@џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џŸc4џŒZ2џŒY2џЙzJџИzJџЈi9џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ–_3џŒY2џSЇcэ;к‰џ5и…џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЌ_џЈ\џDdTЭOSQџORQџORQџORQџORQЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџRVTЈRVTџRVTџRVTџRVTџKsvжQуѕџGтѓџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџТйџПжџ[ž‘яК|LџАqAџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ c5џ‘^6џ]6џЛ|LџК|LџЉj9џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ™a5џ^6џVЊeэ=нŒџ7кˆџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЏ`џЊ^џDdUЭOSRџOSRџOSRџOSRџOSRЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџRVUЈRVUџRVUџRVUџRVUџLswжWхіџMуєџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџФлџСиџ[Ÿ“яМ~NџБsBџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЁd6џ•b:џ”a:џМ~NџМ}NџЉj9џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џœc7џ•b:џXЋhэ@пџ:нŠџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџАaџ­_џEeUЭPSRџPSRџPSRџPSRџPSRЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUЈSWUџSWUџSWUџSWUџMuwж^чїџSфіџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЦоџФлџ\ •яОPџВtCџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЂe6џšf>џ™e>џО€PџОOџЉk:џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џžf:џ™f>џ[­jэCс‘џ=оŒџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџВbџЏ`џEfUЭPTRџPTRџPTRџPTRџPTRЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWVЈSWVџSWVџSWVџSWUџNuwжeщљџYцїџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЩрџЦоџ]Є—яРRџГuDџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЃf7џžjBџjBџР‚RџПQџЊk:џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЁh<џžjBџ]АlэFф”џ?сџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџДcџБaџEfVЭPTSџPTSџPTSџPTSџPTSЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџTWVЈTWVџTWVџTWVџSWVџOuxжkыњџ_шљџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЪтџЩрџ^Є˜яСƒTџДuFџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄg8џЂnFџЂnFџТƒTџСƒSџЊk;џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЃk>џЂnFџ_ВnэIц–џAу‘џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЖdџГcџFgVЭQTSџQTSџQTSџQTSџQTSЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџTXVЈTXVџTXVџTXVџTXVџQvxжrэћџeъњџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЭхџЫуџ^ІšяУ…UџЖwGџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЅh9џЇsJџІrJџФ…VџУ…UџЋl;џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џІm@џЇrJџbЕpэLщ™џDф”џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЙeџЖdџFgXЭQUTџQUSџQUSџQUSџQUSЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџTXWЈTXWџTXWџTXWџTXWџRvyжxя§џkьћџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЯчџЮцџ_Ї›яХ‡WџЖxHџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џІi:џЋwNџЋvNџХ‡XџХ‡WџЋl;џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЉoCџЋwNџeЗrэOыœџFч•џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџКfџИeџGhXЭRUTџRUTџQUTџQUTџQUTЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџUYWЈUYWџUYWџUYWџUXWџSwyжёўџqэќџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџащџашџ_ЉžяЧ‰YџЗyIџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЇj;џА{RџЏzRџЧ‰ZџЧˆYџЋm<џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЋqEџА{RџeИtэQэžџIщ˜џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџМgџКfџGiXЭRVTџRVTџRVTџRVTџRVTЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџUYXЈUYXџUYWџUYWџUYWџSwyжƒђџџwя§џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџгьџвыџ`Њ яЩŠ[џИzJџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЈk<џДVџДVџЩ‹\џШŠ[џЌm<џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЎtGџДVџgЙuэQэžџJъ™џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџОiџМhџGiYЭRVUџRVUџRVUџRVUџRVUЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџUYXЈUYXџUYXџUYXџUYXџSwzжƒђџџwя§џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџеюџеюџaЌЁяЪŒ]џЙ{KџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЉl=џЙƒZџИƒZџЫ^џЪŒ]џЌm=џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џАwIџЙƒZџhЛwэQэžџJъ™џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџСjџПiџHiYЭSVUџSVUџSVUџSVUџSVUЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџVZXЈVZXџVZXџVZXџVZXџTxzжƒђџџwя§џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџиёџзёџb­ЂяЬŽ_џК|LџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЊm=џН‡^џН‡^џЬŽ_џЬŽ_џ­n=џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џВyLџН‡^џjМwэQэžџJъ™џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџУkџСjџHkYЭSWUџSWUџSWUџSWUџSWUЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџVZYЈVZYџVZYџVZYџVZYџTx{жƒђџџwя§џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџйѓџкѓџbЏЃяЭ`џЛ}MџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЋn>џТ‹bџС‹bџЭ`џЭ`џ­n>џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЕ{NџС‹bџkНyэQэžџJъ™џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџФlџУkџHkZЭSWVџSWVџSWVџSWVџSWVЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџW[YЈW[YџW[YџVZYџVZYџTx{жƒђџџwя§џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџмѕџміџbАЄяЭ`џЛ}MџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЌn?џЦfџЦfџЭ`џЭ`џ­n>џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЗ}PџЦfџlПzэQэžџJъ™џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЦnџХlџIlZЭTXVџTXVџTXVџTXVџTXVЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџW[YЈW[YџW[YџW[YџW[YџUy{жƒђџџwя§џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџојџпљџbБЅяЭ`џЛ}MџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ­o@џЫ”jџЪ“jџЭ`џЭ`џ­n>џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џК€RџЪ”jџnП{эQэžџJъ™џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЩnџЧnџIm[ЭTXWџTXWџTXWџTXWџTXVЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџW[ZЈW[ZџW[ZџW[ZџW[ZџUy|жƒђџџy№ўџк№џк№џк№џк№џк№џк№џк№џк№џк№џк№џк№џк№џк№џк№џк№џй№џй№џй№џй№џй№џй№џй№џсљџтњџbДЋ№Э`џП€PџЌm<џЌm<џЌm<џЌm<џЌm<џЌm<џЌm<џЌm<џЌm<џЌm<џЌm<џЌm<џЋm<џЋl<џЋl<џЋl<џЋl;џЋl;џЋl;џЋl;џЊl;џВuEџЭ•jџЬ•jџЭ`џЭ`џГtDџЌm<џЌm<џЌm<џЌm<џЌm<џЌm<џЌm<џЌm<џЌm<џЌm<џЌm<џЋm<џЋm<џЋl<џЋl<џЋl<џЋl;џЋl;џЋl;џЋl;џЊl;џОƒVџЭ•jџpР|яQэžџKъšџ!з{џ!з{џ!з{џ!з{џ!з{џ!з{џ!з{џ!з{џ!з{џ!з{џ!з{џ!з{џ!з{џ!з{џ!з{џ зzџ жzџ жzџ жzџжyџеyџеyџЮuџЭtџJn]аUXWџTXWџTXWџTXWџTXWЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџX\ZЈX\ZџX\ZџX\ZџX\ZџVz|жƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџёџџ~ёўџ|№§џyя§џvюќџtюќџqэћџfЦХѕЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЬŽ_џЬŽ_џЫ^џЫŒ]џЪŒ]џЩ‹\џЩ‹[џШŠ[џШ‰ZџЧ‰ZџЦˆYџЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЬŽ_џЫ^џЫ^џЪŒ]џЪŒ\џЩ‹\џЩŠ[џШŠ[џЧ‰ZџЧ‰YџtНyљQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџPьџOыœџMъšџLщ™џJч˜џIц–џGх•џFф”џEу“џLwaмUYWџUYWџUYWџUYWџUYWЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџX\[ЈX\[џX\[џX\ZџX\ZџWy{дƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџ‚ђџџ€ёўџ}№ўџzя§џxя§џiЧФђЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЬŽ_џЬŽ_џЫ^џЫŒ]џЪŒ]џЩ‹\џЩ‹[џШŠ[џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЬŽ_џЬ^џЫ^џЪŒ]џЪŒ]џЩ‹\џЩŠ[џzМyїQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџPьџOыœџMъšџLщ™џJч˜џIц–џGх•џLwbмUYXџUYXџUYXџUYXџUYXЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџX][ЈX][џX\[џX\[џX\[џCy~убшџЮхџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџiпњюџтџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџюџюџиџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџ“…ѓ"ХsџРnџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ XџJiYЭVZXџVZXџVZXџVZXџVYXЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџY][ЈY][џY][џY][џY][џEy~угщџЯхџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџkпњ юџтџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџ!!юџ юџиџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџ”†ѓ%Шuџ!Тqџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ XџJiZЭVZXџVZXџVZXџVZXџVZXЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџY]\ЈY]\џY]\џY]\џY]\џIx|йеъџгъџЭцџЭцџЭцџЭцџЭцџЭцџЭцџЭцџЭцџЭцџЭцџЭцџЭцџЭцџЭцџЭцџЭцџЭцџЭцџЭцџЭцџЭцџЙЯџЖЬџqтё$$юџ##ьџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџфџвџвџ$$яџ##юџ$$ъџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџ$$щџмџвџ‹™ю'Ъxџ$ЩvџЧmџЧmџЧmџЧmџЧmџЧmџЧmџЧmџЧmџЧmџЧmџЧmџЧmџЧmџЧmџЧmџЧmџЧmџЧmџЧmџЧmџЧmџЄZџ XџJiZЭVZYџVZYџVZYџVZYџVZYЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџZ^\ЈY^\џY^\џY^\џY^\џKx{ж%зьџжыџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџКаџЖЬџtуя''яџ''яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ$$щџвџвџ((яџ''яџ((яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџпџвџˆэ*Эzџ'ЬxџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЅZџ XџKjZЭW[YџW[YџW[YџW[YџW[YЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџZ^]ЈZ^\џZ^\џZ^\џZ^\џLx{ж,йэџ%иьџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџКаџЖЬџuуя++яџ**яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ$$щџвџвџ,,яџ++яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџпџвџ‰žэ-Я}џ)Ю{џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЅZџ XџKj[ЭW[ZџW[ZџW[ZџW[ZџW[ZЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџZ^]ЈZ^]џZ^]џZ^]џZ^]џNx|ж2люџ,йюџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџКаџЖЬџvуя//яџ,,яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ$$щџвџвџ//яџ..яџ**яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџпџвџ ŠŸэ0б€џ,а}џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЅZџ XџKk[ЭX\ZџW\ZџW[ZџW[ZџW[ZЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ[_]Ј[_]џ[_]џ[_]џ[_]џOy|ж9м№џ1ляџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџКаџЖЬџxуя22№џ..яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ$$щџвџвџ33№џ22яџ++яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџпџвџ!‹ э3д‚џ.гџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџІ[џЂYџKk[ЭX\ZџX\ZџX\ZџX\ZџX\ZЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ[_^Ј[_^џ[_^џ[_^џ[_^џPz}ж?оёџ7нёџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџМгџИЯџzфя66№џ00№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ%%щџдџдџ66№џ55№џ,,яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџрџдџ#ŒЁэ6ж…џ0е‚џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЉ\џЄZџKk\ЭX\[џX\[џX\[џX\[џX\[Јџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ[`^Ј[`^џ[`^џ[`^џ[`^џQ{}жFрђџ=пђџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџОеџЛбџ{хя99№џ22№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ%%ъџжџжџ::№џ99№џ,,яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ сџжџ&ŽЃэ9йˆџ3ж„џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЋ^џЇ\џMl\ЭY][џY][џY][џY][џY][Јџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ\`^Ј\`^џ\`^џ\`^џ\`^џR{}жLтєџCрђџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџСзџНдџ~ця==№џ44№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ&&ъџиџзџ>>№џ==№џ--яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ""тџиџ(Ѕэ;лŠџ6й†џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџ­_џЉ]џMm]ЭY]\џY]\џY]\џY][џY][Јџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ\`_Ј\`_џ\`_џ\`_џ\`_џS{жSфѕџIтєџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџТкџРзџ€цяAAёџ66№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ''ъџ йџ йџAAёџ@@ёџ..яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ$$уџ йџ*“Ѕэ>нџ8л‰џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЏ`џЋ^џMn]ЭY^\џY]\џY]\џY]\џY]\Јџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ]a_Ј\a_џ\a_џ\a_џ\a_џT|жZціџOуѕџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџХмџТйџ‚чяDDёџ88№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ((ыџ$$лџ##лџEEёџDDёџ//яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ&&фџ$$лџ,”ЇэAрџ;н‹џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџАaџ­_џMn]ЭZ^\џZ^\џZ^\џZ^\џZ^\Јџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ]a`Ј]a`џ]a_џ]a_џ]a_џV}ж`шјџUхіџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЧоџХмџ!…щяHHёџ::№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))ыџ((нџ''нџIIёџHHёџ00яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ((хџ((нџ/–ЉэDт’џ>пџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџГcџАaџMn^ЭZ^]џZ^]џZ^]џZ^]џZ^]Јџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ]b`Ј]b`џ]b`џ]b`џ]b`џV}€жgъљџ[чјџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЩсџЧпџ"‡ъяLLёџ<<№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ**ыџ,,пџ++оџLLёџKKёџ00яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ++цџ,,пџ0™ЊэGх•џ?сџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЕdџВbџMo_ЭZ_]џZ_]џZ_]џZ_]џZ_]Јџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ^b`Ј^b`џ^b`џ^b`џ^b`џW~€жmьћџaщљџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЫуџЩсџ#ŠыяOOђџ>>ёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ++ьџ00рџ//рџPPђџOOђџ11яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ--чџ//рџ3šЌэJч—џBф’џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЗdџДcџNp_Э[_]џ[_]џ[_]џ[_]џ[_]Јџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ^baЈ^baџ^baџ^baџ^baџY~жtюќџgыњџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЭхџЬфџ%ьяSSђџ@@ёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ++ьџ33тџ33тџSSђџRRђџ22№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ//шџ33тџ5ЎэMщšџEх”џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЙeџЖdџNq`Э[_^џ[_^џ[_^џ[_^џ[_^Јџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ^caЈ^caџ^caџ^caџ^caџZжzя§џmэќџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџашџЮчџ&ŽэяVVђџBBёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ,,эџ77фџ77фџWWђџVVђџ33№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ11щџ77фџ7ŸЏэOьџGш–џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЛgџЙfџOq`Э\`^џ\`^џ\`^џ\`^џ[`^Јџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ_caЈ_caџ_caџ_caџ_caџ[жёџџsюќџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџбъџбщџ'юяZZђџDDёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ--эџ;;цџ;;хџ[[ђџZZђџ44№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ44ъџ;;цџ8 АэQэžџJъ™џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџНhџЛgџOqaЭ\`_џ\`_џ\`_џ\`_џ\`_Јџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ_dbЈ_dbџ_dbџ_dbџ_cbџ[‚жƒђџџwя§џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдьџгьџ)“№я^^ѓџFFёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ..эџ??чџ>>чџ^^ѓџ]]ѓџ44№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ55ыџ??чџ:ЁАэQэžџJъ™џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџПiџНhџOraЭ\a_џ\a_џ\a_џ\a_џ\a_Јџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ_dbЈ_dbџ_dbџ_dbџ_dbџ[€‚жƒђџџwя§џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџжяџжяџ*•ђяaaѓџHHёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ//юџCCщџBBщџbbѓџaaѓџ55№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ88ьџCCщџ;ЂБэQэžџJъ™џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџСjџПiџPsaЭ]a_џ]a_џ]a_џ]a_џ]a_Јџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ`dcЈ`dcџ`dcџ`dcџ`dcџ\€‚жƒђџџwя§џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџиёџиёџ*–ѓяccѓџJJёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ00юџGGыџFFыџccѓџccѓџ66№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ::эџFFыџ<ЄВэQэžџJъ™џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџУlџТjџPsbЭ]a`џ]a`џ]a`џ]a`џ]a`Јџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ`ecЈ`ecџ`ecџ`ecџ`ecџ\ƒжƒђџџwя§џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџкѓџлєџ*–єяccѓџJJёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ00яџKKэџJJьџccѓџccѓџ66№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ<<юџJJэџ=ЅВэQэžџJъ™џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџХmџФlџPtbЭ]b`џ]b`џ]b`џ]b`џ]b`ЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџaecЈaecџaecџaecџaecџ\ƒжƒђџџwя§џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџміџнїџ*—єяccѓџJJёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ11яџNNяџNNюџccѓџccѓџ66№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ>>яџNNюџ?ІВэQэžџJъ™џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЧnџЦmџQudЭ^baџ^baџ^b`џ^b`џ^b`ЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџafdЈafdџafdџaedџaedџ]„жƒђџџwя§џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџпјџпљџ*˜ѕяccѓџJJёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ22яџRR№џRR№џccѓџccѓџ66№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџAA№џRR№џ@ЇГэQэžџJъ™џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЩnџШnџQvdЭ^caџ^caџ^caџ^caџ^caЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџafdЈafdџafdџafdџafdџ]‚„жƒђџџ}ёўџFфїџFфїџFфїџFфїџFфїџFфїџFфїџFфїџFфїџFфїџFфїџFфїџFфїџFфїџFфїџEфїџDуіџCуіџAтіџ?тіџ>тѕџ=сєџ;цњџ:цњџ6ЈіђccѓџWWђџHHёџHHёџHHёџHHёџHHёџHHёџHHёџHHёџHHёџHHёџHHёџHHёџHHёџHHёџHHёџHHёџHHёџGGёџGGёџFFёџFFёџJJђџZZѓџYYѓџccѓџccѓџNNђџHHёџHHёџHHёџHHёџHHёџHHёџHHёџHHёџHHёџHHёџHHёџHHёџHHёџHHёџHHёџHHёџHHёџGGёџGGёџFFёџFFёџQQђџZZѓџCЈЖѓQэžџNьœџ5рŠџ5рŠџ5рŠџ5рŠџ5рŠџ5рŠџ5рŠџ5рŠџ5рŠџ5рŠџ5рŠџ5рŠџ5рŠџ5рŠџ4р‰џ4пˆџ3оˆџ2о‡џ2н†џ1н†џ0м…џ/л„џ.жџ.еџRzfе_caџ_caџ_caџ^caџ^caЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџbfeЈbfdџbfdџbfdџbfdџ]‚„жƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџ€ёўџ~№ўџ{№§џxя§џvюќџsэќџDЖјѕccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџbbѓџaaѓџ``ѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџbbѓџaaѓџGЉЗљQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџOьџNы›џMщšџKш™џJч—џHц–џGх•џEу“џT~jм_cbџ_cbџ_cbџ_cbџ_cbЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџbgeЈbgeџbgeџbgeџbgeџ[tƒеaзљѓaзљѓcлљюcлљюcлљюcлљюcлљюcлљюcлљюcлљюcлљюcлљюcлљюcлљюcлљюcлљюcлљюcлљюcлљюcлљюcлљюcлљюcлљюaкјюYфќјVхњњ6Žѓъ--шѓ11чё55цю55цю55цю55цю55цю55цю55цю55цю55цю55цю55цю55цю55цю55цю55цю55цю55цю55цю55цю55цю55цю66ц№99чњ;;шјQbфѓQbфѓR`цяR_цюR_цюR_цюR_цюR_цюR_цюR_цюR_цюR_цюR_цюR_цюR_цюR_цюR_цюR_цюR_цюR_цюR_цюR_цюR_цюR_цюX`ьє[_яњ:ЃЇю&Шvѓ%Шvђ)Ъyю)Ъyю)Ъyю)Ъyю)Ъyю)Ъyю)Ъyю)Ъyю)Ъyю)Ъyю)Ъyю)Ъyю)Ъyю)Ъyю)Ъyю)Ъyю)Ъyю)Ъyю(Ъxю(Щwю(Щwю&Шvю&Чvј$ЧuњSzfе_dbџ_dbџ_dbџ_dbџ_dbЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџbgeЈbgeџbgeџbgeџbgeџPT†уюџыџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџчњюџтџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџвџ#Чtџ"ХsџЈ]џ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ XџЗiѓ#ЦsџСoџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ XџRqaЭ`dbџ`dbџ`dbџ`dbџ`dbЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџchfЈchfџcgfџcgfџcgfџQT†т""юџ ьџеџеџеџеџеџеџеџеџеџеџеџеџеџеџеџеџеџеџеџеџеџеџвџвџшљ!!юџуџеџеџеџеџеџеџеџеџеџеџеџеџеџеџеџеџеџеџеџеџеџдџвџвџ&Щwџ%ШuџЌ`џЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЂYџ XџКiђ&Щvџ"УrџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџЅZџ Xџ XџRrbЭ`ecџ`ecџ`ecџ`ecџ`ecЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџchfЈchfџchfџchfџchfџVZƒж%%яџ$$яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџжџвџ&&юя%%яџ&&юџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ$$щџвџвџ)Ыyџ'ЪxџаtџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЖdџ XџРmэ(Ыyџ%ЫwџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЅZџ XџRrbЭ`ecџ`ecџ`ecџ`ecџ`ecЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџdhfЈdhfџdhfџchfџchfџW[ƒж))яџ((яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџжџвџ((юя((яџ((яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ$$щџвџвџ,Ю|џ*ЭzџаtџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЖdџ XџСnэ+Э{џ(ЭyџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЅZџ XџSrcЭaedџaedџaedџaedџaedЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџdigЈdigџdigџdigџdigџX\„ж,,яџ,,яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџжџвџ++юя,,яџ**яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ$$щџвџвџ/а~џ-Я}џбuџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЖdџ XџСoэ.а~џ*Я|џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЅZџ XџSscЭafdџafdџafdџafdџafdЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџdigЈdigџdigџdigџdigџX\„ж00яџ..яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџжџвџ--юя00яџ,,яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ$$щџвџвџ1гџ0б€џвuџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЖdџЁXџУqэ1вџ-б~џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЅZџЁXџTscЭbfdџafdџafdџafdџafdЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџeihЈeihџeihџeihџeigџZ]„ж44№џ22№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџжџгџ//яя33№џ..№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ$$щџгџгџ4е„џ3д‚џвvџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЗeџЃZџ!Фrэ4еƒџ/г€џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЇ\џЃZџTtdЭbgeџbgeџbgeџbfeџbfeЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџejhЈejhџejhџejhџejhџZ^…ж77№џ55№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџиџеџ33яя77№џ00№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ%%щџеџдџ7з†џ6ж…џгwџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЙeџЅ[џ"Цsэ7з†џ1ж‚џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЉ]џЅ[џTtdЭbgeџbgeџbgeџbgeџbgeЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџejhЈejhџejhџejhџejhџ[_…ж;;№џ88№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџкџжџ66яя;;№џ33№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ&&ъџжџжџ:к‰џ9йˆџгwџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџКfџЇ\џ#Шuэ:йˆџ4з…џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЌ^џЇ\џUueЭcgfџcgeџcgeџcgeџcgeЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџfkiЈfkiџfkiџfkiџfkiџ\`†ж??№џ<<№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџлџиџ88яя>>№џ44№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ''ъџиџиџ=мŒџ;лŠџдxџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЛgџЊ]џ$Щvэ<м‹џ7к‡џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЎ_џЊ]џUvfЭchfџchfџchfџchfџchfЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџfkiЈfkiџfkiџfkiџfkiџ\`†жBBёџ??ёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ##нџ!!кџ;;яяBBёџ77№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ''ъџ!!кџ!!кџ@пŽџ>нџдxџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџНhџЌ_џ%Ъxэ?оŽџ9м‰џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЏaџЌ_џUvfЭchfџchfџchfџchfџchfЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџgkiЈgkiџfkiџfkiџfkiџ]a†жFFёџBBёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ%%оџ%%мџ==яяEEёџ99№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ((ыџ%%мџ%%лџCс‘џAрџеyџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџОhџЎ`џ'ЬyэBрџ<нŒџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџБbџЎ`џVwfЭdigџdigџdhgџdhgџdhgЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџgljЈgljџgljџgljџgljџ^b‡жIIёџEEёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))рџ))нџ@@яяIIёџ;;№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))ыџ))нџ))нџEу“џDт’џ еzџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџПiџАaџ'ЮzэEу“џ>рŽџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџГcџАaџVwgЭdigџdigџdigџdigџdigЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџgljЈgljџgljџgljџgljџ^b‡жMMёџIIёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ--сџ--пџDDяяMMёџ==№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ**ьџ--пџ,,пџHц–џGх•џ жzџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџРiџГbџ(Я{эHх–џ@тџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЖdџГbџVwgЭeigџdigџdigџdigџdigЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџhmkЈhmkџhmkџhmkџhmkџ`cˆжQQђџLLђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ00уџ00сџFFёяPPђџ>>ёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ++ьџ11сџ00сџKш™џJч—џ!ж{џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџТkџЕcџ)б|эKш˜џCф“џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџИeџЕcџVyhЭejhџejhџejhџejhџejhЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџhmkЈhmkџhmkџhmkџhmkџ`dˆжTTђџOOђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ44фџ44уџHHёяTTђџAAёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ,,ьџ55уџ44уџNы›џMщšџ"з{џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџУkџЗeџ+в~эNъ›џEц”џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЙfџЗeџVyhЭejhџejhџejhџejhџejhЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџhmkЈhmkџhmkџhmkџhmkџaeˆжXXђџSSђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ77цџ88фџKKёяXXђџCCёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ--эџ99хџ88фџQэžџOьџ"з|џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџФlџЙfџ+д€эPьџHш—џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЛgџЙfџWziЭfkiџfjiџfjiџfjiџfjhЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџhmkЈhmkџhmkџhmkџhmkџaeˆж[[ђџVVђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ;;чџ<<цџNNђя[[ђџEEёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ--эџ<<цџ<<цџQэžџQэžџ#и|џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЦlџМgџ+д€эQэžџJъ™џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџОhџМgџWziЭfkiџfkiџfkiџfkiџfkiЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџhmkЈhmkџhmkџhmkџhmkџbeˆж__ѓџYYѓџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ==щџ@@шџPPђя__ѓџGGёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ..эџ@@шџ@@шџQэžџQэžџ#и|џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЧmџОhџ+е€эQэžџJъ™џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџРiџОhџW{iЭfkiџfkiџfkiџfkiџfkiЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџhmkЈhmkџhmkџhmkџhmkџbfˆжccѓџ]]ѓџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџAAыџDDъџRRђяbbѓџIIёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ//юџDDъџDDъџQэžџQэžџ#и|џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџШnџРjџ+жэQэžџJъ™џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџТkџРjџX|kЭgljџgljџgljџgljџgljЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџhmkЈhmkџhmkџhmkџhmkџbfˆжccѓџ^^ѓџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџDDьџHHыџSSђяccѓџJJёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ00юџHHьџGGыџQэžџQэžџ#и|џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЩnџТkџ+жэQэžџJъ™џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџУlџТkџX|kЭgljџgljџgljџgljџgljЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџhmkЈhmkџhmkџhmkџhmkџbfˆжccѓџ^^ѓџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџHHэџKKэџSSђяccѓџJJёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ11яџLLэџKKэџQэžџQэžџ#и|џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЫoџХlџ,зэQэžџJъ™џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЦmџФlџY|lЭgljџgljџgljџgljџgljЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџhmkЈhmkџhmkџhmkџhmkџbfˆжccѓџ^^ѓџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџKKяџOOяџSSђяccѓџJJёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ22яџPPяџOOяџQэžџQэžџ#и|џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЬpџЧmџ,и‚эQэžџJъ™џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџШnџЧmџY~lЭhmkџhmkџhmkџhmkџhmkЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџhmkЈhmkџhmkџhmkџhmkџbfˆжccѓџ^^ѓџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџOOёџSSёџSSђяccѓџJJёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ22яџTTёџSSёџQэžџQэžџ#и|џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЬpџШnџ,и‚эQэžџJъ™џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЩnџШnџY~lЭhmkџhmkџhmkџhmkџhmkЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџhmkЈhmkџhmkџhmkџhmkџbfˆзccѓџbbѓџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџYYђџXXђџXXђџ]]ѓџ]]ѓџTTѓєccѓџ__ѓџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџZZђџYYђџXXђџXXђџYYѓџ^^ѓџ]]ѓџQэžџQэžџJъ™џHщ—џHщ—џHщ—џHщ—џHщ—џHщ—џHщ—џHщ—џHщ—џHщ—џHщ—џHщ—џHщ—џHщ—џFш—џEч•џEх”џCх“џBф‘џ@у‘џ@тџ>пŽџ=оŒџ6о‰їQэžџPэџHщ—џHщ—џHщ—џHщ—џHщ—џHщ—џHщ—џHщ—џHщ—џHщ—џHщ—џHщ—џHщ—џHщ—џGш—џFч–џEц•џDх“џCх’џAу‘џ@т‘џ?сџ>пџ=оŒџZ„oкhmkџhmkџhmkџhmkџhmkЈџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџhmk hmkџhmkџhmkџhmkџejxъccђџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџbbѓџTTѓѕccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџbbѓџaaѓџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџOьџNы›џMщšџKш™џJч—џHц–џGх•џ9п‹љQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџPьџOыœџMъ›џLщ™џKш˜џIц—џHх–џFф”џbynэhmkџhmkџhmkџhmkџhmk џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџhmkthmkџhmkџhmkџhmkџhmkџfkwыdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еcgŽуcgхdhˆжdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‡еdh‰иcgхdhу`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе_†sе_†sе\‰rоZŠrх\‡rм`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе`†sе_†sе_†rеZ‰rуaznђhmkџhmkџhmkџhmkџhmkџhmktџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџhmk-hmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmk-џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџhmkЊhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkЊџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџhmkhmkзhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkзhmkџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџhmkhmkЄhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkЄhmkџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџhmk)hmkrhmkŸhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkЈhmkŸhmkrhmk)џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџрџџџўќ?ќ?ќ?јјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјјќ?ќ?ќ?ўџџџрџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ(@€ џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџGGG=FHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXFHHXGGG=џџџџџџџџџџџџџџџџџџџџџGGG/EHGлEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGлGGG/џџџџџџџџџџџџџџџџџџEHGеEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGеџџџџџџџџџџџџџџџCHH9EHGџEHGџ9dhѓ–Єё’Ёы’Ёы’Ёы’Ёы’Ёы’Ёы’Ёы’Ёы’Ёы’Ёы’Ёы“Ёу›Њѕ’Ёы’Ёы’Ёы’Ёы’Ёы’Ёы’Ёы’Ёы’Ёы’Ёы’Ёы’Ÿщ€Z<јsR7эrQ6ыrQ6ыrQ6ыrQ6ыrQ6ыrQ6ыrQ6ыrQ6ыrQ6ыrQ6ыrQ6ыyV9эxU8ёrQ6ыrQ6ыrQ6ыrQ6ыrQ6ыrQ6ыrQ6ыrQ6ыrQ6ыrQ6ыqQ6ыRJCщEHGџEHGџCHH9џџџџџџџџџџџџFHHXEHGџEHGџ'ŸЌё ЬуџЧпџЧпџЧпџЧпџЧпџЧпџЧпџЧпџЧпџЧпџПжџОеђбшџЧпџЧпџЧпџЧпџЧпџЧпџЧпџЧпџЧпџЧпџХнџЖЬџЌn=џ˜^1џ–\0џ–\0џ–\0џ–\0џ–\0џ–\0џ–\0џ–\0џ–\0џ–\0џ‡S+џ›a4і c5џ–\0џ–\0џ–\0џ–\0џ–\0џ–\0џ–\0џ–\0џ–\0џ–\0џW-џpP7шEHGџEHGџFHHXџџџџџџџџџџџџFHHXFIHџFIHџ/ЃАыеэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЧпџОе№зьџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџбъџЖЬџАqAџЅf5џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џŠU,џc6ѓЉj9џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ–\0џpP6шEHGџEHGџFHHXџџџџџџџџџџџџHKHXGJIџGJIџ5ЅБызюџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЧпџОе№*кяџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџбъџЖЬџГuEџЅf5џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џŠU,џžd7ѓЋl;џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ–\0џpP6шEHGџEHGџFHHXџџџџџџџџџџџџHKKXGKJџGKIџ<ІГыйяџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЩсџСй№5нёџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџвъџЛбџЗyHџІg6џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џŽY0џЂg:ѓЌm=џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ˜^2џrR8шEHGџEHGџFHHXџџџџџџџџџџџџHKKXHKJџHKJџBЉДы"л№џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЫуџЦм№?рђџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџвыџПжџЛ|LџІg6џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ•`6џІk>ѓЎo>џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џœb5џxW<шEHGџEHGџFHHXџџџџџџџџџџџџHKKXILKџILKџHЋЖы(мёџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЭцџЩр№IуѕџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџгыџФмџО€PџІg7џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џœg=џЊoBѓЏp@џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ f9џ|[@шFIHџFIHџFHHXџџџџџџџџџџџџKNKXJMLџJMLџOЎИы.ођџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџашџЭф№SцїџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџгьџЩсџТ„TџЇh7џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЃmCџЎsFѓБrBџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄi<џ_EшGJIџGJIџHKHXџџџџџџџџџџџџKNNXJNMџJNMџVАИы3пєџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџвъџащ№]щњџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдьџЮцџХ‡XџЇh7џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЊsIџВxIѓВsCџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЈm@џ„dIшHKJџHKJџHKKXџџџџџџџџџџџџKNNXKOMџKNMџYАКы9сѕџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџеь№gьћџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџгьџЩ‹\џЇh8џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џБzOџЗ|NѓДuEџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЋqCџˆgMшHLKџHLJџHKKXџџџџџџџџџџџџKNNXLONџLONџZБЛы:сѕџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџжяџй№№gьћџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџиёџЬŽ_џЈi8џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џИ€VџЛRѓЖwGџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЏtGџŽlQшILKџILKџHKKXџџџџџџџџџџџџNQNXMPOџMPOџZБЛы:сѕџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџиёџлє№gьћџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџеюџніџЭ`џЈi8џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џП‡\џО‚UѓЖwGџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џГxJџ’pUшJMLџJMLџKNKXџџџџџџџџџџџџNQQXMQPџMQPџZВЛыZщљџ:сѕџ:сѕџ:сѕџ:сѕџ:сѕџ:сѕџ:сѕџ8сєџ6рєџ3пєџ1сїџ$тјѕsю§џ:сѕџ:сѕџ:сѕџ:сѕџ:сѕџ:сѕџ:сѕџ9сѕџ6рєџ4пєџ2рєџ/хњџЭ`џИzJџЖwGџЖwGџЖwGџЖwGџЖwGџЕwFџЕvFџДvFџДuEџГuDџЧ‹^џТ…WѕР‚RџЖwGџЖwGџЖwGџЖwGџЖwGџЖwGџЕwFџЕvFџДuEџГuEџОRџ˜tWяKNMџKNMџKNNXџџџџџџџџџџџџNQQXNRPџNRPџ4ЃЎу#бцђ'бц№'бц№'бц№'бц№'бц№'бц№'бц№'бц№'бц№'бц№$бцѕ*дшёjочєhпъ№hпъ№hпъ№hпъ№hпъ№hпъ№hпъ№hпъ№hпъ№hпъ№eпъёVхѕљЅj;ѕЅj>№Ѕj>№Ѕj>№Ѕj>№Ѕj>№Ѕj>№Ѕj>№Ѕj>№Ѕj>№Ѕi>№Єi=№ЇmAјYЁ^яOІ`ђXЂ^№XЂ^№XЂ^№XЂ^№XЂ^№XЂ^№XЂ^№XЂ^№X \№W \№[Ё^ѕgYщKONџKONџKNNXџџџџџџџџџџџџNTQXORQџORQџ(ЂЏі ЦмџМгџМгџМгџМгџМгџМгџМгџМгџМгџМгџКаџЕЦєЄg8џŠU,џŠU,џŠU,џŠU,џŠU,џŠU,џŠU,џŠU,џŠU,џŠU,џ‰U,џƒQ*џЋl<џW-џŠU,џŠU,џŠU,џŠU,џŠU,џŠU,џŠU,џŠU,џŠU,џŠU,џ…R*џ#СoљЗhџЋ^џЋ^џЋ^џЋ^џЋ^џЋ^џЋ^џЋ^џЋ^џЋ^џІ[џ)‰YшLPNџLONџKNNXџџџџџџџџџџџџQTQXPSRџPSRџ1ЅГыеьџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЧпџ ЗЩ№Ќn=џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЁc3џƒQ*џЏp?џЅf5џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џŠU,џ,ЩvђЯvџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџМgџ)‰YшMPOџMPOџNQNXџџџџџџџџџџџџQTTXPTSџPTSџ7ЈЕызэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЧпџ ЗЩ№Џp@џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЁc3џƒQ*џВtCџЅf5џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џŠU,џ/Ьzђ!бxџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџМgџ)‰YшNQPџNQPџNQQXџџџџџџџџџџџџQTTXQUSџQUSџ=ЊЖыиюџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџШрџ ЙЫ№БsCџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЁc3џ…S,џЖwGџІg6џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џŒW.џ4а}ђ#гzџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџОhџ)ŒZшNRQџNRQџNQQXџџџџџџџџџџџџQWTXRVTџRVTџCЋЗы!к№џдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЪтџ НЮ№ДvFџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЂd4џŽ[4џЙ{KџІg6џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ“^4џ9д‚ђ&ж}џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџРiџ*Ž\шOSQџOSQџNTQXџџџџџџџџџџџџTWTXSVUџSVUџKЏКы&лёџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЭхџ Рг№ИyHџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЃe5џ—d<џНOџІg6џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џšd:џ<и†ђ(зџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџТkџ*‘]шPTRџPSRџQTQXџџџџџџџџџџџџTWWXSWVџSWVџPАЛы,нђџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЯчџ Фз№К|LџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄf6џ lDџС‚SџЇh7џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЁjAџAмŠђ+йџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџФlџ+“_шQTSџQTSџQTTXџџџџџџџџџџџџTWWXTXWџTXVџWГМы2пѓџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџбщџ Шл№НOџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЅg6џЉtLџФ†VџЇh7џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЈrGџFпŽђ-мƒџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЦmџ+•`шQUTџQUTџQTTXџџџџџџџџџџџџTZWXUYWџUYWџ]ДОы7сєџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџгьџ Ьп№ПRџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЅg7џВ}TџШŠZџЇh7џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЏxMџIсђ0о†џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџШnџ,˜bшRVTџRVTџQWTXџџџџџџџџџџџџWZWXVZXџVYXџ]ДОы:сѕџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџеюџ Яу№У„UџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џІh8џЛ…\џЫ^џЈi8џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЖ~SџIсђ0о†џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЪoџ,›cшSWUџSWUџTWTXџџџџџџџџџџџџWZZXVZYџVZYџ]ЕОы:сѕџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџиёџ гч№Ф†WџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЇi9џФdџЭ`џЈi8џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џН…ZџIс‘ђ0о†џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЬpџ-eшTWVџTWVџTWWXџџџџџџџџџџџџWZZXW[ZџW[Zџ]ЕОы@уіџ зяџ зяџ зяџ зяџ зяџ зяџ зяџ зяџ жюџ жюџ лєџиьёХ‡WџЈi8џЈi8џЈi8џЈi8џЈi8џЈi8џЈi8џЇi8џЇh8џЇh7џЋm<џЫ”jџЭ`џЋl<џЈi8џЈi8џЈi8џЈi8џЈi8џЈi8џЇi8џЇh8џЇh7џЇh7џФ‹_џIт‘ѓ3пˆџеwџеwџеwџеwџеwџеwџдwџдvџдvџдvџаtџ/ hщTXWџTXWџTWWXџџџџџџџџџџџџW]ZXX\ZџX\Zџ_ЕОщƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџƒђџџ~№ўџxя§џUчїњЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЬŽ_џЫ^џЪŒ]џЩ‹[џШ‰ZџЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЭ`џЫ^џЪŒ]џЩ‹\џШŠ[џMрѕQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџNъ›џKш˜џHц–џA­wіUYXџUYWџTZWXџџџџџџџџџџџџZ]ZXY][џY][џ)ЅГјТиџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЖЬџЎдѕшџвџвџвџвџвџвџвџвџвџвџвџвџюџеџвџвџвџвџвџвџвџвџвџвџвџНpћАcџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ Xџ,\шVZXџVZXџWZWXџџџџџџџџџџџџZ]]XY]\џY]\џ1ЉЖэ вщџащџащџащџащџащџащџащџащџащџащџХмџБеё%%юџ&&ыџ&&ыџ&&ыџ&&ыџ&&ыџ&&ыџ&&ыџ&&ыџ&&ыџ&&ыџ$$щџвџ&&яџ&&ыџ&&ыџ&&ыџ&&ыџ&&ыџ&&ыџ&&ыџ&&ыџ&&ыџ&&ыџ&&ыџзџ%У|єЫsџЬpџЬpџЬpџЬpџЬpџЬpџЬpџЬpџЬpџЬpџЙeџ-\шW[YџW[YџWZZXџџџџџџџџџџџџZ]]XZ^]џZ^]џ8ЋИыжэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЧпџГе№++яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ''ьџвџ--яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџиџ(Ш‚ђ аwџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџМgџ-]шW[ZџW[ZџWZZXџџџџџџџџџџџџZ`]X[_]џ[_]џ?ЎКыиюџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџШпџДж№22№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ''ьџгџ44№џ**яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџйџ,Ь…ђ"вzџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџОhџ-_шX\[џX\[џW]ZXџџџџџџџџџџџџ]`]X\`^џ[`^џDАЛыйяџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЪтџИк№77№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ((эџзџ;;№џ++яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџмџ2а‰ђ%е|џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџРiџ-‘_шY][џY][џZ]ZXџџџџџџџџџџџџ]``X\a_џ\a_џLВНы$лёџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЬфџЛо№==ёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ((эџ""кџCCёџ++яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ$$пџ5гђ(з~џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџСjџ.”aшZ^\џY^\џZ]]Xџџџџџџџџџџџџ]``X]a`џ]a`џRГОы*нђџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЮцџПс№BBёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))эџ**оџJJёџ,,яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ**тџ:зђ*й€џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџФkџ.—bшZ^]џZ^]џZ]]Xџџџџџџџџџџџџ]c`X^b`џ^b`џYЕРы0оѓџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџащџТц№HHёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ**юџ11сџQQђџ--яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ00фџ>л•ђ,лƒџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџХlџ/™dш[_^џ[_^џZ`]Xџџџџџџџџџџџџ`c`X_caџ^caџ^ИСы6рєџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџвыџЧъ№NNёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ++юџ99хџXXђџ--яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ66чџB/н…џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЧmџ0›eш\`^џ\`^џ]`]Xџџџџџџџџџџџџ`ecX_dbџ_dbџaИТы:сѕџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџЪэ№SSђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ++юџAAшџ``ѓџ..яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ<<ъџBо™ђ0о†џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЩoџ0gш]a_џ\a_џ]``Xџџџџџџџџџџџџ`ecX`ecџ`ecџaИТы:сѕџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџз№џЮё№VVђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ,,яџHHьџccѓџ..яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџBBэџBо™ђ0о†џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЫoџ0 hш]b`џ]b`џ]c`Xџџџџџџџџџџџџ`eeXaedџaecџbИТы:сѕџдэџдэџдэџдэџдэџдэџдэџдэџдэџдэџйђџвѕ№VVђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ--яџPPяџccѓџ..яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџHHяџCп™ђ0о†џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЭqџ0Ѓjш^baџ^baџ]c`XџџџџџџџџџџџџceeXafdџafdџbКТыqю§џcыћџcыћџcыћџcыћџcыћџcыћџcыћџcыћџ`ъњџ[шљџWщњџ>рљј``ѓџUUђџUUђџUUђџUUђџUUђџUUђџUUђџUUђџUUђџTTђџTTђџ]]ѓџccѓџVVђџUUђџUUђџUUђџUUђџUUђџUUђџUUђџUUђџUUђџSSђџZZѓџDоšѕIщ˜џBц“џBц“џBц“џBц“џBц“џBц“џBц“џAх’џ?уџ<сŽџ:п‹џ?­vѓ_caџ_caџ`c`XџџџџџџџџџџџџcheXbgeџbgeџCmЛы:Šэѕ9Œыё9Œыё9Œыё9Œыё9Œыё9Œыё9Œыё9Œыё9Œыё9Œыё7™юѓ.Œ№эхјлёлёлёлёлёлёлёлёлёлёлёлє7‚Ењ5xБђ4xВё4xВё4xВё4xВё4xВё4xВё4xВё4xВё4xВё4xВё>pУѕНqђЕfѕАaёАaёАaёАaёАaёАaёАaёАaёАaёАaёАbѓ4˜fт_dbџ_dbџ`ecXџџџџџџџџџџџџcheXchfџchfџ:<Кё чџтџтџтџтџтџтџтџтџтџтџлџфђ""ыџтџтџтџтџтџтџтџтџтџтџсџвџ'ЪwџНhџМgџМgџМgџМgџМgџМgџМgџМgџМgџМgџІ[џ"ЦsіСmџМgџМgџМgџМgџМgџМgџМgџМgџМgџМgџА`џ0‘aш`ecџ`ecџ`ecXџџџџџџџџџџџџehhXdhgџdhgџ@BМы))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџтџ!!ц№**яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ''ьџвџ-Ю|џбtџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЋ^џ'ЯyђаwџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџМgџ0‘aшafdџafdџ`eeXџџџџџџџџџџџџehhXdigџdigџCEНы--яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџтџ##ц№//яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ''ьџвџ2г‚џвtџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЌ_џ+г~ђ!вyџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџНhџ0’bшbfeџbfdџceeXџџџџџџџџџџџџekhXejhџejhџHIОы00яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ""фџ((ч№55№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ''эџеџ8и‡џвuџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЏaџ/ж‚ђ$д{џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџПiџ0”bшbgeџbgeџcheXџџџџџџџџџџџџekhXfkiџfkiџKMОы33яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ%%хџ..ш№;;№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ((эџйџ>нŒџгuџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџГcџ4й†ђ'ж}џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџСjџ1—dшchfџchfџcheXџџџџџџџџџџџџhkkXgljџgljџOQОы66№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))чџ33щ№AAёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))эџ''мџCт‘џгvџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЗdџ8н‰ђ)и€џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџУkџ3šfшdigџdigџehhXџџџџџџџџџџџџhkkXgljџgljџQTПы99№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ,,шџ88ы№FFёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ**юџ//рџIц—џдvџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџКfџ<рђ,к‚џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџХlџ3œgшeihџeihџehhXџџџџџџџџџџџџhnkXhmkџhmkџWXПы<<№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ//ъџ==ь№LLёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ**юџ66фџOыœџдwџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџОhџ@х‘ђ.м„џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЧmџ3žhшejhџejhџekhXџџџџџџџџџџџџhnkXhmkџhmkџY[Ры??№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ33ыџBBэ№RRђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ++юџ>>чџQэžџеwџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџТjџ@х’ђ0о†џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЩnџ4 jшfkiџfkiџekhXџџџџџџџџџџџџhnkXhmkџhmkџ[]РыBBёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ66эџFFю№VVђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ,,яџFFыџQэžџеwџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџХlџ@ц’ђ0о†џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЫoџ4ЄkшgljџgljџhkkXџџџџџџџџџџџџhnkXhmkџhmkџ[]РыBBёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ99яџII№№VVђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ--яџMMюџQэžџеwџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЩnџAц’ђ0о†џвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџЭpџ5ЅmшhmkџhmkџhnkXџџџџџџџџџџџџhnkXhmkџhmkџ[]РыQQђџBBёџBBёџBBёџBBёџBBёџBBёџBBёџBBёџBBёџAAёџKKёџNN№ѕ\\ѓџBBёџBBёџBBёџBBёџBBёџBBёџBBёџBBёџBBёџAAёџCCёџXXђџQэžџ3пˆџ0о†џ0о†џ0о†џ0о†џ0о†џ0о†џ/н…џ.м„џ,лƒџ+кџ)е~џAц’є>х‘џ0о†џ0о†џ0о†џ0о†џ0о†џ0о†џ/н…џ.м„џ-лƒџ,к‚џ*зџ<ЋtяhmkџhmkџhnkXџџџџџџџџџџџџgkk9hmkџhmkџchƒъ^`Пъ^_Ръ^_Ръ^_Ръ^_Ръ^_Ръ^_Ръ^_Ръ^_Ръ^_Ръ^_Ръ_`Сё[\Ры^_Ръ^_Ръ^_Ръ^_Ръ^_Ръ^_Ръ^_Ръ^_Ръ^_Ръ^_Ръ^_Ръ]_РыacТјRЙ…ъRЙ…ъRЙ…ъRЙ…ъRЙ…ъRЙ…ъRЙ…ъRЙ…ъRЙ…ъRЙ…ъQЙ„ъNИƒъKЗ€ѕLЖуRЙ…ъRЙ…ъRЙ…ъRЙ…ъRЙ…ъRЙ…ъRЙ…ъRЙ…ъRЙ…ъQЙ…ъPИ„ъMЕё\ƒpѓhmkџhmkџgkk9џџџџџџџџџџџџџџџhmkеhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkеџџџџџџџџџџџџџџџџџџgmm/hmkлhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkлgmm/џџџџџџџџџџџџџџџџџџџџџimm=hnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXhnkXimm=џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ№рррррррррррррррррррррррррррррррррррррррррррррррррррррррр№џџџџџџџџџџџџџџџџџџџџџџџџ(0` џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџEEEDGGЏEHGћEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGћDGGЏEEEџџџџџџџџџџџџEHHВEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHGџEHHВџџџџџџџџџџџџEHGћEHGџНаџЛбџЛбџЛбџЛбџЛбџЛбџЛбџЛбџЖЭџЮфџЛбџЛбџЛбџЛбџЛбџЛбџЛбџЛбџЖЬџЅg9џˆT,џˆT,џˆT,џˆT,џˆT,џˆT,џˆT,џˆT,џ„Q*џЄg8џˆT,џˆT,џˆT,џˆT,џˆT,џˆT,џˆT,џˆT,џ|P.џEHGџEHGћџџџџџџџџџџџџFIHџFIHџ!зьџдэџдэџдэџдэџдэџдэџдэџдэџЛвќ!зьџдэџдэџдэџдэџдэџдэџдэџдэџКб§­n>џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џˆT+§­n>џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џˆT+§EHGџEHGџџџџџџџџџџџџџGJIџGJIџ0л№џдэџдэџдэџдэџдэџдэџдэџдэџМвќ0л№џдэџдэџдэџдэџдэџдэџдэџдэџЛв§ВsBџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џˆT+§БrBџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џˆT+§EHGџEHGџџџџџџџџџџџџџHKJџHKJџ?рђџдэџдэџдэџдэџдэџдэџдэџдэџСйќ?рђџдэџдэџдэџдэџдэџдэџдэџдэџСи§ЖwGџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ\3§ЕvFџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ\3§EHGџEHGџџџџџџџџџџџџџILKџILKџNфѕџдэџдэџдэџдэџдэџдэџдэџдэџЧпќNфѕџдэџдэџдэџдэџдэџдэџдэџдэџЧо§Й{KџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џše;§ИzJџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џše;§FIHџFIHџџџџџџџџџџџџџJMLџJMLџ\шјџдэџдэџдэџдэџдэџдэџдэџдэџЬфќ\шјџдэџдэџдэџдэџдэџдэџдэџдэџЬф§НOџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄnE§М~NџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄnE§GJIџGJIџџџџџџџџџџџџџKNMџKNMџlэћџдэџдэџдэџдэџдэџдэџдэџдэџбъќlэћџдэџдэџдэџдэџдэџдэџдэџдэџвщ§ТƒSџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЎyO§С‚RџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЎxO§HKJџHKJџџџџџџџџџџџџџLONџLONџoэќџдэџдэџдэџдэџдэџдэџдэџдэџз№ќoэќџдэџдэџдэџдэџдэџдэџдэџдэџзя§ЦˆXџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џИ‚W§Х†WџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џИ‚W§ILKџILKџџџџџџџџџџџџџMPOџMPOџoэќџдэџдэџдэџдэџдэџдэџдэџдэџніќoэќџдэџдэџдэџдэџдэџдэџдэџдэџнѕ§Ч‰YџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џУ‹a§Х‡XџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џУ‹a§JMLџJMLџџџџџџџџџџџџџNQPџNQPџёўџlэќќlэќќlэќќlэќќlэќќlэќќkэќќeьњќ_ыћўёўџlэќќlэќќlэќќlэќќlэќќlэќќkэќќeьњќ_ыќўЬŽ_џЦˆXќЦˆXќЦˆXќЦˆXќЦˆXќХ‡XќУ†VќТ…TќШ‹\ўЫ_џЦˆXќЦˆXќЦˆXќЦˆXќЦˆXќХ‡XќУ†VќТ…TќШ‹\ўKNMџKNMџџџџџџџџџџџџџORQџORQџЮфџЛбџЛбџЛбџЛбџЛбџЛбџЛбџЛбџЖЭџЅg9џˆT,џˆT,џˆT,џˆT,џˆT,џˆT,џˆT,џˆT,џ„Q*џЅg9џˆT,џˆT,џˆT,џˆT,џˆT,џˆT,џˆT,џˆT,џ„Q*џ СpџЈ\џЈ\џЈ\џЈ\џЈ\џЈ\џЈ\џЈ\џЁXџLONџLONџџџџџџџџџџџџџPSRџPSRџ!зьџдэџдэџдэџдэџдэџдэџдэџдэџЛвќ­n>џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џˆT+§­n>џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џˆT+§'ЮzџвsџвsџвsџвsџвsџвsџвsџвsџЇ\§MPOџMPOџџџџџџџџџџџџџQTSџQTSџ0л№џдэџдэџдэџдэџдэџдэџдэџдэџМвќВsBџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џˆT+§ВsBџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џˆT+§.дџвsџвsџвsџвsџвsџвsџвsџвsџЉ]§NRPџNQPџџџџџџџџџџџџџRUTџRUTџ?рђџдэџдэџдэџдэџдэџдэџдэџдэџСйќЖwGџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ\3§ЖwGџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ\3§3й…џвsџвsџвsџвsџвsџвsџвsџвsџЎ`§OSQџORQџџџџџџџџџџџџџSWUџSVUџNфѕџдэџдэџдэџдэџдэџдэџдэџдэџЧпќЙ{KџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џše;§Й{KџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џše;§:н‹џвsџвsџвsџвsџвsџвsџвsџвsџДc§PTRџPTRџџџџџџџџџџџџџTXVџTXVџ\шјџдэџдэџдэџдэџдэџдэџдэџдэџЬфќНOџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЃnE§НOџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЃnE§@уџвsџвsџвsџвsџвsџвsџвsџвsџЙe§QUSџQUSџџџџџџџџџџџџџUYWџUYWџlэћџдэџдэџдэџдэџдэџдэџдэџдэџбъќТƒSџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЎxO§ТƒSџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЎxO§Fш–џвsџвsџвsџвsџвsџвsџвsџвsџОi§RVTџRVTџџџџџџџџџџџџџVZXџVZXџoэќџдэџдэџдэџдэџдэџдэџдэџдэџз№ќЦˆXџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џИ‚W§ЦˆXџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џИ‚W§Fш–џвsџвsџвsџвsџвsџвsџвsџвsџУk§SWUџSWUџџџџџџџџџџџџџW[YџW[YџoэќџдэџдэџдэџдэџдэџдэџдэџдэџніќЧ‰YџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џУ‹a§Ч‰YџЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џУ‹a§Fш–џвsџвsџвsџвsџвsџвsџвsџвsџШn§TXVџTXVџџџџџџџџџџџџџX\ZџX\Zџёўџlэќќlэќќlэќќlэќќlэќќlэќќkэќќeьњќ_ыћўЬŽ_џЦˆXќЦˆXќЦˆXќЦˆXќЦˆXќХ‡XќУ†VќТ…TќШ‹\ўЬŽ_џЦˆXќЦˆXќЦˆXќЦˆXќЦˆXќХ‡XќУ†VќТ…TќШ‹\ўOьœџGщ—ќGщ—ќGщ—ќGщ—ќGщ—ќGщ—ќDц”ќ@ф‘ќ=пŽўUYWџUYWџџџџџџџџџџџџџY][џY][џЮфџЛбџЛбџЛбџЛбџЛбџЛбџЛбџЛбџЖЭџъџжџжџжџжџжџжџжџжџвџъџжџжџжџжџжџжџжџжџвџ СpџЈ\џЈ\џЈ\џЈ\џЈ\џЈ\џЈ\џЈ\џЁXџVZXџVZXџџџџџџџџџџџџџZ^\џZ^\џ!зьџдэџдэџдэџдэџдэџдэџдэџдэџЛвќ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџж§))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџж§'ЮzџвsџвsџвsџвsџвsџвsџвsџвsџЇ\§W[YџW[Yџџџџџџџџџџџџџ[_]џ[_]џ0л№џдэџдэџдэџдэџдэџдэџдэџдэџМвќ11№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџз§11№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџз§.дџвsџвsџвsџвsџвsџвsџвsџвsџЉ]§X\ZџX\Zџџџџџџџџџџџџџ\`^џ\`^џ?пђџдэџдэџдэџдэџдэџдэџдэџдэџСйќ99№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџл§99№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџл§3й…џвsџвsџвsџвsџвsџвsџвsџвsџЎ`§Y][џY][џџџџџџџџџџџџџ]a_џ]a_џNфѕџдэџдэџдэџдэџдэџдэџдэџдэџЧпќBBёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ&&п§BBёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ&&п§:н‹џвsџвsџвsџвsџвsџвsџвsџвsџДc§Z^]џZ^\џџџџџџџџџџџџџ^b`џ^b`џ\шјџдэџдэџдэџдэџдэџдэџдэџдэџЬфќJJђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ//у§JJђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ//у§@уџвsџвsџвsџвsџвsџвsџвsџвsџЙe§[_^џ[_]џџџџџџџџџџџџџ_caџ_caџlэћџдэџдэџдэџдэџдэџдэџдэџдэџбъќRRђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ77ц§RRђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ77ц§Fш–џвsџвsџвsџвsџвsџвsџвsџвsџНi§\`_џ\`^џџџџџџџџџџџџџ`dbџ`dbџoэќџдэџдэџдэџдэџдэџдэџдэџдэџз№ќZZђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџAAы§ZZђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџAAы§Fш–џвsџвsџвsџвsџвsџвsџвsџвsџУk§]a`џ]a_џџџџџџџџџџџџџaecџaecџoэќџдэџдэџдэџдэџдэџдэџдэџдэџніќZZђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџJJя§ZZђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџJJя§Fш–џвsџвsџвsџвsџвsџвsџвsџвsџШn§^baџ^baџџџџџџџџџџџџџbfdџbfdџёўџlэќќlэќќlэќќlэќќlэќќlэќќkэќќeьњќ_ыћўaaѓџYYѓќYYѓќYYѓќYYѓќYYѓќYYѓќYYѓќYYѓќ]]ѓўaaѓџYYѓќYYѓќYYѓќYYѓќYYѓќYYѓќYYѓќYYѓќ]]ѓўOьœџGщ—ќGщ—ќGщ—ќGщ—ќGщ—ќGщ—ќDц”ќ@ф‘ќ=пŽў_cbџ_cbџџџџџџџџџџџџџcgeџcgeџъџжџжџжџжџжџжџжџжџвџъџжџжџжџжџжџжџжџжџвџ ТpџЈ\џЈ\џЈ\џЈ\џЈ\џЈ\џЈ\џЈ\џЁXџ СpџЈ\џЈ\џЈ\џЈ\џЈ\џЈ\џЈ\џЈ\џЁXџ`dcџ`dcџџџџџџџџџџџџџdhfџdhfџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџиќ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџж§(ЮzџвsџвsџвsџвsџвsџвsџвsџвsџЇ\§'ЮzџвsџвsџвsџвsџвsџвsџвsџвsџЇ\§aedџaedџџџџџџџџџџџџџeigџeigџ11№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџйќ11№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџз§/д€џвsџвsџвsџвsџвsџвsџвsџвsџЉ]§.дџвsџвsџвsџвsџвsџвsџвsџвsџЉ]§bfeџbfeџџџџџџџџџџџџџfjiџfjhџ99№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџмќ99№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџл§4й†џвsџвsџвsџвsџвsџвsџвsџвsџЎ`§3й…џвsџвsџвsџвsџвsџвsџвsџвsџЎ`§chfџcgfџџџџџџџџџџџџџgkjџgkiџBBёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ&&рќBBёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ&&п§;оŒџвsџвsџвsџвsџвsџвsџвsџвsџДc§:н‹џвsџвsџвsџвsџвsџвsџвsџвsџДc§digџdigџџџџџџџџџџџџџhmkџgljџJJђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ//фќJJђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ//у§Aф‘џвsџвsџвsџвsџвsџвsџвsџвsџИe§@уџвsџвsџвsџвsџвsџвsџвsџвsџИe§ejhџejhџџџџџџџџџџџџџhmkџhmkџRRђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ77чќRRђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ77ц§Hщ—џвsџвsџвsџвsџвsџвsџвsџвsџНi§Fш–џвsџвsџвsџвsџвsџвsџвsџвsџНi§fkiџfkiџџџџџџџџџџџџџhmkџhmkџZZђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ@@ыќZZђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџAAы§Hщ—џвsџвsџвsџвsџвsџвsџвsџвsџУk§Fш–џвsџвsџвsџвsџвsџвsџвsџвsџУk§gljџgljџџџџџџџџџџџџџhmkџhmkџZZђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџIIяќZZђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџJJя§Hщ—џвsџвsџвsџвsџвsџвsџвsџвsџШn§Fш–џвsџвsџвsџвsџвsџвsџвsџвsџШn§hmkџhmkџџџџџџџџџџџџџhmkћhmkџbcтџXXђўXXђўXXђўXXђўXXђўXXђўXXђўXXђў\\ѓўaaѓџXXђўXXђўXXђўXXђўXXђўXXђўXXђўXXђў]]ѓўOьџFш—ўFш—ўFш—ўFш—ўFш—ўFш—ўCх”ў?у‘ў=пŽўOьџFш—ўFш—ўFш—ўFш—ўFш—ўFш—ўCх”ў?у‘ўBбŠўhmkџhmkћџџџџџџџџџџџџimkВhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџimkВџџџџџџџџџџџџlllgmjЏhmkћhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkџhmkћgmjЏlllџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџрРРРРРРРРРРРРРРРРРРРРРРРРРРРРРРРРРРРРРРРРРРрџџџџџџџџџџџџ( @ VZZ>W[ZкW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZкVZZ>W[ZлW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZџW[ZлW[ZџW[ZџJьџџмѕџгьџЫуџУкџЛбџЖЬџJьџџмѕџгьџЫуџУкџЛбџЖЬџ‹U,џ„R*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џ‹U,џ„R*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џW[ZџW[ZџX\[џX\[џdяџџOщќџOщќџOщќџOщќџOщќџОдџdяџџOщќџOщќџOщќџOщќџOщќџОдџЉj9џЄe4џЄe4џЄe4џЄe4џЄe4џƒQ*џЉj9џЄe4џЄe4џЄe4џЄe4џЄe4џƒQ*џW[ZџW[ZџY]\џY]\џ}ђџџOщќџOщќџOщќџOщќџOщќџЩсџ}ђџџOщќџOщќџOщќџOщќџOщќџЩсџЗxHџЄe4џЄe4џЄe4џЄe4џЄe4џƒQ*џЗxHџЄe4џЄe4џЄe4џЄe4џЄe4џƒQ*џW[ZџW[ZџZ^]џZ^]џ—ѕџџOщќџOщќџOщќџOщќџOщќџдэџ—ѕџџOщќџOщќџOщќџOщќџOщќџдэџЬŽ^џЄe4џЄe4џЄe4џЄe4џЄe4џ…R+џЬŽ^џЄe4џЄe4џЄe4џЄe4џЄe4џ…R+џX\[џX\[џ[_^џ[_^џЄіџџOщќџOщќџOщќџOщќџOщќџрњџЄіџџOщќџOщќџOщќџOщќџOщќџрњџЬŽ^џЄe4џЄe4џЄe4џЄe4џЄe4џX.џЬŽ^џЄe4џЄe4џЄe4џЄe4џЄe4џX.џY]\џY]\џ\a_џ\`_џЄіџџOщќџOщќџOщќџOщќџOщќџрњџЄіџџOщќџOщќџOщќџOщќџOщќџрњџЬŽ^џЄe4џЄe4џЄe4џЄe4џЄe4џ™^1џЬŽ^џЄe4џЄe4џЄe4џЄe4џЄe4џ™^1џZ^]џZ^]џ]b`џ]a`џЄіџџЄіџџЄіџџЄіџџєџџ{ђџџeяџџЄіџџЄіџџЄіџџЄіџџєџџ{ђџџeяџџЬŽ^џЬŽ^џЬŽ^џЬŽ^џЬŽ^џЬŽ^џЬŽ^џЬŽ^џЬŽ^џЬŽ^џЬŽ^џЬŽ^џЬŽ^џЬŽ^џ[_^џ[_^џ^caџ^baџJьџџмѕџгьџЫуџУкџЛбџЖЬџ‹U,џ„R*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џ‹U,џ„R*џƒQ*џƒQ*џƒQ*џƒQ*џƒQ*џ)ЬyџФlџМgџДcџЌ_џЄZџ Xџ\a_џ\`_џ_dbџ_dbџdяџџOщќџOщќџOщќџOщќџOщќџОдџЉj9џЄe4џЄe4џЄe4џЄe4џЄe4џƒQ*џЉj9џЄe4џЄe4џЄe4џЄe4џЄe4џƒQ*џ5е„џвsџвsџвsџвsџвsџЇ\џ]b`џ]a`џ`ecџ`ecџ}ђџџOщќџOщќџOщќџOщќџOщќџЩсџЗxHџЄe4џЄe4џЄe4џЄe4џЄe4џƒQ*џЗxHџЄe4џЄe4џЄe4џЄe4џЄe4џƒQ*џ@пŽџвsџвsџвsџвsџвsџВbџ^caџ^caџafdџafdџ—ѕџџOщќџOщќџOщќџOщќџOщќџдэџЬŽ^џЄe4џЄe4џЄe4џЄe4џЄe4џ…R+џЬŽ^џЄe4џЄe4џЄe4џЄe4џЄe4џ…R+џKш™џвsџвsџвsџвsџвsџНhџ_dbџ_dbџbgeџbgeџЄіџџOщќџOщќџOщќџOщќџOщќџрњџЬŽ^џЄe4џЄe4џЄe4џЄe4џЄe4џX.џЬŽ^џЄe4џЄe4џЄe4џЄe4џЄe4џX.џQэžџвsџвsџвsџвsџвsџШnџ`ecџ`ecџchfџchfџЄіџџOщќџOщќџOщќџOщќџOщќџрњџЬŽ^џЄe4џЄe4џЄe4џЄe4џЄe4џ™^1џЬŽ^џЄe4џЄe4џЄe4џЄe4џЄe4џ™^1џQэžџвsџвsџвsџвsџвsџШnџafdџafdџdigџdigџЄіџџЄіџџЄіџџЄіџџєџџ{ђџџeяџџЬŽ^џЬŽ^џЬŽ^џЬŽ^џЬŽ^џЬŽ^џЬŽ^џЬŽ^џЬŽ^џЬŽ^џЬŽ^џЬŽ^џЬŽ^џЬŽ^џQэžџQэžџQэžџQэžџHх–џ?оџ5ж…џbgeџbgeџejhџejhџJьџџмѕџгьџЫуџУкџЛбџЖЬџbbѓџЩџЩџЩџЩџЩџЩџbbѓџЩџЩџЩџЩџЩџЩџ)ЬyџФlџМgџДcџЌ_џЄZџ XџchfџchfџfkiџfkiџdяџџOщќџOщќџOщќџOщќџOщќџОдџllєџ))яџ))яџ))яџ))яџ))яџЩџllєџ))яџ))яџ))яџ))яџ))яџЩџ5е„џвsџвsџвsџвsџвsџЇ\џdigџdigџgljџgljџ}ђџџOщќџOщќџOщќџOщќџOщќџЩсџuuѕџ))яџ))яџ))яџ))яџ))яџЩџuuѕџ))яџ))яџ))яџ))яџ))яџЩџ@пŽџвsџвsџвsџвsџвsџВbџejhџejhџhmkџhmkџ—ѕџџOщќџOщќџOщќџOщќџOщќџдэџ{{ѕџ))яџ))яџ))яџ))яџ))яџЩџ{{ѕџ))яџ))яџ))яџ))яџ))яџЩџKш™џвsџвsџвsџвsџвsџНhџfkiџfkiџinlџinlџЄіџџOщќџOщќџOщќџOщќџOщќџрњџ{{ѕџ))яџ))яџ))яџ))яџ))яџвџ{{ѕџ))яџ))яџ))яџ))яџ))яџвџQэžџвsџвsџвsџвsџвsџШnџgljџgljџjomџjomџЄіџџOщќџOщќџOщќџOщќџOщќџрњџ{{ѕџ))яџ))яџ))яџ))яџ))яџ--нџ{{ѕџ))яџ))яџ))яџ))яџ))яџ--нџQэžџвsџвsџвsџвsџвsџШnџhmkџhmkџkpnџkpnџЄіџџЄіџџЄіџџЄіџџєџџ{ђџџeяџџ{{ѕџ{{ѕџ{{ѕџ{{ѕџ{{ѕџ{{ѕџuuѕџ{{ѕџ{{ѕџ{{ѕџ{{ѕџ{{ѕџ{{ѕџuuѕџQэžџQэžџQэžџQэžџHх–џ?оџ5ж…џinlџinlџlqoџlqoџbbѓџЩџЩџЩџЩџЩџЩџbbѓџЩџЩџЩџЩџЩџЩџ)ЬyџФlџМgџДcџЌ_џЄZџ Xџ)ЬyџФlџМgџДcџЌ_џЄZџ Xџjomџjomџmrpџmrpџllєџ))яџ))яџ))яџ))яџ))яџЩџllєџ))яџ))яџ))яџ))яџ))яџЩџ5е„џвsџвsџвsџвsџвsџЇ\џ5е„џвsџвsџвsџвsџвsџЇ\џkpnџkpnџnsqџnsqџuuѕџ))яџ))яџ))яџ))яџ))яџЩџuuѕџ))яџ))яџ))яџ))яџ))яџЩџ@пŽџвsџвsџвsџвsџвsџВbџ@пŽџвsџвsџвsџвsџвsџВbџlqoџlqoџotrџotrџ{{ѕџ))яџ))яџ))яџ))яџ))яџЩџ{{ѕџ))яџ))яџ))яџ))яџ))яџЩџKш™џвsџвsџвsџвsџвsџНhџKш™џвsџвsџвsџвsџвsџНhџmrpџmrpџpusџpusџ{{ѕџ))яџ))яџ))яџ))яџ))яџвџ{{ѕџ))яџ))яџ))яџ))яџ))яџвџQэžџвsџвsџвsџвsџвsџШnџQэžџвsџвsџвsџвsџвsџШnџnsqџnsqџpusџpusџ{{ѕџ))яџ))яџ))яџ))яџ))яџ--нџ{{ѕџ))яџ))яџ))яџ))яџ))яџ--нџQэžџвsџвsџвsџвsџвsџШnџQэžџвsџвsџвsџвsџвsџШnџotrџotrџpusџpusџ{{ѕџ{{ѕџ{{ѕџ{{ѕџ{{ѕџ{{ѕџuuѕџ{{ѕџ{{ѕџ{{ѕџ{{ѕџ{{ѕџ{{ѕџuuѕџQэžџQэžџQэžџQэžџHх–џ?оџ5ж…џQэžџQэžџQэžџQэžџHх–џ?оџ5ж…џpusџpusџptsлpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџptsлoss>pusкpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusџpusкoss>€€(  ІЙџ Гџ Гџ ГџІЙџ Гџ Гџ ГџeDџ„R*џ„R*џ„R*џeDџ„R*џ„R*џ„R*џ0ГТџвш§ацў Эуў;н№џвш§ацў ЭуўТ—vџЄe4џЄe4џЄe4џТ—vџЄe4џЄe4џ„R*џ\ПЫџ9хљћ5уїћ1рѕќrэћџ9хљћ5уїћ1рѕќЧ ‚џЄe4џЄe4џЄe4џЧ ‚џЄe4џЄe4џ„R*џjУЮџƒђџџƒђџџёўџƒђџџƒђџџƒђџџёўџЧ ‚џЧ ‚џЧ ‚џХ~џЧ ‚џЧ ‚џЧ ‚џŸfџІЙџЦнџЦнџЦнџВ}TџЄe4џЄe4џЄe4џВ}TџЄe4џЄe4џЄe4џOн•џвsџвsџЊ]џ0ГТџвш§ацў ЭуўТ—vџЄe4џЄe4џЄe4џТ—vџЄe4џЄe4џЄe4џˆшИџвsџвsџЊ]џ\ПЫџ9хљћ5уїћ1рѕќЧ ‚џЄe4џЄe4џЄe4џЧ ‚џЄe4џЄe4џЄe4џˆшИџвsџвsџЊ]џjУЮџƒђџџƒђџџёўџЧ ‚џЧ ‚џЧ ‚џХ~џЧ ‚џЧ ‚џЧ ‚џХ~џˆшИџˆшИџˆшИџnЛ•џІЙџЦнџЦнџЦнџKKђџ))яџ))яџ))яџKKђџ))яџ))яџ))яџOн•џвsџвsџЊ]џ0ГТџвш§ацў Эуўooєџ))яџ))яџ))яџooєџ))яџ))яџ))яџˆшИџвsџвsџЊ]џ\ПЫџ9хљћ5уїћ1рѕќ{{ѕџ))яџ))яџ))яџ{{ѕџ))яџ))яџ))яџˆшИџвsџвsџЊ]џjУЮџƒђџџƒђџџёўџ{{ѕџ{{ѕџ{{ѕџwwѕџ{{ѕџ{{ѕџ{{ѕџwwѕџˆшИџˆшИџˆшИџnЛ•џ==Уџ))яџ))яџ))яџKKђџ))яџ))яџ))яџOн•џвsџвsџвsџOн•џвsџвsџЊ]џZZХџ))яџ))яџ))яџooєџ))яџ))яџ))яџˆшИџвsџвsџвsџˆшИџвsџвsџЊ]џccЦџ))яџ))яџ))яџ{{ѕџ))яџ))яџ))яџˆшИџвsџвsџвsџˆшИџвsџвsџЊ]џccЦџccЦџccЦџ``ЦџccЦџccЦџccЦџ``ЦџnЛ•џnЛ•џnЛ•џnЛ•џnЛ•џnЛ•џnЛ•џnЛ•џpentobi-7.2/src/pentobi/pentobi.ts000066400000000000000000000033541227240712600172710ustar00rootroot00000000000000 MainWindow %n move(s) %n move %n moves Blue wins with %n point(s). Blue wins with %n point. Blue wins with %n points. Green wins with %n point(s). Green wins with %n point. Green wins with %n points. Blue/Red wins with %n point(s). Blue/Red wins with %n point. Blue/Red wins with %n points. Yellow/Green wins with %n point(s). Yellow/Green wins with %n point. Yellow/Green wins with %n points. pentobi-7.2/src/pentobi/pentobi_de.ts000066400000000000000000001410211227240712600177330ustar00rootroot00000000000000 AnalyzeGameWidget Win Gewinn Loss Verlust Running game analysis... Spiel wird analysiert ... AnalyzeGameWindow Game Analysis Spielanalyse AnalyzeSpeedDialog Fast Schnell Normal Normal Slow Langsam Analysis speed: Analysegeschwindigkeit: ExportImage Export Image Grafik exportieren Image size: BildgrУЖУŸe: MainWindow About Pentobi Info УМber Pentobi Select Next &Color NУЄchste &Farbe selektieren S&etup Mode &Stellungsaufbau Could not read file '%1' Datei '%1' konnte nicht gelesen werden The file is not a valid Blokus SGF file. Die Datei ist keine gУМltige Blokus-SGF-Datei. Truncate this subtree? Diesen Teilbaum abschneiden? This position and all following moves and variations will be removed from the game tree. Diese Brettstellung und alle folgenden ZУМge und Varianten werden aus dem Spielbaum entfernt. Truncate Abschneiden Truncate children? Kindknoten abschneiden? All following moves and variations will be removed from the game tree. Alle folgenden ZУМge und Varianten werden aus dem Spielbaum entfernt. Truncate Children Kindknoten abschneiden Could not delete %1 %1 konnte nicht gelУЖscht werden Rated game Gewertetes Spiel Continuing unfinished rated game. Unbeendetes gewertetes Spiel wird fortgesetzt. You play %1 in this game. Sie spielen %1 in diesem Spiel. Copyright &copy; 2011&ndash;2014 Markus Enzenberger Copyright &copy; 2011&ndash;2014 Markus Enzenberger Analyze Game Spiel analysieren &About Inf&o &Analyze Game... Spiel &analysieren ... B&ackward &ZurУМck Go one move backward Einen Zug zurУМck gehen 10 Back&ward 10 z&urУМck Go ten moves backward Zehn ZУМge zurУМck gehen Back to &Main Variation ZurУМck zu Hau&ptvariante &Bad Schl&echt &Beginning &Anfang Go to beginning of game Zum Anfang des Spiels gehen Beginning of Bran&ch Anfang der Verz&weigung Clear Piece Spielstein lУЖschen &Computer Colors &Computer-Farben Set the colors played by the computer Die vom Computer gespielten Farben festlegen C&oordinates K&oordinaten &Delete All Variations Alle &Varianten lУЖschen &Doubtful &Zweifelhaft &End &Ende Go to end of moves Zum Ende der ZУМge gehen &ASCII Art &ASCII-Art I&mage &Grafik &Find Move Zug fin&den Flip Horizontally Horizontal umdrehen Flip Vertically Vertikal umdrehen &Forward &VorwУЄrts Go one move forward Einen Zug vorwУЄrts gehen 10 F&orward 10 v&orwУЄrts Go ten moves forward Zehn ZУМge vorwУЄrts gehen &Fullscreen Voll&bild Ga&me Info Spielinf&ormation St&op St&opp &Duo &Duo J&unior J&unior &Good &Gut Game analysis is only possible in the main variation. Spielanalyse ist nur in der Hauptvariante mУЖglich. Find Next &Comment NУЄchsten &Kommentar finden &Go to Move... &Gehe zu Zug ... &Contents I&nhalt I&nteresting I&nteressant &Keep Only Position Nur &Brettstellung behalten Keep Only &Subtree Nur &Teilbaum behalten Leave Fullscreen Vollbild verlassen M&ake Main Variation Zu &Hauptvariante machen Move Variation D&own Variante nach &unten schieben Move Variation &Up Variante nach &oben schieben &1 &1 &2 &2 &3 &3 &4 &4 &5 &5 &6 &6 &7 &7 &8 &8 &All &Alle &Last &Letzter &None move numbers &Keine Next Piece NУЄchster Spielstein &Next Variation &NУЄchste Variante Go to next variation Zur nУЄchsten Variante gehen New &Rated Game Neues ge&wertetes Spiel &New &Neu Start a new game Ein neues Spiel beginnen N&one move annotation &Keine &Open... У–ffn&en ... &Play &Spielen Play &Single Move &Einzelnen Zug spielen Previous Piece Vorheriger Spielstein &Previous Variation Vor&herige Variante Go to previous variation Zur vorherigen Variante gehen Rotate Anticlockwise Gegen den Uhrzeigersinn drehen Rotate Clockwise Im Uhrzeigersinn drehen &Quit &Beenden &Save &Speichern Save &As... Speichern &unter ... &Comment &Kommentar Your &Rating Ihre &Wertung &No Text &Kein Text Text &Beside Icons Text n&eben Symbolen Text Bel&ow Icons Text &unter Symbolen &Text Only &Nur Text &System Default &Systemvorgabe &Truncate &Abschneiden Truncate C&hildren &Kindknoten abschneiden Show &Variations &Varianten zeigen &Undo Move Zug rУМck&gУЄngig V&ery Bad Seh&r schlecht &Very Good &Sehr gut &Edit &Bearbeiten &Move Annotation &Zugkommentierung &View &Ansicht Toolbar T&ext Werkzeugleistent&ext &Move Numbers &Zugnummern &Computer &Computer &Level S&pielstufe &Tools &Extras &Help &Hilfe Delete all variations? Alle Varianten lУЖschen? All variations but the main variation will be removed from the game tree. Alle Varianten auУŸer der Hauptvariante werden aus dem Spielbaum entfernt. Delete Variations Varianten lУЖschen &Toolbar &Werkzeugleiste Text files (*.txt) Textdateien (*.txt) No comment found Kein Kommentar gefunden The computer is thinking... Der Computer denkt ... Blokus games (*.blksgf);;All files (*.*) Blokus-Partien (*.blksgf);;Alle Dateien (*.*) Move number: Zugnummer: Go to Move Gehe zu Zug Keep only position? Nur Brettstellung behalten? All previous and following moves and variations will be removed from the game tree. Alle vorhergehenden und nachfolgenden ZУМge und Varianten werden aus dem Spielbaum entfernt. Keep only subtree? Nur Teilbaum behalten? All previous moves and variations will be removed from the game tree. Alle vorhergehenden ZУМge und Varianten werden aus dem Spielbaum entfernt. Start new rated game? Neues gewertetes Spiel beginnen? In the next game, you will play %1 against Pentobi level&nbsp;%2. Im nУЄchsten Spiel spielen Sie %1 gegen Pentobi Spielstufe&nbsp;%2. &Start Game &Spiel beginnen Pentobi %1 (level %2) The first argument is the version of Pentobi Pentobi %1 (Stufe %2) Human Mensch Open У–ffnen The file could not be saved. Die Datei konnte nicht gespeichert werden. %1: %2 Error message if file cannot be saved. %1 is replaced by the file name, %2 by the error message of the operating system. %1: %2 Game saved: %1 Spiel gespeichert: %1 Untitled Game.blksgf Unbenanntes Spiel.blksgf Untitled Game %1.blksgf Unbenanntes Spiel %1.blksgf Save Speichern Pentobi Pentobi Version %1 Version %1 Pentobi is a computer opponent for the board game Blokus. Pentobi ist ein Computer-Gegner fУМr das Brettspiel Blokus. The file has been modified. Die Datei wurde geУЄndert. Do you want to save your changes? У„nderungen speichern? &Don't Save &Nicht speichern The current game is not finished. Das gegenwУЄrtige Spiel ist nicht beendet. Do you want to abort the game? MУЖchten Sie das Spiel verwerfen? &Abort Game Spiel &verwerfen &Classic (4 Players) &Klassisch (4 Spieler) Classic (&2 Players) Klassisch (&2 Spieler) &Trigon (4 Players) &Trigon (4 Spieler) Tri&gon (2 Players) Tri&gon (2 Spieler) Trigon (&3 Players) Trigon (&3 Spieler) &Game &Spiel Game &Variant Spiel&variante Open R&ecent &Zuletzt benutzte Dateien E&xport E&xportieren G&o &Gehe zu The end of the tree was reached. Das Ende des Spielbaums wurde erreicht. Continue the search from the start of the tree? Die Suche vom Beginn des Spielbaums fortsetzen? Continue From Start Suche vom Beginn fortsetzen Keep Only Position Nur Brettstellung behalten Keep Only Subtree Nur Teilbaum behalten The computer is thinking... (max. %1 seconds remaining) Der Computer denkt ... (max. %1 Sekunden verbleibend) The computer is thinking... (max. %1 minutes remaining) Der Computer denkt ... (max. %1 Minuten verbleibend) [*]%1 [*]%1 Make the computer continue to play Blue/Red Den Computer weiter Blau/Rot spielen lassen Make the computer play Blue/Red Den Computer Blau/Rot spielen lassen Make the computer continue to play Yellow/Green Den Computer weiter Gelb/GrУМn spielen lassen Make the computer play Yellow/Green Den Computer Gelb/GrУМn spielen lassen Make the computer continue to play Blue Den Computer weiter Blau spielen lassen Make the computer play Blue Den Computer Blau spielen lassen Make the computer continue to play Yellow Den Computer weiter Gelb spielen lassen Make the computer play Yellow Den Computer Gelb spielen lassen Make the computer continue to play Green Den Computer weiter GrУМn spielen lassen Make the computer play Green Den Computer GrУМn spielen lassen Make the computer continue to play Red Den Computer weiter Rot spielen lassen Make the computer play Red Den Computer Rot spielen lassen Setup mode cannot be used if moves have been played. Stellungsaufbau-Modus kann nicht benutzt werden, wenn ZУМge gespielt wurden. Setup mode Stellungsaufbau Blue wins with %n point(s). Blau gewinnt mit %n Punkt. Blau gewinnt mit %n Punkten. The game ends in a tie. Das Spiel endet in einem Unentschieden. Blue/Red wins with %n point(s). Blau/Rot gewinnt mit %n Punkt. Blau/Rot gewinnt mit %n Punkten. Yellow/Green wins with %n point(s). Gelb/GrУМn gewinnt mit %n Punkt. Gelb/GrУМn gewinnt mit %n Punkten. The game ends in a tie between all colors. Das Spiel endet in einem Unentschieden zwischen allen Farben. The game ends in a tie between Blue, Yellow and Red. Das Spiel endet in einem Unentschieden zwischen Blau, Gelb und Rot. The game ends in a tie between Blue, Yellow and Green. Das Spiel endet in einem Unentschieden zwischen Blau, Gelb und GrУМn. The game ends in a tie between Blue, Red and Green. Das Spiel endet in einem Unentschieden zwischen Blau, Rot und GrУМn. The game ends in a tie between Yellow, Red and Green. Das Spiel endet in einem Unentschieden zwischen Gelb, Rot und GrУМn. The game ends in a tie between Blue and Yellow. Das Spiel endet in einem Unentschieden zwischen Blau und Gelb. The game ends in a tie between Blue and Red. Das Spiel endet in einem Unentschieden zwischen Blau und Rot. The game ends in a tie between Blue and Green. Das Spiel endet in einem Unentschieden zwischen Blau und GrУМn. The game ends in a tie between Yellow and Red. Das Spiel endet in einem Unentschieden zwischen Gelb und Rot. The game ends in a tie between Yellow and Green. Das Spiel endet in einem Unentschieden zwischen Gelb und GrУМn. The game ends in a tie between Red and Green. Das Spiel endet in einem Unentschieden zwischen Rot und GrУМn. Blue wins. Blau gewinnt. Green wins with %n point(s). GrУМn gewinnt mit %n Punkt. GrУМn gewinnt mit %n Punkten. Yellow wins. Gelb gewinnt. Red wins. Rot gewinnt. Green wins. GrУМn gewinnt. Your rating has increased from %1 to %2. Ihre Wertung hat sich von %1 auf %2 erhУЖht. Your rating stays at %1. Ihre Wertung bleibt auf %1. Your rating has decreased from %1 to %2. Ihre Wertung hat sich von %1 auf %2 erniedrigt. Error in file '%1' Fehler in Datei '%1' Move %1 Zug %1 %n move(s) %n Zug %n ZУМge Move %1 of %2 Zug %1 von %2 Move %1 of %2 in variation %3 Zug %1 von %2 in Variante %3 &%1: %2 Label in Recent Files menu. The first 10 items are numbered to provide a mnemonic. %1 is replaced by the number, %2 by the file name. &%1: %2 RatedGamesList Game Spiel Your Color Ihre Farbe Level Stufe Result Ergebnis Date Datum Win Gewinn Tie Unentschieden Loss Verlust RatingDialog Your Rating Ihre Wertung Your rating: Ihre Wertung: Game variant: Spielvariante: Number rated games: Anzahl gewertete Spiele: Best previous rating: Beste frУМhere Wertung: Recent games: Zuletzt gespielte Spiele: &Clear &LУЖschen Clear rating and delete rating history? Wertung und Wertungsentwicklung lУЖschen? Clear rating Wertung lУЖschen Classic (4 players) Klassisch (4 Spieler) Classic (2 players) Klassisch (2 Spieler) Duo Duo Trigon (4 players) Trigon (4 Spieler) Trigon (2 players) Trigon (2 Spieler) Trigon (3 players) Trigon (3 Spieler) Junior Junior Recent development: Aktuelle Entwicklung: pentobi-7.2/src/pentobi/pentobi_en_CA.ts000066400000000000000000001372241227240712600203220ustar00rootroot00000000000000 AnalyzeGameWidget Win Win Loss Loss Running game analysis... Running game analysis... AnalyzeGameWindow Game Analysis Game Analysis AnalyzeSpeedDialog Fast Fast Normal Normal Slow Slow Analysis speed: Analysis speed: ExportImage Export Image Export Image Image size: Image size: MainWindow Setup mode Setup mode Rated game Rated game Continuing unfinished rated game. Continuing unfinished rated game. You play %1 in this game. You play %1 in this game. About Pentobi About Pentobi Pentobi Pentobi Version %1 Version %1 Copyright &copy; 2011&ndash;2014 Markus Enzenberger Copyright &copy; 2011&ndash;2014 Markus Enzenberger Analyze Game Analyze Game The file has been modified. The file has been modified. Do you want to save your changes? Do you want to save your changes? &Don't Save &Don't Save The current game is not finished. The current game is not finished. Do you want to abort the game? Do you want to abort the game? &Abort Game &Abort Game &About &About &Analyze Game... &Analyze Game... B&ackward B&ackward Back to &Main Variation Back to &Main Variation &Bad &Bad &Beginning &Beginning Beginning of Bran&ch Beginning of Bran&ch Clear Piece Clear Piece &Computer Colors &Computer Colours C&oordinates C&oordinates &Delete All Variations &Delete All Variations &Doubtful &Doubtful &End &End &ASCII Art &ASCII Art I&mage I&mage &Find Move &Find Move Flip Horizontally Flip Horizontally Flip Vertically Flip Vertically &Forward &Forward &Fullscreen &Fullscreen Ga&me Info Ga&me Info &Classic (4 Players) &Classic (4 Players) Classic (&2 Players) Classic (&2 Players) &Duo &Duo J&unior J&unior &Trigon (4 Players) &Trigon (4 Players) Tri&gon (2 Players) Tri&gon (2 Players) Trigon (&3 Players) Trigon (&3 Players) &Good &Good Pentobi is a computer opponent for the board game Blokus. Pentobi is a computer opponent for the board game Blokus. &Go to Move... &Go to Move... &Contents &Contents I&nteresting I&nteresting &Keep Only Position &Keep Only Position Keep Only &Subtree Keep Only &Subtree Leave Fullscreen Leave Fullscreen M&ake Main Variation M&ake Main Variation Move Variation D&own Move Variation D&own Move Variation &Up Move Variation &Up &1 &1 &2 &2 &3 &3 &4 &4 &5 &5 &6 &6 &7 &7 &8 &8 &All &All &Last &Last &None move numbers &None Next Piece Next Piece &Next Variation &Next Variation New &Rated Game New &Rated Game &New &New N&one move annotation N&one &Open... &Open... &Play &Play Play &Single Move Play &Single Move Previous Piece Previous Piece &Previous Variation &Previous Variation Rotate Anticlockwise Rotate Anticlockwise Rotate Clockwise Rotate Clockwise &Quit &Quit &Save &Save Save &As... Save &As... Select Next &Color Select Next &Colour S&etup Mode S&etup Mode &Comment &Comment Your &Rating Your &Rating &No Text &No Text Text &Beside Icons Text &Beside Icons Text Bel&ow Icons Text Bel&ow Icons &Text Only &Text Only &System Default &System Default &Truncate &Truncate Truncate C&hildren Truncate C&hildren Show &Variations Show &Variations &Undo Move &Undo Move V&ery Bad V&ery Bad &Very Good &Very Good &Edit &Edit &Move Annotation &Move Annotation &View &View Toolbar T&ext Toolbar T&ext &Move Numbers &Move Numbers &Computer &Computer &Level &Level &Tools &Tools &Help &Help Delete all variations? Delete all variations? All variations but the main variation will be removed from the game tree. All variations but the main variation will be removed from the game tree. Delete Variations Delete Variations Move %1 Move %1 Move %1 of %2 Move %1 of %2 Move %1 of %2 in variation %3 Move %1 of %2 in variation %3 &Toolbar &Toolbar Could not delete %1 Could not delete %1 Text files (*.txt) Text files (*.txt) The end of the tree was reached. The end of the tree was reached. Continue the search from the start of the tree? Continue the search from the start of the tree? Continue From Start Continue From Start No comment found No comment found Make the computer continue to play Blue/Red Make the computer continue to play Blue/Red Make the computer play Blue/Red Make the computer play Blue/Red Make the computer continue to play Yellow/Green Make the computer continue to play Yellow/Green Make the computer play Yellow/Green Make the computer play Yellow/Green Make the computer continue to play Blue Make the computer continue to play Blue Make the computer play Blue Make the computer play Blue Make the computer continue to play Yellow Make the computer continue to play Yellow Make the computer play Yellow Make the computer play Yellow Make the computer continue to play Green Make the computer continue to play Green Make the computer play Green Make the computer play Green Make the computer continue to play Red Make the computer continue to play Red Make the computer play Red Make the computer play Red Blue wins with %n point(s). Blue wins with %n point. Blue wins with %n points. Green wins with %n point(s). Green wins with %n point. Green wins with %n points. The game ends in a tie. The game ends in a tie. Blue/Red wins with %n point(s). Blue/Red wins with %n point. Blue/Red wins with %n points. Yellow/Green wins with %n point(s). Yellow/Green wins with %n point. Yellow/Green wins with %n points. The game ends in a tie between all colors. The game ends in a tie between all colours. The game ends in a tie between Blue and Yellow. The game ends in a tie between Blue and Yellow. The game ends in a tie between Blue and Red. The game ends in a tie between Blue and Red. The game ends in a tie between Yellow and Red. The game ends in a tie between Yellow and Red. Blue wins. Blue wins. Yellow wins. Yellow wins. Red wins. Red wins. The game ends in a tie between Blue, Yellow and Red. The game ends in a tie between Blue, Yellow and Red. The game ends in a tie between Blue, Yellow and Green. The game ends in a tie between Blue, Yellow and Green. The game ends in a tie between Blue, Red and Green. The game ends in a tie between Blue, Red and Green. The game ends in a tie between Yellow, Red and Green. The game ends in a tie between Yellow, Red and Green. The game ends in a tie between Blue and Green. The game ends in a tie between Blue and Green. The game ends in a tie between Yellow and Green. The game ends in a tie between Yellow and Green. The game ends in a tie between Red and Green. The game ends in a tie between Red and Green. Green wins. Green wins. Your rating has increased from %1 to %2. Your rating has increased from %1 to %2. Your rating stays at %1. Your rating stays at %1. Your rating has decreased from %1 to %2. Your rating has decreased from %1 to %2. Game saved: %1 Game saved: %1 The computer is thinking... (max. %1 seconds remaining) The computer is thinking... (max. %1 seconds remaining) The computer is thinking... (max. %1 minutes remaining) The computer is thinking... (max. %1 minutes remaining) [*]%1 [*]%1 The computer is thinking... The computer is thinking... Go one move backward Go one move backward Game analysis is only possible in the main variation. Game analysis is only possible in the main variation. 10 Back&ward 10 Back&ward Go ten moves backward Go ten moves backward Go to beginning of game Go to beginning of game Set the colors played by the computer Set the colours played by the computer Go to end of moves Go to end of moves Find Next &Comment Find Next &Comment Go one move forward Go one move forward 10 F&orward 10 F&orward Go ten moves forward Go ten moves forward St&op St&op Go to next variation Go to next variation Start a new game Start a new game Go to previous variation Go to previous variation &Game &Game Game &Variant Game &Variant Open R&ecent Open R&ecent E&xport E&xport G&o G&o Blokus games (*.blksgf);;All files (*.*) Blokus games (*.blksgf);;All files (*.*) Go to Move Go to Move Move number: Move number: Keep only position? Keep only position? All previous and following moves and variations will be removed from the game tree. All previous and following moves and variations will be removed from the game tree. Keep Only Position Keep Only Position Keep only subtree? Keep only subtree? All previous moves and variations will be removed from the game tree. All previous moves and variations will be removed from the game tree. Keep Only Subtree Keep Only Subtree Start new rated game? Start new rated game? In the next game, you will play %1 against Pentobi level&nbsp;%2. In the next game, you will play %1 against Pentobi level&nbsp;%2. &Start Game &Start Game Pentobi %1 (level %2) The first argument is the version of Pentobi Pentobi %1 (level %2) Human Human Open Open Could not read file '%1' Could not read file '%1' The file could not be saved. The file could not be saved. %1: %2 Error message if file cannot be saved. %1 is replaced by the file name, %2 by the error message of the operating system. %1: %2 Untitled Game.blksgf Untitled Game.blksgf Untitled Game %1.blksgf Untitled Game %1.blksgf Save Save Setup mode cannot be used if moves have been played. Setup mode cannot be used if moves have been played. Error in file '%1' Error in file '%1' The file is not a valid Blokus SGF file. The file is not a valid Blokus SGF file. Truncate this subtree? Truncate this subtree? This position and all following moves and variations will be removed from the game tree. This position and all following moves and variations will be removed from the game tree. Truncate Truncate Truncate children? Truncate children? All following moves and variations will be removed from the game tree. All following moves and variations will be removed from the game tree. Truncate Children Truncate Children %n move(s) %n move %n moves &%1: %2 Label in Recent Files menu. The first 10 items are numbered to provide a mnemonic. %1 is replaced by the number, %2 by the file name. &%1: %2 RatedGamesList Game Game Your Color Your Colour Level Level Result Result Date Date Win Win Tie Tie Loss Loss RatingDialog Your rating: Your rating: Game variant: Game variant: Number rated games: Number rated games: Best previous rating: Best previous rating: Recent development: Recent development: Your Rating Your Rating Recent games: Recent games: &Clear &Clear Clear rating and delete rating history? Clear rating and delete rating history? Clear rating Clear rating Classic (4 players) Classic (4 players) Classic (2 players) Classic (2 players) Duo Duo Trigon (4 players) Trigon (4 players) Trigon (2 players) Trigon (2 players) Trigon (3 players) Trigon (3 players) Junior Junior pentobi-7.2/src/pentobi/pentobi_en_GB.ts000066400000000000000000001372241227240712600203270ustar00rootroot00000000000000 AnalyzeGameWidget Win Win Loss Loss Running game analysis... Running game analysis... AnalyzeGameWindow Game Analysis Game Analysis AnalyzeSpeedDialog Fast Fast Normal Normal Slow Slow Analysis speed: Analysis speed: ExportImage Export Image Export Image Image size: Image size: MainWindow Setup mode Setup mode Rated game Rated game Continuing unfinished rated game. Continuing unfinished rated game. You play %1 in this game. You play %1 in this game. About Pentobi About Pentobi Pentobi Pentobi Version %1 Version %1 Copyright &copy; 2011&ndash;2014 Markus Enzenberger Copyright &copy; 2011&ndash;2014 Markus Enzenberger Analyze Game Analyse Game The file has been modified. The file has been modified. Do you want to save your changes? Do you want to save your changes? &Don't Save &Don't Save The current game is not finished. The current game is not finished. Do you want to abort the game? Do you want to abort the game? &Abort Game &Abort Game &About &About &Analyze Game... &Analyse Game... B&ackward B&ackward Back to &Main Variation Back to &Main Variation &Bad &Bad &Beginning &Beginning Beginning of Bran&ch Beginning of Bran&ch Clear Piece Clear Piece &Computer Colors &Computer Colours C&oordinates C&oordinates &Delete All Variations &Delete All Variations &Doubtful &Doubtful &End &End &ASCII Art &ASCII Art I&mage I&mage &Find Move &Find Move Flip Horizontally Flip Horizontally Flip Vertically Flip Vertically &Forward &Forward &Fullscreen &Fullscreen Ga&me Info Ga&me Info &Classic (4 Players) &Classic (4 Players) Classic (&2 Players) Classic (&2 Players) &Duo &Duo J&unior J&unior &Trigon (4 Players) &Trigon (4 Players) Tri&gon (2 Players) Tri&gon (2 Players) Trigon (&3 Players) Trigon (&3 Players) &Good &Good Pentobi is a computer opponent for the board game Blokus. Pentobi is a computer opponent for the board game Blokus. &Go to Move... &Go to Move... &Contents &Contents I&nteresting I&nteresting &Keep Only Position &Keep Only Position Keep Only &Subtree Keep Only &Subtree Leave Fullscreen Leave Fullscreen M&ake Main Variation M&ake Main Variation Move Variation D&own Move Variation D&own Move Variation &Up Move Variation &Up &1 &1 &2 &2 &3 &3 &4 &4 &5 &5 &6 &6 &7 &7 &8 &8 &All &All &Last &Last &None move numbers &None Next Piece Next Piece &Next Variation &Next Variation New &Rated Game New &Rated Game &New &New N&one move annotation N&one &Open... &Open... &Play &Play Play &Single Move Play &Single Move Previous Piece Previous Piece &Previous Variation &Previous Variation Rotate Anticlockwise Rotate Anticlockwise Rotate Clockwise Rotate Clockwise &Quit &Quit &Save &Save Save &As... Save &As... Select Next &Color Select Next &Colour S&etup Mode S&etup Mode &Comment &Comment Your &Rating Your &Rating &No Text &No Text Text &Beside Icons Text &Beside Icons Text Bel&ow Icons Text Bel&ow Icons &Text Only &Text Only &System Default &System Default &Truncate &Truncate Truncate C&hildren Truncate C&hildren Show &Variations Show &Variations &Undo Move &Undo Move V&ery Bad V&ery Bad &Very Good &Very Good &Edit &Edit &Move Annotation &Move Annotation &View &View Toolbar T&ext Toolbar T&ext &Move Numbers &Move Numbers &Computer &Computer &Level &Level &Tools &Tools &Help &Help Delete all variations? Delete all variations? All variations but the main variation will be removed from the game tree. All variations but the main variation will be removed from the game tree. Delete Variations Delete Variations Move %1 Move %1 Move %1 of %2 Move %1 of %2 Move %1 of %2 in variation %3 Move %1 of %2 in variation %3 &Toolbar &Toolbar Could not delete %1 Could not delete %1 Text files (*.txt) Text files (*.txt) The end of the tree was reached. The end of the tree was reached. Continue the search from the start of the tree? Continue the search from the start of the tree? Continue From Start Continue From Start No comment found No comment found Make the computer continue to play Blue/Red Make the computer continue to play Blue/Red Make the computer play Blue/Red Make the computer play Blue/Red Make the computer continue to play Yellow/Green Make the computer continue to play Yellow/Green Make the computer play Yellow/Green Make the computer play Yellow/Green Make the computer continue to play Blue Make the computer continue to play Blue Make the computer play Blue Make the computer play Blue Make the computer continue to play Yellow Make the computer continue to play Yellow Make the computer play Yellow Make the computer play Yellow Make the computer continue to play Green Make the computer continue to play Green Make the computer play Green Make the computer play Green Make the computer continue to play Red Make the computer continue to play Red Make the computer play Red Make the computer play Red Blue wins with %n point(s). Blue wins with %n point. Blue wins with %n points. Green wins with %n point(s). Green wins with %n point. Green wins with %n points. The game ends in a tie. The game ends in a tie. Blue/Red wins with %n point(s). Blue/Red wins with %n point. Blue/Red wins with %n points. Yellow/Green wins with %n point(s). Yellow/Green wins with %n point. Yellow/Green wins with %n points. The game ends in a tie between all colors. The game ends in a tie between all colours. The game ends in a tie between Blue and Yellow. The game ends in a tie between Blue and Yellow. The game ends in a tie between Blue and Red. The game ends in a tie between Blue and Red. The game ends in a tie between Yellow and Red. The game ends in a tie between Yellow and Red. Blue wins. Blue wins. Yellow wins. Yellow wins. Red wins. Red wins. The game ends in a tie between Blue, Yellow and Red. The game ends in a tie between Blue, Yellow and Red. The game ends in a tie between Blue, Yellow and Green. The game ends in a tie between Blue, Yellow and Green. The game ends in a tie between Blue, Red and Green. The game ends in a tie between Blue, Red and Green. The game ends in a tie between Yellow, Red and Green. The game ends in a tie between Yellow, Red and Green. The game ends in a tie between Blue and Green. The game ends in a tie between Blue and Green. The game ends in a tie between Yellow and Green. The game ends in a tie between Yellow and Green. The game ends in a tie between Red and Green. The game ends in a tie between Red and Green. Green wins. Green wins. Your rating has increased from %1 to %2. Your rating has increased from %1 to %2. Your rating stays at %1. Your rating stays at %1. Your rating has decreased from %1 to %2. Your rating has decreased from %1 to %2. Game saved: %1 Game saved: %1 The computer is thinking... (max. %1 seconds remaining) The computer is thinking... (max. %1 seconds remaining) The computer is thinking... (max. %1 minutes remaining) The computer is thinking... (max. %1 minutes remaining) [*]%1 [*]%1 The computer is thinking... The computer is thinking... Go one move backward Go one move backward Game analysis is only possible in the main variation. Game analysis is only possible in the main variation. 10 Back&ward 10 Back&ward Go ten moves backward Go ten moves backward Go to beginning of game Go to beginning of game Set the colors played by the computer Set the colours played by the computer Go to end of moves Go to end of moves Find Next &Comment Find Next &Comment Go one move forward Go one move forward 10 F&orward 10 F&orward Go ten moves forward Go ten moves forward St&op St&op Go to next variation Go to next variation Start a new game Start a new game Go to previous variation Go to previous variation &Game &Game Game &Variant Game &Variant Open R&ecent Open R&ecent E&xport E&xport G&o G&o Blokus games (*.blksgf);;All files (*.*) Blokus games (*.blksgf);;All files (*.*) Go to Move Go to Move Move number: Move number: Keep only position? Keep only position? All previous and following moves and variations will be removed from the game tree. All previous and following moves and variations will be removed from the game tree. Keep Only Position Keep Only Position Keep only subtree? Keep only subtree? All previous moves and variations will be removed from the game tree. All previous moves and variations will be removed from the game tree. Keep Only Subtree Keep Only Subtree Start new rated game? Start new rated game? In the next game, you will play %1 against Pentobi level&nbsp;%2. In the next game, you will play %1 against Pentobi level&nbsp;%2. &Start Game &Start Game Pentobi %1 (level %2) The first argument is the version of Pentobi Pentobi %1 (level %2) Human Human Open Open Could not read file '%1' Could not read file '%1' The file could not be saved. The file could not be saved. %1: %2 Error message if file cannot be saved. %1 is replaced by the file name, %2 by the error message of the operating system. %1: %2 Untitled Game.blksgf Untitled Game.blksgf Untitled Game %1.blksgf Untitled Game %1.blksgf Save Save Setup mode cannot be used if moves have been played. Setup mode cannot be used if moves have been played. Error in file '%1' Error in file '%1' The file is not a valid Blokus SGF file. The file is not a valid Blokus SGF file. Truncate this subtree? Truncate this subtree? This position and all following moves and variations will be removed from the game tree. This position and all following moves and variations will be removed from the game tree. Truncate Truncate Truncate children? Truncate children? All following moves and variations will be removed from the game tree. All following moves and variations will be removed from the game tree. Truncate Children Truncate Children %n move(s) %n move %n moves &%1: %2 Label in Recent Files menu. The first 10 items are numbered to provide a mnemonic. %1 is replaced by the number, %2 by the file name. &%1: %2 RatedGamesList Game Game Your Color Your Colour Level Level Result Result Date Date Win Win Tie Tie Loss Loss RatingDialog Your rating: Your rating: Game variant: Game variant: Number rated games: Number rated games: Best previous rating: Best previous rating: Recent development: Recent development: Your Rating Your Rating Recent games: Recent games: &Clear &Clear Clear rating and delete rating history? Clear rating and delete rating history? Clear rating Clear rating Classic (4 players) Classic (4 players) Classic (2 players) Classic (2 players) Duo Duo Trigon (4 players) Trigon (4 players) Trigon (2 players) Trigon (2 players) Trigon (3 players) Trigon (3 players) Junior Junior pentobi-7.2/src/pentobi/resources.qrc000066400000000000000000000030411227240712600177730ustar00rootroot00000000000000 icons/pentobi.png icons/pentobi-16.png icons/pentobi-32.png icons/pentobi-backward.png icons/pentobi-backward-16.png icons/pentobi-backward10.png icons/pentobi-backward10-16.png icons/pentobi-beginning.png icons/pentobi-beginning-16.png icons/pentobi-computer-color.png icons/pentobi-computer-color-16.png icons/pentobi-end.png icons/pentobi-end-16.png icons/pentobi-flip-horizontal.png icons/pentobi-flip-vertical.png icons/pentobi-forward.png icons/pentobi-forward-16.png icons/pentobi-forward10.png icons/pentobi-forward10-16.png icons/pentobi-newgame.png icons/pentobi-newgame-16.png icons/pentobi-next-piece.png icons/pentobi-next-variation.png icons/pentobi-next-variation-16.png icons/pentobi-piece-clear.png icons/pentobi-play.png icons/pentobi-play-16.png icons/pentobi-previous-piece.png icons/pentobi-previous-variation.png icons/pentobi-previous-variation-16.png icons/pentobi-rotate-left.png icons/pentobi-rotate-right.png pentobi-7.2/src/pentobi_gtp/000077500000000000000000000000001227240712600161265ustar00rootroot00000000000000pentobi-7.2/src/pentobi_gtp/CMakeLists.txt000066400000000000000000000007261227240712600206730ustar00rootroot00000000000000include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) link_directories(${Boost_LIBRARY_DIRS}) set(pentobi_gtp_SRCS Engine.cpp Main.cpp ) add_executable(pentobi-gtp ${pentobi_gtp_SRCS}) target_link_libraries(pentobi-gtp pentobi_mcts pentobi_base boardgame_base boardgame_sgf boardgame_util boardgame_sys boardgame_gtp ${Boost_THREAD_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ) pentobi-7.2/src/pentobi_gtp/Engine.cpp000066400000000000000000000206411227240712600200420ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi_gtp/Engine.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "Engine.h" #include #include "libboardgame_sgf/TreeReader.h" #include "libpentobi_base/Tree.h" #include "libpentobi_mcts/Util.h" namespace pentobi_gtp { using libboardgame_gtp::Failure; using libboardgame_mcts::ChildIterator; using libboardgame_sgf::TreeReader; using libboardgame_util::log; using libboardgame_util::ArrayList; using libpentobi_base::Board; using libpentobi_base::Color; using libpentobi_base::Grid; using libpentobi_base::Move; using libpentobi_base::MoveList; using libpentobi_base::MoveMarker; using libpentobi_base::PieceInfo; using libpentobi_base::Point; using libpentobi_base::Tree; using libpentobi_mcts::Float; using libpentobi_mcts::Move; using libpentobi_mcts::State; //----------------------------------------------------------------------------- Engine::Engine(Variant variant, int level, bool use_book, const string& books_dir, unsigned nu_threads, size_t memory) : libpentobi_base::Engine(variant) { create_player(variant, books_dir, nu_threads, memory); get_mcts_player().set_use_book(use_book); get_mcts_player().set_level(level); add("gen_playout_move", &Engine::cmd_gen_playout_move); add("get_value", &Engine::cmd_get_value); add("name", &Engine::cmd_name); add("param", &Engine::cmd_param); add("move_values", &Engine::cmd_move_values); add("moves_stat", &Engine::cmd_moves_stat); add("save_tree", &Engine::cmd_save_tree); add("version", &Engine::cmd_version); } Engine::~Engine() throw() { } void Engine::cmd_get_value(Response& response) { response << get_search().get_tree().get_root().get_value(); } void Engine::cmd_move_values(Response& response) { auto& search = get_search(); auto& tree = search.get_tree(); auto& bd = get_board(); vector children; for (Search::ChildIterator i(tree, tree.get_root()); i; ++i) children.push_back(&(*i)); sort(children.begin(), children.end(), libpentobi_mcts::util::compare_node); response << fixed; for (const auto node : children) response << setprecision(0) << node->get_visit_count() << ' ' << setprecision(1) << node->get_value_count() << ' ' << setprecision(3) << node->get_value() << ' ' << bd.to_string(node->get_move()) << '\n'; } void Engine::cmd_moves_stat(const Arguments& args, Response& response) { Color c = get_color_arg(args); auto& bd = get_board(); Grid nu_moves_grid(bd.get_geometry(), 0); MoveList moves; MoveMarker marker; for (Point p : bd.get_attach_points(c)) { bd.gen_moves(c, p, marker, moves); for (Move mv : moves) for (Point p : bd.get_move_info(mv)) ++nu_moves_grid[p]; marker.clear_all_set_known(moves); moves.clear(); } response << '\n' << nu_moves_grid; } void Engine::cmd_name(Response& response) { response.set("Pentobi"); } void Engine::cmd_save_tree(const Arguments& args) { auto& search = get_search(); if (! search.get_last_state().is_valid()) throw Failure("no search tree"); ofstream out(args.get()); libpentobi_mcts::util::dump_tree(out, search); } void Engine::cmd_param(const Arguments& args, Response& response) { auto& p = get_mcts_player(); auto& s = get_search(); if (args.get_size() == 0) response << "avoid_symmetric_draw " << s.get_avoid_symmetric_draw() << '\n' << "auto_param " << s.get_auto_param() << '\n' << "bias_term_constant " << s.get_bias_term_constant() << '\n' << "bias_term_interval " << s.get_bias_term_interval() << '\n' << "detect_symmetry " << s.get_detect_symmetry() << '\n' << "expand_threshold " << s.get_expand_threshold() << '\n' << "fixed_simulations " << p.get_fixed_simulations() << '\n' << "level " << p.get_level() << '\n' << "rave_dist_final " << s.get_rave_dist_final() << '\n' << "rave_max_child_count " << s.get_rave_max_child_count() << '\n' << "rave_max_parent_count " << s.get_rave_max_parent_count() << '\n' << "rave_weight " << s.get_rave_weight() << '\n' << "reuse_subtree " << s.get_reuse_subtree() << '\n' << "score_modification " << s.get_score_modification() << '\n' << "skip_bias_term_min_count " << s.get_skip_bias_term_min_count() << '\n' << "use_book " << p.get_use_book() << '\n'; else { args.check_size(2); string name = args.get(0); if (name == "avoid_symmetric_draw") s.set_avoid_symmetric_draw(args.parse(1)); else if (name == "auto_param") s.set_auto_param(args.parse(1)); else if (name == "bias_term_constant") s.set_bias_term_constant(args.parse(1)); else if (name == "bias_term_interval") s.set_bias_term_interval(args.parse(1)); else if (name == "detect_symmetry") s.set_detect_symmetry(args.parse(1)); else if (name == "expand_threshold") s.set_expand_threshold(args.parse(1)); else if (name == "fixed_simulations") p.set_fixed_simulations(args.parse(1)); else if (name == "level") p.set_level(args.parse(1)); else if (name == "rave_dist_final") s.set_rave_dist_final(args.parse(1)); else if (name == "rave_max_child_count") s.set_rave_max_child_count(args.parse(1)); else if (name == "rave_max_parent_count") s.set_rave_max_parent_count(args.parse(1)); else if (name == "rave_weight") s.set_rave_weight(args.parse(1)); else if (name == "reuse_subtree") s.set_reuse_subtree(args.parse(1)); else if (name == "score_modification") s.set_score_modification(args.parse(1)); else if (name == "skip_bias_term_min_count") s.set_skip_bias_term_min_count(args.parse(1)); else if (name == "use_book") p.set_use_book(args.parse(1)); else { ostringstream msg; msg << "unknown parameter '" << name << "'"; throw Failure(msg.str()); } } } void Engine::cmd_version(Response& response) { string version; #ifdef VERSION version = VERSION; #endif if (version.empty()) version = "UNKNOWN"; // By convention, the version string of unreleased versions contains the // string UNKNOWN (appended to the last released version). In this case, or // if VERSION was undefined, we append the build date. if (version.find("UNKNOWN") != string::npos) { version.append(" ("); version.append(__DATE__); version.append(")"); } #if LIBBOARDGAME_DEBUG version.append(" (dbg)"); #endif response.set(version); } void Engine::create_player(Variant variant, const string& books_dir, unsigned nu_threads, size_t memory) { m_player.reset(new Player(variant, books_dir, nu_threads, memory)); set_player(*m_player); } void Engine::cmd_gen_playout_move(Response& response) { auto& state = get_mcts_player().get_search().get_state(0); state.start_search(); state.start_simulation(0); state.finish_in_tree(); if (! state.gen_and_play_playout_move(Move::null(), Move::null())) throw Failure("terminal playout position"); auto& bd = get_board(); response << bd.to_string(state.get_move(state.get_nu_moves() - 1).move); } Player& Engine::get_mcts_player() { try { return dynamic_cast(*m_player); } catch (const bad_cast&) { throw Failure("current player is not mcts player"); } } Search& Engine::get_search() { return get_mcts_player().get_search(); } void Engine::set_deterministic() { try { get_search().set_deterministic(); } catch (Failure&) { // Ignore if player is not MCTS player } } void Engine::use_cpu_time(bool enable) { get_mcts_player().use_cpu_time(enable); } //----------------------------------------------------------------------------- } // namespace pentobi_gtp pentobi-7.2/src/pentobi_gtp/Engine.h000066400000000000000000000032771227240712600175150ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi_gtp/Engine.h */ //----------------------------------------------------------------------------- #ifndef PENTOBI_GTP_ENGINE_H #define PENTOBI_GTP_ENGINE_H #include "libpentobi_base/Engine.h" #include "libpentobi_mcts/Player.h" namespace pentobi_gtp { using namespace std; using libboardgame_gtp::Arguments; using libboardgame_gtp::Response; using libpentobi_base::Variant; using libpentobi_mcts::Player; using libpentobi_mcts::Search; //----------------------------------------------------------------------------- class Engine : public libpentobi_base::Engine { public: Engine(Variant variant, int level = 5, bool use_book = true, const string& books_dir = "", unsigned nu_threads = 0, size_t memory = 0); ~Engine() throw(); void cmd_gen_playout_move(Response&); void cmd_param(const Arguments&, Response&); void cmd_get_value(Response&); void cmd_move_values(Response&); void cmd_name(Response&); void cmd_moves_stat(const Arguments&, Response&); void cmd_save_tree(const Arguments&); void cmd_version(Response&); Player& get_mcts_player(); /** @see Search::set_deterministic() */ void set_deterministic(); /** @see Player::use_cpu_time() */ void use_cpu_time(bool enable); private: unique_ptr m_player; void create_player(Variant variant, const string& books_dir, unsigned nu_threads, size_t memory); Search& get_search(); }; //----------------------------------------------------------------------------- } // namespace pentobi_gtp #endif // PENTOBI_GTP_ENGINE_H pentobi-7.2/src/pentobi_gtp/Main.cpp000066400000000000000000000141471227240712600175250ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi_gtp/Main.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include #include #include "Engine.h" #include "libboardgame_util/Exception.h" #include "libboardgame_util/Log.h" #include "libboardgame_util/Options.h" #include "libboardgame_util/RandomGenerator.h" using namespace std; using libboardgame_gtp::Failure; using libboardgame_util::log; using libboardgame_util::set_log_null; using libboardgame_util::Exception; using libboardgame_util::Options; using libboardgame_util::RandomGenerator; using libpentobi_base::parse_variant_id; using libpentobi_base::Board; using libpentobi_base::Variant; //----------------------------------------------------------------------------- namespace { string get_application_dir_path(int argc, char** argv) { if (argc == 0 || argv == nullptr || argv[0] == nullptr) return ""; string application_path(argv[0]); #ifdef _WIN32 auto pos = application_path.find_last_of("/\\"); #else auto pos = application_path.find_last_of("/"); #endif if (pos == string::npos) return ""; return application_path.substr(0, pos); } } // namespace //----------------------------------------------------------------------------- int main(int argc, char** argv) { string application_dir_path = get_application_dir_path(argc, argv); try { vector specs; specs.push_back("book:"); specs.push_back("config|c:"); specs.push_back("color"); specs.push_back("cputime"); specs.push_back("game|g:"); specs.push_back("help|h"); specs.push_back("level|l:"); specs.push_back("memory:"); specs.push_back("nobook"); specs.push_back("noresign"); specs.push_back("quiet|q"); specs.push_back("seed|r:"); specs.push_back("showboard"); specs.push_back("threads:"); specs.push_back("version|v"); Options opt(argc, argv, specs); if (opt.contains("help")) { cout << "Usage: pentobi_gtp [options] [input files]\n" "--book load an external book file\n" "--config,-c set GTP config file\n" "--color colorize text output of boards\n" "--cputime use CPU time\n" "--game,-g game variant (classic, classic_2, duo, trigon,\n" " trigon_2, junior)\n" "--help,-h print help message and exit\n" "--level,-l set playing strength level\n" "--memory memory to allocate for search trees\n" "--seed,-r set random seed\n" "--showboard automatically write board to stderr after\n" " changes\n" "--nobook disable opening book\n" "--noresign disable resign\n" "--quiet,-q do not print logging messages\n" "--threads number of threads in the search\n" "--version,-v print version and exit\n"; return 0; } if (opt.contains("version")) { #ifdef VERSION cout << "Pentobi " << VERSION << '\n'; #else cout << "Pentobi UNKNONW"; #endif return 0; } size_t memory = 0; if (opt.contains("memory")) { memory = opt.get("memory"); if (memory == 0) throw Exception("Value for memory must be greater zero."); } unsigned threads = 1; if (opt.contains("threads")) { threads = opt.get("threads"); if (threads == 0) throw Exception("Number of threads must be greater zero."); } Board::color_output = opt.contains("color"); if (opt.contains("quiet")) set_log_null(); if (opt.contains("seed")) RandomGenerator::set_global_seed(opt.get("seed")); string variant_string = opt.get("game", "classic"); Variant variant; if (! parse_variant_id(variant_string, variant)) throw Exception("invalid game variant " + variant_string); auto level = opt.get("level", 4); auto use_book = (! opt.contains("nobook")); string books_dir = application_dir_path; pentobi_gtp::Engine engine(variant, level, use_book, books_dir, threads, memory); engine.set_resign(! opt.contains("noresign")); if (opt.contains("showboard")) engine.set_show_board(true); if (opt.contains("seed")) engine.set_deterministic(); if (opt.contains("cputime")) engine.use_cpu_time(true); string book_file = opt.get("book", ""); if (! book_file.empty()) { ifstream in(book_file); engine.get_mcts_player().load_book(in); } string config_file = opt.get("config", ""); if (! config_file.empty()) { ifstream in(config_file); if (! in) throw Exception("Error opening " + config_file); engine.exec(in, true, log()); } auto& args = opt.get_args(); if (! args.empty()) for (auto& file : args) { ifstream in(file); if (! in) throw Exception("Error opening " + file); engine.exec_main_loop_st(in, cout); } else engine.exec_main_loop_st(cin, cout); return 0; } catch (const Failure& e) { log() << "Error: command in config file failed: " << e.get_response() << '\n'; return 1; } catch (const exception& e) { log() << "Error: " << e.what() << '\n'; return 1; } catch (...) { log("Error: unknown exception"); return 1; } } //----------------------------------------------------------------------------- pentobi-7.2/src/pentobi_kde_thumbnailer/000077500000000000000000000000001227240712600204715ustar00rootroot00000000000000pentobi-7.2/src/pentobi_kde_thumbnailer/CMakeLists.txt000066400000000000000000000010261227240712600232300ustar00rootroot00000000000000find_package(KDE4 REQUIRED) include(KDE4Defaults) include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ${KDE4_INCLUDES} ${QT_INCLUDES} ) set(pentobi_thumbnail_SRCS PentobiThumbCreator.cpp) kde4_add_plugin(pentobi-thumbnail ${pentobi_thumbnail_SRCS}) target_link_libraries(pentobi-thumbnail pentobi_kde_thumbnailer ${KDE4_KIO_LIBS} ${QT_LIBRARIES} ) install(TARGETS pentobi-thumbnail DESTINATION ${PLUGIN_INSTALL_DIR}) install(FILES pentobi-thumbnail.desktop DESTINATION ${SERVICES_INSTALL_DIR}) pentobi-7.2/src/pentobi_kde_thumbnailer/PentobiThumbCreator.cpp000066400000000000000000000020021227240712600251070ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi_kde_thumbnailer/PentobiThumbCreator.cpp */ //----------------------------------------------------------------------------- #include "PentobiThumbCreator.h" #include #include "libpentobi_thumbnail/CreateThumbnail.h" using namespace std; //----------------------------------------------------------------------------- extern "C" { KDE_EXPORT ThumbCreator* new_creator() { return new PentobiThumbCreator; } } //----------------------------------------------------------------------------- PentobiThumbCreator::~PentobiThumbCreator() { } bool PentobiThumbCreator::create(const QString& path, int width, int height, QImage& image) { image = QImage(width, height, QImage::Format_ARGB32); image.fill(Qt::transparent); return createThumbnail(path, width, height, image); } //----------------------------------------------------------------------------- pentobi-7.2/src/pentobi_kde_thumbnailer/PentobiThumbCreator.h000066400000000000000000000015131227240712600245620ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi_kde_thumbnailer/PentobiThumbCreator.h */ //----------------------------------------------------------------------------- #ifndef PENTOBI_KDE_THUMBNAILER_PENTOBI_THUMB_CREATOR_H #define PENTOBI_KDE_THUMBNAILER_PENTOBI_THUMB_CREATOR_H #include #include //----------------------------------------------------------------------------- class PentobiThumbCreator : public QObject, public ThumbCreator { Q_OBJECT public: virtual ~PentobiThumbCreator(); bool create(const QString& path, int width, int height, QImage& image) override; }; //----------------------------------------------------------------------------- #endif // PENTOBI_KDE_THUMBNAILER_PENTOBI_THUMB_CREATOR_H pentobi-7.2/src/pentobi_kde_thumbnailer/pentobi-thumbnail.desktop000066400000000000000000000002521227240712600255040ustar00rootroot00000000000000[Desktop Entry] Type=Service Name=Blokus games Name[de]=Blokus-Partien X-KDE-ServiceTypes=ThumbCreator MimeType=application/x-blokus-sgf; X-KDE-Library=pentobi-thumbnail pentobi-7.2/src/pentobi_thumbnailer/000077500000000000000000000000001227240712600176465ustar00rootroot00000000000000pentobi-7.2/src/pentobi_thumbnailer/CMakeLists.txt000066400000000000000000000011311227240712600224020ustar00rootroot00000000000000if (NOT USE_QT5) include(${QT_USE_FILE}) endif() include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) link_directories(${Boost_LIBRARY_DIRS}) set(pentobi_thumbnailer_SRCS Main.cpp ) add_executable(pentobi-thumbnailer ${pentobi_thumbnailer_SRCS}) if (USE_QT5) qt5_use_modules(pentobi-thumbnailer Widgets) endif() target_link_libraries(pentobi-thumbnailer pentobi_thumbnail pentobi_gui ${QT_LIBRARIES} pentobi_base boardgame_base boardgame_sgf boardgame_util boardgame_sys ) install(TARGETS pentobi-thumbnailer DESTINATION ${CMAKE_INSTALL_BINDIR}) pentobi-7.2/src/pentobi_thumbnailer/Main.cpp000066400000000000000000000036341227240712600212440ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file pentobi_thumbnailer/Main.cpp */ //----------------------------------------------------------------------------- #include #include #include #include #include #include #include "libboardgame_util/Exception.h" #include "libboardgame_util/Options.h" #include "libpentobi_thumbnail/CreateThumbnail.h" using namespace std; using libboardgame_util::Exception; using libboardgame_util::Options; //----------------------------------------------------------------------------- namespace { void mainFunction(int argc, char* argv[]) { vector specs; specs.push_back("size|s:"); Options opt(argc, argv, specs); auto size = opt.get("size", 128); auto& files = opt.get_args(); if (size <= 0) throw Exception("Invalid image size"); if (files.size() > 2) throw Exception("Too many file arguments"); if (files.size() < 2) throw Exception("Need input and output file argument"); QImage image(size, size, QImage::Format_ARGB32); image.fill(Qt::transparent); if (! createThumbnail(QString::fromLocal8Bit(files[0].c_str()), size, size, image)) throw Exception("Thumbnail generation failed"); QImageWriter writer(QString::fromLocal8Bit(files[1].c_str()), "png"); if (! writer.write(image)) throw Exception(writer.errorString().toLocal8Bit().constData()); } } //namespace //----------------------------------------------------------------------------- int main(int argc, char* argv[]) { QCoreApplication app(argc, argv); try { mainFunction(argc, argv); } catch (const exception& e) { cerr << e.what() << '\n'; return 1; } return 0; } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/000077500000000000000000000000001227240712600154735ustar00rootroot00000000000000pentobi-7.2/src/unittest/CMakeLists.txt000066400000000000000000000004301227240712600202300ustar00rootroot00000000000000add_subdirectory(libboardgame_util) add_subdirectory(libboardgame_sgf) add_subdirectory(libboardgame_base) add_subdirectory(libboardgame_mcts) add_subdirectory(libpentobi_base) add_subdirectory(libpentobi_mcts) if (PENTOBI_BUILD_GTP) add_subdirectory(libboardgame_gtp) endif() pentobi-7.2/src/unittest/libboardgame_base/000077500000000000000000000000001227240712600210755ustar00rootroot00000000000000pentobi-7.2/src/unittest/libboardgame_base/CMakeLists.txt000066400000000000000000000010761227240712600236410ustar00rootroot00000000000000include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) link_directories(${Boost_LIBRARY_DIRS}) set(unittest_libboardgame_base_STAT_SRCS MarkerTest.cpp PointTest.cpp PointTransformTest.cpp RatingTest.cpp RectGeometryTest.cpp SpreadsheetStringRepTest.cpp ) add_executable(unittest_libboardgame_base ${unittest_libboardgame_base_STAT_SRCS}) target_link_libraries(unittest_libboardgame_base boardgame_test_main boardgame_base boardgame_test boardgame_util boardgame_sys ) add_test(libboardgame_base unittest_libboardgame_base) pentobi-7.2/src/unittest/libboardgame_base/MarkerTest.cpp000066400000000000000000000041011227240712600236560ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file MarkerTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "libboardgame_base/SpreadsheetStringRep.h" #include "libboardgame_base/Marker.h" #include "libboardgame_base/Point.h" #include "libboardgame_test/Test.h" using namespace std; using libboardgame_base::SpreadsheetStringRep; //----------------------------------------------------------------------------- typedef libboardgame_base::Point<19,19,unsigned short,SpreadsheetStringRep> Point; typedef libboardgame_base::Marker Marker; //----------------------------------------------------------------------------- LIBBOARDGAME_TEST_CASE(boardgame_marker_basic) { Marker m; Point p1(0, 1); Point p2(0, 0); LIBBOARDGAME_CHECK(! m[p1]); LIBBOARDGAME_CHECK(! m[p2]); m.set(p1); LIBBOARDGAME_CHECK(m[p1]); LIBBOARDGAME_CHECK(! m[p2]); m.clear(); LIBBOARDGAME_CHECK(! m[p1]); LIBBOARDGAME_CHECK(! m[p2]); } /** Test clear after a number of clears around the maximum unsigned integer value. This is a critical point of the implementation, which assumes that values not equal to a clear counter are unmarked and the overflow of the clear counter must be handled correctly. This test is only run, if integers are not larger than 32-bit, otherwise it would take too long. */ LIBBOARDGAME_TEST_CASE(boardgame_marker_overflow) { if (numeric_limits::digits > 32) return; Marker m; m.setup_for_overflow_test(numeric_limits::max() - 5); Point p1(0, 1); Point p2(0, 0); for (int i = 0; i < 10; ++i) { LIBBOARDGAME_CHECK(! m[p1]); LIBBOARDGAME_CHECK(! m[p2]); m.set(p1); LIBBOARDGAME_CHECK(m[p1]); LIBBOARDGAME_CHECK(! m[p2]); m.clear(); LIBBOARDGAME_CHECK(! m[p1]); LIBBOARDGAME_CHECK(! m[p2]); } } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libboardgame_base/PointTest.cpp000066400000000000000000000060321227240712600235330ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file PointTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include #include "libboardgame_base/SpreadsheetStringRep.h" #include "libboardgame_base/Point.h" #include "libboardgame_test/Test.h" using namespace std; using libboardgame_base::SpreadsheetStringRep; //----------------------------------------------------------------------------- typedef libboardgame_base::Point<19,19,unsigned short,SpreadsheetStringRep> Point; //----------------------------------------------------------------------------- LIBBOARDGAME_TEST_CASE(boardgame_point_from_string) { LIBBOARDGAME_CHECK_EQUAL(Point(0, 0), Point::from_string("a1")); LIBBOARDGAME_CHECK_EQUAL(Point(0, 18), Point::from_string("a19")); LIBBOARDGAME_CHECK_EQUAL(Point(0, 0), Point::from_string("A1")); LIBBOARDGAME_CHECK_EQUAL(Point(0, 0), Point::from_string(" A1")); LIBBOARDGAME_CHECK_EQUAL(Point(0, 0), Point::from_string("a1 \t")); LIBBOARDGAME_CHECK_THROW(Point::from_string("a 19"), Point::InvalidString); LIBBOARDGAME_CHECK_THROW(Point::from_string("a1 foo"), Point::InvalidString); LIBBOARDGAME_CHECK_THROW(Point::from_string("a19 foo"), Point::InvalidString); LIBBOARDGAME_CHECK_THROW(Point::from_string("NULL foo"), Point::InvalidString); LIBBOARDGAME_CHECK_THROW(Point::from_string("foobar"), Point::InvalidString); LIBBOARDGAME_CHECK_THROW(Point::from_string("a123"), Point::InvalidString); LIBBOARDGAME_CHECK_THROW(Point::from_string("a56"), Point::InvalidString); LIBBOARDGAME_CHECK_THROW(Point::from_string("aa1"), Point::InvalidString); LIBBOARDGAME_CHECK_THROW(Point::from_string("c3#"), Point::InvalidString); } LIBBOARDGAME_TEST_CASE(boardgame_point_get_down) { LIBBOARDGAME_CHECK_EQUAL(Point(0, 17), Point(0, 18).get_down()); } LIBBOARDGAME_TEST_CASE(boardgame_point_get_left) { LIBBOARDGAME_CHECK_EQUAL(Point(0, 0), Point(1, 0).get_left()); } LIBBOARDGAME_TEST_CASE(boardgame_point_get_right) { LIBBOARDGAME_CHECK_EQUAL(Point(1, 0), Point(0, 0).get_right()); } LIBBOARDGAME_TEST_CASE(boardgame_point_get_up) { LIBBOARDGAME_CHECK_EQUAL(Point(0, 1), Point(0, 0).get_up()); } LIBBOARDGAME_TEST_CASE(boardgame_point_is_onboard) { LIBBOARDGAME_CHECK(! Point::null().is_onboard()); LIBBOARDGAME_CHECK(Point(0, 0).is_onboard()); } LIBBOARDGAME_TEST_CASE(boardgame_point_stream_output) { ostringstream o; o << Point(0, 0); LIBBOARDGAME_CHECK_EQUAL(string("a1"), o.str()); } LIBBOARDGAME_TEST_CASE(boardgame_point_to_string) { LIBBOARDGAME_CHECK_EQUAL(string("a1"), Point(0, 0).to_string()); LIBBOARDGAME_CHECK_EQUAL(string("a19"), Point(0, 18).to_string()); LIBBOARDGAME_CHECK_EQUAL(string("j10"), Point(9, 9).to_string()); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libboardgame_base/PointTransformTest.cpp000066400000000000000000000024071227240712600254310ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file PointTransformTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "libboardgame_base/PointTransform.h" #include "libboardgame_base/SpreadsheetStringRep.h" #include "libboardgame_test/Test.h" using namespace std; using namespace libboardgame_base; //----------------------------------------------------------------------------- LIBBOARDGAME_TEST_CASE(boardgame_point_transform_get_transformed) { typedef libboardgame_base::Point<19,19,unsigned short,SpreadsheetStringRep> Point; unsigned sz = 9; Point p("B7"); { PointTransfIdent transform; LIBBOARDGAME_CHECK_EQUAL(transform.get_transformed(p, sz, sz), p); } { PointTransfRot180 transform; LIBBOARDGAME_CHECK_EQUAL(transform.get_transformed(p, sz, sz), Point("H3")); } { PointTransfRot270Refl transform; LIBBOARDGAME_CHECK_EQUAL(transform.get_transformed(p, sz, sz), Point("C8")); } } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libboardgame_base/RatingTest.cpp000066400000000000000000000036301227240712600236670ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file unittest/libboardgame_base/RatingTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "libboardgame_base/Rating.h" #include "libboardgame_test/Test.h" using namespace libboardgame_base; //----------------------------------------------------------------------------- LIBBOARDGAME_TEST_CASE(boardgame_rating_get_expected_result) { Rating a(2806); Rating b(2577); LIBBOARDGAME_CHECK_CLOSE_EPS(a.get_expected_result(b), 0.789, 0.001); } LIBBOARDGAME_TEST_CASE(boardgame_rating_get_expected_result_multiplayer) { // Player and 3 opponents, all with rating 1000, should have 25% // winning probability Rating a(1000); Rating b(1000); LIBBOARDGAME_CHECK_CLOSE_EPS(a.get_expected_result(b, 3), 0.25, 0.001); } LIBBOARDGAME_TEST_CASE(boardgame_rating_update_1) { Rating a(2806); Rating b(2577); Rating new_a = a; Rating new_b = b; new_a.update(0, b, 10); new_b.update(1, a, 10); LIBBOARDGAME_CHECK_CLOSE_EPS(new_a.get(), 2798.f, 1); LIBBOARDGAME_CHECK_CLOSE_EPS(new_b.get(), 2585.f, 1); } LIBBOARDGAME_TEST_CASE(boardgame_rating_update_2) { Rating a(2806); Rating b(2577); Rating new_a = a; Rating new_b = b; new_a.update(1, b, 10); new_b.update(0, a, 10); LIBBOARDGAME_CHECK_CLOSE_EPS(new_a.get(), 2808.f, 1); LIBBOARDGAME_CHECK_CLOSE_EPS(new_b.get(), 2575.f, 1); } LIBBOARDGAME_TEST_CASE(boardgame_rating_update_3) { Rating a(2806); Rating b(2577); Rating new_a = a; Rating new_b = b; new_a.update(0.5, b, 10); new_b.update(0.5, a, 10); LIBBOARDGAME_CHECK_CLOSE_EPS(new_a.get(), 2803.f, 1); LIBBOARDGAME_CHECK_CLOSE_EPS(new_b.get(), 2580.f, 1); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libboardgame_base/RectGeometryTest.cpp000066400000000000000000000062551227240712600250620ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file RectGeometryTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "libboardgame_base/PointList.h" #include "libboardgame_base/RectGeometry.h" #include "libboardgame_base/SpreadsheetStringRep.h" #include "libboardgame_test/Test.h" using namespace std; using libboardgame_base::NullTermList; using libboardgame_base::SpreadsheetStringRep; //----------------------------------------------------------------------------- typedef libboardgame_base::Point<19,19,unsigned short,SpreadsheetStringRep> Point; typedef libboardgame_base::Geometry Geometry; typedef libboardgame_base::RectGeometry RectGeometry; typedef libboardgame_base::PointList PointList; //----------------------------------------------------------------------------- LIBBOARDGAME_TEST_CASE(boardgame_rect_geometry_get_adj_diag) { auto g = RectGeometry::get(9, 9); PointList l; for (NullTermList::Iterator i(g->get_adj_diag(Point("B9"))); i; ++i) l.push_back(*i); LIBBOARDGAME_CHECK_EQUAL(l.size(), 5u); LIBBOARDGAME_CHECK(l.contains(Point("A9"))); LIBBOARDGAME_CHECK(l.contains(Point("C9"))); LIBBOARDGAME_CHECK(l.contains(Point("A8"))); LIBBOARDGAME_CHECK(l.contains(Point("B8"))); LIBBOARDGAME_CHECK(l.contains(Point("C8"))); } LIBBOARDGAME_TEST_CASE(boardgame_rect_geometry_iterate) { auto g = RectGeometry::get(3, 3); Geometry::Iterator i(*g); LIBBOARDGAME_CHECK(i); LIBBOARDGAME_CHECK_EQUAL(Point(0, 0), *i); ++i; LIBBOARDGAME_CHECK(i); LIBBOARDGAME_CHECK_EQUAL(Point(1, 0), *i); ++i; LIBBOARDGAME_CHECK(i); LIBBOARDGAME_CHECK_EQUAL(Point(2, 0), *i); ++i; LIBBOARDGAME_CHECK(i); LIBBOARDGAME_CHECK_EQUAL(Point(0, 1), *i); ++i; LIBBOARDGAME_CHECK(i); LIBBOARDGAME_CHECK_EQUAL(Point(1, 1), *i); ++i; LIBBOARDGAME_CHECK(i); LIBBOARDGAME_CHECK_EQUAL(Point(2, 1), *i); ++i; LIBBOARDGAME_CHECK(i); LIBBOARDGAME_CHECK_EQUAL(Point(0, 2), *i); ++i; LIBBOARDGAME_CHECK(i); LIBBOARDGAME_CHECK_EQUAL(Point(1, 2), *i); ++i; LIBBOARDGAME_CHECK(i); LIBBOARDGAME_CHECK_EQUAL(Point(2, 2), *i); ++i; LIBBOARDGAME_CHECK(! i); } LIBBOARDGAME_TEST_CASE(boardgame_rect_geometry_dist_to_edge) { auto g = RectGeometry::get(9, 9); LIBBOARDGAME_CHECK_EQUAL(g->get_dist_to_edge(Point(3, 0)), 0u); LIBBOARDGAME_CHECK_EQUAL(g->get_dist_to_edge(Point(3, 2)), 2u); LIBBOARDGAME_CHECK_EQUAL(g->get_dist_to_edge(Point(6, 8)), 0u); LIBBOARDGAME_CHECK_EQUAL(g->get_dist_to_edge(Point(6, 5)), 2u); } LIBBOARDGAME_TEST_CASE(boardgame_rect_geometry_second_dist_to_edge) { auto g = RectGeometry::get(9, 9); LIBBOARDGAME_CHECK_EQUAL(g->get_second_dist_to_edge(Point(3, 0)), 3u); LIBBOARDGAME_CHECK_EQUAL(g->get_second_dist_to_edge(Point(3, 2)), 3u); LIBBOARDGAME_CHECK_EQUAL(g->get_second_dist_to_edge(Point(6, 8)), 2u); LIBBOARDGAME_CHECK_EQUAL(g->get_second_dist_to_edge(Point(6, 5)), 3u); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libboardgame_base/SpreadsheetStringRepTest.cpp000066400000000000000000000043751227240712600265570ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file SpreadsheetStringRepTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include #include "libboardgame_base/SpreadsheetStringRep.h" #include "libboardgame_test/Test.h" using namespace std; using libboardgame_base::SpreadsheetStringRep; //----------------------------------------------------------------------------- namespace { bool read(const string& s, unsigned& x, unsigned& y, unsigned width = SpreadsheetStringRep::max_width, unsigned height = SpreadsheetStringRep::max_height) { istringstream in(s); return SpreadsheetStringRep::read(in, width, height, x, y); } string write(unsigned x, unsigned y) { ostringstream out; SpreadsheetStringRep::write(out, x, y); return out.str(); } } // namespace //----------------------------------------------------------------------------- LIBBOARDGAME_TEST_CASE(boardgame_base_spreadsheet_string_rep_read) { unsigned x; unsigned y; LIBBOARDGAME_CHECK(read("a1", x, y)); LIBBOARDGAME_CHECK_EQUAL(x, 0u); LIBBOARDGAME_CHECK_EQUAL(y, 0u); LIBBOARDGAME_CHECK(read("a23", x, y)); LIBBOARDGAME_CHECK_EQUAL(x, 0u); LIBBOARDGAME_CHECK_EQUAL(y, 22u); LIBBOARDGAME_CHECK(read("A1", x, y)); LIBBOARDGAME_CHECK_EQUAL(x, 0u); LIBBOARDGAME_CHECK_EQUAL(y, 0u); LIBBOARDGAME_CHECK(read("j1", x, y)); LIBBOARDGAME_CHECK_EQUAL(x, 9u); LIBBOARDGAME_CHECK_EQUAL(y, 0u); LIBBOARDGAME_CHECK(read("ab1", x, y)); LIBBOARDGAME_CHECK_EQUAL(x, 27u); LIBBOARDGAME_CHECK_EQUAL(y, 0u); LIBBOARDGAME_CHECK(read(" a1", x, y)); LIBBOARDGAME_CHECK_EQUAL(x, 0u); LIBBOARDGAME_CHECK_EQUAL(y, 0u); LIBBOARDGAME_CHECK(! read("a 1", x, y)); LIBBOARDGAME_CHECK(! read("foobar", x, y)); LIBBOARDGAME_CHECK(! read("c3#", x, y)); } LIBBOARDGAME_TEST_CASE(boardgame_base_spreadsheet_string_rep_write) { LIBBOARDGAME_CHECK_EQUAL(string("a1"), write(0, 0)); LIBBOARDGAME_CHECK_EQUAL(string("ab1"), write(27, 0)); LIBBOARDGAME_CHECK_EQUAL(string("ba1"), write(52, 0)); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libboardgame_gtp/000077500000000000000000000000001227240712600207555ustar00rootroot00000000000000pentobi-7.2/src/unittest/libboardgame_gtp/ArgumentsTest.cpp000066400000000000000000000111641227240712600242710ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file ArgumentsTest.cpp*/ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include #include "libboardgame_gtp/Arguments.h" #include "libboardgame_test/Test.h" using namespace std; using namespace libboardgame_gtp; //----------------------------------------------------------------------------- LIBBOARDGAME_TEST_CASE(gtp_arguments_arg) { CmdLine line("command arg1 \"arg2 \" arg3 "); Arguments args(line); LIBBOARDGAME_CHECK_EQUAL("arg1", string(args.get(0))); LIBBOARDGAME_CHECK_EQUAL("arg2 ", string(args.get(1))); LIBBOARDGAME_CHECK_EQUAL("arg3", string(args.get(2))); } LIBBOARDGAME_TEST_CASE(gtp_arguments_to_lower) { CmdLine line("command cAsE"); Arguments args(line); LIBBOARDGAME_CHECK_EQUAL(string("case"), args.get_tolower(0)); } LIBBOARDGAME_TEST_CASE(gtp_arguments_bool) { { CmdLine line("command 0"); Arguments args(line); LIBBOARDGAME_CHECK(! args.parse(0)); } { CmdLine line("command 1"); Arguments args(line); LIBBOARDGAME_CHECK(args.parse(0)); } { CmdLine line("command 2"); Arguments args(line); LIBBOARDGAME_CHECK_THROW(args.parse(0), Failure); } { CmdLine line("command arg1"); Arguments args(line); LIBBOARDGAME_CHECK_THROW(args.parse(0), Failure); } { CmdLine line("command"); Arguments args(line); LIBBOARDGAME_CHECK_THROW(args.parse(0), Failure); } } LIBBOARDGAME_TEST_CASE(gtp_arguments_float) { CmdLine line("command abc 5.5"); Arguments args(line); LIBBOARDGAME_CHECK_THROW(args.parse(0), Failure); LIBBOARDGAME_CHECK_CLOSE(5.5f, args.parse(1), 1e-4); } LIBBOARDGAME_TEST_CASE(gtp_arguments_int) { CmdLine line("command 5 arg"); Arguments args(line); LIBBOARDGAME_CHECK_EQUAL(5, args.parse(0)); LIBBOARDGAME_CHECK_THROW(args.parse(1), Failure); } LIBBOARDGAME_TEST_CASE(gtp_arguments_min_int) { CmdLine line("command 5"); Arguments args(line); LIBBOARDGAME_CHECK_EQUAL(5, args.parse_min(0, 3)); LIBBOARDGAME_CHECK_THROW(args.parse_min(0, 7), Failure); } LIBBOARDGAME_TEST_CASE(gtp_arguments_min_max_int) { CmdLine line("command 5"); Arguments args(line); LIBBOARDGAME_CHECK_EQUAL(5, args.parse_min_max(0, 3, 10)); LIBBOARDGAME_CHECK_THROW(args.parse_min_max(0, 0, 4), Failure); LIBBOARDGAME_CHECK_THROW(args.parse_min_max(0, 10, 20), Failure); } LIBBOARDGAME_TEST_CASE(gtp_arguments_single_int) { { CmdLine line("command 5"); Arguments args(line); LIBBOARDGAME_CHECK_EQUAL(5, args.parse()); } { CmdLine line("command 5 10"); Arguments args(line); LIBBOARDGAME_CHECK_THROW(args.parse(), Failure); } } LIBBOARDGAME_TEST_CASE(gtp_arguments_nu_arg_0) { CmdLine line("1 command"); Arguments args(line); LIBBOARDGAME_CHECK_NO_THROW(args.check_empty()); LIBBOARDGAME_CHECK_THROW(args.check_size(1), Failure); LIBBOARDGAME_CHECK_NO_THROW(args.check_size_less_equal(2)); } LIBBOARDGAME_TEST_CASE(gtp_arguments_nu_arg_3) { CmdLine line("command arg1 arg2 arg3"); Arguments args(line); LIBBOARDGAME_CHECK_THROW(args.check_empty(), Failure); LIBBOARDGAME_CHECK_THROW(args.check_size(2), Failure); LIBBOARDGAME_CHECK_NO_THROW(args.check_size(3)); LIBBOARDGAME_CHECK_THROW(args.check_size(4), Failure); LIBBOARDGAME_CHECK_THROW(args.check_size_less_equal(2), Failure); LIBBOARDGAME_CHECK_NO_THROW(args.check_size_less_equal(3)); LIBBOARDGAME_CHECK_NO_THROW(args.check_size_less_equal(4)); } LIBBOARDGAME_TEST_CASE(gtp_arguments_remaining_arg) { CmdLine line("command arg1 arg2"); Arguments args(line); LIBBOARDGAME_CHECK_EQUAL("arg2", string(args.get_remaining_line(0))); } LIBBOARDGAME_TEST_CASE(gtp_arguments_remaining_arg_empty) { CmdLine line("command arg1"); Arguments args(line); LIBBOARDGAME_CHECK_EQUAL("", string(args.get_remaining_line(0))); } LIBBOARDGAME_TEST_CASE(gtp_arguments_remaining_line) { CmdLine line("command arg1 \"arg2 \" arg3 "); Arguments args(line); LIBBOARDGAME_CHECK_EQUAL("\"arg2 \" arg3", string(args.get_remaining_line(0))); LIBBOARDGAME_CHECK_EQUAL("arg3", string(args.get_remaining_line(1))); LIBBOARDGAME_CHECK_EQUAL("", string(args.get_remaining_line(2))); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libboardgame_gtp/CMakeLists.txt000066400000000000000000000011321227240712600235120ustar00rootroot00000000000000include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) link_directories(${Boost_LIBRARY_DIRS}) set(unittest_libboardgame_gtp_STAT_SRCS ArgumentsTest.cpp CmdLineTest.cpp EngineTest.cpp ResponseTest.cpp ) add_executable(unittest_libboardgame_gtp ${unittest_libboardgame_gtp_STAT_SRCS}) target_link_libraries(unittest_libboardgame_gtp boardgame_test_main boardgame_test boardgame_util boardgame_gtp ${Boost_THREAD_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ) add_test(libboardgame_gtp unittest_libboardgame_gtp) pentobi-7.2/src/unittest/libboardgame_gtp/CmdLineTest.cpp000066400000000000000000000042041227240712600236340ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file CmdLineTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "libboardgame_gtp/CmdLine.h" #include "libboardgame_test/Test.h" using namespace std; using namespace libboardgame_gtp; //----------------------------------------------------------------------------- namespace { string get_id(const CmdLine& c) { ostringstream s; c.write_id(s); return s.str(); } } //----------------------------------------------------------------------------- LIBBOARDGAME_TEST_CASE(gtp_cmd_line_init) { CmdLine c("100 command1 arg1 arg2"); LIBBOARDGAME_CHECK_EQUAL("100", get_id(c)); LIBBOARDGAME_CHECK_EQUAL("command1", string(c.get_name())); LIBBOARDGAME_CHECK_EQUAL(4u, c.get_elements().size()); LIBBOARDGAME_CHECK_EQUAL("arg1", string(c.get_element(2))); LIBBOARDGAME_CHECK_EQUAL("arg2", string(c.get_element(3))); c.init("2 command2 arg3"); LIBBOARDGAME_CHECK_EQUAL("2", get_id(c)); LIBBOARDGAME_CHECK_EQUAL("command2", string(c.get_name())); LIBBOARDGAME_CHECK_EQUAL(3u, c.get_elements().size()); LIBBOARDGAME_CHECK_EQUAL("arg3", string(c.get_element(2))); } LIBBOARDGAME_TEST_CASE(gtp_cmd_line_parse) { CmdLine c("10 boardsize 11"); LIBBOARDGAME_CHECK_EQUAL("10 boardsize 11", c.get_line()); LIBBOARDGAME_CHECK_EQUAL("11", string(c.get_trimmed_line_after_elem(1))); LIBBOARDGAME_CHECK_EQUAL("10", get_id(c)); LIBBOARDGAME_CHECK_EQUAL("boardsize", string(c.get_name())); LIBBOARDGAME_CHECK_EQUAL(3u, c.get_elements().size()); LIBBOARDGAME_CHECK_EQUAL("11", string(c.get_element(2))); c.init(" 20 clear_board "); LIBBOARDGAME_CHECK_EQUAL(" 20 clear_board ", c.get_line()); LIBBOARDGAME_CHECK_EQUAL("", string(c.get_trimmed_line_after_elem(1))); LIBBOARDGAME_CHECK_EQUAL("20", get_id(c)); LIBBOARDGAME_CHECK_EQUAL("clear_board", string(c.get_name())); LIBBOARDGAME_CHECK_EQUAL(2u, c.get_elements().size()); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libboardgame_gtp/EngineTest.cpp000066400000000000000000000077701227240712600235410ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file gtp/EngineTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "libboardgame_gtp/Engine.h" #include "libboardgame_test/Test.h" using namespace std; using namespace libboardgame_gtp; //----------------------------------------------------------------------------- namespace { //----------------------------------------------------------------------------- /** GTP engine returning invalid responses for testing class Engine. For testing that the base class Engine sanitizes responses of subclasses that contain empty lines (see @ref Engine::exec_main_loop). */ class InvalidResponseEngine : public Engine { public: InvalidResponseEngine(); ~InvalidResponseEngine() throw(); void invalid_response(const Arguments&, Response&); void invalid_response_2(const Arguments&, Response&); }; InvalidResponseEngine::InvalidResponseEngine() { add("invalid_response", &InvalidResponseEngine::invalid_response); add("invalid_response_2", &InvalidResponseEngine::invalid_response_2); } InvalidResponseEngine::~InvalidResponseEngine() throw() { } void InvalidResponseEngine::invalid_response(const Arguments&, Response& r) { r << "This response is invalid\n" << "\n" << "because it contains an empty line"; } void InvalidResponseEngine::invalid_response_2(const Arguments&, Response& r) { r << "This response is invalid\n" << "\n" << "\n" << "because it contains two empty lines"; } //----------------------------------------------------------------------------- void cmd_foo(Response& response) { response << "foo"; } //----------------------------------------------------------------------------- } // namespace //----------------------------------------------------------------------------- LIBBOARDGAME_TEST_CASE(gtp_engine_command) { istringstream in("version\n"); ostringstream out; Engine engine; engine.exec_main_loop_st(in, out); LIBBOARDGAME_CHECK_EQUAL(string("= \n\n"), out.str()); } LIBBOARDGAME_TEST_CASE(gtp_engine_command_with_id) { istringstream in("10 version\n"); ostringstream out; Engine engine; engine.exec_main_loop_st(in, out); LIBBOARDGAME_CHECK_EQUAL(string("=10 \n\n"), out.str()); } /** Check that invalid responses with one empty line are sanitized. */ LIBBOARDGAME_TEST_CASE(gtp_engine_empty_lines) { istringstream in("invalid_response\n"); ostringstream out; InvalidResponseEngine engine; engine.exec_main_loop_st(in, out); LIBBOARDGAME_CHECK_EQUAL(string("= This response is invalid\n" " \n" "because it contains an empty line\n" "\n"), out.str()); } /** Check that invalid responses with two empty lines are sanitized. */ LIBBOARDGAME_TEST_CASE(gtp_engine_empty_lines_2) { istringstream in("invalid_response_2\n"); ostringstream out; InvalidResponseEngine engine; engine.exec_main_loop_st(in, out); LIBBOARDGAME_CHECK_EQUAL(string("= This response is invalid\n" " \n" " \n" "because it contains two empty lines\n" "\n"), out.str()); } /** Check replacing an existing command by calling add() with a new handler. */ LIBBOARDGAME_TEST_CASE(gtp_engine_replace_cmd) { Engine engine; engine.add("version", cmd_foo); LIBBOARDGAME_CHECK_EQUAL(engine.exec("version"), "foo"); } LIBBOARDGAME_TEST_CASE(gtp_engine_unknown_command) { istringstream in("unknowncommand\n"); ostringstream out; Engine engine; engine.exec_main_loop_st(in, out); LIBBOARDGAME_CHECK(out.str().size() >= 2); LIBBOARDGAME_CHECK_EQUAL(string("? "), out.str().substr(0, 2)); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libboardgame_gtp/ResponseTest.cpp000066400000000000000000000013651227240712600241240ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file ResponseTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "libboardgame_gtp/Response.h" #include "libboardgame_test/Test.h" using namespace std; using namespace libboardgame_gtp; //----------------------------------------------------------------------------- LIBBOARDGAME_TEST_CASE(gtp_response_basic) { Response r; r << "Name"; LIBBOARDGAME_CHECK_EQUAL(string("Name"), r.to_string()); r.set("Name2"); LIBBOARDGAME_CHECK_EQUAL(string("Name2"), r.to_string()); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libboardgame_mcts/000077500000000000000000000000001227240712600211315ustar00rootroot00000000000000pentobi-7.2/src/unittest/libboardgame_mcts/CMakeLists.txt000066400000000000000000000007131227240712600236720ustar00rootroot00000000000000include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) link_directories(${Boost_LIBRARY_DIRS}) set(unittest_libboardgame_mcts_STAT_SRCS NodeTest.cpp ) add_executable(unittest_libboardgame_mcts ${unittest_libboardgame_mcts_STAT_SRCS}) target_link_libraries(unittest_libboardgame_mcts boardgame_test_main boardgame_test boardgame_sgf boardgame_util boardgame_sys ) add_test(libboardgame_mcts unittest_libboardgame_mcts) pentobi-7.2/src/unittest/libboardgame_mcts/NodeTest.cpp000066400000000000000000000013031227240712600233570ustar00rootroot00000000000000//----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "libboardgame_mcts/Node.h" #include #include #include "libboardgame_test/Test.h" using namespace std; //----------------------------------------------------------------------------- LIBBOARDGAME_TEST_CASE(mcts_node_add_value) { libboardgame_mcts::Node node; node.init(0, 0.5, 0); node.add_value(5); LIBBOARDGAME_CHECK_CLOSE(node.get_value(), 5., 1e-4); node.add_value(2); LIBBOARDGAME_CHECK_CLOSE(node.get_value(), 3.5, 1e-4); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libboardgame_sgf/000077500000000000000000000000001227240712600207425ustar00rootroot00000000000000pentobi-7.2/src/unittest/libboardgame_sgf/CMakeLists.txt000066400000000000000000000007601227240712600235050ustar00rootroot00000000000000include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) link_directories(${Boost_LIBRARY_DIRS}) set(unittest_libboardgame_sgf_STAT_SRCS NodeTest.cpp TreeIteratorTest.cpp TreeReaderTest.cpp UtilTest.cpp ) add_executable(unittest_libboardgame_sgf ${unittest_libboardgame_sgf_STAT_SRCS}) target_link_libraries(unittest_libboardgame_sgf boardgame_test_main boardgame_test boardgame_sgf boardgame_util ) add_test(libboardgame_sgf unittest_libboardgame_sgf) pentobi-7.2/src/unittest/libboardgame_sgf/NodeTest.cpp000066400000000000000000000023671227240712600232030ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file unittest/libboardgame_sgf/NodeTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include #include "libboardgame_sgf/Node.h" #include "libboardgame_test/Test.h" using namespace std; using namespace libboardgame_sgf; //----------------------------------------------------------------------------- LIBBOARDGAME_TEST_CASE(sgf_node_create_new_child) { unique_ptr parent(new Node()); auto& child = parent->create_new_child(); LIBBOARDGAME_CHECK_EQUAL(&parent->get_child(), &child); LIBBOARDGAME_CHECK_EQUAL(&child.get_parent(), parent.get()); } LIBBOARDGAME_TEST_CASE(sgf_node_remove_property) { string id = "B"; unique_ptr node(new Node()); LIBBOARDGAME_CHECK(! node->has_property(id)); node->set_property(id, "foo"); LIBBOARDGAME_CHECK(node->has_property(id)); LIBBOARDGAME_CHECK_EQUAL(node->get_property(id), "foo"); bool result = node->remove_property(id); LIBBOARDGAME_CHECK(result); LIBBOARDGAME_CHECK(! node->has_property(id)); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libboardgame_sgf/TreeIteratorTest.cpp000066400000000000000000000026611227240712600247240ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file unittest/libboardgame_sgf/TreeIteratorTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "libboardgame_sgf/Tree.h" #include "libboardgame_sgf/TreeIterator.h" #include "libboardgame_test/Test.h" using namespace libboardgame_sgf; //----------------------------------------------------------------------------- LIBBOARDGAME_TEST_CASE(boardgamesgf_tree_iterator_1) { Tree tree; auto& root = tree.get_root(); TreeIterator i(root); LIBBOARDGAME_CHECK(i); LIBBOARDGAME_CHECK_EQUAL(&(*i), &root); ++i; LIBBOARDGAME_CHECK(! i); } LIBBOARDGAME_TEST_CASE(boardgamesgf_tree_iterator_2) { Tree tree; auto& root = tree.get_root(); auto& child1 = tree.create_new_child(root); auto& child2 = tree.create_new_child(root); auto& child3 = tree.create_new_child(child1); TreeIterator i(root); LIBBOARDGAME_CHECK(i); LIBBOARDGAME_CHECK_EQUAL(&(*i), &root); ++i; LIBBOARDGAME_CHECK(i); LIBBOARDGAME_CHECK_EQUAL(&(*i), &child1); ++i; LIBBOARDGAME_CHECK(i); LIBBOARDGAME_CHECK_EQUAL(&(*i), &child3); ++i; LIBBOARDGAME_CHECK(i); LIBBOARDGAME_CHECK_EQUAL(&(*i), &child2); ++i; LIBBOARDGAME_CHECK(! i); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libboardgame_sgf/TreeReaderTest.cpp000066400000000000000000000075011227240712600243330ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file unittest/libboardgame_sgf/TreeReaderTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "libboardgame_sgf/TreeReader.h" #include #include "libboardgame_sgf/TreeWriter.h" #include "libboardgame_test/Test.h" using namespace std; using namespace libboardgame_sgf; //----------------------------------------------------------------------------- LIBBOARDGAME_TEST_CASE(sgf_tree_reader_basic) { istringstream in("(;B[aa];W[bb])"); TreeReader reader; reader.read(in); auto& root = reader.get_tree(); LIBBOARDGAME_CHECK(root.has_property("B")); LIBBOARDGAME_CHECK(root.has_single_child()); auto& child = root.get_child(); LIBBOARDGAME_CHECK(child.has_property("W")); LIBBOARDGAME_CHECK(! child.has_children()); } LIBBOARDGAME_TEST_CASE(sgf_tree_reader_basic_2) { istringstream in("(;C[1](;C[2.1])(;C[2.2]))"); TreeReader reader; reader.read(in); auto& root = reader.get_tree(); LIBBOARDGAME_CHECK_EQUAL(root.get_property("C"), "1"); LIBBOARDGAME_CHECK_EQUAL(root.get_nu_children(), 2u); LIBBOARDGAME_CHECK_EQUAL(root.get_child(0).get_property("C"), "2.1"); LIBBOARDGAME_CHECK_EQUAL(root.get_child(1).get_property("C"), "2.2"); } /** Test that a property value with a unicode character is preserved after reading and writing. In previous versions this was broken because of a bug in the replacement of non-newline whitespaces (as required by SGF) by the writer. (The bug occurred only on some platforms depending on the std::isspace() implementation.) */ LIBBOARDGAME_TEST_CASE(sgf_tree_reader_unicode) { Node root; const char* id = "C"; const char* value = "\xc3\xbc"; // German u-umlaut as UTF-8 root.set_property(id, value); ostringstream out; TreeWriter writer(out, root); writer.write(); istringstream in(out.str()); TreeReader reader; reader.read(in); LIBBOARDGAME_CHECK_EQUAL(reader.get_tree().get_property(id), value); } LIBBOARDGAME_TEST_CASE(sgf_tree_reader_property_after_newline) { istringstream in("(;FF[4]\n" "CA[UTF-8])"); TreeReader reader; reader.read(in); auto& root = reader.get_tree(); LIBBOARDGAME_CHECK(root.has_property("FF")); LIBBOARDGAME_CHECK(root.has_property("CA")); } /** Test cross-platform handling of property values containing newlines. The reader should convert all platform-dependent newline sequences (LF, CR+LF, CR) into LF, such that property values containing newlines are independent on the platform that was used to write the file. */ LIBBOARDGAME_TEST_CASE(sgf_tree_reader_newline) { { istringstream in("(;C[1\n2])"); TreeReader reader; reader.read(in); auto& root = reader.get_tree(); LIBBOARDGAME_CHECK_EQUAL(root.get_property("C"), "1\n2"); } { istringstream in("(;C[1\r\n2])"); TreeReader reader; reader.read(in); auto& root = reader.get_tree(); LIBBOARDGAME_CHECK_EQUAL(root.get_property("C"), "1\n2"); } { istringstream in("(;C[1\r2])"); TreeReader reader; reader.read(in); auto& root = reader.get_tree(); LIBBOARDGAME_CHECK_EQUAL(root.get_property("C"), "1\n2"); } } LIBBOARDGAME_TEST_CASE(sgf_tree_reader_property_without_value) { istringstream in("(;B)"); TreeReader reader; LIBBOARDGAME_CHECK_THROW(reader.read(in), TreeReader::ReadError); } LIBBOARDGAME_TEST_CASE(sgf_tree_reader_text_before_node) { istringstream in("(B;)"); TreeReader reader; LIBBOARDGAME_CHECK_THROW(reader.read(in), TreeReader::ReadError); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libboardgame_sgf/UtilTest.cpp000066400000000000000000000016601227240712600232260ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file unittest/libboardgame_sgf/UtilTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "libboardgame_sgf/Util.h" #include "libboardgame_test/Test.h" using namespace std; using namespace libboardgame_sgf; using namespace libboardgame_sgf::util; //----------------------------------------------------------------------------- LIBBOARDGAME_TEST_CASE(sgf_util_get_path_from_root) { unique_ptr root(new Node()); auto& child = root->create_new_child(); vector path; get_path_from_root(child, path); LIBBOARDGAME_CHECK_EQUAL(path.size(), 2u); LIBBOARDGAME_CHECK_EQUAL(path[0], root.get()); LIBBOARDGAME_CHECK_EQUAL(path[1], &child); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libboardgame_util/000077500000000000000000000000001227240712600211405ustar00rootroot00000000000000pentobi-7.2/src/unittest/libboardgame_util/ArrayListTest.cpp000066400000000000000000000043311227240712600244170ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file ArrayListTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include #include "libboardgame_util/ArrayList.h" #include "libboardgame_test/Test.h" using namespace std; using namespace libboardgame_util; //----------------------------------------------------------------------------- LIBBOARDGAME_TEST_CASE(util_array_list_basic) { ArrayList l; LIBBOARDGAME_CHECK_EQUAL(0u, l.size()); LIBBOARDGAME_CHECK(l.empty()); l.push_back(5); LIBBOARDGAME_CHECK_EQUAL(1u, l.size()); LIBBOARDGAME_CHECK(! l.empty()); LIBBOARDGAME_CHECK_EQUAL(5, l[0]); l.push_back(7); LIBBOARDGAME_CHECK_EQUAL(2u, l.size()); LIBBOARDGAME_CHECK(! l.empty()); LIBBOARDGAME_CHECK_EQUAL(5, l[0]); LIBBOARDGAME_CHECK_EQUAL(7, l[1]); l.clear(); LIBBOARDGAME_CHECK_EQUAL(0u, l.size()); LIBBOARDGAME_CHECK(l.empty()); } LIBBOARDGAME_TEST_CASE(util_array_list_construct_single_element) { ArrayList l(5); LIBBOARDGAME_CHECK_EQUAL(1u, l.size()); LIBBOARDGAME_CHECK_EQUAL(5, l[0]); } LIBBOARDGAME_TEST_CASE(util_array_list_equals) { ArrayList l1; l1.push_back(1); l1.push_back(2); l1.push_back(3); ArrayList l2; l2.push_back(1); l2.push_back(2); l2.push_back(3); LIBBOARDGAME_CHECK(l1 == l2); l2.push_back(4); LIBBOARDGAME_CHECK(! (l1 == l2)); l2.clear(); l2.push_back(2); l2.push_back(1); l2.push_back(3); LIBBOARDGAME_CHECK(! (l1 == l2)); } LIBBOARDGAME_TEST_CASE(util_array_list_pop_back) { ArrayList l(5); int i = l.pop_back(); LIBBOARDGAME_CHECK_EQUAL(5, i); LIBBOARDGAME_CHECK(l.empty()); } LIBBOARDGAME_TEST_CASE(util_array_list_remove) { ArrayList l; l.push_back(1); l.push_back(2); l.push_back(3); l.push_back(4); l.remove(2); LIBBOARDGAME_CHECK_EQUAL(3u, l.size()); LIBBOARDGAME_CHECK_EQUAL(1, l[0]); LIBBOARDGAME_CHECK_EQUAL(3, l[1]); LIBBOARDGAME_CHECK_EQUAL(4, l[2]); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libboardgame_util/CMakeLists.txt000066400000000000000000000010201227240712600236710ustar00rootroot00000000000000include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) link_directories(${Boost_LIBRARY_DIRS}) set(unittest_libboardgame_util_STAT_SRCS ArrayListTest.cpp FastLogTest.cpp NullTermListTest.cpp OptionsTest.cpp StringUtilTest.cpp ) add_executable(unittest_libboardgame_util ${unittest_libboardgame_util_STAT_SRCS}) target_link_libraries(unittest_libboardgame_util boardgame_test_main boardgame_test boardgame_util boardgame_sys ) add_test(libboardgame_util unittest_libboardgame_util) pentobi-7.2/src/unittest/libboardgame_util/FastLogTest.cpp000066400000000000000000000016251227240712600240470ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file FastLogTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include #include #include "libboardgame_util/FastLog.h" #include "libboardgame_test/Test.h" using namespace std; using namespace libboardgame_util; //---------------------------------------------------------------------------- LIBBOARDGAME_TEST_CASE(util_fast_log_basic) { FastLog fast_log(10); float epsilon = 0.1f; LIBBOARDGAME_CHECK_CLOSE(fast_log.get_log(1), 0.f, epsilon); LIBBOARDGAME_CHECK_CLOSE(fast_log.get_log(exp(1.f)), 1.f, epsilon); LIBBOARDGAME_CHECK_CLOSE(fast_log.get_log(pow(exp(1.f), 2.f)), 2.f, epsilon); } //---------------------------------------------------------------------------- pentobi-7.2/src/unittest/libboardgame_util/NullTermListTest.cpp000066400000000000000000000016021227240712600251010ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file NullTermListTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "libboardgame_test/Test.h" #include "libboardgame_util/NullTermList.h" using namespace std; using namespace libboardgame_util; //----------------------------------------------------------------------------- LIBBOARDGAME_TEST_CASE(util_null_term_list_empty) { NullTermList list; { NullTermList::Init init(list); init.finish(); } LIBBOARDGAME_CHECK(list.empty()); { NullTermList::Init init(list); init.push_back(5); init.finish(); } LIBBOARDGAME_CHECK(! list.empty()); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libboardgame_util/OptionsTest.cpp000066400000000000000000000062151227240712600241430ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file unittest/libboardgame_util/OptionsTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "libboardgame_util/Options.h" #include "libboardgame_test/Test.h" using namespace std; using namespace libboardgame_util; //----------------------------------------------------------------------------- LIBBOARDGAME_TEST_CASE(libboardgame_util_options_basic) { vector specs; specs.push_back("first|a:"); specs.push_back("second|b:"); specs.push_back("third|c"); specs.push_back("fourth"); specs.push_back("fifth"); const char* argv[] = { nullptr, "--second", "secondval", "--first", "firstval", "--fourth", "-c", "arg1", "arg2" }; int argc = sizeof(argv) / sizeof(argv[0]); Options opt(argc, argv, specs); LIBBOARDGAME_CHECK(opt.contains("first")); LIBBOARDGAME_CHECK_EQUAL(opt.get("first"), "firstval"); LIBBOARDGAME_CHECK(opt.contains("second")); LIBBOARDGAME_CHECK_EQUAL(opt.get("second"), "secondval"); LIBBOARDGAME_CHECK(opt.contains("third")); LIBBOARDGAME_CHECK(opt.contains("fourth")); LIBBOARDGAME_CHECK(! opt.contains("fifth")); auto& args = opt.get_args(); LIBBOARDGAME_CHECK_EQUAL(args.size(), 2u); LIBBOARDGAME_CHECK_EQUAL(args[0], "arg1"); LIBBOARDGAME_CHECK_EQUAL(args[1], "arg2"); } LIBBOARDGAME_TEST_CASE(libboardgame_util_options_end_options) { vector specs; specs.push_back("first:"); const char* argv[] = { nullptr, "--first", "firstval", "--", "--arg1" }; int argc = sizeof(argv) / sizeof(argv[0]); Options opt(argc, argv, specs); LIBBOARDGAME_CHECK_EQUAL(opt.get("first"), "firstval"); auto& args = opt.get_args(); LIBBOARDGAME_CHECK_EQUAL(args.size(), 1u); LIBBOARDGAME_CHECK_EQUAL(args[0], "--arg1"); } LIBBOARDGAME_TEST_CASE(libboardgame_util_options_nospace) { vector specs; specs.push_back("first|a:"); specs.push_back("second|b:"); const char* argv[] = { nullptr, "-abc" }; int argc = sizeof(argv) / sizeof(argv[0]); Options opt(argc, argv, specs); LIBBOARDGAME_CHECK_EQUAL(opt.get("first"), "bc"); } LIBBOARDGAME_TEST_CASE(libboardgame_util_options_multi_short_with_val) { vector specs; specs.push_back("first|a"); specs.push_back("second|b:"); const char* argv[] = { nullptr, "-ab", "c" }; int argc = sizeof(argv) / sizeof(argv[0]); Options opt(argc, argv, specs); LIBBOARDGAME_CHECK(opt.contains("first")); LIBBOARDGAME_CHECK_EQUAL(opt.get("second"), "c"); } LIBBOARDGAME_TEST_CASE(libboardgame_util_options_type) { vector specs; specs.push_back("first:"); specs.push_back("second:"); const char* argv[] = { nullptr, "--first", "10", "--second", "foo" }; int argc = sizeof(argv) / sizeof(argv[0]); Options opt(argc, argv, specs); LIBBOARDGAME_CHECK_EQUAL(opt.get("first"), 10); LIBBOARDGAME_CHECK_THROW(opt.get("second"), Exception); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libboardgame_util/StringUtilTest.cpp000066400000000000000000000067001227240712600246130ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file StringUtilTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "libboardgame_util/StringUtil.h" #include "libboardgame_test/Test.h" using namespace std; using namespace libboardgame_util; //---------------------------------------------------------------------------- LIBBOARDGAME_TEST_CASE(libboardgame_util_get_letter_coord) { LIBBOARDGAME_CHECK_EQUAL(get_letter_coord(0), "a"); LIBBOARDGAME_CHECK_EQUAL(get_letter_coord(1), "b"); LIBBOARDGAME_CHECK_EQUAL(get_letter_coord(25), "z"); LIBBOARDGAME_CHECK_EQUAL(get_letter_coord(26), "aa"); LIBBOARDGAME_CHECK_EQUAL(get_letter_coord(26 + 1), "ab"); LIBBOARDGAME_CHECK_EQUAL(get_letter_coord(26 + 25), "az"); LIBBOARDGAME_CHECK_EQUAL(get_letter_coord(2 * 26), "ba"); LIBBOARDGAME_CHECK_EQUAL(get_letter_coord(2 * 26 + 1), "bb"); LIBBOARDGAME_CHECK_EQUAL(get_letter_coord(2 * 26 + 25), "bz"); LIBBOARDGAME_CHECK_EQUAL(get_letter_coord(26 * 26), "za"); LIBBOARDGAME_CHECK_EQUAL(get_letter_coord(26 * 26 + 1), "zb"); LIBBOARDGAME_CHECK_EQUAL(get_letter_coord(26 * 26 + 25), "zz"); LIBBOARDGAME_CHECK_EQUAL(get_letter_coord(27 * 26), "aaa"); LIBBOARDGAME_CHECK_EQUAL(get_letter_coord(27 * 26 + 1), "aab"); LIBBOARDGAME_CHECK_EQUAL(get_letter_coord(27 * 26 + 25), "aaz"); LIBBOARDGAME_CHECK_EQUAL(get_letter_coord(28 * 26), "aba"); LIBBOARDGAME_CHECK_EQUAL(get_letter_coord(28 * 26 + 1), "abb"); LIBBOARDGAME_CHECK_EQUAL(get_letter_coord(28 * 26 + 25), "abz"); } LIBBOARDGAME_TEST_CASE(libboardgame_util_split) { { vector v = split("a,b,cc,d", ','); LIBBOARDGAME_CHECK_EQUAL(v.size(), 4u); LIBBOARDGAME_CHECK_EQUAL(v[0], "a"); LIBBOARDGAME_CHECK_EQUAL(v[1], "b"); LIBBOARDGAME_CHECK_EQUAL(v[2], "cc"); LIBBOARDGAME_CHECK_EQUAL(v[3], "d"); } { vector v = split("", ','); LIBBOARDGAME_CHECK_EQUAL(v.size(), 0u); } { vector v = split("a,", ','); LIBBOARDGAME_CHECK_EQUAL(v.size(), 2u); LIBBOARDGAME_CHECK_EQUAL(v[0], "a"); LIBBOARDGAME_CHECK_EQUAL(v[1], ""); } { vector v = split(",a", ','); LIBBOARDGAME_CHECK_EQUAL(v.size(), 2u); LIBBOARDGAME_CHECK_EQUAL(v[0], ""); LIBBOARDGAME_CHECK_EQUAL(v[1], "a"); } { vector v = split("a,,b", ','); LIBBOARDGAME_CHECK_EQUAL(v.size(), 3u); LIBBOARDGAME_CHECK_EQUAL(v[0], "a"); LIBBOARDGAME_CHECK_EQUAL(v[1], ""); LIBBOARDGAME_CHECK_EQUAL(v[2], "b"); } } LIBBOARDGAME_TEST_CASE(libboardgame_util_to_lower) { LIBBOARDGAME_CHECK_EQUAL(to_lower("AabC "), "aabc "); } LIBBOARDGAME_TEST_CASE(libboardgame_util_trim) { LIBBOARDGAME_CHECK_EQUAL(trim("aa bb"), "aa bb"); LIBBOARDGAME_CHECK_EQUAL(trim(" \t\r\naa bb"), "aa bb"); LIBBOARDGAME_CHECK_EQUAL(trim("aa bb \t\r\n"), "aa bb"); LIBBOARDGAME_CHECK_EQUAL(trim(""), ""); } LIBBOARDGAME_TEST_CASE(libboardgame_util_trim_right) { LIBBOARDGAME_CHECK_EQUAL(trim_right("aa bb"), "aa bb"); LIBBOARDGAME_CHECK_EQUAL(trim_right(" \t\r\naa bb"), " \t\r\naa bb"); LIBBOARDGAME_CHECK_EQUAL(trim_right("aa bb \t\r\n"), "aa bb"); LIBBOARDGAME_CHECK_EQUAL(trim_right(""), ""); } //---------------------------------------------------------------------------- pentobi-7.2/src/unittest/libpentobi_base/000077500000000000000000000000001227240712600206145ustar00rootroot00000000000000pentobi-7.2/src/unittest/libpentobi_base/BoardConstTest.cpp000066400000000000000000000020161227240712600242150ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file unittest/libpentobi_base/BoardConstTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "libpentobi_base/BoardConst.h" #include "libboardgame_test/Test.h" using namespace std; using namespace libpentobi_base; //----------------------------------------------------------------------------- /** Check symmetry information in MoveInfoExt for some moves. */ LIBBOARDGAME_TEST_CASE(pentobi_base_board_const_symmetry_info) { auto& bc = BoardConst::get(Variant::trigon_2); auto& info_ext_2 = bc.get_move_info_ext_2(bc.from_string("q9,q10,r10,q11,r11,s11")); LIBBOARDGAME_CHECK(! info_ext_2.breaks_symmetry); LIBBOARDGAME_CHECK_EQUAL(info_ext_2.symmetric_move.to_int(), bc.from_string("q8,r8,s8,r9,s9,s10").to_int()); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libpentobi_base/BoardTest.cpp000066400000000000000000000174051227240712600232160ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file unittest/libpentobi_base/BoardTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "libpentobi_base/Board.h" #include "libboardgame_test/Test.h" #include "libpentobi_base/MoveList.h" #include "libpentobi_base/MoveMarker.h" using namespace std; using namespace libpentobi_base; //----------------------------------------------------------------------------- /** Check some basic functions in a Classic Two-Player game. */ LIBBOARDGAME_TEST_CASE(pentobi_base_board_classic_2) { /* ( ;GM[Blokus Two-Player] ;1[a20,b20,c20,d20,e20] ;2[q20,r20,s20,t20] ;3[p1,q1,r1,s1,t1] ;4[a1,b1,c1,d1] ;1[f19,g19,h19,i19] ;2[o19,p19] ;3[m1,l2,m2,n2,o2] ;4[e2,f2] ;1[j18,k18,l18,l19,m19] ;2[n20] ;3[h2,i2,i3,j3,k3] ;4[g1] ;1[o17,n18,o18,p18,q18] ;3[d2,d3,e3,f3,g3] ;1[n13,o13,n14,n15,n16] ;3[p3,p4,p5,p6] ;1[n10,n11,o11,p11,p12] ;3[l4,m4,m5,n5] ;1[o7,p7,q7,o8,o9] ;3[j5,k5] ;1[l6,m6,n6,m7,m8] ;3[a3,a4,b4,c4] ;1[i6,j6,j7,k7,j8] ;3[d5,e5,f5] ;1[g6,f7,g7,h7] ;3[j1] ;1[c6,d6,e6,c7] ;1[a8,b8,b9,c9] ;1[d10,e10,d11,e11] ;1[f9,g9,h9] ;1[r4,s4,r5,r6,s6] ;1[t7,s8,t8,r9,s9] ;1[q13,r13,p14,q14,r14] ;1[s16,r17,s17,t17,s18] ;1[l9,k10,l10] ;1[j11,j12] ;1[i10] ) */ unique_ptr bd(new Board(Variant::classic_2)); bd->play(Color(0), bd->from_string("a20,b20,c20,d20,e20")); bd->play(Color(1), bd->from_string("q20,r20,s20,t20")); bd->play(Color(2), bd->from_string("p1,q1,r1,s1,t1")); bd->play(Color(3), bd->from_string("a1,b1,c1,d1")); bd->play(Color(0), bd->from_string("f19,g19,h19,i19")); bd->play(Color(1), bd->from_string("o19,p19")); bd->play(Color(2), bd->from_string("m1,l2,m2,n2,o2")); bd->play(Color(3), bd->from_string("e2,f2")); bd->play(Color(0), bd->from_string("j18,k18,l18,l19,m19")); bd->play(Color(1), bd->from_string("n20")); bd->play(Color(2), bd->from_string("h2,i2,i3,j3,k3")); bd->play(Color(3), bd->from_string("g1")); bd->play(Color(0), bd->from_string("o17,n18,o18,p18,q18")); bd->play(Color(2), bd->from_string("d2,d3,e3,f3,g3")); bd->play(Color(0), bd->from_string("n13,o13,n14,n15,n16")); bd->play(Color(2), bd->from_string("p3,p4,p5,p6")); bd->play(Color(0), bd->from_string("n10,n11,o11,p11,p12")); bd->play(Color(2), bd->from_string("l4,m4,m5,n5")); bd->play(Color(0), bd->from_string("o7,p7,q7,o8,o9")); bd->play(Color(2), bd->from_string("j5,k5")); bd->play(Color(0), bd->from_string("l6,m6,n6,m7,m8")); bd->play(Color(2), bd->from_string("a3,a4,b4,c4")); bd->play(Color(0), bd->from_string("i6,j6,j7,k7,j8")); bd->play(Color(2), bd->from_string("d5,e5,f5")); bd->play(Color(0), bd->from_string("g6,f7,g7,h7")); bd->play(Color(2), bd->from_string("j1")); bd->play(Color(0), bd->from_string("c6,d6,e6,c7")); bd->play(Color(0), bd->from_string("a8,b8,b9,c9")); bd->play(Color(0), bd->from_string("d10,e10,d11,e11")); bd->play(Color(0), bd->from_string("f9,g9,h9")); bd->play(Color(0), bd->from_string("r4,s4,r5,r6,s6")); bd->play(Color(0), bd->from_string("t7,s8,t8,r9,s9")); bd->play(Color(0), bd->from_string("q13,r13,p14,q14,r14")); bd->play(Color(0), bd->from_string("s16,r17,s17,t17,s18")); bd->play(Color(0), bd->from_string("l9,k10,l10")); bd->play(Color(0), bd->from_string("j11,j12")); bd->play(Color(0), bd->from_string("i10")); LIBBOARDGAME_CHECK_EQUAL(bd->get_nu_moves(), 37u); LIBBOARDGAME_CHECK_EQUAL(bd->get_points(Color(0)), 89u); LIBBOARDGAME_CHECK_EQUAL(bd->get_points(Color(1)), 7u); LIBBOARDGAME_CHECK_EQUAL(bd->get_points(Color(2)), 38u); LIBBOARDGAME_CHECK_EQUAL(bd->get_points(Color(3)), 7u); LIBBOARDGAME_CHECK_EQUAL(bd->get_bonus(Color(0)), 20u); LIBBOARDGAME_CHECK_EQUAL(bd->get_bonus(Color(1)), 0u); LIBBOARDGAME_CHECK_EQUAL(bd->get_bonus(Color(2)), 0u); LIBBOARDGAME_CHECK_EQUAL(bd->get_bonus(Color(3)), 0u); LIBBOARDGAME_CHECK_EQUAL(bd->get_points_with_bonus(Color(0)), 109u); LIBBOARDGAME_CHECK_EQUAL(bd->get_points_with_bonus(Color(1)), 7u); LIBBOARDGAME_CHECK_EQUAL(bd->get_points_with_bonus(Color(2)), 38u); LIBBOARDGAME_CHECK_EQUAL(bd->get_points_with_bonus(Color(3)), 7u); LIBBOARDGAME_CHECK_EQUAL(bd->get_score(Color(0)), 133); LIBBOARDGAME_CHECK_EQUAL(bd->get_score(Color(1)), -133); LIBBOARDGAME_CHECK_EQUAL(bd->get_score(Color(2)), 133); LIBBOARDGAME_CHECK_EQUAL(bd->get_score(Color(3)), -133); LIBBOARDGAME_CHECK_EQUAL(bd->get_nu_onboard_pieces(Color(0)), 21u); LIBBOARDGAME_CHECK_EQUAL(bd->get_nu_onboard_pieces(Color(1)), 3u); LIBBOARDGAME_CHECK_EQUAL(bd->get_nu_onboard_pieces(Color(2)), 10u); LIBBOARDGAME_CHECK_EQUAL(bd->get_nu_onboard_pieces(Color(3)), 3u); // Make sure that bonus computation still works if after the 1-piece an // additional pass move was played bd->play_pass(Color(0)); LIBBOARDGAME_CHECK_EQUAL(bd->get_bonus(Color(0)), 20u); LIBBOARDGAME_CHECK_EQUAL(bd->get_bonus(Color(1)), 0u); LIBBOARDGAME_CHECK_EQUAL(bd->get_bonus(Color(2)), 0u); LIBBOARDGAME_CHECK_EQUAL(bd->get_bonus(Color(3)), 0u); LIBBOARDGAME_CHECK_EQUAL(bd->get_points_with_bonus(Color(0)), 109u); LIBBOARDGAME_CHECK_EQUAL(bd->get_points_with_bonus(Color(1)), 7u); LIBBOARDGAME_CHECK_EQUAL(bd->get_points_with_bonus(Color(2)), 38u); LIBBOARDGAME_CHECK_EQUAL(bd->get_points_with_bonus(Color(3)), 7u); } /** Check the number of generated moves at each attach point in a known case. */ LIBBOARDGAME_TEST_CASE(pentobi_base_board_gen_moves) { unique_ptr bd(new Board(Variant::classic)); Color c(0); bd->play(c, bd->from_string("i11,j11,k11,l11")); unique_ptr moves(new MoveList()); unique_ptr marker(new MoveMarker()); for (Point p : bd->get_attach_points(c)) { bd->gen_moves(c, p, *marker, *moves); LIBBOARDGAME_CHECK_EQUAL(moves->size(), 124u); marker->clear_all_set_known(*moves); moves->clear(); } } /** Check the number of generated moves at a starting point. */ LIBBOARDGAME_TEST_CASE(pentobi_base_board_gen_moves_2) { unique_ptr bd(new Board(Variant::classic)); unique_ptr moves(new MoveList()); unique_ptr marker(new MoveMarker()); bd->gen_moves(Color(0), Point("A20"), *marker, *moves); LIBBOARDGAME_CHECK_EQUAL(moves->size(), 58u); } /** Test get_place() in a 4-color, 2-player game when the player 1 has a higher score but color 1 has less points than color 2. */ LIBBOARDGAME_TEST_CASE(pentobi_base_board_get_place) { unique_ptr bd(new Board(Variant::classic_2)); bd->play(Color(0), bd->from_string("a20,b20")); bd->play(Color(1), bd->from_string("r20,s20,t20")); bd->play(Color(2), bd->from_string("q1,r1,s1,t1")); bd->play(Color(3), bd->from_string("a1,b1")); // Not a final position but Board::get_place() should not care about that unsigned place; bool isPlaceShared; bd->get_place(Color(0), place, isPlaceShared); LIBBOARDGAME_CHECK_EQUAL(place, 0u); LIBBOARDGAME_CHECK(! isPlaceShared); bd->get_place(Color(1), place, isPlaceShared); LIBBOARDGAME_CHECK_EQUAL(place, 1u); LIBBOARDGAME_CHECK(! isPlaceShared); bd->get_place(Color(2), place, isPlaceShared); LIBBOARDGAME_CHECK_EQUAL(place, 0u); LIBBOARDGAME_CHECK(! isPlaceShared); bd->get_place(Color(3), place, isPlaceShared); LIBBOARDGAME_CHECK_EQUAL(place, 1u); LIBBOARDGAME_CHECK(! isPlaceShared); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libpentobi_base/BoardUpdaterTest.cpp000066400000000000000000000100751227240712600245370ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file unittest/libpentobi_base/BoardUpdaterTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "libpentobi_base/BoardUpdater.h" #include "libboardgame_sgf/TreeReader.h" #include "libboardgame_sgf/Util.h" #include "libboardgame_test/Test.h" using namespace std; using namespace libpentobi_base; using libboardgame_sgf::TreeReader; using libboardgame_sgf::util::get_last_node; //----------------------------------------------------------------------------- /** Test that BoardUpdater throws an exception if a piece is played twice. A tree from a file written by another application could contain move sequences where a piece is played twice. This could break assumptions about the maximum number of moves in a game at some places in Pentobi's code, so BoardUpdater should detect this and throw an exception. */ LIBBOARDGAME_TEST_CASE(pentobi_base_board_updater_piece_played_twice) { istringstream in("(;GM[Blokus];1[a1];1[a3])"); TreeReader reader; reader.read(in); unique_ptr root = reader.get_tree_transfer_ownership(); Tree tree(root); unique_ptr bd(new Board(tree.get_variant())); BoardUpdater updater(tree, *bd); auto& node = get_last_node(tree.get_root()); LIBBOARDGAME_CHECK_THROW(updater.update(node), Exception); } /** Test BoardUpdater with setup properties in root node. */ LIBBOARDGAME_TEST_CASE(pentobi_base_board_updater_setup) { istringstream in("(;GM[Blokus Duo]" "AB[e8,e9,f9,d10,e10][g6,f7,g7,h7,g8]" "AW[i4,h5,i5,j5,i6][j7,j8,j9,k9,j10])"); TreeReader reader; reader.read(in); unique_ptr root = reader.get_tree_transfer_ownership(); Tree tree(root); unique_ptr bd(new Board(tree.get_variant())); BoardUpdater updater(tree, *bd); updater.update(tree.get_root()); LIBBOARDGAME_CHECK_EQUAL(bd->get_nu_moves(), 0u); LIBBOARDGAME_CHECK_EQUAL(bd->get_points(Color(0)), 10u); LIBBOARDGAME_CHECK_EQUAL(bd->get_points(Color(1)), 10u); } /** Test BoardUpdater with setup properties in an inner node. */ LIBBOARDGAME_TEST_CASE(pentobi_base_board_updater_setup_inner_node) { istringstream in("(;GM[Blokus Duo]" " ;B[e8,e9,f9,d10,e10]" " ;AB[g6,f7,g7,h7,g8]AW[i4,h5,i5,j5,i6]" " ;W[j7,j8,j9,k9,j10])"); TreeReader reader; reader.read(in); unique_ptr root = reader.get_tree_transfer_ownership(); Tree tree(root); unique_ptr bd(new Board(tree.get_variant())); BoardUpdater updater(tree, *bd); auto& node = get_last_node(tree.get_root()); updater.update(node); // BoardUpdater merges setup properties with existing position, so // get_nu_moves() should return the number of moves played after the setup LIBBOARDGAME_CHECK_EQUAL(bd->get_nu_moves(), 1u); LIBBOARDGAME_CHECK_EQUAL(bd->get_points(Color(0)), 10u); LIBBOARDGAME_CHECK_EQUAL(bd->get_points(Color(1)), 10u); } /** Test removing a piece with the AE property. */ LIBBOARDGAME_TEST_CASE(pentobi_base_board_updater_setup_empty) { istringstream in("(;GM[Blokus Duo]" " ;B[e8,e9,f9,d10,e10]" " ;W[j7,j8,j9,k9,j10]" " ;AE[e8,e9,f9,d10,e10])"); TreeReader reader; reader.read(in); unique_ptr root = reader.get_tree_transfer_ownership(); Tree tree(root); unique_ptr bd(new Board(tree.get_variant())); BoardUpdater updater(tree, *bd); auto& node = get_last_node(tree.get_root()); updater.update(node); // BoardUpdater merges setup properties with existing position, so // get_nu_moves() should return the number of moves played after the setup LIBBOARDGAME_CHECK_EQUAL(bd->get_nu_moves(), 0u); LIBBOARDGAME_CHECK_EQUAL(bd->get_points(Color(0)), 0u); LIBBOARDGAME_CHECK_EQUAL(bd->get_points(Color(1)), 5u); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libpentobi_base/CMakeLists.txt000066400000000000000000000010521227240712600233520ustar00rootroot00000000000000include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) link_directories(${Boost_LIBRARY_DIRS}) set(unittest_libpentobi_base_STAT_SRCS BoardConstTest.cpp BoardTest.cpp BoardUpdaterTest.cpp GameTest.cpp TreeTest.cpp ) add_executable(unittest_libpentobi_base ${unittest_libpentobi_base_STAT_SRCS}) target_link_libraries(unittest_libpentobi_base boardgame_test_main pentobi_base boardgame_base boardgame_sgf boardgame_test boardgame_util boardgame_sys ) add_test(libpentobi_base unittest_libpentobi_base) pentobi-7.2/src/unittest/libpentobi_base/GameTest.cpp000066400000000000000000000023571227240712600230400ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file unittest/libpentobi_base/GameTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "libpentobi_base/Game.h" #include "libboardgame_sgf/TreeReader.h" #include "libboardgame_sgf/Util.h" #include "libboardgame_test/Test.h" using namespace std; using namespace libpentobi_base; using libboardgame_sgf::TreeReader; using libboardgame_sgf::util::get_last_node; //----------------------------------------------------------------------------- /** Test that the current node is in a defined state if the root node contains invalid properties. */ LIBBOARDGAME_TEST_CASE(pentobi_base_game_current_defined_invalid_root) { istringstream in("(;GM[Blokus]1[a99999])"); TreeReader reader; reader.read(in); unique_ptr root = reader.get_tree_transfer_ownership(); Game game(Variant::classic); try { game.init(root); } catch (const Exception&) { // ignore } LIBBOARDGAME_CHECK_EQUAL(&game.get_current(), &game.get_tree().get_root()); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libpentobi_base/TreeTest.cpp000066400000000000000000000120711227240712600230600ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file unittest/libpentobi_base/TreeTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "libboardgame_sgf/MissingProperty.h" #include "libboardgame_sgf/TreeReader.h" #include "libboardgame_test/Test.h" #include "libpentobi_base/Tree.h" using namespace std; using namespace libpentobi_base; using libboardgame_sgf::InvalidPropertyValue; using libboardgame_sgf::MissingProperty; using libboardgame_sgf::TreeReader; //----------------------------------------------------------------------------- /** Check backwards compatibility to move properties used in Pentobi 0.1. Pentobi 0.1 used the property id's BLUE,YELLOW,RED,GREEN in four-player game variants instead of 1,2,3,4. (It also used point lists instead of single-value move properties. */ LIBBOARDGAME_TEST_CASE(pentobi_base_tree_backward_compatibility_0_1) { istringstream in("(;GM[Blokus Two-Player];BLUE[a16][a17][a18][a19][a20]" ";YELLOW[s17][t17][t18][t19][t20];RED[t1][t2][t3][t4][t5]" ";GREEN[a1][b1][c1][d1][d2])"); TreeReader reader; reader.read(in); unique_ptr root = reader.get_tree_transfer_ownership(); Tree tree(root); auto& board_const = tree.get_board_const(); auto node = &tree.get_root(); node = &node->get_child(); { auto mv = tree.get_move(*node); LIBBOARDGAME_CHECK(! mv.is_null()); LIBBOARDGAME_CHECK_EQUAL(mv.color, Color(0)); auto& info = board_const.get_move_info(mv.move); LIBBOARDGAME_CHECK_EQUAL(info.size(), 5u); LIBBOARDGAME_CHECK(info.contains(Point("a16"))); LIBBOARDGAME_CHECK(info.contains(Point("a17"))); LIBBOARDGAME_CHECK(info.contains(Point("a18"))); LIBBOARDGAME_CHECK(info.contains(Point("a19"))); LIBBOARDGAME_CHECK(info.contains(Point("a20"))); } node = &node->get_child(); { auto mv = tree.get_move(*node); LIBBOARDGAME_CHECK(! mv.is_null()); LIBBOARDGAME_CHECK_EQUAL(mv.color, Color(1)); auto& info = board_const.get_move_info(mv.move); LIBBOARDGAME_CHECK_EQUAL(info.size(), 5u); LIBBOARDGAME_CHECK(info.contains(Point("s17"))); LIBBOARDGAME_CHECK(info.contains(Point("t17"))); LIBBOARDGAME_CHECK(info.contains(Point("t18"))); LIBBOARDGAME_CHECK(info.contains(Point("t19"))); LIBBOARDGAME_CHECK(info.contains(Point("t20"))); } node = &node->get_child(); { auto mv = tree.get_move(*node); LIBBOARDGAME_CHECK(! mv.is_null()); LIBBOARDGAME_CHECK_EQUAL(mv.color, Color(2)); auto& info = board_const.get_move_info(mv.move); LIBBOARDGAME_CHECK_EQUAL(info.size(), 5u); LIBBOARDGAME_CHECK(info.contains(Point("t1"))); LIBBOARDGAME_CHECK(info.contains(Point("t2"))); LIBBOARDGAME_CHECK(info.contains(Point("t3"))); LIBBOARDGAME_CHECK(info.contains(Point("t4"))); LIBBOARDGAME_CHECK(info.contains(Point("t5"))); } node = &node->get_child(); { auto mv = tree.get_move(*node); LIBBOARDGAME_CHECK(! mv.is_null()); LIBBOARDGAME_CHECK_EQUAL(mv.color, Color(3)); auto& info = board_const.get_move_info(mv.move); LIBBOARDGAME_CHECK_EQUAL(info.size(), 5u); LIBBOARDGAME_CHECK(info.contains(Point("a1"))); LIBBOARDGAME_CHECK(info.contains(Point("b1"))); LIBBOARDGAME_CHECK(info.contains(Point("c1"))); LIBBOARDGAME_CHECK(info.contains(Point("d1"))); LIBBOARDGAME_CHECK(info.contains(Point("d2"))); } } /** Check that Tree::get_move() can handle pass moves. The current Blokus SGF documentation included in Pentobi does not specify if pass moves are allowed, but they may be used in the future and early (unreleased) versions of Pentobi use them. */ LIBBOARDGAME_TEST_CASE(pentobi_base_tree_get_move_pass) { istringstream in("(;GM[Blokus Duo];B[])"); TreeReader reader; reader.read(in); unique_ptr root = reader.get_tree_transfer_ownership(); Tree tree(root); auto node = &tree.get_root(); node = &node->get_child(); auto mv = tree.get_move(*node); LIBBOARDGAME_CHECK(mv.move.is_pass()); } /** Check that Tree constructor throws InvalidPropertyValue on unknown GM property value. */ LIBBOARDGAME_TEST_CASE(pentobi_base_tree_invalid_game) { istringstream in("(;GM[1])"); TreeReader reader; reader.read(in); unique_ptr root = reader.get_tree_transfer_ownership(); LIBBOARDGAME_CHECK_THROW(Tree tree(root), InvalidPropertyValue); } /** Check that Tree constructor throws MissingProperty on missing GM property. */ LIBBOARDGAME_TEST_CASE(pentobi_base_tree_missing_game_property) { istringstream in("(;)"); TreeReader reader; reader.read(in); unique_ptr root = reader.get_tree_transfer_ownership(); LIBBOARDGAME_CHECK_THROW(Tree tree(root), MissingProperty); } //----------------------------------------------------------------------------- pentobi-7.2/src/unittest/libpentobi_mcts/000077500000000000000000000000001227240712600206505ustar00rootroot00000000000000pentobi-7.2/src/unittest/libpentobi_mcts/CMakeLists.txt000066400000000000000000000011321227240712600234050ustar00rootroot00000000000000include_directories( ${CMAKE_SOURCE_DIR}/src ${Boost_INCLUDE_DIRS} ) link_directories(${Boost_LIBRARY_DIRS}) set(unittest_libpentobi_mcts_STAT_SRCS SearchTest.cpp ) add_executable(unittest_libpentobi_mcts ${unittest_libpentobi_mcts_STAT_SRCS}) target_link_libraries(unittest_libpentobi_mcts boardgame_test_main pentobi_mcts pentobi_base boardgame_base boardgame_sgf boardgame_test boardgame_util boardgame_sys ${Boost_THREAD_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ) add_test(libpentobi_mcts unittest_libpentobi_mcts) pentobi-7.2/src/unittest/libpentobi_mcts/SearchTest.cpp000066400000000000000000000060231227240712600234220ustar00rootroot00000000000000//----------------------------------------------------------------------------- /** @file unittest/libpentobi_mcts/SearchTest.cpp */ //----------------------------------------------------------------------------- #ifdef HAVE_CONFIG_H #include #endif #include "libpentobi_mcts/Search.h" #include "libboardgame_sgf/TreeReader.h" #include "libboardgame_sgf/Util.h" #include "libboardgame_test/Test.h" #include "libboardgame_util/CpuTime.h" #include "libpentobi_base/BoardUpdater.h" #include "libpentobi_base/Tree.h" using namespace std; using namespace libpentobi_mcts; using libboardgame_sgf::TreeReader; using libboardgame_sgf::util::get_last_node; using libboardgame_util::CpuTime; using libpentobi_base::BoardUpdater; //----------------------------------------------------------------------------- /** Test that state generates a playout move even if no large pieces are playable early in the game. This tests for a bug that occurred in Pentobi 1.1 with game variant Trigon: Because moves that are below a certain piece size are not generated early in the game, it could happen in rare cases that no moves were generated, which could even cause crashes, because the maximum game length was set with the assumption that pass moves are only played if a color has no more legal moves. */ LIBBOARDGAME_TEST_CASE(pentobi_mcts_search_no_large_pieces) { istringstream in("(" ";GM[Blokus Trigon Two-Player]" ";1[r4,r5,s5,r6,s6,r7]" ";2[r12,q13,r13,q14,r14,r15]" ";3[k11,l11,m11,n11,j12,k12]" ";4[w7,x7,y7,z7,v8,w8]" ";1[s8,t8,r9,s9,t9,u9]" ";2[n12,o12,m13,n13,o13,o14]" ";3[k13,k14,l14,l15,m15,n15]" ";4[w9,t10,u10,v10,w10,x10]" ";1[n10,o10,p10,q10,r10,r11]" ";2[o15,k16,l16,m16,n16,o16]" ";3[i15,j15,h16,i16,j16,j17]" ";4[u11,s12,t12,u12,v12,v13]" ";1[p4,m5,n5,o5,p5,m6]" ";2[k17,i18,j18,k18,l18,m18]" ";3[l17,m17,n17,o17,p17,o18]" ";4[t14,u14,s15,t15,r16,s16]" ";1[l8,m8,j9,k9,l9,m9])" ); TreeReader reader; reader.read(in); unique_ptr root = reader.get_tree_transfer_ownership(); libpentobi_base::Tree tree(root); unique_ptr bd(new Board(tree.get_variant())); BoardUpdater updater(tree, *bd); updater.update(get_last_node(tree.get_root())); unsigned nu_threads = 1; size_t memory = 10000; unique_ptr search(new Search(bd->get_variant(), nu_threads, memory)); Float max_count = 1; Float min_simulations = 1; double max_time = 0; CpuTime time_source; Move mv; bool res = search->search(mv, *bd, Color(1), max_count, min_simulations, max_time, time_source); LIBBOARDGAME_CHECK(res); LIBBOARDGAME_CHECK(! mv.is_null()); LIBBOARDGAME_CHECK(! mv.is_pass()); } //----------------------------------------------------------------------------- pentobi-7.2/tools/000077500000000000000000000000001227240712600141655ustar00rootroot00000000000000pentobi-7.2/tools/gdb/000077500000000000000000000000001227240712600147215ustar00rootroot00000000000000pentobi-7.2/tools/gdb/gdb_pentobi_utils000066400000000000000000000003421227240712600203370ustar00rootroot00000000000000define p_point set $p = ($arg0).m_i if ($p == 0) printf "null (%i)\n", $p else set $x = ($p - 1) % 35 set $x_char = 'A' + $x set $y = ($p - 1) / 35 printf "%c%i (%i)\n", $x_char, $y + 1, $p end end pentobi-7.2/tools/gtpregress/000077500000000000000000000000001227240712600163525ustar00rootroot00000000000000pentobi-7.2/tools/gtpregress/README.regression000066400000000000000000000017431227240712600214160ustar00rootroot00000000000000This directory contains tests with GTP input files using the convention of GNU Go[1], which adds expected responses in special comment lines after the commands. Note that (despite the name) these are not really regression tests because there is no clear distinction between tests that must not fail if the program is correct and tests that merely test the performance of the program (i.e. the outcome depends on heuristics used or on the amount of search). Maybe a subset of these tests will become real regression tests is the future but with the statistical nature of Monte-Carlo searches, this will be difficult. The tests can be run with the lightweight script regression/regress.awk from the GNU Go distribution, with the Java program gogui-regress from GoGui[2] (see run.sh for an example how to invoke it) or they can be used as the standard input for pentobi-gtp directly (without automatic comparison of the responses). [1] http://www.gnu.org/software/gnugo/ [2] http://gogui.sf.net pentobi-7.2/tools/gtpregress/run.sh000077500000000000000000000014271227240712600175210ustar00rootroot00000000000000#!/bin/bash # Runs test GTP files using gogui-regress (http://gogui.sf.net) PENTOBI_DBG="../../build/dbg/src/pentobi_gtp/pentobi-gtp" PENTOBI_OPT="../../build/opt/src/pentobi_gtp/pentobi-gtp" TESTS=\ symmetry.tst usage() { cat >&2 < 0: print "%s: %.2f+-%.2f" \ % (statistics.get_name(), statistics.get_mean(), statistics.get_error_mean()), def print_stat_percent(statistics): if statistics.get_count() > 0: print "%s: %.1f%%+-%.1f%%" \ % (statistics.get_name(), 100 * statistics.get_mean(), 100 * statistics.get_error_mean()), def print_stat_percent_count(statistics): if statistics.get_count() > 0: print "%s: %.1f%%+-%.1f%%" \ % (statistics.get_name(), 100 * statistics.get_mean(), 100 * statistics.get_error_mean()), print_result = False opts, args = getopt(argv[1:], "r", [ "result", ]) for opt, val in opts: if opt in ("-r", "--result"): print_result = True games = 0 win = Statistics("Win") loss = Statistics("Loss") draw = Statistics("Draw") result = Statistics("Res") result_color_black = Statistics("ResB") result_color_white = Statistics("ResW") alternate_used = False cpu_black = Statistics("CpuB") cpu_white = Statistics("CpuW") len = Statistics("Len") for line in stdin.readlines(): if line.strip().startswith("#"): continue games += 1 columns = line.split("\t") game_number = int(columns[0]) result_black = columns[1] result_white = columns[2] exchange_color = (columns[4].strip() == "True") cpu_black.add(float(columns[5])) cpu_white.add(float(columns[6])) len.add(int(columns[3])) if exchange_color: alternate_used = True if result_black.startswith("B+"): win.add(1) loss.add(0) draw.add(0) result_value = 1.0 elif result_black.startswith("W+"): win.add(0) loss.add(1) draw.add(0) result_value = 0.0 elif result_black == "0": win.add(0) loss.add(0) draw.add(1) result_value = 0.5 else: exit("Invalid result: " + result_black) result.add(result_value) if exchange_color: result_color_white.add(result_value) else: result_color_black.add(result_value) if print_result: print "%.2f\t%.2f\t%.2f\t%.2f\t%i" \ % (100 * result.get_mean(), 100 * result.get_error_mean(), cpu_black.get_mean(), cpu_white.get_mean(), games) else: print "Gam: %i" % games, print ",", print_stat_percent(result) if games > 0: if alternate_used: print ",", print_stat_percent(result_color_black) print ",", print_stat_percent(result_color_white) print print_stat_percent_count(win) print ",", print_stat_percent_count(loss) print ",", print_stat_percent_count(draw) print print_stat(cpu_black) print ",", print_stat(cpu_white) print ",", print_stat(len) print pentobi-7.2/tools/twogtp/split-gamefile.py000077500000000000000000000007351227240712600207750ustar00rootroot00000000000000#!/usr/bin/python # Split multi-game SGF file produced by twogtp.py into single-game files. from re import search from sys import stdin def write_file(): with open(filename, "w") as f: f.write(buffer) filename = "" buffer = "" for line in stdin.readlines(): match = search("GN\[(\d+)\]", line) if match: if filename != "": write_file() filename = match.group(1) + ".blksgf" buffer = "" buffer += line write_file() pentobi-7.2/tools/twogtp/twogtp.py000077500000000000000000000245751227240712600174270ustar00rootroot00000000000000#!/usr/bin/python # Python script to play a number of games between two different GTP engines. from fcntl import flock, LOCK_EX, LOCK_NB from getopt import getopt from os import remove from os.path import exists from shlex import split from string import lower, strip from subprocess import PIPE, Popen from sys import argv, exit, stderr from threading import Lock, Thread class GtpClient: def __init__(self, cmd, color): self.color = color self.process = Popen(split(cmd), stdin = PIPE, stdout = PIPE, close_fds = True) def send(self, cmd): stderr.write(self.color + ">> " + cmd + "\n") self.process.stdin.write(cmd + "\n") self.process.stdin.flush() response = "" line = self._readline() while line != "\n": response = response + line line = self._readline() if response[-1] == "\n": response = response[:-1] stderr.write(self.color + "<< " + response + "\n") if (response[0] == "?"): raise Exception(response[2:]) if (response[0] == "="): response = response[2:] return response exit(self.color + ": invalid response: " + response) def send_no_err(self, cmd): try: self.send(cmd) except: pass def _readline(self): line = self.process.stdout.readline() if self.process.poll() != None: exit(self.color + " terminated") return line class OutputFile: """ Result file. Keeps track of the game results and provides synchronized access for the threads to the number of the next game to play. """ def __init__(self, prefix): self.prefix = prefix self.lock = Lock() self.games = dict() self.game_numbers = set() self.next_game_number = 0 if not exists(self.prefix + ".dat"): return with open(self.prefix + ".dat", "r") as f: for line in f.readlines(): if line.strip().startswith("#"): continue columns = line.split("\t") game_number = int(columns[0]) self.game_numbers.add(game_number) self.games[game_number] = line while self.next_game_number in self.game_numbers: self.next_game_number += 1 def get_next_game_number(self): with self.lock: result = self.next_game_number self.next_game_number += 1 while self.next_game_number in self.game_numbers: self.next_game_number += 1 return result def add_result(self, game_number, result_black, result_white, move_number, exchange_color, cpu_black, cpu_white, sgf): with self.lock: self.game_numbers.add(game_number) self.games[game_number] = \ "%s\t%s\t%s\t%s\t%s\t%s\t%s\n" \ % (game_number, result_black, result_white, move_number, exchange_color, cpu_black, cpu_white) with open(self.prefix + ".dat", "w") as f: f.write("# game\tres_b\tres_w\tlen\texchg\tcpu_b\tcpu_w\n") for i in self.game_numbers: f.write(self.games[i]) with open(self.prefix + ".blksgf", "a") as f: f.write(sgf) def invert_result(result): if result.find("B+") != -1: return result.replace("B+", "W+") elif result.find("W+") != -1: return result.replace("W+", "B+") else: return result def convert_four_player_result(result): """ Make a two-player score out of the score of the four players in game variant classic. Assumes that Black plays Blue and Red, and White plays Yellow and Green (but as independent players without adding the score of both players like in game variant classic two-player). The score is B+1 if the winning color was played by Black, W+1 if it was played by White and 0 if it ties with one of the colors played by the other player. """ result_array = result.split() if int(result_array[0]) > int(result_array[2]): max_black = int(result_array[0]) else: max_black = int(result_array[2]) if int(result_array[1]) > int(result_array[3]): max_white = int(result_array[1]) else: max_white = int(result_array[3]) if max_black > max_white: return "B+1" elif max_white > max_black: return "W+1" else: return "0" def play_game(game_number, black, white, variant, output_file): stderr.write("=========================================================\n") stderr.write("Game %i\n" % game_number) stderr.write("=========================================================\n") exchange_color = (alternate and game_number % 2 != 0) black.send("clear_board") white.send("clear_board") cpu_black = float(black.send("cputime")) cpu_white = float(white.send("cputime")) if exchange_color: black, white = white, black to_play = black other = white if variant == "duo" or variant == "junior": colors = [ "b", "w" ] else: colors = [ "1", "2", "3", "4" ] color_to_play = 0 move_number = 0 nu_passes = 0 resign = False sgf = "(;GM[%s]GN[%s]\n" % (game_name, game_number) if exchange_color: sgf += "C[Player 1: %s\nPlayer 2: %s]\n" % (white_cmd, black_cmd) else: sgf += "C[Player 1: %s\nPlayer 2: %s]\n" % (black_cmd, white_cmd) while True: try: move = strip(to_play.send("genmove " + colors[color_to_play])) except: sgf += ")\n" with open(prefix + ".fail.blksgf", "w") as f: f.write(sgf) raise move = lower(move) if move == "resign": resign = True break other.send("play " + colors[color_to_play] + " " + move) if move != "pass": nu_passes = 0 if variant == "duo": if color_to_play == 0: prop_id = "B" else: prop_id = "W" else: prop_id = str(color_to_play + 1) sgf += ";%s" % (prop_id) for p in split(move): sgf += "[%s]" % (p) if to_play.evaluate: try: sgf += "V[%s]" % (to_play.send("get_value")) except: pass sgf += "\n" else: nu_passes += 1 if nu_passes == len(colors): break move_number += 1 to_play, other = other, to_play color_to_play = color_to_play + 1 if color_to_play == len(colors): color_to_play = 0 sgf += ")\n" if exchange_color: black, white = white, black cpu_black = float(black.send("cputime")) - cpu_black cpu_white = float(white.send("cputime")) - cpu_white if resign: if not (variant == "duo" or variant == "junior" or variant == "classic_2" or variant == "trigon_2"): exit("resign only allowed in two-player game variants") if color_to_play == 0 or color_to_play == 2: result_black = "W+R" result_white = "W+R" else: result_black = "B+R" result_white = "B+R" else: result_black = black.send("final_score") result_white = white.send("final_score") if variant == "classic" or variant == "trigon": result_black = convert_four_player_result(result_black) result_white = convert_four_player_result(result_white) if exchange_color: result_black = invert_result(result_black) result_white = invert_result(result_white) output_file.add_result(game_number, result_black, result_white, move_number, exchange_color, cpu_black, cpu_white, sgf) def thread_main(): black = GtpClient(black_cmd, "B") white = GtpClient(white_cmd, "W") black.evaluate = (evaluate == "black" or evaluate == "both") white.evaluate = (evaluate == "white" or evaluate == "both") black.send_no_err("set_game " + game_name) white.send_no_err("set_game " + game_name) while True: game_number = output_file.get_next_game_number() if game_number >= nu_games: break play_game(game_number, black, white, variant, output_file) white_cmd = "" black_cmd = "" alternate = False nu_games = 1 nu_threads = 1 variant = "duo" prefix = "output" evaluate = "" opts, args = getopt(argv[1:], "ab:f:g:w:n:", [ "alternate", "black=", "eval=", "file=", "game=", "nugames=", "threads=", "white=", ]) for opt, val in opts: if opt in ("-a", "--alternate"): alternate = True elif opt in ("-b", "--black"): black_cmd = val elif opt in ("--eval"): evaluate = val elif opt in ("-f", "--file"): prefix = val elif opt in ("-g", "--game"): variant = val elif opt in ("-n", "--nugames"): nu_games = int(val) elif opt in ("--threads"): nu_threads = int(val) elif opt in ("-w", "--white"): white_cmd = val if black_cmd == "": exit("Missing black player") if white_cmd == "": exit("Missing white player") if nu_threads <= 0: exit("Invalid number of threads") if variant == "c": variant = "classic" elif variant == "c2": variant = "classic_2" elif variant == "d": variant = "duo" elif variant == "t": variant = "trigon" elif variant == "t2": variant = "trigon_2" elif variant == "j": variant = "junior" if variant == "classic": game_name = "Blokus" elif variant == "classic_2": game_name = "Blokus Two-Player" elif variant == "trigon": game_name = "Blokus Trigon" elif variant == "trigon_2": game_name = "Blokus Trigon Two-Player" elif variant == "duo": game_name = "Blokus Duo" elif variant == "junior": game_name = "Blokus Junior" else: exit("invalid game variant: " + variant) output_file = OutputFile(prefix) lock_filename = prefix + ".lock" with open(lock_filename, "w") as lock_file: flock(lock_file, LOCK_EX | LOCK_NB) threads = [] for i in range(0, nu_threads): t = Thread(None, thread_main) t.start() threads.append(t) for t in threads: t.join() remove(lock_filename) pentobi-7.2/windows_installer/000077500000000000000000000000001227240712600165745ustar00rootroot00000000000000pentobi-7.2/windows_installer/CMakeLists.txt000066400000000000000000000000631227240712600213330ustar00rootroot00000000000000configure_file(install.nsis.in install.nsis @ONLY) pentobi-7.2/windows_installer/German.nsh000066400000000000000000000005721227240712600205230ustar00rootroot00000000000000; German translations ; NSIS version 2.46 does not support Unicode yet, so this file needs to be ; encoded in ISO 8859 LangString ADD_START_MENU_ENTRY ${LANG_GERMAN} \ "Eintrag im Startmenќ hinzufќgen" LangString CREATE_DESKTOP_SHORTCUT ${LANG_GERMAN} \ "Desktopverknќpfung erstellen" LangString INSTALLER_TITLE ${LANG_GERMAN} \ "Pentobi ${PENTOBI_VERSION} installieren" pentobi-7.2/windows_installer/blksgf.ico000066400000000000000000003025361227240712600205510ustar00rootroot00000000000000€€ (V@@ (B~00 Ј%ІJ  ЈNp hі€(€ џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUSWURSWUМSWUќSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUќSWUМSWURSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUSWUНSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUНSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWURSWUџSWUџUYWџœžžџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџИКЙџЇЉЈџ[_]џSWUџSWUџSWURџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUМSWUџTXVџцчцџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџєѕѕџTXVџSWUџSWUМџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUќSWUџy|{џќ§ќџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ’‘џSWUџSWUќџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџќќќџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџёђђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџђѓђџђѓђџђѓђџђѓђџђѓђџђѓђџђѓђџђѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџјљјџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьюэџьяюџьяюџьяюџьяюџьяюџьяюџьяюџьяюџэяюџэяюџэяюџэяюџэяяџэяяџэяяџэяяџэяяџэ№яџэ№яџэяяџэяяџэяяџэяяџэяяџэяюџэяюџэяюџэяюџьяюџьяюџьяюџьяюџьяюџьяюџьяюџьяюџьююџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьюэџьяюџьяюџьяюџьяюџьяюџьяюџьяюџэяюџэяюџэяюџэяяџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэяяџэяюџэяюџэяюџьяюџьяюџьяюџьяюџьяюџьяюџьяюџьюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьюэџьяюџьяюџьяюџьяюџьяюџьяюџьяюџэяюџэяюџэяяџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэяяџэяюџэяюџьяюџьяюџьяюџьяюџьяюџьяюџьяюџьюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьююџьяюџьяюџьяюџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџю№яџю№яџю№яџю№яџю№яџю№яџю№яџю№яџю№яџю№яџю№яџю№яџю№яџю№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџьяюџьяюџьяюџьююџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьяюџьяюџьяюџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџю№яџю№яџю№яџю№яџю№яџю№№џю№№џю№№џю№№џю№№џю№№џю№№џю№№џю№№џю№№џю№№џю№№џю№яџю№яџю№яџю№яџю№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџьяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьюэџьяюџьяюџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџэ№яџэ№яџэ№яџю№яџю№яџю№яџю№яџю№№џю№№џю№№џюё№џюё№џюё№џюё№џюё№џюё№џюё№џюё№џюё№џюё№џюё№џюё№џюё№џюё№џю№№џю№№џю№№џю№яџю№яџю№яџю№яџэ№яџэ№яџэ№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџьяюџьяюџьюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьюэџьяюџьяюџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџэ№яџэ№яџю№яџю№яџю№яџю№№џю№№џюё№џюё№џюё№џюё№џюё№џюё№џюё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џюё№џюё№џюё№џюё№џюё№џюё№џюё№џю№№џю№№џю№яџю№яџю№яџэ№яџэ№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџьяюџьяюџьюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьююџьяюџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџэ№яџэ№яџю№яџю№яџю№яџю№№џю№№џюё№џюё№џюё№џюё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џюё№џюё№џюё№џюё№џю№№џю№№џю№яџю№яџю№яџэ№яџэ№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџьяюџьююџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьююџьяюџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџэ№яџэ№яџю№яџю№яџю№№џю№№џюё№џюё№џюё№џяё№џяё№џяё№џяё№џяё№џяё№џяёёџяёёџяёёџяёёџяёёџяёёџяёёџяёёџяёёџяёёџяёёџяёёџяё№џяё№џяё№џяё№џяё№џяё№џюё№џюё№џюё№џю№№џю№№џю№яџю№яџэ№яџэ№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџьяюџьююџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьююџьяюџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџэ№яџю№яџю№яџю№№џю№№џюё№џюё№џюё№џяё№џяё№џяё№џяё№џяёёџяёёџяёёџяёёџяёёџяђёџяђёџяђёџяђёџяђёџяђёџяђёџяђёџяђёџяђёџяёёџяёёџяёёџяёёџяёёџяё№џяё№џяё№џяё№џюё№џюё№џюё№џю№№џю№№џю№яџю№яџэ№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџьяюџьююџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьююџьяюџьяюџьяюџьяюџэяюџэ№яџэ№яџэ№яџэ№яџю№яџю№яџю№№џюё№џюё№џюё№џяё№џяё№џяё№џяё№џяёёџяёёџяёёџяђёџяђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџяђёџяђёџяёёџяёёџяёёџяё№џяё№џяё№џяё№џюё№џюё№џюё№џю№№џю№яџю№яџэ№яџэ№яџэ№яџэ№яџэяюџьяюџьяюџьяюџьяюџьююџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьюэџьяюџьяюџьяюџьяюџэяюџэ№яџэ№яџэ№яџю№яџю№яџю№№џю№№џюё№џюё№џяё№џяё№џяё№џяё№џяёёџяёёџяђёџяђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџяђёџяђёџяёёџяёёџяё№џяё№џяё№џяё№џюё№џюё№џю№№џю№№џю№яџю№яџэ№яџэ№яџэ№яџэяюџьяюџьяюџьяюџьяюџьюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьюэџьяюџьяюџьяюџьяюџэяюџэ№яџэ№яџэ№яџю№яџю№яџю№№џюё№џюё№џюё№џяё№џяё№џяё№џяёёџяёёџяђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџяђёџяёёџяёёџяё№џяё№џяё№џюё№џюё№џюё№џю№№џю№яџю№яџэ№яџэ№яџэ№яџэяюџьяюџьяюџьяюџьяюџьюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьяюџьяюџьяюџьяюџэяюџэ№яџэ№яџэ№яџю№яџю№яџю№№џюё№џюё№џяё№џяё№џяё№џяёёџяёёџяђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџёђђџёђђџёѓђџёѓђџёѓђџёѓђџёђђџёђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџяђёџяёёџяёёџяё№џяё№џяё№џюё№џюё№џю№№џю№яџю№яџэ№яџэ№яџэ№яџэяюџьяюџьяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьяюџьяюџьяюџьяюџэяюџэ№яџэ№яџэ№яџю№яџю№яџю№№џюё№џюё№џяё№џяё№џяё№џяёёџяёёџяђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђђџ№ђђџ№ђђџ№ђђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђёџ№ђёџ№ђёџ№ђёџяђёџяёёџяёёџяё№џяё№џяё№џюё№џюё№џю№№џю№яџю№яџэ№яџэ№яџэ№яџэяюџьяюџьяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџьююџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџю№яџю№яџю№№џюё№џюё№џяё№џяё№џяёёџяёёџяђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђђџ№ђђџ№ђђџёђђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёђђџ№ђђџ№ђђџ№ђђџ№ђёџ№ђёџ№ђёџ№ђёџяђёџяёёџяёёџяё№џяё№џюё№џюё№џю№№џю№яџю№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџьююџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџьюэџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџю№яџю№яџю№№џюё№џюё№џяё№џяё№џяёёџяёёџяђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђђџ№ђђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓѓџёѓѓџёѓѓџёѓѓџёѓѓџёѓѓџёѓѓџёѓѓџёѓѓџёѓѓџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџ№ђђџ№ђђџ№ђёџ№ђёџ№ђёџ№ђёџяђёџяёёџяёёџяё№џяё№џюё№џюё№џю№№џю№яџю№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџьюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџьяюџьяюџьяюџьяюџэяяџэ№яџэ№яџэ№яџю№яџю№№џюё№џюё№џяё№џяё№џяёёџяёёџяђёџ№ђёџ№ђёџ№ђёџ№ђђџ№ђђџёђђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓѓџёѓѓџёѓѓџђѓѓџђѓѓџђѓѓџђѓѓџђѓѓџђѓѓџђѓѓџђѓѓџђѓѓџђѓѓџђѓѓџђѓѓџёѓѓџёѓѓџёѓѓџёѓђџёѓђџёѓђџёѓђџёѓђџёђђџ№ђђџ№ђђџ№ђёџ№ђёџ№ђёџяђёџяёёџяёёџяё№џяё№џюё№џюё№џю№№џю№яџэ№яџэ№яџэ№яџэяяџьяюџьяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџьююџьяюџьяюџьяюџэяюџэ№яџ6цќџ6цќџ$оіџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџ6цќџ6цќџ$оіџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџПyAџОw?џЕp:џЅe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џПyAџОw?џЕp:џЅe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џэ№яџэяюџьяюџьяюџьяюџьююџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџьяюџьяюџьяюџэяюџэяяџэ№яџ6цќџ6цќџ%рїџбъџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџ6цќџ6цќџ%рїџбъџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџС|EџРzCџИs=џЈh6џІf5џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џС|EџРzCџИs=џЈh6џІf5џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џэ№яџэяяџэяюџьяюџьяюџьяюџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџьяюџьяюџьяюџьяюџэяяџэ№яџэ№яџ>чќџ8цќџ.уљџлђџкёџйяџияџияџияџияџияџияџияџ въџЯшџЯшџ>чќџ8цќџ.уљџлђџкёџйяџияџияџияџияџияџияџияџ въџЯшџЯшџУIџТ}FџМv@џЏl8џЎk7џЌj7џЋi6џЊi6џЊi6џЊi6џЊi6џЊi6џЊi6џІf5џЄe4џЄe4џУIџТ}FџМv@џЏl8џЎk7џЌj7џЋi6џЊi6џЊi6џЊi6џЊi6џЊi6џЊi6џІf5џЄe4џЄe4џэ№яџэ№яџэяяџьяюџьяюџьяюџьяюџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџьюэџьяюџьяюџьяюџэяюџэ№яџэ№яџэ№яџMщќџGшќџFшќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџияџЯшџЯшџMщќџGшќџFшќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџияџЯшџЯшџФMџУ€JџОyCџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЊi6џЄe4џЄe4џФMџУ€JџОyCџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЊi6џЄe4џЄe4џэ№яџэ№яџэ№яџэяюџьяюџьяюџьяюџьюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџю№яџ\ыќџVъќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџияџЯшџЯшџ\ыќџVъќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџияџЯшџЯшџЦ„PџХ‚NџП{EџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЊi6џЄe4џЄe4џЦ„PџХ‚NџП{EџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЊi6џЄe4џЄe4џю№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџьюэџьяюџьяюџьяюџэяюџэ№яџэ№яџю№яџю№яџkэ§џeьќџYъќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџияџЯшџЯшџkэ§џeьќџYъќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџияџЯшџЯшџШ‡TџЦ…RџР}HџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЊi6џЄe4џЄe4џШ‡TџЦ…RџР}HџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЊi6џЄe4џЄe4џю№яџю№яџэ№яџэ№яџэяюџьяюџьяюџьяюџьюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџю№яџю№№џzя§џtю§џcь§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџияџЯшџЯшџzя§џtю§џcь§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџияџЯшџЯшџЩŠXџШˆUџС~JџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЊi6џЄe4џЄe4џЩŠXџШˆUџС~JџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЊi6џЄe4џЄe4џю№№џю№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџьюэџьяюџьяюџьяюџэяюџэ№яџэ№яџю№яџю№яџюё№џ‰ё§џƒ№§џmэ§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџияџЯшџЯшџ‰ё§џƒ№§џmэ§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџияџЯшџЯшџЫ[џЪ‹YџУ€MџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џ­j7џЅf5џЄe4џЫ[џЪ‹YџУ€MџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џ­j7џЅf5џЄe4џюё№џю№яџю№яџэ№яџэ№яџэяюџьяюџьяюџьяюџьюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџю№яџю№№џюё№џ˜ѓ§џ’ђ§џwю§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџкёџащџЯшџ˜ѓ§џ’ђ§џwю§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџкёџащџЯшџЫ\џЫ\џУ‚OџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЏl8џЉh6џЇg5џЫ\џЫ\џУ‚OџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЏl8џЉh6џЇg5џюё№џю№№џю№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџьяюџьяюџьяюџэяюџэ№яџэ№яџю№яџю№яџюё№џюё№џ›ѓ§џ›ѓ§џ€№§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџмѓџгьџбъџ›ѓ§џ›ѓ§џ€№§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџмѓџгьџбъџЫ\џЫ\џФƒPџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џБm9џЌj7џЊi6џЫ\џЫ\џФƒPџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џБm9џЌj7џЊi6џюё№џюё№џю№яџю№яџэ№яџэ№яџэяюџьяюџьяюџьяюџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџьяюџьяюџьяюџэяяџэ№яџэ№яџю№яџю№№џюё№џяё№џ›ѓ§џ›ѓ§џ№§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџ оѕџжяџдэџ›ѓ§џ›ѓ§џ№§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџ оѕџжяџдэџЫ\џЫ\џФƒPџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џГo:џАm9џЎk8џЫ\џЫ\џФƒPџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џГo:џАm9џЎk8џяё№џюё№џю№№џю№яџэ№яџэ№яџэяяџьяюџьяюџьяюџіїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџьяюџьяюџэяюџэ№яџэ№яџю№яџю№яџюё№џюё№џяё№џ›ѓ§џ›ѓ§џ№§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџ"рїџйђџз№џ›ѓ§џ›ѓ§џ№§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџ"рїџйђџз№џЫ\џЫ\џФƒPџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp;џДo:џБn9џЫ\џЫ\џФƒPџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp;џДo:џБn9џяё№џюё№џюё№џю№яџю№яџэ№яџэ№яџэяюџьяюџьяюџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџьяюџьяюџэяюџэ№яџэ№яџю№яџю№№џюё№џяё№џяё№џ›ѓ§џ›ѓ§џ№§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџ#тљџ мѕџ кѓџ›ѓ§џ›ѓ§џ№§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџ#тљџ мѕџ кѓџЫ\џЫ\џФƒPџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џИr;џЗq;џЕp;џЫ\џЫ\џФƒPџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џИr;џЗq;џЕp;џяё№џяё№џюё№џю№№џю№яџэ№яџэ№яџэяюџьяюџьяюџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџьяюџэяюџэяяџэ№яџэ№яџю№яџю№№џюё№џяё№џяё№џ›ѓ§џ›ѓ§џ’ђ§џ№§џ№§џ№§џ№§џ№§џ№§џ№§џ№§џ№§џ№§џlэќџ`ыћџ[ъћџ›ѓ§џ›ѓ§џ’ђ§џ№§џ№§џ№§џ№§џ№§џ№§џ№§џ№§џ№§џ№§џlэќџ`ыћџ[ъћџЫ\џЫ\џЩŠXџФƒPџФƒPџФƒPџФƒPџФƒPџФƒPџФƒPџУ‚OџУMџТ€LџУ€KџТJџС}HџЫ\џЫ\џЩŠXџФƒPџФƒPџФƒPџФƒPџФƒPџФƒPџФƒPџУ‚OџУMџТ€LџУ€KџТJџС}Hџяё№џяё№џюё№џю№№џю№яџэ№яџэ№яџэяяџэяюџьяюџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџьяюџэяюџэ№яџэ№яџю№яџю№яџюё№џюё№џяё№џяёёџ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џšѓ§џ”ђ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џšѓ§џ”ђ§џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫŒ[џЪŠYџШ‰VџЧ‡TџЦ…QџЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫŒ[џЪŠYџШ‰VџЧ‡TџЦ…Qџяёёџяё№џюё№џюё№џю№яџю№яџэ№яџэ№яџэяюџьяюџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџьяюџэяюџэ№яџэ№яџю№яџю№№џюё№џяё№џяё№џяёёџ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЪ‹ZџЩŠXџШˆUџЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЪ‹ZџЩŠXџШˆUџяёёџяё№џяё№џюё№џю№№џю№яџэ№яџэ№яџэяюџьяюџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџьяюџэяяџэ№яџэ№яџю№яџю№№џюё№џяё№џяё№џяёёџ6цќџ6цќџ$оіџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџПyAџОw?џЕp:џЅe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џПyAџОw?џЕp:џЅe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џгuџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџяёёџяё№џяё№џюё№џю№№џю№яџэ№яџэ№яџэяяџьяюџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџэяюџэ№яџэ№яџю№яџю№яџюё№џюё№џяё№џяёёџяђёџ6цќџ6цќџ%рїџбъџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџС|EџРzCџИs=џЈh6џІf5џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џС|EџРzCџИs=џЈh6џІf5џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џ!з{џеxџгtџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџяђёџяёёџяё№џюё№џюё№џю№яџю№яџэ№яџэ№яџэяюџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџэяюџэ№яџэ№яџю№яџю№№џюё№џяё№џяё№џяёёџ№ђёџ>чќџ8цќџ.уљџлђџкёџйяџияџияџияџияџияџияџияџ въџЯшџЯшџУIџТ}FџМv@џЏl8џЎk7џЌj7џЋi6џЊi6џЊi6џЊi6џЊi6џЊi6џЊi6џІf5џЄe4џЄe4џУIџТ}FџМv@џЏl8џЎk7џЌj7џЋi6џЊi6џЊi6џЊi6џЊi6џЊi6џЊi6џІf5џЄe4џЄe4џ)кџ%й~џ!з{џ з{џ з{џ з{џ з{џ з{џ з{џ з{џ з{џ з{џ з{џдvџвsџвsџ№ђёџяёёџяё№џяё№џюё№џю№№џю№яџэ№яџэ№яџэяюџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџэяяџэ№яџэ№яџю№яџю№№џюё№џяё№џяё№џяёёџ№ђёџMщќџGшќџFшќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџияџЯшџЯшџФMџУ€JџОyCџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЊi6џЄe4џЄe4џФMџУ€JџОyCџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЊi6џЄe4џЄe4џ0о†џ,мƒџ-н„џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ№ђёџяёёџяё№џяё№џюё№џю№№џю№яџэ№яџэ№яџэяяџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџэяяџэ№яџэ№яџю№яџюё№џюё№џяё№џяёёџяђёџ№ђёџ\ыќџVъќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџияџЯшџЯшџЦ„PџХ‚NџП{EџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЊi6џЄe4џЄe4џЦ„PџХ‚NџП{EџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЊi6џЄe4џЄe4џ8тŒџ4р‰џ1п‡џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ№ђёџяђёџяёёџяё№џюё№џюё№џю№яџэ№яџэ№яџэяяџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџэяяџэ№яџю№яџю№яџюё№џюё№џяё№џяёёџяђёџ№ђёџkэ§џeьќџYъќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџияџЯшџЯшџШ‡TџЦ…RџР}HџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЊi6џЄe4џЄe4џШ‡TџЦ…RџР}HџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЊi6џЄe4џЄe4џ@х’џ<уџ7т‹џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ№ђёџяђёџяёёџяё№џюё№џюё№џю№яџю№яџэ№яџэяяџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџэ№яџэ№яџю№яџю№№џюё№џяё№џяё№џяёёџ№ђёџ№ђёџzя§џtю§џcь§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџияџЯшџЯшџЩŠXџШˆUџС~JџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЊi6џЄe4џЄe4џЩŠXџШˆUџС~JџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЊi6џЄe4џЄe4џHщ—џDч•џ<фџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ№ђёџ№ђёџяёёџяё№џяё№џюё№џю№№џю№яџэ№яџэ№яџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџэ№яџэ№яџю№яџю№№џюё№џяё№џяё№џяёёџ№ђёџ№ђёџ‰ё§џƒ№§џmэ§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџияџЯшџЯшџЫ[џЪ‹YџУ€MџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џ­j7џЅf5џЄe4џЫ[џЪ‹YџУ€MџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џ­j7џЅf5џЄe4џPьџLыšџAч“џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ№ђёџ№ђёџяёёџяё№џяё№џюё№џю№№џю№яџэ№яџэ№яџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџэ№яџэ№яџю№яџю№№џюё№џяё№џяё№џяёёџ№ђёџ№ђёџ˜ѓ§џ’ђ§џwю§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџкёџащџЯшџЫ\џЫ\џУ‚OџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЏl8џЉh6џЇg5џЫ\џЫ\џУ‚OџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЏl8џЉh6џЇg5џQэžџQэžџFщ—џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ№ђёџ№ђёџяёёџяё№џяё№џюё№џю№№џю№яџэ№яџэ№яџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџэ№яџэ№яџю№яџю№№џюё№џяё№џяёёџяђёџ№ђёџ№ђёџ›ѓ§џ›ѓ§џ€№§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџмѓџгьџбъџЫ\џЫ\џФƒPџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џБm9џЌj7џЊi6џЫ\џЫ\џФƒPџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џБm9џЌj7џЊi6џQэžџQэžџGщ—џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ№ђёџ№ђёџяђёџяёёџяё№џюё№џю№№џю№яџэ№яџэ№яџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќќџэ№яџэ№яџю№яџю№№џюё№џяё№џяёёџяђёџ№ђёџ№ђёџ›ѓ§џ›ѓ§џ№§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџ оѕџжяџдэџЫ\џЫ\џФƒPџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џГo:џАm9џЎk8џЫ\џЫ\џФƒPџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џГo:џАm9џЎk8џQэžџQэžџGщ—џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ№ђёџ№ђёџяђёџяёёџяё№џюё№џю№№џю№яџэ№яџэ№яџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќќџэ№яџэ№яџю№яџюё№џюё№џяё№џяёёџяђёџ№ђёџ№ђёџ›ѓ§џ›ѓ§џ№§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџ"рїџйђџз№џЫ\џЫ\џФƒPџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp;џДo:џБn9џЫ\џЫ\џФƒPџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp;џДo:џБn9џQэžџQэžџGщ—џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ№ђёџ№ђёџяђёџяёёџяё№џюё№џюё№џю№яџэ№яџэ№яџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќќџэ№яџэ№яџю№яџюё№џюё№џяё№џяёёџяђёџ№ђёџ№ђёџ›ѓ§џ›ѓ§џ№§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџ#тљџ мѕџ кѓџЫ\џЫ\џФƒPџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џИr;џЗq;џЕp;џЫ\џЫ\џФƒPџЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џЖp:џИr;џЗq;џЕp;џQэžџQэžџGщ—џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ№ђёџ№ђёџяђёџяёёџяё№џюё№џюё№џю№яџэ№яџэ№яџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќќџэ№яџэ№яџю№яџюё№џюё№џяё№џяёёџяђёџ№ђёџ№ђђџ›ѓ§џ›ѓ§џ’ђ§џ№§џ№§џ№§џ№§џ№§џ№§џ№§џ№§џ№§џ№§џlэќџ`ыћџ[ъћџЫ\џЫ\џЩŠXџФƒPџФƒPџФƒPџФƒPџФƒPџФƒPџФƒPџУ‚OџУMџТ€LџУ€KџТJџС}HџЫ\џЫ\џЩŠXџФƒPџФƒPџФƒPџФƒPџФƒPџФƒPџФƒPџУ‚OџУMџТ€LџУ€KџТJџС}HџQэžџQэžџNьœџGщ—џGщ—џGщ—џGщ—џGщ—џGщ—џGщ—џGщ—џGщ—џFщ—џ=фџ7с‹џ4р‰џ№ђђџ№ђёџяђёџяёёџяё№џюё№џюё№џю№яџэ№яџэ№яџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќќџэ№яџэ№яџю№яџюё№џюё№џяё№џяёёџяђёџ№ђёџ№ђђџ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џšѓ§џ”ђ§џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫŒ[џЪŠYџШ‰VџЧ‡TџЦ…QџЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫŒ[џЪŠYџШ‰VџЧ‡TџЦ…QџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџPэџLыšџ№ђђџ№ђёџяђёџяёёџяё№џюё№џюё№џю№яџю№яџэ№яџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќќџэ№яџю№яџю№яџюё№џяё№џяё№џяёёџ№ђёџ№ђёџ№ђђџ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЪ‹ZџЩŠXџШˆUџЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЪ‹ZџЩŠXџШˆUџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџ№ђђџ№ђёџ№ђёџяёёџяё№џяё№џюё№џю№яџю№яџэ№яџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќќџэ№яџю№яџю№яџюё№џяё№џяё№џяёёџ№ђёџ№ђёџ№ђђџ6цќџ6цќџ$оіџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџюџюџфџвџвџвџвџвџвџвџвџвџвџвџвџвџюџюџфџвџвџвџвџвџвџвџвџвџвџвџвџвџгuџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџ№ђђџ№ђёџ№ђёџяёёџяё№џяё№џюё№џю№яџю№яџэ№яџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќќџэ№яџэ№яџю№яџюё№џюё№џяё№џяёёџяђёџ№ђёџ№ђђџ6цќџ6цќџ%рїџбъџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџЯшџ!!юџ юџфџвџвџвџвџвџвџвџвџвџвџвџвџвџ!!юџ юџфџвџвџвџвџвџвџвџвџвџвџвџвџвџ!з{џеxџгtџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџ№ђђџ№ђёџяђёџяёёџяё№џюё№џюё№џю№яџю№яџэ№яџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќќџэ№яџэ№яџю№яџюё№џюё№џяё№џяёёџяђёџ№ђёџ№ђђџ>чќџ8цќџ.уљџлђџкёџйяџияџияџияџияџияџияџияџ въџЯшџЯшџ((яџ&&яџ!!щџмџмџмџмџмџмџмџмџмџмџеџвџвџ((яџ&&яџ!!щџмџмџмџмџмџмџмџмџмџмџеџвџвџ)кџ%й~џ!з{џ з{џ з{џ з{џ з{џ з{џ з{џ з{џ з{џ з{џ з{џдvџвsџвsџ№ђђџ№ђёџяђёџяёёџяё№џюё№џюё№џю№яџэ№яџэ№яџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќќџэ№яџэ№яџю№яџюё№џюё№џяё№џяёёџяђёџ№ђёџ№ђёџMщќџGшќџFшќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџияџЯшџЯшџ//яџ--яџ**яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџмџвџвџ//яџ--яџ**яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџмџвџвџ0о†џ,мƒџ-н„џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ№ђёџ№ђёџяђёџяёёџяё№џюё№џюё№џю№яџэ№яџэ№яџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќќџэ№яџэ№яџю№яџюё№џюё№џяё№џяёёџяђёџ№ђёџ№ђёџ\ыќџVъќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџияџЯшџЯшџ55№џ44№џ//яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџнџгџгџ55№џ44№џ//яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџнџгџгџ8тŒџ4р‰џ1п‡џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ№ђёџ№ђёџяђёџяёёџяё№џюё№џюё№џю№яџэ№яџэ№яџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќќџэ№яџэ№яџю№яџю№№џюё№џяё№џяёёџяђёџ№ђёџ№ђёџkэ§џeьќџYъќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџияџЯшџЯшџ<<№џ::№џ33№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџпџжџжџ<<№џ::№џ33№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџпџжџжџ@х’џ<уџ7т‹џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ№ђёџ№ђёџяђёџяёёџяё№џюё№џюё№џю№яџэ№яџэ№яџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџэ№яџэ№яџю№яџю№№џюё№џяё№џяёёџяђёџ№ђёџ№ђёџzя§џtю§џcь§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџияџЯшџЯшџBBёџAAёџ77№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ$$сџ кџйџBBёџAAёџ77№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ$$сџ кџйџHщ—џDч•џ<фџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ№ђёџ№ђёџяђёџяёёџяё№џюё№џю№№џю№яџэ№яџэ№яџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџэ№яџэ№яџю№яџю№№џюё№џяё№џяё№џяёёџ№ђёџ№ђёџ‰ё§џƒ№§џmэ§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџияџЯшџЯшџIIёџGGёџ<<№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))уџ((нџ''мџIIёџGGёџ<<№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))уџ((нџ''мџPьџLыšџAч“џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ№ђёџ№ђёџяёёџяё№џяё№џюё№џю№№џю№яџэ№яџэ№яџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџэ№яџэ№яџю№яџю№№џюё№џяё№џяё№џяёёџ№ђёџ№ђёџ˜ѓ§џ’ђ§џwю§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџкёџащџЯшџPPђџNNђџ@@№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ..хџ//рџ..рџPPђџNNђџ@@№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ..хџ//рџ..рџQэžџQэžџFщ—џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ№ђёџ№ђёџяёёџяё№џяё№џюё№џю№№џю№яџэ№яџэ№яџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџэ№яџэ№яџю№яџю№№џюё№џяё№џяё№џяёёџ№ђёџ№ђёџ›ѓ§џ›ѓ§џ€№§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџмѓџгьџбъџVVђџUUђџEEёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ22шџ66уџ55уџVVђџUUђџEEёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ22шџ66уџ55уџQэžџQэžџGщ—џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ№ђёџ№ђёџяёёџяё№џяё№џюё№џю№№џю№яџэ№яџэ№яџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџэяяџэ№яџю№яџю№яџюё№џюё№џяё№џяёёџяђёџ№ђёџ›ѓ§џ›ѓ§џ№§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџ оѕџжяџдэџ]]ѓџ[[ђџIIёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ77ъџ==цџ<<цџ]]ѓџ[[ђџIIёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ77ъџ==цџ<<цџQэžџQэžџGщ—џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ№ђёџяђёџяёёџяё№џюё№џюё№џю№яџю№яџэ№яџэяяџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџэяяџэ№яџэ№яџю№яџюё№џюё№џяё№џяёёџяђёџ№ђёџ›ѓ§џ›ѓ§џ№§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџ"рїџйђџз№џccѓџbbѓџMMђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ;;ьџDDъџCCщџccѓџbbѓџMMђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ;;ьџDDъџCCщџQэžџQэžџGщ—џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ№ђёџяђёџяёёџяё№џюё№џюё№џю№яџэ№яџэ№яџэяяџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџэяяџэ№яџэ№яџю№яџю№№џюё№џяё№џяё№џяёёџ№ђёџ›ѓ§џ›ѓ§џ№§џOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџOщќџ#тљџ мѕџ кѓџccѓџccѓџOOђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ@@юџKKэџJJьџccѓџccѓџOOђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ@@юџKKэџJJьџQэžџQэžџGщ—џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ№ђёџяёёџяё№џяё№џюё№џю№№џю№яџэ№яџэ№яџэяяџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџэяюџэ№яџэ№яџю№яџю№№џюё№џяё№џяё№џяёёџ№ђёџ›ѓ§џ›ѓ§џ’ђ§џ№§џ№§џ№§џ№§џ№§џ№§џ№§џ№§џ№§џ№§џlэќџ`ыћџ[ъћџccѓџccѓџ\\ѓџOOђџOOђџOOђџOOђџOOђџOOђџOOђџMMђџLLђџKKђџSSёџWWёџVVёџccѓџccѓџ\\ѓџOOђџOOђџOOђџOOђџOOђџOOђџOOђџMMђџLLђџKKђџSSёџWWёџVVёџQэžџQэžџNьœџGщ—џGщ—џGщ—џGщ—џGщ—џGщ—џGщ—џGщ—џGщ—џFщ—џ=фџ7с‹џ4р‰џ№ђёџяёёџяё№џяё№џюё№џю№№џю№яџэ№яџэ№яџэяюџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџэяюџэ№яџэ№яџю№яџю№яџюё№џюё№џяё№џяёёџяђёџ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џšѓ§џ”ђ§џccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџbbѓџ``ѓџ^^ѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџbbѓџ``ѓџ^^ѓџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџPэџLыšџяђёџяёёџяё№џюё№џюё№џю№яџю№яџэ№яџэ№яџэяюџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџьяюџэяяџэ№яџэ№яџю№яџю№№џюё№џяё№џяё№џяёёџ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџяёёџяё№џяё№џюё№џю№№џю№яџэ№яџэ№яџэяяџьяюџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџьяюџэяюџэ№яџэ№яџю№яџю№№џюё№џяё№џяё№џяёёџюџюџфџвџвџвџвџвџвџвџвџвџвџвџвџвџюџюџфџвџвџвџвџвџвџвџвџвџвџвџвџвџгuџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџгuџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџяёёџяё№џяё№џюё№џю№№џю№яџэ№яџэ№яџэяюџьяюџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџьяюџэяюџэ№яџэ№яџю№яџю№яџюё№џюё№џяё№џяёёџ!!юџ юџфџвџвџвџвџвџвџвџвџвџвџвџвџвџ!!юџ юџфџвџвџвџвџвџвџвџвџвџвџвџвџвџ!з{џеxџгtџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџ!з{џеxџгtџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџвsџяёёџяё№џюё№џюё№џю№яџю№яџэ№яџэ№яџэяюџьяюџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџьяюџэяюџэяяџэ№яџэ№яџю№яџю№№џюё№џяё№џяё№џ((яџ&&яџ!!щџмџмџмџмџмџмџмџмџмџмџеџвџвџ((яџ&&яџ!!щџмџмџмџмџмџмџмџмџмџмџеџвџвџ)кџ%й~џ!з{џ з{џ з{џ з{џ з{џ з{џ з{џ з{џ з{џ з{џ з{џдvџвsџвsџ)кџ%й~џ!з{џ з{џ з{џ з{џ з{џ з{џ з{џ з{џ з{џ з{џ з{џдvџвsџвsџяё№џяё№џюё№џю№№џю№яџэ№яџэ№яџэяяџэяюџьяюџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџьяюџьяюџэяюџэ№яџэ№яџю№яџю№№џюё№џяё№џяё№џ//яџ--яџ**яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџмџвџвџ//яџ--яџ**яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџмџвџвџ0о†џ,мƒџ-н„џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ0о†џ,мƒџ-н„џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџяё№џяё№џюё№џю№№џю№яџэ№яџэ№яџэяюџьяюџьяюџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћќћџьяюџьяюџэяюџэ№яџэ№яџю№яџю№яџюё№џюё№џяё№џ55№џ44№џ//яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџнџгџгџ55№џ44№џ//яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџнџгџгџ8тŒџ4р‰џ1п‡џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ8тŒџ4р‰џ1п‡џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџяё№џюё№џюё№џю№яџю№яџэ№яџэ№яџэяюџьяюџьяюџіїїџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџьяюџьяюџьяюџэяяџэ№яџэ№яџю№яџю№№џюё№џяё№џ<<№џ::№џ33№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџпџжџжџ<<№џ::№џ33№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџпџжџжџ@х’џ<уџ7т‹џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџ@х’џ<уџ7т‹џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџяё№џюё№џю№№џю№яџэ№яџэ№яџэяяџьяюџьяюџьяюџіїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџьяюџьяюџьяюџэяюџэ№яџэ№яџю№яџю№яџюё№џюё№џBBёџAAёџ77№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ$$сџ кџйџBBёџAAёџ77№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ$$сџ кџйџHщ—џDч•џ<фџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџHщ—џDч•џ<фџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџюё№џюё№џю№яџю№яџэ№яџэ№яџэяюџьяюџьяюџьяюџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџю№яџю№№џюё№џIIёџGGёџ<<№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))уџ((нџ''мџIIёџGGёџ<<№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))уџ((нџ''мџPьџLыšџAч“џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџPьџLыšџAч“џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџюё№џю№№џю№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџьюэџьяюџьяюџьяюџэяюџэ№яџэ№яџю№яџю№яџюё№џPPђџNNђџ@@№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ..хџ//рџ..рџPPђџNNђџ@@№џ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ..хџ//рџ..рџQэžџQэžџFщ—џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџQэžџQэžџFщ—џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџюё№џю№яџю№яџэ№яџэ№яџэяюџьяюџьяюџьяюџьюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџю№яџю№№џVVђџUUђџEEёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ22шџ66уџ55уџVVђџUUђџEEёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ22шџ66уџ55уџQэžџQэžџGщ—џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџQэžџQэžџGщ—џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџю№№џю№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџьюэџьяюџьяюџьяюџэяюџэ№яџэ№яџю№яџю№яџ]]ѓџ[[ђџIIёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ77ъџ==цџ<<цџ]]ѓџ[[ђџIIёџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ77ъџ==цџ<<цџQэžџQэžџGщ—џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџQэžџQэžџGщ—џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџю№яџю№яџэ№яџэ№яџэяюџьяюџьяюџьяюџьюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџю№яџccѓџbbѓџMMђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ;;ьџDDъџCCщџccѓџbbѓџMMђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ;;ьџDDъџCCщџQэžџQэžџGщ—џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџQэžџQэžџGщ—џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџю№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџьюэџьяюџьяюџьяюџэяюџэ№яџэ№яџэ№яџccѓџccѓџOOђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ@@юџKKэџJJьџccѓџccѓџOOђџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ))яџ@@юџKKэџJJьџQэžџQэžџGщ—џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџQэžџQэžџGщ—џ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ4тŠџ з{џвsџвsџэ№яџэ№яџэ№яџэяюџьяюџьяюџьяюџьюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџьяюџьяюџьяюџьяюџэяяџэ№яџэ№яџccѓџccѓџ\\ѓџOOђџOOђџOOђџOOђџOOђџOOђџOOђџMMђџLLђџKKђџSSёџWWёџVVёџccѓџccѓџ\\ѓџOOђџOOђџOOђџOOђџOOђџOOђџOOђџMMђџLLђџKKђџSSёџWWёџVVёџQэžџQэžџNьœџGщ—џGщ—џGщ—џGщ—џGщ—џGщ—џGщ—џGщ—џGщ—џFщ—џ=фџ7с‹џ4р‰џQэžџQэžџNьœџGщ—џGщ—џGщ—џGщ—џGщ—џGщ—џGщ—џGщ—џGщ—џFщ—џ=фџ7с‹џ4р‰џэ№яџэ№яџэяяџьяюџьяюџьяюџьяюџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџьяюџьяюџьяюџэяюџэяяџэ№яџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџbbѓџ``ѓџ^^ѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџbbѓџ``ѓџ^^ѓџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџPэџLыšџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџPэџLыšџэ№яџэяяџэяюџьяюџьяюџьяюџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџьююџьяюџьяюџьяюџэяюџэ№яџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџэ№яџэяюџьяюџьяюџьяюџьююџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџьяюџьяюџьяюџьяюџэяяџэ№яџэ№яџэ№яџю№яџю№№џюё№џюё№џяё№џяё№џяёёџяёёџяђёџ№ђёџ№ђёџ№ђёџ№ђђџ№ђђџёђђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓѓџёѓѓџёѓѓџђѓѓџђѓѓџђѓѓџђѓѓџђѓѓџђѓѓџђѓѓџђѓѓџђѓѓџђѓѓџђѓѓџђѓѓџёѓѓџёѓѓџёѓѓџёѓђџёѓђџёѓђџёѓђџёѓђџёђђџ№ђђџ№ђђџ№ђёџ№ђёџ№ђёџяђёџяёёџяёёџяё№џяё№џюё№џюё№џю№№џю№яџэ№яџэ№яџэ№яџэяяџьяюџьяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџьюэџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџю№яџю№яџю№№џюё№џюё№џяё№џяё№џяёёџяёёџяђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђђџ№ђђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓѓџёѓѓџёѓѓџёѓѓџёѓѓџёѓѓџёѓѓџёѓѓџёѓѓџёѓѓџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџ№ђђџ№ђђџ№ђёџ№ђёџ№ђёџ№ђёџяђёџяёёџяёёџяё№џяё№џюё№џюё№џю№№џю№яџю№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџьюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџьююџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџю№яџю№яџю№№џюё№џюё№џяё№џяё№џяёёџяёёџяђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђђџ№ђђџ№ђђџёђђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёђђџ№ђђџ№ђђџ№ђђџ№ђёџ№ђёџ№ђёџ№ђёџяђёџяёёџяёёџяё№џяё№џюё№џюё№џю№№џю№яџю№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџьююџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьяюџьяюџьяюџьяюџэяюџэ№яџэ№яџэ№яџю№яџю№яџю№№џюё№џюё№џяё№џяё№џяё№џяёёџяёёџяђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђђџ№ђђџ№ђђџ№ђђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђёџ№ђёџ№ђёџ№ђёџяђёџяёёџяёёџяё№џяё№џяё№џюё№џюё№џю№№џю№яџю№яџэ№яџэ№яџэ№яџэяюџьяюџьяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьяюџьяюџьяюџьяюџэяюџэ№яџэ№яџэ№яџю№яџю№яџю№№џюё№џюё№џяё№џяё№џяё№џяёёџяёёџяђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџёђђџёђђџёѓђџёѓђџёѓђџёѓђџёђђџёђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџяђёџяёёџяёёџяё№џяё№џяё№џюё№џюё№џю№№џю№яџю№яџэ№яџэ№яџэ№яџэяюџьяюџьяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьюэџьяюџьяюџьяюџьяюџэяюџэ№яџэ№яџэ№яџю№яџю№яџю№№џюё№џюё№џюё№џяё№џяё№џяё№џяёёџяёёџяђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџяђёџяёёџяёёџяё№џяё№џяё№џюё№џюё№џюё№џю№№џю№яџю№яџэ№яџэ№яџэ№яџэяюџьяюџьяюџьяюџьяюџьюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьюэџьяюџьяюџьяюџьяюџэяюџэ№яџэ№яџэ№яџю№яџю№яџю№№џю№№џюё№џюё№џяё№џяё№џяё№џяё№џяёёџяёёџяђёџяђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџяђёџяђёџяёёџяёёџяё№џяё№џяё№џяё№џюё№џюё№џю№№џю№№џю№яџю№яџэ№яџэ№яџэ№яџэяюџьяюџьяюџьяюџьяюџьюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьююџьяюџьяюџьяюџьяюџэяюџэ№яџэ№яџэ№яџэ№яџю№яџю№яџю№№џюё№џюё№џюё№џяё№џяё№џяё№џяё№џяёёџяёёџяёёџяђёџяђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџяђёџяђёџяёёџяёёџяёёџяё№џяё№џяё№џяё№џюё№џюё№џюё№џю№№џю№яџю№яџэ№яџэ№яџэ№яџэ№яџэяюџьяюџьяюџьяюџьяюџьююџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьююџьяюџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџэ№яџю№яџю№яџю№№џю№№џюё№џюё№џюё№џяё№џяё№џяё№џяё№џяёёџяёёџяёёџяёёџяёёџяђёџяђёџяђёџяђёџяђёџяђёџяђёџяђёџяђёџяђёџяёёџяёёџяёёџяёёџяёёџяё№џяё№џяё№џяё№џюё№џюё№џюё№џю№№џю№№џю№яџю№яџэ№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџьяюџьююџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьююџьяюџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџэ№яџэ№яџю№яџю№яџю№№џю№№џюё№џюё№џюё№џяё№џяё№џяё№џяё№џяё№џяё№џяёёџяёёџяёёџяёёџяёёџяёёџяёёџяёёџяёёџяёёџяёёџяёёџяё№џяё№џяё№џяё№џяё№џяё№џюё№џюё№џюё№џю№№џю№№џю№яџю№яџэ№яџэ№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџьяюџьююџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьююџьяюџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџэ№яџэ№яџю№яџю№яџю№яџю№№џю№№џюё№џюё№џюё№џюё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џюё№џюё№џюё№џюё№џю№№џю№№џю№яџю№яџю№яџэ№яџэ№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџьяюџьююџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьюэџьяюџьяюџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџэ№яџэ№яџю№яџю№яџю№яџю№№џю№№џюё№џюё№џюё№џюё№џюё№џюё№џюё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џюё№џюё№џюё№џюё№џюё№џюё№џюё№џю№№џю№№џю№яџю№яџю№яџэ№яџэ№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџьяюџьяюџьюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьюэџьяюџьяюџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџэ№яџэ№яџэ№яџю№яџю№яџю№яџю№яџю№№џю№№џю№№џюё№џюё№џюё№џюё№џюё№џюё№џюё№џюё№џюё№џюё№џюё№џюё№џюё№џюё№џю№№џю№№џю№№џю№яџю№яџю№яџю№яџэ№яџэ№яџэ№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџьяюџьяюџьюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьяюџьяюџьяюџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџю№яџю№яџю№яџю№яџю№яџю№№џю№№џю№№џю№№џю№№џю№№џю№№џю№№џю№№џю№№џю№№џю№№џю№яџю№яџю№яџю№яџю№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџьяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьююџьяюџьяюџьяюџьяюџьяюџьяюџэяюџэяяџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџю№яџю№яџю№яџю№яџю№яџю№яџю№яџю№яџю№яџю№яџю№яџю№яџю№яџю№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэяяџэяюџьяюџьяюџьяюџьяюџьяюџьяюџьююџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьюэџьяюџьяюџьяюџьяюџьяюџьяюџьяюџэяюџэяюџэяяџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэяяџэяюџэяюџьяюџьяюџьяюџьяюџьяюџьяюџьяюџьюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьюэџьяюџьяюџьяюџьяюџьяюџьяюџьяюџэяюџэяюџэяюџэяяџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэяяџэяюџэяюџэяюџьяюџьяюџьяюџьяюџьяюџьяюџьяюџьюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџћћћџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьююџьяюџьяюџьяюџьяюџьяюџьяюџьяюџьяюџэяюџэяюџэяюџэяюџэяяџэяяџэяяџэяяџэяяџэ№яџэ№яџэяяџэяяџэяяџэяяџэяяџэяюџэяюџэяюџэяюџьяюџьяюџьяюџьяюџьяюџьяюџьяюџьяюџьююџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџѕїіџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџSWUџ‡‹‰џ§§§џџџџџ§§§џіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџїјїџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјјџїјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџіјїџћћћџџџџџџџџџžЁŸџSWUџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUќSWUџy|{џћќћџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџˆŠ‰џSWUџSWUќџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUМSWUџTXVџкнмџўўўџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџчшчџTXVџSWUџSWUМџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWURSWUџSWUџTXVџ~€џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ–š˜џ‚…„џTXVџSWUџSWUџSWURџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUSWUНSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUНSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUSWURSWUМSWUќSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUќSWUМSWURSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџрџџџџ€џџџџ€џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ€џџџџ€џџџџрџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ(@€ џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџQWW/W[XХSWUўSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUўW[XЧSXS1џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџW[XХ‚†„ќчшшџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџыььџщъщџ§W[XХџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUўджеџўўўџјљјџјљјџјљјџјљјџјљјџјљјџјљјџјљјџјљјџјљјџјљјџјљјџјљјџјљљџјљљџјљљџјљљџјљљџјљљџјљљџјљљџјљљџјљљџјљљџјљљџјљљџјљљџјљљџјљљџјљјџјљјџјљјџјљјџјљјџјљјџјљјџјљјџјљјџјљјџјљјџјљјџјљјџ§§§џрссџSWUўџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьяюџьяюџьяюџьяюџэяюџэяюџэяяџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэяяџэяюџэяюџьяюџьяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьюэџьяюџьяюџьяюџэяюџэ№яџэ№яџэ№яџэ№яџэ№яџю№яџю№яџю№яџю№яџэ№яџэ№яџэ№яџэ№яџэ№яџэяюџьяюџьяюџьяюџьюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьяюџьяюџьяюџэяюџэ№яџэ№яџэ№яџю№яџю№яџю№№џю№№џюё№џюё№џюё№џюё№џю№№џю№№џю№яџю№яџэ№яџэ№яџэ№яџэяюџьяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџыюэџыюэџыюэџыюэџыюэџыюэџьяюџьяюџэяюџэ№яџэ№яџэ№яџю№яџю№№џюё№џюё№џюё№џяё№џяё№џяё№џяё№џяё№џяё№џюё№џюё№џюё№џю№№џю№яџэ№яџэ№яџэ№яџэяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџыюэџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџыюэџыюэџыюэџыюэџыюэџьяюџьяюџэяюџэ№яџэ№яџю№яџю№№џюё№џяё№џяё№џяё№џяёёџяёёџяёёџяёёџяёёџяёёџяёёџяёёџяё№џяё№џяё№џюё№џю№№џю№яџэ№яџэ№яџэяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџыюэџыюэџыюэџыюэџьяюџьяюџэяюџэ№яџэ№яџю№яџюё№џяё№џяё№џяёёџяёёџяђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџяђёџяёёџяёёџяё№џяё№џюё№џю№яџэ№яџэ№яџэяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџыюэџыюэџыюэџьяюџьяюџэяюџэ№яџю№яџю№№џюё№џяё№џяёёџяёёџ№ђёџ№ђёџ№ђёџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђёџ№ђёџ№ђёџяёёџяёёџяё№џюё№џю№№џю№яџэ№яџэяюџьяюџьяюџыюэџыюэџыюэџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџыюэџыюэџьяюџьяюџэяюџэ№яџю№яџю№№џюё№џяё№џяёёџ№ђёџ№ђёџ№ђёџ№ђђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџ№ђђџ№ђёџ№ђёџ№ђёџяёёџяё№џюё№џю№№џю№яџэ№яџэяюџьяюџьяюџыюэџыюэџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџыюэџыюэџьяюџэяюџэ№яџю№яџю№№џюё№џяё№џяёёџ№ђёџ№ђёџ№ђђџёѓђџёѓђџёѓђџёѓѓџёѓѓџђѓѓџђѓѓџђѓѓџђѓѓџёѓѓџёѓѓџёѓђџёѓђџёѓђџ№ђђџ№ђёџ№ђёџяёёџяё№џюё№џю№№џю№яџэ№яџэяюџьяюџыюэџыюэџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџыюэџьяюџьяюџэ№яџ6цќџи№џЯшџЯшџЯшџЯшџЯшџЯшџ6цќџи№џЯшџЯшџЯшџЯшџЯшџЯшџРzBџЏl8џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џРzBџЏl8џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џэ№яџьяюџьяюџыюэџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџьююџьяюџэяюџэ№яџBшќџ6уљџ5сіџ4рѕџ4рѕџ4рѕџ"кёџЯшџBшќџ6уљџ5сіџ4рѕџ4рѕџ4рѕџ"кёџЯшџУJџИs=џВn9џАm8џАl8џАl8џЌj7џЄe4џУJџИs=џВn9џАm8џАl8џАl8џЌj7џЄe4џэ№яџэяюџьяюџьююџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџьяюџьяюџэ№яџю№яџ`ыќџQщќџOщќџOщќџOщќџOщќџ4рѕџЯшџ`ыќџQщќџOщќџOщќџOщќџOщќџ4рѕџЯшџЦ…QџЛv@џЖp:џЖp:џЖp:џЖp:џАl8џЄe4џЦ…QџЛv@џЖp:џЖp:џЖp:џЖp:џАl8џЄe4џю№яџэ№яџьяюџьяюџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џьююџьяюџэяяџэ№яџю№№џ~я§џ[ыќџOщќџOщќџOщќџOщќџ4рѕџЯшџ~я§џ[ыќџOщќџOщќџOщќџOщќџ4рѕџЯшџЩŠXџМxCџЖp:џЖp:џЖp:џЖp:џБm8џЄe4џЩŠXџМxCџЖp:џЖp:џЖp:џЖp:џБm8џЄe4џю№№џэ№яџэяяџьяюџьююџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џьяюџьяюџэ№яџю№яџюё№џ›ѓ§џeьќџOщќџOщќџOщќџOщќџ5тїџбъџ›ѓ§џeьќџOщќџOщќџOщќџOщќџ5тїџбъџЫ\џНzEџЖp:џЖp:џЖp:џЖp:џГn9џЊi6џЫ\џНzEџЖp:џЖp:џЖp:џЖp:џГn9џЊi6џюё№џю№яџэ№яџьяюџьяюџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џьяюџэяюџэ№яџю№№џюё№џ›ѓ§џiьќџOщќџOщќџOщќџOщќџ7фљџз№џ›ѓ§џiьќџOщќџOщќџOщќџOщќџ7фљџз№џЫ\џНzFџЖp:џЖp:џЖp:џЖp:џЖp:џБm9џЫ\џНzFџЖp:џЖp:џЖp:џЖp:џЖp:џБm9џюё№џю№№џэ№яџэяюџьяюџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џьяюџэ№яџю№яџюё№џяё№џ›ѓ§џzя§џiьќџiьќџiьќџiьќџVъќџ4ујџ›ѓ§џzя§џiьќџiьќџiьќџiьќџVъќџ4ујџЫ\џТ€MџНzFџНzFџНzFџНyDџНyCџМxBџЫ\џТ€MџНzFџНzFџНzFџНyDџНyCџМxBџяё№џюё№џю№яџэ№яџьяюџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џэяюџэ№яџю№яџюё№џяё№џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЪ‹YџШ‡TџЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЪ‹YџШ‡Tџяё№џюё№џю№яџэ№яџэяюџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џэяюџэ№яџю№№џяё№џяёёџ6цќџи№џЯшџЯшџЯшџЯшџЯшџЯшџРzBџЏl8џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џРzBџЏl8џЄe4џЄe4џЄe4џЄe4џЄe4џЄe4џдwџвsџвsџвsџвsџвsџвsџвsџяёёџяё№џю№№џэ№яџэяюџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џэяяџэ№яџюё№џяё№џяђёџBшќџ6уљџ5сіџ4рѕџ4рѕџ4рѕџ"кёџЯшџУJџИs=џВn9џАm8џАl8џАl8џЌj7џЄe4џУJџИs=џВn9џАm8џАl8џАl8џЌj7џЄe4џ+л‚џ(л€џ*м‚џ*м‚џ*м‚џ*м‚џ#й}џвsџяђёџяё№џюё№џэ№яџэяяџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзйиџ§§§џэ№яџю№яџюё№џяё№џ№ђёџ`ыќџQщќџOщќџOщќџOщќџOщќџ4рѕџЯшџЦ…QџЛv@џЖp:џЖp:џЖp:џЖp:џАl8џЄe4џЦ…QџЛv@џЖp:џЖp:џЖp:џЖp:џАl8џЄe4џ:уџ3с‰џ4тŠџ4тŠџ4тŠџ4тŠџ*м‚џвsџ№ђёџяё№џюё№џю№яџэ№яџњћћџтфуџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзйиџ§§§џэ№яџю№яџюё№џяёёџ№ђёџ~я§џ[ыќџOщќџOщќџOщќџOщќџ4рѕџЯшџЩŠXџМxCџЖp:џЖp:џЖp:џЖp:џБm8џЄe4џЩŠXџМxCџЖp:џЖp:џЖp:џЖp:џБm8џЄe4џJъ™џ9уџ4тŠџ4тŠџ4тŠџ4тŠџ*м‚џвsџ№ђёџяёёџюё№џю№яџэ№яџњћћџтфуџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзйиџ§§§џэ№яџю№яџяё№џяёёџ№ђёџ›ѓ§џeьќџOщќџOщќџOщќџOщќџ5тїџбъџЫ\џНzEџЖp:џЖp:џЖp:џЖp:џГn9џЊi6џЫ\џНzEџЖp:џЖp:џЖp:џЖp:џГn9џЊi6џQэžџ>ц‘џ4тŠџ4тŠџ4тŠџ4тŠџ*м‚џвsџ№ђёџяёёџяё№џю№яџэ№яџћћћџтфуџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзйиџ§§§џэ№яџю№№џяё№џяёёџ№ђёџ›ѓ§џiьќџOщќџOщќџOщќџOщќџ7фљџз№џЫ\џНzFџЖp:џЖp:џЖp:џЖp:џЖp:џБm9џЫ\џНzFџЖp:џЖp:џЖp:џЖp:џЖp:џБm9џQэžџ>ц‘џ4тŠџ4тŠџ4тŠџ4тŠџ*м‚џвsџ№ђёџяёёџяё№џю№№џэ№яџћћћџтфуџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзйиџ§§§џэ№яџю№№џяё№џяёёџ№ђёџ›ѓ§џzя§џiьќџiьќџiьќџiьќџVъќџ4ујџЫ\џТ€MџНzFџНzFџНzFџНyDџНyCџМxBџЫ\џТ€MџНzFџНzFџНzFџНyDџНyCџМxBџQэžџDш•џ>ц‘џ>ц‘џ>ц‘џ>ц‘џ5сŠџ%й~џ№ђёџяёёџяё№џю№№џэ№яџћћћџтфуџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзйиџ§§§џэ№яџю№№џяё№џяђёџ№ђёџ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЪ‹YџШ‡TџЫ\џЫ\џЫ\џЫ\џЫ\џЫ\џЪ‹YџШ‡TџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџ№ђёџяђёџяё№џю№№џэ№яџћћћџтфуџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзйиџ§§§џэ№яџю№№џяё№џяђёџ№ђёџ6цќџи№џЯшџЯшџЯшџЯшџЯшџЯшџюџмџвџвџвџвџвџвџюџмџвџвџвџвџвџвџдwџвsџвsџвsџвsџвsџвsџвsџ№ђёџяђёџяё№џю№№џэ№яџћћћџтфуџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзйиџ§§§џэ№яџю№№џяё№џяёёџ№ђёџBшќџ6уљџ5сіџ4рѕџ4рѕџ4рѕџ"кёџЯшџ**яџ$$щџ!!хџ хџ хџ хџпџвџ**яџ$$щџ!!хџ хџ хџ хџпџвџ+л‚џ(л€џ*м‚џ*м‚џ*м‚џ*м‚џ#й}џвsџ№ђёџяёёџяё№џю№№џэ№яџћћћџтфуџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзйиџ§§§џэ№яџю№№џяё№џяёёџ№ђёџ`ыќџQщќџOщќџOщќџOщќџOщќџ4рѕџЯшџ88№џ--яџ))яџ))яџ))яџ))яџ##цџдџ88№џ--яџ))яџ))яџ))яџ))яџ##цџдџ:уџ3с‰џ4тŠџ4тŠџ4тŠџ4тŠџ*м‚џвsџ№ђёџяёёџяё№џю№№џэ№яџћћћџтфуџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзйиџ§§§џэ№яџю№яџяё№џяёёџ№ђёџ~я§џ[ыќџOщќџOщќџOщќџOщќџ4рѕџЯшџEEёџ22№џ))яџ))яџ))яџ))яџ((шџ$$лџEEёџ22№џ))яџ))яџ))яџ))яџ((шџ$$лџJъ™џ9уџ4тŠџ4тŠџ4тŠџ4тŠџ*м‚џвsџ№ђёџяёёџяё№џю№яџэ№яџћћћџтфуџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзйиџ§§§џэ№яџю№яџюё№џяёёџ№ђёџ›ѓ§џeьќџOщќџOщќџOщќџOщќџ5тїџбъџRRђџ66№џ))яџ))яџ))яџ))яџ--ыџ22сџRRђџ66№џ))яџ))яџ))яџ))яџ--ыџ22сџQэžџ>ц‘џ4тŠџ4тŠџ4тŠџ4тŠџ*м‚џвsџ№ђёџяёёџюё№џю№яџэ№яџњћћџтфуџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзйиџ§§§џэ№яџю№яџюё№џяё№џ№ђёџ›ѓ§џiьќџOщќџOщќџOщќџOщќџ7фљџз№џ``ѓџ;;№џ))яџ))яџ))яџ))яџ22эџ@@шџ``ѓџ;;№џ))яџ))яџ))яџ))яџ22эџ@@шџQэžџ>ц‘џ4тŠџ4тŠџ4тŠџ4тŠџ*м‚џвsџ№ђёџяё№џюё№џю№яџэ№яџњћћџтфуџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џэяяџэ№яџюё№џяё№џяђёџ›ѓ§џzя§џiьќџiьќџiьќџiьќџVъќџ4ујџccѓџJJёџ==№џ==№џ<<№џ;;№џBB№џPPяџccѓџJJёџ==№џ==№џ<<№џ;;№џBB№џPPяџQэžџDш•џ>ц‘џ>ц‘џ>ц‘џ>ц‘џ5сŠџ%й~џяђёџяё№џюё№џэ№яџэяяџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џэяюџэ№яџю№№џяё№џяёёџ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџяёёџяё№џю№№џэ№яџэяюџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џэяюџэ№яџю№яџюё№џяё№џюџмџвџвџвџвџвџвџюџмџвџвџвџвџвџвџдwџвsџвsџвsџвsџвsџвsџвsџдwџвsџвsџвsџвsџвsџвsџвsџяё№џюё№џю№яџэ№яџэяюџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џьяюџэ№яџю№яџюё№џяё№џ**яџ$$щџ!!хџ хџ хџ хџпџвџ**яџ$$щџ!!хџ хџ хџ хџпџвџ+л‚џ(л€џ*м‚џ*м‚џ*м‚џ*м‚џ#й}џвsџ+л‚џ(л€џ*м‚џ*м‚џ*м‚џ*м‚џ#й}џвsџяё№џюё№џю№яџэ№яџьяюџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џьяюџэяюџэ№яџю№№џюё№џ88№џ--яџ))яџ))яџ))яџ))яџ##цџдџ88№џ--яџ))яџ))яџ))яџ))яџ##цџдџ:уџ3с‰џ4тŠџ4тŠџ4тŠџ4тŠџ*м‚џвsџ:уџ3с‰џ4тŠџ4тŠџ4тŠџ4тŠџ*м‚џвsџюё№џю№№џэ№яџэяюџьяюџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џьяюџьяюџэ№яџю№яџюё№џEEёџ22№џ))яџ))яџ))яџ))яџ((шџ$$лџEEёџ22№џ))яџ))яџ))яџ))яџ((шџ$$лџJъ™џ9уџ4тŠџ4тŠџ4тŠџ4тŠџ*м‚џвsџJъ™џ9уџ4тŠџ4тŠџ4тŠџ4тŠџ*м‚џвsџюё№џю№яџэ№яџьяюџьяюџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џьююџьяюџэяяџэ№яџю№№џRRђџ66№џ))яџ))яџ))яџ))яџ--ыџ22сџRRђџ66№џ))яџ))яџ))яџ))яџ--ыџ22сџQэžџ>ц‘џ4тŠџ4тŠџ4тŠџ4тŠџ*м‚џвsџQэžџ>ц‘џ4тŠџ4тŠџ4тŠџ4тŠџ*м‚џвsџю№№џэ№яџэяяџьяюџьююџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџьяюџьяюџэ№яџю№яџ``ѓџ;;№џ))яџ))яџ))яџ))яџ22эџ@@шџ``ѓџ;;№џ))яџ))яџ))яџ))яџ22эџ@@шџQэžџ>ц‘џ4тŠџ4тŠџ4тŠџ4тŠџ*м‚џвsџQэžџ>ц‘џ4тŠџ4тŠџ4тŠџ4тŠџ*м‚џвsџю№яџэ№яџьяюџьяюџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџьююџьяюџэяюџэ№яџccѓџJJёџ==№џ==№џ<<№џ;;№џBB№џPPяџccѓџJJёџ==№џ==№џ<<№џ;;№џBB№џPPяџQэžџDш•џ>ц‘џ>ц‘џ>ц‘џ>ц‘џ5сŠџ%й~џQэžџDш•џ>ц‘џ>ц‘џ>ц‘џ>ц‘џ5сŠџ%й~џэ№яџэяюџьяюџьююџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџыюэџьяюџьяюџэ№яџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџccѓџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџэ№яџьяюџьяюџыюэџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџыюэџыюэџьяюџэяюџэ№яџю№яџю№№џюё№џяё№џяёёџ№ђёџ№ђёџ№ђђџёѓђџёѓђџёѓђџёѓѓџёѓѓџђѓѓџђѓѓџђѓѓџђѓѓџёѓѓџёѓѓџёѓђџёѓђџёѓђџ№ђђџ№ђёџ№ђёџяёёџяё№џюё№џю№№џю№яџэ№яџэяюџьяюџыюэџыюэџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџыюэџыюэџьяюџьяюџэяюџэ№яџю№яџю№№џюё№џяё№џяёёџ№ђёџ№ђёџ№ђёџ№ђђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџ№ђђџ№ђёџ№ђёџ№ђёџяёёџяё№џюё№џю№№џю№яџэ№яџэяюџьяюџьяюџыюэџыюэџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџыюэџыюэџыюэџьяюџьяюџэяюџэ№яџю№яџю№№џюё№џяё№џяёёџяёёџ№ђёџ№ђёџ№ђёџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђђџ№ђёџ№ђёџ№ђёџяёёџяёёџяё№џюё№џю№№џю№яџэ№яџэяюџьяюџьяюџыюэџыюэџыюэџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџыюэџыюэџыюэџыюэџьяюџьяюџэяюџэ№яџэ№яџю№яџюё№џяё№џяё№џяёёџяёёџяђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџяђёџяёёџяёёџяё№џяё№џюё№џю№яџэ№яџэ№яџэяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџыюэџыюэџыюэџыюэџыюэџьяюџьяюџэяюџэ№яџэ№яџю№яџю№№џюё№џяё№џяё№џяё№џяёёџяёёџяёёџяёёџяёёџяёёџяёёџяёёџяё№џяё№џяё№џюё№џю№№џю№яџэ№яџэ№яџэяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџыюэџыюэџыюэџыюэџыюэџыюэџьяюџьяюџэяюџэ№яџэ№яџэ№яџю№яџю№№џюё№џюё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џяё№џюё№џюё№џю№№џю№яџэ№яџэ№яџэ№яџэяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџыюэџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьяюџьяюџьяюџэяюџэ№яџэ№яџэ№яџю№яџю№яџю№№џю№№џюё№џюё№џюё№џюё№џю№№џю№№џю№яџю№яџэ№яџэ№яџэ№яџэяюџьяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьюэџьяюџьяюџьяюџэяюџэ№яџэ№яџэ№яџэ№яџэ№яџю№яџю№яџю№яџю№яџэ№яџэ№яџэ№яџэ№яџэ№яџэяюџьяюџьяюџьяюџьюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџзииџ§§§џыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьяюџьяюџьяюџьяюџэяюџэяюџэяяџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэяяџэяюџэяюџьяюџьяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџњћћџтууџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUўгееџџџџџћћћџћћћџћћћџћћћџћћћџћћћџћћћџћћћџћћћџћћћџћћћџћћћџћћћџћћћџћќћџћќћџћќћџћќћџћќћџћќћџћќћџћќћџћќћџћќћџћќћџћќћџћќћџћќћџћћћџћћћџћћћџћћћџћћћџћћћџћћћџћћћџћћћџћћћџћћћџћћћџћћћџћћћџўўўџпсрџSWUўџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџW[XЧz~|ќмннџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџрссџнооџ„‡†ќW[XЧџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSXS1W[XХSWUўSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUўW[XЧUUU3џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ€џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ€џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ(0` џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџX][kX\ZіX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZџX\ZіX][kџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџW[YіѓєѓџџџџџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўўўџўџџџїјїџW[YіџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџюё№џыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьяюџьяюџьяюџэяюџэяяџэяяџэ№яџэ№яџэяяџэяяџэяюџьяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџыюэџыюэџыюэџьяюџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџюё№џыюэџыюэџыюэџыюэџыюэџыюэџьяюџьяюџьяюџэяяџэ№яџэ№яџэ№яџю№яџю№яџю№яџю№яџэ№яџэ№яџэ№яџэяяџьяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџыюэџьяюџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџюё№џыюэџыюэџыюэџыюэџыюэџьяюџьяюџэяяџэ№яџю№яџю№яџю№№џюё№џюё№џюё№џюё№џюё№џюё№џю№№џю№яџю№яџэ№яџэяяџьяюџьяюџыюэџыюэџыюэџыюэџыюэџьяюџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџюё№џыюэџыюэџыюэџыюэџьяюџьяюџэ№яџэ№яџю№яџюё№џяё№џяё№џяё№џяёёџяёёџяёёџяёёџяё№џяё№џяё№џюё№џю№яџэ№яџэ№яџьяюџьяюџыюэџыюэџыюэџыюэџьяюџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџюё№џыюэџыюэџыюэџьяюџэяюџэ№яџю№яџюё№џяё№џяё№џяёёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџяёёџяё№џяё№џюё№џю№яџэ№яџэяюџьяюџыюэџыюэџыюэџьяюџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџюё№џыюэџыюэџьяюџэяюџэ№яџю№яџюё№џяё№џяёёџ№ђёџ№ђёџ№ђђџ№ђђџёѓђџёѓђџёѓђџёѓђџ№ђђџ№ђђџ№ђёџ№ђёџяёёџяё№џюё№џю№яџэ№яџэяюџьяюџыюэџыюэџьяюџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџюё№џыюэџьяюџьяюџэ№яџю№яџюё№џяё№џяђёџ№ђёџ№ђђџёѓђџёѓђџёѓђџёѓѓџёѓѓџёѓѓџёѓѓџёѓђџёѓђџёѓђџ№ђђџ№ђёџяђёџяё№џюё№џю№яџэ№яџьяюџьяюџыюэџьяюџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџюё№џыюэџьяюџэяяџ6цќџащџЯшџЯшџЯшџЯшџ6цќџащџЯшџЯшџЯшџЯшџРzBџІg5џЄe4џЄe4џЄe4џЄe4џРzBџІg5џЄe4џЄe4џЄe4џЄe4џэяяџьяюџыюэџьяюџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџюё№џьяюџэяюџэ№яџOщќџOщќџOщќџOщќџOщќџЯшџOщќџOщќџOщќџOщќџOщќџЯшџФLџЖp:џЖp:џЖp:џЖp:џЄe4џФLџЖp:џЖp:џЖp:џЖp:џЄe4џэ№яџэяюџьяюџьяюџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџюё№џьяюџэ№яџю№яџwю§џOщќџOщќџOщќџOщќџЯшџwю§џOщќџOщќџOщќџOщќџЯшџЩ‰VџЖp:џЖp:џЖp:џЖp:џЄe4џЩ‰VџЖp:џЖp:џЖp:џЖp:џЄe4џю№яџэ№яџьяюџьяюџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџяђёџэяюџэ№яџюё№џ›ѓ§џOщќџOщќџOщќџOщќџвыџ›ѓ§џOщќџOщќџOщќџOщќџвыџЫ\џЖp:џЖp:џЖp:џЖp:џЌj7џЫ\џЖp:џЖp:џЖp:џЖp:џЌj7џюё№џэ№яџэяюџэ№яџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџяђёџэ№яџю№яџяё№џ›ѓ§џOщќџOщќџOщќџOщќџ кѓџ›ѓ§џOщќџOщќџOщќџOщќџ кѓџЫ\џЖp:џЖp:џЖp:џЖp:џЕp;џЫ\џЖp:џЖp:џЖp:џЖp:џЕp;џяё№џю№яџэ№яџэ№яџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџяђёџэ№яџю№№џяё№џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џЫ\џЫ\џЫ\џЫ\џЪŒZџШ‡TџЫ\џЫ\џЫ\џЫ\џЪŒZџШ‡Tџяё№џю№№џэ№яџэ№яџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќ§ќџ№ђёџэ№яџюё№џяёёџ6цќџащџЯшџЯшџЯшџЯшџРzBџІg5џЄe4џЄe4џЄe4џЄe4џРzBџІg5џЄe4џЄe4џЄe4џЄe4џеwџвsџвsџвsџвsџвsџяёёџюё№џэ№яџю№яџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќ§ќџ№ђђџю№яџяё№џяђёџOщќџOщќџOщќџOщќџOщќџЯшџФLџЖp:џЖp:џЖp:џЖp:џЄe4џФLџЖp:џЖp:џЖp:џЖp:џЄe4џ1о‡џ4тŠџ4тŠџ4тŠџ4тŠџвsџяђёџяё№џю№яџю№№џўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќ§ќџ№ђђџю№яџяё№џ№ђёџwю§џOщќџOщќџOщќџOщќџЯшџЩ‰VџЖp:џЖp:џЖp:џЖp:џЄe4џЩ‰VџЖp:џЖp:џЖp:џЖp:џЄe4џFш–џ4тŠџ4тŠџ4тŠџ4тŠџвsџ№ђёџяё№џю№яџюё№џўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќ§ќџ№ђђџю№№џяё№џ№ђёџ›ѓ§џOщќџOщќџOщќџOщќџвыџЫ\џЖp:џЖp:џЖp:џЖp:џЌj7џЫ\џЖp:џЖp:џЖp:џЖp:џЌj7џQэžџ4тŠџ4тŠџ4тŠџ4тŠџвsџ№ђёџяё№џю№№џюё№џўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќ§ќџ№ђђџю№№џяё№џ№ђёџ›ѓ§џOщќџOщќџOщќџOщќџ кѓџЫ\џЖp:џЖp:џЖp:џЖp:џЕp;џЫ\џЖp:џЖp:џЖp:џЖp:џЕp;џQэžџ4тŠџ4тŠџ4тŠџ4тŠџвsџ№ђёџяё№џю№№џюё№џўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќ§ќџ№ђђџюё№џяё№џ№ђёџ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џЫ\џЫ\џЫ\џЫ\џЪŒZџШ‡TџЫ\џЫ\џЫ\џЫ\џЪŒZџШ‡TџQэžџQэžџQэžџQэžџQэžџQэžџ№ђёџяёёџюё№џюё№џўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќ§ќџ№ђђџюё№џяё№џ№ђёџ6цќџащџЯшџЯшџЯшџЯшџюџвџвџвџвџвџюџвџвџвџвџвџеwџвsџвsџвsџвsџвsџ№ђёџяёёџюё№џюё№џўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќ§ќџ№ђђџю№№џяё№џ№ђёџOщќџOщќџOщќџOщќџOщќџЯшџ00яџ))яџ))яџ))яџ))яџвџ00яџ))яџ))яџ))яџ))яџвџ1о‡џ4тŠџ4тŠџ4тŠџ4тŠџвsџ№ђёџяё№џю№№џюё№џўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќ§ќџ№ђђџю№№џяё№џ№ђёџwю§џOщќџOщќџOщќџOщќџЯшџBBёџ))яџ))яџ))яџ))яџ""кџBBёџ))яџ))яџ))яџ))яџ""кџFш–џ4тŠџ4тŠџ4тŠџ4тŠџвsџ№ђёџяё№џю№№џюё№џўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќ§ќџ№ђђџю№яџяё№џ№ђёџ›ѓ§џOщќџOщќџOщќџOщќџвыџTTђџ))яџ))яџ))яџ))яџ44уџTTђџ))яџ))яџ))яџ))яџ44уџQэžџ4тŠџ4тŠџ4тŠџ4тŠџвsџ№ђёџяё№џю№яџюё№џўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќ§ќџ№ђђџю№яџяё№џяђёџ›ѓ§џOщќџOщќџOщќџOщќџ кѓџccѓџ))яџ))яџ))яџ))яџGGыџccѓџ))яџ))яџ))яџ))яџGGыџQэžџ4тŠџ4тŠџ4тŠџ4тŠџвsџяђёџяё№џю№яџю№№џўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќ§ќџ№ђёџэ№яџюё№џяёёџ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џ›ѓ§џccѓџccѓџccѓџccѓџccѓџaaѓџccѓџccѓџccѓџccѓџccѓџaaѓџQэžџQэžџQэžџQэžџQэžџQэžџяёёџюё№џэ№яџю№яџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџяђёџэ№яџю№№џяё№џюџвџвџвџвџвџюџвџвџвџвџвџеwџвsџвsџвsџвsџвsџеwџвsџвsџвsџвsџвsџяё№џю№№џэ№яџэ№яџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџяђёџэ№яџю№яџяё№џ00яџ))яџ))яџ))яџ))яџвџ00яџ))яџ))яџ))яџ))яџвџ1о‡џ4тŠџ4тŠџ4тŠџ4тŠџвsџ1о‡џ4тŠџ4тŠџ4тŠџ4тŠџвsџяё№џю№яџэ№яџэ№яџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџяђёџэяюџэ№яџюё№џBBёџ))яџ))яџ))яџ))яџ""кџBBёџ))яџ))яџ))яџ))яџ""кџFш–џ4тŠџ4тŠџ4тŠџ4тŠџвsџFш–џ4тŠџ4тŠџ4тŠџ4тŠџвsџюё№џэ№яџэяюџэ№яџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџюё№џьяюџэ№яџю№яџTTђџ))яџ))яџ))яџ))яџ44уџTTђџ))яџ))яџ))яџ))яџ44уџQэžџ4тŠџ4тŠџ4тŠџ4тŠџвsџQэžџ4тŠџ4тŠџ4тŠџ4тŠџвsџю№яџэ№яџьяюџьяюџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџюё№џьяюџэяюџэ№яџccѓџ))яџ))яџ))яџ))яџGGыџccѓџ))яџ))яџ))яџ))яџGGыџQэžџ4тŠџ4тŠџ4тŠџ4тŠџвsџQэžџ4тŠџ4тŠџ4тŠџ4тŠџвsџэ№яџэяюџьяюџьяюџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџюё№џыюэџьяюџэяяџccѓџccѓџccѓџccѓџccѓџaaѓџccѓџccѓџccѓџccѓџccѓџaaѓџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџQэžџэяяџьяюџыюэџьяюџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџюё№џыюэџьяюџьяюџэ№яџю№яџюё№џяё№џяђёџ№ђёџ№ђђџёѓђџёѓђџёѓђџёѓѓџёѓѓџёѓѓџёѓѓџёѓђџёѓђџёѓђџ№ђђџ№ђёџяђёџяё№џюё№џю№яџэ№яџьяюџьяюџыюэџьяюџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџюё№џыюэџыюэџьяюџэяюџэ№яџю№яџюё№џяё№џяёёџ№ђёџ№ђёџ№ђђџ№ђђџёѓђџёѓђџёѓђџёѓђџ№ђђџ№ђђџ№ђёџ№ђёџяёёџяё№џюё№џю№яџэ№яџэяюџьяюџыюэџыюэџьяюџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџюё№џыюэџыюэџыюэџьяюџэяюџэ№яџю№яџюё№џяё№џяё№џяёёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџяёёџяё№џяё№џюё№џю№яџэ№яџэяюџьяюџыюэџыюэџыюэџьяюџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџюё№џыюэџыюэџыюэџыюэџьяюџьяюџэ№яџэ№яџю№яџюё№џяё№џяё№џяё№џяёёџяёёџяёёџяёёџяё№џяё№џяё№џюё№џю№№џэ№яџэ№яџьяюџьяюџыюэџыюэџыюэџыюэџьяюџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџюё№џыюэџыюэџыюэџыюэџыюэџьяюџьяюџэяяџэ№яџю№яџю№яџю№№џюё№џюё№џюё№џюё№џюё№џюё№џю№№џю№яџю№яџэ№яџэяяџьяюџьяюџыюэџыюэџыюэџыюэџыюэџьяюџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџюё№џыюэџыюэџыюэџыюэџыюэџыюэџьяюџьяюџьяюџэяяџэ№яџэ№яџэ№яџю№яџю№яџю№яџю№яџэ№яџэ№яџэ№яџэяяџьяюџьяюџьяюџыюэџыюэџыюэџыюэџыюэџыюэџьяюџўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџSWUџќќќџ№ђёџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџюё№џюё№џюё№џяё№џяёёџяёёџяёёџяёёџяёёџяёёџяё№џюё№џюё№џюё№џэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџэ№яџюё№џўўўџSWUџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџW[Yія№яџ§§§џ§§§џ§§§џ§§§џ§§§џ§§§џ§§§џ§§§џ§§§џ§§§џ§§§џ§§§џ§ў§џ§ў§џ§ў§џ§ў§џ§ў§џ§ў§џ§ў§џ§ў§џ§§§џ§§§џ§§§џ§§§џ§§§џ§§§џ§§§џ§§§џ§§§џ§§§џ§§§џ§§§џѓєѓџW[YіџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџX][kW[YіSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџSWUџW[YіX][kџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџўќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ќ?ўџџџџџџџџџџџџџџџџџџ( @ џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџorqšЇЊЉь­Џ­ы­Џ­ы­Џ­ы­Џ­ы­Џ­ы­Џ­ы­Џ­ы­Џ­ы­Џ­ы­Џ­ы­Џ­ы­Џ­ы­Џ­ы­Џ­ы­Џ­ы­Џ­ы­Џ­ы­Џ­ы­Џ­ы­Џ­ыЈЊЉэrvtœџџџџџџџџџџџџџџџџџџџџџџџџ–™˜щїјјџёѓѓџёѓѓџёѓѓџёѓѓџёѓѓџђѓѓџђєѓџђєѓџѓєѓџѓєѓџѓєѓџѓєѓџђєѓџђєѓџђѓѓџёѓѓџёѓѓџёѓѓџёѓѓџёѓѓџіјїџ ЂЁыџџџџџџџџџџџџџџџџџџџџџџџџš›шєѕѕџыюэџыюэџыюэџыюэџьяюџэяюџэ№яџэ№яџю№яџю№яџю№яџю№яџэ№яџэ№яџэяюџьяюџыюэџыюэџыюэџыюэџѓєєџЄІЄщџџџџџџџџџџџџџџџџџџџџџџџџš›шєѕѕџыюэџыюэџыюэџьяюџэ№яџю№яџюё№џяё№џяё№џяё№џяё№џяё№џяё№џюё№џю№яџэ№яџьяюџыюэџыюэџыюэџѓєєџЄІЄщџџџџџџџџџџџџџџџџџџџџџџџџš›шєѕѕџыюэџыюэџьяюџэ№яџю№№џяё№џяёёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџяёёџяё№џю№№џэ№яџьяюџыюэџыюэџѓєєџЄІЄщџџџџџџџџџџџџџџџџџџџџџџџџš›шєѕѕџыюэџьяюџэ№яџю№№џяё№џ№ђёџ№ђђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџ№ђђџ№ђёџяё№џю№№џэ№яџьяюџыюэџѓєєџЄІЄщџџџџџџџџџџџџџџџџџџџџџџџџš›шєѕѕџьяюџэяяџ.уљџияџияџ въџ.уљџияџияџ въџЛv@џЊi6џЊi6џІf5џЛv@џЊi6џЊi6џІf5џэяяџьяюџѓєєџЄІЄщџџџџџџџџџџџџџџџџџџџџџџџџš›шєѕѕџьяюџэ№яџ`ь§џOщќџOщќџияџ`ь§џOщќџOщќџияџС~JџЖp:џЖp:џЊi6џС~JџЖp:џЖp:џЊi6џэ№яџьяюџѓєєџЄІЄщџџџџџџџџџџџџџџџџџџџџџџџџš›шєіѕџэ№яџю№№џ№§џOщќџOщќџмѓџ№§џOщќџOщќџмѓџФƒPџЖp:џЖp:џБm9џФƒPџЖp:џЖp:џБm9џю№№џэ№яџѓѕєџЄІЄщџџџџџџџџџџџџџџџџџџџџџџџџš›шєіѕџэ№яџяё№џ’ђ§џ№§џ№§џoэќџ’ђ§џ№§џ№§џoэќџЩŠXџФƒPџФƒPџУ€KџЩŠXџФƒPџФƒPџУ€Kџяё№џэ№яџѓѕєџЄІЄщџџџџџџџџџџџџџџџџџџџџџџџџš›шєіѕџю№яџяё№џ.уљџияџияџ въџЛv@џЊi6џЊi6џІf5џЛv@џЊi6џЊi6џІf5џжzџ з{џ з{џдvџяё№џю№яџѓѕєџЄІЄщџџџџџџџџџџџџџџџџџџџџџџџџš›шѕіѕџю№№џяђёџ`ь§џOщќџOщќџияџС~JџЖp:џЖp:џЊi6џС~JџЖp:џЖp:џЊi6џ;уŽџ4тŠџ4тŠџ з{џяђёџю№№џєѕѕџЄІЄщџџџџџџџџџџџџџџџџџџџџџџџџšœшѕїіџюё№џ№ђёџ№§џOщќџOщќџмѓџФƒPџЖp:џЖp:џБm9џФƒPџЖp:џЖp:џБm9џGщ—џ4тŠџ4тŠџ з{џ№ђёџюё№џєіѕџЄІЅщџџџџџџџџџџџџџџџџџџџџџџџџšœшѕїіџюё№џ№ђёџ’ђ§џ№§џ№§џoэќџЩŠXџФƒPџФƒPџУ€KџЩŠXџФƒPџФƒPџУ€KџNьœџGщ—џGщ—џ>х‘џ№ђёџюё№џєіѕџЄІЅщџџџџџџџџџџџџџџџџџџџџџџџџšœшѕїіџюё№џ№ђёџ.уљџияџияџ въџшџмџмџеџшџмџмџеџжzџ з{џ з{џдvџ№ђёџюё№џєіѕџЄІЅщџџџџџџџџџџџџџџџџџџџџџџџџšœшѕїіџюё№џ№ђёџ`ь§џOщќџOщќџияџ66№џ))яџ))яџ!!рџ66№џ))яџ))яџ!!рџ;уŽџ4тŠџ4тŠџ з{џ№ђёџюё№џєіѕџЄІЅщџџџџџџџџџџџџџџџџџџџџџџџџš›шѕіѕџю№№џяђёџ№§џOщќџOщќџмѓџGGёџ))яџ))яџ44шџGGёџ))яџ))яџ44шџGщ—џ4тŠџ4тŠџ з{џяђёџю№№џєѕѕџЄІЄщџџџџџџџџџџџџџџџџџџџџџџџџš›шєіѕџю№яџяё№џ’ђ§џ№§џ№§џoэќџ\\ѓџOOђџOOђџVVђџ\\ѓџOOђџOOђџVVђџNьœџGщ—џGщ—џ>х‘џяё№џю№яџѓѕєџЄІЄщџџџџџџџџџџџџџџџџџџџџџџџџš›шєіѕџэ№яџяё№џшџмџмџеџшџмџмџеџжzџ з{џ з{џдvџжzџ з{џ з{џдvџяё№џэ№яџѓѕєџЄІЄщџџџџџџџџџџџџџџџџџџџџџџџџš›шєіѕџэ№яџю№№џ66№џ))яџ))яџ!!рџ66№џ))яџ))яџ!!рџ;уŽџ4тŠџ4тŠџ з{џ;уŽџ4тŠџ4тŠџ з{џю№№џэ№яџѓѕєџЄІЄщџџџџџџџџџџџџџџџџџџџџџџџџš›шєѕѕџьяюџэ№яџGGёџ))яџ))яџ44шџGGёџ))яџ))яџ44шџGщ—џ4тŠџ4тŠџ з{џGщ—џ4тŠџ4тŠџ з{џэ№яџьяюџѓєєџЄІЄщџџџџџџџџџџџџџџџџџџџџџџџџš›шєѕѕџьяюџэяяџ\\ѓџOOђџOOђџVVђџ\\ѓџOOђџOOђџVVђџNьœџGщ—џGщ—џ>х‘џNьœџGщ—џGщ—џ>х‘џэяяџьяюџѓєєџЄІЄщџџџџџџџџџџџџџџџџџџџџџџџџš›шєѕѕџыюэџьяюџэ№яџю№№џяё№џ№ђёџ№ђђџёѓђџёѓђџёѓђџёѓђџёѓђџёѓђџ№ђђџ№ђёџяё№џю№№џэ№яџьяюџыюэџѓєєџЄІЄщџџџџџџџџџџџџџџџџџџџџџџџџš›шєѕѕџыюэџыюэџьяюџэ№яџю№№џяё№џяёёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџ№ђёџяёёџяё№џю№№џэ№яџьяюџыюэџыюэџѓєєџЄІЄщџџџџџџџџџџџџџџџџџџџџџџџџš›шєѕѕџыюэџыюэџыюэџьяюџэ№яџю№яџюё№џяё№џяё№џяё№џяё№џяё№џяё№џюё№џю№яџэ№яџьяюџыюэџыюэџыюэџѓєєџЄІЄщџџџџџџџџџџџџџџџџџџџџџџџџš›шєѕѕџыюэџыюэџыюэџыюэџьяюџэяюџэ№яџэ№яџю№яџю№яџю№яџю№яџэ№яџэ№яџэяюџьяюџыюэџыюэџыюэџыюэџѓєєџЄІЄщџџџџџџџџџџџџџџџџџџџџџџџџ–˜—щјљљџѓєєџѓєєџѓєєџѓєєџѓєєџѓєєџѓѕєџѓѕєџєѕєџєѕєџєѕєџєѕєџѓѕєџѓѕєџѓєєџѓєєџѓєєџѓєєџѓєєџѓєєџїјјџžЁЁыџџџџџџџџџџџџџџџџџџџџџџџџlpn™žЁ ыЄІЄщЄІЄщЄІЄщЄІЄщЄІЄщЄІЄщЄІЄщЄІЄщЄІЄщЄІЄщЄІЄщЄІЄщЄІЄщЄІЄщЄІЄщЄІЄщЄІЄщЄІЄщЄІЄщЄІЄщ ЂЁыorqšџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ№№№№№№№№№№№№№№№№№№№№№№№№№№№№џџџџџџџџ(  џџџ†ƒТy|џy|џy|џy|џy|џy|џy|џy|џy|џy|џy|џy|џ†ƒТџџџџџџy|џ§§§џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ§§§џy|џџџџџџџy|џџџџџыюэџэяяџюё№џяёёџяђёџяђёџяёёџюё№џэяяџыюэџџџџџy|џџџџџџџy|џџџџџэяяџюё№џ№ђёџёѓђџђѓѓџђѓѓџёѓђџ№ђёџюё№џэяяџџџџџy|џџџџџџџy|џџџџџю№№џЭхџЦнџЦнџЦнџВ}TџЄe4џЄe4џЄe4џю№№џџџџџy|џџџџџџџy|џџџџџяёёџ;н№џвшџацџЭуџТ—vџЄe4џЄe4џЄe4џяёёџџџџџy|џџџџџџџy|џџџџџ№ђёџrэћџ<хљџ8уїџ3рѕџЧ ‚џЄe4џЄe4џЄe4џ№ђёџџџџџy|џџџџџџџy|џџџџџ№ђђџƒђџџƒђџџƒђџџёўџЧ ‚џЧ ‚џЧ ‚џХ~џ№ђђџџџџџy|џџџџџџџy|џџџџџ№ђђџKKђџ))яџ))яџ))яџOн•џвsџвsџвsџ№ђђџџџџџy|џџџџџџџy|џџџџџ№ђёџooєџ))яџ))яџ))яџˆшИџвsџвsџвsџ№ђёџџџџџy|џџџџџџџy|џџџџџяёёџ{{ѕџ))яџ))яџ))яџˆшИџвsџвsџвsџяёёџџџџџy|џџџџџџџy|џџџџџю№№џ{{ѕџ{{ѕџ{{ѕџwwѕџˆшИџˆшИџˆшИџˆшИџю№№џџџџџy|џџџџџџџy|џџџџџэяяџюё№џ№ђёџёѓђџђѓѓџђѓѓџёѓђџ№ђёџюё№џэяяџџџџџy|џџџџџџџy|џџџџџыюэџэяяџюё№џяёёџяђёџяђёџяёёџюё№џэяяџыюэџџџџџy|џџџџџџџy|џ§§§џџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџџ§§§џy|џџџџџџџ†ƒТy|џy|џy|џy|џy|џy|џy|џy|џy|џy|џy|џy|џ†ƒТџџџ€€€€€€€€€€€€€€€€pentobi-7.2/windows_installer/install.nsis.in000066400000000000000000000111601227240712600215440ustar00rootroot00000000000000; Script for creating a Windows installer with NSIS ; (http://nsis.sf.net) !define PENTOBI_VERSION "@PENTOBI_VERSION@" !define PENTOBI_SRC_DIR "@CMAKE_SOURCE_DIR@" !define PENTOBI_BUILD_DIR "@CMAKE_BINARY_DIR@" !define QT_TRANSLATIONS_DIR "@QT_TRANSLATIONS_DIR@" !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\Pentobi" SetCompressor /SOLID lzma !define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\orange-install.ico" !define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\orange-uninstall.ico" !define MUI_WELCOMEFINISHPAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Wizard\orange.bmp" !define MUI_COMPONENTSPAGE_NODESC !include "MUI.nsh" !insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_LICENSE "${PENTOBI_SRC_DIR}\COPYING" !insertmacro MUI_PAGE_COMPONENTS !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_INSTFILES !define MUI_FINISHPAGE_RUN "$INSTDIR\Pentobi.exe" !insertmacro MUI_PAGE_FINISH !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES !insertmacro MUI_LANGUAGE "English" !insertmacro MUI_LANGUAGE "German" !insertmacro MUI_RESERVEFILE_INSTALLOPTIONS !define ADD_START_MENU_ENTRY_DEFAULT "Add start menu entry" !define CREATE_DESKTOP_SHORTCUT_DEFAULT "Create desktop shortcut" !define INSTALLER_TITLE_DEFAULT "Pentobi ${PENTOBI_VERSION} Installer" LangString ADD_START_MENU_ENTRY ${LANG_ENGLISH} \ "${ADD_START_MENU_ENTRY_DEFAULT}" LangString CREATE_DESKTOP_SHORTCUT ${LANG_ENGLISH} \ "${CREATE_DESKTOP_SHORTCUT_DEFAULT}" LangString INSTALLER_TITLE ${LANG_ENGLISH} \ "${INSTALLER_TITLE_DEFAULT}" !include "${PENTOBI_SRC_DIR}\windows_installer\German.nsh" Name "Pentobi" Caption "$(INSTALLER_TITLE)" OutFile "pentobi-${PENTOBI_VERSION}-install.exe" InstallDir "$PROGRAMFILES\Pentobi" InstallDirRegKey HKLM "Software\Pentobi" "" ; Set admin level, needed for shortcut removal on Vista ; (http://nsis.sf.net/Shortcuts_removal_fails_on_Windows_Vista) RequestExecutionLevel admin Section IfFileExists "$INSTDIR\Uninstall.exe" 0 +2 ExecWait '"$INSTDIR\Uninstall.exe" /S _?=$INSTDIR' SetOutPath "$INSTDIR\translations" File "${PENTOBI_BUILD_DIR}\src\libpentobi_gui\*.qm" File "${PENTOBI_BUILD_DIR}\src\pentobi\*.qm" File "${QT_TRANSLATIONS_DIR}\qt_de.qm" SetOutPath "$INSTDIR\books" File "${PENTOBI_SRC_DIR}\src\books\book_*.blksgf" SetOutPath "$INSTDIR" File /r "${PENTOBI_SRC_DIR}\src\pentobi\manual" File /oname=COPYING.txt "${PENTOBI_SRC_DIR}\COPYING" File /oname=Pentobi.exe "${PENTOBI_BUILD_DIR}\src\pentobi\pentobi.exe" File "${PENTOBI_SRC_DIR}\src\pentobi\pentobi.ico" File "${PENTOBI_SRC_DIR}\windows_installer\qt.conf" WriteRegStr HKLM "Software\Pentobi" "" $INSTDIR WriteUninstaller $INSTDIR\Uninstall.exe WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "Pentobi" WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${PENTOBI_VERSION}" WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\pentobi.ico" WriteRegStr HKLM "${UNINST_KEY}" "URLInfoAbout" "http://pentobi.sf.net/" WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$INSTDIR\Uninstall.exe" File "${PENTOBI_SRC_DIR}\windows_installer\blksgf.ico" WriteRegStr HKCR ".blksgf" "" "Pentobi" WriteRegStr HKCR ".blksgf" "Content Type" "application/x-blokus-sgf" WriteRegStr HKCR "Pentobi" "" "Blokus Game" WriteRegStr HKCR "Pentobi\DefaultIcon" "" "$INSTDIR\blksgf.ico" WriteRegStr HKCR "Pentobi\shell\open\command" "" \ "$\"$INSTDIR\Pentobi.exe$\" $\"%1$\"" WriteRegStr HKCR "MIME\Database\Content Type\application/x-blokus-sgf" \ "Extension" ".blksgf" WriteRegStr HKCR "Applications\Pentobi.exe" "SupportedTypes" ".blksgf" WriteRegStr HKCR "Applications\Pentobi\shell\open\command" "" \ "$\"$INSTDIR\Pentobi.exe$\" $\"%1$\"" SectionEnd Section "$(ADD_START_MENU_ENTRY)" SetShellVarContext all CreateDirectory "$SMPROGRAMS\Games" CreateShortCut "$SMPROGRAMS\Games\Pentobi.lnk" "$INSTDIR\Pentobi.exe" SectionEnd Section "$(CREATE_DESKTOP_SHORTCUT)" SetShellVarContext all CreateShortCut "$DESKTOP\Pentobi.lnk" "$INSTDIR\Pentobi.exe" SectionEnd Section "Uninstall" Delete "$INSTDIR\Uninstall.exe" Delete "$INSTDIR\Pentobi.exe" Delete "$INSTDIR\qt.conf" Delete "$INSTDIR\COPYING.txt" Delete "$INSTDIR\pentobi.ico" Delete "$INSTDIR\blksgf.ico" RmDir /r "$INSTDIR\books" RmDir /r "$INSTDIR\translations" RmDir /r "$INSTDIR\manual" RmDir "$INSTDIR" SetShellVarContext all Delete "$SMPROGRAMS\Games\Pentobi.lnk" Delete "$DESKTOP\Pentobi.lnk" DeleteRegKey HKLM "Software\Pentobi" DeleteRegKey HKLM "${UNINST_KEY}" DeleteRegKey HKCR "Pentobi" DeleteRegKey HKCR "Applications\Pentobi.exe" DeleteRegKey HKCR "Applications\Pentobi" SectionEnd pentobi-7.2/windows_installer/qt.conf000077500000000000000000000000441227240712600200700ustar00rootroot00000000000000[Paths] Translations=translations