Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlan Aguiar <alanjas@hotmail.com>2013-12-28 02:58:45 (GMT)
committer Alan Aguiar <alanjas@hotmail.com>2013-12-28 02:58:45 (GMT)
commitf950609248b6df02069920f6d055fc597aac694c (patch)
tree8cc3c59d1280e2eef5b235a4858e6d9a8f615d71
add all files of v2
-rw-r--r--COPYING674
-rwxr-xr-xactivity.py6
-rw-r--r--activity/activity-pacman.svg72
-rw-r--r--activity/activity.info7
-rw-r--r--activity/ghost_green.pngbin0 -> 424 bytes
-rw-r--r--activity/ghost_orange.pngbin0 -> 420 bytes
-rw-r--r--activity/ghost_red.pngbin0 -> 412 bytes
-rw-r--r--activity/ghost_white.pngbin0 -> 342 bytes
-rw-r--r--activity/pacman-blue-closed.pngbin0 -> 321 bytes
-rw-r--r--activity/pacman-blue-open-down.pngbin0 -> 318 bytes
-rw-r--r--activity/pacman-blue-open-left.pngbin0 -> 317 bytes
-rw-r--r--activity/pacman-blue-open-right.pngbin0 -> 326 bytes
-rw-r--r--activity/pacman-blue-open-up.pngbin0 -> 325 bytes
-rw-r--r--activity/pacman-green-closed.pngbin0 -> 644 bytes
-rw-r--r--activity/pacman-green-open-down.pngbin0 -> 723 bytes
-rw-r--r--activity/pacman-green-open-left.pngbin0 -> 696 bytes
-rw-r--r--activity/pacman-green-open-right.pngbin0 -> 718 bytes
-rw-r--r--activity/pacman-green-open-up.pngbin0 -> 739 bytes
-rw-r--r--activity/pacman-yellow-closed.pngbin0 -> 338 bytes
-rw-r--r--activity/pacman-yellow-open-down.pngbin0 -> 307 bytes
-rw-r--r--activity/pacman-yellow-open-left.pngbin0 -> 319 bytes
-rw-r--r--activity/pacman-yellow-open-right.pngbin0 -> 326 bytes
-rw-r--r--activity/pacman-yellow-open-up.pngbin0 -> 321 bytes
-rw-r--r--game.py569
-rw-r--r--ghost.py101
-rw-r--r--maze.py324
-rw-r--r--olpcgames/COPYING24
-rw-r--r--olpcgames/__init__.py42
-rw-r--r--olpcgames/_cairoimage.py58
-rw-r--r--olpcgames/activity.py162
-rw-r--r--olpcgames/camera.py235
-rw-r--r--olpcgames/canvas.py124
-rwxr-xr-xolpcgames/data/__init__.py36
-rw-r--r--olpcgames/data/sleeping_svg.py501
-rw-r--r--olpcgames/eventwrap.py198
-rw-r--r--olpcgames/gtkEvent.py270
-rw-r--r--olpcgames/mesh.py398
-rw-r--r--olpcgames/pangofont.py279
-rw-r--r--olpcgames/pausescreen.py89
-rw-r--r--olpcgames/svgsprite.py69
-rw-r--r--olpcgames/util.py68
-rw-r--r--olpcgames/video.py170
-rw-r--r--player.py122
-rwxr-xr-xsetup.py4
44 files changed, 4602 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..20d40b6
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ 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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+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:
+
+ <program> Copyright (C) <year> <name of author>
+ 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
+<http://www.gnu.org/licenses/>.
+
+ 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
+<http://www.gnu.org/philosophy/why-not-lgpl.html>. \ No newline at end of file
diff --git a/activity.py b/activity.py
new file mode 100755
index 0000000..e26a0f6
--- /dev/null
+++ b/activity.py
@@ -0,0 +1,6 @@
+import olpcgames
+
+class PacmanActivity(olpcgames.PyGameActivity):
+ game_name = 'game'
+ game_title = 'Pacman'
+ game_size = None # let olpcgames pick a nice size for us
diff --git a/activity/activity-pacman.svg b/activity/activity-pacman.svg
new file mode 100644
index 0000000..60c0d2c
--- /dev/null
+++ b/activity/activity-pacman.svg
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ id="Layer_1"
+ x="0px"
+ y="0px"
+ width="50px"
+ height="50px"
+ viewBox="0 0 50 50"
+ enable-background="new 0 0 50 50"
+ xml:space="preserve"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="5851.0.activity-maze.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"><metadata
+ id="metadata8"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs6"><inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 25 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="50 : 25 : 1"
+ inkscape:persp3d-origin="25 : 16.666667 : 1"
+ id="perspective10" /></defs><sodipodi:namedview
+ inkscape:window-height="953"
+ inkscape:window-width="1280"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="10.86"
+ inkscape:cx="12.877803"
+ inkscape:cy="25"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:current-layer="Layer_1" />
+
+<path
+ sodipodi:type="arc"
+ style="fill:#fff700;fill-opacity:1;fill-rule:evenodd;stroke:#fff700;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path2650"
+ sodipodi:cx="20.119705"
+ sodipodi:cy="20.303867"
+ sodipodi:rx="17.081032"
+ sodipodi:ry="17.81768"
+ d="M 34.912313,29.212708 A 17.081032,17.81768 0 1 1 34.912312,11.395027 L 20.119705,20.303867 z"
+ sodipodi:start="0.52359878"
+ sodipodi:end="5.7595865"
+ transform="matrix(1.1544758,-9.0457548e-3,9.4738818e-3,1.1023047,-2.8068479,3.482163)" /><path
+ sodipodi:type="arc"
+ style="fill:#fff700;fill-opacity:1;fill-rule:evenodd;stroke:#fff700;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="path3149"
+ sodipodi:cx="37.937386"
+ sodipodi:cy="25.920811"
+ sodipodi:rx="3.222836"
+ sodipodi:ry="3.0847147"
+ d="M 41.160222,25.920811 A 3.222836,3.0847147 0 1 1 34.71455,25.920811 A 3.222836,3.0847147 0 1 1 41.160222,25.920811 z"
+ transform="translate(2.7624309,-0.2762431)" /></svg> \ No newline at end of file
diff --git a/activity/activity.info b/activity/activity.info
new file mode 100644
index 0000000..0ecc40e
--- /dev/null
+++ b/activity/activity.info
@@ -0,0 +1,7 @@
+[Activity]
+name = Pacman
+service_name = vu.lux.olpc.Pacman
+class = activity.PacmanActivity
+icon = activity-pacman
+activity_version = 2
+show_launcher = yes
diff --git a/activity/ghost_green.png b/activity/ghost_green.png
new file mode 100644
index 0000000..96dd762
--- /dev/null
+++ b/activity/ghost_green.png
Binary files differ
diff --git a/activity/ghost_orange.png b/activity/ghost_orange.png
new file mode 100644
index 0000000..657f280
--- /dev/null
+++ b/activity/ghost_orange.png
Binary files differ
diff --git a/activity/ghost_red.png b/activity/ghost_red.png
new file mode 100644
index 0000000..a94ec58
--- /dev/null
+++ b/activity/ghost_red.png
Binary files differ
diff --git a/activity/ghost_white.png b/activity/ghost_white.png
new file mode 100644
index 0000000..167fe14
--- /dev/null
+++ b/activity/ghost_white.png
Binary files differ
diff --git a/activity/pacman-blue-closed.png b/activity/pacman-blue-closed.png
new file mode 100644
index 0000000..4a0feb1
--- /dev/null
+++ b/activity/pacman-blue-closed.png
Binary files differ
diff --git a/activity/pacman-blue-open-down.png b/activity/pacman-blue-open-down.png
new file mode 100644
index 0000000..46f0607
--- /dev/null
+++ b/activity/pacman-blue-open-down.png
Binary files differ
diff --git a/activity/pacman-blue-open-left.png b/activity/pacman-blue-open-left.png
new file mode 100644
index 0000000..19af544
--- /dev/null
+++ b/activity/pacman-blue-open-left.png
Binary files differ
diff --git a/activity/pacman-blue-open-right.png b/activity/pacman-blue-open-right.png
new file mode 100644
index 0000000..a67f483
--- /dev/null
+++ b/activity/pacman-blue-open-right.png
Binary files differ
diff --git a/activity/pacman-blue-open-up.png b/activity/pacman-blue-open-up.png
new file mode 100644
index 0000000..d2b8c5e
--- /dev/null
+++ b/activity/pacman-blue-open-up.png
Binary files differ
diff --git a/activity/pacman-green-closed.png b/activity/pacman-green-closed.png
new file mode 100644
index 0000000..34d563e
--- /dev/null
+++ b/activity/pacman-green-closed.png
Binary files differ
diff --git a/activity/pacman-green-open-down.png b/activity/pacman-green-open-down.png
new file mode 100644
index 0000000..f5aac63
--- /dev/null
+++ b/activity/pacman-green-open-down.png
Binary files differ
diff --git a/activity/pacman-green-open-left.png b/activity/pacman-green-open-left.png
new file mode 100644
index 0000000..ca42b58
--- /dev/null
+++ b/activity/pacman-green-open-left.png
Binary files differ
diff --git a/activity/pacman-green-open-right.png b/activity/pacman-green-open-right.png
new file mode 100644
index 0000000..8b20399
--- /dev/null
+++ b/activity/pacman-green-open-right.png
Binary files differ
diff --git a/activity/pacman-green-open-up.png b/activity/pacman-green-open-up.png
new file mode 100644
index 0000000..1b114a0
--- /dev/null
+++ b/activity/pacman-green-open-up.png
Binary files differ
diff --git a/activity/pacman-yellow-closed.png b/activity/pacman-yellow-closed.png
new file mode 100644
index 0000000..9e4962e
--- /dev/null
+++ b/activity/pacman-yellow-closed.png
Binary files differ
diff --git a/activity/pacman-yellow-open-down.png b/activity/pacman-yellow-open-down.png
new file mode 100644
index 0000000..1e15a11
--- /dev/null
+++ b/activity/pacman-yellow-open-down.png
Binary files differ
diff --git a/activity/pacman-yellow-open-left.png b/activity/pacman-yellow-open-left.png
new file mode 100644
index 0000000..2b35f48
--- /dev/null
+++ b/activity/pacman-yellow-open-left.png
Binary files differ
diff --git a/activity/pacman-yellow-open-right.png b/activity/pacman-yellow-open-right.png
new file mode 100644
index 0000000..ef2ff1e
--- /dev/null
+++ b/activity/pacman-yellow-open-right.png
Binary files differ
diff --git a/activity/pacman-yellow-open-up.png b/activity/pacman-yellow-open-up.png
new file mode 100644
index 0000000..17dcc33
--- /dev/null
+++ b/activity/pacman-yellow-open-up.png
Binary files differ
diff --git a/game.py b/game.py
new file mode 100644
index 0000000..f379223
--- /dev/null
+++ b/game.py
@@ -0,0 +1,569 @@
+# Pacman.activity
+# A pac-man clone for the XO laptop.
+#
+# Thanks to Raphael Reinig for testing
+#
+# Copyright (C) 2008 Tristan Hoffmann, tristanhoffmann@boxbe.com
+# Thanks to Joshua Minor, I used a lot of code from his Maze.activity
+#
+# This file is part of Pacman.activity
+#
+# Pacman.activity 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.
+#
+# Pacman.activity 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 Pacman.activity. If not, see <http://www.gnu.org/licenses/>.
+
+
+import sys
+import os
+import time
+
+import pygame
+import olpcgames
+
+import olpcgames.pausescreen as pausescreen
+import olpcgames.mesh as mesh
+import olpcgames.svgsprite as svgsprite
+from pygame import sprite as sprite
+from olpcgames.util import get_bundle_path
+from sugar.presence import presenceservice
+
+bundlepath = get_bundle_path()
+presenceService = presenceservice.get_instance()
+
+from maze import Maze
+from player import Player
+from ghost import Ghost
+
+class PacmanGame:
+ """Maze game controller.
+ This class handles all of the game logic, event loop, mulitplayer, etc."""
+
+ # Munsell color values http://wiki.laptop.org/go/Munsell
+ N10 = (255, 255, 255)
+ N9p5 = (243, 243, 243)
+ N9 = (232, 232, 232)
+ N8 = (203, 203, 203)
+ N7 = (179, 179, 179)
+ N6 = (150, 150, 150)
+ N5 = (124, 124, 124)
+ N4 = ( 97, 97, 97)
+ N3 = ( 70, 70, 70)
+ N2 = ( 48, 48, 48)
+ N1 = ( 28, 28, 28)
+ N0 = ( 0, 0, 0)
+ EMPTY_COLOR = N1
+ SOLID_COLOR = (40, 80, 65)
+ TRAIL_COLOR = N10
+ GOAL_COLOR = (0x00, 0xff, 0x00)
+ WIN_COLOR = (0xff, 0xff, 0x00)
+
+ def __init__(self, screen):
+ print "init"
+ # note what time it was when we first launched
+ self.game_start_time = time.time()
+ self.pause = 0
+
+ xoOwner = presenceService.get_owner()
+ seed = int(time.time())
+ # keep a list of all local players
+ self.localplayers = []
+ print (xoOwner)
+ # start with just one player
+ player = Player(xoOwner, 1, 1, "yellow")
+ self.localplayers.append(player)
+ # plus some bonus players (all hidden to start with)
+ self.localplayers.extend(player.bonusPlayers())
+
+ # keep a dictionary of all remote players, indexed by handle
+ self.remoteplayers = {}
+ # keep a list of all players, local and remote,
+ self.allplayers = [] + self.localplayers
+
+ self.ghosts = []
+ redghost = Ghost ("red", seed, 19, 8)
+ whiteghost = Ghost ("white", 2*seed, 13, 8)
+ greenghost = Ghost ("green", 3*seed, 13, 11)
+ orangeghost = Ghost ("orange", 4*seed, 19, 11)
+ self.ghosts.append (whiteghost)
+ self.ghosts.append (redghost)
+ self.ghosts.append (greenghost)
+ self.ghosts.append (orangeghost)
+
+ self.screen = screen
+ canvas_size = screen.get_size()
+ self.aspectRatio = canvas_size[0] / float(canvas_size[1])
+
+ # start with a small maze using a seed that will be different each time you play
+ self.maze = Maze(seed, 33, 23)
+ self.reset()
+ self.frame = 0
+
+ self.font = pygame.font.Font(None, 30)
+
+ # support arrow keys, game pad arrows and game pad buttons
+ # each set maps to a local player index and a direction
+ self.arrowkeys = {
+ # real key: (localplayer index, ideal key)
+ pygame.K_UP: (0, pygame.K_UP),
+ pygame.K_DOWN: (0, pygame.K_DOWN),
+ pygame.K_LEFT: (0, pygame.K_LEFT),
+ pygame.K_RIGHT: (0, pygame.K_RIGHT),
+ pygame.K_KP8: (1, pygame.K_UP),
+ pygame.K_KP2: (1, pygame.K_DOWN),
+ pygame.K_KP4: (1, pygame.K_LEFT),
+ pygame.K_KP6: (1, pygame.K_RIGHT),
+ pygame.K_w: (1, pygame.K_UP),
+ pygame.K_s: (1, pygame.K_DOWN),
+ pygame.K_a: (1, pygame.K_LEFT),
+ pygame.K_d: (1, pygame.K_RIGHT),
+ pygame.K_KP9: (2, pygame.K_UP),
+ pygame.K_KP3: (2, pygame.K_DOWN),
+ pygame.K_KP7: (2, pygame.K_LEFT),
+ pygame.K_KP1: (2, pygame.K_RIGHT),
+ }
+
+
+ def game_running_time(self, newelapsed=None):
+ return int(time.time() - self.game_start_time)
+
+ def reset(self):
+ """Reset the game state. Everyone starts in the top-left.
+ The goal starts in the bottom-right corner."""
+ self.running = True
+ self.level_start_time = time.time()
+ self.finish_time = None
+ for player in self.allplayers:
+ player.reset()
+ self.dirtyRect = None
+ self.dirtyPoints = []
+ #self.maze.map[self.maze.width-2][self.maze.height-2] = self.maze.GOAL -> no goal needed
+
+ # clear and mark the whole screen as dirty
+ self.screen.fill((0,0,0))
+ self.markRectDirty(pygame.Rect(0,0,99999,99999))
+
+ def markRectDirty(self, rect):
+ """Mark an area that needs to be redrawn. This lets us
+ play really big mazes without needing to re-draw the whole
+ thing each frame."""
+ if self.dirtyRect is None:
+ self.dirtyRect = rect
+ else:
+ self.dirtyRect.union_ip(rect)
+
+ def markPointDirty(self, pt):
+ """Mark a single point that needs to be redrawn."""
+ self.dirtyPoints.append(pt)
+
+ def processEvent(self, event):
+ """Process a single pygame event. This includes keystrokes
+ as well as multiplayer events from the mesh."""
+ if event.type == pygame.QUIT:
+ self.running = False
+ elif event.type == pygame.KEYDOWN:
+ if event.key == pygame.K_ESCAPE:
+ self.running = False
+ if event.key == pygame.K_p:
+ self.togglePause()
+ elif self.arrowkeys.has_key(event.key):
+ playernum, direction = self.arrowkeys[event.key]
+ player = self.localplayers[playernum]
+ player.hidden = False
+
+ if direction == pygame.K_UP:
+ player.direction=(0,-1)
+ player.open.image = pygame.image.load (player.dirName + "pacman-" + player.color + "-open-up.png")
+ elif direction == pygame.K_DOWN:
+ player.direction=(0,1)
+ player.open.image = pygame.image.load (player.dirName + "pacman-" + player.color + "-open-down.png")
+ elif direction == pygame.K_LEFT:
+ player.direction=(-1,0)
+ player.open.image = pygame.image.load (player.dirName + "pacman-" + player.color + "-open-left.png")
+ elif direction == pygame.K_RIGHT:
+ player.direction=(1,0)
+ player.open.image = pygame.image.load (player.dirName + "pacman-" + player.color + "-open-right.png")
+
+ if len(self.remoteplayers)>0:
+ mesh.broadcast("move:%s,%d,%d,%d,%d" % (player.nick, player.position[0], player.position[1], player.direction[0], player.direction[1]))
+ elif event.type == pygame.KEYUP:
+ pass
+ elif event.type in (pygame.MOUSEMOTION, pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP):
+ pass
+ elif event.type == mesh.CONNECT:
+ print "Connected to the mesh."
+ elif event.type == mesh.PARTICIPANT_ADD:
+ buddy = mesh.get_buddy(event.handle)
+ if event.handle == mesh.my_handle():
+ print "Me:", buddy.props.nick, buddy.props.color
+ else:
+ print "Join:", buddy.props.nick, buddy.props.color
+ player = Player(buddy)
+ self.remoteplayers[event.handle] = player
+ self.allplayers.append(player)
+ self.allplayers.extend(player.bonusPlayers())
+ self.markPointDirty(player.position)
+ # send a test message to the new player
+ mesh.broadcast("Welcome %s" % player.nick)
+ # tell them which maze we are playing, so they can sync up
+ mesh.send_to(event.handle, "maze:%d,%d,%d,%d" % (self.game_running_time(), self.maze.seed, self.maze.width, self.maze.height))
+ for player in self.localplayers:
+ if not player.hidden:
+ mesh.send_to(event.handle, "move:%s,%d,%d,%d,%d" % (player.nick, player.position[0], player.position[1], player.direction[0], player.direction[1]))
+ elif event.type == mesh.PARTICIPANT_REMOVE:
+ if self.remoteplayers.has_key(event.handle):
+ player = self.remoteplayers[event.handle]
+ print "Leave:", player.nick
+ self.markPointDirty(player.position)
+ self.allplayers.remove(player)
+ for bonusplayer in player.bonusPlayers():
+ self.markPointDirty(bonusplayer.position)
+ self.allplayers.remove(bonusplayer)
+ del self.remoteplayers[event.handle]
+ elif event.type == mesh.MESSAGE_UNI or event.type == mesh.MESSAGE_MULTI:
+ buddy = mesh.get_buddy(event.handle)
+ #print "Message from %s / %s: %s" % (buddy.props.nick, event.handle, event.content)
+ if event.handle == mesh.my_handle():
+ # ignore messages from ourself
+ pass
+ elif self.remoteplayers.has_key(event.handle):
+ player = self.remoteplayers[event.handle]
+ try:
+ self.handleMessage(player, event.content)
+ except:
+ print "Error handling message: %s\n%s" % (event, sys.exc_info())
+ else:
+ print "Message from unknown buddy?"
+ else:
+ print "Unknown event:", event
+
+ def handleMessage(self, player, message):
+ """Handle a message from a player on the mesh.
+ We try to be forward compatible with new versions of Maze by allowing messages to
+ have extra stuff at the end and ignoring unrecognized messages.
+ We allow some messages to contain a different nick than the message's source player
+ to support bonus players on that player's XO.
+ The valid messages are:
+ maze:running_time,seed,width,height
+ A player has a differen maze.
+ The one that has been running the longest will force all other players to use that maze.
+ This way new players will join the existing game properly.
+ move:nick,x,y,dx,dy
+ A player's at x,y is now moving in direction dx,dy
+ finish:nick,elapsed
+ A player has finished the maze
+ """
+ # ignore messages from myself
+ if player in self.localplayers:
+ return
+ if message.startswith("move:"):
+ # a player has moved
+ nick,x,y,dx,dy = message[5:].split(",")[:5]
+ player = player.bonusPlayer(nick)
+ player.hidden = False
+ self.markPointDirty(player.position)
+ player.position = (int(x),int(y))
+ player.direction = (int(dx),int(dy))
+ self.markPointDirty(player.position)
+ elif message.startswith("maze:"):
+ # someone has a different maze than us
+ running_time,seed,width,height = map(lambda x: int(x), message[5:].split(",")[:4])
+ # is that maze older than the one we're already playing?
+ # note that we use elapsed time instead of absolute time because
+ # people's clocks are often set to something totally wrong
+ if self.game_running_time() < running_time:
+ # make note of the earlier time that the game really started (before we joined)
+ self.game_start_time = time.time() - running_time
+ # use the new seed
+ self.maze = Maze(seed, width, height)
+ self.reset()
+ elif message.startswith("finish:"):
+ # someone finished the maze
+ nick, elapsed = message[7:].split(",")[:2]
+ player = player.bonusPlayer(nick)
+ player.elapsed = float(elapsed)
+ self.markPointDirty(player.position)
+ else:
+ # it was something I don't recognize...
+ print "Message from %s: %s" % (player.nick, message)
+ pass
+
+ def arrowKeysPressed(self):
+ keys = pygame.key.get_pressed()
+ for key in self.allkeys:
+ if keys[key]:
+ return True
+ return False
+
+ def togglePause(self):
+ if (self.pause == 1):
+ self.pause = 0
+ self.markRectDirty(pygame.Rect(0,0,99999,99999)) # repaint
+ else:
+ self.pause = 1
+
+ def run(self):
+ """Run the main loop of the game."""
+ # lets draw once before we enter the event loop
+ self.draw()
+ pygame.display.flip()
+ clock = pygame.time.Clock()
+
+ while self.running:
+ if (self.pause == 0):
+ self.frame += 1
+ # process all queued events
+ for event in pausescreen.get_events(sleep_timeout=30):
+ self.processEvent(event)
+
+ if (self.pause == 0):
+ self.animate(self.maze)
+ self.draw()
+ else: # repaint pause information frequently to prevent empty window after minimizing
+ font = pygame.font.Font(None, 100)
+ fg = (248,234,32)
+ text = "Press p to resume the game"
+ textimg = font.render(text, 1, fg)
+ textwidth, textheight = font.size(text)
+ rect = pygame.Rect(100, 200, textwidth, textheight)
+ pygame.draw.rect (self.screen, self.SOLID_COLOR, rect)
+ self.screen.blit(textimg, rect)
+
+ pygame.display.flip()
+
+
+ # this keeps the speed reasonable and limits cpu usage
+ clock.tick(3);
+
+ def animate(self, maze):
+ """Animate one frame of action."""
+ for player in self.allplayers:
+ if (player.supertime > 0):
+ player.supertime -= 1
+ oldposition = player.position
+ newposition = player.animate(self.maze)
+ if oldposition != newposition:
+ self.markPointDirty(oldposition)
+ self.markPointDirty(newposition)
+ if player in self.localplayers:
+ self.maze.map[player.previous[0]][player.previous[1]] = self.maze.SEEN
+
+ finished = 1
+ for x in range(0, self.maze.width):
+ for y in range(0, self.maze.height):
+ if (self.maze.map[x][y] == self.maze.EMPTY):
+ finished = 0
+ break;
+ if (finished == 1):
+ # reset maze
+ maze.generate()
+ # reset players
+ for player in self.allplayers:
+ player.position = player.startposition
+ # reset ghosts
+ for ghost in self.ghosts:
+ ghost.reset ();
+ ghost.position = ghost.startposition
+ # stop game
+ self.togglePause ()
+ # repaint
+ self.markRectDirty(pygame.Rect(0,0,99999,99999))
+
+ self.checkGhostPositions () # checks if player runs into ghost
+
+ for ghost in self.ghosts:
+ self.animateGhost (ghost, maze)
+
+ self.checkGhostPositions() # checks if ghost runs into player
+
+
+
+ def animateGhost (self, ghost, maze):
+ # ghosts follow any pacman they see
+ x = ghost.position[0]
+
+ # is there a ghost to the left?
+ while x > 0:
+ x = x - 1
+ if (maze.map[x][ghost.position[1]] == maze.SOLID): # ghosts cannot see through walls, this would be unfair :)
+ break
+ for player in self.allplayers:
+ if (player.hidden):
+ continue
+ if (player.position == (x, ghost.position[1])):
+ ghost.direction = (-1, 0)
+ self.markPointDirty (ghost.position)
+ ghost.move (ghost.direction, maze)
+ self.markPointDirty (ghost.position)
+ return
+
+ # is there a ghost to the right?
+ x = ghost.position[0]
+ while x < maze.width:
+ x = x + 1
+ if (maze.map[x][ghost.position[1]] == maze.SOLID): # ghosts cannot see through walls, this would be unfair :)
+ break
+ for player in self.allplayers:
+ if (player.hidden):
+ continue
+ if (player.position == (x, ghost.position[1])):
+ ghost.direction = (1, 0)
+ self.markPointDirty (ghost.position)
+ ghost.move (ghost.direction, maze)
+ self.markPointDirty (ghost.position)
+ return
+
+ # is there a ghost to the top?
+ y = ghost.position[1]
+ while y > 0:
+ y = y - 1
+ if (maze.map[ghost.position[0]][y] == maze.SOLID): # ghosts cannot see through walls, this would be unfair :)
+ break
+ for player in self.allplayers:
+ if (player.hidden):
+ continue
+ if (player.position == (ghost.position[0], y)):
+ ghost.direction = (0, -1)
+ self.markPointDirty (ghost.position)
+ ghost.move (ghost.direction, maze)
+ self.markPointDirty (ghost.position)
+ return
+
+ # is there a ghost to the right?
+ y = ghost.position[1]
+ while y < maze.height:
+ y = y + 1
+ if (maze.map[ghost.position[0]][y] == maze.SOLID): # ghosts cannot see through walls, this would be unfair :)
+ break
+ for player in self.allplayers:
+ if (player.hidden):
+ continue
+ if (player.position == (ghost.position[0], y)):
+ ghost.direction = (0, 1)
+ self.markPointDirty (ghost.position)
+ ghost.move (ghost.direction, maze)
+ self.markPointDirty (ghost.position)
+ return
+
+ oldposition = ghost.position
+ ghost.animate(self.maze)
+ newposition = ghost.position
+ if oldposition != newposition:
+ self.markPointDirty(oldposition)
+ self.markPointDirty(newposition)
+
+
+
+ def checkGhostPositions (self):
+ # is there a ghost hitting a player?
+ for player in self.allplayers:
+ if (player.hidden):
+ continue
+ for ghost in self.ghosts:
+ if (ghost.position == player.position):
+ if (player.supertime > 0):
+ ghost.reset ();
+ ghost.position = ghost.startposition
+ else:
+ player.position = player.startposition
+ self.markPointDirty (player.position)
+ player.score = int(player.score/2)
+ player.supertime = 10 # make sure the player will not be eaten again in the next 10 moves
+ break;
+
+ def draw(self):
+ """Draw the current state of the game.
+ This makes use of the dirty rectangle to reduce CPU load."""
+ if self.dirtyRect is None and len(self.dirtyPoints)==0:
+ return
+
+ # compute the size of the tiles given the screen size, etc.
+ size = self.screen.get_size()
+ self.tileSize = min(size[0] / self.maze.width, size[1] / self.maze.height)
+ self.bounds = pygame.Rect((size[0] - self.tileSize * self.maze.width)/2,
+ (size[1] - self.tileSize * self.maze.height)/2,
+ self.tileSize * self.maze.width,
+ self.tileSize * self.maze.height)
+ self.outline = int(self.tileSize)
+
+ def drawPoint(x,y):
+ rect = pygame.Rect(self.bounds.x + x*self.tileSize, self.bounds.y + y*self.tileSize, self.tileSize, self.tileSize)
+ tile = self.maze.map[x][y]
+ if tile == self.maze.EMPTY:
+ pygame.draw.rect(self.screen, self.EMPTY_COLOR, rect, 0)
+ dot = rect.inflate(-self.outline/2, -self.outline/2)
+ pygame.draw.ellipse(self.screen, self.TRAIL_COLOR, dot, 0)
+ elif tile == self.maze.SOLID:
+ pygame.draw.rect(self.screen, self.SOLID_COLOR, rect, 0)
+ elif tile == self.maze.SEEN:
+ pygame.draw.rect(self.screen, self.EMPTY_COLOR, rect, 0)
+ else:
+ pygame.draw.rect(self.screen, (0xff, 0x00, 0xff), rect, 0)
+
+ # re-draw the dirty rectangle
+ if self.dirtyRect is not None:
+ # compute the area that needs to be redrawn
+ left = max(0, self.dirtyRect.left)
+ right = min(self.maze.width, self.dirtyRect.right)
+ top = max(0, self.dirtyRect.top)
+ bottom = min(self.maze.height, self.dirtyRect.bottom)
+
+ # loop over the dirty rect and draw
+ for x in range(left, right):
+ for y in range(top, bottom):
+ drawPoint(x,y)
+
+ # re-draw the dirty points
+ for x,y in self.dirtyPoints:
+ drawPoint(x,y)
+
+ # draw all players
+ for player in self.allplayers:
+ if not player.hidden:
+ player.draw(self.screen, self.bounds, self.tileSize)
+
+ #draw all ghosts
+ for ghost in self.ghosts:
+ ghost.draw (self.screen, self.bounds, self.tileSize)
+
+ # clear the dirty rect so nothing will be drawn until there is a change
+ self.dirtyRect = None
+ self.dirtyPoints = []
+
+ # draw score
+ fg = (21, 203, 35)
+ text = ""
+ for player in self.allplayers:
+ if not player.hidden:
+ text += player.nick + ": " + str(player.score) + " "
+ textimg = self.font.render(text, 1, fg)
+ textwidth, textheight = self.font.size(text)
+ rect = pygame.Rect(25, 15, textwidth, textheight)
+ pygame.draw.rect (self.screen, self.SOLID_COLOR, rect)
+ self.screen.blit(textimg, rect)
+
+def main():
+ """Run a game of Maze."""
+ #canvas_size = 1024,768-75
+ #screen = pygame.display.set_mode(canvas_size)
+
+ # ask pygame how big the screen is, leaving a little room for the toolbar
+ toolbarheight = 75
+ pygame.display.init()
+ maxX,maxY = pygame.display.list_modes()[0]
+ screen = pygame.display.set_mode( ( maxX, maxY-toolbarheight ) )
+
+ game = PacmanGame(screen)
+ game.run()
+
+if __name__ == '__main__':
+ main()
+
diff --git a/ghost.py b/ghost.py
new file mode 100644
index 0000000..50f8cd2
--- /dev/null
+++ b/ghost.py
@@ -0,0 +1,101 @@
+# Pacman.activity
+# A pac-man clone for the XO laptop.
+#
+# Thanks to Raphael Reinig for testing
+#
+# Copyright (C) 2008 Tristan Hoffmann, tristanhoffmann@boxbe.com
+# Thanks to Joshua Minor, I used a lot of code from his Maze.activity
+#
+# This file is part of Pacman.activity
+#
+# Pacman.activity 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.
+#
+# Pacman.activity 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 Pacman.activity. If not, see <http://www.gnu.org/licenses/>.
+
+import pygame
+import random
+import re, os
+import olpcgames.svgsprite as svgsprite
+from pygame import sprite as sprite
+
+class Ghost:
+ def __init__(self, color, seed, x, y):
+ self.seed = seed
+ self.generator = random.Random(seed)
+ self.color = color
+ self.sprites = sprite.RenderUpdates()
+ self.sprite = sprite.Sprite()
+ self.sprite.image = pygame.image.load ("/home/olpc/Activities/Pacman.activity/activity/ghost_" + color + ".png")
+ self.sprites.add(self.sprite)
+ self.position = (x, y)
+ self.startposition = self.position
+ self.reset()
+
+ def draw(self, screen, bounds, size):
+ rect = pygame.Rect(bounds.x+self.position[0]*size, bounds.y+self.position[1]*size, size, size)
+ self.sprite.rect = rect
+ self.sprites.draw(screen)
+
+ def reset(self):
+ self.direction = (0,0)
+ self.previous = self.startposition
+ self.elapsed = None
+
+ def animate(self, maze):
+ directions = []
+ if (self.canGo ((0,1), maze)):
+ directions.append ((0,1))
+ if (self.direction != (0,-1)):
+ directions.append ((0,1)) # raise the probability that the ghost doesn't go back
+
+ if (self.canGo ((0,-1), maze)):
+ directions.append ((0,-1))
+ if (self.direction != (0,1)):
+ directions.append ((0,-1)) # raise the probability that the ghost doesn't go back
+
+ if (self.canGo ((1,0), maze)):
+ directions.append ((1,0))
+ if (self.direction != (-1,0)):
+ directions.append ((1,0)) # raise the probability that the ghost doesn't go back
+
+ if (self.canGo ((-1,0), maze)):
+ directions.append ((-1,0))
+ if (self.direction != (1,0)):
+ directions.append ((-1,0)) # raise the probability that the ghost doesn't go back
+
+ if (self.canGo (self.direction, maze)): # don't go back if we can go forward
+ backDirection = (-self.direction[0], -self.direction[1])
+ directions.append (self.direction) # raise the probability that the ghost keeps it's direction
+ directions.remove (backDirection) # remove possibility to go back
+
+ self.direction = self.generator.choice (directions)
+ self.move (self.direction, maze)
+
+
+ def move(self, direction, maze):
+ """Move the ghost in a given direction (deltax,deltay)"""
+ if (direction[0] > 1):
+ print "Direction[0] is greater than 1!"
+ if (direction[1] > 1):
+ print "Direction[1] is greater than 1!"
+ if (direction == (1, 1)):
+ print "Direction is (1, 1)"
+ newposition = (self.position[0]+direction[0], self.position[1]+direction[1])
+ self.position = newposition
+
+ def canGo(self, direction, maze):
+ """Can the ghost go in this direction without bumping into something?"""
+ if (direction == (0,0)):
+ return 0 # make sure the ghost isn't sleeping :)
+ newposition = (self.position[0]+direction[0], self.position[1]+direction[1])
+ return maze.validMove(newposition[0], newposition[1])
+
diff --git a/maze.py b/maze.py
new file mode 100644
index 0000000..47039df
--- /dev/null
+++ b/maze.py
@@ -0,0 +1,324 @@
+# Pacman.activity
+# A pac-man clone for the XO laptop.
+#
+# Thanks to Raphael Reinig for testing
+#
+# Copyright (C) 2008 Tristan Hoffmann, tristanhoffmann@boxbe.com
+# Thanks to Joshua Minor, I used a lot of code from his Maze.activity
+#
+# This file is part of Pacman.activity
+#
+# Pacman.activity 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.
+#
+# Pacman.activity 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 Pacman.activity. If not, see <http://www.gnu.org/licenses/>.
+
+from pygame import Rect
+
+class Maze:
+ SOLID = 0 #wall
+ EMPTY = 1 #where pacman can move
+ SEEN = 2 #already eaten
+
+ def __init__(self, seed, width, height):
+ # use the seed given to us to make a pseudo-random number generator
+ # we will use that to generate the maze, so that other players can
+ # generate the exact same maze given the same seed.
+ print "Generating maze:%d,%d,%d" % (seed, width, height)
+ self.width, self.height = width, height
+ self.bounds = Rect(0,0,width,height)
+
+ self.generate ()
+
+
+ def generate (self):
+ self.map = []
+ for x in range(0, self.width-1):
+ if (x == 0):
+ self.map.append([self.SOLID] * self.height) # draw left border
+ else:
+ self.map.append([self.EMPTY] * self.height)
+ self.map.append([self.SOLID] * self.height) # draw right border
+
+ for x in range(1, self.width-1):
+ self.map[x][0] = self.SOLID # draw top border
+ self.map[x][self.height - 1] = self.SOLID # draw bottom border
+
+ self.map[8][1] = self.SOLID
+ self.map[24][1] = self.SOLID
+ self.map[2][2] = self.SOLID
+ self.map[3][2] = self.SOLID
+ self.map[4][2] = self.SOLID
+ self.map[5][2] = self.SOLID
+ self.map[6][2] = self.SOLID
+ self.map[8][2] = self.SOLID
+ self.map[10][2] = self.SOLID
+ self.map[11][2] = self.SOLID
+ self.map[12][2] = self.SOLID
+ self.map[13][2] = self.SOLID
+ self.map[14][2] = self.SOLID
+ self.map[15][2] = self.SOLID
+ self.map[16][2] = self.SOLID
+ self.map[17][2] = self.SOLID
+ self.map[18][2] = self.SOLID
+ self.map[19][2] = self.SOLID
+ self.map[20][2] = self.SOLID
+ self.map[21][2] = self.SOLID
+ self.map[22][2] = self.SOLID
+ self.map[24][2] = self.SOLID
+ self.map[26][2] = self.SOLID
+ self.map[27][2] = self.SOLID
+ self.map[28][2] = self.SOLID
+ self.map[29][2] = self.SOLID
+ self.map[30][2] = self.SOLID
+ self.map[2][3] = self.SOLID
+ self.map[30][3] = self.SOLID
+ self.map[2][4] = self.SOLID
+ self.map[4][4] = self.SOLID
+ self.map[6][4] = self.SOLID
+ self.map[8][4] = self.SOLID
+ self.map[9][4] = self.SOLID
+ self.map[10][4] = self.SOLID
+ self.map[12][4] = self.SOLID
+ self.map[13][4] = self.SOLID
+ self.map[14][4] = self.SOLID
+ self.map[15][4] = self.SOLID
+ self.map[16][4] = self.SOLID
+ self.map[17][4] = self.SOLID
+ self.map[18][4] = self.SOLID
+ self.map[19][4] = self.SOLID
+ self.map[20][4] = self.SOLID
+ self.map[22][4] = self.SOLID
+ self.map[23][4] = self.SOLID
+ self.map[24][4] = self.SOLID
+ self.map[26][4] = self.SOLID
+ self.map[28][4] = self.SOLID
+ self.map[30][4] = self.SOLID
+ self.map[4][5] = self.SOLID
+ self.map[6][5] = self.SOLID
+ self.map[26][5] = self.SOLID
+ self.map[28][5] = self.SOLID
+ self.map[1][6] = self.SOLID
+ self.map[2][6] = self.SOLID
+ self.map[4][6] = self.SOLID
+ self.map[6][6] = self.SOLID
+ self.map[8][6] = self.SOLID
+ self.map[9][6] = self.SOLID
+ self.map[10][6] = self.SOLID
+ self.map[11][6] = self.SOLID
+ self.map[12][6] = self.SOLID
+ self.map[14][6] = self.SOLID
+ self.map[15][6] = self.SOLID
+ self.map[16][6] = self.SOLID
+ self.map[17][6] = self.SOLID
+ self.map[18][6] = self.SOLID
+ self.map[20][6] = self.SOLID
+ self.map[21][6] = self.SOLID
+ self.map[22][6] = self.SOLID
+ self.map[23][6] = self.SOLID
+ self.map[24][6] = self.SOLID
+ self.map[26][6] = self.SOLID
+ self.map[28][6] = self.SOLID
+ self.map[30][6] = self.SOLID
+ self.map[31][6] = self.SOLID
+ self.map[4][7] = self.SOLID
+ self.map[12][7] = self.SOLID
+ self.map[20][7] = self.SOLID
+ self.map[28][7] = self.SOLID
+ self.map[2][8] = self.SOLID
+ self.map[4][8] = self.SOLID
+ self.map[6][8] = self.SOLID
+ self.map[7][8] = self.SOLID
+ self.map[8][8] = self.SOLID
+ self.map[9][8] = self.SOLID
+ self.map[10][8] = self.SOLID
+ self.map[12][8] = self.SOLID
+ self.map[14][8] = self.SOLID
+ self.map[15][8] = self.SOLID
+ self.map[16][8] = self.SOLID
+ self.map[17][8] = self.SOLID
+ self.map[18][8] = self.SOLID
+ self.map[20][8] = self.SOLID
+ self.map[22][8] = self.SOLID
+ self.map[23][8] = self.SOLID
+ self.map[24][8] = self.SOLID
+ self.map[25][8] = self.SOLID
+ self.map[26][8] = self.SOLID
+ self.map[28][8] = self.SOLID
+ self.map[30][8] = self.SOLID
+ self.map[2][9] = self.SOLID
+ self.map[10][9] = self.SOLID
+ self.map[12][9] = self.SOLID
+ self.map[20][9] = self.SOLID
+ self.map[22][9] = self.SOLID
+ self.map[30][9] = self.SOLID
+ self.map[2][10] = self.SOLID
+ self.map[4][10] = self.SOLID
+ self.map[5][10] = self.SOLID
+ self.map[6][10] = self.SOLID
+ self.map[7][10] = self.SOLID
+ self.map[8][10] = self.SOLID
+ self.map[10][10] = self.SOLID
+ self.map[12][10] = self.SOLID
+ self.map[13][10] = self.SOLID
+ self.map[14][10] = self.SOLID
+ self.map[15][10] = self.SOLID
+ self.map[16][10] = self.SOLID
+ self.map[17][10] = self.SOLID
+ self.map[18][10] = self.SOLID
+ self.map[19][10] = self.SOLID
+ self.map[20][10] = self.SOLID
+ self.map[22][10] = self.SOLID
+ self.map[24][10] = self.SOLID
+ self.map[25][10] = self.SOLID
+ self.map[26][10] = self.SOLID
+ self.map[27][10] = self.SOLID
+ self.map[28][10] = self.SOLID
+ self.map[30][10] = self.SOLID
+ self.map[2][11] = self.SOLID
+ self.map[10][11] = self.SOLID
+ self.map[12][11] = self.SOLID
+ self.map[20][11] = self.SOLID
+ self.map[22][11] = self.SOLID
+ self.map[30][11] = self.SOLID
+ self.map[2][12] = self.SOLID
+ self.map[3][12] = self.SOLID
+ self.map[4][12] = self.SOLID
+ self.map[6][12] = self.SOLID
+ self.map[8][12] = self.SOLID
+ self.map[10][12] = self.SOLID
+ self.map[12][12] = self.SOLID
+ self.map[14][12] = self.SOLID
+ self.map[15][12] = self.SOLID
+ self.map[16][12] = self.SOLID
+ self.map[17][12] = self.SOLID
+ self.map[18][12] = self.SOLID
+ self.map[20][12] = self.SOLID
+ self.map[22][12] = self.SOLID
+ self.map[24][12] = self.SOLID
+ self.map[26][12] = self.SOLID
+ self.map[28][12] = self.SOLID
+ self.map[29][12] = self.SOLID
+ self.map[30][12] = self.SOLID
+ self.map[6][13] = self.SOLID
+ self.map[8][13] = self.SOLID
+ self.map[24][13] = self.SOLID
+ self.map[26][13] = self.SOLID
+ self.map[2][14] = self.SOLID
+ self.map[3][14] = self.SOLID
+ self.map[4][14] = self.SOLID
+ self.map[5][14] = self.SOLID
+ self.map[6][14] = self.SOLID
+ self.map[8][14] = self.SOLID
+ self.map[8][14] = self.SOLID
+ self.map[10][14] = self.SOLID
+ self.map[12][14] = self.SOLID
+ self.map[13][14] = self.SOLID
+ self.map[14][14] = self.SOLID
+ self.map[15][14] = self.SOLID
+ self.map[16][14] = self.SOLID
+ self.map[17][14] = self.SOLID
+ self.map[18][14] = self.SOLID
+ self.map[19][14] = self.SOLID
+ self.map[20][14] = self.SOLID
+ self.map[22][14] = self.SOLID
+ self.map[24][14] = self.SOLID
+ self.map[26][14] = self.SOLID
+ self.map[27][14] = self.SOLID
+ self.map[28][14] = self.SOLID
+ self.map[29][14] = self.SOLID
+ self.map[30][14] = self.SOLID
+ self.map[10][15] = self.SOLID
+ self.map[22][15] = self.SOLID
+ self.map[2][16] = self.SOLID
+ self.map[3][16] = self.SOLID
+ self.map[4][16] = self.SOLID
+ self.map[5][16] = self.SOLID
+ self.map[6][16] = self.SOLID
+ self.map[7][16] = self.SOLID
+ self.map[8][16] = self.SOLID
+ self.map[10][16] = self.SOLID
+ self.map[12][16] = self.SOLID
+ self.map[13][16] = self.SOLID
+ self.map[14][16] = self.SOLID
+ self.map[15][16] = self.SOLID
+ self.map[16][16] = self.SOLID
+ self.map[17][16] = self.SOLID
+ self.map[18][16] = self.SOLID
+ self.map[19][16] = self.SOLID
+ self.map[20][16] = self.SOLID
+ self.map[22][16] = self.SOLID
+ self.map[24][16] = self.SOLID
+ self.map[25][16] = self.SOLID
+ self.map[26][16] = self.SOLID
+ self.map[27][16] = self.SOLID
+ self.map[28][16] = self.SOLID
+ self.map[29][16] = self.SOLID
+ self.map[30][16] = self.SOLID
+ self.map[4][17] = self.SOLID
+ self.map[28][17] = self.SOLID
+ self.map[1][18] = self.SOLID
+ self.map[2][18] = self.SOLID
+ self.map[4][18] = self.SOLID
+ self.map[6][18] = self.SOLID
+ self.map[7][18] = self.SOLID
+ self.map[8][18] = self.SOLID
+ self.map[10][18] = self.SOLID
+ self.map[12][18] = self.SOLID
+ self.map[13][18] = self.SOLID
+ self.map[14][18] = self.SOLID
+ self.map[15][18] = self.SOLID
+ self.map[16][18] = self.SOLID
+ self.map[17][18] = self.SOLID
+ self.map[18][18] = self.SOLID
+ self.map[19][18] = self.SOLID
+ self.map[20][18] = self.SOLID
+ self.map[22][18] = self.SOLID
+ self.map[24][18] = self.SOLID
+ self.map[25][18] = self.SOLID
+ self.map[26][18] = self.SOLID
+ self.map[28][18] = self.SOLID
+ self.map[30][18] = self.SOLID
+ self.map[31][18] = self.SOLID
+ self.map[4][19] = self.SOLID
+ self.map[10][19] = self.SOLID
+ self.map[22][19] = self.SOLID
+ self.map[28][19] = self.SOLID
+ self.map[2][20] = self.SOLID
+ self.map[3][20] = self.SOLID
+ self.map[4][20] = self.SOLID
+ self.map[5][20] = self.SOLID
+ self.map[6][20] = self.SOLID
+ self.map[7][20] = self.SOLID
+ self.map[8][20] = self.SOLID
+ self.map[10][20] = self.SOLID
+ self.map[12][20] = self.SOLID
+ self.map[13][20] = self.SOLID
+ self.map[14][20] = self.SOLID
+ self.map[15][20] = self.SOLID
+ self.map[16][20] = self.SOLID
+ self.map[17][20] = self.SOLID
+ self.map[18][20] = self.SOLID
+ self.map[19][20] = self.SOLID
+ self.map[20][20] = self.SOLID
+ self.map[22][20] = self.SOLID
+ self.map[24][20] = self.SOLID
+ self.map[25][20] = self.SOLID
+ self.map[26][20] = self.SOLID
+ self.map[27][20] = self.SOLID
+ self.map[28][20] = self.SOLID
+ self.map[29][20] = self.SOLID
+ self.map[30][20] = self.SOLID
+ self.map[10][21] = self.SOLID
+ self.map[22][21] = self.SOLID
+
+ def validMove(self, x, y):
+ return self.bounds.collidepoint(x,y) and self.map[x][y]!=self.SOLID
diff --git a/olpcgames/COPYING b/olpcgames/COPYING
new file mode 100644
index 0000000..b8adee0
--- /dev/null
+++ b/olpcgames/COPYING
@@ -0,0 +1,24 @@
+* Copyright (c) 2007, One Laptop Per Child.
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are met:
+* * Redistributions of source code must retain the above copyright
+* notice, this list of conditions and the following disclaimer.
+* * Redistributions in binary form must reproduce the above copyright
+* notice, this list of conditions and the following disclaimer in the
+* documentation and/or other materials provided with the distribution.
+* * Neither the name of One Laptop Per Child nor the
+* names of its contributors may be used to endorse or promote products
+* derived from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY ONE LAPTOP PER CHILD ``AS IS'' AND ANY
+* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED. IN NO EVENT SHALL ONE LAPTOP PER CHILD BE LIABLE FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/olpcgames/__init__.py b/olpcgames/__init__.py
new file mode 100644
index 0000000..5fd28b7
--- /dev/null
+++ b/olpcgames/__init__.py
@@ -0,0 +1,42 @@
+"""Wrapper/adaptation system for writing/porting PyGame games to OLPC/Sugar
+
+The wrapper system attempts to substitute various pieces of the PyGame
+implementation in order to make code written without knowledge of the
+OLPC/Sugar environment run "naturally" under the GTK environment of
+Sugar. It also provides some convenience mechanisms for dealing with
+e.g. the Camera and Mesh Network system.
+
+Considerations for Developers:
+
+PyGame programs running under OLPCGames will generally not have
+"hardware" surfaces, and will not be able to have a reduced-resolution
+full-screen view to optimise rendering. The PyGame code will run in
+a secondary thread, with the main GTK UI running in the primary thread.
+A third "mainloop" thread will occasionally be created to handle the
+GStreamer interface to the camera.
+
+Attributes of Note:
+
+ ACTIVITY -- if not None, then the activity instance which represents
+ this activity at the Sugar shell level.
+ WIDGET -- PygameCanvas instance, a GTK widget with an embedded
+ socket object which is a proxy for the SDL window Pygame to which
+ pygame renders.
+"""
+# XXX handle configurations that are not running under Sugar and
+# report proper errors to describe the problem, rather than letting the
+# particular errors propagate outward.
+# XXX allow use of a particular feature within the library without needing
+# to be running under sugar. e.g. allow importing mesh or camera without
+# needing to import the activity machinery.
+from olpcgames.canvas import *
+try:
+ from olpcgames.activity import *
+except ImportError, err:
+ PyGameActivity = None
+from olpcgames import camera
+from olpcgames import pangofont
+from olpcgames import mesh
+
+ACTIVITY = None
+widget = WIDGET = None
diff --git a/olpcgames/_cairoimage.py b/olpcgames/_cairoimage.py
new file mode 100644
index 0000000..302b916
--- /dev/null
+++ b/olpcgames/_cairoimage.py
@@ -0,0 +1,58 @@
+"""Utility functions for cairo-specific operations"""
+import cairo, pygame, struct
+big_endian = struct.pack( '=i', 1 ) == struct.pack( '>i', 1 )
+
+def newContext( width, height ):
+ """Create a new render-to-image context
+
+ width, height -- pixel dimensions to be rendered
+
+ returns surface, context for rendering
+ """
+ csrf = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
+ return csrf, cairo.Context (csrf)
+
+def mangle_color(color):
+ """Mange a colour depending on endian-ness, and swap-necessity
+
+ This implementation has only been tested on an AMD64
+ machine with a get_data implementation (rather than
+ a get_data_as_rgba implementation).
+ """
+ r,g,b = color[:3]
+ if len(color) > 3:
+ a = color[3]
+ else:
+ a = 255.0
+ return map(_fixColorBase, (r,g,b,a) )
+
+def _fixColorBase( v ):
+ """Return a properly clamped colour in floating-point space"""
+ return max((0,min((v,255.0))))/255.0
+
+def asImage( csrf ):
+ """Get the pixels in csrf as a Pygame image"""
+ # Create and return a new Pygame Image derived from the Cairo Surface
+ format = 'ARGB'
+ if hasattr(csrf,'get_data'):
+ # more recent API, native-format, but have to (potentially) convert the format...
+ data = csrf.get_data()
+ if not big_endian:
+ import numpy
+ data = numpy.frombuffer( data, 'I' ).astype( '>I4' ).tostring()
+ else:
+ data = str(data) # there's one copy
+ else:
+ # older api, not native, but we know what it is...
+ data = csrf.get_data_as_rgba()
+ data = str(data) # there's one copy
+ width, height = csrf.get_width(),csrf.get_height()
+ try:
+ return pygame.image.fromstring(
+ data,
+ (width,height),
+ format
+ ) # there's the next
+ except ValueError, err:
+ err.args += (len(data), (width,height), width*height*4,format )
+ raise
diff --git a/olpcgames/activity.py b/olpcgames/activity.py
new file mode 100644
index 0000000..45a6a69
--- /dev/null
+++ b/olpcgames/activity.py
@@ -0,0 +1,162 @@
+"""Embeds the Canvas widget into a Sugar-specific Activity environment"""
+import logging
+logging.root.setLevel( logging.WARN )
+log = logging.getLogger( 'olpcgames.activity' )
+#log.setLevel( logging.INFO )
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gtk.gdk
+
+from sugar.activity import activity
+from sugar.graphics import style
+from olpcgames.canvas import PyGameCanvas
+from olpcgames import mesh, util
+
+__all__ = ['PyGameActivity']
+
+class PyGameActivity(activity.Activity):
+ """PyGame-specific activity type, provides boilerplate toolbar, creates canvas
+
+ Subclass Overrides:
+
+ game_name -- specifies a fully-qualified name for the game's main-loop
+ format like so:
+ 'package.module:main'
+ if not function name is provided, "main" is assumed.
+ game_handler -- alternate specification via direct reference to a main-loop
+ function
+
+ game_size -- two-value tuple specifying the size of the display in pixels,
+ this is currently static, so once the window is created it cannot be
+ changed.
+
+ If None, use the bulk of the screen for the PyGame surface based on
+ the values reported by the gtk.gdk functions. Note that None is
+ *not* the default value.
+
+ game_title -- title to be displayed in the Sugar Shell UI
+
+ pygame_mode -- chooses the rendering engine used for handling the
+ PyGame drawing mode, 'SDL' chooses the standard PyGame renderer,
+ 'Cairo' chooses the experimental pygamecairo renderer.
+
+ PYGAME_CANVAS_CLASS -- normally PyGameCanvas, but can be overridden
+ if you want to provide a different canvas class, e.g. to provide a different
+ internal layout. Note: only used where pygame_mode == 'SDL'
+
+ The Activity, once created, will be made available as olpcgames.ACTIVITY,
+ and that access mechanism should allow code to test for the presence of the
+ activity before accessing Sugar-specific functionality.
+
+ XXX Note that currently the toolbar and window layout are hard-coded into
+ this super-class, with no easy way of overriding without completely rewriting
+ the __init__ method. We should allow for customising both the UI layout and
+ the toolbar contents/layout/connection.
+
+ XXX Note that if you change the title of your activity in the toolbar you may
+ see the same focus issues as we have patched around in the build_toolbar
+ method. If so, please report them to Mike Fletcher.
+ """
+ game_name = None
+ game_title = 'PyGame Game'
+ game_handler = None
+ game_size = (16 * style.GRID_CELL_SIZE,
+ 11 * style.GRID_CELL_SIZE)
+ pygame_mode = 'SDL'
+
+ def __init__(self, handle):
+ """Initialise the Activity with the activity-description handle"""
+ super(PyGameActivity, self).__init__(handle)
+ self.make_global()
+ if self.game_size is None:
+ width,height = gtk.gdk.screen_width(), gtk.gdk.screen_height()
+ log.info( 'Total screen size: %s %s', width,height)
+ # for now just fudge the toolbar size...
+ self.game_size = width, height - (1*style.GRID_CELL_SIZE)
+ self.set_title(self.game_title)
+ toolbar = self.build_toolbar()
+ log.debug( 'Toolbar size: %s', toolbar.get_size_request())
+ canvas = self.build_canvas()
+
+ def make_global( self ):
+ """Hack to make olpcgames.ACTIVITY point to us
+ """
+ import weakref, olpcgames
+ assert not olpcgames.ACTIVITY, """Activity.make_global called twice, have you created two Activity instances in a single process?"""
+ olpcgames.ACTIVITY = weakref.proxy( self )
+
+ def build_toolbar( self ):
+ """Build our Activity toolbar for the Sugar system
+
+ This is a customisation point for those games which want to
+ provide custom toolbars when running under Sugar.
+ """
+ toolbar = activity.ActivityToolbar(self)
+ toolbar.show()
+ self.set_toolbox(toolbar)
+ def shared_cb(*args, **kwargs):
+ log.info( 'shared: %s, %s', args, kwargs )
+ try:
+ mesh.activity_shared(self)
+ except Exception, err:
+ log.error( """Failure signaling activity sharing to mesh module: %s""", util.get_traceback(err) )
+ else:
+ log.info( 'mesh activity shared message sent, trying to grab focus' )
+ try:
+ self._pgc.grab_focus()
+ except Exception, err:
+ log.warn( 'Focus failed: %s', err )
+ else:
+ log.info( 'asserting focus' )
+ assert self._pgc.is_focus(), """Did not successfully set pygame canvas focus"""
+ log.info( 'callback finished' )
+
+ def joined_cb(*args, **kwargs):
+ log.info( 'joined: %s, %s', args, kwargs )
+ mesh.activity_joined(self)
+ self._pgc.grab_focus()
+ self.connect("shared", shared_cb)
+ self.connect("joined", joined_cb)
+
+ if self.get_shared():
+ # if set at this point, it means we've already joined (i.e.,
+ # launched from Neighborhood)
+ joined_cb()
+
+ toolbar.title.unset_flags(gtk.CAN_FOCUS)
+ return toolbar
+
+ PYGAME_CANVAS_CLASS = PyGameCanvas
+ def build_canvas( self ):
+ """Construct the PyGame or PyGameCairo canvas for drawing"""
+ assert self.game_handler or self.game_name, 'You must specify a game_handler or game_name on your Activity (%r)'%(
+ self.game_handler or self.game_name
+ )
+ if self.pygame_mode != 'Cairo':
+ self._pgc = self.PYGAME_CANVAS_CLASS(*self.game_size)
+ self.set_canvas(self._pgc)
+ self._pgc.grab_focus()
+ self._pgc.connect_game(self.game_handler or self.game_name)
+ gtk.gdk.threads_init()
+ return self._pgc
+ else:
+ import hippo
+ self._drawarea = gtk.DrawingArea()
+ canvas = hippo.Canvas()
+ canvas.grab_focus()
+ self.set_canvas(canvas)
+ self.show_all()
+
+ import pygamecairo
+ pygamecairo.install()
+
+ pygamecairo.display.init(canvas)
+ app = self.game_handler or self.game_name
+ if ':' not in app:
+ app += ':main'
+ mod_name, fn_name = app.split(':')
+ mod = __import__(mod_name, globals(), locals(), [])
+ fn = getattr(mod, fn_name)
+ fn()
diff --git a/olpcgames/camera.py b/olpcgames/camera.py
new file mode 100644
index 0000000..b51a394
--- /dev/null
+++ b/olpcgames/camera.py
@@ -0,0 +1,235 @@
+"""Accesses OLPC Camera functionality via gstreamer
+
+Depends upon:
+ pygame
+ python-gstreamer
+"""
+import threading
+import logging
+import time
+import os
+import pygame
+import gst
+from olpcgames.util import get_activity_root
+
+log = logging.getLogger( 'olpcgames.camera' )
+#log.setLevel( logging.DEBUG )
+
+CAMERA_LOAD = 9917
+CAMERA_LOAD_FAIL = 9918
+
+class CameraSprite(object):
+ """Create gstreamer surface for the camera."""
+ def __init__(self, x, y):
+ import olpcgames
+ if olpcgames.WIDGET:
+ self._init_video(olpcgames.WIDGET, x, y)
+
+ def _init_video(self, widget, x, y):
+ from olpcgames import video
+ self._vid = video.VideoWidget()
+ widget._fixed.put(self._vid, x, y)
+ self._vid.show()
+
+ self.player = video.Player(self._vid)
+ self.player.play()
+
+class Camera(object):
+ """A class representing a still-picture camera
+
+ Produces a simple gstreamer bus that terminates in a filesink, that is,
+ it stores the results in a file. When a picture is "snapped" the gstreamer
+ stream is iterated until it finishes processing and then the file can be
+ read.
+
+ There are two APIs available, a synchronous API which can potentially
+ stall your activity's GUI (and is NOT recommended) and an
+ asynchronous API which returns immediately and delivers the captured
+ camera image via a Pygame event. To be clear, it is recommended
+ that you use the snap_async method, *not* the snap method.
+
+ Note:
+
+ The Camera class is simply a convenience wrapper around a fairly
+ straightforward gstreamer bus. If you have more involved
+ requirements for your camera manipulations you will probably
+ find it easier to write your own camera implementation than to
+ use this one. Basically we provide here the "normal" use case of
+ snapping a picture into a pygame image.
+ """
+ _aliases = {
+ 'camera': 'v4l2src',
+ 'test': 'videotestsrc',
+ 'testing': 'videotestsrc',
+ 'png': 'pngenc',
+ 'jpeg': 'jpegenc',
+ 'jpg': 'jpegenc',
+ }
+ def __init__(self, source='camera', format='png', filename='snap.png', directory = None):
+ """Initialises the Camera's internal description
+
+ source -- the gstreamer source for the video to capture, useful values:
+ 'v4l2src','camera' -- the camera
+ 'videotestsrc','test' -- test pattern generator source
+ format -- the gstreamer encoder to use for the capture, useful values:
+ 'pngenc','png' -- PNG format graphic
+ 'jpegenc','jpg','jpeg' -- JPEG format graphic
+ filename -- the filename to use for the capture
+ directory -- the directory in which to create the temporary file, defaults
+ to get_activity_root() + 'tmp'
+ """
+ self.source = self._aliases.get( source, source )
+ self.format = self._aliases.get( format, format )
+ self.filename = filename
+ self.directory = directory
+ SNAP_PIPELINE = '%(source)s ! ffmpegcolorspace ! %(format)s ! filesink location="%(filename)s"'
+ def _create_pipe( self ):
+ """Method to create the cstreamer pipe from our settings"""
+ if not self.directory:
+ path = os.path.join( get_activity_root(), 'tmp' )
+ try:
+ os.makedirs( path )
+ log.info( 'Created temporary directory: %s', path )
+ except (OSError,IOError), err:
+ pass
+ else:
+ path = self.directory
+ filename = os.path.join( path, self.filename )
+ format = self.format
+ source = self.source
+ pipeline = self.SNAP_PIPELINE % locals()
+ log.debug( 'Background thread processing: %s', pipeline )
+ return filename, gst.parse_launch(pipeline)
+
+ def snap(self):
+ """Snap a picture via the camera by iterating gstreamer until finished
+
+ Note: this is an unsafe implementation, it will cause the whole
+ activity to hang if the operation happens to fail! It is strongly
+ recommended that you use snap_async instead of snap!
+ """
+ log.debug( 'Starting snap' )
+ filename, pipe = self._create_pipe()
+ pipe.set_state(gst.STATE_PLAYING)
+ bus = pipe.get_bus()
+ tmp = False
+ while True:
+ event = self.bus.poll(gst.MESSAGE_STATE_CHANGED, 5)
+ if event:
+ old, new, pending = event.parse_state_changed()
+ if pending == gst.STATE_VOID_PENDING:
+ if tmp:
+ break
+ else:
+ tmp = True
+ else:
+ break
+ log.log( 'Ending snap, loading: %s', filename )
+ return self._load_and_clean( filename )
+ def _load_and_clean( self, filename ):
+ """Use pygame to load given filename, delete after loading/attempt"""
+ try:
+ log.info( 'Loading snapshot file: %s', filename )
+ return pygame.image.load(filename)
+ finally:
+ try:
+ os.remove( filename )
+ except (IOError,OSError), err:
+ pass
+ def snap_async( self, token=None ):
+ """Snap a picture asynchronously generating event on success/failure
+
+ token -- passed back as attribute of the event which signals that capture
+ is finished
+
+ We return two types of events CAMERA_LOAD and CAMERA_LOAD_FAIL,
+ depending on whether we succeed or not. Attributes of the events which
+ are returned:
+
+ token -- as passed to this method
+ filename -- the filename in our temporary directory we used to store
+ the file temporarily
+ image -- pygame image.load result if successful, None otherwise
+ err -- Exception instance if failed, None otherwise
+
+ Basically identical to the snap method, save that it posts a message
+ to the event bus in eventwrap instead of blocking and returning...
+ """
+ log.debug( 'beginning async snap')
+ t = threading.Thread(target=self._background_snap, args=(token,))
+ t.start()
+ log.debug( 'background thread started for gstreamer' )
+ return token
+
+ def _background_snap(
+ self,
+ token = None,
+ ):
+ """Process gst messages until pipe is finished
+
+ pipe -- gstreamer pipe definition for parse_launch, normally it will
+ produce a file into which the camera should store an image
+
+ We consider pipe to be finished when we have had two "state changed"
+ gstreamer events where the pending state is VOID, the first for when
+ we begin playing, the second for when we finish.
+ """
+ log.debug( 'Background thread kicking off gstreamer capture begun' )
+ from olpcgames import eventwrap
+ from pygame.event import Event
+ filename, pipe = self._create_pipe()
+ bus = pipe.get_bus()
+ bus.add_signal_watch()
+ def _background_snap_onmessage( bus, message ):
+ """Handle messages from the picture-snapping bus"""
+ log.debug( 'Message handler for gst messages: %s', message )
+ t = message.type
+ if t == gst.MESSAGE_EOS:
+ pipe.set_state(gst.STATE_NULL)
+ try:
+ image = self._load_and_clean( filename )
+ success = True
+ except Exception, err:
+ success = False
+ image = None
+ else:
+ err = None
+ log.debug( 'Success loading file %r', token )
+ eventwrap.post(Event(
+ CAMERA_LOAD,
+ filename=filename,
+ success = success,
+ token = token,
+ image=image,
+ err=err
+ ))
+
+ elif t == gst.MESSAGE_ERROR:
+ log.warn( 'Failure loading file %r: %s', token, message )
+ pipe.set_state(gst.STATE_NULL)
+ err, debug = message.parse_error()
+ eventwrap.post(Event(
+ CAMERA_LOAD_FAIL,
+ filename=filename,
+ success = False,
+ token = token,
+ image=None,
+ err=err
+ ))
+ return False
+ bus.connect('message', _background_snap_onmessage)
+ pipe.set_state(gst.STATE_PLAYING)
+
+def snap():
+ """Dump a snapshot from the camera to a pygame surface in background thread
+
+ See Camera.snap
+ """
+ return Camera().snap()
+
+def snap_async( token=None, **named ):
+ """Dump snapshot from camera return asynchronously as event in Pygame
+
+ See Camera.snap_async
+ """
+ return Camera(**named).snap_async( token )
diff --git a/olpcgames/canvas.py b/olpcgames/canvas.py
new file mode 100644
index 0000000..0874d2d
--- /dev/null
+++ b/olpcgames/canvas.py
@@ -0,0 +1,124 @@
+"""Implements bridge connection between Sugar/GTK and PyGame"""
+import os
+import sys
+import logging
+log = logging.getLogger( 'olpcgames.canvas' )
+#log.setLevel( logging.DEBUG )
+import threading
+from pprint import pprint
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gobject
+import pygame
+
+from olpcgames import gtkEvent, util
+
+__all__ = ['PyGameCanvas']
+
+class PyGameCanvas(gtk.Layout):
+ """Canvas providing bridge methods to run PyGame in GTK
+
+ The PyGameCanvas creates a secondary thread in which the Pygame instance will
+ live, providing synthetic PyGame events to that thread via a Queue. The GUI
+ connection is done by having the PyGame canvas use a GTK Port object as it's
+ window pointer, it draws to that X-level window in order to produce output.
+ """
+ def __init__(self, width, height):
+ """Initializes the Canvas Object
+
+ width,height -- passed to the inner EventBox in order to request a given size,
+ the Socket is the only child of this EventBox, and the PyGame commands
+ will be writing to the Window ID of the socket. The internal EventBox is
+ centered via an Alignment instance within the PyGameCanvas instance.
+
+ XXX Should refactor so that the internal setup can be controlled by the
+ sub-class, e.g. to get size from the host window, or something similar.
+ """
+ # Build the main widget
+ super(PyGameCanvas,self).__init__()
+ self.set_flags(gtk.CAN_FOCUS)
+
+ # Build the sub-widgets
+ self._align = gtk.Alignment(0.5, 0.5)
+ self._inner_evb = gtk.EventBox()
+ self._socket = gtk.Socket()
+
+
+ # Add internal widgets
+ self._inner_evb.set_size_request(width, height)
+ self._inner_evb.add(self._socket)
+
+ self._socket.show()
+
+ self._align.add(self._inner_evb)
+ self._inner_evb.show()
+
+ self._align.show()
+
+ self.put(self._align, 0,0)
+
+ # Construct a gtkEvent.Translator
+ self._translator = gtkEvent.Translator(self, self._inner_evb)
+ # <Cue Thus Spract Zarathustra>
+ self.show()
+ def connect_game(self, app):
+ """Imports the given main-loop and starts processing in secondary thread
+
+ app -- fully-qualified Python path-name for the game's main-loop, with
+ name within module as :functionname, if no : character is present then
+ :main will be assumed.
+
+ Side effects:
+
+ Sets the SDL_WINDOWID variable to our socket's window ID
+ Calls PyGame init
+ Causes the gtkEvent.Translator to "hook" PyGame
+ Creates and starts secondary thread for Game/PyGame event processing.
+ """
+ # Setup the embedding
+ os.environ['SDL_WINDOWID'] = str(self._socket.get_id())
+ #print 'Socket ID=%s'%os.environ['SDL_WINDOWID']
+ pygame.init()
+
+ self._translator.hook_pygame()
+
+ # Load the modules
+ # NOTE: This is delayed because pygame.init() must come after the embedding is up
+ if ':' not in app:
+ app += ':main'
+ mod_name, fn_name = app.split(':')
+ mod = __import__(mod_name, globals(), locals(), [])
+ fn = getattr(mod, fn_name)
+
+ # Start Pygame
+ self.__thread = threading.Thread(target=self._start, args=[fn])
+ self.__thread.start()
+
+ def _start(self, fn):
+ """The method that actually runs in the background thread"""
+ import olpcgames
+ olpcgames.widget = olpcgames.WIDGET = self
+ try:
+ import sugar.activity.activity,os
+ except ImportError, err:
+ log.info( """Running outside Sugar""" )
+ else:
+ try:
+ os.chdir(sugar.activity.activity.get_bundle_path())
+ except KeyError, err:
+ pass
+
+ try:
+ try:
+ log.info( '''Running mainloop: %s''', fn )
+ fn()
+ except Exception, err:
+ log.error(
+ """Uncaught top-level exception: %s""",
+ util.get_traceback( err ),
+ )
+ raise
+ finally:
+ gtk.main_quit()
diff --git a/olpcgames/data/__init__.py b/olpcgames/data/__init__.py
new file mode 100755
index 0000000..8510186
--- /dev/null
+++ b/olpcgames/data/__init__.py
@@ -0,0 +1,36 @@
+"""Design-time __init__.py for resourcepackage
+
+This is the scanning version of __init__.py for your
+resource modules. You replace it with a blank or doc-only
+init when ready to release.
+"""
+try:
+ __file__
+except NameError:
+ pass
+else:
+ import os
+ if os.path.splitext(os.path.basename( __file__ ))[0] == "__init__":
+ try:
+ from resourcepackage import package, defaultgenerators
+ generators = defaultgenerators.generators.copy()
+
+ ### CUSTOMISATION POINT
+ ## import specialised generators here, such as for wxPython
+ #from resourcepackage import wxgenerators
+ #generators.update( wxgenerators.generators )
+ except ImportError:
+ pass
+ else:
+ package = package.Package(
+ packageName = __name__,
+ directory = os.path.dirname( os.path.abspath(__file__) ),
+ generators = generators,
+ )
+ package.scan(
+ ### CUSTOMISATION POINT
+ ## force true -> always re-loads from external files, otherwise
+ ## only reloads if the file is newer than the generated .py file.
+ # force = 1,
+ )
+
diff --git a/olpcgames/data/sleeping_svg.py b/olpcgames/data/sleeping_svg.py
new file mode 100644
index 0000000..fa67eee
--- /dev/null
+++ b/olpcgames/data/sleeping_svg.py
@@ -0,0 +1,501 @@
+# -*- coding: ISO-8859-1 -*-
+"""Resource sleeping_svg (from file sleeping.svg)"""
+# written by resourcepackage: (1, 0, 1)
+source = 'sleeping.svg'
+package = 'olpcgames.data'
+data = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\012<!-- \
+Created with Inkscape (http://www.inkscape.org/) -->\012<svg\012 \
+ xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\012 xmlns:cc=\"ht\
+tp://web.resource.org/cc/\"\012 xmlns:rdf=\"http://www.w3.org/1\
+999/02/22-rdf-syntax-ns#\"\012 xmlns:svg=\"http://www.w3.org/20\
+00/svg\"\012 xmlns=\"http://www.w3.org/2000/svg\"\012 xmlns:xlink\
+=\"http://www.w3.org/1999/xlink\"\012 xmlns:sodipodi=\"http://so\
+dipodi.sourceforge.net/DTD/sodipodi-0.dtd\"\012 xmlns:inkscape\
+=\"http://www.inkscape.org/namespaces/inkscape\"\012 width=\"736\
+.60107\"\012 height=\"923.09717\"\012 id=\"svg2\"\012 sodipodi:versi\
+on=\"0.32\"\012 inkscape:version=\"0.45.1\"\012 sodipodi:docbase=\"\
+/home/mcfletch/olpc/code/games-misc/olpcgames-src/olpcgames/\
+data\"\012 sodipodi:docname=\"sleeping.svg\"\012 inkscape:output_\
+extension=\"org.inkscape.output.svg.inkscape\"\012 version=\"1.0\
+\">\012 <defs\012 id=\"defs4\">\012 <linearGradient\012 id=\"l\
+inearGradient3152\">\012 <stop\012 style=\"stop-color:#\
+b8ffb4;stop-opacity:1;\"\012 offset=\"0\"\012 id=\"sto\
+p3154\" />\012 <stop\012 id=\"stop3214\"\012 offset\
+=\"0.5\"\012 style=\"stop-color:#2eff22;stop-opacity:0.498\
+03922;\" />\012 <stop\012 style=\"stop-color:#ffffff;st\
+op-opacity:0;\"\012 offset=\"1\"\012 id=\"stop3156\" />\
+\012 </linearGradient>\012 <radialGradient\012 inkscape:c\
+ollect=\"always\"\012 xlink:href=\"#linearGradient3152\"\012 \
+ id=\"radialGradient3158\"\012 cx=\"260.12256\"\012 cy=\"\
+235.24702\"\012 fx=\"260.12256\"\012 fy=\"235.24702\"\012 \
+ r=\"259.29678\"\012 gradientTransform=\"matrix(1,0,0,1.253\
+1846,0,-59.560934)\"\012 gradientUnits=\"userSpaceOnUse\" />\
+\012 </defs>\012 <sodipodi:namedview\012 id=\"base\"\012 pagecol\
+or=\"#ffffff\"\012 bordercolor=\"#666666\"\012 borderopacity=\"\
+1.0\"\012 gridtolerance=\"10000\"\012 guidetolerance=\"10\"\012 \
+ objecttolerance=\"10\"\012 inkscape:pageopacity=\"0.0\"\012 \
+inkscape:pageshadow=\"2\"\012 inkscape:zoom=\"1.2109676\"\012 \
+inkscape:cx=\"382.85714\"\012 inkscape:cy=\"461.2069\"\012 ink\
+scape:document-units=\"px\"\012 inkscape:current-layer=\"layer\
+1\"\012 inkscape:window-width=\"1600\"\012 inkscape:window-he\
+ight=\"1127\"\012 inkscape:window-x=\"0\"\012 inkscape:window-\
+y=\"0\" />\012 <metadata\012 id=\"metadata7\">\012 <rdf:RDF>\012 \
+ <cc:Work\012 rdf:about=\"\">\012 <dc:format>image/s\
+vg+xml</dc:format>\012 <dc:type\012 rdf:resource=\
+\"http://purl.org/dc/dcmitype/StillImage\" />\012 </cc:Work>\
+\012 </rdf:RDF>\012 </metadata>\012 <g\012 inkscape:label=\"Laye\
+r 1\"\012 inkscape:groupmode=\"layer\"\012 id=\"layer1\"\012 t\
+ransform=\"translate(-3.6769901,-73.051161)\">\012 <path\012 \
+ sodipodi:type=\"arc\"\012 style=\"opacity:1;color:#000000;\
+fill:url(#radialGradient3158);fill-opacity:1;fill-rule:eveno\
+dd;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-l\
+inejoin:miter;marker:none;marker-start:none;marker-mid:none;\
+marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;st\
+roke-dashoffset:0;stroke-opacity:1;visibility:visible;displa\
+y:inline;overflow:visible\"\012 id=\"path2178\"\012 sodip\
+odi:cx=\"260.12256\"\012 sodipodi:cy=\"235.24702\"\012 sod\
+ipodi:rx=\"259.29678\"\012 sodipodi:ry=\"324.94675\"\012 d\
+=\"M 519.41934 235.24702 A 259.29678 324.94675 0 1 1 0.82577\
+515,235.24702 A 259.29678 324.94675 0 1 1 519.41934 235.247\
+02 z\"\012 transform=\"matrix(1.4203822,0,0,1.4203822,2.504\
+0738,200.45905)\" />\012 <path\012 style=\"fill:#000000;fil\
+l-opacity:0.75;fill-rule:nonzero;stroke:none;stroke-width:1p\
+t;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\
+\"\012 id=\"path2160\"\012 d=\"M 420.07983,366.13071 C 420\
+.35487,366.47717 420.72076,366.7246 421.0413,367.02162 C 421\
+.54375,367.47276 422.06281,367.90429 422.57382,368.34583 C 4\
+23.28729,368.97986 424.02228,369.58903 424.74649,370.2106 C \
+425.62503,370.96294 426.49869,371.72131 427.37129,372.48063 \
+C 428.38549,373.33736 429.37161,374.22678 430.36963,375.1020\
+2 C 431.51957,376.11144 432.66227,377.12909 433.8068,378.144\
+7 C 435.10375,379.28207 436.39638,380.42416 437.68916,381.56\
+629 C 439.07238,382.75868 440.43413,383.9754 441.79974,385.1\
+8789 C 443.20004,386.42238 444.62683,387.62616 446.04562,388\
+.8391 C 447.49676,390.05005 448.96429,391.24028 450.43445,39\
+2.42792 C 451.96417,393.65117 453.55907,394.78949 455.13698,\
+395.94898 C 456.83948,397.17768 458.60045,398.31988 460.3532\
+5,399.47417 C 462.27994,400.72959 464.31105,401.80854 466.32\
+055,402.92156 C 468.55661,404.14536 470.8622,405.23086 473.1\
+6967,406.31074 C 475.67184,407.45821 478.22515,408.48722 480\
+.77405,409.52454 C 483.51937,410.62204 486.31152,411.59223 4\
+89.10452,412.55924 C 492.05602,413.59241 495.06998,414.42774\
+ 498.07923,415.2716 C 501.23779,416.16616 504.44361,416.8708\
+8 507.64836,417.5745 C 510.92868,418.27561 514.24385,418.790\
+77 517.55871,419.29543 C 520.86716,419.83295 524.19756,420.1\
+9662 527.5309,420.53139 C 530.86062,420.85145 534.19927,421.\
+03598 537.53901,421.20717 C 540.87406,421.39592 544.2147,421\
+.33746 547.55212,421.28446 C 550.92768,421.21773 554.29512,4\
+20.98306 557.66012,420.72502 C 561.0209,420.45787 564.3698,4\
+20.06437 567.71901,419.68331 C 570.96499,419.33121 574.18795\
+,418.8046 577.41097,418.29085 C 580.55689,417.7694 583.67686\
+,417.11029 586.79515,416.44738 C 589.93249,415.74608 593.044\
+28,414.93908 596.15094,414.1143 C 599.21068,413.29028 602.23\
+59,412.3538 605.25231,411.38586 C 608.26457,410.38752 611.22\
+46,409.24314 614.18464,408.1019 C 617.09398,406.98339 619.95\
+45,405.75011 622.80697,404.49587 C 625.61411,403.24034 628.3\
+8046,401.89814 631.14782,400.55819 C 633.83692,399.24273 636\
+.49752,397.87128 639.15408,396.49166 C 641.69701,395.16459 6\
+44.21761,393.79564 646.73927,392.4287 C 649.226,391.07864 65\
+1.69596,389.6984 654.16621,388.31856 C 664.28369,382.47714 6\
+35.39954,399.24448 647.39096,392.20906 C 649.71362,390.83128\
+ 652.01714,389.42151 654.32347,388.01666 C 656.58074,386.649\
+11 658.81401,385.24283 661.04492,383.83292 C 663.187,382.479\
+81 665.31986,381.11219 667.45597,379.74972 L 682.67421,373.8\
+2852 C 680.53333,375.19377 678.39504,376.56299 676.25306,377\
+.92652 C 674.02228,379.3423 671.79047,380.75695 669.53525,38\
+2.13372 C 667.22445,383.53974 664.91761,384.95222 662.60433,\
+386.35423 C 655.61128,390.50089 648.56215,394.55382 641.4868\
+8,398.55869 C 639.01339,399.93149 636.53782,401.30047 634.04\
+976,402.64675 C 631.52675,404.00642 629.00428,405.36735 626.\
+45516,406.67776 C 623.79203,408.04539 621.12639,409.40838 61\
+8.42657,410.70277 C 615.65387,412.03449 612.87794,413.36009 \
+610.05858,414.59127 C 607.18935,415.82018 604.3116,417.02842\
+ 601.39097,418.13174 C 598.41438,419.25931 595.43663,420.387\
+92 592.40026,421.34741 C 589.36654,422.28426 586.3267,423.20\
+206 583.25307,424.00155 C 580.12921,424.80704 577.00369,425.\
+61049 573.84621,426.27614 C 570.71325,426.91875 567.57887,42\
+7.5585 564.41815,428.0531 C 561.18267,428.54536 557.94654,42\
+9.03847 554.69048,429.37858 C 551.33145,429.74649 547.97234,\
+430.1215 544.60158,430.36782 C 541.21644,430.5953 537.82951,\
+430.81131 534.43558,430.8518 C 531.07392,430.87518 527.71049\
+,430.89848 524.35301,430.68741 C 520.99953,430.49414 517.647\
+13,430.28301 514.30415,429.94706 C 510.95344,429.57661 507.6\
+051,429.18426 504.27719,428.63828 C 500.94305,428.10693 497.\
+60828,427.56958 494.31089,426.83677 C 491.08361,426.10923 48\
+7.85646,425.37704 484.67505,424.46407 C 481.64095,423.59885 \
+478.60428,422.73682 475.62985,421.67916 C 472.81539,420.6957\
+9 470.00238,419.70689 467.23755,418.58909 C 464.66531,417.53\
+539 462.08915,416.48836 459.56863,415.31414 C 457.23636,414.\
+21006 454.9071,413.09837 452.64473,411.85467 C 450.60324,410\
+.7133 448.53944,409.60683 446.58754,408.31323 C 444.81432,40\
+7.13778 443.02862,405.98003 441.31079,404.72322 C 439.71016,\
+403.54798 438.09384,402.39203 436.55288,401.13812 C 435.0762\
+1,399.94004 433.59657,398.74568 432.14156,397.52114 C 430.71\
+41,396.29841 429.28064,395.08245 427.873,393.83679 C 426.503\
+83,392.62682 425.13537,391.41618 423.75477,390.21924 C 422.4\
+5847,389.08225 421.16119,387.9464 419.86302,386.81152 C 418.\
+70791,385.79925 417.55469,384.78482 416.40007,383.772 C 415.\
+40028,382.90611 414.40456,382.03536 413.39731,381.17806 C 41\
+2.52422,380.4317 411.656,379.67945 410.77279,378.9451 C 410.\
+03818,378.32256 409.30237,377.70143 408.56894,377.07747 C 40\
+8.05171,376.64287 407.52411,376.22075 407.01733,375.77379 C \
+406.66439,375.42865 406.27154,375.12044 405.95609,374.736 L \
+420.07983,366.13071 z \" />\012 <path\012 d=\"M 322.28988,3\
+66.13071 C 322.01483,366.47717 321.64895,366.7246 321.32841,\
+367.02162 C 320.82597,367.47276 320.3069,367.90429 319.79589\
+,368.34583 C 319.08241,368.97986 318.34743,369.58903 317.623\
+22,370.2106 C 316.74468,370.96294 315.87102,371.72131 314.99\
+842,372.48063 C 313.98423,373.33736 312.9981,374.22678 312.0\
+001,375.10202 C 310.85015,376.11144 309.70744,377.12909 308.\
+56293,378.1447 C 307.26598,379.28207 305.97336,380.42416 304\
+.68058,381.56629 C 303.29734,382.75868 301.93559,383.9754 30\
+0.57,385.18789 C 299.1697,386.42238 297.74291,387.62616 296.\
+32412,388.8391 C 294.873,390.05005 293.40545,391.24028 291.9\
+3531,392.42792 C 290.40559,393.65117 288.81068,394.78949 287\
+.23278,395.94898 C 285.53028,397.17768 283.7693,398.31988 28\
+2.01651,399.47417 C 280.08982,400.72959 278.0587,401.80854 2\
+76.0492,402.92156 C 273.81315,404.14536 271.50756,405.23086 \
+269.20009,406.31074 C 266.69792,407.45821 264.14461,408.4872\
+2 261.59571,409.52454 C 258.85039,410.62204 256.05823,411.59\
+223 253.26523,412.55924 C 250.31374,413.59241 247.29977,414.\
+42774 244.29052,415.2716 C 241.13196,416.16616 237.92614,416\
+.87088 234.72139,417.5745 C 231.44108,418.27561 228.12591,41\
+8.79077 224.81105,419.29543 C 221.50259,419.83295 218.17219,\
+420.19662 214.83886,420.53139 C 211.50914,420.85145 208.1704\
+9,421.03598 204.83074,421.20717 C 201.4957,421.39592 198.155\
+06,421.33746 194.81763,421.28446 C 191.44208,421.21773 188.0\
+7464,420.98306 184.70964,420.72502 C 181.34886,420.45787 177\
+.99995,420.06437 174.65075,419.68331 C 171.40476,419.33121 1\
+68.1818,418.8046 164.95878,418.29085 C 161.81286,417.7694 15\
+8.69291,417.11029 155.57462,416.44738 C 152.43728,415.74608 \
+149.3255,414.93908 146.21884,414.1143 C 143.1591,413.29028 1\
+40.13389,412.3538 137.1175,411.38586 C 134.10524,410.38752 1\
+31.14523,409.24314 128.1852,408.1019 C 125.27586,406.98339 1\
+22.41534,405.75011 119.56288,404.49587 C 116.75575,403.24034\
+ 113.9894,401.89814 111.22207,400.55819 C 108.53297,399.2427\
+3 105.87237,397.87128 103.21581,396.49166 C 100.67287,395.16\
+459 98.152294,393.79564 95.630638,392.4287 C 93.14389,391.07\
+864 90.673951,389.6984 88.203707,388.31856 C 78.086243,382.4\
+7714 106.97036,399.24448 94.978939,392.20906 C 92.656273,390\
+.83128 90.352774,389.42151 88.046448,388.01666 C 85.789177,3\
+86.64911 83.555916,385.24283 81.324993,383.83292 C 79.182934\
+,382.47981 77.050074,381.11219 74.913953,379.74972 L 59.6957\
+31,373.82852 C 61.836611,375.19377 63.974911,376.56299 66.11\
+6884,377.92652 C 68.34766,379.3423 70.579478,380.75695 72.83\
+467,382.13372 C 75.145475,383.53974 77.452326,384.95222 79.7\
+65589,386.35423 C 86.758645,390.50089 93.807748,394.55382 10\
+0.883,398.55869 C 103.3565,399.93149 105.83208,401.30047 108\
+.32013,402.64675 C 110.84314,404.00642 113.36559,405.36735 1\
+15.9147,406.67776 C 118.57783,408.04539 121.24346,409.40838 \
+123.94327,410.70277 C 126.71597,412.03449 129.49189,413.3600\
+9 132.31123,414.59127 C 135.18046,415.82018 138.0582,417.028\
+42 140.97881,418.13174 C 143.95541,419.25931 146.93315,420.3\
+8792 149.96952,421.34741 C 153.00323,422.28426 156.04307,423\
+.20206 159.1167,424.00155 C 162.24054,424.80704 165.36606,42\
+5.61049 168.52355,426.27614 C 171.65651,426.91875 174.79088,\
+427.5585 177.9516,428.0531 C 181.18709,428.54536 184.42322,4\
+29.03847 187.67927,429.37858 C 191.03831,429.74649 194.39741\
+,430.1215 197.76818,430.36782 C 201.15332,430.5953 204.54025\
+,430.81131 207.93418,430.8518 C 211.29584,430.87518 214.6592\
+6,430.89848 218.01675,430.68741 C 221.37023,430.49414 224.72\
+263,430.28301 228.06561,429.94706 C 231.41632,429.57661 234.\
+76466,429.18426 238.09257,428.63828 C 241.4267,428.10693 244\
+.76148,427.56958 248.05887,426.83677 C 251.28615,426.10923 2\
+54.5133,425.37704 257.69471,424.46407 C 260.7288,423.59885 2\
+63.76548,422.73682 266.7399,421.67916 C 269.55436,420.69579 \
+272.36737,419.70689 275.1322,418.58909 C 277.70444,417.53539\
+ 280.2806,416.48836 282.80113,415.31414 C 285.1334,414.21006\
+ 287.46265,413.09837 289.72502,411.85467 C 291.7665,410.7133\
+ 293.83031,409.60683 295.78222,408.31323 C 297.55542,407.137\
+78 299.34112,405.98003 301.05894,404.72322 C 302.65958,403.5\
+4798 304.27589,402.39203 305.81685,401.13812 C 307.29352,399\
+.94004 308.77316,398.74568 310.22816,397.52114 C 311.65561,3\
+96.29841 313.08907,395.08245 314.49672,393.83679 C 315.86588\
+,392.62682 317.23435,391.41618 318.61494,390.21924 C 319.911\
+24,389.08225 321.20852,387.9464 322.50669,386.81152 C 323.66\
+18,385.79925 324.81503,384.78482 325.96963,383.772 C 326.969\
+42,382.90611 327.96513,382.03536 328.97238,381.17806 C 329.8\
+4548,380.4317 330.71369,379.67945 331.59691,378.9451 C 332.3\
+3152,378.32256 333.06732,377.70143 333.80075,377.07747 C 334\
+.31799,376.64287 334.84558,376.22075 335.35236,375.77379 C 3\
+35.70529,375.42865 336.09814,375.12044 336.4136,374.736 L 32\
+2.28988,366.13071 z \"\012 id=\"path2162\"\012 style=\"fil\
+l:#000000;fill-opacity:0.75;fill-rule:nonzero;stroke:none;st\
+roke-width:1pt;stroke-linecap:butt;stroke-linejoin:miter;str\
+oke-opacity:1\" />\012 <path\012 style=\"fill:#000000;fill-\
+opacity:0.75;fill-rule:nonzero;stroke:none;stroke-width:1pt;\
+stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"\012\
+ id=\"path2174\"\012 d=\"M 363.56451,383.66406 C 363.2\
+9789,384.11947 363.11097,384.61437 362.88438,385.08913 C 362\
+.56503,385.71349 362.28415,386.35637 361.98417,386.98968 C 3\
+61.59883,387.80682 361.31001,388.66309 360.97303,389.49955 C\
+ 360.58158,390.48782 360.2446,391.49591 359.88304,392.49501 \
+C 359.47472,393.65559 359.09862,394.82693 358.71289,395.9950\
+8 C 358.28054,397.30269 357.91308,398.63001 357.52373,399.95\
+051 C 357.10226,401.42131 356.77445,402.91584 356.41772,404.\
+40288 C 356.03758,406.05957 355.67624,407.72044 355.31143,40\
+9.38051 C 354.93639,411.10586 354.62209,412.8432 354.29778,4\
+14.57846 C 353.95914,416.41072 353.68547,418.25378 353.39986\
+,420.09471 C 353.09767,422.01995 352.87618,423.95563 352.643\
+78,425.88995 C 352.40586,427.8967 352.24593,429.91073 352.07\
+562,431.92382 C 351.91629,433.95529 351.78304,435.98864 351.\
+64542,438.02165 C 351.5051,440.1096 351.38765,442.19895 351.\
+26622,444.28805 C 351.13491,446.44906 350.9941,448.60949 350\
+.85664,450.77013 C 350.72765,452.93795 350.56813,455.10378 3\
+50.41467,457.26996 C 350.25248,459.46881 350.07615,461.66658\
+ 349.90426,463.86466 C 349.72711,466.05242 349.50047,468.235\
+71 349.28474,470.41986 C 349.06933,472.63327 348.80288,474.8\
+409 348.54333,477.04935 C 348.27687,479.24544 347.9552,481.4\
+3401 347.6439,483.62388 C 347.3292,485.821 346.94529,488.006\
+52 346.56931,490.19356 C 346.18304,492.38223 345.7145,494.55\
+499 345.2634,496.73079 C 344.80304,498.88744 344.25754,501.0\
+2401 343.72541,503.16357 C 343.19873,505.27872 342.60709,507\
+.37642 342.02636,509.477 C 341.45242,511.5036 340.81692,513.\
+51179 340.19303,515.52327 C 339.579,517.48655 338.89351,519.\
+4259 338.22066,521.3693 C 337.52576,523.32558 336.75445,525.\
+25314 335.99664,527.18551 C 335.25621,529.09523 334.4656,530\
+.98465 333.68412,532.87762 C 332.89153,534.72375 332.09494,5\
+36.56812 331.29879,538.4127 C 330.52012,540.19102 329.72848,\
+541.9636 328.94044,543.73774 C 328.17627,545.48552 327.37923\
+,547.21844 326.58698,548.95351 C 325.79159,550.70985 324.963\
+38,552.45063 324.13807,554.19294 C 323.29657,555.94061 322.4\
+3736,557.67961 321.58006,559.41955 C 320.71339,561.15716 319\
+.85494,562.89885 318.99475,564.63967 C 318.12909,566.38264 3\
+17.29007,568.13858 316.44572,569.8919 C 315.57499,571.66105 \
+314.78167,573.46556 313.97771,575.26527 C 313.19325,577.0957\
+7 312.47033,578.95167 311.73461,580.80204 C 310.99762,582.66\
+364 310.33451,584.55312 309.65608,586.43649 C 308.96316,588.\
+34295 308.36923,590.28231 307.75868,592.21589 C 307.15712,59\
+4.14403 306.62912,596.09342 306.0891,598.03922 C 305.56263,5\
+99.9412 305.15523,601.87189 304.72869,603.79759 C 304.30073,\
+605.67457 304.06586,607.58362 303.80201,609.48748 C 303.5324\
+9,611.35505 303.50387,613.23908 303.43969,615.12014 C 303.37\
+464,616.94922 303.54021,618.77033 303.66927,620.59176 C 303.\
+79296,622.3199 304.08519,624.02622 304.35157,625.73538 C 304\
+.61502,627.39034 305.10873,628.99125 305.56074,630.60013 C 3\
+06.00752,632.15988 306.58838,633.67514 307.12864,635.20312 C\
+ 307.6775,636.69384 308.43059,638.09344 309.11955,639.52034 \
+C 309.81034,640.91074 310.62616,642.2312 311.40271,643.57325\
+ C 312.16936,644.91811 313.11662,646.14135 314.00418,647.403\
+93 C 314.99219,648.72656 316.15747,649.89632 317.28843,651.0\
+9288 C 318.46185,652.30686 319.74291,653.40671 321.00019,654\
+.53049 C 322.2966,655.6648 323.68994,656.67667 325.06765,657\
+.70741 C 326.47942,658.75983 327.95117,659.72705 329.40559,6\
+60.71847 C 330.83949,661.67678 332.34116,662.52443 333.83093\
+,663.39053 C 335.29556,664.25037 336.83732,664.96109 338.365\
+67,665.69625 C 339.90883,666.42997 341.52771,666.97576 343.1\
+3097,667.55664 C 344.73218,668.10981 346.35529,668.58875 347\
+.97795,669.07256 C 349.65491,669.51863 351.38306,669.68065 3\
+53.09946,669.88963 C 354.84867,670.06333 356.60545,670.12506\
+ 358.36018,670.20906 C 360.07756,670.2966 361.79638,670.2728\
+7 363.51489,670.26883 C 365.20531,670.27174 366.89234,670.15\
+027 368.57919,670.0587 C 370.24527,669.9842 371.90054,669.78\
+813 373.55711,669.60806 C 375.19642,669.43166 376.82701,669.\
+1875 378.45965,668.9609 C 380.05633,668.73502 381.639,668.42\
+532 383.22608,668.14241 C 384.79034,667.88127 386.33919,667.\
+53818 387.89293,667.22433 C 389.43429,666.90277 390.96344,66\
+6.52725 392.49528,666.16451 C 394.03056,665.81731 395.5471,6\
+65.39645 397.06849,664.99483 C 398.64264,664.56434 400.22202\
+,664.15336 401.79852,663.73164 C 403.36426,663.26119 404.935\
+2,662.80866 406.50541,662.3535 C 408.05433,661.88535 409.586\
+83,661.36613 411.1234,660.86012 C 412.66775,660.35111 414.18\
+526,659.76629 415.71252,659.20982 C 417.23752,658.65947 418.\
+74032,658.0508 420.25157,657.4645 C 421.78841,656.85547 423.\
+2868,656.15704 424.79163,655.47466 C 426.29961,654.81287 427\
+.76918,654.06794 429.24814,653.34475 C 430.6928,652.6429 432\
+.0654,651.81007 433.46052,651.01822 C 409.43233,665.22265 41\
+8.13774,659.96775 423.38798,656.71194 C 424.6585,655.8887 42\
+5.88948,655.00695 427.13279,654.14384 C 428.32441,653.29299 \
+429.45698,652.3653 430.60682,651.46022 C 431.47422,650.77115\
+ 432.34875,650.09101 433.22174,649.40909 L 448.5228,643.3551\
+8 C 447.63804,644.01509 446.72472,644.64308 445.86728,645.34\
+265 C 444.71845,646.26217 443.57512,647.18953 442.40365,648.\
+08019 C 441.17228,648.9734 439.95693,649.89058 438.68912,650\
+.73413 C 432.83662,654.52282 427.0541,658.31426 420.69402,66\
+1.34738 C 419.27728,662.11016 417.8829,662.91763 416.41627,6\
+63.58508 C 414.92691,664.29306 413.44902,665.02549 411.93432\
+,665.67932 C 410.41153,666.3424 408.89415,667.01896 407.3392\
+1,667.60508 C 405.82142,668.184 404.30728,668.77253 402.7802\
+3,669.32688 C 401.24293,669.87049 399.71138,670.43093 398.15\
+957,670.93266 C 396.61026,671.42265 395.06745,671.93429 393.\
+5035,672.37678 C 391.92791,672.83163 390.35562,673.29957 388\
+.77032,673.71957 C 387.19381,674.14305 385.61788,674.56863 3\
+84.04086,674.99015 C 382.50743,675.37425 380.97868,675.77697\
+ 379.43716,676.12841 C 377.89624,676.47487 376.35896,676.837\
+7 374.81053,677.15004 C 373.24643,677.44935 371.68464,677.76\
+125 370.11446,678.02797 C 368.51685,678.29656 366.9236,678.5\
+9385 365.31562,678.79756 C 363.67262,679.0061 362.03203,679.\
+23485 360.38388,679.40114 C 358.70992,679.54982 357.03695,67\
+9.70997 355.35705,679.78096 C 353.653,679.85299 351.94917,67\
+9.93986 350.24276,679.91999 C 348.51003,679.89948 346.77686,\
+679.89135 345.046,679.79257 C 343.26754,679.68408 341.48595,\
+679.6006 339.71726,679.37303 C 337.94225,679.10105 336.15156\
+,678.88936 334.43306,678.33765 C 332.79598,677.8244 331.1544\
+,677.3242 329.53893,676.74424 C 327.90173,676.11886 326.2496\
+3,675.52578 324.67627,674.74596 C 323.1254,673.97225 321.562\
+17,673.22067 320.07267,672.33029 C 318.56132,671.43198 317.0\
+3713,670.55306 315.58562,669.55826 C 314.11604,668.54878 312\
+.63482,667.55487 311.20896,666.48351 C 309.80579,665.41766 3\
+08.38349,664.37439 307.0683,663.19816 C 305.78429,662.03802 \
+304.47767,660.90001 303.28216,659.64604 C 302.11242,658.3914\
+3 300.9084,657.16342 299.88424,655.78196 C 298.96626,654.474\
+88 297.98646,653.20835 297.19409,651.81588 C 296.39988,650.4\
+375 295.56515,649.0813 294.85536,647.65537 C 294.13919,646.1\
+7664 293.36012,644.72494 292.78129,643.18325 C 292.22278,641\
+.62181 291.61949,640.07435 291.16208,638.47811 C 290.68499,6\
+36.81415 290.1599,635.15907 289.86919,633.44829 C 289.57968,\
+631.69328 289.25735,629.942 289.12427,628.16477 C 288.97373,\
+626.29499 288.78192,624.42493 288.82889,622.5457 C 288.86416\
+,620.6118 288.86309,618.67496 289.1145,616.75238 C 289.35851\
+,614.80657 289.56627,612.85449 289.98378,610.93512 C 290.396\
+86,608.98011 290.79545,607.02179 291.3074,605.08916 C 291.83\
+86,603.12616 292.35898,601.16001 292.95529,599.21532 C 293.5\
+5531,597.26108 294.13954,595.30137 294.82052,593.37312 C 295\
+.49687,591.47567 296.15668,589.57191 296.87156,587.68842 C 2\
+97.60792,585.82579 298.33182,583.95803 299.09663,582.10678 C\
+ 299.88215,580.28367 300.65555,578.45551 301.52849,576.67149\
+ C 302.37027,574.91155 303.21063,573.15093 304.0723,571.4006\
+ C 304.92867,569.65917 305.77955,567.91493 306.65382,566.182\
+38 C 307.51229,564.44638 308.37352,562.71183 309.21869,560.9\
+6929 C 310.05554,559.23843 310.89164,557.50748 311.67649,555\
+.75215 C 312.47234,554.02453 313.28387,552.30409 314.03792,5\
+50.55739 C 314.82916,548.78481 315.62848,547.01578 316.40577\
+,545.23701 C 317.2127,543.40023 318.01405,541.56117 318.8030\
+7,539.71654 C 319.59553,537.83852 320.40019,535.96609 321.14\
+196,534.06683 C 321.89793,532.14643 322.66854,530.2315 323.3\
+8361,528.29533 C 324.06665,526.36841 324.76378,524.44612 325\
+.38216,522.4969 C 326.01357,520.50014 326.65767,518.50728 32\
+7.24477,516.49664 C 327.83761,514.40974 328.44098,512.32573 \
+328.96655,510.2204 C 329.50731,508.0994 330.05662,505.98047 \
+330.53336,503.84375 C 330.99109,501.68477 331.46412,499.5286\
+4 331.86906,497.35878 C 332.25347,495.1853 332.64721,493.013\
+59 332.95502,490.8274 C 333.27834,488.65205 333.61051,486.47\
+836 333.87985,484.29519 C 334.14635,482.09722 334.42445,479.\
+90067 334.63252,477.69603 C 334.85115,475.52144 335.08185,47\
+3.34776 335.26753,471.17003 C 335.44247,468.97902 335.61987,\
+466.78814 335.7857,464.59636 C 335.9374,462.43353 336.09908,\
+460.27127 336.2325,458.10726 C 336.36861,455.95054 336.50471\
+,453.79383 336.64346,451.63726 C 336.75954,449.54451 336.871\
+8,447.45154 337.00837,445.36 C 337.14218,443.32145 337.27141\
+,441.28256 337.42819,439.24561 C 337.58804,437.21771 337.738\
+7,435.18901 337.96899,433.16744 C 338.18602,431.21577 338.39\
+223,429.26293 338.69246,427.3216 C 338.96648,425.46725 339.2\
+2994,423.61113 339.55622,421.76492 C 339.87123,420.01556 340\
+.1748,418.26393 340.54188,416.52441 C 340.89585,414.85685 34\
+1.25483,413.1906 341.62509,411.52661 C 341.96789,410.02221 3\
+42.29031,408.5126 342.68484,407.02054 C 343.05884,405.68159 \
+343.40898,404.33587 343.82991,403.01022 C 344.20476,401.8347\
+4 344.56584,400.65438 344.97285,399.48945 C 345.31926,398.47\
+686 345.63507,397.45329 346.02625,396.45637 C 346.34746,395.\
+60134 346.62749,394.73113 346.98966,393.89195 C 347.27371,39\
+3.23878 347.49217,392.5551 347.8422,391.93148 C 348.06066,39\
+1.44853 348.29399,390.96969 348.49861,390.48035 L 363.56451,\
+383.66406 z \" />\012 <path\012 style=\"fill:#000000;fill-o\
+pacity:0.75;fill-rule:nonzero;stroke:none;stroke-width:1pt;s\
+troke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"\012 \
+ id=\"path2176\"\012 d=\"M 206.26655,735.18853 C 206.83\
+428,735.13263 207.3974,735.24282 207.96264,735.26997 C 208.7\
+4945,735.38766 209.54574,735.41257 210.337,735.48383 C 211.4\
+0487,735.57658 212.4743,735.65567 213.54253,735.74595 C 214.\
+85506,735.85864 216.16829,735.96544 217.48119,736.07504 C 21\
+9.09858,736.19868 220.71821,736.28835 222.33737,736.38462 C \
+224.22785,736.49412 226.11976,736.57498 228.01139,736.66113 \
+C 230.21087,736.77385 232.40938,736.90196 234.6081,737.0278 \
+C 237.0714,737.16983 239.53507,737.30307 241.99787,737.45342\
+ C 244.67148,737.62094 247.34414,737.8036 250.01759,737.9742\
+6 C 252.83872,738.14605 255.65783,738.34915 258.47629,738.55\
+974 C 261.33166,738.75949 264.18381,739.00073 267.03608,739.\
+23951 C 269.88729,739.49322 272.74001,739.72881 275.59292,73\
+9.96221 C 278.4075,740.17784 281.21951,740.42472 284.0323,74\
+0.66212 C 286.81296,740.89496 289.59172,741.14985 292.37152,\
+741.39286 C 295.06522,741.63657 297.75949,741.87322 300.4530\
+1,742.11926 C 303.13613,742.35628 305.82004,742.58296 308.50\
+255,742.82687 C 311.19656,743.07346 313.89254,743.29738 316.\
+58863,743.51984 C 319.25058,743.73995 321.91436,743.93665 32\
+4.57819,744.1323 C 327.24092,744.32619 329.90506,744.49944 3\
+32.56926,744.6716 C 335.22359,744.84166 337.8814,744.9515 34\
+0.53834,745.07164 C 343.22564,745.19118 345.91516,745.25311 \
+348.60401,745.32616 C 351.31696,745.39874 354.03014,745.4615\
+8 356.7433,745.52498 C 359.44136,745.58758 362.13954,745.644\
+81 364.83774,745.70053 C 367.57326,745.73256 370.30893,745.7\
+4706 373.04456,745.76403 C 375.74646,745.78149 378.4484,745.\
+79575 381.15034,745.81059 C 383.82002,745.82382 386.48979,74\
+5.79548 389.15942,745.77758 C 391.8574,745.75953 394.55539,7\
+45.74216 397.25338,745.7246 C 399.91742,745.69135 402.58082,\
+745.63331 405.24395,745.55765 C 407.87129,745.50812 410.4949\
+8,745.36564 413.11893,745.23177 C 415.73845,745.09297 418.35\
+327,744.88722 420.96717,744.66993 C 423.51009,744.47596 426.\
+03684,744.14066 428.56376,743.80513 C 431.09356,743.45034 43\
+3.61486,743.042 436.1332,742.61487 C 438.63924,742.2061 441.\
+1236,741.68002 443.60982,741.16769 C 446.05806,740.633 448.5\
+0068,740.07365 450.9482,739.5357 C 453.40114,739.00595 455.8\
+4213,738.42516 458.28253,737.84124 C 460.69422,737.27756 463\
+.08874,736.64498 465.48757,736.02949 C 467.86795,735.42847 4\
+70.23683,734.78314 472.6091,734.1515 C 474.96773,733.51309 4\
+77.3144,732.83138 479.66421,732.16211 C 482.02051,731.48263 \
+484.35755,730.73855 486.69943,730.01148 C 488.98277,729.2684\
+1 491.25255,728.48635 493.5211,727.69967 C 495.74694,726.936\
+98 497.95792,726.13263 500.17079,725.33341 C 502.40879,724.5\
+2209 504.6282,723.66124 506.85109,722.80971 C 509.07688,721.\
+93485 511.30134,721.05647 513.52615,720.17909 C 515.67658,71\
+9.35468 517.79651,718.45525 519.92,717.56489 C 521.99762,716\
+.70152 524.05115,715.78147 526.11272,714.88069 C 528.11898,7\
+14.02833 530.1048,713.13028 532.09212,712.2351 C 534.05859,7\
+11.36195 536.02997,710.50006 538.00085,709.63697 C 539.90815\
+,708.77989 541.81101,707.91315 543.71275,707.04382 C 545.595\
+86,706.14929 547.48078,705.25855 549.36482,704.36601 C 550.2\
+541,703.91604 551.18227,703.55198 552.09107,703.14493 L 539.\
+24675,713.31982 C 538.34968,713.73375 537.44287,714.12701 53\
+6.55531,714.56169 C 534.67153,715.45623 532.79173,716.35952 \
+530.89422,717.22476 C 528.98861,718.0878 527.08548,718.95654\
+ 525.16954,719.79655 C 523.20122,720.65889 521.23139,721.517\
+91 519.27311,722.40296 C 517.28095,723.28623 515.28912,724.1\
+7001 513.28789,725.03273 C 511.22562,725.93162 509.1655,726.\
+83555 507.08618,727.6946 C 504.95435,728.56704 502.82537,729\
+.44643 500.67673,730.27721 C 498.45095,731.151 496.22608,732\
+.02716 493.99544,732.8885 C 491.76535,733.73663 489.53765,73\
+4.59119 487.29042,735.39333 C 485.07262,736.18412 482.85606,\
+736.9783 480.62694,737.73693 C 478.35074,738.51017 476.07912\
+,739.2981 473.7808,740.00435 C 471.43416,740.72754 469.09091\
+,741.46253 466.72735,742.12936 C 464.37161,742.79347 462.017\
+78,743.46469 459.65348,744.09791 C 457.27768,744.72254 454.9\
+0331,745.3528 452.52239,745.95783 C 450.11973,746.56848 447.\
+71825,747.18361 445.30364,747.74595 C 442.85614,748.31887 44\
+0.40672,748.88318 437.95004,749.41578 C 435.50275,749.96341 \
+433.05776,750.52223 430.60157,751.02901 C 428.10469,751.5294\
+5 425.60682,752.02583 423.09172,752.42808 C 420.56033,752.83\
+41 418.02848,753.23913 415.4862,753.57247 C 412.94418,753.88\
+674 410.40025,754.18248 407.84474,754.36767 C 405.22137,754.\
+56722 402.5975,754.76255 399.96919,754.88716 C 397.33809,755\
+.01363 394.70672,755.12897 392.07288,755.18189 C 389.40449,7\
+55.24329 386.7359,755.29941 384.06678,755.31801 C 381.36875,\
+755.33501 378.67073,755.35242 375.97272,755.37191 C 373.2990\
+3,755.38931 370.62523,755.41374 367.95151,755.38874 C 365.25\
+018,755.37285 362.54886,755.35643 359.84754,755.339 C 357.10\
+766,755.31917 354.36761,755.30541 351.62803,755.2566 C 348.9\
+2819,755.2002 346.22832,755.14489 343.52869,755.07854 C 340.\
+81464,755.01316 338.1006,754.94719 335.38672,754.87448 C 332\
+.69076,754.79861 329.99438,754.7288 327.30034,754.59865 C 32\
+4.63702,754.47443 321.97325,754.355 319.3131,754.17258 C 316\
+.64644,753.99677 313.97981,753.81979 311.31469,753.62143 C 3\
+08.64815,753.42194 305.98167,753.22127 303.31717,752.99564 C\
+ 300.61972,752.76816 297.92242,752.539 295.22668,752.29197 C\
+ 292.54232,752.05594 289.85729,751.82776 287.17355,751.58489\
+ C 284.48325,751.34013 281.79236,751.10214 279.10203,750.857\
+82 C 276.32458,750.61156 273.54785,750.3567 270.76879,750.12\
+897 C 267.95809,749.88986 265.14741,749.65067 262.3352,749.4\
+2984 C 259.48229,749.192 256.62979,748.94948 253.77775,748.7\
+0157 C 250.92829,748.46986 248.07881,748.23864 245.22702,748\
+.03704 C 242.40911,747.83606 239.59086,747.63932 236.77071,7\
+47.47191 C 234.09337,747.30158 231.41656,747.12239 228.73827\
+,746.96768 C 226.269,746.82962 223.79932,746.69923 221.33017\
+,746.55878 C 219.12742,746.44441 216.92494,746.32277 214.721\
+13,746.22959 C 212.81711,746.14391 210.91276,746.06422 209.0\
+1003,745.95189 C 207.37594,745.85328 205.74134,745.76175 204\
+.10919,745.63339 C 202.78951,745.52596 201.47005,745.4154 20\
+0.14929,745.32195 C 199.07382,745.24115 197.99943,745.14624 \
+196.92282,745.0813 C 196.13428,745.02941 195.34766,744.93008\
+ 194.55682,744.92561 C 194.04688,744.97798 193.52122,744.934\
+4 193.02027,745.08341 L 206.26655,735.18853 z \" />\012 </g>\012</\
+svg>\012"
+### end
diff --git a/olpcgames/eventwrap.py b/olpcgames/eventwrap.py
new file mode 100644
index 0000000..92fbabb
--- /dev/null
+++ b/olpcgames/eventwrap.py
@@ -0,0 +1,198 @@
+"""Provides substitute for Pygame's "event" module using gtkEvent
+
+Provides methods which will be substituted into Pygame in order to
+provide the synthetic events that we will feed into the Pygame queue.
+These methods are registered by the "install" method.
+
+Extension:
+
+ last_event_time() -- returns period since the last event was produced
+ in seconds. This can be used to create "pausing" effects for games.
+"""
+import pygame
+import gtk
+import Queue
+import thread
+import logging
+
+log = logging.getLogger( 'olpcgames.eventwrap' )
+
+# This module reuses Pygame's Event, but
+# reimplements the event queue.
+from pygame.event import Event, event_name, pump as pygame_pump, get as pygame_get
+
+class Event(object):
+ """Mock pygame events"""
+ def __init__(self, type, **named):
+ self.type = type
+ self.__dict__.update( named )
+
+#print "Initializing own event.py"
+
+# Install myself on top of pygame.event
+def install():
+ """Installs this module (eventwrap) as an in-place replacement for the pygame.event module.
+
+ Use install() when you need to interact with Pygame code written
+ without reference to the olpcgames wrapper mechanisms to have the
+ code use this module's event queue.
+
+ XXX Really, use it everywhere you want to use olpcgames, as olpcgames
+ registers the handler itself, so you will always wind up with it registered when
+ you use olpcgames (the gtkEvent.Translator.hook_pygame method calls it).
+ """
+ import eventwrap,pygame
+ pygame.event = eventwrap
+ import sys
+ sys.modules["pygame.event"] = eventwrap
+
+
+# Event queue:
+g_events = Queue.Queue()
+
+# Set of blocked events as set by set
+g_blocked = set()
+g_blockedlock = thread.allocate_lock()
+g_blockAll = False
+
+def pump():
+ """Handle any window manager and other external events that aren't passed to the user. Call this periodically (once a frame) if you don't call get(), poll() or wait()."""
+ pygame_pump()
+
+def get():
+ """Get a list of all pending events. (Unlike pygame, there's no option to filter by event type; you should use set_blocked() if you don't want to see certain events.)"""
+ pump()
+ eventlist = []
+ try:
+ while True:
+ eventlist.append(g_events.get(block=False))
+ except Queue.Empty:
+ pass
+
+ pygameEvents = pygame_get()
+ if pygameEvents:
+ log.info( 'Raw Pygame events: %s', pygameEvents)
+ eventlist.extend( pygameEvents )
+ if eventlist:
+ _set_last_event_time()
+ return eventlist
+
+_LAST_EVENT_TIME = 0
+
+def _set_last_event_time( time=None ):
+ """Set this as the last event time"""
+ global _LAST_EVENT_TIME
+ if time is None:
+ time = pygame.time.get_ticks()
+ _LAST_EVENT_TIME = time
+ return time
+
+def last_event_time( ):
+ """Return the last event type for pausing operations in seconds"""
+ global _LAST_EVENT_TIME
+ return (pygame.time.get_ticks() - _LAST_EVENT_TIME)/1000.
+
+def poll():
+ """Get the next pending event if exists. Otherwise, return pygame.NOEVENT."""
+ pump()
+ try:
+ result = g_events.get(block=False)
+ _set_last_event_time()
+ return result
+ except Queue.Empty:
+ return Event(pygame.NOEVENT)
+
+
+def wait( timeout = None):
+ """Get the next pending event, wait up to timeout if none
+
+ timeout -- if present, only wait up to timeout seconds, if we
+ do not find an event before then, return None
+ """
+ pump()
+ try:
+ result = g_events.get(block=True, timeout=timeout)
+ _set_last_event_time()
+ return result
+ except Queue.Empty, err:
+ return None
+
+def peek(types=None):
+ """True if there is any pending event. (Unlike pygame, there's no option to
+ filter by event type)"""
+ return not g_events.empty()
+
+def clear():
+ """Dunno why you would do this, but throws every event out of the queue"""
+ try:
+ while True:
+ g_events.get(block=False)
+ except Queue.Empty:
+ pass
+
+def set_blocked(item):
+ g_blockedlock.acquire()
+ try:
+ # FIXME: we do not currently know how to block all event types when
+ # you set_blocked(none).
+ [g_blocked.add(x) for x in makeseq(item)]
+ finally:
+ g_blockedlock.release()
+
+def set_allowed(item):
+ g_blockedlock.acquire()
+ try:
+ if item is None:
+ # Allow all events when you set_allowed(none). Strange, eh?
+ # Pygame is a wonderful API.
+ g_blocked.clear()
+ else:
+ [g_blocked.remove(x) for x in makeseq(item)]
+ finally:
+ g_blockedlock.release()
+
+def get_blocked(*args, **kwargs):
+ g_blockedlock.acquire()
+ try:
+ blocked = frozenset(g_blocked)
+ return blocked
+ finally:
+ g_blockedlock.release()
+
+def set_grab(grabbing):
+ # We don't do this.
+ pass
+
+def get_grab():
+ # We don't do this.
+ return False
+
+def post(event):
+ #print "posting on own"
+ g_blockedlock.acquire()
+ try:
+ if event.type not in g_blocked:
+ g_events.put(event, block=False)
+ finally:
+ g_blockedlock.release()
+
+def makeseq(obj):
+ """Accept either a scalar object or a sequence, and return a sequence
+ over which we can iterate. If we were passed a sequence, return it
+ unchanged. If we were passed a scalar, return a tuple containing only
+ that scalar. This allows the caller to easily support one-or-many.
+ """
+ # Strings are the exception because you can iterate over their chars
+ # -- yet, for all the purposes I've ever cared about, I want to treat
+ # a string as a scalar.
+ if isinstance(obj, basestring):
+ return (obj,)
+ try:
+ # Except as noted above, if you can get an iter() from an object,
+ # it's a collection.
+ iter(obj)
+ return obj
+ except TypeError:
+ # obj is a scalar. Wrap it in a tuple so we can iterate over the
+ # one item.
+ return (obj,)
diff --git a/olpcgames/gtkEvent.py b/olpcgames/gtkEvent.py
new file mode 100644
index 0000000..ce4f9eb
--- /dev/null
+++ b/olpcgames/gtkEvent.py
@@ -0,0 +1,270 @@
+"""gtkEvent.py: translate GTK events into Pygame events."""
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gobject
+import pygame
+from olpcgames import eventwrap
+import logging
+log = logging.getLogger( 'olpcgames.gtkevent' )
+#log.setLevel( logging.DEBUG )
+
+class _MockEvent(object):
+ """Used to inject key-repeat events on the gtk side."""
+ def __init__(self, keyval):
+ self.keyval = keyval
+
+class Translator(object):
+ """Utility class to translate GTK events into Pygame events
+
+ The Translator object interprets incoming GTK events and generates
+ Pygame events in the eventwrap module's queue as a result.
+ It also handles generating Pygame style key-repeat events
+ by synthesizing them via a GTK timer.
+ """
+ key_trans = {
+ 'Alt_L': pygame.K_LALT,
+ 'Alt_R': pygame.K_RALT,
+ 'Control_L': pygame.K_LCTRL,
+ 'Control_R': pygame.K_RCTRL,
+ 'Shift_L': pygame.K_LSHIFT,
+ 'Shift_R': pygame.K_RSHIFT,
+ 'Super_L': pygame.K_LSUPER,
+ 'Super_R': pygame.K_RSUPER,
+ 'KP_Page_Up' : pygame.K_KP9,
+ 'KP_Page_Down' : pygame.K_KP3,
+ 'KP_End' : pygame.K_KP1,
+ 'KP_Home' : pygame.K_KP7,
+ 'KP_Up' : pygame.K_KP8,
+ 'KP_Down' : pygame.K_KP2,
+ 'KP_Left' : pygame.K_KP4,
+ 'KP_Right' : pygame.K_KP6,
+
+ }
+
+ mod_map = {
+ pygame.K_LALT: pygame.KMOD_LALT,
+ pygame.K_RALT: pygame.KMOD_RALT,
+ pygame.K_LCTRL: pygame.KMOD_LCTRL,
+ pygame.K_RCTRL: pygame.KMOD_RCTRL,
+ pygame.K_LSHIFT: pygame.KMOD_LSHIFT,
+ pygame.K_RSHIFT: pygame.KMOD_RSHIFT,
+ }
+
+ def __init__(self, mainwindow, mouselistener=None):
+ """Initialise the Translator with the windows to which to listen"""
+ # _inner_evb is Mouselistener
+ self._mainwindow = mainwindow
+ if mouselistener is None:
+ mouselistener = mainwindow
+
+ self._inner_evb = mouselistener
+
+ # Need to set our X event masks so we see mouse motion and stuff --
+ mainwindow.set_events(
+ gtk.gdk.KEY_PRESS_MASK | \
+ gtk.gdk.KEY_RELEASE_MASK \
+ )
+
+ self._inner_evb.set_events(
+ gtk.gdk.POINTER_MOTION_MASK | \
+ gtk.gdk.POINTER_MOTION_HINT_MASK | \
+ gtk.gdk.BUTTON_MOTION_MASK | \
+ gtk.gdk.BUTTON_PRESS_MASK | \
+ gtk.gdk.BUTTON_RELEASE_MASK
+ )
+
+ # Callback functions to link the event systems
+ mainwindow.connect('unrealize', self._quit)
+ mainwindow.connect('key_press_event', self._keydown)
+ mainwindow.connect('key_release_event', self._keyup)
+ self._inner_evb.connect('button_press_event', self._mousedown)
+ self._inner_evb.connect('button_release_event', self._mouseup)
+ self._inner_evb.connect('motion-notify-event', self._mousemove)
+
+ # You might need to do this
+ mainwindow.set_flags(gtk.CAN_FOCUS)
+ self._inner_evb.set_flags(gtk.CAN_FOCUS)
+
+ # Internal data
+ self.__stopped = False
+ self.__keystate = [0] * 323
+ self.__button_state = [0,0,0]
+ self.__mouse_pos = (0,0)
+ self.__repeat = (None, None)
+ self.__held = set()
+ self.__held_time_left = {}
+ self.__held_last_time = {}
+ self.__tick_id = None
+
+ #print "translator initialized"
+ mainwindow.connect( 'expose-event', self.do_expose_event )
+ def do_expose_event(self, event, widget):
+ """Handle exposure event (trigger redraw by gst)"""
+ log.info( 'Expose event: %s', event )
+ from olpcgames import eventwrap
+ eventwrap.post( eventwrap.Event( eventwrap.pygame.VIDEOEXPOSE ))
+ return True
+ def hook_pygame(self):
+ """Hook the various Pygame features so that we implement the event APIs"""
+ # Pygame should be initialized. Hijack their key and mouse methods
+ pygame.key.get_pressed = self._get_pressed
+ pygame.key.set_repeat = self._set_repeat
+ pygame.mouse.get_pressed = self._get_mouse_pressed
+ pygame.mouse.get_pos = self._get_mouse_pos
+ import eventwrap
+ eventwrap.install()
+
+ def _quit(self, data=None):
+ self.__stopped = True
+ eventwrap.post(eventwrap.Event(pygame.QUIT))
+
+ def _keydown(self, widget, event):
+ key = event.keyval
+ log.debug( 'key down: %s', key )
+ if key in self.__held:
+ return True
+ else:
+ if self.__repeat[0] is not None:
+ self.__held_last_time[key] = pygame.time.get_ticks()
+ self.__held_time_left[key] = self.__repeat[0]
+ self.__held.add(key)
+
+ return self._keyevent(widget, event, pygame.KEYDOWN)
+
+ def _keyup(self, widget, event):
+ key = event.keyval
+ if self.__repeat[0] is not None:
+ if key in self.__held:
+ # This is possibly false if set_repeat() is called with a key held
+ del self.__held_time_left[key]
+ del self.__held_last_time[key]
+ self.__held.discard(key)
+
+ return self._keyevent(widget, event, pygame.KEYUP)
+
+ def _keymods(self):
+ """Extract the keymods as they stand currently."""
+ mod = 0
+ for key_val, mod_val in self.mod_map.iteritems():
+ mod |= self.__keystate[key_val] and mod_val
+ return mod
+
+
+ def _keyevent(self, widget, event, type):
+ key = gtk.gdk.keyval_name(event.keyval)
+ if key is None:
+ # No idea what this key is.
+ return False
+
+ keycode = None
+ if key in self.key_trans:
+ keycode = self.key_trans[key]
+ elif hasattr(pygame, 'K_'+key.upper()):
+ keycode = getattr(pygame, 'K_'+key.upper())
+ elif hasattr(pygame, 'K_'+key.lower()):
+ keycode = getattr(pygame, 'K_'+key.lower())
+ else:
+ print 'Key %s unrecognized'%key
+
+ if keycode is not None:
+ if type == pygame.KEYDOWN:
+ mod = self._keymods()
+ self.__keystate[keycode] = type == pygame.KEYDOWN
+ if type == pygame.KEYUP:
+ mod = self._keymods()
+ ukey = unichr(gtk.gdk.keyval_to_unicode(event.keyval))
+ if ukey == '\000':
+ ukey = ''
+ evt = eventwrap.Event(type, key=keycode, unicode=ukey, mod=mod)
+ assert evt.key, evt
+ self._post(evt)
+ return True
+
+ def _get_pressed(self):
+ """Retrieve map/array of which keys are currently depressed (held down)"""
+ return self.__keystate
+
+ def _get_mouse_pressed(self):
+ """Return three-element array of which mouse-buttons are currently depressed (held down)"""
+ return self.__button_state
+
+ def _mousedown(self, widget, event):
+ self.__button_state[event.button-1] = 1
+ return self._mouseevent(widget, event, pygame.MOUSEBUTTONDOWN)
+
+ def _mouseup(self, widget, event):
+ self.__button_state[event.button-1] = 0
+ return self._mouseevent(widget, event, pygame.MOUSEBUTTONUP)
+
+ def _mouseevent(self, widget, event, type):
+
+ evt = eventwrap.Event(type,
+ button=event.button,
+ pos=(event.x, event.y))
+ self._post(evt)
+ return True
+
+ def _mousemove(self, widget, event):
+ # From http://www.learningpython.com/2006/07/25/writing-a-custom-widget-using-pygtk/
+ # if this is a hint, then let's get all the necessary
+ # information, if not it's all we need.
+ if event.is_hint:
+ x, y, state = event.window.get_pointer()
+ else:
+ x = event.x
+ y = event.y
+ state = event.state
+
+ rel = (x - self.__mouse_pos[0],
+ y - self.__mouse_pos[1])
+ self.__mouse_pos = (x, y)
+
+ self.__button_state = [
+ state & gtk.gdk.BUTTON1_MASK and 1 or 0,
+ state & gtk.gdk.BUTTON2_MASK and 1 or 0,
+ state & gtk.gdk.BUTTON3_MASK and 1 or 0,
+ ]
+
+ evt = eventwrap.Event(pygame.MOUSEMOTION,
+ pos=self.__mouse_pos,
+ rel=rel,
+ buttons=self.__button_state)
+ self._post(evt)
+ return True
+
+ def _tick(self):
+ """Generate synthetic events for held-down keys"""
+ cur_time = pygame.time.get_ticks()
+ for key in self.__held:
+ delta = cur_time - self.__held_last_time[key]
+ self.__held_last_time[key] = cur_time
+
+ self.__held_time_left[key] -= delta
+ if self.__held_time_left[key] <= 0:
+ self.__held_time_left[key] = self.__repeat[1]
+ self._keyevent(None, _MockEvent(key), pygame.KEYDOWN)
+
+ return True
+
+ def _set_repeat(self, delay=None, interval=None):
+ """Set the key-repetition frequency for held-down keys"""
+ if delay is not None and self.__repeat[0] is None:
+ self.__tick_id = gobject.timeout_add(10, self._tick)
+ elif delay is None and self.__repeat[0] is not None:
+ gobject.source_remove(self.__tick_id)
+ self.__repeat = (delay, interval)
+
+ def _get_mouse_pos(self):
+ """Retrieve the current mouse position as a two-tuple of integers"""
+ return self.__mouse_pos
+
+ def _post(self, evt):
+ try:
+ eventwrap.post(evt)
+ except pygame.error, e:
+ if str(e) == 'Event queue full':
+ print "Event queue full!"
+ pass
+ else:
+ raise e
diff --git a/olpcgames/mesh.py b/olpcgames/mesh.py
new file mode 100644
index 0000000..254089f
--- /dev/null
+++ b/olpcgames/mesh.py
@@ -0,0 +1,398 @@
+'''mesh.py: utilities for wrapping the mesh and making it accessible to Pygame'''
+import logging
+log = logging.getLogger( 'olpcgames.mesh' )
+#log.setLevel( logging.DEBUG )
+try:
+ from sugar.presence.tubeconn import TubeConnection
+except ImportError, err:
+ TubeConnection = object
+try:
+ from dbus.gobject_service import ExportedGObject
+except ImportError, err:
+ ExportedGObject = object
+from dbus.service import method, signal
+
+try:
+ import telepathy
+except ImportError, err:
+ telepathy = None
+
+class OfflineError( Exception ):
+ """Raised when we cannot complete an operation due to being offline"""
+
+DBUS_IFACE="org.laptop.games.pygame"
+DBUS_PATH="/org/laptop/games/pygame"
+DBUS_SERVICE = None
+
+
+### NEW PYGAME EVENTS ###
+
+'''The tube connection was started. (i.e., the user clicked Share or started
+the activity from the Neighborhood screen).
+Event properties:
+ id: a unique identifier for this connection. (shouldn't be needed for anything)'''
+CONNECT = 9912
+
+'''A participant joined the activity. This will trigger for the local user
+as well as any arriving remote users.
+Event properties:
+ handle: the arriving user's handle.'''
+PARTICIPANT_ADD = 9913
+
+'''A participant quit the activity.
+Event properties:
+ handle: the departing user's handle.'''
+PARTICIPANT_REMOVE = 9914
+
+'''A message was sent to you.
+Event properties:
+ content: the content of the message (a string)
+ handle: the handle of the sending user.'''
+MESSAGE_UNI = 9915
+
+'''A message was sent to everyone.
+Event properties:
+ content: the content of the message (a string)
+ handle: the handle of the sending user.'''
+MESSAGE_MULTI = 9916
+
+
+# Private objects for useful purposes!
+pygametubes = []
+text_chan, tubes_chan = (None, None)
+conn = None
+initiating = False
+joining = False
+
+connect_callback = None
+
+def is_initiating():
+ '''A version of is_initiator that's a bit less goofy, and can be used
+ before the Tube comes up.'''
+ global initiating
+ return initiating
+
+def is_joining():
+ '''Returns True if the activity was started up by means of the
+ Neighbourhood mesh view.'''
+ global joining
+ return joining
+
+def set_connect_callback(cb):
+ '''Just the same as the Pygame event loop can listen for CONNECT,
+ this is just an ugly callback that the glib side can use to be aware
+ of when the Tube is ready.'''
+ global connect_callback
+ connect_callback = cb
+
+def activity_shared(activity):
+ '''Called when the user clicks Share.'''
+
+ global initiating
+ initiating = True
+
+ _setup(activity)
+
+
+ log.debug('This is my activity: making a tube...')
+ channel = tubes_chan[telepathy.CHANNEL_TYPE_TUBES]
+ if hasattr( channel, 'OfferDBusTube' ):
+ id = channel.OfferDBusTube(
+ DBUS_SERVICE, {})
+ else:
+ id = channel.OfferTube(
+ telepathy.TUBE_TYPE_DBUS, DBUS_SERVICE, {})
+
+ global connect_callback
+ if connect_callback is not None:
+ connect_callback()
+
+def activity_joined(activity):
+ '''Called at the startup of our Activity, when the user started it via Neighborhood intending to join an existing activity.'''
+
+ # Find out who's already in the shared activity:
+ log.debug('Joined an existing shared activity')
+
+ for buddy in activity._shared_activity.get_joined_buddies():
+ log.debug('Buddy %s is already in the activity' % buddy.props.nick)
+
+
+ global initiating
+ global joining
+ initiating = False
+ joining = True
+
+
+ _setup(activity)
+
+ tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
+ reply_handler=_list_tubes_reply_cb,
+ error_handler=_list_tubes_error_cb)
+
+ global connect_callback
+ if connect_callback is not None:
+ connect_callback()
+
+def _getConn():
+ log.info( '_getConn' )
+ pservice = _get_presence_service()
+ name, path = pservice.get_preferred_connection()
+ global conn
+ conn = telepathy.client.Connection(name, path)
+ return conn
+
+
+
+def _setup(activity):
+ '''Determines text and tube channels for the current Activity. If no tube
+channel present, creates one. Updates text_chan and tubes_chan.
+
+setup(sugar.activity.Activity, telepathy.client.Connection)'''
+ global text_chan, tubes_chan, DBUS_SERVICE
+ if not DBUS_SERVICE:
+ DBUS_SERVICE = activity.get_bundle_id()
+ if not activity.get_shared():
+ log.error('Failed to share or join activity')
+ raise "Failure"
+
+ bus_name, conn_path, channel_paths = activity._shared_activity.get_channels()
+ _getConn()
+
+ # Work out what our room is called and whether we have Tubes already
+ room = None
+ tubes_chan = None
+ text_chan = None
+ for channel_path in channel_paths:
+ channel = telepathy.client.Channel(bus_name, channel_path)
+ htype, handle = channel.GetHandle()
+ if htype == telepathy.HANDLE_TYPE_ROOM:
+ log.debug('Found our room: it has handle#%d "%s"',
+ handle, conn.InspectHandles(htype, [handle])[0])
+ room = handle
+ ctype = channel.GetChannelType()
+ if ctype == telepathy.CHANNEL_TYPE_TUBES:
+ log.debug('Found our Tubes channel at %s', channel_path)
+ tubes_chan = channel
+ elif ctype == telepathy.CHANNEL_TYPE_TEXT:
+ log.debug('Found our Text channel at %s', channel_path)
+ text_chan = channel
+
+ if room is None:
+ log.error("Presence service didn't create a room")
+ raise "Failure"
+ if text_chan is None:
+ log.error("Presence service didn't create a text channel")
+ raise "Failure"
+
+ # Make sure we have a Tubes channel - PS doesn't yet provide one
+ if tubes_chan is None:
+ log.debug("Didn't find our Tubes channel, requesting one...")
+ tubes_chan = conn.request_channel(telepathy.CHANNEL_TYPE_TUBES,
+ telepathy.HANDLE_TYPE_ROOM, room, True)
+
+ tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube',
+ new_tube_cb)
+
+ return (text_chan, tubes_chan)
+
+def new_tube_cb(id, initiator, type, service, params, state):
+ log.debug("New_tube_cb called: %s %s %s" % (id, initiator, type))
+ if (type == telepathy.TUBE_TYPE_DBUS and service == DBUS_SERVICE):
+ if state == telepathy.TUBE_STATE_LOCAL_PENDING:
+ channel = tubes_chan[telepathy.CHANNEL_TYPE_TUBES]
+ if hasattr( channel, 'AcceptDBusTube' ):
+ channel.AcceptDBusTube( id )
+ else:
+ channel.AcceptTube(id)
+
+ tube_conn = TubeConnection(conn,
+ tubes_chan[telepathy.CHANNEL_TYPE_TUBES],
+ id, group_iface=text_chan[telepathy.CHANNEL_INTERFACE_GROUP])
+
+ global pygametubes, initiating
+ pygametubes.append(PygameTube(tube_conn, initiating, len(pygametubes)))
+
+
+def _list_tubes_reply_cb(tubes):
+ for tube_info in tubes:
+ new_tube_cb(*tube_info)
+
+def _list_tubes_error_cb(e):
+ log.error('ListTubes() failed: %s', e)
+
+
+
+def get_buddy(dbus_handle):
+ """Get a Buddy from a handle."""
+ log.debug('Trying to find owner of handle %s...', dbus_handle)
+ cs_handle = instance().tube.bus_name_to_handle[dbus_handle]
+ log.debug('Trying to find my handle in %s...', cs_handle)
+ group = text_chan[telepathy.CHANNEL_INTERFACE_GROUP]
+ log.debug( 'Calling GetSelfHandle' )
+ my_csh = group.GetSelfHandle()
+ log.debug('My handle in that group is %s', my_csh)
+ if my_csh == cs_handle:
+ handle = conn.GetSelfHandle()
+ log.debug('CS handle %s belongs to me, %s', cs_handle, handle)
+ elif group.GetGroupFlags() & telepathy.CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
+ handle = group.GetHandleOwners([cs_handle])[0]
+ log.debug('CS handle %s belongs to %s', cs_handle, handle)
+ else:
+ handle = cs_handle
+ log.debug('non-CS handle %s belongs to itself', handle)
+
+ # XXX: we're assuming that we have Buddy objects for all contacts -
+ # this might break when the server becomes scalable.
+ pservice = _get_presence_service()
+ name, path = pservice.get_preferred_connection()
+ return pservice.get_buddy_by_telepathy_handle(name, path, handle)
+
+def _get_presence_service( ):
+ """Attempt to retrieve the presence service (check for offline condition)
+
+ The presence service, when offline, has no preferred connection type,
+ so we check that before returning the object...
+ """
+ import sugar.presence.presenceservice
+ pservice = sugar.presence.presenceservice.get_instance()
+ try:
+ name, path = pservice.get_preferred_connection()
+ except (TypeError,ValueError), err:
+ log.warn('Working in offline mode, cannot retrieve buddy information for %s: %s', handle, err )
+ raise OfflineError( """Unable to retrieve buddy information, currently offline""" )
+ else:
+ return pservice
+
+def instance(idx=0):
+ return pygametubes[idx]
+
+import eventwrap,pygame.event as PEvent
+
+class PygameTube(ExportedGObject):
+ '''The object whose instance is shared across D-bus
+
+ Call instance() to get the instance of this object for your activity service.
+ Its 'tube' property contains the underlying D-bus Connection.
+ '''
+ def __init__(self, tube, is_initiator, tube_id):
+ super(PygameTube, self).__init__(tube, DBUS_PATH)
+ log.info( 'PygameTube init' )
+ self.tube = tube
+ self.is_initiator = is_initiator
+ self.entered = False
+ self.ordered_bus_names = []
+ eventwrap.post(PEvent.Event(CONNECT, id=tube_id))
+
+ if not self.is_initiator:
+ self.tube.add_signal_receiver(self.new_participant_cb, 'NewParticipants', DBUS_IFACE, path=DBUS_PATH)
+ self.tube.watch_participants(self.participant_change_cb)
+ self.tube.add_signal_receiver(self.broadcast_cb, 'Broadcast', DBUS_IFACE, path=DBUS_PATH, sender_keyword='sender')
+
+
+ def participant_change_cb(self, added, removed):
+ log.debug( 'participant_change_cb: %s %s', added, removed )
+ def nick(buddy):
+ if buddy is not None:
+ return buddy.props.nick
+ else:
+ return 'Unknown'
+
+ for handle, bus_name in added:
+ dbus_handle = self.tube.participants[handle]
+ self.ordered_bus_names.append(dbus_handle)
+ eventwrap.post(PEvent.Event(PARTICIPANT_ADD, handle=dbus_handle))
+
+ for handle in removed:
+ dbus_handle = self.tube.participants[handle]
+ self.ordered_bus_names.remove(dbus_handle)
+ eventwrap.post(PEvent.Event(PARTICIPANT_REMOVE, handle=dbus_handle))
+
+ if self.is_initiator:
+ if not self.entered:
+ # Initiator will broadcast a new ordered_bus_names each time
+ # a participant joins.
+ self.ordered_bus_names = [self.tube.get_unique_name()]
+ self.NewParticipants(self.ordered_bus_names)
+
+ self.entered = True
+
+ @signal(dbus_interface=DBUS_IFACE, signature='as')
+ def NewParticipants(self, ordered_bus_names):
+ '''This is the NewParticipants signal, sent when the authoritative list of ordered_bus_names changes.'''
+ log.debug("sending NewParticipants: %s" % ordered_bus_names)
+ pass
+
+ @signal(dbus_interface=DBUS_IFACE, signature='s')
+ def Broadcast(self, content):
+ '''This is the Broadcast signal; it sends a message to all other activity participants.'''
+ pass
+
+ @method(dbus_interface=DBUS_IFACE, in_signature='s', out_signature='', sender_keyword='sender')
+ def Tell(self, content, sender=None):
+ '''This is the targeted-message interface; called when a message is received that was sent directly to me.'''
+ eventwrap.post(PEvent.Event(MESSAGE_UNI, handle=sender, content=content))
+
+ def broadcast_cb(self, content, sender=None):
+ '''This is the Broadcast callback, fired when someone sends a Broadcast signal along the bus.'''
+ eventwrap.post(PEvent.Event(MESSAGE_MULTI, handle=sender, content=content))
+
+ def new_participant_cb(self, new_bus_names):
+ '''This is the NewParticipants callback, fired when someone joins or leaves.'''
+ log.debug("new participant. new bus names %s, old %s" % (new_bus_names, self.ordered_bus_names))
+ if self.ordered_bus_names != new_bus_names:
+ log.warn("ordered bus names out of sync with server, resyncing")
+ self.ordered_bus_names = new_bus_names
+
+def send_to(handle, content=""):
+ '''Sends the given message to the given buddy identified by handle.'''
+ log.debug( 'send_to: %s %s', handle, content )
+ remote_proxy = dbus_get_object(handle, DBUS_PATH)
+ remote_proxy.Tell(content, reply_handler=dbus_msg, error_handler=dbus_err)
+
+def dbus_msg():
+ log.debug("async reply to send_to")
+def dbus_err(e):
+ log.error("async error: %s" % e)
+
+def broadcast(content=""):
+ '''Sends the given message to all participants.'''
+ log.debug( 'Broadcast: %s', content )
+ instance().Broadcast(content)
+
+def my_handle():
+ '''Returns the handle of this user
+
+ Note, you can get a DBusException from this if you have
+ not yet got a unique ID assigned by the bus. You may need
+ to delay calling until you are sure you are connected.
+ '''
+ log.debug( 'my handle' )
+ return instance().tube.get_unique_name()
+
+def is_initiator():
+ '''Returns the handle of this user.'''
+ log.debug( 'is initiator' )
+ return instance().is_initiator
+
+def get_participants():
+ '''Returns the list of active participants, in order of arrival.
+ List is maintained by the activity creator; if that person leaves it may not stay in sync.'''
+ log.debug( 'get_participants' )
+ try:
+ return instance().ordered_bus_names[:]
+ except IndexError, err:
+ return [] # no participants yet, as we don't yet have a connection
+
+def dbus_get_object(handle, path):
+ '''Get a D-bus object from another participant.
+
+ This is how you can communicate with other participants using
+ arbitrary D-bus objects without having to manage the participants
+ yourself.
+
+ Simply define a D-bus class with an interface and path that you
+ choose; when you want a reference to the corresponding remote
+ object on a participant, call this method.
+ '''
+ log.debug( 'dbus_get_object: %s %s', handle, path )
+ return instance().tube.get_object(handle, path)
diff --git a/olpcgames/pangofont.py b/olpcgames/pangofont.py
new file mode 100644
index 0000000..81a2d7c
--- /dev/null
+++ b/olpcgames/pangofont.py
@@ -0,0 +1,279 @@
+"""Implement Pygame's font interface using Pango for international support
+
+Depends on:
+
+ pygtk (to get the pango context)
+ pycairo (for the pango rendering context)
+ python-pango (obviously)
+ pygame (obviously)
+"""
+import pango
+import logging
+import cairo
+import pangocairo
+import pygame.rect, pygame.image
+import gtk
+import struct
+from pygame import surface
+from olpcgames import _cairoimage
+
+log = logging.getLogger( 'olpcgames.pangofont' )
+#log.setLevel( logging.DEBUG )
+
+# Install myself on top of pygame.font
+def install():
+ """Replace Pygame's font module with this module"""
+ log.info( 'installing' )
+ from olpcgames import pangofont
+ import pygame
+ pygame.font = pangofont
+ import sys
+ sys.modules["pygame.font"] = pangofont
+
+class PangoFont(object):
+ """Base class for a pygame.font.Font-like object drawn by Pango
+
+ Attributes of note:
+
+ fd -- instances Pango FontDescription object
+ WEIGHT_* -- parameters for use with set_weight
+ STYLE_* -- parameters for use with set_style
+
+ """
+ WEIGHT_BOLD = pango.WEIGHT_BOLD
+ WEIGHT_HEAVY = pango.WEIGHT_HEAVY
+ WEIGHT_LIGHT = pango.WEIGHT_LIGHT
+ WEIGHT_NORMAL = pango.WEIGHT_NORMAL
+ WEIGHT_SEMIBOLD = pango.WEIGHT_SEMIBOLD
+ WEIGHT_ULTRABOLD = pango.WEIGHT_ULTRABOLD
+ WEIGHT_ULTRALIGHT = pango.WEIGHT_ULTRALIGHT
+ STYLE_NORMAL = pango.STYLE_NORMAL
+ STYLE_ITALIC = pango.STYLE_ITALIC
+ STYLE_OBLIQUE = pango.STYLE_OBLIQUE
+ def __init__(self, family=None, size=None, bold=False, italic=False, underline=False, fd=None):
+ """If you know what pango.FontDescription (fd) you want, pass it in as
+ 'fd'. Otherwise, specify any number of family, size, bold, or italic,
+ and we will try to match something up for you."""
+
+ # Always set the FontDescription (FIXME - only set it if the user wants
+ # to change something?)
+ if fd is None:
+ fd = pango.FontDescription()
+ if family is not None:
+ fd.set_family(family)
+ if size is not None:
+ fd.set_size(size*1000)
+ self.fd = fd
+ self.set_bold( bold )
+ self.set_italic( italic )
+ self.set_underline( underline )
+
+ def render(self, text, antialias=True, color=(255,255,255), background=None ):
+ """Render the font onto a new Surface and return it.
+ We ignore 'antialias' and use system settings.
+
+ text -- (unicode) string with the text to render
+ antialias -- attempt to antialias the text or not
+ color -- three or four-tuple of 0-255 values specifying rendering
+ colour for the text
+ background -- three or four-tuple of 0-255 values specifying rendering
+ colour for the background, or None for trasparent background
+
+ returns a pygame image instance
+ """
+ log.info( 'render: %r, antialias = %s, color=%s, background=%s', text, antialias, color, background )
+
+ # create layout
+ layout = pango.Layout(gtk.gdk.pango_context_get())
+ layout.set_font_description(self.fd)
+ if self.underline:
+ attrs = layout.get_attributes()
+ if not attrs:
+ attrs = pango.AttrList()
+ attrs.insert(pango.AttrUnderline(pango.UNDERLINE_SINGLE, 0, 32767))
+ layout.set_attributes( attrs )
+ layout.set_text(text)
+
+ # determine pixel size
+ (logical, ink) = layout.get_pixel_extents()
+ ink = pygame.rect.Rect(ink)
+
+ # Create a new Cairo ImageSurface
+ csrf,cctx = _cairoimage.newContext( ink.w, ink.h )
+ cctx = pangocairo.CairoContext(cctx)
+
+ # Mangle the colors on little-endian machines. The reason for this
+ # is that Cairo writes native-endian 32-bit ARGB values whereas
+ # Pygame expects endian-independent values in whatever format. So we
+ # tell our users not to expect transparency here (avoiding the A issue)
+ # and we swizzle all the colors around.
+
+ # render onto it
+ if background is not None:
+ background = _cairoimage.mangle_color( background )
+ cctx.set_source_rgba(*background)
+ cctx.paint()
+
+ log.debug( 'incoming color: %s', color )
+ color = _cairoimage.mangle_color( color )
+ log.debug( ' translated color: %s', color )
+
+ cctx.new_path()
+ cctx.layout_path(layout)
+ cctx.set_source_rgba(*color)
+ cctx.fill()
+
+ # Create and return a new Pygame Image derived from the Cairo Surface
+ return _cairoimage.asImage( csrf )
+
+ def set_bold( self, bold=True):
+ """Set our font description's weight to "bold" or "normal"
+
+ bold -- boolean, whether to set the value to "bold" weight or not
+ """
+ if bold:
+ self.set_weight( self.WEIGHT_BOLD )
+ else:
+ self.set_weight( self.WEIGHT_NORMAL )
+ def set_weight( self, weight ):
+ """Explicitly set our pango-style weight value"""
+ self.fd.set_weight( weight )
+ return self.get_weight()
+ def get_weight( self ):
+ """Explicitly get our pango-style weight value"""
+ return self.fd.get_weight()
+ def get_bold( self ):
+ """Return whether our font's weight is bold (or above)"""
+ return self.fd.get_weight() >= pango.WEIGHT_BOLD
+
+ def set_italic( self, italic=True ):
+ """Set our "italic" value (style)"""
+ if italic:
+ self.set_style( self.STYLE_ITALIC )
+ else:
+ self.set_style( self.STYLE_NORMAL )
+ def set_style( self, style ):
+ """Set our font description's pango-style"""
+ self.fd.set_style( style )
+ return self.fd.get_style()
+ def get_style( self ):
+ """Get our font description's pango-style"""
+ return self.fd.get_style()
+ def get_italic( self ):
+ """Return whether we are currently italicised"""
+ return self.fd.get_style() == self.STYLE_ITALIC # what about oblique?
+
+ def set_underline( self, underline=True ):
+ """Set our current underlining properly"""
+ self.underline = underline
+ def get_underline( self ):
+ return self.underline
+
+class SysFont(PangoFont):
+ """Construct a PangoFont from a font description (name), size in pixels,
+ bold, and italic designation. Similar to SysFont from Pygame."""
+ def __init__(self, name, size, bold=False, italic=False):
+ fd = pango.FontDescription(name)
+ fd.set_absolute_size(size*pango.SCALE)
+ if bold:
+ fd.set_weight(pango.WEIGHT_BOLD)
+ if italic:
+ fd.set_style(pango.STYLE_OBLIQUE)
+ super(SysFont, self).__init__(fd=fd)
+
+# originally defined a new class, no reason for that...
+NotImplemented = NotImplementedError
+
+class Font(PangoFont):
+ """Abstract class, do not use"""
+ def __init__(self, *args, **kwargs):
+ raise NotImplementedError("PangoFont doesn't support Font directly, use SysFont or .fontByDesc")
+
+def match_font(name,bold=False,italic=False):
+ """Stub, does not work, use fontByDesc instead"""
+ raise NotImplementedError("PangoFont doesn't support match_font directly, use SysFont or .fontByDesc")
+
+def fontByDesc(desc="",bold=False,italic=False):
+ """Constructs a FontDescription from the given string representation.
+The format of the string representation is:
+
+ "[FAMILY-LIST] [STYLE-OPTIONS] [SIZE]"
+
+where FAMILY-LIST is a comma separated list of families optionally terminated by a comma, STYLE_OPTIONS is a whitespace separated list of words where each WORD describes one of style, variant, weight, or stretch, and SIZE is an decimal number (size in points). For example the following are all valid string representations:
+
+ "sans bold 12"
+ "serif,monospace bold italic condensed 16"
+ "normal 10"
+
+The commonly available font families are: Normal, Sans, Serif and Monospace. The available styles are:
+Normal the font is upright.
+Oblique the font is slanted, but in a roman style.
+Italic the font is slanted in an italic style.
+
+The available weights are:
+Ultra-Light the ultralight weight (= 200)
+Light the light weight (=300)
+Normal the default weight (= 400)
+Bold the bold weight (= 700)
+Ultra-Bold the ultra-bold weight (= 800)
+Heavy the heavy weight (= 900)
+
+The available variants are:
+Normal
+Small-Caps
+
+The available stretch styles are:
+Ultra-Condensed the smallest width
+Extra-Condensed
+Condensed
+Semi-Condensed
+Normal the normal width
+Semi-Expanded
+Expanded
+Extra-Expanded
+Ultra-Expanded the widest width
+ """
+ fd = pango.FontDescription(name)
+ if bold:
+ fd.set_weight(pango.WEIGHT_BOLD)
+ if italic:
+ fd.set_style(pango.STYLE_OBLIQUE)
+ return PangoFont(fd=fd)
+
+def get_init():
+ """Return boolean indicating whether we are initialised
+
+ Always returns True
+ """
+ return True
+
+def init():
+ """Initialise the module (null operation)"""
+ pass
+
+def quit():
+ """De-initialise the module (null operation)"""
+ pass
+
+def get_default_font():
+ """Return default-font specification to be passed to e.g. fontByDesc"""
+ return "sans"
+
+def get_fonts():
+ """Return the set of all fonts available (currently just 3 generic types)"""
+ return ["sans","serif","monospace"]
+
+
+def stdcolor(color):
+ """Produce a 4-element 0.0-1.0 color value from input"""
+ def fixlen(color):
+ if len(color) == 3:
+ return tuple(color) + (255,)
+ elif len(color) == 4:
+ return color
+ else:
+ raise TypeError("What sort of color is this: %s" % (color,))
+ return [_fixColorBase(x) for x in fixlen(color)]
+def _fixColorBase( v ):
+ """Return a properly clamped colour in floating-point space"""
+ return max((0,min((v,255.0))))/255.0
diff --git a/olpcgames/pausescreen.py b/olpcgames/pausescreen.py
new file mode 100644
index 0000000..1c1d6c3
--- /dev/null
+++ b/olpcgames/pausescreen.py
@@ -0,0 +1,89 @@
+"""Display a "paused" version of the currently-displayed screen
+
+This code is largely cribbed from the Pippy activity's display code,
+but we try to be a little more generally usable than they are, as
+we have more involved activities using the code.
+
+We use svgsprite to render a graphic which is stored in the
+olpcgames data directory over a dimmed version of the current
+screen contents.
+"""
+import logging
+log = logging.getLogger( 'olpcgames.pausescreen' )
+import pygame
+from pygame import sprite
+
+def get_events( sleep_timeout = 10, pause=None, **args ):
+ """Retrieve the set of pending events or sleep
+
+ sleep_timeout -- dormant period before we invoke pause_screen
+ pause -- callable to produce visual notification of pausing, normally
+ by taking the current screen and modifying it in some way. Defaults
+ to pauseScreen in this module. If you return nothing from this
+ function then no restoration or display-flipping will occur
+ *args -- if present, passed to pause to configuration operation (e.g.
+ to specify a different overlaySVG file)
+
+ returns set of pending events (potentially empty)
+ """
+ if not pause:
+ pause = pauseScreen
+ events = pygame.event.get( )
+ if not events:
+ log.info( 'No events in queue' )
+ old_screen = None
+ if hasattr(pygame.event, 'last_event_time') and pygame.event.last_event_time() > sleep_timeout:
+ # we've been waiting long enough, go to sleep visually
+ log.warn( 'Pausing activity after %s with function %s', sleep_timeout, pause )
+ old_screen = pause( )
+ if old_screen:
+ pygame.display.flip()
+ # now we wait until there *are* some events (efficiently)
+ # and retrieve any extra events that are waiting...
+ events = [ pygame.event.wait() ] + pygame.event.get()
+ log.warn( 'Activity restarted')
+ if old_screen:
+ restoreScreen( old_screen )
+ else:
+ log.info( 'Not running under OLPCGames' )
+ return events
+
+def pauseScreen( overlaySVG=None ):
+ """Display a "Paused" screen and suspend
+
+ This default implementation will not do anything to shut down your
+ simulation or other code running in other threads. It will merely block
+ this thread (the pygame thread) until an event shows up in the
+ eventwrap queue.
+
+ Returns a surface to pass to restoreScreen to continue...
+ """
+ from olpcgames import svgsprite
+ if not overlaySVG:
+ from olpcgames.data import sleeping_svg
+ overlaySVG = sleeping_svg.data
+ screen = pygame.display.get_surface()
+ old_screen = screen.copy() # save this for later.
+ pause_sprite = svgsprite.SVGSprite(
+ overlaySVG,
+ )
+ pause_sprite.rect.center = screen.get_rect().center
+ group = sprite.RenderUpdates( )
+ group.add( pause_sprite )
+
+ # dim the screen and display the 'paused' message in the center.
+ BLACK = (0,0,0)
+ WHITE = (255,255,255)
+ dimmed = screen.copy()
+ dimmed.set_alpha(128)
+ screen.fill(BLACK)
+ screen.blit(dimmed, (0,0))
+
+ group.draw( screen )
+ return old_screen
+
+def restoreScreen( old_screen ):
+ """Restore the original screen and return"""
+ screen = pygame.display.get_surface()
+ screen.blit(old_screen, (0,0))
+ return old_screen
diff --git a/olpcgames/svgsprite.py b/olpcgames/svgsprite.py
new file mode 100644
index 0000000..2c53178
--- /dev/null
+++ b/olpcgames/svgsprite.py
@@ -0,0 +1,69 @@
+"""RSVG/Cairo-based rendering of SVG into Pygame Images"""
+from pygame import sprite
+from olpcgames import _cairoimage
+import cairo, rsvg
+
+class SVGSprite( sprite.Sprite ):
+ """Sprite class which renders SVG source-code as a Pygame image"""
+ rect = image = None
+ resolution = None
+ def __init__(
+ self, svg=None, size=None, *args
+ ):
+ """Initialise the svg sprite
+
+ svg -- svg source text (i.e. content of an svg file)
+ size -- optional, to constrain size, (width,height), leaving one
+ as None or 0 causes proportional scaling, leaving both
+ as None or 0 causes natural scaling (screen resolution)
+ args -- if present, groups to which to automatically add
+ """
+ self.size = size
+ super( SVGSprite, self ).__init__( *args )
+ if svg:
+ self.setSVG( svg )
+ def setSVG( self, svg ):
+ """Set our SVG source"""
+ self.svg = svg
+ # XXX could delay this until actually asked to display...
+ if self.size:
+ width,height = self.size
+ else:
+ width,height = None,None
+ self.image = self._render( width,height )
+ rect = self.image.get_rect()
+ if self.rect:
+ rect.move( self.rect ) # should let something higher-level do that...
+ self.rect = rect
+
+ def _render( self, width, height ):
+ """Render our SVG to a Pygame image"""
+ handle = rsvg.Handle( data = self.svg )
+ originalSize = (width,height)
+ scale = 1.0
+ hw,hh = handle.get_dimension_data()[:2]
+ if hw and hh:
+ if not width:
+ if not height:
+ width,height = hw,hh
+ else:
+ scale = float(height)/hh
+ width = hh/float(hw) * height
+ elif not height:
+ scale = float(width)/hw
+ height = hw/float(hh) * width
+ else:
+ # scale only, only rendering as large as it is...
+ if width/height > hw/hh:
+ # want it taller than it is...
+ width = hh/float(hw) * height
+ else:
+ height = hw/float(hh) * width
+ scale = float(height)/hh
+
+ csrf, ctx = _cairoimage.newContext( int(width), int(height) )
+ ctx.scale( scale, scale )
+ handle.render_cairo( ctx )
+ return _cairoimage.asImage( csrf )
+ return None
+
diff --git a/olpcgames/util.py b/olpcgames/util.py
new file mode 100644
index 0000000..f4ecbf0
--- /dev/null
+++ b/olpcgames/util.py
@@ -0,0 +1,68 @@
+"""Abstraction layer for working outside the Sugar environment"""
+import traceback, cStringIO
+import logging
+log = logging.getLogger( 'olpcgames.util' )
+import os
+import os.path
+
+NON_SUGAR_ROOT = '~/.sugar/default/olpcgames'
+
+try:
+ from sugar.activity.activity import get_bundle_path as _get_bundle_path
+ def get_bundle_path( ):
+ """Retrieve bundle path from activity with fix for silly registration bug"""
+ path = _get_bundle_path()
+ if path.endswith( '.activity.activity' ):
+ log.warn( '''Found double .activity suffix in bundle path, truncating: %s''', path )
+ path = path[:-9]
+ return path
+except ImportError:
+ log.warn( '''Do not appear to be running under Sugar, stubbing-in get_bundle_path''' )
+ def get_bundle_path():
+ """Retrieve a substitute data-path for non OLPC systems"""
+ return os.getcwd()
+
+
+def get_activity_root( ):
+ """Return the activity root for data storage operations
+
+ If the activity is present, returns the activity's root,
+ otherwise returns NON_SUGAR_ROOT as the directory.
+ """
+ import olpcgames
+ if olpcgames.ACTIVITY:
+ return olpcgames.ACTIVITY.get_activity_root()
+ else:
+ return os.path.expanduser( NON_SUGAR_ROOT )
+
+def data_path(file_name):
+ """Return the full path to a file in the data sub-directory of the bundle"""
+ return os.path.join(get_bundle_path(), 'data', file_name)
+def tmp_path(file_name):
+ """Return the full path to a file in the temporary directory"""
+ return os.path.join(get_activity_root(), 'tmp', file_name)
+
+def get_traceback(error):
+ """Get formatted traceback from current exception
+
+ error -- Exception instance raised
+
+ Attempts to produce a 10-level traceback as a string
+ that you can log off. Use like so:
+
+ try:
+ doSomething()
+ except Exception, err:
+ log.error(
+ '''Failure during doSomething with X,Y,Z parameters: %s''',
+ util.get_traceback( err ),
+ )
+ """
+ exception = str(error)
+ file = cStringIO.StringIO()
+ try:
+ traceback.print_exc( limit=10, file = file )
+ exception = file.getvalue()
+ finally:
+ file.close()
+ return exception
diff --git a/olpcgames/video.py b/olpcgames/video.py
new file mode 100644
index 0000000..0cf9ac9
--- /dev/null
+++ b/olpcgames/video.py
@@ -0,0 +1,170 @@
+"""Video widget for displaying a gstreamer pipe"""
+import logging
+log = logging.getLogger( 'olpcgames.video' )
+#log.setLevel( logging.INFO )
+import os
+import signal
+import pygame
+import olpcgames
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gst
+
+class VideoWidget(gtk.DrawingArea):
+ """Widget to render GStreamer video over our Pygame Canvas
+
+ The VideoWidget is a simple GTK window which is
+ held by the PygameCanvas, just as is the Pygame
+ window we normally use. As such this approach
+ *cannot* work without the GTK wrapper.
+
+ It *should* be possible to use raw X11 operations
+ to create a child window of the Pygame/SDL window
+ and use that for the same purpose, but that would
+ require some pretty low-level ctypes hacking.
+
+ Attributes of Note:
+
+ rect -- Pygame rectangle which tells us where to
+ display ourselves, setting the rect changes the
+ position and size of the window.
+ """
+ _imagesink = None
+ _renderedRect = None
+ def __init__(self, rect=None, force_aspect_ratio=True):
+ super(VideoWidget, self).__init__()
+ self.unset_flags(gtk.DOUBLE_BUFFERED)
+ if rect is None:
+ rect = pygame.Rect( (0,0), (160,120))
+ self.rect = rect
+ self.force_aspect_ratio = force_aspect_ratio
+ self.set_size_request(rect.width,rect.height)
+ olpcgames.WIDGET.put( self, rect.left,rect.top)
+ self._renderedRect = rect
+ self.show()
+
+ def set_rect( self, rect ):
+ """Set our rectangle (area of the screen)"""
+ log.debug( 'Set rectangle: %s', rect )
+ self.set_size_request(rect.width,rect.height)
+ olpcgames.WIDGET.move( self, rect.left,rect.top)
+ self.rect = rect
+
+ def do_expose_event(self, event):
+ """Handle exposure event (trigger redraw by gst)"""
+ if self._imagesink:
+ self._imagesink.expose()
+ return False
+ else:
+ return True
+
+ def set_sink(self, sink):
+ """Set our window-sink for output"""
+ assert self.window.xid
+ self._imagesink = sink
+ self._imagesink.set_xwindow_id(self.window.xid)
+ self._imagesink.set_property('force-aspect-ratio', self.force_aspect_ratio)
+
+class PygameWidget( object ):
+ """Render "full-screen" video to the entire Pygame screen
+
+ Not particularly useful unless this happens to be exactly what you need.
+ """
+ def __init__( self ):
+ try:
+ window_id = pygame.display.get_wm_info()['window']
+ except KeyError, err: # pygame-ctypes...
+ window_id = int(os.environ['SDL_WINDOWID'])
+ self.window_id = window_id
+ self._imagesink = None
+ def set_sink( self, sink ):
+ """Set up our gst sink"""
+ log.info( 'Setting sink: %s', sink )
+ self._imagesink = sink
+ sink.set_xwindow_id( self.window_id )
+
+#pipe_desc = 'v4l2src ! video/x-raw-yuv,width=160,height=120 ! ffmpegcolorspace ! xvimagesink'
+class Player(object):
+ pipe_desc = 'v4l2src ! ffmpegcolorspace ! video/x-raw-yuv ! xvimagesink'
+ test_pipe_desc = 'videotestsrc ! ffmpegcolorspace ! video/x-raw-yuv ! xvimagesink'
+ _synchronized = False
+ def __init__(self, videowidget, pipe_desc=pipe_desc):
+ self._playing = False
+ self._videowidget = videowidget
+
+ self._pipeline = gst.parse_launch(pipe_desc)
+
+ bus = self._pipeline.get_bus()
+ bus.enable_sync_message_emission()
+ bus.add_signal_watch()
+ bus.connect('sync-message::element', self.on_sync_message)
+ bus.connect('message', self.on_message)
+
+ def play(self):
+ log.info( 'Play' )
+ if self._playing == False:
+ self._pipeline.set_state(gst.STATE_PLAYING)
+ self._playing = True
+
+ def pause(self):
+ log.info( 'Pause' )
+ if self._playing == True:
+ if self._synchronized:
+ log.debug( ' pause already sync\'d' )
+ self._pipeline.set_state(gst.STATE_PAUSED)
+ self._playing = False
+ def stop( self ):
+ """Stop all playback"""
+ self._pipeline.set_state( gst.STATE_NULL )
+
+ def on_sync_message(self, bus, message):
+ log.info( 'Sync: %s', message )
+ if message.structure is None:
+ return
+ if message.structure.get_name() == 'prepare-xwindow-id':
+ self._synchronized = True
+ self._videowidget.set_sink(message.src)
+
+ def on_message(self, bus, message):
+ log.info( 'Message: %s', message )
+ t = message.type
+ if t == gst.MESSAGE_ERROR:
+ err, debug = message.parse_error()
+ log.warn("Video error: (%s) %s" ,err, debug)
+ self._playing = False
+
+if __name__ == "__main__":
+ # Simple testing code...
+ logging.basicConfig()
+ log.setLevel( logging.DEBUG )
+ from pygame import image,display, event
+ import pygame
+ def main():
+ display.init()
+ maxX,maxY = display.list_modes()[0]
+ screen = display.set_mode( (maxX/3, maxY/3 ) )
+
+ display.flip()
+
+ pgw = PygameWidget( )
+ p = Player( pgw )
+ p.play()
+
+ clock = pygame.time.Clock()
+
+ running = True
+ while running:
+ clock.tick( 60 )
+ for evt in [pygame.event.wait()] + pygame.event.get():
+ if evt.type == pygame.KEYDOWN:
+ if p._playing:
+ p.pause()
+ else:
+ p.play()
+ elif evt.type == pygame.QUIT:
+ p.stop()
+ running = False
+ #display.flip()
+ main()
diff --git a/player.py b/player.py
new file mode 100644
index 0000000..8b16a21
--- /dev/null
+++ b/player.py
@@ -0,0 +1,122 @@
+# Pacman.activity
+# A pac-man clone for the XO laptop.
+#
+# Thanks to Raphael Reinig for testing
+#
+# Copyright (C) 2008 Tristan Hoffmann, tristanhoffmann@boxbe.com
+# Thanks to Joshua Minor, I used a lot of code from his Maze.activity
+#
+# This file is part of Pacman.activity
+#
+# Pacman.activity 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.
+#
+# Pacman.activity 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 Pacman.activity. If not, see <http://www.gnu.org/licenses/>.
+
+from olpcgames.util import get_bundle_path
+bundlepath = get_bundle_path()
+from sugar.graphics.icon import Icon
+from sugar.graphics.xocolor import XoColor
+import pygame
+import re, os
+import olpcgames.svgsprite as svgsprite
+from pygame import sprite as sprite
+
+class Player:
+ def __init__(self, buddy, startx, starty, color = 'green'):
+ self.buddy = buddy
+ self.nick = buddy.props.nick
+ self.score = 0
+ self.color = color
+ self.startposition = (startx, starty)
+ self.hidden = False
+ self.bonusplayers = None
+ self.opensprites = sprite.RenderUpdates()
+ self.closedsprites = sprite.RenderUpdates()
+ self.dirName = "/home/olpc/Activities/Pacman.activity/activity/"
+ self.open = sprite.Sprite()
+ self.closed = sprite.Sprite()
+ self.open.image = pygame.image.load (self.dirName + "pacman-" + self.color + "-open-right.png")
+ self.closed.image = pygame.image.load (self.dirName + "pacman-" + self.color + "-closed.png")
+ self.opensprites.add(self.open)
+ self.closedsprites.add (self.closed)
+ self.mouthClosed = 1
+ self.supertime = 0
+ self.reset()
+
+ def draw(self, screen, bounds, size):
+ rect = pygame.Rect(bounds.x+self.position[0]*size, bounds.y+self.position[1]*size, size, size)
+ border = size / 10.
+ center = rect.inflate(-border*2, -border*2)
+ if (self.mouthClosed == 1):
+ self.closed.rect = rect
+ self.closedsprites.draw(screen)
+ self.mouthClosed = 0
+ else:
+ self.open.rect = rect
+ self.opensprites.draw(screen)
+ self.mouthClosed = 1
+
+ def reset(self):
+ self.direction = (0,0)
+ self.position = self.startposition
+ self.previous = (1,1)
+ self.elapsed = None
+
+ def animate(self, maze):
+ # if the player finished the maze, then don't move
+ if self.direction == (0,0):
+ return self.position
+ if self.canGo(self.direction, maze):
+ self.move(self.direction, maze)
+ else:
+ self.direction = (0,0)
+ self.mouthClosed = 1
+ return self.position
+
+ def move(self, direction, maze):
+ """Move the player in a given direction (deltax,deltay)"""
+ newposition = (self.position[0]+direction[0], self.position[1]+direction[1])
+ self.previous = self.position
+ self.position = newposition
+ if (maze.map [newposition[0]][newposition[1]] == maze.EMPTY):
+ self.score += 1
+
+ def canGo(self, direction, maze):
+ """Can the player go in this direction without bumping into something?"""
+ newposition = (self.position[0]+direction[0], self.position[1]+direction[1])
+ return maze.validMove(newposition[0], newposition[1])
+
+ def cameFrom(self, direction):
+ """Note the position/direction that we just came from."""
+ return self.previous == (self.position[0]+direction[0], self.position[1]+direction[1])
+
+ def bonusPlayers(self):
+ if self.bonusplayers is None:
+ self.bonusplayers = []
+ self.bonusplayers.append(Player(self.buddy, 1, 21, 'green'))
+ self.bonusplayers.append(Player(self.buddy, 31, 21, 'blue'))
+ #self.bonusplayers.append(Player(self.buddy, 31, 1, ''
+ count = 2
+ for player in self.bonusplayers:
+ player.nick = self.nick + "-%d" % count
+ player.hidden = True
+ count += 1
+
+ return self.bonusplayers
+
+ def bonusPlayer(self, nick):
+ if nick == self.nick:
+ return self
+ for bonusplayer in self.bonusPlayers():
+ if bonusplayer.nick == nick:
+ return bonusplayer
+
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..7a02132
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+from sugar.activity import bundlebuilder
+bundlebuilder.start("Pacman")
+