summaryrefslogtreecommitdiffstats
path: root/kolf
diff options
context:
space:
mode:
authortoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
committertoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
commitc90c389a8a8d9d8661e9772ec4144c5cf2039f23 (patch)
tree6d8391395bce9eaea4ad78958617edb20c6a7573 /kolf
downloadtdegames-c90c389a8a8d9d8661e9772ec4144c5cf2039f23.tar.gz
tdegames-c90c389a8a8d9d8661e9772ec4144c5cf2039f23.zip
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdegames@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kolf')
-rw-r--r--kolf/AUTHORS3
-rw-r--r--kolf/COPYING341
-rw-r--r--kolf/Makefile.am49
-rw-r--r--kolf/Makefile.am.fast32
-rw-r--r--kolf/PLUGINS.docbook1
-rw-r--r--kolf/README.pool7
-rw-r--r--kolf/TODO1
-rw-r--r--kolf/ball.cpp466
-rw-r--r--kolf/ball.h114
-rw-r--r--kolf/canvasitem.cpp48
-rw-r--r--kolf/canvasitem.h182
-rw-r--r--kolf/config.cpp48
-rw-r--r--kolf/config.h42
-rw-r--r--kolf/configure.in.in3
-rw-r--r--kolf/courses.list10
-rw-r--r--kolf/courses/ADDING_COURSES8
-rw-r--r--kolf/courses/Classic.kolf1986
-rw-r--r--kolf/courses/Easy.kolf1737
-rw-r--r--kolf/courses/Hard.kolf4403
-rw-r--r--kolf/courses/Impossible3573
-rw-r--r--kolf/courses/Makefile.am4
-rw-r--r--kolf/courses/Medium.kolf2557
-rw-r--r--kolf/courses/Practice376
-rw-r--r--kolf/courses/ReallyEasy2404
-rw-r--r--kolf/courses/USApro1982
-rw-r--r--kolf/editor.cpp60
-rw-r--r--kolf/editor.h37
-rw-r--r--kolf/floater.cpp277
-rw-r--r--kolf/floater.h81
-rw-r--r--kolf/game.cpp4302
-rw-r--r--kolf/game.h1026
-rw-r--r--kolf/graphics/Makefile.am4
-rw-r--r--kolf/graphics/cup.pngbin0 -> 876 bytes
-rw-r--r--kolf/graphics/grass.pngbin0 -> 13002 bytes
-rw-r--r--kolf/graphics/puddle.pngbin0 -> 622 bytes
-rw-r--r--kolf/graphics/sand.pngbin0 -> 968 bytes
-rw-r--r--kolf/intro283
-rw-r--r--kolf/kcomboboxdialog.cpp152
-rw-r--r--kolf/kcomboboxdialog.h114
-rw-r--r--kolf/kolf.cpp815
-rw-r--r--kolf/kolf.desktop73
-rw-r--r--kolf/kolf.h147
-rw-r--r--kolf/kolf.magic2
-rw-r--r--kolf/kolfui.rc71
-rw-r--r--kolf/kvolumecontrol.cpp67
-rw-r--r--kolf/kvolumecontrol.h28
-rw-r--r--kolf/main.cpp93
-rw-r--r--kolf/newgame.cpp347
-rw-r--r--kolf/newgame.h106
-rw-r--r--kolf/object.cpp2
-rw-r--r--kolf/object.h30
-rw-r--r--kolf/objects/Makefile.am1
-rw-r--r--kolf/objects/poolball/Makefile.am13
-rw-r--r--kolf/objects/poolball/poolball.cpp86
-rw-r--r--kolf/objects/poolball/poolball.h63
-rw-r--r--kolf/objects/poolball/poolball.plugin1
-rw-r--r--kolf/objects/test/Makefile.am13
-rw-r--r--kolf/objects/test/test.cpp104
-rw-r--r--kolf/objects/test/test.h56
-rw-r--r--kolf/objects/test/test.plugin1
-rw-r--r--kolf/pics/Makefile.am1
-rw-r--r--kolf/pics/hi128-app-kolf.pngbin0 -> 9779 bytes
-rw-r--r--kolf/pics/hi16-app-kolf.pngbin0 -> 665 bytes
-rw-r--r--kolf/pics/hi22-app-kolf.pngbin0 -> 926 bytes
-rw-r--r--kolf/pics/hi32-app-kolf.pngbin0 -> 1986 bytes
-rw-r--r--kolf/pics/hi48-app-kolf.pngbin0 -> 2909 bytes
-rw-r--r--kolf/pics/hi64-app-kolf.pngbin0 -> 4216 bytes
-rw-r--r--kolf/pluginloader.cpp65
-rw-r--r--kolf/pluginloader.h13
-rw-r--r--kolf/pool.kolf303
-rw-r--r--kolf/printdialogpage.cpp35
-rw-r--r--kolf/printdialogpage.h27
-rw-r--r--kolf/rtti.h6
-rw-r--r--kolf/scoreboard.cpp94
-rw-r--r--kolf/scoreboard.h29
-rw-r--r--kolf/slope.cpp585
-rw-r--r--kolf/slope.h98
-rw-r--r--kolf/sounds/FROM11
-rw-r--r--kolf/sounds/Makefile.am4
-rw-r--r--kolf/sounds/blackhole.wavbin0 -> 391724 bytes
-rw-r--r--kolf/sounds/blackholeeject.wavbin0 -> 25708 bytes
-rw-r--r--kolf/sounds/blackholeputin.wavbin0 -> 90668 bytes
-rw-r--r--kolf/sounds/hit.wavbin0 -> 11980 bytes
-rw-r--r--kolf/sounds/holed.wavbin0 -> 10124 bytes
-rw-r--r--kolf/sounds/holeinone.wavbin0 -> 40200 bytes
-rw-r--r--kolf/sounds/puddle.wavbin0 -> 248296 bytes
-rw-r--r--kolf/sounds/wall.wavbin0 -> 471 bytes
-rw-r--r--kolf/statedb.h23
-rw-r--r--kolf/tutorial.kolf1245
-rw-r--r--kolf/tutorial.kolfgame10
-rw-r--r--kolf/vector.cpp106
-rw-r--r--kolf/vector.h92
-rw-r--r--kolf/x-kolf.desktop60
-rw-r--r--kolf/x-kourse.desktop53
94 files changed, 31692 insertions, 0 deletions
diff --git a/kolf/AUTHORS b/kolf/AUTHORS
new file mode 100644
index 00000000..4ab73b0f
--- /dev/null
+++ b/kolf/AUTHORS
@@ -0,0 +1,3 @@
+Jason Katz-Brown <jason@katzbrown.com>
+
+Others listed in main.cpp.
diff --git a/kolf/COPYING b/kolf/COPYING
new file mode 100644
index 00000000..d3a8f707
--- /dev/null
+++ b/kolf/COPYING
@@ -0,0 +1,341 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) 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
+this service 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 make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. 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.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+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
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the 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 a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE 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.
+
+ 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
+convey 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) 19yy <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 2 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, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) 19yy name of author
+ Gnomovision 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, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This 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 Library General
+Public License instead of this License.
diff --git a/kolf/Makefile.am b/kolf/Makefile.am
new file mode 100644
index 00000000..95449e11
--- /dev/null
+++ b/kolf/Makefile.am
@@ -0,0 +1,49 @@
+SUBDIRS = objects sounds courses pics graphics
+INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/libkdegames -I$(top_srcdir)/libkdegames/highscore $(all_includes)
+
+bin_PROGRAMS =
+lib_LTLIBRARIES = libkolf.la
+kdeinit_LTLIBRARIES = kolf.la
+
+libkolf_la_SOURCES = \
+ kolf.cpp game.cpp canvasitem.cpp ball.cpp newgame.cpp config.cpp \
+ scoreboard.cpp editor.cpp pluginloader.cpp object.cpp vector.cpp \
+ printdialogpage.cpp kcomboboxdialog.cpp kvolumecontrol.cpp \
+ floater.cpp slope.cpp
+
+libkolf_la_LDFLAGS = $(all_libraries) -no-undefined -version-info 3:0:2
+libkolf_la_LIBADD = $(LIB_KDEGAMES) $(LIB_KDEPRINT) $(LIB_KIO) -lartskde
+libkolf_la_DEPENDENCIES = $(LIB_KDEGAMES_DEP)
+
+
+# the installed header files
+kolfinclude_HEADERS = \
+ ball.h game.h statedb.h config.h canvasitem.h object.h rtti.h vector.h \
+ floater.h slope.h
+
+kolfincludedir=$(includedir)/kolf
+
+
+# kdeinited lib
+kolf_la_SOURCES = main.cpp
+kolf_la_LDFLAGS = $(KDE_RPATH) $(all_libraries) -module -avoid-version
+kolf_la_LIBADD = libkolf.la
+
+
+EXTRA_DIST = kolf.desktop
+
+appdir = $(kde_datadir)/kolf
+app_DATA = kolfui.rc intro tutorial.kolf tutorial.kolfgame
+
+xdg_apps_DATA = kolf.desktop
+
+applicationmimedir = $(kde_mimedir)/application
+applicationmime_DATA = x-kourse.desktop x-kolf.desktop
+
+magicdir = $(kde_confdir)/magic
+magic_DATA = kolf.magic
+
+METASOURCES = AUTO
+
+messages: rc.cpp game.h floater.h slope.h
+ $(XGETTEXT) *.cpp canvasitem.h game.h -o $(podir)/kolf.pot
diff --git a/kolf/Makefile.am.fast b/kolf/Makefile.am.fast
new file mode 100644
index 00000000..2d39a25e
--- /dev/null
+++ b/kolf/Makefile.am.fast
@@ -0,0 +1,32 @@
+SUBDIRS=objects sounds courses pics graphics
+bin_PROGRAMS=kolf
+INCLUDES=-I$(top_srcdir)/libkdegames $(all_includes)
+
+kolf_SOURCES=main.cpp kolf.cpp game.cpp canvasitem.cpp ball.cpp newgame.cpp config.cpp scoreboard.cpp editor.cpp pluginloader.cpp object.cpp vector.cpp printdialogpage.cpp kcomboboxdialog.cpp kvolumecontrol.cpp floater.cpp slope.cpp
+kolf_LDFLAGS=$(all_libraries) $(KDE_RPATH) -export-dynamic
+kolf_LDADD=$(top_builddir)/libkdegames/libkdegames.la $(LIB_KDEPRINT) $(LIB_KIO) -lartskde
+
+EXTRA_DIST=kolf.desktop
+
+appdir=$(kde_datadir)/kolf
+app_DATA=kolfui.rc intro tutorial.kolf tutorial.kolfgame
+
+appsdir=$(kde_appsdir)/Games/Arcade
+apps_DATA=kolf.desktop
+
+applicationmimedir=$(kde_mimedir)/application
+applicationmime_DATA=x-kourse.desktop x-kolf.desktop
+
+magicdir=$(kde_confdir)/magic
+magic_DATA=kolf.magic
+
+kolfinclude_HEADERS=ball.h game.h statedb.h config.h canvasitem.h object.h rtti.h vector.h floater.h slope.h
+kolfincludedir=$(includedir)/kolf
+
+METASOURCES=AUTO
+
+messages: rc.cpp game.h floater.h slope.h
+ $(XGETTEXT) *.cpp -o $(podir)/kolf.pot
+
+install-data-local:
+ rm -rf $(DESTDIR)$(kde_appsdir)/Games/kolf.desktop
diff --git a/kolf/PLUGINS.docbook b/kolf/PLUGINS.docbook
new file mode 100644
index 00000000..4da65ddb
--- /dev/null
+++ b/kolf/PLUGINS.docbook
@@ -0,0 +1 @@
+This will include plugin writing instructions later.
diff --git a/kolf/README.pool b/kolf/README.pool
new file mode 100644
index 00000000..a571b1dd
--- /dev/null
+++ b/kolf/README.pool
@@ -0,0 +1,7 @@
+Heya,
+
+I made a kolf pool game as an example of kolf plugin use. kolf/obects/poolball/ contains the PoolBall class, which is installed as a plugin.
+
+So, compile PoolBall, install it, and open pool.kolf. Kolf courses can specify any object name, and it is up to the user to install these objects that the course needs. Kolf will warn when an object is not found in the hole being loaded.
+
+Jason
diff --git a/kolf/TODO b/kolf/TODO
new file mode 100644
index 00000000..4a584e49
--- /dev/null
+++ b/kolf/TODO
@@ -0,0 +1 @@
+Nothing
diff --git a/kolf/ball.cpp b/kolf/ball.cpp
new file mode 100644
index 00000000..8adaa8cb
--- /dev/null
+++ b/kolf/ball.cpp
@@ -0,0 +1,466 @@
+#include <qcanvas.h>
+#include <qcolor.h>
+#include <qpen.h>
+
+#include <kapplication.h>
+#include <kdebug.h>
+
+#include <math.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "rtti.h"
+#include "vector.h"
+#include "canvasitem.h"
+#include "game.h"
+#include "ball.h"
+
+Ball::Ball(QCanvas *canvas)
+ : QCanvasEllipse(canvas)
+{
+ m_doDetect = true;
+ m_collisionLock = false;
+ setBeginningOfHole(false);
+ setBlowUp(false);
+ setPen(black);
+ resetSize();
+ collisionId = 0;
+ m_addStroke = false;
+ m_placeOnGround = false;
+ m_forceStillGoing = false;
+ frictionMultiplier = 1.0;
+ QFont font(kapp->font());
+ //font.setPixelSize(10);
+ label = new QCanvasText("", font, canvas);
+ label->setColor(white);
+ label->setVisible(false);
+
+ // this sets z
+ setState(Stopped);
+ label->setZ(z() - .1);
+}
+
+void Ball::aboutToDie()
+{
+ delete label;
+}
+
+void Ball::setState(BallState newState)
+{
+ state = newState;
+ if (state == Stopped)
+ setZ(1000);
+ else
+ setBeginningOfHole(false);
+}
+
+void Ball::advance(int phase)
+{
+ // not used anymore
+ // can be used to make ball wobble
+ if (phase == 1 && m_blowUp)
+ {
+ if (blowUpCount >= 50)
+ {
+ // i should make this a config option
+ //setAddStroke(addStroke() + 1);
+ setBlowUp(false);
+ resetSize();
+ return;
+ }
+
+ const double diff = 8;
+ double randnum = kapp->random();
+ const double width = 6 + randnum * (diff / RAND_MAX);
+ randnum = kapp->random();
+ const double height = 6 + randnum * (diff / RAND_MAX);
+ setSize(width, height);
+ blowUpCount++;
+ }
+}
+
+void Ball::friction()
+{
+ if (state == Stopped || state == Holed || !isVisible()) { setVelocity(0, 0); return; }
+ const double subtractAmount = .027 * frictionMultiplier;
+ if (m_vector.magnitude() <= subtractAmount)
+ {
+ state = Stopped;
+ setVelocity(0, 0);
+ game->timeout();
+ return;
+ }
+ m_vector.setMagnitude(m_vector.magnitude() - subtractAmount);
+ setVector(m_vector);
+
+ frictionMultiplier = 1.0;
+}
+
+void Ball::setVelocity(double vx, double vy)
+{
+ QCanvasEllipse::setVelocity(vx, vy);
+
+ if (vx == 0 && vy == 0)
+ {
+ m_vector.setDirection(0);
+ m_vector.setMagnitude(0);
+ return;
+ }
+
+ double ballAngle = atan2(-vy, vx);
+
+ m_vector.setDirection(ballAngle);
+ m_vector.setMagnitude(sqrt(pow(vx, 2) + pow(vy, 2)));
+}
+
+void Ball::setVector(const Vector &newVector)
+{
+ m_vector = newVector;
+
+ if (newVector.magnitude() == 0)
+ {
+ setVelocity(0, 0);
+ return;
+ }
+
+ QCanvasEllipse::setVelocity(cos(newVector.direction()) * newVector.magnitude(), -sin(newVector.direction()) * newVector.magnitude());
+}
+
+void Ball::moveBy(double dx, double dy)
+{
+ double oldx;
+ double oldy;
+ oldx = x();
+ oldy = y();
+ QCanvasEllipse::moveBy(dx, dy);
+
+ if (game && !game->isPaused())
+ collisionDetect(oldx, oldy);
+
+ if ((dx || dy) && game && game->curBall() == this)
+ game->ballMoved();
+
+ label->move(x() + width(), y() + height());
+}
+
+void Ball::doAdvance()
+{
+ QCanvasEllipse::advance(1);
+}
+
+namespace Lines
+{
+ // provides a point made of doubles
+
+ struct Line
+ {
+ Point p1, p2;
+ };
+
+ int ccw(const Point &p0, const Point &p1, const Point &p2)
+ {
+ double dx1, dx2, dy1, dy2;
+ dx1 = p1.x - p0.x; dy1 = p1.y - p0.y;
+ dx2 = p2.x - p0.x; dy2 = p2.y - p0.y;
+ if (dx1*dy2 > dy1*dx2) return +1;
+ if (dx1*dy2 < dy1*dx2) return -1;
+ if ((dx1*dx2 < 0) || (dy1*dy2 < 0)) return -1;
+ if ((dx1*dx1+dy1*dy1) < (dx2*dx2+dy2*dy2))
+ return +1;
+ return 0;
+ }
+
+ int intersects(const Line &l1, const Line &l2)
+ {
+ // Charles says, TODO: Account for vertical lines
+ // Jason says, in my testing vertical lines work
+ return ((ccw(l1.p1, l1.p2, l2.p1)
+ *ccw(l1.p1, l1.p2, l2.p2)) <= 0)
+ && ((ccw(l2.p1, l2.p2, l1.p1)
+ *ccw(l2.p1, l2.p2, l1.p2)) <= 0);
+ }
+
+
+ bool intersects(
+ double xa1, double ya1, double xb1, double yb1,
+ double xa2, double ya2, double xb2, double yb2
+ )
+ {
+ Line l1, l2;
+ l1.p1.x = xa1;
+ l1.p1.y = ya1;
+ l1.p2.x = xb1;
+ l1.p2.y = yb1;
+
+ l2.p1.x = xa2;
+ l2.p1.y = ya2;
+ l2.p2.x = xb2;
+ l2.p2.y = yb2;
+
+ return intersects(l1, l2);
+ }
+}
+
+void Ball::collisionDetect(double oldx, double oldy)
+{
+ if (!isVisible() || state == Holed || !m_doDetect)
+ return;
+
+ if (collisionId >= INT_MAX - 1)
+ collisionId = 0;
+ else
+ collisionId++;
+
+ //kdDebug(12007) << "------" << endl;
+ //kdDebug(12007) << "Ball::collisionDetect id " << collisionId << endl;
+
+ // every other time...
+ // do friction
+ if (collisionId % 2 && !(xVelocity() == 0 && yVelocity() == 0))
+ friction();
+
+ const double minSpeed = .06;
+
+ QCanvasItemList m_list = collisions(true);
+
+ // please don't ask why QCanvas doesn't actually sort its list;
+ // it just doesn't.
+ m_list.sort();
+
+ this->m_list = m_list;
+
+ for (QCanvasItemList::Iterator it = m_list.begin(); it != m_list.end(); ++it)
+ {
+ QCanvasItem *item = *it;
+
+ if (item->rtti() == Rtti_NoCollision || item->rtti() == Rtti_Putter)
+ continue;
+
+ if (item->rtti() == rtti() && !m_collisionLock)
+ {
+ // it's one of our own kind, a ball
+ Ball *oball = dynamic_cast<Ball *>(item);
+ if (!oball || oball->collisionLock())
+ continue;
+ oball->setCollisionLock(true);
+
+ if ((oball->x() - x() != 0 && oball->y() - y() != 0) && state == Rolling && oball->curState() != Holed)
+ {
+ m_collisionLock = true;
+ // move this ball to where it was barely touching
+ double ballAngle = m_vector.direction();
+ while (collisions(true).contains(item) > 0)
+ move(x() - cos(ballAngle) / 2.0, y() + sin(ballAngle) / 2.0);
+
+ // make a 2 pixel separation
+ move(x() - 2 * cos(ballAngle), y() + 2 * sin(ballAngle));
+
+ Vector bvector = oball->curVector();
+ m_vector -= bvector;
+
+ Vector unit1 = Vector(QPoint(x(), y()), QPoint(oball->x(), oball->y()));
+ unit1 = unit1.unit();
+
+ Vector unit2 = m_vector.unit();
+
+ double cos = unit1 * unit2;
+
+ unit1 *= m_vector.magnitude() * cos;
+ m_vector -= unit1;
+ m_vector += bvector;
+
+ bvector += unit1;
+
+ oball->setVector(bvector);
+ setVector(m_vector);
+
+ oball->setState(Rolling);
+ setState(Rolling);
+
+ oball->doAdvance();
+ }
+
+ continue;
+ }
+ else if (item->rtti() == Rtti_WallPoint)
+ {
+ //kdDebug(12007) << "collided with WallPoint\n";
+ // iterate through the rst
+ QPtrList<WallPoint> points;
+ for (QCanvasItemList::Iterator pit = it; pit != m_list.end(); ++pit)
+ {
+ if ((*pit)->rtti() == Rtti_WallPoint)
+ {
+ WallPoint *point = (WallPoint *)(*pit);
+ if (point)
+ points.prepend(point);
+ }
+ }
+
+ // ok now we have a list of wall points we are on
+
+ WallPoint *iterpoint = 0;
+ WallPoint *finalPoint = 0;
+
+ // this wont be least when we're done hopefully
+ double leastAngleDifference = 9999;
+
+ for (iterpoint = points.first(); iterpoint; iterpoint = points.next())
+ {
+ //kdDebug(12007) << "-----\n";
+ const Wall *parentWall = iterpoint->parentWall();
+ const QPoint qp(iterpoint->x() + parentWall->x(), iterpoint->y() + parentWall->y());
+ const Point p(qp.x(), qp.y());
+ const QPoint qother = QPoint(parentWall->startPoint() == qp? parentWall->endPoint() : parentWall->startPoint()) + QPoint(parentWall->x(), parentWall->y());
+ const Point other(qother.x(), qother.y());
+
+ // vector of wall
+ Vector v = Vector(p, other);
+
+ // difference between our path and the wall path
+ double ourDir = m_vector.direction();
+
+ double wallDir = M_PI - v.direction();
+
+ //kdDebug(12007) << "ourDir: " << rad2deg(ourDir) << endl;
+ //kdDebug(12007) << "wallDir: " << rad2deg(wallDir) << endl;
+
+ const double angleDifference = fabs(M_PI - fabs(ourDir - wallDir));
+ //kdDebug(12007) << "computed angleDifference: " << rad2deg(angleDifference) << endl;
+
+ // only if this one is the least of all
+ if (angleDifference < leastAngleDifference)
+ {
+ leastAngleDifference = angleDifference;
+ finalPoint = iterpoint;
+ //kdDebug(12007) << "it's the one\n";
+ }
+ }
+
+ // this'll never happen
+ if (!finalPoint)
+ continue;
+
+ // collide with our chosen point
+ finalPoint->collision(this, collisionId);
+
+ // don't worry about colliding with walls
+ // wall points are ok alone
+ goto end;
+ }
+
+ if (!isVisible() || state == Holed)
+ return;
+
+ CanvasItem *citem = dynamic_cast<CanvasItem *>(item);
+ if (citem)
+ {
+ if (!citem->terrainCollisions())
+ {
+ // read: if (not do terrain collisions)
+ if (!citem->collision(this, collisionId))
+ {
+ // if (skip smart wall test)
+ if (citem->vStrut() || item->rtti() == Rtti_Wall)
+ goto end;
+ else
+ goto wallCheck;
+ }
+ }
+ break;
+ }
+ }
+
+ for (QCanvasItemList::Iterator it = m_list.begin(); it != m_list.end(); ++it)
+ {
+ CanvasItem *citem = dynamic_cast<CanvasItem *>(*it);
+ if (citem && citem->terrainCollisions())
+ {
+ // slopes return false
+ // as only one should be processed
+ // however that might not always be true
+
+ // read: if (not do terrain collisions)
+ if (!citem->collision(this, collisionId))
+ {
+ break;
+ }
+ }
+ }
+
+// Charles's smart wall check:
+
+ wallCheck:
+
+ { // check if I went through a wall
+ QCanvasItemList items;
+ if (game)
+ items = game->canvas()->allItems();
+ for (QCanvasItemList::Iterator i = items.begin(); i != items.end(); ++i)
+ {
+ if ((*i)->rtti() != Rtti_Wall)
+ continue;
+
+ QCanvasItem *item = (*i);
+ Wall *wall = dynamic_cast<Wall*>(item);
+ if (!wall || !wall->isVisible())
+ continue;
+
+ if (Lines::intersects(
+ wall->startPoint().x() + wall->x(), wall->startPoint().y() + wall->y(),
+ wall->endPoint().x() + wall->x(), wall->endPoint().y() + wall->y(),
+
+ oldx, oldy, x(), y()
+ ))
+ {
+ //kdDebug(12007) << "smart wall collision\n";
+ wall->collision(this, collisionId);
+ break;
+ }
+
+
+ }
+ }
+
+ end:
+
+ if (m_vector.magnitude() < minSpeed && m_vector.magnitude())
+ {
+ setVelocity(0, 0);
+ setState(Stopped);
+ }
+}
+
+BallState Ball::currentState()
+{
+ return state;
+}
+
+void Ball::showInfo()
+{
+ label->setVisible(isVisible());
+}
+
+void Ball::hideInfo()
+{
+ label->setVisible(false);
+}
+
+void Ball::setName(const QString &name)
+{
+ label->setText(name);
+}
+
+void Ball::setCanvas(QCanvas *c)
+{
+ QCanvasEllipse::setCanvas(c);
+ label->setCanvas(c);
+}
+
+void Ball::setVisible(bool yes)
+{
+ QCanvasEllipse::setVisible(yes);
+
+ label->setVisible(yes && game && game->isInfoShowing());
+}
+
diff --git a/kolf/ball.h b/kolf/ball.h
new file mode 100644
index 00000000..098d82ef
--- /dev/null
+++ b/kolf/ball.h
@@ -0,0 +1,114 @@
+#ifndef KOLF_BALL_H
+#define KOLF_BALL_H
+
+#include <qcanvas.h>
+#include <qcolor.h>
+
+#include <math.h>
+
+#include "vector.h"
+#include "rtti.h"
+
+enum BallState { Rolling = 0, Stopped, Holed };
+
+class Ball : public QCanvasEllipse, public CanvasItem
+{
+public:
+ Ball(QCanvas *canvas);
+ virtual void aboutToDie();
+
+ BallState currentState();
+
+ virtual void resetSize() { setSize(7, 7); }
+ virtual void advance(int phase);
+ virtual void doAdvance();
+ virtual void moveBy(double dx, double dy);
+ virtual void setVelocity(double vx, double vy);
+
+ virtual bool deleteable() const { return false; }
+
+ virtual bool canBeMovedByOthers() const { return true; }
+
+ BallState curState() const { return state; }
+ void setState(BallState newState);
+
+ QColor color() const { return m_color; }
+ void setColor(QColor color) { m_color = color; setBrush(color); }
+
+ void setMoved(bool yes) { m_moved = yes; }
+ bool moved() const { return m_moved; }
+ void setBlowUp(bool yes) { m_blowUp = yes; blowUpCount = 0; }
+ bool blowUp() const { return m_blowUp; }
+
+ void setFrictionMultiplier(double news) { frictionMultiplier = news; };
+ void friction();
+ void collisionDetect(double oldx, double oldy);
+
+ virtual int rtti() const { return Rtti_Ball; };
+
+ int addStroke() const { return m_addStroke; }
+ bool placeOnGround(Vector &v) { v = oldVector; return m_placeOnGround; }
+ void setAddStroke(int newStrokes) { m_addStroke = newStrokes; }
+ void setPlaceOnGround(bool placeOnGround) { m_placeOnGround = placeOnGround; oldVector = m_vector; }
+
+ bool beginningOfHole() const { return m_beginningOfHole; }
+ void setBeginningOfHole(bool yes) { m_beginningOfHole = yes; }
+
+ bool forceStillGoing() const { return m_forceStillGoing; }
+ void setForceStillGoing(bool yes) { m_forceStillGoing = yes; }
+
+ Vector curVector() const { return m_vector; }
+ void setVector(const Vector &newVector);
+
+ bool collisionLock() const { return m_collisionLock; }
+ void setCollisionLock(bool yes) { m_collisionLock = yes; }
+ virtual void fastAdvanceDone() { setCollisionLock(false); }
+
+ void setDoDetect(bool yes) { m_doDetect = yes; }
+ bool doDetect() const { return m_doDetect; }
+
+ virtual void showInfo();
+ virtual void hideInfo();
+ virtual void setName(const QString &);
+ virtual void setCanvas(QCanvas *c);
+ virtual void setVisible(bool yes);
+
+private:
+ BallState state;
+ QColor m_color;
+ long int collisionId;
+ double frictionMultiplier;
+
+ bool m_blowUp;
+ int blowUpCount;
+ int m_addStroke;
+ bool m_placeOnGround;
+ double m_oldvx;
+ double m_oldvy;
+
+ bool m_moved;
+ bool m_beginningOfHole;
+ bool m_forceStillGoing;
+
+ Vector m_vector;
+ Vector oldVector;
+ bool m_collisionLock;
+
+ bool m_doDetect;
+ QCanvasItemList m_list;
+
+ QCanvasText *label;
+};
+
+
+inline int rad2deg(double theDouble)
+{
+ return (int)((360L / (2L * M_PI)) * theDouble);
+}
+
+inline double deg2rad(double theDouble)
+{
+ return (((2L * M_PI) / 360L) * theDouble);
+}
+
+#endif
diff --git a/kolf/canvasitem.cpp b/kolf/canvasitem.cpp
new file mode 100644
index 00000000..5e39257c
--- /dev/null
+++ b/kolf/canvasitem.cpp
@@ -0,0 +1,48 @@
+#include <qcanvas.h>
+
+#include <kconfig.h>
+
+#include "game.h"
+#include "canvasitem.h"
+
+QCanvasRectangle *CanvasItem::onVStrut()
+{
+ QCanvasItem *qthis = dynamic_cast<QCanvasItem *>(this);
+ if (!qthis)
+ return 0;
+ QCanvasItemList l = qthis->collisions(true);
+ l.sort();
+ bool aboveVStrut = false;
+ CanvasItem *item = 0;
+ QCanvasItem *qitem = 0;
+ for (QCanvasItemList::Iterator it = l.begin(); it != l.end(); ++it)
+ {
+ item = dynamic_cast<CanvasItem *>(*it);
+ if (item)
+ {
+ qitem = *it;
+ if (item->vStrut())
+ {
+ //kdDebug(12007) << "above vstrut\n";
+ aboveVStrut = true;
+ break;
+ }
+ }
+ }
+
+ QCanvasRectangle *ritem = dynamic_cast<QCanvasRectangle *>(qitem);
+
+ return aboveVStrut && ritem? ritem : 0;
+}
+
+void CanvasItem::save(KConfig *cfg)
+{
+ cfg->writeEntry("dummykey", true);
+}
+
+void CanvasItem::playSound(QString file, double vol)
+{
+ if (game)
+ game->playSound(file, vol);
+}
+
diff --git a/kolf/canvasitem.h b/kolf/canvasitem.h
new file mode 100644
index 00000000..e7a37cb6
--- /dev/null
+++ b/kolf/canvasitem.h
@@ -0,0 +1,182 @@
+#ifndef KOLF_CANVASITEM_H
+#define KOLF_CANVASITEM_H
+
+#include <qcanvas.h>
+
+#include "config.h"
+
+class Ball;
+class KConfig;
+class StateDB;
+class KolfGame;
+
+class CanvasItem
+{
+public:
+ CanvasItem() { game = 0; }
+ virtual ~CanvasItem() {}
+ /**
+ * load your settings from the KConfig, which represents a course.
+ */
+ virtual void load(KConfig *) {}
+ /**
+ * load a point if you wish. Rarely necessary.
+ */
+ virtual void loadState(StateDB * /*db*/) {}
+ /**
+ * returns a bool that is true if your item needs to load after other items
+ */
+ virtual bool loadLast() const { return false; }
+ /**
+ * called if the item is made by user while editing, with the item that was selected on the hole;
+ */
+ virtual void selectedItem(QCanvasItem * /*item*/) {}
+ /**
+ * called after the item is moved the very first time by the game
+ */
+ virtual void firstMove(int /*x*/, int /*y*/) {}
+ /**
+ * save your settings.
+ */
+ virtual void save(KConfig *cfg);
+ /**
+ * save a point if you wish. Rarely necessary.
+ */
+ virtual void saveState(StateDB * /*db*/) {}
+ /**
+ * called for information when shot started
+ */
+ virtual void shotStarted() {}
+ /**
+ * called right before any items are saved.
+ */
+ virtual void aboutToSave() {}
+ /**
+ * called right after all items are saved.
+ */
+ virtual void savingDone() {}
+ /**
+ * called when the edit mode has been changed.
+ */
+ virtual void editModeChanged(bool /*editing*/) {}
+ /**
+ * the item should delete any other objects it's created.
+ * DO NOT DO THIS KIND OF STUFF IN THE DESTRUCTOR!
+ */
+ virtual void aboutToDie() {}
+ /**
+ * returns the object to get rid of when the delete button is pressed on this item. Some sub-objects will return something other than this.
+ */
+ virtual CanvasItem *itemToDelete() { return this; }
+ /**
+ * called when user presses delete key while editing. This is very rarely reimplemented, and generally shouldn't be.
+ */
+ virtual void aboutToDelete() {}
+ /**
+ * returns whether this item should be able to be deleted by user while editing.
+ */
+ virtual bool deleteable() const { return true; }
+ /**
+ * returns whether this item should get doAdvance called -- it is called in sync with ball advancing (which is twice as fast as the advance() calling rate)
+ */
+ virtual bool fastAdvance() const { return false; }
+ /**
+ * called when all items have had their chance at a doAdvance
+ */
+ virtual void fastAdvanceDone() {}
+ /**
+ * called if fastAdvance is enabled
+ */
+ virtual void doAdvance() {}
+ /**
+ * if all items of this type of item (based on rtti()) that are "colliding" (ie, in the same spot) with ball should get collision() called.
+ */
+ virtual bool terrainCollisions() const { return false; }
+ /**
+ * returns whether or not this item lifts items on top of it.
+ */
+ virtual bool vStrut() const { return false; }
+ /**
+ * show extra item info
+ */
+ virtual void showInfo() {};
+ /**
+ * hide extra item info
+ */
+ virtual void hideInfo() {};
+ /**
+ * update your Z value (this is called by various things when perhaps the value should change) if this is called by a vStrut, it will pass 'this'.
+ */
+ virtual void updateZ(QCanvasRectangle * /*vStrut*/ = 0) {};
+ /**
+ * clean up for prettyness
+ */
+ virtual void clean() {};
+ /**
+ * scale factor changed (game->scaleFactor(), the world matrix is game->worldMatrix())
+ * NOTE: not used in Kolf 1.1, which comes with KDE 3.1.
+ */
+ virtual void scaleChanged() {};
+ /**
+ * returns whether this item can be moved by others (if you want to move an item, you should honor this!)
+ */
+ virtual bool canBeMovedByOthers() const { return false; }
+ /**
+ * returns a Config that can be used to configure this item by the user.
+ * The default implementation returns one that says 'No configuration options'.
+ */
+ virtual Config *config(QWidget *parent) { return new DefaultConfig(parent); }
+ /**
+ * returns other items that should be moveable (besides this one of course).
+ */
+ virtual QPtrList<QCanvasItem> moveableItems() const { return QPtrList<QCanvasItem>(); }
+ /**
+ * returns whether this can be moved by the user while editing.
+ */
+ virtual bool moveable() const { return true; }
+
+ void setId(int newId) { id = newId; }
+ int curId() const { return id; }
+
+ /**
+ * call to play sound (ie, playSound("wall") plays kdedir/share/apps/kolf/sounds/wall.wav).
+ * optionally, specify vol to be between 0-1, for no sound to full volume, respectively.
+ */
+ void playSound(QString file, double vol = 1);
+
+ /**
+ * called on ball's collision. Return if terrain collisions should be processed.
+ */
+ virtual bool collision(Ball * /*ball*/, long int /*id*/) { return true; }
+
+ /**
+ * reimplement if you want extra items to have access to the game object.
+ * playSound() relies on having this.
+ */
+ virtual void setGame(KolfGame *game) { this->game = game; }
+
+ /**
+ * returns whether this is a corner resizer
+ */
+ virtual bool cornerResize() const { return false; }
+
+ QString name() const { return m_name; }
+ void setName(const QString &newname) { m_name = newname; }
+
+protected:
+ /**
+ * pointer to main KolfGame
+ */
+ KolfGame *game;
+
+ /**
+ * returns the highest vertical strut the item is on
+ */
+ QCanvasRectangle *onVStrut();
+
+private:
+ QString m_name;
+ int id;
+};
+
+#endif
diff --git a/kolf/config.cpp b/kolf/config.cpp
new file mode 100644
index 00000000..7652cf40
--- /dev/null
+++ b/kolf/config.cpp
@@ -0,0 +1,48 @@
+#include <qlabel.h>
+#include <qlayout.h>
+
+#include <kdialog.h>
+#include <klocale.h>
+
+#include "config.h"
+
+Config::Config(QWidget *parent, const char *name)
+ : QFrame(parent, name)
+{
+ startedUp = false;
+}
+
+void Config::ctorDone()
+{
+ startedUp = true;
+}
+
+int Config::spacingHint()
+{
+ return KDialog::spacingHint() / 2;
+}
+
+int Config::marginHint()
+{
+ return KDialog::marginHint();
+}
+
+void Config::changed()
+{
+ if (startedUp)
+ emit modified();
+}
+
+MessageConfig::MessageConfig(QString text, QWidget *parent, const char *name)
+ : Config(parent, name)
+{
+ QVBoxLayout *layout = new QVBoxLayout(this, marginHint(), spacingHint());
+ layout->addWidget(new QLabel(text, this));
+}
+
+DefaultConfig::DefaultConfig(QWidget *parent, const char *name)
+ : MessageConfig(i18n("No configuration options"), parent, name)
+{
+}
+
+#include "config.moc"
diff --git a/kolf/config.h b/kolf/config.h
new file mode 100644
index 00000000..07c68938
--- /dev/null
+++ b/kolf/config.h
@@ -0,0 +1,42 @@
+#ifndef KOLF_CONFIG_H
+#define KOLF_CONFIG_H
+
+#include <qframe.h>
+
+class Config : public QFrame
+{
+ Q_OBJECT
+
+public:
+ Config(QWidget *parent, const char *name = 0);
+ void ctorDone();
+
+signals:
+ void modified();
+
+protected:
+ int spacingHint();
+ int marginHint();
+ bool startedUp;
+ void changed();
+};
+
+// this is easy to use to show a message
+class MessageConfig : public Config
+{
+ Q_OBJECT
+
+public:
+ MessageConfig(QString text, QWidget *parent, const char *name = 0);
+};
+
+// internal
+class DefaultConfig : public MessageConfig
+{
+ Q_OBJECT
+
+public:
+ DefaultConfig(QWidget *parent, const char *name = 0);
+};
+
+#endif
diff --git a/kolf/configure.in.in b/kolf/configure.in.in
new file mode 100644
index 00000000..9754545e
--- /dev/null
+++ b/kolf/configure.in.in
@@ -0,0 +1,3 @@
+if test "x$build_arts" = "xno"; then
+ DO_NOT_COMPILE="$DO_NOT_COMPILE kolf"
+fi
diff --git a/kolf/courses.list b/kolf/courses.list
new file mode 100644
index 00000000..021d608c
--- /dev/null
+++ b/kolf/courses.list
@@ -0,0 +1,10 @@
+tutorial.kolf
+pool.kolf
+courses/Classic.kolf
+courses/Easy.kolf
+courses/Hard.kolf
+courses/Medium.kolf
+courses/Practice
+courses/ReallyEasy
+courses/Impossible
+courses/USApro
diff --git a/kolf/courses/ADDING_COURSES b/kolf/courses/ADDING_COURSES
new file mode 100644
index 00000000..2b86c6b2
--- /dev/null
+++ b/kolf/courses/ADDING_COURSES
@@ -0,0 +1,8 @@
+Hi!
+
+To add a course to the default distribution, first email me at jason@katzbrown.com.
+
+If I say 'sure!', cvs add it here (with or without file extension, preferably without), add it to the Makefile.am, and add it to kolf/courses.list.
+
+Thanks!
+Jason
diff --git a/kolf/courses/Classic.kolf b/kolf/courses/Classic.kolf
new file mode 100644
index 00000000..199d937a
--- /dev/null
+++ b/kolf/courses/Classic.kolf
@@ -0,0 +1,1986 @@
+[0-course@-50,-50]
+Name=Classic
+Name[af]=Klasieke
+Name[bg]=Класическо
+Name[bn]=ক্লাসিক
+Name[br]=Da gustum
+Name[bs]=Klasični
+Name[ca]=Clàsic
+Name[cs]=Klasický
+Name[da]=Klassisk
+Name[de]=Klassisch
+Name[el]=Κλασσικό
+Name[es]=Clásico
+Name[et]=Klassika
+Name[fi]=Klassinen
+Name[fr]=Classique
+Name[gl]=Clásico
+Name[he]=קלסי
+Name[hi]=क्लासिक
+Name[hr]=Klasično
+Name[hu]=Klasszikus
+Name[is]=Klassísk
+Name[it]=Classico
+Name[ja]=クラッシック
+Name[mk]=Класичен
+Name[nb]=Klassisk
+Name[nl]=Klassiek
+Name[nn]=Klassisk
+Name[pl]=Klasyczny
+Name[pt]=Clássico
+Name[pt_BR]=Clássico
+Name[ro]=Clasic
+Name[ru]=Классика
+Name[sk]=Klasické
+Name[sl]=Klasična
+Name[sr]=Класичан
+Name[sr@Latn]=Klasičan
+Name[sv]=Klassisk
+Name[ta]=சிறந்த
+Name[tg]=Классика
+Name[tr]=Klasik
+Name[uk]=Класичний
+Name[ven]=Maimbo
+Name[wa]=Classike
+Name[xh]=Yeyakudala
+Name[xx]=xxClassicxx
+Name[zh_CN]=经典
+Name[zh_TW]=古典
+Name[zu]=Okuhle kakhulu
+author=Niklas Knutsson
+name=Classic
+
+[1-ball@200,360]
+dummykey=true
+
+[1-cup@197,56|3]
+dummykey=true
+
+[1-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[1-sand@-49,288|16]
+changeEnabled=false
+changeEvery=50
+height=482
+width=332
+
+[1-slope@160,19|13]
+grade=4
+gradient=Elliptic
+height=76
+reversed=false
+stuckOnGround=false
+width=76
+
+[1-wall@0,0|10]
+endPoint=285,66
+startPoint=247,9
+
+[1-wall@0,0|11]
+endPoint=285,66
+startPoint=238,128
+
+[1-wall@0,0|13]
+endPoint=213,190
+startPoint=185,190
+
+[1-wall@0,0|26]
+endPoint=238,387
+startPoint=156,387
+
+[1-wall@0,0|27]
+endPoint=247,9
+startPoint=149,9
+
+[1-wall@0,0|4]
+endPoint=156,128
+startPoint=156,387
+
+[1-wall@0,0|5]
+endPoint=238,127
+startPoint=238,387
+
+[1-wall@0,0|6]
+endPoint=180,128
+startPoint=155,129
+
+[1-wall@0,0|7]
+endPoint=238,128
+startPoint=212,127
+
+[1-wall@0,0|8]
+endPoint=111,67
+startPoint=156,128
+
+[1-wall@0,0|9]
+endPoint=149,9
+startPoint=111,67
+
+[10-ball@327,329]
+dummykey=true
+
+[10-cup@59,60|16]
+dummykey=true
+
+[10-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[10-slope@10,11|15]
+grade=4
+gradient=Elliptic
+height=54
+reversed=false
+stuckOnGround=false
+width=54
+
+[10-slope@105,66|9]
+grade=3
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[10-slope@116,118|13]
+grade=8
+gradient=Elliptic
+height=59
+reversed=false
+stuckOnGround=false
+width=59
+
+[10-slope@132,178|12]
+grade=4
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[10-slope@16,63|17]
+grade=5
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[10-slope@17,247|26]
+grade=4
+gradient=Elliptic
+height=138
+reversed=false
+stuckOnGround=false
+width=138
+
+[10-slope@172,124|11]
+grade=4
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[10-slope@176,232|5]
+grade=4
+gradient=Elliptic
+height=60
+reversed=false
+stuckOnGround=false
+width=60
+
+[10-slope@178,174|8]
+grade=4
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[10-slope@211,174|14]
+grade=3
+gradient=Elliptic
+height=66
+reversed=false
+stuckOnGround=false
+width=66
+
+[10-slope@242,6|25]
+grade=4
+gradient=Elliptic
+height=144
+reversed=false
+stuckOnGround=false
+width=144
+
+[10-slope@248,224|6]
+grade=4
+gradient=Elliptic
+height=63
+reversed=false
+stuckOnGround=false
+width=63
+
+[10-slope@59,63|10]
+grade=7
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[10-slope@66,25|18]
+grade=4
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[10-slope@72,106|8]
+grade=4
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[10-wall@0,0|19]
+endPoint=242,256
+startPoint=242,329
+
+[10-wall@0,0|20]
+endPoint=179,90
+startPoint=126,116
+
+[10-wall@0,0|21]
+endPoint=80,104
+startPoint=50,135
+
+[10-wall@0,0|22]
+endPoint=390,301
+startPoint=299,387
+
+[10-wall@0,0|23]
+endPoint=10,11
+startPoint=10,95
+
+[10-wall@0,0|24]
+endPoint=100,11
+startPoint=10,11
+
+[10-wall@0,0|3]
+endPoint=10,95
+startPoint=299,387
+
+[10-wall@0,0|5]
+endPoint=390,301
+startPoint=100,11
+
+[11-ball@200,356]
+dummykey=true
+
+[11-cup@204,194|8]
+dummykey=true
+
+[11-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[11-slope@117,42|10]
+grade=2
+gradient=Vertical
+height=64
+reversed=false
+stuckOnGround=false
+width=169
+
+[11-slope@153,144|7]
+grade=5
+gradient=Elliptic
+height=103
+reversed=false
+stuckOnGround=false
+width=103
+
+[11-slope@283,42|14]
+grade=2
+gradient=Opposite Diagonal
+height=64
+reversed=false
+stuckOnGround=false
+width=73
+
+[11-slope@284,106|11]
+grade=2
+gradient=Horizontal
+height=132
+reversed=true
+stuckOnGround=false
+width=70
+
+[11-slope@284,235|13]
+grade=2
+gradient=Diagonal
+height=35
+reversed=true
+stuckOnGround=false
+width=73
+
+[11-slope@286,279|21]
+grade=4
+gradient=Elliptic
+height=102
+reversed=false
+stuckOnGround=false
+width=102
+
+[11-slope@40,235|15]
+grade=2
+gradient=Opposite Diagonal
+height=39
+reversed=true
+stuckOnGround=false
+width=77
+
+[11-slope@40,42|12]
+grade=2
+gradient=Diagonal
+height=64
+reversed=false
+stuckOnGround=false
+width=78
+
+[11-slope@42,106|9]
+grade=2
+gradient=Horizontal
+height=131
+reversed=false
+stuckOnGround=false
+width=75
+
+[11-slope@9,278|20]
+grade=4
+gradient=Elliptic
+height=106
+reversed=false
+stuckOnGround=false
+width=106
+
+[11-wall@0,0|16]
+endPoint=115,276
+startPoint=116,388
+
+[11-wall@0,0|17]
+endPoint=287,271
+startPoint=287,388
+
+[11-wall@0,0|18]
+endPoint=115,276
+startPoint=11,276
+
+[11-wall@0,0|19]
+endPoint=388,271
+startPoint=287,271
+
+[11-wall@0,0|3]
+endPoint=141,171
+startPoint=141,222
+
+[11-wall@0,0|4]
+endPoint=224,259
+startPoint=183,259
+
+[11-wall@0,0|5]
+endPoint=265,169
+startPoint=265,221
+
+[11-wall@0,0|6]
+endPoint=228,132
+startPoint=180,132
+
+[12-ball@191,359]
+dummykey=true
+
+[12-cup@190,61|4]
+dummykey=true
+
+[12-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[12-sand@-19,297|16]
+changeEnabled=false
+changeEvery=50
+height=394
+width=212
+
+[12-sand@27,396|17]
+changeEnabled=false
+changeEvery=50
+height=192
+width=186
+
+[12-slope@141,119|13]
+grade=3
+gradient=Vertical
+height=26
+reversed=false
+stuckOnGround=false
+width=92
+
+[12-wall@0,0|14]
+endPoint=230,9
+startPoint=146,9
+
+[12-wall@0,0|15]
+endPoint=234,387
+startPoint=143,387
+
+[12-wall@0,0|3]
+endPoint=142,117
+startPoint=143,387
+
+[12-wall@0,0|5]
+endPoint=234,118
+startPoint=234,387
+
+[12-wall@0,0|6]
+endPoint=111,63
+startPoint=142,117
+
+[12-wall@0,0|7]
+endPoint=146,9
+startPoint=111,63
+
+[12-wall@0,0|8]
+endPoint=266,61
+startPoint=234,118
+
+[12-wall@0,0|9]
+endPoint=230,9
+startPoint=266,61
+
+[12-windmill@142,146|12]
+botWallVisible=false
+bottom=true
+height=47
+leftWallVisible=true
+rightWallVisible=true
+speed=6
+topWallVisible=false
+width=92
+
+[12-windmill@142,192|11]
+botWallVisible=false
+bottom=true
+height=65
+leftWallVisible=true
+rightWallVisible=true
+speed=5
+topWallVisible=false
+width=92
+
+[12-windmill@142,257|10]
+botWallVisible=false
+bottom=true
+height=49
+leftWallVisible=true
+rightWallVisible=true
+speed=3
+topWallVisible=false
+width=92
+
+[13-ball@42,350]
+dummykey=true
+
+[13-blackhole@302,95|4]
+exit=133,78
+exitDeg=180
+maxspeed=5
+minspeed=2
+
+[13-cup@33,77|9]
+dummykey=true
+
+[13-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[13-puddle@209,99|13]
+changeEnabled=false
+changeEvery=50
+height=46
+width=54
+
+[13-puddle@212,77|12]
+changeEnabled=false
+changeEvery=50
+height=46
+width=50
+
+[13-puddle@214,120|14]
+changeEnabled=false
+changeEvery=50
+height=38
+width=48
+
+[13-puddle@221,57|11]
+changeEnabled=false
+changeEvery=50
+height=48
+width=48
+
+[13-puddle@226,143|15]
+changeEnabled=false
+changeEvery=50
+height=46
+width=52
+
+[13-puddle@236,38|10]
+changeEnabled=false
+changeEvery=50
+height=36
+width=46
+
+[13-slope@237,31|3]
+grade=8
+gradient=Elliptic
+height=130
+reversed=true
+stuckOnGround=false
+width=130
+
+[13-wall@0,0|16]
+endPoint=368,23
+startPoint=237,24
+
+[13-wall@0,0|17]
+endPoint=368,23
+startPoint=367,187
+
+[13-wall@0,0|18]
+endPoint=367,187
+startPoint=16,391
+
+[13-wall@0,0|19]
+endPoint=237,24
+startPoint=16,348
+
+[13-wall@0,0|20]
+endPoint=16,391
+startPoint=16,348
+
+[13-wall@0,0|21]
+endPoint=172,201
+startPoint=142,216
+
+[13-wall@0,0|22]
+endPoint=163,268
+startPoint=134,250
+
+[13-wall@0,0|23]
+endPoint=242,187
+startPoint=223,221
+
+[13-wall@0,0|24]
+endPoint=206,280
+startPoint=199,247
+
+[13-wall@0,0|25]
+endPoint=120,277
+startPoint=90,292
+
+[13-wall@0,0|5]
+endPoint=137,19
+startPoint=10,17
+
+[13-wall@0,0|6]
+endPoint=138,142
+startPoint=137,19
+
+[13-wall@0,0|7]
+endPoint=138,142
+startPoint=10,143
+
+[13-wall@0,0|8]
+endPoint=10,17
+startPoint=10,143
+
+[14-ball@265,361]
+dummykey=true
+
+[14-cup@76,93|15]
+dummykey=true
+
+[14-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[14-slope@105,206|18]
+grade=4
+gradient=Opposite Diagonal
+height=181
+reversed=true
+stuckOnGround=false
+width=248
+
+[14-slope@108,13|20]
+grade=4
+gradient=Diagonal
+height=194
+reversed=false
+stuckOnGround=false
+width=245
+
+[14-slope@351,14|21]
+grade=4
+gradient=Opposite Diagonal
+height=194
+reversed=false
+stuckOnGround=false
+width=50
+
+[14-slope@351,206|22]
+grade=4
+gradient=Diagonal
+height=181
+reversed=true
+stuckOnGround=false
+width=49
+
+[14-wall@0,0|10]
+endPoint=50,37
+startPoint=20,68
+
+[14-wall@0,0|11]
+endPoint=20,68
+startPoint=20,109
+
+[14-wall@0,0|12]
+endPoint=20,109
+startPoint=49,152
+
+[14-wall@0,0|13]
+endPoint=100,151
+startPoint=49,152
+
+[14-wall@0,0|14]
+endPoint=131,135
+startPoint=100,151
+
+[14-wall@0,0|16]
+endPoint=246,207
+startPoint=213,206
+
+[14-wall@0,0|17]
+endPoint=313,206
+startPoint=271,207
+
+[14-wall@0,0|20]
+endPoint=145,110
+startPoint=131,135
+
+[14-wall@0,0|3]
+endPoint=213,207
+startPoint=212,387
+
+[14-wall@0,0|4]
+endPoint=312,206
+startPoint=313,387
+
+[14-wall@0,0|5]
+endPoint=313,387
+startPoint=212,387
+
+[14-wall@0,0|6]
+endPoint=131,135
+startPoint=213,206
+
+[14-wall@0,0|7]
+endPoint=138,69
+startPoint=313,206
+
+[14-wall@0,0|8]
+endPoint=138,69
+startPoint=108,37
+
+[14-wall@0,0|9]
+endPoint=108,37
+startPoint=50,37
+
+[15-ball@82,336]
+dummykey=true
+
+[15-cup@283,95|7]
+dummykey=true
+
+[15-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[15-slope@-1,0|16]
+grade=4
+gradient=Diagonal
+height=319
+reversed=true
+stuckOnGround=false
+width=281
+
+[15-slope@1,319|19]
+grade=4
+gradient=Opposite Diagonal
+height=80
+reversed=false
+stuckOnGround=false
+width=102
+
+[15-slope@119,75|17]
+grade=4
+gradient=Diagonal
+height=322
+reversed=false
+stuckOnGround=false
+width=279
+
+[15-slope@251,-1|18]
+grade=4
+gradient=Opposite Diagonal
+height=91
+reversed=true
+stuckOnGround=false
+width=150
+
+[15-wall@0,0|10]
+endPoint=98,277
+startPoint=98,218
+
+[15-wall@0,0|11]
+endPoint=190,301
+startPoint=132,301
+
+[15-wall@0,0|12]
+endPoint=283,197
+startPoint=222,197
+
+[15-wall@0,0|13]
+endPoint=193,167
+startPoint=193,106
+
+[15-wall@0,0|14]
+endPoint=159,240
+startPoint=158,196
+
+[15-wall@0,0|15]
+endPoint=202,240
+startPoint=159,240
+
+[15-wall@0,0|3]
+endPoint=10,315
+startPoint=269,18
+
+[15-wall@0,0|4]
+endPoint=377,88
+startPoint=113,394
+
+[15-wall@0,0|5]
+endPoint=377,88
+startPoint=269,18
+
+[15-wall@0,0|6]
+endPoint=113,394
+startPoint=10,315
+
+[15-wall@0,0|8]
+endPoint=250,96
+startPoint=250,133
+
+[15-wall@0,0|9]
+endPoint=290,134
+startPoint=250,133
+
+[16-ball@132,358]
+dummykey=true
+
+[16-blackhole@109,200|12]
+exit=110,111
+exitDeg=90
+maxspeed=5
+minspeed=3
+
+[16-cup@318,52|13]
+dummykey=true
+
+[16-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[16-sand@233,399|19]
+changeEnabled=false
+changeEvery=50
+height=150
+width=86
+
+[16-sand@307,259|17]
+changeEnabled=false
+changeEvery=50
+height=116
+width=26
+
+[16-sand@387,206|18]
+changeEnabled=false
+changeEvery=50
+height=146
+width=100
+
+[16-sand@402,371|16]
+changeEnabled=false
+changeEvery=50
+height=404
+width=358
+
+[16-slope@177,19|14]
+grade=4
+gradient=Opposite Diagonal
+height=101
+reversed=true
+stuckOnGround=false
+width=175
+
+[16-slope@55,115|11]
+grade=5
+gradient=Vertical
+height=224
+reversed=true
+stuckOnGround=false
+width=107
+
+[16-wall@0,0|10]
+endPoint=160,117
+startPoint=56,116
+
+[16-wall@0,0|16]
+endPoint=241,94
+startPoint=186,59
+
+[16-wall@0,0|3]
+endPoint=56,78
+startPoint=57,381
+
+[16-wall@0,0|4]
+endPoint=160,379
+startPoint=57,381
+
+[16-wall@0,0|5]
+endPoint=160,117
+startPoint=160,379
+
+[16-wall@0,0|6]
+endPoint=114,19
+startPoint=56,78
+
+[16-wall@0,0|7]
+endPoint=354,19
+startPoint=114,19
+
+[16-wall@0,0|8]
+endPoint=354,116
+startPoint=160,117
+
+[16-wall@0,0|9]
+endPoint=354,19
+startPoint=354,116
+
+[16-windmill@288,278|20]
+botWallVisible=true
+bottom=false
+height=96
+leftWallVisible=true
+rightWallVisible=true
+speed=5
+topWallVisible=false
+width=86
+
+[17-ball@91,316]
+dummykey=true
+
+[17-bumper@152,251|14]
+dummykey=true
+
+[17-bumper@175,181|12]
+dummykey=true
+
+[17-bumper@214,222|13]
+dummykey=true
+
+[17-bumper@245,156|15]
+dummykey=true
+
+[17-bumper@333,114|16]
+dummykey=true
+
+[17-cup@321,312|10]
+dummykey=true
+
+[17-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[17-slope@-3,-2|17]
+grade=2
+gradient=Diagonal
+height=323
+reversed=true
+stuckOnGround=false
+width=291
+
+[17-slope@288,190|18]
+grade=2
+gradient=Vertical
+height=156
+reversed=false
+stuckOnGround=false
+width=68
+
+[17-wall@0,0|11]
+endPoint=310,66
+startPoint=243,66
+
+[17-wall@0,0|3]
+endPoint=271,32
+startPoint=32,299
+
+[17-wall@0,0|4]
+endPoint=289,179
+startPoint=107,375
+
+[17-wall@0,0|5]
+endPoint=355,103
+startPoint=271,32
+
+[17-wall@0,0|6]
+endPoint=107,375
+startPoint=32,299
+
+[17-wall@0,0|7]
+endPoint=289,347
+startPoint=289,179
+
+[17-wall@0,0|8]
+endPoint=355,103
+startPoint=355,347
+
+[17-wall@0,0|9]
+endPoint=355,347
+startPoint=289,347
+
+[18-ball@173,353]
+dummykey=true
+
+[18-bridge@172,92|28]
+botWallVisible=false
+height=47
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=30
+
+[18-cup@188,44|7]
+dummykey=true
+
+[18-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[18-puddle@215,126|17]
+changeEnabled=false
+changeEvery=50
+height=44
+width=322
+
+[18-puddle@31,169|14]
+changeEnabled=false
+changeEvery=50
+height=44
+width=80
+
+[18-puddle@341,133|18]
+changeEnabled=false
+changeEvery=50
+height=56
+width=176
+
+[18-puddle@41,155|15]
+changeEnabled=false
+changeEvery=50
+height=52
+width=70
+
+[18-puddle@5,178|27]
+changeEnabled=false
+changeEvery=50
+height=48
+width=122
+
+[18-puddle@53,146|16]
+changeEnabled=false
+changeEvery=50
+height=36
+width=76
+
+[18-puddle@74,135|18]
+changeEnabled=false
+changeEvery=50
+height=30
+width=90
+
+[18-slope@122,156|16]
+grade=2
+gradient=Vertical
+height=222
+reversed=true
+stuckOnGround=false
+width=134
+
+[18-slope@172,118|9]
+grade=3
+gradient=Vertical
+height=30
+reversed=true
+stuckOnGround=false
+width=30
+
+[18-slope@172,89|10]
+grade=3
+gradient=Vertical
+height=30
+reversed=false
+stuckOnGround=false
+width=30
+
+[18-wall@0,0|3]
+endPoint=122,16
+startPoint=122,377
+
+[18-wall@0,0|4]
+endPoint=258,16
+startPoint=257,377
+
+[18-wall@0,0|5]
+endPoint=257,377
+startPoint=122,377
+
+[18-wall@0,0|6]
+endPoint=258,16
+startPoint=122,16
+
+[2-ball@191,371]
+dummykey=true
+
+[2-bumper@165,269|19]
+dummykey=true
+
+[2-cup@362,65|14]
+dummykey=true
+
+[2-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[2-puddle@-43,17|23]
+changeEnabled=false
+changeEvery=50
+height=380
+width=288
+
+[2-sand@-28,-1|22]
+changeEnabled=false
+changeEvery=50
+height=502
+width=336
+
+[2-sand@223,221|18]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[2-slope@139,151|19]
+grade=3
+gradient=Vertical
+height=193
+reversed=true
+stuckOnGround=true
+width=113
+
+[2-slope@180,116|20]
+grade=6
+gradient=Diagonal
+height=33
+reversed=false
+stuckOnGround=false
+width=15
+
+[2-slope@193,117|9]
+grade=6
+gradient=Vertical
+height=33
+reversed=false
+stuckOnGround=false
+width=42
+
+[2-slope@235,116|21]
+grade=6
+gradient=Opposite Diagonal
+height=34
+reversed=false
+stuckOnGround=false
+width=16
+
+[2-wall@0,0|10]
+endPoint=244,38
+startPoint=140,151
+
+[2-wall@0,0|11]
+endPoint=296,91
+startPoint=251,153
+
+[2-wall@0,0|12]
+endPoint=389,37
+startPoint=244,38
+
+[2-wall@0,0|13]
+endPoint=389,91
+startPoint=297,92
+
+[2-wall@0,0|16]
+endPoint=274,69
+startPoint=296,91
+
+[2-wall@0,0|17]
+endPoint=314,60
+startPoint=294,41
+
+[2-wall@0,0|20]
+endPoint=250,388
+startPoint=139,388
+
+[2-wall@0,0|21]
+endPoint=389,37
+startPoint=389,91
+
+[2-wall@0,0|3]
+endPoint=139,149
+startPoint=139,388
+
+[2-wall@0,0|4]
+endPoint=251,153
+startPoint=250,388
+
+[2-wall@0,0|7]
+endPoint=191,149
+startPoint=140,151
+
+[2-wall@0,0|8]
+endPoint=251,153
+startPoint=230,153
+
+[3-ball@197,369]
+dummykey=true
+
+[3-blackhole@200,179|6]
+exit=197,147
+exitDeg=90
+maxspeed=2
+minspeed=1
+
+[3-cup@364,39|13]
+dummykey=true
+
+[3-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[3-slope@-2,-1|18]
+grade=4
+gradient=Diagonal
+height=318
+reversed=true
+stuckOnGround=false
+width=150
+
+[3-slope@143,152|5]
+grade=5
+gradient=Vertical
+height=197
+reversed=true
+stuckOnGround=true
+width=106
+
+[3-slope@176,108|10]
+grade=7
+gradient=Diagonal
+height=42
+reversed=false
+stuckOnGround=false
+width=39
+
+[3-slope@257,83|19]
+grade=4
+gradient=Diagonal
+height=319
+reversed=false
+stuckOnGround=false
+width=143
+
+[3-wall@0,0|11]
+endPoint=178,9
+startPoint=178,150
+
+[3-wall@0,0|12]
+endPoint=215,71
+startPoint=214,151
+
+[3-wall@0,0|14]
+endPoint=385,72
+startPoint=215,71
+
+[3-wall@0,0|16]
+endPoint=250,388
+startPoint=144,388
+
+[3-wall@0,0|17]
+endPoint=393,10
+startPoint=178,9
+
+[3-wall@0,0|18]
+endPoint=296,38
+startPoint=236,38
+
+[3-wall@0,0|20]
+endPoint=393,10
+startPoint=385,72
+
+[3-wall@0,0|3]
+endPoint=145,151
+startPoint=144,388
+
+[3-wall@0,0|4]
+endPoint=250,151
+startPoint=250,388
+
+[3-wall@0,0|7]
+endPoint=250,151
+startPoint=145,151
+
+[3-wall@0,0|8]
+endPoint=198,224
+startPoint=171,201
+
+[3-wall@0,0|9]
+endPoint=226,201
+startPoint=198,224
+
+[4-ball@181,367]
+dummykey=true
+
+[4-cup@360,284|14]
+dummykey=true
+
+[4-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[4-puddle@-2,45|24]
+changeEnabled=false
+changeEvery=50
+height=314
+width=236
+
+[4-puddle@67,-7|25]
+changeEnabled=false
+changeEvery=50
+height=194
+width=348
+
+[4-slope@328,206|23]
+grade=3
+gradient=Diagonal
+height=180
+reversed=true
+stuckOnGround=false
+width=63
+
+[4-slope@330,200|15]
+grade=3
+gradient=Diagonal
+height=186
+reversed=false
+stuckOnGround=false
+width=60
+
+[4-wall@0,0|10]
+endPoint=321,83
+startPoint=206,82
+
+[4-wall@0,0|11]
+endPoint=389,150
+startPoint=322,82
+
+[4-wall@0,0|12]
+endPoint=272,145
+startPoint=329,202
+
+[4-wall@0,0|13]
+endPoint=329,203
+startPoint=329,386
+
+[4-wall@0,0|16]
+endPoint=236,386
+startPoint=124,387
+
+[4-wall@0,0|17]
+endPoint=389,150
+startPoint=388,386
+
+[4-wall@0,0|18]
+endPoint=388,386
+startPoint=329,386
+
+[4-wall@0,0|19]
+endPoint=321,83
+startPoint=310,96
+
+[4-wall@0,0|20]
+endPoint=283,129
+startPoint=271,144
+
+[4-wall@0,0|21]
+endPoint=343,189
+startPoint=329,202
+
+[4-wall@0,0|22]
+endPoint=389,150
+startPoint=373,162
+
+[4-wall@0,0|3]
+endPoint=124,205
+startPoint=124,387
+
+[4-wall@0,0|4]
+endPoint=236,201
+startPoint=236,386
+
+[4-wall@0,0|6]
+endPoint=144,186
+startPoint=124,205
+
+[4-wall@0,0|7]
+endPoint=219,186
+startPoint=236,201
+
+[4-wall@0,0|8]
+endPoint=206,82
+startPoint=144,144
+
+[4-wall@0,0|9]
+endPoint=272,145
+startPoint=219,144
+
+[4-windmill@144,144|5]
+botWallVisible=false
+bottom=true
+height=42
+leftWallVisible=true
+rightWallVisible=true
+speed=6
+topWallVisible=false
+width=75
+
+[5-ball@194,372]
+dummykey=true
+
+[5-bumper@246,44|15]
+dummykey=true
+
+[5-cup@347,44|9]
+dummykey=true
+
+[5-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[5-slope@-1,-1|16]
+grade=4
+gradient=Horizontal
+height=398
+reversed=false
+stuckOnGround=false
+width=140
+
+[5-slope@139,126|10]
+grade=5
+gradient=Vertical
+height=225
+reversed=true
+stuckOnGround=false
+width=111
+
+[5-slope@139,96|10]
+grade=6
+gradient=Vertical
+height=32
+reversed=false
+stuckOnGround=false
+width=112
+
+[5-slope@252,98|17]
+grade=4
+gradient=Horizontal
+height=296
+reversed=true
+stuckOnGround=false
+width=148
+
+[5-slope@260,84|18]
+grade=4
+gradient=Horizontal
+height=14
+reversed=true
+stuckOnGround=false
+width=171
+
+[5-slope@319,18|11]
+grade=6
+gradient=Elliptic
+height=57
+reversed=false
+stuckOnGround=false
+width=57
+
+[5-slope@387,0|19]
+grade=4
+gradient=Horizontal
+height=84
+reversed=true
+stuckOnGround=false
+width=17
+
+[5-wall@0,0|12]
+endPoint=250,388
+startPoint=140,388
+
+[5-wall@0,0|13]
+endPoint=388,7
+startPoint=227,8
+
+[5-wall@0,0|14]
+endPoint=388,7
+startPoint=387,80
+
+[5-wall@0,0|3]
+endPoint=140,95
+startPoint=140,388
+
+[5-wall@0,0|4]
+endPoint=250,95
+startPoint=250,388
+
+[5-wall@0,0|6]
+endPoint=227,8
+startPoint=140,95
+
+[5-wall@0,0|7]
+endPoint=266,80
+startPoint=250,95
+
+[5-wall@0,0|8]
+endPoint=387,80
+startPoint=266,80
+
+[6-ball@212,370]
+dummykey=true
+
+[6-bridge@193,151|8]
+botWallVisible=false
+height=47
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=35
+
+[6-cup@210,61|17]
+dummykey=true
+
+[6-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[6-puddle@204,177|4]
+changeEnabled=false
+changeEvery=50
+height=38
+width=126
+
+[6-puddle@238,175|5]
+changeEnabled=false
+changeEvery=50
+height=34
+width=92
+
+[6-sand@419,109|18]
+changeEnabled=false
+changeEvery=50
+height=150
+width=126
+
+[6-sand@436,9|17]
+changeEnabled=false
+changeEvery=50
+height=276
+width=228
+
+[6-slope@142,8|20]
+grade=3
+gradient=Vertical
+height=27
+reversed=true
+stuckOnGround=false
+width=140
+
+[6-slope@193,143|9]
+grade=3
+gradient=Vertical
+height=26
+reversed=false
+stuckOnGround=false
+width=35
+
+[6-slope@193,168|8]
+grade=3
+gradient=Vertical
+height=30
+reversed=true
+stuckOnGround=false
+width=35
+
+[6-wall@0,0|12]
+endPoint=140,8
+startPoint=141,387
+
+[6-wall@0,0|13]
+endPoint=283,8
+startPoint=284,387
+
+[6-wall@0,0|14]
+endPoint=211,269
+startPoint=211,280
+
+[6-wall@0,0|15]
+endPoint=181,252
+startPoint=181,289
+
+[6-wall@0,0|16]
+endPoint=240,252
+startPoint=240,288
+
+[6-wall@0,0|18]
+endPoint=283,8
+startPoint=140,8
+
+[6-wall@0,0|19]
+endPoint=284,387
+startPoint=141,387
+
+[7-ball@343,355]
+dummykey=true
+
+[7-cup@235,349|12]
+dummykey=true
+
+[7-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[7-puddle@170,137|7]
+changeEnabled=false
+changeEvery=50
+height=38
+width=120
+
+[7-puddle@204,128|23]
+changeEnabled=false
+changeEvery=50
+height=48
+width=56
+
+[7-puddle@257,247|8]
+changeEnabled=false
+changeEvery=50
+height=70
+width=40
+
+[7-slope@10,149|19]
+grade=2
+gradient=Vertical
+height=27
+reversed=false
+stuckOnGround=false
+width=88
+
+[7-slope@113,22|22]
+grade=4
+gradient=Elliptic
+height=68
+reversed=false
+stuckOnGround=false
+width=68
+
+[7-slope@12,175|20]
+grade=2
+gradient=Vertical
+height=27
+reversed=true
+stuckOnGround=false
+width=86
+
+[7-slope@123,169|17]
+grade=8
+gradient=Elliptic
+height=104
+reversed=false
+stuckOnGround=false
+width=104
+
+[7-slope@128,306|15]
+grade=2
+gradient=Horizontal
+height=82
+reversed=false
+stuckOnGround=false
+width=24
+
+[7-slope@148,306|16]
+grade=2
+gradient=Horizontal
+height=81
+reversed=true
+stuckOnGround=false
+width=23
+
+[7-slope@205,13|21]
+grade=4
+gradient=Elliptic
+height=68
+reversed=true
+stuckOnGround=false
+width=68
+
+[7-slope@291,217|18]
+grade=2
+gradient=Vertical
+height=171
+reversed=false
+stuckOnGround=false
+width=96
+
+[7-wall@0,0|10]
+endPoint=75,9
+startPoint=10,74
+
+[7-wall@0,0|11]
+endPoint=77,388
+startPoint=11,322
+
+[7-wall@0,0|13]
+endPoint=291,334
+startPoint=266,308
+
+[7-wall@0,0|14]
+endPoint=292,362
+startPoint=265,388
+
+[7-wall@0,0|3]
+endPoint=293,91
+startPoint=292,389
+
+[7-wall@0,0|4]
+endPoint=293,91
+startPoint=97,91
+
+[7-wall@0,0|5]
+endPoint=97,91
+startPoint=97,308
+
+[7-wall@0,0|6]
+endPoint=292,308
+startPoint=97,308
+
+[7-wall@0,0|9]
+endPoint=389,77
+startPoint=323,10
+
+[8-ball@197,360]
+dummykey=true
+
+[8-cup@197,41|5]
+dummykey=true
+
+[8-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[8-puddle@320,230|19]
+changeEnabled=false
+changeEvery=50
+height=62
+width=82
+
+[8-puddle@324,228|22]
+changeEnabled=false
+changeEvery=50
+height=90
+width=88
+
+[8-puddle@340,216|21]
+changeEnabled=false
+changeEvery=50
+height=54
+width=68
+
+[8-puddle@340,243|20]
+changeEnabled=false
+changeEvery=50
+height=88
+width=122
+
+[8-slope@138,205|14]
+grade=3
+gradient=Opposite Diagonal
+height=46
+reversed=false
+stuckOnGround=false
+width=52
+
+[8-slope@140,139|16]
+grade=3
+gradient=Diagonal
+height=40
+reversed=true
+stuckOnGround=false
+width=54
+
+[8-slope@19,32|18]
+grade=4
+gradient=Elliptic
+height=96
+reversed=false
+stuckOnGround=false
+width=96
+
+[8-slope@196,256|15]
+grade=3
+gradient=Diagonal
+height=53
+reversed=false
+stuckOnGround=false
+width=56
+
+[8-slope@210,85|17]
+grade=3
+gradient=Opposite Diagonal
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[8-wall@0,0|10]
+endPoint=252,87
+startPoint=216,87
+
+[8-wall@0,0|11]
+endPoint=252,388
+startPoint=139,389
+
+[8-wall@0,0|12]
+endPoint=252,10
+startPoint=139,9
+
+[8-wall@0,0|13]
+endPoint=190,141
+startPoint=140,141
+
+[8-wall@0,0|3]
+endPoint=139,9
+startPoint=139,389
+
+[8-wall@0,0|4]
+endPoint=252,10
+startPoint=252,388
+
+[8-wall@0,0|6]
+endPoint=251,310
+startPoint=197,310
+
+[8-wall@0,0|7]
+endPoint=190,251
+startPoint=141,251
+
+[8-wall@0,0|8]
+endPoint=251,192
+startPoint=199,192
+
+[8-wall@0,0|9]
+endPoint=176,87
+startPoint=140,87
+
+[9-ball@275,369]
+dummykey=true
+
+[9-cup@183,50|6]
+dummykey=true
+
+[9-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[9-puddle@280,79|19]
+changeEnabled=false
+changeEvery=50
+height=54
+width=68
+
+[9-sand@-11,294|21]
+changeEnabled=false
+changeEvery=50
+height=174
+width=152
+
+[9-sand@-16,398|20]
+changeEnabled=false
+changeEvery=50
+height=254
+width=362
+
+[9-slope@141,105|15]
+grade=3
+gradient=Vertical
+height=24
+reversed=true
+stuckOnGround=false
+width=84
+
+[9-slope@141,79|14]
+grade=3
+gradient=Vertical
+height=26
+reversed=false
+stuckOnGround=false
+width=84
+
+[9-slope@224,204|12]
+grade=4
+gradient=Vertical
+height=31
+reversed=false
+stuckOnGround=false
+width=86
+
+[9-slope@224,298|10]
+grade=3
+gradient=Vertical
+height=22
+reversed=false
+stuckOnGround=false
+width=87
+
+[9-slope@226,235|13]
+grade=4
+gradient=Vertical
+height=25
+reversed=true
+stuckOnGround=false
+width=84
+
+[9-slope@226,320|11]
+grade=3
+gradient=Vertical
+height=21
+reversed=true
+stuckOnGround=false
+width=85
+
+[9-wall@0,0|16]
+endPoint=310,387
+startPoint=225,388
+
+[9-wall@0,0|17]
+endPoint=225,9
+startPoint=142,9
+
+[9-wall@0,0|18]
+endPoint=225,160
+startPoint=225,169
+
+[9-wall@0,0|3]
+endPoint=226,205
+startPoint=225,388
+
+[9-wall@0,0|4]
+endPoint=226,205
+startPoint=142,203
+
+[9-wall@0,0|5]
+endPoint=142,9
+startPoint=142,203
+
+[9-wall@0,0|7]
+endPoint=311,128
+startPoint=310,387
+
+[9-wall@0,0|8]
+endPoint=311,128
+startPoint=225,128
+
+[9-wall@0,0|9]
+endPoint=225,9
+startPoint=225,128
diff --git a/kolf/courses/Easy.kolf b/kolf/courses/Easy.kolf
new file mode 100644
index 00000000..97f7d292
--- /dev/null
+++ b/kolf/courses/Easy.kolf
@@ -0,0 +1,1737 @@
+[0-course@-50,-50]
+Name=Easy Course
+Name[af]=Maklike Natuurlik
+Name[bg]=Лесно
+Name[bn]=সহজ খেলা
+Name[bs]=Lagani teren
+Name[ca]=Camp fàcil
+Name[cs]=Jednoduchý
+Name[da]=Nem bane
+Name[de]=Leicht
+Name[el]=Εύκολη πίστα
+Name[es]=Campo sencillo
+Name[et]=Lihtne väljak
+Name[fi]=Helppo kenttä
+Name[fr]=Parcours facile
+Name[ga]=Cúrsa Éasca
+Name[gl]=Campo sinxelo
+Name[he]=מסלול קל
+Name[hi]=आसान कोर्स
+Name[hr]=Lagani teren
+Name[hu]=Könnyű pálya
+Name[is]=Auðveld leið
+Name[it]=Percorso facile
+Name[ja]=簡単なコース
+Name[mk]=Лесен терен
+Name[nb]=Enkel bane
+Name[nl]=Eenvoudig parcours
+Name[nn]=Enkel bane
+Name[nso]=Course ye Bonolo
+Name[pl]=Łatwy tor
+Name[pt]=Percurso Fácil
+Name[pt_BR]=Curso Fácil
+Name[ro]=Cale uşoară
+Name[ru]=Лёгкая площадка
+Name[sk]=Jednoduché ihrisko
+Name[sl]=Lahko igrišče
+Name[sr]=Лак терен
+Name[sr@Latn]=Lak teren
+Name[sv]=Enkel bana
+Name[ta]=சுலபமான மார்க்கம்
+Name[tg]=Майдони Сабук
+Name[tr]=Kolay Pist
+Name[uk]=Легкий
+Name[ven]=Thero i Leluwaho
+Name[xh]=Indlela Elula
+Name[xx]=xxEasy Coursexx
+Name[zh_CN]=轻松的路线
+Name[zh_TW]=簡單的路線
+Name[zu]=Indlela elula
+author=Jason Katz-Brown
+name=Easy Course
+
+[1-ball@267,126]
+dummykey=true
+
+[1-cup@313,325|22]
+dummykey=true
+
+[1-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[1-puddle@69,72|14]
+changeEnabled=false
+changeEvery=50
+height=92
+width=112
+
+[1-puddle@84,103|19]
+changeEnabled=false
+changeEvery=50
+height=144
+width=96
+
+[1-sand@-6,255|19]
+changeEnabled=false
+changeEvery=50
+height=256
+width=112
+
+[1-sand@-6,368|18]
+changeEnabled=false
+changeEvery=50
+height=238
+width=190
+
+[1-sand@33,295|0]
+changeEnabled=false
+changeEvery=50
+height=116
+width=54
+
+[1-sand@37,261|1]
+changeEnabled=false
+changeEvery=50
+height=84
+width=34
+
+[1-sand@40,303|2]
+changeEnabled=false
+changeEvery=50
+height=88
+width=54
+
+[1-slope@211,282|21]
+grade=3
+gradient=Horizontal
+height=83
+reversed=false
+stuckOnGround=false
+width=61
+
+[1-wall@0,0|10]
+endPoint=347,282
+startPoint=348,364
+
+[1-wall@0,0|11]
+endPoint=347,282
+startPoint=211,281
+
+[1-wall@0,0|12]
+endPoint=195,257
+startPoint=211,281
+
+[1-wall@0,0|13]
+endPoint=281,208
+startPoint=195,257
+
+[1-wall@0,0|3]
+endPoint=343,148
+startPoint=281,208
+
+[1-wall@0,0|4]
+endPoint=327,85
+startPoint=343,148
+
+[1-wall@0,0|5]
+endPoint=327,85
+startPoint=273,56
+
+[1-wall@0,0|6]
+endPoint=273,56
+startPoint=122,182
+
+[1-wall@0,0|7]
+endPoint=122,182
+startPoint=112,349
+
+[1-wall@0,0|8]
+endPoint=138,368
+startPoint=112,349
+
+[1-wall@0,0|9]
+endPoint=348,364
+startPoint=138,368
+
+[10-ball@107,335]
+dummykey=true
+
+[10-blackhole@128,259|15]
+exit=227,337
+exitDeg=220
+maxspeed=2
+minspeed=1
+
+[10-cup@322,331|3]
+dummykey=true
+
+[10-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[10-puddle@262,-7|45]
+changeEnabled=false
+changeEvery=50
+height=90
+width=166
+
+[10-puddle@50,-21|44]
+changeEnabled=false
+changeEvery=50
+height=224
+width=466
+
+[10-slope@109,151|41]
+grade=4
+gradient=Horizontal
+height=71
+reversed=true
+stuckOnGround=false
+width=54
+
+[10-slope@150,349|45]
+grade=8
+gradient=Horizontal
+height=41
+reversed=false
+stuckOnGround=false
+width=44
+
+[10-slope@162,329|46]
+grade=7
+gradient=Horizontal
+height=21
+reversed=false
+stuckOnGround=false
+width=33
+
+[10-slope@173,121|42]
+grade=6
+gradient=Elliptic
+height=165
+reversed=false
+stuckOnGround=false
+width=165
+
+[10-slope@21,275|47]
+grade=7
+gradient=Vertical
+height=65
+reversed=false
+stuckOnGround=false
+width=49
+
+[10-wall@0,0|10]
+endPoint=139,218
+startPoint=107,225
+
+[10-wall@0,0|11]
+endPoint=107,225
+startPoint=80,256
+
+[10-wall@0,0|12]
+endPoint=80,256
+startPoint=69,302
+
+[10-wall@0,0|13]
+endPoint=69,302
+startPoint=75,340
+
+[10-wall@0,0|14]
+endPoint=91,360
+startPoint=75,340
+
+[10-wall@0,0|16]
+endPoint=372,358
+startPoint=335,379
+
+[10-wall@0,0|17]
+endPoint=335,379
+startPoint=288,374
+
+[10-wall@0,0|18]
+endPoint=288,374
+startPoint=248,353
+
+[10-wall@0,0|19]
+endPoint=246,351
+startPoint=219,321
+
+[10-wall@0,0|20]
+endPoint=219,321
+startPoint=194,325
+
+[10-wall@0,0|22]
+endPoint=194,325
+startPoint=174,314
+
+[10-wall@0,0|23]
+endPoint=372,358
+startPoint=386,320
+
+[10-wall@0,0|24]
+endPoint=386,320
+startPoint=387,260
+
+[10-wall@0,0|25]
+endPoint=372,197
+startPoint=355,158
+
+[10-wall@0,0|26]
+endPoint=355,158
+startPoint=289,111
+
+[10-wall@0,0|27]
+endPoint=289,111
+startPoint=232,111
+
+[10-wall@0,0|28]
+endPoint=232,111
+startPoint=187,128
+
+[10-wall@0,0|29]
+endPoint=187,128
+startPoint=158,152
+
+[10-wall@0,0|30]
+endPoint=158,152
+startPoint=111,153
+
+[10-wall@0,0|33]
+endPoint=111,153
+startPoint=34,222
+
+[10-wall@0,0|35]
+endPoint=34,222
+startPoint=21,286
+
+[10-wall@0,0|36]
+endPoint=21,286
+startPoint=21,353
+
+[10-wall@0,0|37]
+endPoint=21,353
+startPoint=55,393
+
+[10-wall@0,0|38]
+endPoint=55,393
+startPoint=201,394
+
+[10-wall@0,0|39]
+endPoint=201,394
+startPoint=240,374
+
+[10-wall@0,0|4]
+endPoint=128,367
+startPoint=91,360
+
+[10-wall@0,0|40]
+endPoint=240,374
+startPoint=248,353
+
+[10-wall@0,0|45]
+endPoint=372,197
+startPoint=387,260
+
+[10-wall@0,0|5]
+endPoint=128,367
+startPoint=159,343
+
+[10-wall@0,0|6]
+endPoint=159,343
+startPoint=176,311
+
+[10-wall@0,0|7]
+endPoint=174,314
+startPoint=180,266
+
+[10-wall@0,0|8]
+endPoint=180,266
+startPoint=164,230
+
+[10-wall@0,0|9]
+endPoint=164,230
+startPoint=139,218
+
+[11-ball@63,369]
+dummykey=true
+
+[11-blackhole@116,216|10]
+exit=275,307
+exitDeg=140
+maxspeed=4
+minspeed=2
+
+[11-blackhole@124,260|6]
+exit=23,381
+exitDeg=50
+maxspeed=6
+minspeed=5
+
+[11-blackhole@128,126|21]
+exit=103,387
+exitDeg=90
+maxspeed=2
+minspeed=1
+
+[11-blackhole@139,184|7]
+exit=347,109
+exitDeg=130
+maxspeed=2
+minspeed=1
+
+[11-blackhole@163,234|11]
+exit=24,25
+exitDeg=309
+maxspeed=5
+minspeed=3
+
+[11-blackhole@164,136|4]
+exit=15,147
+exitDeg=349
+maxspeed=7
+minspeed=6
+
+[11-blackhole@187,159|12]
+exit=281,22
+exitDeg=329
+maxspeed=2
+minspeed=1
+
+[11-blackhole@194,207|5]
+exit=308,183
+exitDeg=170
+maxspeed=10
+minspeed=3
+
+[11-blackhole@59,187|9]
+exit=101,20
+exitDeg=289
+maxspeed=5
+minspeed=3
+
+[11-blackhole@82,233|8]
+exit=301,235
+exitDeg=160
+maxspeed=8
+minspeed=4
+
+[11-blackhole@86,154|13]
+exit=198,384
+exitDeg=110
+maxspeed=5
+minspeed=3
+
+[11-cup@307,85|3]
+dummykey=true
+
+[11-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[11-sand@383,371|16]
+changeEnabled=false
+changeEvery=50
+height=214
+width=252
+
+[11-sand@388,295|15]
+changeEnabled=false
+changeEvery=50
+height=178
+width=146
+
+[11-slope@-15,24|17]
+grade=2
+gradient=Elliptic
+height=316
+reversed=true
+stuckOnGround=false
+width=316
+
+[11-slope@226,1|17]
+grade=4
+gradient=Elliptic
+height=162
+reversed=true
+stuckOnGround=false
+width=162
+
+[11-wall@0,0|18]
+endPoint=242,70
+startPoint=185,27
+
+[11-wall@0,0|19]
+endPoint=242,70
+startPoint=288,157
+
+[11-wall@0,0|20]
+endPoint=185,27
+startPoint=72,11
+
+[11-wall@0,0|22]
+endPoint=81,346
+startPoint=84,388
+
+[12-ball@247,85]
+dummykey=true
+
+[12-cup@79,101|3]
+dummykey=true
+
+[12-floater@245,248|11]
+botWallVisible=false
+endPoint=245,248
+height=66
+leftWallVisible=true
+rightWallVisible=true
+speed=2
+startPoint=241,177
+topWallVisible=false
+width=47
+
+[12-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=true
+maxstrokes=10
+par=3
+
+[12-slope@205,213|21]
+grade=4
+gradient=Horizontal
+height=170
+reversed=false
+stuckOnGround=true
+width=85
+
+[12-wall@0,0|10]
+endPoint=361,153
+startPoint=339,75
+
+[12-wall@0,0|13]
+endPoint=38,326
+startPoint=36,166
+
+[12-wall@0,0|14]
+endPoint=88,382
+startPoint=296,383
+
+[12-wall@0,0|15]
+endPoint=346,333
+startPoint=313,214
+
+[12-wall@0,0|17]
+endPoint=38,326
+startPoint=88,382
+
+[12-wall@0,0|20]
+endPoint=346,333
+startPoint=296,383
+
+[12-wall@0,0|4]
+endPoint=339,75
+startPoint=246,29
+
+[12-wall@0,0|5]
+endPoint=246,29
+startPoint=137,64
+
+[12-wall@0,0|7]
+endPoint=137,166
+startPoint=215,213
+
+[12-wall@0,0|8]
+endPoint=215,213
+startPoint=314,213
+
+[12-wall@0,0|9]
+endPoint=313,214
+startPoint=361,153
+
+[12-windmill@36,64|12]
+botWallVisible=false
+bottom=true
+height=102
+leftWallVisible=true
+rightWallVisible=true
+speed=5
+topWallVisible=true
+width=101
+
+[13-ball@356,340]
+dummykey=true
+
+[13-cup@213,189|3]
+dummykey=true
+
+[13-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[13-puddle@166,190|8]
+changeEnabled=false
+changeEvery=50
+height=86
+width=22
+
+[13-puddle@234,150|9]
+changeEnabled=false
+changeEvery=50
+height=26
+width=140
+
+[13-puddle@276,228|7]
+changeEnabled=false
+changeEvery=50
+height=28
+width=230
+
+[13-sand@137,322|14]
+changeEnabled=false
+changeEvery=50
+height=96
+width=12
+
+[13-sand@359,72|11]
+changeEnabled=false
+changeEvery=50
+height=76
+width=14
+
+[13-sand@362,118|12]
+changeEnabled=false
+changeEvery=50
+height=72
+width=10
+
+[13-sign@219,247|14]
+Comment=Hit slowly...
+Comment[af]=Getref stadig...
+Comment[ar]=إضرب ببطء...
+Comment[bg]=Леки и технични удари...
+Comment[bn]=ধীরে আঘাত করুন...
+Comment[bs]=Polako pucaj...
+Comment[ca]=Picar lentament...
+Comment[cs]=Trefujte pomalu...
+Comment[da]=Slå langsomt...
+Comment[de]=Langsam...
+Comment[el]=Ρίξε αργά...
+Comment[es]=Golpear lentamente...
+Comment[et]=Löö aeglaselt...
+Comment[fi]=Lyö hitaasti...
+Comment[fr]=Taper doucement...
+Comment[gl]=Golpear a modo
+Comment[he]=חבוט לאט...
+Comment[hi]=धीरे से मारें...
+Comment[hr]=Udarite polako...
+Comment[hu]=Lassan megütni...
+Comment[is]=Sláðu varlega...
+Comment[it]=Colpisci piano...
+Comment[ja]=ゆっくり打とう...
+Comment[lv]=Sit lēnām...
+Comment[mk]=Удрете пополека...
+Comment[nb]=Slå forsiktig ...
+Comment[nl]=Zachtjes slaan...
+Comment[nn]=Slå forsiktig ...
+Comment[nso]=Betha kago nanya...
+Comment[pl]=Powolne uderzenie...
+Comment[pt]=Bata devagarinho...
+Comment[pt_BR]=Arremessar lentamente...
+Comment[ro]=Loveşte încet...
+Comment[ru]=Бейте не спеша...
+Comment[sk]=Jemné údery...
+Comment[sl]=Udarite počasi ...
+Comment[sr]=Ударите полако...
+Comment[sr@Latn]=Udarite polako...
+Comment[sv]=Slå långsamt...
+Comment[ta]=மெதுவாக அடிக்கவும்...
+Comment[tg]=Оҳиста занед...
+Comment[tr]=Yavaş basın...
+Comment[uk]=Бийте без поспіху...
+Comment[ven]=Irwani nga u ongolowa...
+Comment[xh]=Betha ngokucothayo...
+Comment[xx]=xxHit slowly...xx
+Comment[zh_CN]=缓慢击打...
+Comment[zh_TW]=緩慢地敲擊...
+Comment[zu]=Shaya kancane...
+botWallVisible=true
+height=55
+leftWallVisible=true
+rightWallVisible=false
+topWallVisible=true
+width=180
+
+[13-slope@12,11|8]
+grade=2
+gradient=Horizontal
+height=124
+reversed=true
+stuckOnGround=false
+width=373
+
+[13-slope@12,133|7]
+grade=2
+gradient=Vertical
+height=254
+reversed=false
+stuckOnGround=false
+width=139
+
+[13-slope@139,266|6]
+grade=2
+gradient=Horizontal
+height=121
+reversed=false
+stuckOnGround=false
+width=248
+
+[13-wall@0,0|10]
+endPoint=385,132
+startPoint=365,12
+
+[14-ball@94,323]
+dummykey=true
+
+[14-bridge@261,66|13]
+botWallVisible=false
+height=91
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=99
+
+[14-bumper@135,356|12]
+dummykey=true
+
+[14-cup@136,355|13]
+dummykey=true
+
+[14-cup@147,145|3]
+dummykey=true
+
+[14-cup@293,288|8]
+dummykey=true
+
+[14-cup@313,113|11]
+dummykey=true
+
+[14-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[14-puddle@292,288|7]
+changeEnabled=false
+changeEvery=50
+height=102
+width=102
+
+[14-sand@145,142|5]
+changeEnabled=false
+changeEvery=50
+height=130
+width=132
+
+[14-slope@219,215|6]
+grade=6
+gradient=Elliptic
+height=145
+reversed=false
+stuckOnGround=false
+width=145
+
+[14-slope@233,34|9]
+grade=3
+gradient=Elliptic
+height=160
+reversed=false
+stuckOnGround=false
+width=160
+
+[14-slope@58,54|4]
+grade=7
+gradient=Elliptic
+height=177
+reversed=false
+stuckOnGround=false
+width=177
+
+[15-ball@32,360]
+dummykey=true
+
+[15-cup@357,168|3]
+dummykey=true
+
+[15-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=4
+
+[15-slope@10,9|6]
+grade=4
+gradient=Vertical
+height=332
+reversed=true
+stuckOnGround=false
+width=63
+
+[15-slope@34,12|4]
+grade=4
+gradient=Diagonal
+height=376
+reversed=false
+stuckOnGround=false
+width=355
+
+[15-slope@71,10|5]
+grade=4
+gradient=Diagonal
+height=332
+reversed=true
+stuckOnGround=false
+width=313
+
+[16-ball@299,105]
+dummykey=true
+
+[16-blackhole@308,282|23]
+exit=358,136
+exitDeg=160
+maxspeed=3
+minspeed=2
+
+[16-blackhole@37,334|24]
+exit=227,218
+exitDeg=239
+maxspeed=1
+minspeed=0
+
+[16-cup@106,333|3]
+dummykey=true
+
+[16-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[16-slope@183,211|22]
+grade=4
+gradient=Elliptic
+height=108
+reversed=true
+stuckOnGround=false
+width=108
+
+[16-slope@48,70|20]
+grade=6
+gradient=Elliptic
+height=100
+reversed=false
+stuckOnGround=false
+width=100
+
+[16-wall@0,0|10]
+endPoint=371,115
+startPoint=323,59
+
+[16-wall@0,0|11]
+endPoint=323,59
+startPoint=64,58
+
+[16-wall@0,0|12]
+endPoint=64,58
+startPoint=27,141
+
+[16-wall@0,0|13]
+endPoint=81,374
+startPoint=61,341
+
+[16-wall@0,0|14]
+endPoint=27,292
+startPoint=68,281
+
+[16-wall@0,0|15]
+endPoint=68,281
+startPoint=170,269
+
+[16-wall@0,0|16]
+endPoint=170,269
+startPoint=173,249
+
+[16-wall@0,0|17]
+endPoint=173,249
+startPoint=155,236
+
+[16-wall@0,0|18]
+endPoint=155,236
+startPoint=49,225
+
+[16-wall@0,0|19]
+endPoint=49,225
+startPoint=27,141
+
+[16-wall@0,0|25]
+endPoint=61,341
+startPoint=49,366
+
+[16-wall@0,0|26]
+endPoint=27,368
+startPoint=13,351
+
+[16-wall@0,0|27]
+endPoint=13,351
+startPoint=12,312
+
+[16-wall@0,0|28]
+endPoint=12,312
+startPoint=27,292
+
+[16-wall@0,0|29]
+endPoint=27,368
+startPoint=49,366
+
+[16-wall@0,0|30]
+endPoint=346,277
+startPoint=335,244
+
+[16-wall@0,0|4]
+endPoint=319,316
+startPoint=81,374
+
+[16-wall@0,0|5]
+endPoint=319,316
+startPoint=346,277
+
+[16-wall@0,0|6]
+endPoint=335,244
+startPoint=156,185
+
+[16-wall@0,0|7]
+endPoint=156,185
+startPoint=160,161
+
+[16-wall@0,0|8]
+endPoint=160,161
+startPoint=357,159
+
+[16-wall@0,0|9]
+endPoint=357,159
+startPoint=371,115
+
+[17-ball@37,125]
+dummykey=true
+
+[17-cup@356,45|3]
+dummykey=true
+
+[17-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[17-wall@0,0|10]
+endPoint=69,156
+startPoint=136,161
+
+[17-wall@0,0|11]
+endPoint=136,161
+startPoint=133,359
+
+[17-wall@0,0|12]
+endPoint=133,359
+startPoint=117,338
+
+[17-wall@0,0|13]
+endPoint=134,210
+startPoint=71,208
+
+[17-wall@0,0|14]
+endPoint=71,247
+startPoint=138,248
+
+[17-wall@0,0|15]
+endPoint=243,191
+startPoint=195,152
+
+[17-wall@0,0|16]
+endPoint=278,190
+startPoint=326,152
+
+[17-wall@0,0|17]
+endPoint=312,189
+startPoint=180,199
+
+[17-wall@0,0|18]
+endPoint=312,189
+startPoint=355,196
+
+[17-wall@0,0|19]
+endPoint=322,242
+startPoint=175,248
+
+[17-wall@0,0|20]
+endPoint=322,242
+startPoint=348,249
+
+[17-wall@0,0|21]
+endPoint=264,147
+startPoint=256,208
+
+[17-wall@0,0|22]
+endPoint=256,208
+startPoint=226,257
+
+[17-wall@0,0|23]
+endPoint=226,257
+startPoint=175,295
+
+[17-wall@0,0|24]
+endPoint=303,260
+startPoint=271,198
+
+[17-wall@0,0|25]
+endPoint=265,365
+startPoint=265,266
+
+[17-wall@0,0|26]
+endPoint=303,260
+startPoint=355,291
+
+[17-wall@0,0|27]
+endPoint=265,365
+startPoint=251,351
+
+[17-wall@0,0|28]
+endPoint=224,287
+startPoint=245,306
+
+[17-wall@0,0|29]
+endPoint=242,322
+startPoint=209,343
+
+[17-wall@0,0|31]
+endPoint=320,352
+startPoint=271,320
+
+[17-wall@0,0|4]
+endPoint=277,76
+startPoint=66,83
+
+[17-wall@0,0|5]
+endPoint=277,76
+startPoint=323,87
+
+[17-wall@0,0|6]
+endPoint=121,41
+startPoint=131,118
+
+[17-wall@0,0|7]
+endPoint=272,44
+startPoint=248,117
+
+[17-wall@0,0|8]
+endPoint=68,316
+startPoint=69,156
+
+[17-wall@0,0|9]
+endPoint=68,316
+startPoint=47,358
+
+[17-wall@3,0|30]
+endPoint=316,280
+startPoint=279,302
+
+[18-ball@246,335]
+dummykey=true
+
+[18-cup@109,107|3]
+dummykey=true
+
+[18-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[18-puddle@348,-8|18]
+changeEnabled=false
+changeEvery=50
+height=100
+width=272
+
+[18-puddle@410,-2|17]
+changeEnabled=false
+changeEvery=50
+height=280
+width=172
+
+[18-slope@164,26|16]
+grade=4
+gradient=Horizontal
+height=107
+reversed=false
+stuckOnGround=false
+width=219
+
+[18-wall@0,0|10]
+endPoint=367,151
+startPoint=315,47
+
+[18-wall@0,0|11]
+endPoint=315,47
+startPoint=209,26
+
+[18-wall@0,0|12]
+endPoint=209,26
+startPoint=66,47
+
+[18-wall@0,0|13]
+endPoint=66,47
+startPoint=41,105
+
+[18-wall@0,0|14]
+endPoint=41,105
+startPoint=89,175
+
+[18-wall@0,0|15]
+endPoint=89,175
+startPoint=168,175
+
+[18-wall@0,0|19]
+endPoint=140,314
+startPoint=169,361
+
+[18-wall@0,0|20]
+endPoint=140,314
+startPoint=167,256
+
+[18-wall@0,0|6]
+endPoint=299,377
+startPoint=169,361
+
+[18-wall@0,0|7]
+endPoint=299,377
+startPoint=362,323
+
+[18-wall@0,0|8]
+endPoint=362,323
+startPoint=387,252
+
+[18-wall@0,0|9]
+endPoint=387,252
+startPoint=367,151
+
+[18-windmill@167,133|4]
+botWallVisible=false
+bottom=true
+height=123
+leftWallVisible=true
+rightWallVisible=true
+speed=3
+topWallVisible=false
+width=180
+
+[18-windmill@167,171|5]
+botWallVisible=false
+bottom=true
+height=85
+leftWallVisible=false
+rightWallVisible=false
+speed=5
+topWallVisible=false
+width=179
+
+[2-ball@60,82]
+dummykey=true
+
+[2-cup@343,362|3]
+dummykey=true
+
+[2-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[2-puddle@379,-8|10]
+changeEnabled=false
+changeEvery=50
+height=172
+width=206
+
+[2-puddle@397,65|13]
+changeEnabled=false
+changeEvery=50
+height=96
+width=112
+
+[2-slope@10,232|16]
+grade=4
+gradient=Vertical
+height=158
+reversed=false
+stuckOnGround=false
+width=215
+
+[2-slope@224,156|9]
+grade=3
+gradient=Vertical
+height=76
+reversed=true
+stuckOnGround=false
+width=167
+
+[2-wall@0,0|4]
+endPoint=130,12
+startPoint=130,210
+
+[2-wall@0,0|5]
+endPoint=225,129
+startPoint=225,388
+
+[3-ball@210,356]
+dummykey=true
+
+[3-bumper@54,246|9]
+dummykey=true
+
+[3-bumper@98,299|8]
+dummykey=true
+
+[3-cup@341,351|12]
+dummykey=true
+
+[3-cup@62,354|11]
+dummykey=true
+
+[3-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[3-slope@184,11|7]
+grade=4
+gradient=Vertical
+height=177
+reversed=true
+stuckOnGround=false
+width=21
+
+[3-slope@205,11|6]
+grade=4
+gradient=Diagonal
+height=177
+reversed=true
+stuckOnGround=false
+width=162
+
+[3-slope@32,10|5]
+grade=4
+gradient=Opposite Diagonal
+height=179
+reversed=true
+stuckOnGround=false
+width=154
+
+[3-wall@0,0|3]
+endPoint=262,212
+startPoint=263,389
+
+[3-wall@0,0|4]
+endPoint=159,213
+startPoint=159,386
+
+[3-windmill@263,288|10]
+botWallVisible=false
+bottom=false
+height=101
+leftWallVisible=true
+rightWallVisible=false
+speed=2
+topWallVisible=false
+width=125
+
+[4-ball@91,344]
+dummykey=true
+
+[4-cup@291,91|18]
+dummykey=true
+
+[4-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[4-puddle@115,111|7]
+changeEnabled=false
+changeEvery=50
+height=70
+width=86
+
+[4-puddle@125,133|3]
+changeEnabled=false
+changeEvery=50
+height=62
+width=66
+
+[4-puddle@136,121|5]
+changeEnabled=false
+changeEvery=50
+height=60
+width=72
+
+[4-puddle@277,291|15]
+changeEnabled=false
+changeEvery=50
+height=76
+width=90
+
+[4-puddle@296,322|11]
+changeEnabled=false
+changeEvery=50
+height=128
+width=98
+
+[4-slope@255,136|20]
+grade=4
+gradient=Elliptic
+height=123
+reversed=false
+stuckOnGround=false
+width=123
+
+[4-slope@28,155|19]
+grade=3
+gradient=Elliptic
+height=145
+reversed=false
+stuckOnGround=false
+width=145
+
+[5-ball@197,252]
+dummykey=true
+
+[5-cup@85,77|33]
+dummykey=true
+
+[5-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[5-puddle@150,258|42]
+changeEnabled=false
+changeEvery=50
+height=134
+width=62
+
+[5-puddle@198,207|11]
+changeEnabled=false
+changeEvery=50
+height=52
+width=130
+
+[5-puddle@248,182|10]
+changeEnabled=false
+changeEvery=50
+height=76
+width=42
+
+[5-puddle@278,158|11]
+changeEnabled=false
+changeEvery=50
+height=44
+width=84
+
+[5-slope@14,195|36]
+grade=3
+gradient=Elliptic
+height=106
+reversed=true
+stuckOnGround=false
+width=106
+
+[5-slope@160,169|15]
+grade=4
+gradient=Diagonal
+height=220
+reversed=false
+stuckOnGround=false
+width=227
+
+[5-slope@181,12|35]
+grade=3
+gradient=Elliptic
+height=128
+reversed=false
+stuckOnGround=false
+width=128
+
+[5-wall@0,0|9]
+endPoint=127,390
+startPoint=13,314
+
+[6-ball@351,186]
+dummykey=true
+
+[6-blackhole@306,352|12]
+exit=16,145
+exitDeg=80
+maxspeed=2
+minspeed=2
+
+[6-blackhole@367,38|11]
+exit=19,10
+exitDeg=279
+maxspeed=1
+minspeed=1
+
+[6-bridge@281,64|6]
+botWallVisible=true
+height=155
+leftWallVisible=false
+rightWallVisible=true
+topWallVisible=true
+width=116
+
+[6-bridge@5,2|7]
+botWallVisible=true
+height=149
+leftWallVisible=true
+rightWallVisible=false
+topWallVisible=true
+width=96
+
+[6-cup@30,67|9]
+dummykey=true
+
+[6-floater@78,34|8]
+botWallVisible=true
+endPoint=78,34
+height=78
+leftWallVisible=false
+rightWallVisible=false
+speed=2
+startPoint=207,104
+topWallVisible=true
+width=128
+
+[6-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=true
+maxstrokes=10
+par=3
+
+[6-puddle@198,123|6]
+changeEnabled=false
+changeEvery=50
+height=394
+width=316
+
+[6-slope@109,429|8]
+grade=4
+gradient=Elliptic
+height=77
+reversed=true
+stuckOnGround=false
+width=77
+
+[6-slope@99,35|18]
+grade=3
+gradient=Elliptic
+height=75
+reversed=true
+stuckOnGround=false
+width=75
+
+[6-wall@0,0|10]
+endPoint=381,9
+startPoint=392,65
+
+[6-wall@0,0|13]
+endPoint=381,218
+startPoint=386,329
+
+[6-wall@0,0|14]
+endPoint=386,329
+startPoint=299,391
+
+[6-wall@0,0|15]
+endPoint=299,391
+startPoint=39,345
+
+[6-wall@0,0|16]
+endPoint=39,345
+startPoint=14,204
+
+[6-wall@0,0|17]
+endPoint=14,204
+startPoint=24,152
+
+[6-wall@0,0|9]
+endPoint=381,9
+startPoint=317,3
+
+[7-ball@65,363]
+dummykey=true
+
+[7-cup@309,83|3]
+dummykey=true
+
+[7-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[7-puddle@337,401|4]
+changeEnabled=false
+changeEvery=50
+height=504
+width=412
+
+[7-slope@-2,-3|5]
+grade=2
+gradient=Diagonal
+height=353
+reversed=true
+stuckOnGround=false
+width=394
+
+[7-slope@247,27|10]
+grade=2
+gradient=Elliptic
+height=123
+reversed=true
+stuckOnGround=false
+width=123
+
+[7-wall@0,0|6]
+endPoint=370,157
+startPoint=379,91
+
+[7-wall@0,0|7]
+endPoint=379,91
+startPoint=370,42
+
+[7-wall@0,0|8]
+endPoint=370,42
+startPoint=321,15
+
+[7-wall@0,0|9]
+endPoint=321,15
+startPoint=110,16
+
+[8-ball@324,331]
+dummykey=true
+
+[8-cup@171,249|32]
+dummykey=true
+
+[8-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[8-slope@212,51|33]
+grade=2
+gradient=Elliptic
+height=96
+reversed=false
+stuckOnGround=false
+width=96
+
+[8-slope@84,231|34]
+grade=4
+gradient=Elliptic
+height=51
+reversed=false
+stuckOnGround=false
+width=51
+
+[8-wall@0,0|10]
+endPoint=104,210
+startPoint=84,204
+
+[8-wall@0,0|11]
+endPoint=84,204
+startPoint=80,183
+
+[8-wall@0,0|12]
+endPoint=80,183
+startPoint=105,150
+
+[8-wall@0,0|13]
+endPoint=105,150
+startPoint=159,137
+
+[8-wall@0,0|14]
+endPoint=159,137
+startPoint=222,143
+
+[8-wall@0,0|15]
+endPoint=222,143
+startPoint=275,180
+
+[8-wall@0,0|16]
+endPoint=275,180
+startPoint=305,263
+
+[8-wall@0,0|17]
+endPoint=305,263
+startPoint=276,308
+
+[8-wall@0,0|18]
+endPoint=276,308
+startPoint=275,337
+
+[8-wall@0,0|19]
+endPoint=275,337
+startPoint=285,369
+
+[8-wall@0,0|20]
+endPoint=285,369
+startPoint=310,382
+
+[8-wall@0,0|21]
+endPoint=310,382
+startPoint=346,377
+
+[8-wall@0,0|22]
+endPoint=346,377
+startPoint=377,350
+
+[8-wall@0,0|23]
+endPoint=377,350
+startPoint=382,302
+
+[8-wall@0,0|24]
+endPoint=382,302
+startPoint=382,136
+
+[8-wall@0,0|25]
+endPoint=382,136
+startPoint=338,61
+
+[8-wall@0,0|26]
+endPoint=338,61
+startPoint=243,30
+
+[8-wall@0,0|27]
+endPoint=243,30
+startPoint=99,43
+
+[8-wall@0,0|28]
+endPoint=99,43
+startPoint=35,104
+
+[8-wall@0,0|29]
+endPoint=35,104
+startPoint=18,230
+
+[8-wall@0,0|3]
+endPoint=147,351
+startPoint=83,329
+
+[8-wall@0,0|30]
+endPoint=18,230
+startPoint=32,285
+
+[8-wall@0,0|31]
+endPoint=32,285
+startPoint=83,329
+
+[8-wall@0,0|4]
+endPoint=147,351
+startPoint=230,330
+
+[8-wall@0,0|5]
+endPoint=230,330
+startPoint=271,265
+
+[8-wall@0,0|6]
+endPoint=271,265
+startPoint=247,193
+
+[8-wall@0,0|7]
+endPoint=247,193
+startPoint=202,157
+
+[8-wall@0,0|8]
+endPoint=202,157
+startPoint=138,165
+
+[8-wall@0,0|9]
+endPoint=138,165
+startPoint=104,210
+
+[9-ball@342,333]
+dummykey=true
+
+[9-blackhole@73,307|3]
+exit=385,46
+exitDeg=180
+maxspeed=6
+minspeed=1
+
+[9-cup@314,199|9]
+dummykey=true
+
+[9-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[9-sand@155,305|6]
+changeEnabled=false
+changeEvery=50
+height=70
+width=48
+
+[9-sand@165,309|7]
+changeEnabled=false
+changeEvery=50
+height=40
+width=34
+
+[9-sand@174,305|8]
+changeEnabled=false
+changeEvery=50
+height=28
+width=44
+
+[9-sand@179,290|5]
+changeEnabled=false
+changeEvery=50
+height=52
+width=92
+
+[9-wall@0,0|10]
+endPoint=386,143
+startPoint=140,120
+
+[9-wall@0,0|11]
+endPoint=106,12
+startPoint=10,50
+
+[9-wall@0,0|4]
+endPoint=388,253
+startPoint=12,223
diff --git a/kolf/courses/Hard.kolf b/kolf/courses/Hard.kolf
new file mode 100644
index 00000000..47ba23bf
--- /dev/null
+++ b/kolf/courses/Hard.kolf
@@ -0,0 +1,4403 @@
+[0-course@-50,-50]
+Name=Hard Course
+Name[af]=Hard Natuurlik
+Name[bg]=Трудно
+Name[bn]=কঠিন খেলা
+Name[bs]=Težak teren
+Name[ca]=Camp difícil
+Name[cs]=Těžký
+Name[da]=Svær bane
+Name[de]=Schwer
+Name[el]=Δύσκολη πίστα
+Name[es]=Campo difícil
+Name[et]=Raske väljak
+Name[fi]=Vaikea kenttä
+Name[fr]=Parcours difficile
+Name[ga]=Cúrsa Crua
+Name[gl]=Campo difícil
+Name[he]=מסלול קשה
+Name[hi]=कठिन कोर्स
+Name[hu]=Nehéz pálya
+Name[is]=Erfið braut
+Name[it]=Percorso difficile
+Name[ja]=難しいコース
+Name[mk]=Тежок терен
+Name[nb]=Vanskelig bane
+Name[nl]=Moeilijk parcours
+Name[nn]=Vanskeleg bane
+Name[nso]=Course ye Bothata
+Name[pl]=Trudny tor
+Name[pt]=Percurso Difícil
+Name[pt_BR]=Curso Difícil
+Name[ro]=Cale grea
+Name[ru]=Сложная площадка
+Name[sk]=Ťažké ihrisko
+Name[sl]=Težko igrišče
+Name[sr]=Тежак терен
+Name[sr@Latn]=Težak teren
+Name[sv]=Svår bana
+Name[ta]=கடினமான மார்க்கம்
+Name[tg]=Майдони Душвор
+Name[tr]=Zor Pist
+Name[uk]=Складна майданчик
+Name[ven]=Thero i Kondaho
+Name[xh]=Indlela enzima
+Name[xx]=xxHard Coursexx
+Name[zh_CN]=困难的路线
+Name[zh_TW]=困難的路線
+Name[zu]=Indlela elukhuni
+author=Jason Katz-Brown
+
+[1-ball@84,73]
+dummykey=true
+
+[1-blackhole@83,203|10]
+exit=111,214
+exitDeg=330
+maxspeed=5
+minspeed=3
+
+[1-cup@40,198|5]
+dummykey=true
+
+[1-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[1-puddle@-24,361|16]
+changeEnabled=false
+changeEvery=50
+height=124
+width=168
+
+[1-puddle@1,336|17]
+changeEnabled=false
+changeEvery=50
+height=60
+width=110
+
+[1-puddle@34,377|22]
+changeEnabled=false
+changeEvery=50
+height=54
+width=90
+
+[1-puddle@42,351|18]
+changeEnabled=false
+changeEvery=50
+height=56
+width=40
+
+[1-puddle@51,352|19]
+changeEnabled=false
+changeEvery=50
+height=32
+width=36
+
+[1-puddle@54,363|20]
+changeEnabled=false
+changeEvery=50
+height=40
+width=46
+
+[1-puddle@62,373|21]
+changeEnabled=false
+changeEvery=50
+height=52
+width=36
+
+[1-sand@169,250|21]
+changeEnabled=false
+changeEvery=50
+height=60
+width=46
+
+[1-sand@174,240|23]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[1-slope@10,135|15]
+grade=3
+gradient=Vertical
+height=40
+reversed=true
+stuckOnGround=false
+width=119
+
+[1-slope@10,229|6]
+grade=3
+gradient=Vertical
+height=60
+reversed=false
+stuckOnGround=false
+width=83
+
+[1-slope@221,145|17]
+grade=4
+gradient=Elliptic
+height=77
+reversed=false
+stuckOnGround=false
+width=77
+
+[1-slope@56,177|18]
+grade=4
+gradient=Elliptic
+height=52
+reversed=true
+stuckOnGround=false
+width=52
+
+[1-slope@65,78|24]
+grade=2
+gradient=Diagonal
+height=309
+reversed=false
+stuckOnGround=false
+width=323
+
+[1-slope@8,14|14]
+grade=2
+gradient=Opposite Diagonal
+height=105
+reversed=true
+stuckOnGround=false
+width=383
+
+[1-wall@0,0|17]
+endPoint=129,176
+startPoint=129,137
+
+[1-wall@0,0|24]
+endPoint=94,287
+startPoint=93,234
+
+[1-wall@0,0|3]
+endPoint=93,234
+startPoint=129,176
+
+[1-wall@0,0|4]
+endPoint=129,137
+startPoint=14,137
+
+[10-ball@308,365]
+dummykey=true
+
+[10-cup@66,343|3]
+dummykey=true
+
+[10-floater@171,129|54]
+botWallVisible=false
+endPoint=171,129
+height=53
+leftWallVisible=true
+rightWallVisible=false
+speed=3
+startPoint=25,212
+topWallVisible=true
+width=76
+
+[10-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=true
+maxstrokes=10
+par=3
+
+[10-puddle@111,207|40]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[10-puddle@115,231|25]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[10-puddle@136,250|28]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[10-puddle@139,193|58]
+changeEnabled=false
+changeEvery=50
+height=162
+width=122
+
+[10-puddle@163,123|12]
+changeEnabled=false
+changeEvery=50
+height=40
+width=104
+
+[10-puddle@164,260|36]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[10-puddle@188,144|52]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[10-puddle@190,268|34]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[10-puddle@196,261|35]
+changeEnabled=false
+changeEvery=50
+height=64
+width=60
+
+[10-puddle@199,237|37]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[10-puddle@200,157|53]
+changeEnabled=false
+changeEvery=50
+height=94
+width=72
+
+[10-puddle@200,200|54]
+changeEnabled=false
+changeEvery=50
+height=134
+width=72
+
+[10-puddle@206,276|39]
+changeEnabled=false
+changeEvery=50
+height=48
+width=36
+
+[10-slope@112,365|52]
+grade=2
+gradient=Vertical
+height=19
+reversed=true
+stuckOnGround=false
+width=98
+
+[10-slope@12,271|46]
+grade=2
+gradient=Diagonal
+height=45
+reversed=false
+stuckOnGround=true
+width=20
+
+[10-slope@12,315|53]
+grade=2
+gradient=Horizontal
+height=52
+reversed=false
+stuckOnGround=true
+width=21
+
+[10-slope@134,308|48]
+grade=2
+gradient=Vertical
+height=23
+reversed=false
+stuckOnGround=true
+width=74
+
+[10-slope@149,30|31]
+grade=3
+gradient=Horizontal
+height=85
+reversed=true
+stuckOnGround=true
+width=52
+
+[10-slope@18,32|60]
+grade=3
+gradient=Diagonal
+height=59
+reversed=true
+stuckOnGround=false
+width=138
+
+[10-slope@199,133|59]
+grade=4
+gradient=Horizontal
+height=69
+reversed=true
+stuckOnGround=true
+width=52
+
+[10-slope@203,87|60]
+grade=4
+gradient=Opposite Diagonal
+height=58
+reversed=false
+stuckOnGround=true
+width=48
+
+[10-slope@207,306|49]
+grade=2
+gradient=Opposite Diagonal
+height=28
+reversed=false
+stuckOnGround=false
+width=29
+
+[10-slope@209,331|50]
+grade=2
+gradient=Horizontal
+height=32
+reversed=true
+stuckOnGround=false
+width=28
+
+[10-slope@209,363|51]
+grade=2
+gradient=Diagonal
+height=19
+reversed=true
+stuckOnGround=false
+width=29
+
+[10-slope@211,199|61]
+grade=4
+gradient=Diagonal
+height=40
+reversed=true
+stuckOnGround=true
+width=40
+
+[10-slope@250,140|60]
+grade=1
+gradient=Horizontal
+height=243
+reversed=false
+stuckOnGround=false
+width=136
+
+[10-slope@264,102|61]
+grade=1
+gradient=Diagonal
+height=39
+reversed=false
+stuckOnGround=true
+width=124
+
+[10-slope@32,275|44]
+grade=2
+gradient=Vertical
+height=41
+reversed=false
+stuckOnGround=true
+width=59
+
+[10-slope@89,276|45]
+grade=2
+gradient=Opposite Diagonal
+height=55
+reversed=false
+stuckOnGround=true
+width=75
+
+[10-slope@9,365|47]
+grade=2
+gradient=Opposite Diagonal
+height=23
+reversed=true
+stuckOnGround=false
+width=104
+
+[10-wall@0,0|10]
+endPoint=77,31
+startPoint=29,60
+
+[10-wall@0,0|11]
+endPoint=29,60
+startPoint=11,187
+
+[10-wall@0,0|4]
+endPoint=229,252
+startPoint=288,385
+
+[10-wall@0,0|5]
+endPoint=232,206
+startPoint=229,252
+
+[10-wall@0,0|57]
+endPoint=338,159
+startPoint=302,131
+
+[10-wall@0,0|6]
+endPoint=389,148
+startPoint=355,92
+
+[10-wall@0,0|62]
+endPoint=6,316
+startPoint=11,187
+
+[10-wall@0,0|63]
+endPoint=8,374
+startPoint=6,316
+
+[10-wall@0,0|64]
+endPoint=35,387
+startPoint=8,374
+
+[10-wall@0,0|65]
+endPoint=70,392
+startPoint=35,387
+
+[10-wall@0,0|66]
+endPoint=246,397
+startPoint=70,392
+
+[10-wall@0,0|67]
+endPoint=275,355
+startPoint=246,397
+
+[10-wall@0,0|68]
+endPoint=387,384
+startPoint=288,385
+
+[10-wall@0,0|69]
+endPoint=389,148
+startPoint=387,384
+
+[10-wall@0,0|7]
+endPoint=355,92
+startPoint=274,56
+
+[10-wall@0,0|8]
+endPoint=274,56
+startPoint=179,31
+
+[10-wall@0,0|9]
+endPoint=179,31
+startPoint=77,31
+
+[11-ball@27,26]
+dummykey=true
+
+[11-blackhole@-23,90|4]
+exit=-26,293
+exitDeg=0
+maxspeed=2
+minspeed=1
+
+[11-blackhole@293,44|16]
+exit=132,260
+exitDeg=0
+maxspeed=3
+minspeed=2
+
+[11-cup@221,409|13]
+dummykey=true
+
+[11-cup@223,332|15]
+dummykey=true
+
+[11-floater@127,241|13]
+botWallVisible=false
+endPoint=127,241
+height=36
+leftWallVisible=true
+rightWallVisible=true
+speed=5
+startPoint=13,235
+topWallVisible=true
+width=260
+
+[11-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=true
+maxstrokes=10
+par=4
+
+[11-puddle@142,397|19]
+changeEnabled=false
+changeEvery=50
+height=94
+width=74
+
+[11-puddle@149,362|3]
+changeEnabled=false
+changeEvery=50
+height=30
+width=56
+
+[11-puddle@159,387|9]
+changeEnabled=false
+changeEvery=50
+height=72
+width=58
+
+[11-puddle@170,398|18]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[11-puddle@173,382|20]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[11-puddle@192,390|10]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[11-puddle@210,393|44]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[11-puddle@231,394|12]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[11-puddle@258,388|11]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[11-puddle@272,380|45]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[11-puddle@284,376|8]
+changeEnabled=false
+changeEvery=50
+height=46
+width=46
+
+[11-puddle@290,401|7]
+changeEnabled=false
+changeEvery=50
+height=88
+width=60
+
+[11-puddle@293,-12|40]
+changeEnabled=false
+changeEvery=50
+height=50
+width=82
+
+[11-puddle@332,7|39]
+changeEnabled=false
+changeEvery=50
+height=32
+width=82
+
+[11-puddle@335,10|38]
+changeEnabled=false
+changeEvery=50
+height=34
+width=50
+
+[11-puddle@349,12|37]
+changeEnabled=false
+changeEvery=50
+height=50
+width=56
+
+[11-puddle@353,33|34]
+changeEnabled=false
+changeEvery=50
+height=34
+width=36
+
+[11-puddle@356,12|33]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[11-puddle@361,32|39]
+changeEnabled=false
+changeEvery=50
+height=58
+width=48
+
+[11-puddle@388,45|36]
+changeEnabled=false
+changeEvery=50
+height=48
+width=88
+
+[11-puddle@391,10|35]
+changeEnabled=false
+changeEvery=50
+height=74
+width=102
+
+[11-sand@111,152|26]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[11-sand@113,140|23]
+changeEnabled=false
+changeEvery=50
+height=80
+width=80
+
+[11-sand@120,160|25]
+changeEnabled=false
+changeEvery=50
+height=56
+width=56
+
+[11-sand@122,120|24]
+changeEnabled=false
+changeEvery=50
+height=40
+width=70
+
+[11-sand@133,177|46]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[11-sand@141,155|27]
+changeEnabled=false
+changeEvery=50
+height=96
+width=56
+
+[11-slope@11,37|22]
+grade=2
+gradient=Opposite Diagonal
+height=166
+reversed=false
+stuckOnGround=false
+width=178
+
+[11-slope@13,217|41]
+grade=4
+gradient=Vertical
+height=22
+reversed=false
+stuckOnGround=true
+width=376
+
+[11-slope@168,280|40]
+grade=4
+gradient=Elliptic
+height=107
+reversed=true
+stuckOnGround=false
+width=107
+
+[11-slope@343,53|44]
+grade=3
+gradient=Diagonal
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[11-slope@61,13|30]
+grade=2
+gradient=Vertical
+height=79
+reversed=true
+stuckOnGround=true
+width=286
+
+[11-slope@7,271|42]
+grade=4
+gradient=Vertical
+height=29
+reversed=true
+stuckOnGround=true
+width=382
+
+[11-slope@73,128|28]
+grade=2
+gradient=Vertical
+height=77
+reversed=false
+stuckOnGround=true
+width=314
+
+[11-slope@9,11|43]
+grade=6
+gradient=Diagonal
+height=56
+reversed=true
+stuckOnGround=false
+width=52
+
+[11-slope@9,8|32]
+grade=2
+gradient=Diagonal
+height=140
+reversed=true
+stuckOnGround=false
+width=116
+
+[11-wall@0,0|14]
+endPoint=392,207
+startPoint=12,207
+
+[11-wall@0,0|17]
+endPoint=253,280
+startPoint=10,278
+
+[11-wall@0,0|42]
+endPoint=199,63
+startPoint=209,113
+
+[12-ball@58,341]
+dummykey=true
+
+[12-blackhole@244,110|62]
+exit=166,91
+exitDeg=280
+maxspeed=3
+minspeed=3
+
+[12-blackhole@359,23|65]
+exit=111,236
+exitDeg=20
+maxspeed=5
+minspeed=3
+
+[12-blackhole@46,112|41]
+exit=144,33
+exitDeg=350
+maxspeed=5
+minspeed=3
+
+[12-cup@177,164|16]
+dummykey=true
+
+[12-cup@212,294|38]
+dummykey=true
+
+[12-cup@229,270|40]
+dummykey=true
+
+[12-cup@230,315|39]
+dummykey=true
+
+[12-cup@256,319|37]
+dummykey=true
+
+[12-floater@329,91|50]
+botWallVisible=false
+endPoint=329,91
+height=46
+leftWallVisible=false
+rightWallVisible=false
+speed=5
+startPoint=277,35
+topWallVisible=false
+width=52
+
+[12-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=true
+maxstrokes=10
+par=3
+
+[12-puddle@101,37|66]
+changeEnabled=false
+changeEvery=50
+height=96
+width=76
+
+[12-puddle@104,63|28]
+changeEnabled=false
+changeEvery=50
+height=40
+width=54
+
+[12-puddle@114,15|25]
+changeEnabled=false
+changeEvery=50
+height=98
+width=60
+
+[12-puddle@62,43|24]
+changeEnabled=false
+changeEvery=50
+height=44
+width=54
+
+[12-puddle@68,-60|62]
+changeEnabled=false
+changeEvery=50
+height=244
+width=150
+
+[12-puddle@85,65|26]
+changeEnabled=false
+changeEvery=50
+height=34
+width=42
+
+[12-puddle@91,43|21]
+changeEnabled=false
+changeEvery=50
+height=72
+width=100
+
+[12-puddle@95,72|27]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[12-slope@238,231|53]
+grade=3
+gradient=Vertical
+height=11
+reversed=false
+stuckOnGround=false
+width=58
+
+[12-slope@238,242|36]
+grade=3
+gradient=Vertical
+height=41
+reversed=true
+stuckOnGround=false
+width=51
+
+[12-slope@262,236|49]
+grade=6
+gradient=Diagonal
+height=151
+reversed=false
+stuckOnGround=false
+width=126
+
+[12-slope@7,74|67]
+grade=4
+gradient=Elliptic
+height=78
+reversed=true
+stuckOnGround=false
+width=78
+
+[12-slope@86,130|17]
+grade=4
+gradient=Diagonal
+height=69
+reversed=true
+stuckOnGround=false
+width=63
+
+[12-slope@92,94|52]
+grade=4
+gradient=Horizontal
+height=38
+reversed=true
+stuckOnGround=false
+width=56
+
+[12-wall@0,0|10]
+endPoint=21,360
+startPoint=35,379
+
+[12-wall@0,0|12]
+endPoint=274,328
+startPoint=288,292
+
+[12-wall@0,0|13]
+endPoint=81,161
+startPoint=96,93
+
+[12-wall@0,0|14]
+endPoint=81,161
+startPoint=136,332
+
+[12-wall@0,0|15]
+endPoint=20,305
+startPoint=13,332
+
+[12-wall@0,0|20]
+endPoint=282,74
+startPoint=96,93
+
+[12-wall@0,0|30]
+endPoint=252,339
+startPoint=218,337
+
+[12-wall@0,0|31]
+endPoint=218,337
+startPoint=196,320
+
+[12-wall@0,0|32]
+endPoint=274,328
+startPoint=252,339
+
+[12-wall@0,0|33]
+endPoint=196,320
+startPoint=189,292
+
+[12-wall@0,0|34]
+endPoint=196,264
+startPoint=189,292
+
+[12-wall@0,0|35]
+endPoint=236,251
+startPoint=196,264
+
+[12-wall@0,0|4]
+endPoint=13,332
+startPoint=21,360
+
+[12-wall@0,0|42]
+endPoint=316,78
+startPoint=282,74
+
+[12-wall@0,0|43]
+endPoint=337,57
+startPoint=318,79
+
+[12-wall@0,0|44]
+endPoint=339,30
+startPoint=337,57
+
+[12-wall@0,0|45]
+endPoint=333,14
+startPoint=339,30
+
+[12-wall@0,0|46]
+endPoint=316,78
+startPoint=310,111
+
+[12-wall@0,0|47]
+endPoint=365,51
+startPoint=337,57
+
+[12-wall@0,0|48]
+endPoint=38,285
+startPoint=20,305
+
+[12-wall@0,0|49]
+endPoint=387,54
+startPoint=365,51
+
+[12-wall@0,0|5]
+endPoint=71,383
+startPoint=35,379
+
+[12-wall@0,0|51]
+endPoint=307,131
+startPoint=284,137
+
+[12-wall@0,0|54]
+endPoint=136,332
+startPoint=133,354
+
+[12-wall@0,0|55]
+endPoint=56,280
+startPoint=38,285
+
+[12-wall@0,0|56]
+endPoint=284,137
+startPoint=264,134
+
+[12-wall@0,0|57]
+endPoint=264,134
+startPoint=241,139
+
+[12-wall@0,0|58]
+endPoint=241,139
+startPoint=221,129
+
+[12-wall@0,0|59]
+endPoint=221,129
+startPoint=212,115
+
+[12-wall@0,0|6]
+endPoint=104,374
+startPoint=71,383
+
+[12-wall@0,0|60]
+endPoint=212,115
+startPoint=194,107
+
+[12-wall@0,0|61]
+endPoint=194,107
+startPoint=186,86
+
+[12-wall@0,0|63]
+endPoint=147,372
+startPoint=134,357
+
+[12-wall@0,0|64]
+endPoint=186,388
+startPoint=147,372
+
+[12-wall@0,0|68]
+endPoint=236,251
+startPoint=238,230
+
+[12-wall@0,0|7]
+endPoint=288,292
+startPoint=310,111
+
+[12-wall@0,0|8]
+endPoint=133,354
+startPoint=104,374
+
+[12-wall@0,0|9]
+endPoint=265,189
+startPoint=154,189
+
+[13-ball@38,51]
+dummykey=true
+
+[13-cup@360,359|20]
+dummykey=true
+
+[13-floater@68,306|7]
+botWallVisible=true
+endPoint=68,306
+height=11
+leftWallVisible=false
+rightWallVisible=false
+speed=9
+startPoint=133,75
+topWallVisible=false
+width=50
+
+[13-floater@83,372|16]
+botWallVisible=false
+endPoint=83,372
+height=47
+leftWallVisible=true
+rightWallVisible=false
+speed=4
+startPoint=344,371
+topWallVisible=true
+width=14
+
+[13-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=true
+maxstrokes=10
+par=3
+
+[13-sign@257,44|26]
+Comment=Let the floaters <strong>push</strong> you!
+Comment[bg]=Обръщане на грешките във Ваша полза!
+Comment[bn]=ফ্লোটারগুলো আপনাকে <strong>ধাক্কা</strong> দিক!
+Comment[bs]=Dopustite plutačima da vas <strong>guraju</strong>!
+Comment[ca]=Permetre que els flotadors t'<strong>empenyin</strong>!
+Comment[da]=Lad svæverne <strong>skubbe</strong> dig!
+Comment[de]=Lassen Sie sich von den Flößen <strong>weiterschubsen</strong>.
+Comment[es]=¡Dejar que los flotadores le <strong>empujen</strong>!
+Comment[et]=Lase hõljukitel ennast <strong>lükata</strong> !
+Comment[fi]=Anna kelluvien <strong>painaa</strong> sinua!
+Comment[fr]=Laissez les flotteurs vous <strong>pousser</strong> !
+Comment[gl]=Deixe que os flotadores o <strong>empuxen</strong>!
+Comment[he]=תן למשטחים הצפים <strong>לדחוף</strong> אותך!
+Comment[hi]=तैरने वाले आपको <strong>धकेल</strong> सकते हैं!
+Comment[hu]=Csak a legjobbaknak!
+Comment[is]=Láttu flotin <strong>ýta</strong> þér!
+Comment[it]=Lasciati <strong>trasportare</strong> dai floater!
+Comment[ja]=フローターでボールを<strong>プッシュ</strong>させよう!
+Comment[mk]=Дозволете им на лебдачите да ве <strong>турнат</strong>!
+Comment[nb]=La plattformene <strong>dytte</strong> deg!
+Comment[nl]=Laat u door de drijvers <strong>duwen</strong>.
+Comment[nn]=Lat plattformane <strong>dytta</strong> deg!
+Comment[nso]=Dumelela di-floater <strong>digo tshofe</strong>!
+Comment[pl]=Niech ta piłeczka <strong>leci</strong>!
+Comment[pt]=Deixe os flutuadores <strong>empurrá-lo</strong>!
+Comment[pt_BR]=Deixe as bóias <strong>levarem</strong> você!
+Comment[ru]=Пусть вас <strong>несут</strong> ползуны!
+Comment[sk]=Nechajte sa unášať!
+Comment[sl]=Naj vas plovci <strong>porinejo</strong>!
+Comment[sr]=Нека вас плутачи <strong>гурају</strong>!
+Comment[sr@Latn]=Neka vas plutači <strong>guraju</strong>!
+Comment[sv]=Låt flottarna <strong>knuffa</strong> dig!
+Comment[ta]=மிதவைகள் உங்களை<strong>தள்ளட்டும்</strong> !
+Comment[tg]=Иҷозат диҳед, ки хазандаҳо шуморо <strong>тела диҳанд</strong>!
+Comment[tr]=Yüzücülerin size <strong>vurmasına</strong> izin verin!
+Comment[uk]=Нехай плавуни вас <strong>підштовхнуть</strong>!
+Comment[ven]=Irani zwipapamali<strong>sukumedza</strong>inwi!
+Comment[xh]=Vumela ezidadayo <strong>tyhala</strong> wena!
+Comment[xx]=xxLet the floaters <strong>push</strong> you!xx
+Comment[zh_CN]=让浮台<strong>托起</strong>您!
+Comment[zh_TW]=讓浮標<strong>推動</strong>您!
+Comment[zu]=Yekela okuntantayo <strong>kukududule</strong>!
+botWallVisible=true
+height=196
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=111
+
+[13-slope@10,10|3]
+grade=6
+gradient=Vertical
+height=137
+reversed=true
+stuckOnGround=true
+width=227
+
+[13-slope@10,378|0]
+grade=3
+gradient=Vertical
+height=9
+reversed=false
+stuckOnGround=true
+width=209
+
+[13-slope@11,363|1]
+grade=3
+gradient=Vertical
+height=17
+reversed=false
+stuckOnGround=true
+width=59
+
+[13-slope@111,102|4]
+grade=3
+gradient=Vertical
+height=10
+reversed=false
+stuckOnGround=true
+width=27
+
+[13-slope@12,147|16]
+grade=5
+gradient=Vertical
+height=131
+reversed=true
+stuckOnGround=true
+width=227
+
+[13-slope@136,102|6]
+grade=4
+gradient=Vertical
+height=6
+reversed=false
+stuckOnGround=true
+width=17
+
+[13-slope@183,277|25]
+grade=5
+gradient=Vertical
+height=111
+reversed=true
+stuckOnGround=true
+width=206
+
+[13-slope@218,378|2]
+grade=3
+gradient=Vertical
+height=10
+reversed=false
+stuckOnGround=true
+width=105
+
+[13-slope@332,330|19]
+grade=6
+gradient=Elliptic
+height=59
+reversed=true
+stuckOnGround=false
+width=59
+
+[13-slope@69,307|8]
+grade=8
+gradient=Vertical
+height=8
+reversed=false
+stuckOnGround=false
+width=46
+
+[13-slope@82,373|13]
+grade=8
+gradient=Horizontal
+height=18
+reversed=true
+stuckOnGround=false
+width=16
+
+[13-slope@9,277|17]
+grade=5
+gradient=Vertical
+height=100
+reversed=true
+stuckOnGround=true
+width=175
+
+[13-wall@0,0|10]
+endPoint=124,297
+startPoint=65,372
+
+[13-wall@0,0|11]
+endPoint=13,364
+startPoint=36,377
+
+[13-wall@0,0|12]
+endPoint=238,12
+startPoint=236,276
+
+[13-wall@0,0|14]
+endPoint=113,114
+startPoint=96,12
+
+[13-wall@0,0|16]
+endPoint=322,389
+startPoint=331,343
+
+[13-wall@0,0|17]
+endPoint=331,343
+startPoint=353,326
+
+[13-wall@0,0|18]
+endPoint=353,326
+startPoint=390,326
+
+[13-wall@0,0|21]
+endPoint=388,276
+startPoint=236,276
+
+[13-wall@0,0|23]
+endPoint=159,103
+startPoint=173,108
+
+[13-wall@0,0|24]
+endPoint=113,114
+startPoint=159,103
+
+[13-wall@0,0|5]
+endPoint=173,108
+startPoint=124,297
+
+[13-wall@0,0|9]
+endPoint=65,372
+startPoint=36,377
+
+[14-ball@360,259]
+dummykey=true
+
+[14-blackhole@87,369|7]
+exit=366,49
+exitDeg=50
+maxspeed=2
+minspeed=1
+
+[14-cup@315,106|10]
+dummykey=true
+
+[14-cup@87,276|6]
+dummykey=true
+
+[14-floater@289,36|9]
+botWallVisible=true
+endPoint=289,36
+height=97
+leftWallVisible=true
+rightWallVisible=true
+speed=2
+startPoint=250,24
+topWallVisible=true
+width=100
+
+[14-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=true
+maxstrokes=10
+par=2
+
+[14-slope@10,362|8]
+grade=4
+gradient=Horizontal
+height=14
+reversed=true
+stuckOnGround=false
+width=83
+
+[14-slope@11,10|4]
+grade=2
+gradient=Vertical
+height=338
+reversed=true
+stuckOnGround=true
+width=163
+
+[14-slope@11,352|11]
+grade=4
+gradient=Opposite Diagonal
+height=11
+reversed=false
+stuckOnGround=false
+width=81
+
+[14-slope@11,374|12]
+grade=4
+gradient=Diagonal
+height=12
+reversed=true
+stuckOnGround=false
+width=83
+
+[14-slope@174,10|12]
+grade=2
+gradient=Diagonal
+height=339
+reversed=true
+stuckOnGround=true
+width=63
+
+[14-slope@289,50|11]
+grade=2
+gradient=Vertical
+height=83
+reversed=false
+stuckOnGround=false
+width=101
+
+[14-slope@290,78|18]
+grade=4
+gradient=Elliptic
+height=53
+reversed=true
+stuckOnGround=false
+width=53
+
+[14-wall@0,0|13]
+endPoint=203,205
+startPoint=181,353
+
+[14-wall@0,0|14]
+endPoint=170,362
+startPoint=115,361
+
+[14-wall@0,0|15]
+endPoint=105,365
+startPoint=115,361
+
+[14-wall@0,0|16]
+endPoint=98,387
+startPoint=105,365
+
+[14-wall@0,0|17]
+endPoint=181,353
+startPoint=170,362
+
+[15-ball@20,380]
+dummykey=true
+
+[15-blackhole@172,240|6]
+exit=316,255
+exitDeg=42
+maxspeed=1
+minspeed=0
+
+[15-blackhole@228,90|22]
+exit=345,328
+exitDeg=10
+maxspeed=5
+minspeed=3
+
+[15-blackhole@242,55|24]
+exit=24,53
+exitDeg=10
+maxspeed=5
+minspeed=3
+
+[15-blackhole@330,369|8]
+exit=353,363
+exitDeg=10
+maxspeed=5
+minspeed=3
+
+[15-blackhole@375,215|46]
+exit=16,128
+exitDeg=0
+maxspeed=2
+minspeed=1
+
+[15-blackhole@61,244|7]
+exit=21,70
+exitDeg=10
+maxspeed=5
+minspeed=3
+
+[15-bridge@206,351|38]
+botWallVisible=false
+height=29
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=51
+
+[15-bridge@233,337|36]
+botWallVisible=false
+height=28
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=53
+
+[15-bridge@52,20|13]
+botWallVisible=true
+height=79
+leftWallVisible=false
+rightWallVisible=true
+topWallVisible=true
+width=153
+
+[15-cup@182,78|14]
+dummykey=true
+
+[15-cup@373,269|27]
+dummykey=true
+
+[15-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[15-puddle@247,365|35]
+changeEnabled=false
+changeEvery=50
+height=88
+width=58
+
+[15-puddle@252,383|37]
+changeEnabled=false
+changeEvery=50
+height=92
+width=68
+
+[15-slope@10,311|45]
+grade=6
+gradient=Opposite Diagonal
+height=76
+reversed=false
+stuckOnGround=false
+width=84
+
+[15-slope@107,329|31]
+grade=7
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[15-slope@116,285|30]
+grade=4
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[15-slope@117,178|38]
+grade=4
+gradient=Elliptic
+height=116
+reversed=true
+stuckOnGround=false
+width=116
+
+[15-slope@14,120|37]
+grade=4
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[15-slope@14,190|43]
+grade=4
+gradient=Elliptic
+height=98
+reversed=true
+stuckOnGround=false
+width=98
+
+[15-slope@147,135|35]
+grade=4
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[15-slope@149,339|45]
+grade=6
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[15-slope@168,296|34]
+grade=4
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[15-slope@218,265|44]
+grade=4
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[15-slope@221,182|29]
+grade=4
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[15-slope@240,238|41]
+grade=4
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[15-slope@26,293|36]
+grade=4
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[15-slope@283,9|33]
+grade=4
+gradient=Opposite Diagonal
+height=96
+reversed=true
+stuckOnGround=false
+width=105
+
+[15-slope@339,275|26]
+grade=4
+gradient=Opposite Diagonal
+height=36
+reversed=false
+stuckOnGround=false
+width=39
+
+[15-slope@361,304|25]
+grade=4
+gradient=Vertical
+height=79
+reversed=false
+stuckOnGround=false
+width=23
+
+[15-slope@58,141|42]
+grade=4
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[15-slope@61,22|17]
+grade=4
+gradient=Horizontal
+height=75
+reversed=false
+stuckOnGround=false
+width=144
+
+[15-slope@67,343|40]
+grade=5
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[15-slope@8,363|46]
+grade=6
+gradient=Opposite Diagonal
+height=25
+reversed=false
+stuckOnGround=false
+width=25
+
+[15-slope@95,163|32]
+grade=4
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[15-wall@0,0|10]
+endPoint=257,172
+startPoint=295,241
+
+[15-wall@0,0|11]
+endPoint=256,173
+startPoint=201,137
+
+[15-wall@0,0|12]
+endPoint=201,137
+startPoint=12,110
+
+[15-wall@0,0|18]
+endPoint=295,241
+startPoint=331,283
+
+[15-wall@0,0|19]
+endPoint=363,242
+startPoint=330,281
+
+[15-wall@0,0|20]
+endPoint=382,237
+startPoint=363,242
+
+[15-wall@0,0|23]
+endPoint=273,135
+startPoint=257,172
+
+[15-wall@0,0|28]
+endPoint=382,237
+startPoint=390,249
+
+[15-wall@0,0|9]
+endPoint=331,283
+startPoint=351,387
+
+[15-windmill@273,10|21]
+botWallVisible=false
+bottom=true
+height=125
+leftWallVisible=false
+rightWallVisible=false
+speed=7
+topWallVisible=false
+width=115
+
+[16-ball@39,319]
+dummykey=true
+
+[16-blackhole@354,290|51]
+exit=319,39
+exitDeg=180
+maxspeed=4
+minspeed=3
+
+[16-cup@-48,-27|3]
+dummykey=true
+
+[16-cup@-48,-43|70]
+dummykey=true
+
+[16-cup@-50,-40|71]
+dummykey=true
+
+[16-cup@-51,-42|68]
+dummykey=true
+
+[16-cup@-52,-41|69]
+dummykey=true
+
+[16-cup@-57,-40|72]
+dummykey=true
+
+[16-cup@87,83|73]
+dummykey=true
+
+[16-floater@122,335|39]
+botWallVisible=true
+endPoint=122,335
+height=42
+leftWallVisible=false
+rightWallVisible=false
+speed=5
+startPoint=67,242
+topWallVisible=true
+width=165
+
+[16-floater@182,24|63]
+botWallVisible=true
+endPoint=182,24
+height=34
+leftWallVisible=false
+rightWallVisible=true
+speed=8
+startPoint=147,80
+topWallVisible=true
+width=141
+
+[16-floater@64,69|64]
+botWallVisible=false
+endPoint=64,69
+height=66
+leftWallVisible=true
+rightWallVisible=false
+speed=5
+startPoint=45,34
+topWallVisible=true
+width=73
+
+[16-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=true
+maxstrokes=10
+par=3
+
+[16-puddle@110,211|60]
+changeEnabled=false
+changeEvery=50
+height=60
+width=56
+
+[16-puddle@115,229|44]
+changeEnabled=false
+changeEvery=50
+height=70
+width=88
+
+[16-puddle@128,239|45]
+changeEnabled=false
+changeEvery=50
+height=62
+width=98
+
+[16-puddle@14,174|39]
+changeEnabled=false
+changeEvery=50
+height=64
+width=162
+
+[16-puddle@148,390|55]
+changeEnabled=false
+changeEvery=50
+height=94
+width=58
+
+[16-puddle@152,246|46]
+changeEnabled=false
+changeEvery=50
+height=60
+width=114
+
+[16-puddle@153,373|54]
+changeEnabled=false
+changeEvery=50
+height=86
+width=66
+
+[16-puddle@160,355|53]
+changeEnabled=false
+changeEvery=50
+height=96
+width=74
+
+[16-puddle@170,257|47]
+changeEnabled=false
+changeEvery=50
+height=70
+width=94
+
+[16-puddle@172,334|52]
+changeEnabled=false
+changeEvery=50
+height=94
+width=88
+
+[16-puddle@173,367|56]
+changeEnabled=false
+changeEvery=50
+height=82
+width=58
+
+[16-puddle@177,309|51]
+changeEnabled=false
+changeEvery=50
+height=72
+width=80
+
+[16-puddle@178,263|48]
+changeEnabled=false
+changeEvery=50
+height=72
+width=82
+
+[16-puddle@178,271|49]
+changeEnabled=false
+changeEvery=50
+height=94
+width=82
+
+[16-puddle@183,284|50]
+changeEnabled=false
+changeEvery=50
+height=122
+width=82
+
+[16-puddle@189,358|57]
+changeEnabled=false
+changeEvery=50
+height=54
+width=36
+
+[16-puddle@206,312|58]
+changeEnabled=false
+changeEvery=50
+height=70
+width=32
+
+[16-puddle@65,186|40]
+changeEnabled=false
+changeEvery=50
+height=68
+width=100
+
+[16-puddle@76,216|59]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[16-puddle@77,193|41]
+changeEnabled=false
+changeEvery=50
+height=74
+width=98
+
+[16-puddle@89,201|42]
+changeEnabled=false
+changeEvery=50
+height=76
+width=80
+
+[16-puddle@97,216|43]
+changeEnabled=false
+changeEvery=50
+height=72
+width=70
+
+[16-sand@192,162|51]
+changeEnabled=false
+changeEvery=50
+height=58
+width=78
+
+[16-sand@200,168|55]
+changeEnabled=false
+changeEvery=50
+height=62
+width=62
+
+[16-sand@205,172|56]
+changeEnabled=false
+changeEvery=50
+height=62
+width=52
+
+[16-sand@211,175|57]
+changeEnabled=false
+changeEvery=50
+height=60
+width=56
+
+[16-sand@220,192|58]
+changeEnabled=false
+changeEvery=50
+height=32
+width=56
+
+[16-sand@225,182|59]
+changeEnabled=false
+changeEvery=50
+height=54
+width=34
+
+[16-sand@235,186|51]
+changeEnabled=false
+changeEvery=50
+height=22
+width=20
+
+[16-sand@338,126|45]
+changeEnabled=false
+changeEvery=50
+height=62
+width=38
+
+[16-sand@348,108|43]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[16-sand@363,121|41]
+changeEnabled=false
+changeEvery=50
+height=82
+width=78
+
+[16-sand@366,95|50]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[16-sand@374,88|49]
+changeEnabled=false
+changeEvery=50
+height=40
+width=38
+
+[16-sand@379,142|46]
+changeEnabled=false
+changeEvery=50
+height=64
+width=108
+
+[16-sand@396,83|48]
+changeEnabled=false
+changeEvery=50
+height=72
+width=58
+
+[16-slope@293,269|52]
+grade=4
+gradient=Diagonal
+height=118
+reversed=false
+stuckOnGround=true
+width=93
+
+[16-wall@0,0|65]
+endPoint=345,264
+startPoint=251,389
+
+[16-wall@0,0|67]
+endPoint=389,271
+startPoint=345,264
+
+[17-ball@170,349]
+dummykey=true
+
+[17-blackhole@233,359|24]
+exit=288,38
+exitDeg=50
+maxspeed=3
+minspeed=1
+
+[17-bumper@248,22|51]
+dummykey=true
+
+[17-bumper@263,33|50]
+dummykey=true
+
+[17-bumper@278,45|52]
+dummykey=true
+
+[17-bumper@289,59|41]
+dummykey=true
+
+[17-bumper@297,75|49]
+dummykey=true
+
+[17-bumper@301,93|44]
+dummykey=true
+
+[17-bumper@302,111|45]
+dummykey=true
+
+[17-bumper@309,128|42]
+dummykey=true
+
+[17-bumper@318,144|43]
+dummykey=true
+
+[17-bumper@335,151|48]
+dummykey=true
+
+[17-bumper@355,154|43]
+dummykey=true
+
+[17-bumper@375,154|42]
+dummykey=true
+
+[17-cup@353,116|36]
+dummykey=true
+
+[17-cup@365,358|15]
+dummykey=true
+
+[17-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[17-puddle@243,204|45]
+changeEnabled=false
+changeEvery=50
+height=50
+width=52
+
+[17-puddle@253,194|44]
+changeEnabled=false
+changeEvery=50
+height=54
+width=66
+
+[17-puddle@254,183|46]
+changeEnabled=false
+changeEvery=50
+height=64
+width=58
+
+[17-puddle@262,213|47]
+changeEnabled=false
+changeEvery=50
+height=56
+width=88
+
+[17-sand@-19,263|16]
+changeEnabled=false
+changeEvery=50
+height=138
+width=94
+
+[17-sand@1,214|52]
+changeEnabled=false
+changeEvery=50
+height=82
+width=64
+
+[17-sand@102,219|56]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[17-sand@109,197|12]
+changeEnabled=false
+changeEvery=50
+height=44
+width=58
+
+[17-sand@115,216|4]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[17-sand@126,200|5]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[17-sand@136,201|29]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[17-sand@155,202|30]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[17-sand@160,189|32]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[17-sand@166,172|17]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[17-sand@167,162|3]
+changeEnabled=false
+changeEvery=50
+height=48
+width=44
+
+[17-sand@178,156|54]
+changeEnabled=false
+changeEvery=50
+height=36
+width=52
+
+[17-sand@187,166|55]
+changeEnabled=false
+changeEvery=50
+height=48
+width=60
+
+[17-sand@193,175|31]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[17-sand@5,238|8]
+changeEnabled=false
+changeEvery=50
+height=98
+width=64
+
+[17-sand@85,225|34]
+changeEnabled=false
+changeEvery=50
+height=60
+width=34
+
+[17-sand@91,232|9]
+changeEnabled=false
+changeEvery=50
+height=28
+width=26
+
+[17-sand@92,208|6]
+changeEnabled=false
+changeEvery=50
+height=46
+width=38
+
+[17-slope@103,219|40]
+grade=2
+gradient=Elliptic
+height=108
+reversed=false
+stuckOnGround=false
+width=108
+
+[17-slope@11,8|42]
+grade=4
+gradient=Diagonal
+height=160
+reversed=true
+stuckOnGround=false
+width=124
+
+[17-slope@204,241|25]
+grade=2
+gradient=Opposite Diagonal
+height=268
+reversed=true
+stuckOnGround=false
+width=181
+
+[17-slope@205,331|41]
+grade=4
+gradient=Elliptic
+height=57
+reversed=true
+stuckOnGround=false
+width=57
+
+[17-slope@210,240|38]
+grade=2
+gradient=Vertical
+height=103
+reversed=true
+stuckOnGround=false
+width=63
+
+[17-slope@238,332|40]
+grade=2
+gradient=Horizontal
+height=52
+reversed=false
+stuckOnGround=false
+width=66
+
+[17-slope@78,12|39]
+grade=3
+gradient=Vertical
+height=68
+reversed=true
+stuckOnGround=false
+width=308
+
+[17-slope@9,269|41]
+grade=4
+gradient=Elliptic
+height=109
+reversed=false
+stuckOnGround=false
+width=109
+
+[17-wall@0,0|18]
+endPoint=206,196
+startPoint=203,387
+
+[17-wall@0,0|19]
+endPoint=46,59
+startPoint=11,123
+
+[17-wall@0,0|23]
+endPoint=89,12
+startPoint=46,59
+
+[17-windmill@322,91|53]
+botWallVisible=false
+bottom=false
+height=43
+leftWallVisible=false
+rightWallVisible=false
+speed=5
+topWallVisible=false
+width=67
+
+[18-ball@174,371]
+dummykey=true
+
+[18-cup@340,103|3]
+dummykey=true
+
+[18-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[18-slope@308,71|19]
+grade=4
+gradient=Elliptic
+height=65
+reversed=true
+stuckOnGround=false
+width=65
+
+[18-wall@0,0|10]
+endPoint=388,174
+startPoint=250,151
+
+[18-wall@0,0|11]
+endPoint=324,257
+startPoint=320,270
+
+[18-wall@0,0|12]
+endPoint=342,336
+startPoint=339,348
+
+[18-wall@0,0|13]
+endPoint=228,98
+startPoint=239,90
+
+[18-wall@0,0|14]
+endPoint=15,367
+startPoint=12,143
+
+[18-wall@0,0|15]
+endPoint=219,390
+startPoint=36,391
+
+[18-wall@0,0|16]
+endPoint=36,391
+startPoint=15,367
+
+[18-wall@0,0|17]
+endPoint=388,12
+startPoint=275,11
+
+[18-wall@0,0|18]
+endPoint=388,12
+startPoint=390,212
+
+[18-wall@0,0|4]
+endPoint=238,238
+startPoint=75,312
+
+[18-wall@0,0|5]
+endPoint=228,98
+startPoint=12,143
+
+[18-wall@0,0|6]
+endPoint=275,11
+startPoint=239,90
+
+[18-wall@0,0|7]
+endPoint=390,212
+startPoint=324,257
+
+[18-wall@0,0|8]
+endPoint=320,270
+startPoint=342,336
+
+[18-wall@0,0|9]
+endPoint=339,348
+startPoint=219,390
+
+[2-ball@112,41]
+dummykey=true
+
+[2-blackhole@142,61|20]
+exit=261,80
+exitDeg=305
+maxspeed=4
+minspeed=3
+
+[2-blackhole@178,367|17]
+exit=17,31
+exitDeg=349
+maxspeed=2
+minspeed=2
+
+[2-blackhole@28,148|15]
+exit=238,26
+exitDeg=0
+maxspeed=5
+minspeed=1
+
+[2-blackhole@374,89|7]
+exit=308,84
+exitDeg=244
+maxspeed=3
+minspeed=2
+
+[2-cup@23,188|21]
+dummykey=true
+
+[2-cup@375,52|6]
+dummykey=true
+
+[2-floater@43,240|24]
+botWallVisible=false
+endPoint=43,240
+height=64
+leftWallVisible=false
+rightWallVisible=true
+speed=5
+startPoint=33,173
+topWallVisible=true
+width=81
+
+[2-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=true
+maxstrokes=10
+par=2
+
+[2-puddle@10,241|50]
+changeEnabled=false
+changeEvery=50
+height=42
+width=142
+
+[2-puddle@48,248|29]
+changeEnabled=false
+changeEvery=50
+height=28
+width=66
+
+[2-puddle@57,233|51]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[2-puddle@60,244|31]
+changeEnabled=false
+changeEvery=50
+height=32
+width=58
+
+[2-puddle@68,232|52]
+changeEnabled=false
+changeEvery=50
+height=50
+width=40
+
+[2-puddle@72,241|32]
+changeEnabled=false
+changeEvery=50
+height=30
+width=44
+
+[2-puddle@75,225|34]
+changeEnabled=false
+changeEvery=50
+height=54
+width=38
+
+[2-puddle@75,235|33]
+changeEnabled=false
+changeEvery=50
+height=36
+width=38
+
+[2-sand@147,241|43]
+changeEnabled=false
+changeEvery=50
+height=54
+width=48
+
+[2-sand@160,261|45]
+changeEnabled=false
+changeEvery=50
+height=22
+width=52
+
+[2-sand@169,242|44]
+changeEnabled=false
+changeEvery=50
+height=52
+width=54
+
+[2-slope@11,97|51]
+grade=4
+gradient=Diagonal
+height=21
+reversed=true
+stuckOnGround=false
+width=43
+
+[2-slope@12,198|36]
+grade=3
+gradient=Vertical
+height=44
+reversed=false
+stuckOnGround=true
+width=35
+
+[2-slope@12,75|50]
+grade=4
+gradient=Horizontal
+height=22
+reversed=true
+stuckOnGround=false
+width=42
+
+[2-slope@13,35|49]
+grade=4
+gradient=Opposite Diagonal
+height=40
+reversed=false
+stuckOnGround=false
+width=42
+
+[2-slope@190,89|58]
+grade=3
+gradient=Diagonal
+height=30
+reversed=false
+stuckOnGround=false
+width=47
+
+[2-slope@192,119|57]
+grade=3
+gradient=Opposite Diagonal
+height=127
+reversed=true
+stuckOnGround=true
+width=43
+
+[2-slope@210,22|58]
+grade=2
+gradient=Opposite Diagonal
+height=39
+reversed=true
+stuckOnGround=false
+width=20
+
+[2-slope@224,11|46]
+grade=2
+gradient=Vertical
+height=48
+reversed=true
+stuckOnGround=false
+width=92
+
+[2-slope@232,243|48]
+grade=3
+gradient=Diagonal
+height=29
+reversed=true
+stuckOnGround=true
+width=135
+
+[2-slope@233,119|22]
+grade=3
+gradient=Vertical
+height=126
+reversed=true
+stuckOnGround=true
+width=153
+
+[2-slope@234,243|23]
+grade=3
+gradient=Horizontal
+height=144
+reversed=false
+stuckOnGround=true
+width=154
+
+[2-slope@235,88|59]
+grade=3
+gradient=Vertical
+height=31
+reversed=false
+stuckOnGround=false
+width=153
+
+[2-slope@314,9|47]
+grade=2
+gradient=Diagonal
+height=49
+reversed=true
+stuckOnGround=false
+width=19
+
+[2-slope@334,13|16]
+grade=4
+gradient=Opposite Diagonal
+height=31
+reversed=true
+stuckOnGround=false
+width=52
+
+[2-slope@43,112|18]
+grade=8
+gradient=Opposite Diagonal
+height=84
+reversed=false
+stuckOnGround=true
+width=68
+
+[2-slope@7,174|59]
+grade=4
+gradient=Elliptic
+height=33
+reversed=true
+stuckOnGround=true
+width=33
+
+[2-wall@0,0|10]
+endPoint=86,192
+startPoint=54,196
+
+[2-wall@0,0|11]
+endPoint=33,173
+startPoint=54,196
+
+[2-wall@0,0|12]
+endPoint=11,167
+startPoint=33,173
+
+[2-wall@0,0|13]
+endPoint=48,144
+startPoint=33,173
+
+[2-wall@0,0|14]
+endPoint=28,113
+startPoint=50,133
+
+[2-wall@0,0|3]
+endPoint=233,12
+startPoint=104,130
+
+[2-wall@0,0|37]
+endPoint=116,82
+startPoint=141,96
+
+[2-wall@0,0|38]
+endPoint=104,85
+startPoint=89,115
+
+[2-wall@0,0|4]
+endPoint=275,63
+startPoint=86,192
+
+[2-wall@0,0|49]
+endPoint=116,82
+startPoint=104,85
+
+[2-wall@0,0|5]
+endPoint=387,117
+startPoint=275,63
+
+[2-wall@0,0|52]
+endPoint=104,130
+startPoint=89,115
+
+[2-wall@0,0|54]
+endPoint=131,11
+startPoint=170,36
+
+[2-wall@0,0|55]
+endPoint=172,66
+startPoint=171,36
+
+[2-wall@0,0|56]
+endPoint=198,44
+startPoint=170,36
+
+[2-wall@0,0|60]
+endPoint=48,144
+startPoint=50,133
+
+[3-ball@346,322]
+dummykey=true
+
+[3-blackhole@192,132|23]
+exit=47,35
+exitDeg=310
+maxspeed=3.75
+minspeed=1.78
+
+[3-blackhole@208,286|7]
+exit=147,367
+exitDeg=180
+maxspeed=6.63
+minspeed=2.33
+
+[3-cup@341,43|16]
+dummykey=true
+
+[3-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[3-puddle@260,299|41]
+changeEnabled=false
+changeEvery=50
+height=22
+width=22
+
+[3-puddle@261,283|40]
+changeEnabled=false
+changeEvery=50
+height=20
+width=26
+
+[3-puddle@262,312|10]
+changeEnabled=false
+changeEvery=50
+height=36
+width=54
+
+[3-puddle@267,272|8]
+changeEnabled=false
+changeEvery=50
+height=36
+width=52
+
+[3-puddle@276,290|9]
+changeEnabled=false
+changeEvery=50
+height=68
+width=50
+
+[3-sand@269,142|45]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[3-sand@270,146|30]
+changeEnabled=false
+changeEvery=50
+height=32
+width=46
+
+[3-sand@272,135|44]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[3-sand@277,127|43]
+changeEnabled=false
+changeEvery=50
+height=28
+width=34
+
+[3-sand@282,122|42]
+changeEnabled=false
+changeEvery=50
+height=22
+width=32
+
+[3-sand@284,133|29]
+changeEnabled=false
+changeEvery=50
+height=46
+width=32
+
+[3-slope@11,10|25]
+grade=3
+gradient=Diagonal
+height=326
+reversed=true
+stuckOnGround=false
+width=128
+
+[3-slope@112,381|41]
+grade=8
+gradient=Opposite Diagonal
+height=15
+reversed=true
+stuckOnGround=false
+width=35
+
+[3-slope@113,334|40]
+grade=8
+gradient=Diagonal
+height=18
+reversed=false
+stuckOnGround=false
+width=35
+
+[3-slope@113,351|14]
+grade=8
+gradient=Horizontal
+height=30
+reversed=false
+stuckOnGround=false
+width=30
+
+[3-slope@150,302|11]
+grade=6
+gradient=Opposite Diagonal
+height=86
+reversed=false
+stuckOnGround=false
+width=81
+
+[3-slope@170,248|46]
+grade=4
+gradient=Elliptic
+height=81
+reversed=true
+stuckOnGround=false
+width=81
+
+[3-slope@174,112|47]
+grade=4
+gradient=Elliptic
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[3-slope@281,117|41]
+grade=2
+gradient=Diagonal
+height=56
+reversed=false
+stuckOnGround=false
+width=53
+
+[3-slope@282,172|40]
+grade=2
+gradient=Opposite Diagonal
+height=53
+reversed=true
+stuckOnGround=false
+width=53
+
+[3-slope@304,10|41]
+grade=4
+gradient=Opposite Diagonal
+height=48
+reversed=true
+stuckOnGround=false
+width=84
+
+[3-slope@334,119|24]
+grade=2
+gradient=Opposite Diagonal
+height=54
+reversed=false
+stuckOnGround=false
+width=52
+
+[3-slope@334,173|33]
+grade=2
+gradient=Diagonal
+height=54
+reversed=true
+stuckOnGround=false
+width=48
+
+[3-slope@99,36|39]
+grade=3
+gradient=Elliptic
+height=190
+reversed=true
+stuckOnGround=false
+width=190
+
+[3-wall@0,0|13]
+endPoint=90,390
+startPoint=14,350
+
+[3-wall@0,0|19]
+endPoint=301,42
+startPoint=323,62
+
+[3-wall@0,0|20]
+endPoint=274,39
+startPoint=261,46
+
+[3-wall@0,0|22]
+endPoint=255,24
+startPoint=242,41
+
+[3-wall@0,0|28]
+endPoint=100,175
+startPoint=13,132
+
+[3-wall@0,0|29]
+endPoint=78,206
+startPoint=14,175
+
+[3-wall@0,0|3]
+endPoint=390,233
+startPoint=224,228
+
+[3-wall@0,0|30]
+endPoint=150,198
+startPoint=224,228
+
+[3-wall@0,0|31]
+endPoint=192,250
+startPoint=128,227
+
+[3-wall@0,0|34]
+endPoint=255,24
+startPoint=250,13
+
+[3-wall@0,0|35]
+endPoint=192,252
+startPoint=192,250
+
+[3-wall@0,0|36]
+endPoint=192,250
+startPoint=189,264
+
+[3-wall@0,0|38]
+endPoint=261,46
+startPoint=242,41
+
+[3-wall@0,0|42]
+endPoint=292,10
+startPoint=301,42
+
+[3-wall@0,0|43]
+endPoint=301,42
+startPoint=274,39
+
+[3-wall@0,0|5]
+endPoint=189,264
+startPoint=151,310
+
+[3-wall@0,0|6]
+endPoint=151,390
+startPoint=151,310
+
+[3-windmill@323,62|31]
+botWallVisible=false
+bottom=true
+height=60
+leftWallVisible=true
+rightWallVisible=false
+speed=2
+topWallVisible=false
+width=61
+
+[4-ball@356,151]
+dummykey=true
+
+[4-blackhole@160,36|45]
+exit=156,218
+exitDeg=350
+maxspeed=2
+minspeed=1
+
+[4-blackhole@360,332|116]
+exit=122,234
+exitDeg=290
+maxspeed=5
+minspeed=3
+
+[4-bridge@184,22|136]
+botWallVisible=true
+height=25
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=true
+width=81
+
+[4-cup@105,116|3]
+dummykey=true
+
+[4-cup@187,330|136]
+dummykey=true
+
+[4-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=4
+
+[4-puddle@113,399|146]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[4-puddle@136,397|147]
+changeEnabled=false
+changeEvery=50
+height=48
+width=74
+
+[4-puddle@141,184|76]
+changeEnabled=false
+changeEvery=50
+height=66
+width=100
+
+[4-puddle@151,162|116]
+changeEnabled=false
+changeEvery=50
+height=54
+width=84
+
+[4-puddle@159,395|60]
+changeEnabled=false
+changeEvery=50
+height=60
+width=108
+
+[4-puddle@178,382|53]
+changeEnabled=false
+changeEvery=50
+height=34
+width=84
+
+[4-puddle@188,158|117]
+changeEnabled=false
+changeEvery=50
+height=100
+width=106
+
+[4-puddle@197,136|81]
+changeEnabled=false
+changeEvery=50
+height=154
+width=92
+
+[4-puddle@207,397|144]
+changeEnabled=false
+changeEvery=50
+height=48
+width=40
+
+[4-puddle@211,189|75]
+changeEnabled=false
+changeEvery=50
+height=40
+width=232
+
+[4-puddle@214,67|139]
+changeEnabled=false
+changeEvery=50
+height=24
+width=66
+
+[4-puddle@217,78|124]
+changeEnabled=false
+changeEvery=50
+height=42
+width=50
+
+[4-puddle@218,2|143]
+changeEnabled=false
+changeEvery=50
+height=74
+width=38
+
+[4-puddle@226,139|118]
+changeEnabled=false
+changeEvery=50
+height=82
+width=60
+
+[4-puddle@226,149|120]
+changeEnabled=false
+changeEvery=50
+height=92
+width=116
+
+[4-puddle@233,255|55]
+changeEnabled=false
+changeEvery=50
+height=30
+width=64
+
+[4-puddle@236,65|123]
+changeEnabled=false
+changeEvery=50
+height=28
+width=66
+
+[4-puddle@240,313|128]
+changeEnabled=false
+changeEvery=50
+height=26
+width=36
+
+[4-puddle@241,3|140]
+changeEnabled=false
+changeEvery=50
+height=90
+width=42
+
+[4-puddle@244,26|139]
+changeEnabled=false
+changeEvery=50
+height=82
+width=32
+
+[4-puddle@244,315|59]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[4-puddle@247,257|56]
+changeEnabled=false
+changeEvery=50
+height=32
+width=54
+
+[4-puddle@251,320|52]
+changeEnabled=false
+changeEvery=50
+height=24
+width=32
+
+[4-puddle@266,258|58]
+changeEnabled=false
+changeEvery=50
+height=24
+width=24
+
+[4-puddle@267,157|119]
+changeEnabled=false
+changeEvery=50
+height=42
+width=46
+
+[4-puddle@273,261|51]
+changeEnabled=false
+changeEvery=50
+height=26
+width=24
+
+[4-puddle@275,323|130]
+changeEnabled=false
+changeEvery=50
+height=22
+width=70
+
+[4-puddle@279,176|121]
+changeEnabled=false
+changeEvery=50
+height=62
+width=98
+
+[4-puddle@281,264|50]
+changeEnabled=false
+changeEvery=50
+height=26
+width=22
+
+[4-puddle@287,267|54]
+changeEnabled=false
+changeEvery=50
+height=24
+width=22
+
+[4-puddle@293,189|122]
+changeEnabled=false
+changeEvery=50
+height=42
+width=66
+
+[4-puddle@295,273|57]
+changeEnabled=false
+changeEvery=50
+height=28
+width=26
+
+[4-puddle@296,320|131]
+changeEnabled=false
+changeEvery=50
+height=22
+width=30
+
+[4-puddle@302,312|127]
+changeEnabled=false
+changeEvery=50
+height=30
+width=24
+
+[4-puddle@305,278|125]
+changeEnabled=false
+changeEvery=50
+height=24
+width=14
+
+[4-puddle@307,291|128]
+changeEnabled=false
+changeEvery=50
+height=40
+width=24
+
+[4-puddle@310,302|126]
+changeEnabled=false
+changeEvery=50
+height=32
+width=18
+
+[4-puddle@311,185|99]
+changeEnabled=false
+changeEvery=50
+height=44
+width=52
+
+[4-puddle@321,198|96]
+changeEnabled=false
+changeEvery=50
+height=32
+width=48
+
+[4-puddle@333,195|123]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[4-puddle@354,202|154]
+changeEnabled=false
+changeEvery=50
+height=40
+width=62
+
+[4-puddle@376,208|115]
+changeEnabled=false
+changeEvery=50
+height=40
+width=46
+
+[4-puddle@397,211|114]
+changeEnabled=false
+changeEvery=50
+height=62
+width=54
+
+[4-slope@12,188|117]
+grade=4
+gradient=Vertical
+height=28
+reversed=false
+stuckOnGround=false
+width=71
+
+[4-slope@12,213|118]
+grade=4
+gradient=Vertical
+height=24
+reversed=true
+stuckOnGround=false
+width=82
+
+[4-slope@146,60|125]
+grade=4
+gradient=Diagonal
+height=83
+reversed=true
+stuckOnGround=false
+width=48
+
+[4-slope@169,225|124]
+grade=1
+gradient=Vertical
+height=32
+reversed=false
+stuckOnGround=false
+width=128
+
+[4-slope@205,22|162]
+grade=4
+gradient=Horizontal
+height=25
+reversed=false
+stuckOnGround=false
+width=22
+
+[4-slope@215,-1|137]
+grade=3
+gradient=Opposite Diagonal
+height=209
+reversed=true
+stuckOnGround=false
+width=181
+
+[4-slope@261,193|138]
+grade=3
+gradient=Opposite Diagonal
+height=23
+reversed=true
+stuckOnGround=false
+width=12
+
+[4-slope@273,196|125]
+grade=3
+gradient=Vertical
+height=21
+reversed=true
+stuckOnGround=false
+width=43
+
+[4-slope@296,258|130]
+grade=4
+gradient=Horizontal
+height=47
+reversed=true
+stuckOnGround=false
+width=25
+
+[4-slope@297,225|137]
+grade=1
+gradient=Opposite Diagonal
+height=35
+reversed=false
+stuckOnGround=false
+width=24
+
+[4-slope@328,302|145]
+grade=2
+gradient=Elliptic
+height=64
+reversed=true
+stuckOnGround=false
+width=64
+
+[4-slope@40,280|111]
+grade=4
+gradient=Diagonal
+height=32
+reversed=false
+stuckOnGround=false
+width=32
+
+[4-slope@41,309|113]
+grade=4
+gradient=Opposite Diagonal
+height=30
+reversed=true
+stuckOnGround=false
+width=30
+
+[4-slope@70,281|112]
+grade=4
+gradient=Opposite Diagonal
+height=30
+reversed=false
+stuckOnGround=false
+width=30
+
+[4-slope@70,308|114]
+grade=4
+gradient=Diagonal
+height=30
+reversed=true
+stuckOnGround=false
+width=30
+
+[4-slope@99,9|130]
+grade=3
+gradient=Horizontal
+height=43
+reversed=false
+stuckOnGround=false
+width=37
+
+[4-wall@0,0|10]
+endPoint=95,237
+startPoint=141,225
+
+[4-wall@0,0|101]
+endPoint=249,280
+startPoint=220,278
+
+[4-wall@0,0|103]
+endPoint=267,277
+startPoint=249,280
+
+[4-wall@0,0|104]
+endPoint=288,293
+startPoint=267,277
+
+[4-wall@0,0|105]
+endPoint=242,292
+startPoint=216,291
+
+[4-wall@0,0|106]
+endPoint=267,299
+startPoint=242,292
+
+[4-wall@0,0|107]
+endPoint=285,303
+startPoint=273,307
+
+[4-wall@0,0|108]
+endPoint=288,293
+startPoint=285,303
+
+[4-wall@0,0|11]
+endPoint=97,154
+startPoint=76,195
+
+[4-wall@0,0|111]
+endPoint=216,291
+startPoint=189,299
+
+[4-wall@0,0|115]
+endPoint=138,131
+startPoint=120,137
+
+[4-wall@0,0|119]
+endPoint=389,274
+startPoint=342,227
+
+[4-wall@0,0|12]
+endPoint=136,101
+startPoint=146,113
+
+[4-wall@0,0|13]
+endPoint=146,113
+startPoint=138,131
+
+[4-wall@0,0|131]
+endPoint=28,49
+startPoint=34,27
+
+[4-wall@0,0|132]
+endPoint=267,299
+startPoint=273,307
+
+[4-wall@0,0|133]
+endPoint=61,25
+startPoint=34,27
+
+[4-wall@0,0|134]
+endPoint=80,40
+startPoint=61,25
+
+[4-wall@0,0|138]
+endPoint=243,98
+startPoint=245,83
+
+[4-wall@0,0|139]
+endPoint=255,44
+startPoint=277,62
+
+[4-wall@0,0|14]
+endPoint=77,75
+startPoint=93,90
+
+[4-wall@0,0|140]
+endPoint=277,62
+startPoint=271,76
+
+[4-wall@0,0|141]
+endPoint=314,12
+startPoint=96,8
+
+[4-wall@0,0|142]
+endPoint=13,336
+startPoint=9,263
+
+[4-wall@0,0|143]
+endPoint=395,355
+startPoint=350,388
+
+[4-wall@0,0|144]
+endPoint=350,388
+startPoint=230,394
+
+[4-wall@0,0|145]
+endPoint=389,274
+startPoint=395,355
+
+[4-wall@0,0|15]
+endPoint=74,58
+startPoint=77,75
+
+[4-wall@0,0|152]
+endPoint=383,162
+startPoint=378,176
+
+[4-wall@0,0|153]
+endPoint=345,178
+startPoint=378,176
+
+[4-wall@0,0|162]
+endPoint=153,355
+startPoint=121,366
+
+[4-wall@0,0|17]
+endPoint=12,189
+startPoint=28,49
+
+[4-wall@0,0|18]
+endPoint=9,263
+startPoint=12,189
+
+[4-wall@0,0|19]
+endPoint=39,353
+startPoint=13,336
+
+[4-wall@0,0|20]
+endPoint=69,367
+startPoint=39,353
+
+[4-wall@0,0|21]
+endPoint=190,299
+startPoint=164,241
+
+[4-wall@0,0|22]
+endPoint=379,147
+startPoint=383,162
+
+[4-wall@0,0|23]
+endPoint=345,178
+startPoint=322,148
+
+[4-wall@0,0|24]
+endPoint=275,113
+startPoint=243,98
+
+[4-wall@0,0|25]
+endPoint=291,137
+startPoint=275,113
+
+[4-wall@0,0|26]
+endPoint=182,55
+startPoint=158,60
+
+[4-wall@0,0|27]
+endPoint=80,40
+startPoint=79,24
+
+[4-wall@0,0|28]
+endPoint=208,51
+startPoint=182,55
+
+[4-wall@0,0|29]
+endPoint=132,51
+startPoint=103,51
+
+[4-wall@0,0|30]
+endPoint=230,49
+startPoint=208,51
+
+[4-wall@0,0|31]
+endPoint=245,83
+startPoint=271,76
+
+[4-wall@0,0|32]
+endPoint=103,51
+startPoint=80,40
+
+[4-wall@0,0|33]
+endPoint=322,148
+startPoint=291,137
+
+[4-wall@0,0|34]
+endPoint=96,8
+startPoint=79,24
+
+[4-wall@0,0|38]
+endPoint=379,147
+startPoint=385,136
+
+[4-wall@0,0|39]
+endPoint=385,136
+startPoint=383,113
+
+[4-wall@0,0|4]
+endPoint=121,366
+startPoint=69,367
+
+[4-wall@0,0|40]
+endPoint=383,113
+startPoint=379,83
+
+[4-wall@0,0|41]
+endPoint=379,83
+startPoint=385,60
+
+[4-wall@0,0|42]
+endPoint=385,60
+startPoint=383,43
+
+[4-wall@0,0|43]
+endPoint=383,43
+startPoint=365,22
+
+[4-wall@0,0|44]
+endPoint=365,22
+startPoint=314,12
+
+[4-wall@0,0|5]
+endPoint=189,299
+startPoint=153,355
+
+[4-wall@0,0|7]
+endPoint=141,225
+startPoint=220,278
+
+[4-wall@0,0|8]
+endPoint=76,195
+startPoint=95,237
+
+[4-wall@0,0|9]
+endPoint=120,137
+startPoint=97,154
+
+[4-wall@0,0|94]
+endPoint=136,101
+startPoint=93,90
+
+[4-wall@0,0|95]
+endPoint=158,60
+startPoint=132,51
+
+[5-ball@364,360]
+dummykey=true
+
+[5-cup@32,41|3]
+dummykey=true
+
+[5-floater@288,52|16]
+botWallVisible=false
+endPoint=288,52
+height=40
+leftWallVisible=false
+rightWallVisible=false
+speed=7
+startPoint=101,37
+topWallVisible=false
+width=80
+
+[5-floater@294,95|10]
+botWallVisible=false
+endPoint=294,95
+height=40
+leftWallVisible=false
+rightWallVisible=false
+speed=18
+startPoint=88,293
+topWallVisible=false
+width=80
+
+[5-floater@33,319|15]
+botWallVisible=false
+endPoint=33,319
+height=40
+leftWallVisible=false
+rightWallVisible=false
+speed=5
+startPoint=64,200
+topWallVisible=false
+width=80
+
+[5-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=true
+maxstrokes=10
+par=2
+
+[5-sand@137,111|16]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[5-sand@137,118|17]
+changeEnabled=false
+changeEvery=50
+height=48
+width=48
+
+[5-sand@145,132|19]
+changeEnabled=false
+changeEvery=50
+height=20
+width=26
+
+[5-sand@152,120|18]
+changeEnabled=false
+changeEvery=50
+height=44
+width=28
+
+[5-sand@152,258|13]
+changeEnabled=false
+changeEvery=50
+height=64
+width=62
+
+[5-sand@155,265|15]
+changeEnabled=false
+changeEvery=50
+height=60
+width=56
+
+[5-sand@158,279|19]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[5-sand@159,244|11]
+changeEnabled=false
+changeEvery=50
+height=26
+width=44
+
+[5-sand@162,269|20]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[5-sand@165,257|14]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[5-slope@12,11|17]
+grade=2
+gradient=Diagonal
+height=162
+reversed=true
+stuckOnGround=true
+width=77
+
+[5-slope@271,268|16]
+grade=4
+gradient=Diagonal
+height=120
+reversed=false
+stuckOnGround=true
+width=118
+
+[5-wall@0,0|4]
+endPoint=345,237
+startPoint=122,68
+
+[5-wall@0,0|5]
+endPoint=148,359
+startPoint=68,99
+
+[6-ball@36,37]
+dummykey=true
+
+[6-blackhole@331,316|12]
+exit=199,62
+exitDeg=0
+maxspeed=6
+minspeed=2
+
+[6-blackhole@374,374|17]
+exit=17,32
+exitDeg=0
+maxspeed=1
+minspeed=1
+
+[6-bridge@166,277|10]
+botWallVisible=true
+height=74
+leftWallVisible=false
+rightWallVisible=true
+topWallVisible=true
+width=219
+
+[6-bridge@223,134|16]
+botWallVisible=true
+height=27
+leftWallVisible=true
+rightWallVisible=false
+topWallVisible=true
+width=29
+
+[6-bridge@310,72|15]
+botWallVisible=true
+height=32
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=28
+
+[6-cup@307,154|14]
+dummykey=true
+
+[6-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[6-slope@10,187|5]
+grade=2
+gradient=Diagonal
+height=87
+reversed=true
+stuckOnGround=false
+width=67
+
+[6-slope@11,11|8]
+grade=2
+gradient=Diagonal
+height=130
+reversed=false
+stuckOnGround=false
+width=64
+
+[6-slope@11,140|3]
+grade=2
+gradient=Opposite Diagonal
+height=51
+reversed=false
+stuckOnGround=false
+width=64
+
+[6-slope@11,148|11]
+grade=3
+gradient=Opposite Diagonal
+height=241
+reversed=false
+stuckOnGround=false
+width=181
+
+[6-slope@12,12|6]
+grade=2
+gradient=Diagonal
+height=127
+reversed=true
+stuckOnGround=false
+width=62
+
+[6-slope@12,140|7]
+grade=2
+gradient=Opposite Diagonal
+height=48
+reversed=true
+stuckOnGround=false
+width=63
+
+[6-slope@231,275|18]
+grade=4
+gradient=Horizontal
+height=77
+reversed=true
+stuckOnGround=false
+width=23
+
+[6-wall@0,0|18]
+endPoint=76,231
+startPoint=76,13
+
+[6-windmill@194,42|19]
+botWallVisible=false
+bottom=true
+height=149
+leftWallVisible=true
+rightWallVisible=true
+speed=6
+topWallVisible=true
+width=166
+
+[7-ball@211,293]
+dummykey=true
+
+[7-blackhole@236,39|38]
+exit=83,55
+exitDeg=40
+maxspeed=2
+minspeed=1
+
+[7-blackhole@248,83|20]
+exit=82,30
+exitDeg=30
+maxspeed=2
+minspeed=1
+
+[7-cup@340,62|4]
+dummykey=true
+
+[7-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[7-puddle@-30,384|21]
+changeEnabled=false
+changeEvery=50
+height=288
+width=384
+
+[7-puddle@123,354|35]
+changeEnabled=false
+changeEvery=50
+height=136
+width=96
+
+[7-puddle@147,390|14]
+changeEnabled=false
+changeEvery=50
+height=170
+width=64
+
+[7-puddle@15,318|13]
+changeEnabled=false
+changeEvery=50
+height=116
+width=278
+
+[7-puddle@271,143|22]
+changeEnabled=false
+changeEvery=50
+height=30
+width=62
+
+[7-puddle@294,150|21]
+changeEnabled=false
+changeEvery=50
+height=32
+width=80
+
+[7-puddle@341,161|15]
+changeEnabled=false
+changeEvery=50
+height=38
+width=86
+
+[7-sand@132,70|14]
+changeEnabled=false
+changeEvery=50
+height=46
+width=72
+
+[7-sand@185,132|19]
+changeEnabled=false
+changeEvery=50
+height=38
+width=60
+
+[7-sand@311,116|18]
+changeEnabled=false
+changeEvery=50
+height=40
+width=80
+
+[7-sand@329,119|17]
+changeEnabled=false
+changeEvery=50
+height=42
+width=68
+
+[7-sand@349,123|16]
+changeEnabled=false
+changeEvery=50
+height=52
+width=60
+
+[7-sand@364,134|24]
+changeEnabled=false
+changeEvery=50
+height=52
+width=30
+
+[7-slope@11,10|5]
+grade=4
+gradient=Vertical
+height=209
+reversed=true
+stuckOnGround=false
+width=378
+
+[7-slope@11,238|17]
+grade=4
+gradient=Vertical
+height=46
+reversed=false
+stuckOnGround=false
+width=118
+
+[7-slope@127,237|16]
+grade=4
+gradient=Opposite Diagonal
+height=41
+reversed=false
+stuckOnGround=false
+width=63
+
+[7-slope@129,278|15]
+grade=4
+gradient=Horizontal
+height=121
+reversed=true
+stuckOnGround=false
+width=62
+
+[7-slope@226,238|41]
+grade=4
+gradient=Diagonal
+height=149
+reversed=false
+stuckOnGround=false
+width=163
+
+[7-wall@0,0|23]
+endPoint=284,123
+startPoint=240,117
+
+[8-ball@40,30]
+dummykey=true
+
+[8-bridge@151,203|21]
+botWallVisible=true
+height=26
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=66
+
+[8-cup@198,167|10]
+dummykey=true
+
+[8-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=6
+
+[8-puddle@170,236|22]
+changeEnabled=false
+changeEvery=50
+height=36
+width=38
+
+[8-puddle@186,239|41]
+changeEnabled=false
+changeEvery=50
+height=32
+width=30
+
+[8-puddle@207,240|43]
+changeEnabled=false
+changeEvery=50
+height=32
+width=30
+
+[8-puddle@268,292|17]
+changeEnabled=false
+changeEvery=50
+height=44
+width=48
+
+[8-puddle@268,307|42]
+changeEnabled=false
+changeEvery=50
+height=34
+width=90
+
+[8-puddle@279,280|19]
+changeEnabled=false
+changeEvery=50
+height=42
+width=40
+
+[8-puddle@296,280|13]
+changeEnabled=false
+changeEvery=50
+height=82
+width=50
+
+[8-slope@133,253|37]
+grade=4
+gradient=Opposite Diagonal
+height=15
+reversed=true
+stuckOnGround=false
+width=16
+
+[8-slope@134,206|38]
+grade=4
+gradient=Horizontal
+height=47
+reversed=false
+stuckOnGround=false
+width=15
+
+[8-slope@14,287|20]
+grade=8
+gradient=Opposite Diagonal
+height=101
+reversed=false
+stuckOnGround=false
+width=109
+
+[8-slope@146,253|24]
+grade=4
+gradient=Vertical
+height=13
+reversed=true
+stuckOnGround=false
+width=78
+
+[8-slope@150,129|27]
+grade=3
+gradient=Horizontal
+height=69
+reversed=false
+stuckOnGround=false
+width=28
+
+[8-slope@221,204|39]
+grade=4
+gradient=Horizontal
+height=49
+reversed=true
+stuckOnGround=false
+width=18
+
+[8-slope@223,253|36]
+grade=4
+gradient=Diagonal
+height=13
+reversed=true
+stuckOnGround=false
+width=15
+
+[8-slope@233,91|35]
+grade=3
+gradient=Vertical
+height=66
+reversed=true
+stuckOnGround=false
+width=27
+
+[8-slope@256,156|28]
+grade=3
+gradient=Opposite Diagonal
+height=20
+reversed=true
+stuckOnGround=false
+width=59
+
+[8-slope@257,105|25]
+grade=3
+gradient=Vertical
+height=49
+reversed=true
+stuckOnGround=false
+width=27
+
+[8-slope@284,122|26]
+grade=3
+gradient=Vertical
+height=34
+reversed=true
+stuckOnGround=false
+width=31
+
+[8-slope@291,10|12]
+grade=8
+gradient=Opposite Diagonal
+height=98
+reversed=true
+stuckOnGround=false
+width=97
+
+[8-wall@0,0|11]
+endPoint=11,161
+startPoint=30,90
+
+[8-wall@0,0|29]
+endPoint=318,125
+startPoint=238,83
+
+[8-wall@0,0|3]
+endPoint=238,83
+startPoint=61,70
+
+[8-wall@0,0|30]
+endPoint=300,226
+startPoint=320,150
+
+[8-wall@0,0|31]
+endPoint=90,126
+startPoint=70,145
+
+[8-wall@0,0|32]
+endPoint=61,70
+startPoint=30,90
+
+[8-wall@0,0|33]
+endPoint=227,129
+startPoint=241,143
+
+[8-wall@0,0|34]
+endPoint=241,181
+startPoint=225,196
+
+[8-wall@0,0|4]
+endPoint=300,226
+startPoint=185,332
+
+[8-wall@0,0|40]
+endPoint=318,125
+startPoint=320,150
+
+[8-wall@0,0|41]
+endPoint=185,332
+startPoint=171,331
+
+[8-wall@0,0|5]
+endPoint=171,331
+startPoint=75,228
+
+[8-wall@0,0|6]
+endPoint=70,145
+startPoint=75,228
+
+[8-wall@0,0|7]
+endPoint=90,126
+startPoint=227,129
+
+[8-wall@0,0|8]
+endPoint=241,143
+startPoint=241,181
+
+[8-wall@0,0|9]
+endPoint=225,196
+startPoint=141,196
+
+[9-ball@43,342]
+dummykey=true
+
+[9-blackhole@189,342|22]
+exit=44,144
+exitDeg=0
+maxspeed=3
+minspeed=1
+
+[9-cup@195,165|20]
+dummykey=true
+
+[9-cup@274,316|10]
+dummykey=true
+
+[9-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[9-puddle@260,356|30]
+changeEnabled=false
+changeEvery=50
+height=34
+width=72
+
+[9-puddle@284,362|3]
+changeEnabled=false
+changeEvery=50
+height=28
+width=60
+
+[9-puddle@295,281|6]
+changeEnabled=false
+changeEvery=50
+height=36
+width=48
+
+[9-puddle@298,286|31]
+changeEnabled=false
+changeEvery=50
+height=38
+width=38
+
+[9-puddle@303,367|4]
+changeEnabled=false
+changeEvery=50
+height=28
+width=62
+
+[9-puddle@308,294|7]
+changeEnabled=false
+changeEvery=50
+height=38
+width=46
+
+[9-puddle@314,301|32]
+changeEnabled=false
+changeEvery=50
+height=38
+width=44
+
+[9-puddle@316,364|5]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[9-puddle@321,307|8]
+changeEnabled=false
+changeEvery=50
+height=42
+width=40
+
+[9-puddle@328,355|29]
+changeEnabled=false
+changeEvery=50
+height=36
+width=34
+
+[9-puddle@329,320|28]
+changeEnabled=false
+changeEvery=50
+height=42
+width=38
+
+[9-puddle@333,336|9]
+changeEnabled=false
+changeEvery=50
+height=62
+width=36
+
+[9-sand@87,234|26]
+changeEnabled=false
+changeEvery=50
+height=36
+width=62
+
+[9-slope@11,11|19]
+grade=2
+gradient=Diagonal
+height=513
+reversed=true
+stuckOnGround=false
+width=499
+
+[9-slope@38,337|28]
+grade=5
+gradient=Diagonal
+height=10
+reversed=true
+stuckOnGround=false
+width=8
+
+[9-slope@93,148|27]
+grade=4
+gradient=Elliptic
+height=72
+reversed=false
+stuckOnGround=false
+width=72
+
+[9-wall@0,0|11]
+endPoint=88,205
+startPoint=68,172
+
+[9-wall@0,0|12]
+endPoint=222,186
+startPoint=166,193
+
+[9-wall@0,0|13]
+endPoint=265,251
+startPoint=217,229
+
+[9-wall@0,0|14]
+endPoint=295,205
+startPoint=228,133
+
+[9-wall@0,0|15]
+endPoint=178,146
+startPoint=107,128
+
+[9-wall@0,0|16]
+endPoint=164,219
+startPoint=133,239
+
+[9-wall@0,0|17]
+endPoint=209,325
+startPoint=167,326
+
+[9-wall@0,0|18]
+endPoint=166,351
+startPoint=123,311
+
+[9-wall@0,0|21]
+endPoint=180,278
+startPoint=184,242
+
+[9-wall@0,0|23]
+endPoint=54,276
+startPoint=35,245
+
+[9-wall@0,0|28]
+endPoint=93,345
+startPoint=61,369
diff --git a/kolf/courses/Impossible b/kolf/courses/Impossible
new file mode 100644
index 00000000..40e72e4e
--- /dev/null
+++ b/kolf/courses/Impossible
@@ -0,0 +1,3573 @@
+[0-course@-50,-50]
+Name=Impossible Course
+Name[af]=Onmoontlik Natuurlik
+Name[bg]=Невъзможно
+Name[bn]=অসম্ভব কঠিন খেলা
+Name[bs]=Nemoguć teren
+Name[ca]=Cursa impossible
+Name[cs]=Nesplnitelný
+Name[da]=Umulig bane
+Name[de]=Unmöglich
+Name[es]=Campo imposible
+Name[et]=Võimatu väljak
+Name[fi]=Mahdoton kenttä
+Name[fr]=Parcours impossible
+Name[gl]=Campo imposible
+Name[he]=מסלול בלתי אפשרי
+Name[hi]=असंभव कोर्स
+Name[hr]=Nemogući smjer
+Name[hu]=Különlegesen nehéz pálya
+Name[is]=Ómöguleg leið
+Name[it]=Percorso impossibile
+Name[ja]=不可能なコース
+Name[mk]=Невозможен терен
+Name[nb]=Umulig bane
+Name[nl]=Onmogelijk parcours
+Name[nn]=Umogleg bane
+Name[nso]=Course yeo esa Kgonegego
+Name[pl]=Niemożliwy do wykonania
+Name[pt]=Curso Impossível
+Name[pt_BR]=Curso Impossível
+Name[ro]=Cale imposibilă
+Name[ru]=Миссия невыполнима
+Name[sk]=Nezvládnuteľné ihrisko
+Name[sl]=Nemogoče igrišče
+Name[sr]=Немогућ терен
+Name[sr@Latn]=Nemoguć teren
+Name[sv]=Omöjlig bana
+Name[ta]=சாத்தியமில்லாத மார்க்கம்
+Name[tg]=Майдони Имконнопазир
+Name[tr]=İmkansız Pist
+Name[uk]=Найскладніший майданчик
+Name[ven]=Thero i sa koneiho
+Name[xh]=Indlela Engenzekiyo
+Name[xx]=xxImpossible Coursexx
+Name[zh_CN]=无法完成的路线
+Name[zh_TW]=不可能的路線
+Name[zu]=Indlela engenzeki
+author=Jason Katz-Brown
+
+[1-ball@31,330]
+dummykey=true
+
+[1-cup@328,318|16]
+dummykey=true
+
+[1-floater@227,64|8]
+botWallVisible=false
+endPoint=227,64
+height=85
+leftWallVisible=false
+rightWallVisible=false
+speed=5
+startPoint=105,76
+topWallVisible=true
+width=93
+
+[1-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=true
+maxstrokes=10
+par=2
+
+[1-puddle@-5,99|20]
+changeEnabled=false
+changeEvery=50
+height=210
+width=60
+
+[1-puddle@108,17|39]
+changeEnabled=false
+changeEvery=50
+height=32
+width=36
+
+[1-puddle@11,15|22]
+changeEnabled=false
+changeEvery=50
+height=30
+width=32
+
+[1-puddle@117,33|45]
+changeEnabled=false
+changeEvery=50
+height=32
+width=46
+
+[1-puddle@124,27|25]
+changeEnabled=false
+changeEvery=50
+height=36
+width=78
+
+[1-puddle@127,31|42]
+changeEnabled=false
+changeEvery=50
+height=36
+width=76
+
+[1-puddle@131,37|43]
+changeEnabled=false
+changeEvery=50
+height=38
+width=58
+
+[1-puddle@131,46|46]
+changeEnabled=false
+changeEvery=50
+height=26
+width=38
+
+[1-puddle@139,46|44]
+changeEnabled=false
+changeEvery=50
+height=40
+width=46
+
+[1-puddle@145,42|24]
+changeEnabled=false
+changeEvery=50
+height=52
+width=54
+
+[1-puddle@163,41|40]
+changeEnabled=false
+changeEvery=50
+height=26
+width=30
+
+[1-puddle@172,45|41]
+changeEnabled=false
+changeEvery=50
+height=26
+width=32
+
+[1-puddle@177,56|6]
+changeEnabled=false
+changeEvery=50
+height=46
+width=104
+
+[1-puddle@188,207|1]
+changeEnabled=false
+changeEvery=50
+height=68
+width=28
+
+[1-puddle@19,9|5]
+changeEnabled=false
+changeEvery=50
+height=28
+width=34
+
+[1-puddle@2,22|7]
+changeEnabled=false
+changeEvery=50
+height=38
+width=34
+
+[1-puddle@216,158|4]
+changeEnabled=false
+changeEvery=50
+height=232
+width=82
+
+[1-puddle@216,256|3]
+changeEnabled=false
+changeEvery=50
+height=184
+width=98
+
+[1-puddle@22,8|0]
+changeEnabled=false
+changeEvery=50
+height=24
+width=44
+
+[1-puddle@247,201|2]
+changeEnabled=false
+changeEvery=50
+height=84
+width=26
+
+[1-puddle@40,-3|21]
+changeEnabled=false
+changeEvery=50
+height=42
+width=124
+
+[1-puddle@7,24|0]
+changeEnabled=false
+changeEvery=50
+height=38
+width=28
+
+[1-puddle@88,5|0]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[1-puddle@98,9|26]
+changeEnabled=false
+changeEvery=50
+height=50
+width=46
+
+[1-sand@224,10|27]
+changeEnabled=false
+changeEvery=50
+height=46
+width=124
+
+[1-sand@247,19|31]
+changeEnabled=false
+changeEvery=50
+height=30
+width=68
+
+[1-sand@277,19|30]
+changeEnabled=false
+changeEvery=50
+height=40
+width=94
+
+[1-sand@292,10|33]
+changeEnabled=false
+changeEvery=50
+height=16
+width=58
+
+[1-sand@304,26|32]
+changeEnabled=false
+changeEvery=50
+height=32
+width=66
+
+[1-sand@314,26|29]
+changeEnabled=false
+changeEvery=50
+height=40
+width=54
+
+[1-sand@349,114|37]
+changeEnabled=false
+changeEvery=50
+height=62
+width=22
+
+[1-sand@358,92|36]
+changeEnabled=false
+changeEvery=50
+height=80
+width=58
+
+[1-sand@367,123|35]
+changeEnabled=false
+changeEvery=50
+height=96
+width=54
+
+[1-sand@368,133|34]
+changeEnabled=false
+changeEvery=50
+height=82
+width=56
+
+[1-sand@369,150|15]
+changeEnabled=false
+changeEvery=50
+height=86
+width=58
+
+[1-sand@383,101|38]
+changeEnabled=false
+changeEvery=50
+height=38
+width=14
+
+[1-slope@-42,10|22]
+grade=2
+gradient=Horizontal
+height=387
+reversed=true
+stuckOnGround=false
+width=135
+
+[1-slope@135,310|11]
+grade=4
+gradient=Opposite Diagonal
+height=76
+reversed=true
+stuckOnGround=true
+width=61
+
+[1-slope@136,82|10]
+grade=4
+gradient=Horizontal
+height=229
+reversed=false
+stuckOnGround=true
+width=60
+
+[1-slope@192,301|12]
+grade=4
+gradient=Vertical
+height=90
+reversed=true
+stuckOnGround=true
+width=66
+
+[1-slope@211,6|28]
+grade=4
+gradient=Opposite Diagonal
+height=82
+reversed=false
+stuckOnGround=true
+width=75
+
+[1-slope@244,89|14]
+grade=4
+gradient=Horizontal
+height=213
+reversed=true
+stuckOnGround=true
+width=47
+
+[1-slope@257,301|13]
+grade=4
+gradient=Diagonal
+height=88
+reversed=true
+stuckOnGround=true
+width=35
+
+[1-slope@286,276|29]
+grade=4
+gradient=Elliptic
+height=82
+reversed=true
+stuckOnGround=false
+width=82
+
+[1-slope@29,-3|9]
+grade=4
+gradient=Opposite Diagonal
+height=149
+reversed=true
+stuckOnGround=true
+width=173
+
+[1-wall@0,0|17]
+endPoint=358,358
+startPoint=298,362
+
+[1-wall@0,0|18]
+endPoint=358,358
+startPoint=380,331
+
+[1-wall@0,0|19]
+endPoint=380,331
+startPoint=383,206
+
+[1-wall@0,0|23]
+endPoint=280,345
+startPoint=298,362
+
+[10-ball@351,365]
+dummykey=true
+
+[10-blackhole@255,206|4]
+exit=15,39
+exitDeg=0
+maxspeed=1
+minspeed=1
+
+[10-cup@44,38|3]
+dummykey=true
+
+[10-floater@220,184|2]
+botWallVisible=true
+endPoint=220,184
+height=43
+leftWallVisible=true
+rightWallVisible=true
+speed=7
+startPoint=69,209
+topWallVisible=true
+width=71
+
+[10-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=true
+maxstrokes=10
+par=2
+
+[10-puddle@194,199|0]
+changeEnabled=false
+changeEvery=50
+height=60
+width=72
+
+[10-puddle@201,188|0]
+changeEnabled=false
+changeEvery=50
+height=56
+width=66
+
+[10-puddle@201,213|0]
+changeEnabled=false
+changeEvery=50
+height=32
+width=56
+
+[10-puddle@214,197|0]
+changeEnabled=false
+changeEvery=50
+height=60
+width=48
+
+[10-slope@35,30|0]
+grade=6
+gradient=Elliptic
+height=338
+reversed=false
+stuckOnGround=true
+width=338
+
+[10-wall@0,0|0]
+endPoint=387,198
+startPoint=307,198
+
+[10-wall@0,0|1]
+endPoint=73,196
+startPoint=12,196
+
+[11-ball@182,47]
+dummykey=true
+
+[11-bridge@158,36|17]
+botWallVisible=false
+height=27
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=true
+width=43
+
+[11-cup@97,297|0]
+dummykey=true
+
+[11-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=1
+
+[11-sand@204,336|18]
+changeEnabled=false
+changeEvery=50
+height=30
+width=34
+
+[11-sand@213,327|21]
+changeEnabled=false
+changeEvery=50
+height=18
+width=22
+
+[11-sand@213,331|19]
+changeEnabled=false
+changeEvery=50
+height=24
+width=26
+
+[11-sand@222,324|20]
+changeEnabled=false
+changeEvery=50
+height=26
+width=30
+
+[11-slope@129,150|2]
+grade=4
+gradient=Opposite Diagonal
+height=91
+reversed=true
+stuckOnGround=false
+width=99
+
+[11-slope@130,148|1]
+grade=4
+gradient=Opposite Diagonal
+height=93
+reversed=false
+stuckOnGround=false
+width=98
+
+[11-slope@130,242|15]
+grade=4
+gradient=Diagonal
+height=75
+reversed=true
+stuckOnGround=false
+width=97
+
+[11-slope@130,63|3]
+grade=4
+gradient=Diagonal
+height=88
+reversed=false
+stuckOnGround=false
+width=99
+
+[11-slope@130,64|4]
+grade=4
+gradient=Diagonal
+height=87
+reversed=true
+stuckOnGround=false
+width=97
+
+[11-slope@73,241|22]
+grade=5
+gradient=Opposite Diagonal
+height=73
+reversed=true
+stuckOnGround=false
+width=58
+
+[11-wall@0,0|10]
+endPoint=118,365
+startPoint=69,296
+
+[11-wall@0,0|11]
+endPoint=69,296
+startPoint=81,244
+
+[11-wall@0,0|12]
+endPoint=81,244
+startPoint=129,242
+
+[11-wall@0,0|13]
+endPoint=129,242
+startPoint=130,63
+
+[11-wall@0,0|14]
+endPoint=130,63
+startPoint=158,36
+
+[11-wall@0,0|16]
+endPoint=201,36
+startPoint=231,64
+
+[11-wall@0,0|5]
+endPoint=229,242
+startPoint=231,64
+
+[11-wall@0,0|6]
+endPoint=229,242
+startPoint=261,319
+
+[11-wall@0,0|7]
+endPoint=261,319
+startPoint=252,353
+
+[11-wall@0,0|8]
+endPoint=252,353
+startPoint=201,376
+
+[11-wall@0,0|9]
+endPoint=201,376
+startPoint=118,365
+
+[12-ball@196,183]
+dummykey=true
+
+[12-cup@107,101|2]
+dummykey=true
+
+[12-cup@107,271|1]
+dummykey=true
+
+[12-cup@285,103|0]
+dummykey=true
+
+[12-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[12-slope@177,164|7]
+grade=6
+gradient=Elliptic
+height=37
+reversed=false
+stuckOnGround=true
+width=37
+
+[12-slope@197,54|4]
+grade=8
+gradient=Opposite Diagonal
+height=128
+reversed=false
+stuckOnGround=true
+width=124
+
+[12-slope@198,182|6]
+grade=8
+gradient=Diagonal
+height=123
+reversed=true
+stuckOnGround=true
+width=122
+
+[12-slope@288,-2|15]
+grade=3
+gradient=Elliptic
+height=103
+reversed=false
+stuckOnGround=false
+width=103
+
+[12-slope@347,68|8]
+grade=2
+gradient=Elliptic
+height=79
+reversed=false
+stuckOnGround=false
+width=79
+
+[12-slope@68,183|5]
+grade=8
+gradient=Opposite Diagonal
+height=123
+reversed=true
+stuckOnGround=true
+width=130
+
+[12-slope@69,52|3]
+grade=8
+gradient=Diagonal
+height=129
+reversed=false
+stuckOnGround=true
+width=128
+
+[12-windmill@234,300|9]
+botWallVisible=true
+bottom=false
+height=83
+leftWallVisible=true
+rightWallVisible=true
+speed=5
+topWallVisible=false
+width=121
+
+[13-ball@72,345]
+dummykey=true
+
+[13-cup@296,86|0]
+dummykey=true
+
+[13-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[13-puddle@338,3|16]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[13-puddle@357,-16|15]
+changeEnabled=false
+changeEvery=50
+height=62
+width=132
+
+[13-puddle@358,21|13]
+changeEnabled=false
+changeEvery=50
+height=50
+width=46
+
+[13-puddle@367,103|2]
+changeEnabled=false
+changeEvery=50
+height=64
+width=38
+
+[13-puddle@367,55|12]
+changeEnabled=false
+changeEvery=50
+height=64
+width=38
+
+[13-puddle@369,0|7]
+changeEnabled=false
+changeEvery=50
+height=80
+width=94
+
+[13-puddle@370,145|4]
+changeEnabled=false
+changeEvery=50
+height=120
+width=78
+
+[13-puddle@376,81|5]
+changeEnabled=false
+changeEvery=50
+height=76
+width=50
+
+[13-puddle@380,31|6]
+changeEnabled=false
+changeEvery=50
+height=82
+width=80
+
+[13-puddle@394,99|14]
+changeEnabled=false
+changeEvery=50
+height=68
+width=46
+
+[13-puddle@428,179|8]
+changeEnabled=false
+changeEvery=50
+height=88
+width=164
+
+[13-sand@103,64|10]
+changeEnabled=false
+changeEvery=50
+height=94
+width=130
+
+[13-sand@106,102|26]
+changeEnabled=false
+changeEvery=50
+height=86
+width=48
+
+[13-sand@106,172|20]
+changeEnabled=false
+changeEvery=50
+height=62
+width=36
+
+[13-sand@107,91|25]
+changeEnabled=false
+changeEvery=50
+height=50
+width=64
+
+[13-sand@120,149|21]
+changeEnabled=false
+changeEvery=50
+height=74
+width=14
+
+[13-sand@35,210|18]
+changeEnabled=false
+changeEvery=50
+height=82
+width=46
+
+[13-sand@43,201|27]
+changeEnabled=false
+changeEvery=50
+height=40
+width=48
+
+[13-sand@60,148|23]
+changeEnabled=false
+changeEvery=50
+height=36
+width=30
+
+[13-sand@66,96|9]
+changeEnabled=false
+changeEvery=50
+height=134
+width=72
+
+[13-sand@67,181|11]
+changeEnabled=false
+changeEvery=50
+height=38
+width=92
+
+[13-sand@72,194|19]
+changeEnabled=false
+changeEvery=50
+height=40
+width=90
+
+[13-sand@73,171|24]
+changeEnabled=false
+changeEvery=50
+height=28
+width=66
+
+[13-sand@76,164|22]
+changeEnabled=false
+changeEvery=50
+height=70
+width=56
+
+[13-sand@96,126|17]
+changeEnabled=false
+changeEvery=50
+height=138
+width=66
+
+[13-sign@142,193|1]
+Comment=Reprieve
+Comment[bg]=Отмяна
+Comment[bn]=সাময়িকভাবে স্থগিত
+Comment[bs]=Odgodi
+Comment[ca]=Respir
+Comment[da]=Red dig selv
+Comment[de]=Aufschub
+Comment[es]=Respiro
+Comment[et]=Kergendus
+Comment[fi]=Armonaika
+Comment[fr]=Sursis
+Comment[gl]=Respiro
+Comment[he]=הקלה
+Comment[hi]=विराम
+Comment[hr]=Odgodi
+Comment[hu]=Mázli
+Comment[is]=Milda
+Comment[it]=Pausa
+Comment[ja]=カンタンな一瞬
+Comment[mk]=Одложување
+Comment[nb]=Nåde
+Comment[nl]=Gratie
+Comment[nn]=Nåde
+Comment[pl]=Odroczenie
+Comment[pt]=Tortura
+Comment[pt_BR]=Suspensão
+Comment[ru]=Отсрочка
+Comment[sk]=Omilostnenie
+Comment[sl]=Odložitev
+Comment[sr]=Предах
+Comment[sr@Latn]=Predah
+Comment[sv]=Lättnad
+Comment[ta]=ஒத்தி வை
+Comment[tg]=Таъхир
+Comment[tr]=Ceza Ertele
+Comment[uk]=Відкладання
+Comment[ven]=U vhofhololwa
+Comment[xh]=Urhoxiso lwethutyana
+Comment[xx]=xxReprievexx
+Comment[zh_CN]=暂时缓解
+Comment[zh_TW]=暫緩
+Comment[zu]=Phumuza
+botWallVisible=false
+height=47
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=121
+
+[13-slope@212,12|3]
+grade=4
+gradient=Elliptic
+height=117
+reversed=false
+stuckOnGround=false
+width=117
+
+[14-ball@63,341]
+dummykey=true
+
+[14-blackhole@137,332|12]
+exit=22,383
+exitDeg=40
+maxspeed=5
+minspeed=3
+
+[14-blackhole@317,208|10]
+exit=17,377
+exitDeg=40
+maxspeed=5
+minspeed=3
+
+[14-bumper@179,215|4]
+dummykey=true
+
+[14-bumper@290,116|6]
+dummykey=true
+
+[14-cup@351,37|0]
+dummykey=true
+
+[14-floater@113,315|3]
+botWallVisible=true
+endPoint=113,315
+height=39
+leftWallVisible=true
+rightWallVisible=true
+speed=7
+startPoint=29,195
+topWallVisible=true
+width=49
+
+[14-floater@281,189|5]
+botWallVisible=true
+endPoint=281,189
+height=39
+leftWallVisible=true
+rightWallVisible=true
+speed=19
+startPoint=112,79
+topWallVisible=true
+width=74
+
+[14-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=true
+maxstrokes=10
+par=3
+
+[14-puddle@334,315|9]
+changeEnabled=false
+changeEvery=50
+height=60
+width=96
+
+[14-puddle@372,404|8]
+changeEnabled=false
+changeEvery=50
+height=242
+width=332
+
+[14-puddle@391,360|7]
+changeEnabled=false
+changeEvery=50
+height=218
+width=226
+
+[14-slope@-2,-2|2]
+grade=4
+gradient=Diagonal
+height=357
+reversed=true
+stuckOnGround=true
+width=360
+
+[14-slope@36,37|1]
+grade=4
+gradient=Diagonal
+height=362
+reversed=false
+stuckOnGround=true
+width=364
+
+[14-windmill@283,12|11]
+botWallVisible=false
+bottom=true
+height=71
+leftWallVisible=true
+rightWallVisible=true
+speed=5
+topWallVisible=true
+width=92
+
+[15-ball@202,250]
+dummykey=true
+
+[15-blackhole@122,62|42]
+exit=107,219
+exitDeg=180
+maxspeed=5
+minspeed=3
+
+[15-bridge@129,134|62]
+botWallVisible=false
+height=20
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=74
+
+[15-bridge@129,135|63]
+botWallVisible=false
+height=37
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=16
+
+[15-bridge@161,134|59]
+botWallVisible=false
+height=46
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=87
+
+[15-bridge@180,152|61]
+botWallVisible=false
+height=42
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=34
+
+[15-bridge@202,120|60]
+botWallVisible=false
+height=45
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=32
+
+[15-bridge@222,152|64]
+botWallVisible=false
+height=28
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=47
+
+[15-cup@185,340|0]
+dummykey=true
+
+[15-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[15-slope@102,285|56]
+grade=5
+gradient=Opposite Diagonal
+height=25
+reversed=true
+stuckOnGround=false
+width=45
+
+[15-slope@105,251|47]
+grade=6
+gradient=Horizontal
+height=16
+reversed=false
+stuckOnGround=false
+width=27
+
+[15-slope@108,313|57]
+grade=4
+gradient=Opposite Diagonal
+height=55
+reversed=false
+stuckOnGround=false
+width=39
+
+[15-slope@132,215|58]
+grade=6
+gradient=Vertical
+height=20
+reversed=true
+stuckOnGround=false
+width=34
+
+[15-slope@145,308|53]
+grade=4
+gradient=Horizontal
+height=61
+reversed=true
+stuckOnGround=false
+width=27
+
+[15-slope@167,212|46]
+grade=4
+gradient=Horizontal
+height=77
+reversed=true
+stuckOnGround=false
+width=118
+
+[15-slope@172,308|54]
+grade=4
+gradient=Vertical
+height=25
+reversed=true
+stuckOnGround=false
+width=28
+
+[15-slope@174,345|55]
+grade=4
+gradient=Vertical
+height=22
+reversed=false
+stuckOnGround=false
+width=27
+
+[15-slope@304,150|45]
+grade=4
+gradient=Horizontal
+height=105
+reversed=false
+stuckOnGround=false
+width=59
+
+[15-slope@65,251|52]
+grade=4
+gradient=Opposite Diagonal
+height=60
+reversed=false
+stuckOnGround=false
+width=40
+
+[15-slope@81,207|51]
+grade=5
+gradient=Horizontal
+height=24
+reversed=false
+stuckOnGround=false
+width=30
+
+[15-slope@90,51|65]
+grade=4
+gradient=Diagonal
+height=51
+reversed=true
+stuckOnGround=false
+width=25
+
+[15-wall@0,0|10]
+endPoint=216,102
+startPoint=191,102
+
+[15-wall@0,0|11]
+endPoint=191,102
+startPoint=191,87
+
+[15-wall@0,0|12]
+endPoint=191,87
+startPoint=153,87
+
+[15-wall@0,0|13]
+endPoint=153,87
+startPoint=153,111
+
+[15-wall@0,0|14]
+endPoint=153,111
+startPoint=115,111
+
+[15-wall@0,0|15]
+endPoint=115,111
+startPoint=115,78
+
+[15-wall@0,0|16]
+endPoint=115,78
+startPoint=134,76
+
+[15-wall@0,0|17]
+endPoint=134,76
+startPoint=134,49
+
+[15-wall@0,0|18]
+endPoint=134,49
+startPoint=87,49
+
+[15-wall@0,0|19]
+endPoint=87,49
+startPoint=87,184
+
+[15-wall@0,0|2]
+endPoint=305,151
+startPoint=363,151
+
+[15-wall@0,0|20]
+endPoint=87,184
+startPoint=130,184
+
+[15-wall@0,0|21]
+endPoint=130,184
+startPoint=130,239
+
+[15-wall@0,0|22]
+endPoint=123,252
+startPoint=86,252
+
+[15-wall@0,0|23]
+endPoint=86,252
+startPoint=86,232
+
+[15-wall@0,0|24]
+endPoint=86,232
+startPoint=111,232
+
+[15-wall@0,0|25]
+endPoint=111,232
+startPoint=111,205
+
+[15-wall@0,0|26]
+endPoint=111,205
+startPoint=64,205
+
+[15-wall@0,0|27]
+endPoint=64,205
+startPoint=64,314
+
+[15-wall@0,0|28]
+endPoint=64,314
+startPoint=106,314
+
+[15-wall@0,0|29]
+endPoint=106,314
+startPoint=106,369
+
+[15-wall@0,0|3]
+endPoint=305,151
+startPoint=305,115
+
+[15-wall@0,0|30]
+endPoint=106,369
+startPoint=199,369
+
+[15-wall@0,0|31]
+endPoint=199,369
+startPoint=199,307
+
+[15-wall@0,0|32]
+endPoint=199,307
+startPoint=146,307
+
+[15-wall@0,0|33]
+endPoint=146,307
+startPoint=146,285
+
+[15-wall@0,0|34]
+endPoint=146,285
+startPoint=105,285
+
+[15-wall@0,0|35]
+endPoint=105,285
+startPoint=105,268
+
+[15-wall@0,0|36]
+endPoint=105,268
+startPoint=166,268
+
+[15-wall@0,0|37]
+endPoint=166,268
+startPoint=166,291
+
+[15-wall@0,0|38]
+endPoint=166,291
+startPoint=305,291
+
+[15-wall@0,0|39]
+endPoint=305,291
+startPoint=305,256
+
+[15-wall@0,0|4]
+endPoint=305,115
+startPoint=270,115
+
+[15-wall@0,0|40]
+endPoint=305,256
+startPoint=363,256
+
+[15-wall@0,0|41]
+endPoint=363,256
+startPoint=363,151
+
+[15-wall@0,0|43]
+endPoint=166,268
+startPoint=166,212
+
+[15-wall@0,0|44]
+endPoint=166,212
+startPoint=285,211
+
+[15-wall@0,0|49]
+endPoint=165,236
+startPoint=133,270
+
+[15-wall@0,0|5]
+endPoint=270,115
+startPoint=270,70
+
+[15-wall@0,0|50]
+endPoint=123,252
+startPoint=130,239
+
+[15-wall@0,0|6]
+endPoint=270,70
+startPoint=238,70
+
+[15-wall@0,0|7]
+endPoint=238,70
+startPoint=238,89
+
+[15-wall@0,0|8]
+endPoint=238,89
+startPoint=216,89
+
+[15-wall@0,0|9]
+endPoint=216,89
+startPoint=216,102
+
+[15-windmill@133,212|48]
+botWallVisible=false
+bottom=false
+height=11
+leftWallVisible=false
+rightWallVisible=false
+speed=2
+topWallVisible=false
+width=33
+
+[16-ball@56,49]
+dummykey=true
+
+[16-cup@212,321|1]
+dummykey=true
+
+[16-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[16-puddle@266,315|25]
+changeEnabled=false
+changeEvery=50
+height=18
+width=10
+
+[16-puddle@274,301|22]
+changeEnabled=false
+changeEvery=50
+height=22
+width=22
+
+[16-puddle@276,314|28]
+changeEnabled=false
+changeEvery=50
+height=8
+width=12
+
+[16-puddle@280,328|26]
+changeEnabled=false
+changeEvery=50
+height=12
+width=20
+
+[16-puddle@286,315|24]
+changeEnabled=false
+changeEvery=50
+height=14
+width=10
+
+[16-puddle@290,309|29]
+changeEnabled=false
+changeEvery=50
+height=16
+width=14
+
+[16-puddle@292,327|31]
+changeEnabled=false
+changeEvery=50
+height=12
+width=18
+
+[16-puddle@296,300|23]
+changeEnabled=false
+changeEvery=50
+height=16
+width=10
+
+[16-puddle@300,321|27]
+changeEnabled=false
+changeEvery=50
+height=16
+width=14
+
+[16-puddle@306,308|21]
+changeEnabled=false
+changeEvery=50
+height=16
+width=10
+
+[16-puddle@329,382|8]
+changeEnabled=false
+changeEvery=50
+height=26
+width=36
+
+[16-puddle@336,400|4]
+changeEnabled=false
+changeEvery=50
+height=52
+width=390
+
+[16-puddle@351,359|7]
+changeEnabled=false
+changeEvery=50
+height=46
+width=38
+
+[16-puddle@355,386|6]
+changeEnabled=false
+changeEvery=50
+height=74
+width=72
+
+[16-puddle@409,335|5]
+changeEnabled=false
+changeEvery=50
+height=336
+width=150
+
+[16-puddle@422,231|9]
+changeEnabled=false
+changeEvery=50
+height=228
+width=114
+
+[16-puddle@58,420|0]
+changeEnabled=false
+changeEvery=50
+height=96
+width=550
+
+[16-sand@306,84|19]
+changeEnabled=false
+changeEvery=50
+height=28
+width=52
+
+[16-sand@312,-7|16]
+changeEnabled=false
+changeEvery=50
+height=102
+width=84
+
+[16-sand@322,78|18]
+changeEnabled=false
+changeEvery=50
+height=38
+width=44
+
+[16-sand@332,65|17]
+changeEnabled=false
+changeEvery=50
+height=58
+width=54
+
+[16-sand@342,76|20]
+changeEnabled=false
+changeEvery=50
+height=32
+width=76
+
+[16-sand@387,22|15]
+changeEnabled=false
+changeEvery=50
+height=142
+width=196
+
+[16-slope@-2,-3|14]
+grade=7
+gradient=Diagonal
+height=260
+reversed=true
+stuckOnGround=false
+width=267
+
+[16-slope@2,360|3]
+grade=4
+gradient=Vertical
+height=30
+reversed=false
+stuckOnGround=false
+width=293
+
+[16-slope@202,43|13]
+grade=2
+gradient=Elliptic
+height=140
+reversed=true
+stuckOnGround=false
+width=140
+
+[16-slope@271,313|10]
+grade=4
+gradient=Diagonal
+height=66
+reversed=false
+stuckOnGround=false
+width=74
+
+[16-slope@319,91|12]
+grade=4
+gradient=Diagonal
+height=139
+reversed=false
+stuckOnGround=false
+width=81
+
+[16-slope@320,230|11]
+grade=4
+gradient=Horizontal
+height=108
+reversed=false
+stuckOnGround=false
+width=44
+
+[16-slope@69,154|2]
+grade=3
+gradient=Elliptic
+height=154
+reversed=false
+stuckOnGround=false
+width=154
+
+[17-ball@55,55]
+dummykey=true
+
+[17-blackhole@269,363|4]
+exit=198,384
+exitDeg=90
+maxspeed=3
+minspeed=2
+
+[17-cup@146,232|21]
+dummykey=true
+
+[17-cup@88,233|1]
+dummykey=true
+
+[17-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[17-sand@333,344|15]
+changeEnabled=false
+changeEvery=50
+height=40
+width=54
+
+[17-sand@340,343|16]
+changeEnabled=false
+changeEvery=50
+height=28
+width=56
+
+[17-sand@351,351|14]
+changeEnabled=false
+changeEvery=50
+height=38
+width=52
+
+[17-sand@359,354|13]
+changeEnabled=false
+changeEvery=50
+height=44
+width=50
+
+[17-sign@23,152|20]
+Comment=Luck
+Comment[ar]=حظ
+Comment[bg]=Късмет
+Comment[bn]=ভাগ্য
+Comment[br]=Chañs
+Comment[bs]=Sreća
+Comment[ca]=Sort
+Comment[cs]=Štěstí
+Comment[da]=Held
+Comment[de]=Glück
+Comment[el]=Τύχη
+Comment[es]=Suerte
+Comment[et]=Õnn
+Comment[fi]=Onni
+Comment[fr]=Chance
+Comment[gl]=Sorte
+Comment[he]=מזל
+Comment[hi]=भाग्य
+Comment[hr]=Sreća
+Comment[hu]=Szerencse
+Comment[is]=Lukka
+Comment[it]=Fortunato
+Comment[ja]=運
+Comment[lv]=Veiksme
+Comment[mk]=Среќа
+Comment[nb]=Flaks
+Comment[nl]=Geluk
+Comment[nn]=Flaks
+Comment[nso]=Mahlatse
+Comment[pl]=Szczęście
+Comment[pt]=Sorte
+Comment[pt_BR]=Sorte
+Comment[ro]=Noroc
+Comment[ru]=Удача
+Comment[sk]=Šťastie
+Comment[sl]=Sreča
+Comment[sr]=Срећа
+Comment[sr@Latn]=Sreća
+Comment[sv]=Tur
+Comment[ta]=அதிர்ஷ்டம்
+Comment[tg]=Барор
+Comment[tr]=Şans
+Comment[uk]=удача
+Comment[ven]=Mashudu
+Comment[wa]=Tchance
+Comment[xh]=Ithamsanqa
+Comment[xx]=xxLuckxx
+Comment[zh_CN]=幸运
+Comment[zh_TW]=幸運
+Comment[zu]=Inhlanhla
+botWallVisible=false
+height=51
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=105
+
+[17-slope@113,18|9]
+grade=2
+gradient=Elliptic
+height=183
+reversed=false
+stuckOnGround=false
+width=183
+
+[17-slope@237,229|5]
+grade=5
+gradient=Vertical
+height=109
+reversed=false
+stuckOnGround=false
+width=153
+
+[17-slope@289,85|19]
+grade=2
+gradient=Elliptic
+height=146
+reversed=false
+stuckOnGround=false
+width=146
+
+[17-slope@43,331|11]
+grade=8
+gradient=Horizontal
+height=58
+reversed=true
+stuckOnGround=false
+width=27
+
+[17-slope@71,246|10]
+grade=2
+gradient=Vertical
+height=43
+reversed=false
+stuckOnGround=false
+width=91
+
+[17-slope@77,297|18]
+grade=3
+gradient=Diagonal
+height=89
+reversed=false
+stuckOnGround=false
+width=84
+
+[17-wall@0,0|0]
+endPoint=235,141
+startPoint=12,141
+
+[17-wall@0,0|12]
+endPoint=70,330
+startPoint=44,330
+
+[17-wall@0,0|17]
+endPoint=43,389
+startPoint=13,332
+
+[17-wall@0,0|2]
+endPoint=373,113
+startPoint=311,33
+
+[17-wall@0,0|22]
+endPoint=131,289
+startPoint=119,298
+
+[17-wall@0,0|23]
+endPoint=119,298
+startPoint=109,288
+
+[17-wall@0,0|3]
+endPoint=235,141
+startPoint=235,387
+
+[17-wall@0,0|6]
+endPoint=162,217
+startPoint=70,217
+
+[17-wall@0,0|7]
+endPoint=162,217
+startPoint=162,388
+
+[17-wall@0,0|8]
+endPoint=70,217
+startPoint=70,330
+
+[18-ball@134,349]
+dummykey=true
+
+[18-blackhole@362,297|7]
+exit=207,44
+exitDeg=0
+maxspeed=2
+minspeed=1
+
+[18-cup@309,150|0]
+dummykey=true
+
+[18-floater@55,53|1]
+botWallVisible=false
+endPoint=55,53
+height=119
+leftWallVisible=false
+rightWallVisible=false
+speed=8
+startPoint=42,235
+topWallVisible=false
+width=28
+
+[18-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=true
+maxstrokes=10
+par=2
+
+[18-puddle@104,229|32]
+changeEnabled=false
+changeEvery=50
+height=32
+width=174
+
+[18-puddle@238,220|2]
+changeEnabled=false
+changeEvery=50
+height=52
+width=404
+
+[18-puddle@279,199|5]
+changeEnabled=false
+changeEvery=50
+height=26
+width=100
+
+[18-puddle@307,201|4]
+changeEnabled=false
+changeEvery=50
+height=26
+width=244
+
+[18-puddle@33,222|33]
+changeEnabled=false
+changeEvery=50
+height=28
+width=42
+
+[18-puddle@50,218|34]
+changeEnabled=false
+changeEvery=50
+height=22
+width=52
+
+[18-puddle@605,204|3]
+changeEnabled=false
+changeEvery=50
+height=90
+width=750
+
+[18-sand@-35,168|17]
+changeEnabled=false
+changeEvery=50
+height=40
+width=152
+
+[18-sand@17,105|16]
+changeEnabled=false
+changeEvery=50
+height=152
+width=106
+
+[18-sand@36,11|15]
+changeEnabled=false
+changeEvery=50
+height=272
+width=86
+
+[18-sand@43,152|19]
+changeEnabled=false
+changeEvery=50
+height=50
+width=104
+
+[18-sand@51,106|20]
+changeEnabled=false
+changeEvery=50
+height=60
+width=54
+
+[18-sand@58,167|26]
+changeEnabled=false
+changeEvery=50
+height=26
+width=48
+
+[18-sand@63,83|23]
+changeEnabled=false
+changeEvery=50
+height=36
+width=24
+
+[18-sand@65,164|25]
+changeEnabled=false
+changeEvery=50
+height=38
+width=50
+
+[18-sand@69,119|22]
+changeEnabled=false
+changeEvery=50
+height=44
+width=34
+
+[18-sand@69,139|21]
+changeEnabled=false
+changeEvery=50
+height=70
+width=60
+
+[18-sand@69,149|24]
+changeEnabled=false
+changeEvery=50
+height=82
+width=56
+
+[18-slope@145,-5|8]
+grade=7
+gradient=Horizontal
+height=409
+reversed=false
+stuckOnGround=true
+width=129
+
+[18-slope@269,244|28]
+grade=4
+gradient=Opposite Diagonal
+height=185
+reversed=true
+stuckOnGround=false
+width=132
+
+[18-slope@30,233|18]
+grade=4
+gradient=Vertical
+height=34
+reversed=true
+stuckOnGround=true
+width=98
+
+[18-slope@31,323|12]
+grade=6
+gradient=Vertical
+height=43
+reversed=false
+stuckOnGround=true
+width=52
+
+[18-slope@32,243|14]
+grade=5
+gradient=Horizontal
+height=125
+reversed=true
+stuckOnGround=true
+width=20
+
+[18-slope@50,241|10]
+grade=5
+gradient=Horizontal
+height=163
+reversed=false
+stuckOnGround=true
+width=76
+
+[18-wall@0,0|11]
+endPoint=84,367
+startPoint=35,369
+
+[18-wall@0,0|13]
+endPoint=84,328
+startPoint=84,367
+
+[18-wall@0,0|27]
+endPoint=385,123
+startPoint=385,166
+
+[18-wall@0,0|29]
+endPoint=422,377
+startPoint=226,377
+
+[18-wall@0,0|30]
+endPoint=91,380
+startPoint=84,328
+
+[18-wall@0,0|31]
+endPoint=91,380
+startPoint=35,369
+
+[18-wall@0,0|9]
+endPoint=31,244
+startPoint=31,400
+
+[18-windmill@201,-2|6]
+botWallVisible=false
+bottom=true
+height=121
+leftWallVisible=true
+rightWallVisible=false
+speed=5
+topWallVisible=false
+width=201
+
+[2-ball@62,329]
+dummykey=true
+
+[2-cup@71,229|3]
+dummykey=true
+
+[2-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[2-puddle@285,-3|2]
+changeEnabled=false
+changeEvery=50
+height=42
+width=20
+
+[2-puddle@315,13|12]
+changeEnabled=false
+changeEvery=50
+height=32
+width=70
+
+[2-puddle@331,-5|1]
+changeEnabled=false
+changeEvery=50
+height=68
+width=76
+
+[2-puddle@364,0|9]
+changeEnabled=false
+changeEvery=50
+height=58
+width=132
+
+[2-sand@15,163|19]
+changeEnabled=false
+changeEvery=50
+height=86
+width=66
+
+[2-sand@31,76|16]
+changeEnabled=false
+changeEvery=50
+height=58
+width=42
+
+[2-sand@76,33|17]
+changeEnabled=false
+changeEvery=50
+height=36
+width=52
+
+[2-slope@1,124|14]
+grade=4
+gradient=Vertical
+height=146
+reversed=false
+stuckOnGround=false
+width=179
+
+[2-slope@111,100|4]
+grade=3
+gradient=Diagonal
+height=301
+reversed=false
+stuckOnGround=false
+width=291
+
+[2-slope@181,130|0]
+grade=4
+gradient=Vertical
+height=32
+reversed=true
+stuckOnGround=false
+width=119
+
+[2-slope@255,9|19]
+grade=4
+gradient=Opposite Diagonal
+height=34
+reversed=true
+stuckOnGround=false
+width=36
+
+[2-slope@256,-12|20]
+grade=4
+gradient=Horizontal
+height=25
+reversed=false
+stuckOnGround=false
+width=30
+
+[2-slope@290,10|18]
+grade=4
+gradient=Vertical
+height=34
+reversed=true
+stuckOnGround=false
+width=111
+
+[2-wall@0,0|10]
+endPoint=298,169
+startPoint=278,188
+
+[2-wall@0,0|11]
+endPoint=181,200
+startPoint=191,188
+
+[2-wall@0,0|15]
+endPoint=179,178
+startPoint=191,186
+
+[2-wall@0,0|18]
+endPoint=278,20
+startPoint=115,20
+
+[2-wall@0,0|5]
+endPoint=180,270
+startPoint=3,272
+
+[2-wall@0,0|6]
+endPoint=179,127
+startPoint=180,270
+
+[2-wall@0,0|7]
+endPoint=191,188
+startPoint=278,188
+
+[2-wall@0,0|8]
+endPoint=298,169
+startPoint=298,103
+
+[3-ball@55,341]
+dummykey=true
+
+[3-blackhole@85,70|9]
+exit=166,79
+exitDeg=269
+maxspeed=4
+minspeed=1
+
+[3-cup@252,186|3]
+dummykey=true
+
+[3-floater@87,154|13]
+botWallVisible=true
+endPoint=87,154
+height=14
+leftWallVisible=true
+rightWallVisible=true
+speed=8
+startPoint=29,78
+topWallVisible=true
+width=15
+
+[3-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=true
+maxstrokes=10
+par=2
+
+[3-sand@42,34|11]
+changeEnabled=false
+changeEvery=50
+height=50
+width=40
+
+[3-sand@50,28|14]
+changeEnabled=false
+changeEvery=50
+height=26
+width=32
+
+[3-sand@59,20|12]
+changeEnabled=false
+changeEvery=50
+height=28
+width=64
+
+[3-sand@79,26|0]
+changeEnabled=false
+changeEvery=50
+height=28
+width=26
+
+[3-slope@0,166|10]
+grade=3
+gradient=Vertical
+height=45
+reversed=false
+stuckOnGround=false
+width=134
+
+[3-slope@136,74|4]
+grade=4
+gradient=Horizontal
+height=234
+reversed=true
+stuckOnGround=true
+width=205
+
+[3-wall@0,0|6]
+endPoint=338,308
+startPoint=132,307
+
+[3-wall@0,0|7]
+endPoint=132,307
+startPoint=136,75
+
+[3-wall@0,0|8]
+endPoint=343,77
+startPoint=338,308
+
+[3-wall@0,0|9]
+endPoint=343,77
+startPoint=136,75
+
+[4-ball@125,336]
+dummykey=true
+
+[4-cup@321,206|3]
+dummykey=true
+
+[4-floater@286,190|4]
+botWallVisible=true
+endPoint=286,190
+height=40
+leftWallVisible=true
+rightWallVisible=true
+speed=19
+startPoint=78,89
+topWallVisible=false
+width=80
+
+[4-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=true
+maxstrokes=10
+par=2
+
+[4-puddle@-22,215|6]
+changeEnabled=false
+changeEvery=50
+height=228
+width=204
+
+[4-slope@-1,280|9]
+grade=4
+gradient=Diagonal
+height=89
+reversed=true
+stuckOnGround=false
+width=102
+
+[4-slope@-4,77|7]
+grade=4
+gradient=Opposite Diagonal
+height=74
+reversed=false
+stuckOnGround=false
+width=102
+
+[4-slope@-81,6|5]
+grade=4
+gradient=Opposite Diagonal
+height=295
+reversed=true
+stuckOnGround=true
+width=482
+
+[4-slope@37,150|8]
+grade=4
+gradient=Horizontal
+height=131
+reversed=true
+stuckOnGround=false
+width=60
+
+[5-ball@106,356]
+dummykey=true
+
+[5-blackhole@167,37|21]
+exit=212,97
+exitDeg=339
+maxspeed=2
+minspeed=1
+
+[5-bridge@156,148|17]
+botWallVisible=true
+height=23
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=true
+width=46
+
+[5-cup@237,229|3]
+dummykey=true
+
+[5-cup@249,34|33]
+dummykey=true
+
+[5-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[5-puddle@213,14|39]
+changeEnabled=false
+changeEvery=50
+height=44
+width=34
+
+[5-puddle@218,15|15]
+changeEnabled=false
+changeEvery=50
+height=32
+width=30
+
+[5-puddle@223,13|13]
+changeEnabled=false
+changeEvery=50
+height=26
+width=32
+
+[5-puddle@248,1|37]
+changeEnabled=false
+changeEvery=50
+height=42
+width=76
+
+[5-puddle@262,57|38]
+changeEnabled=false
+changeEvery=50
+height=22
+width=48
+
+[5-puddle@271,59|30]
+changeEnabled=false
+changeEvery=50
+height=22
+width=38
+
+[5-puddle@279,22|41]
+changeEnabled=true
+changeEvery=28
+height=50
+width=26
+
+[5-sand@-20,411|36]
+changeEnabled=false
+changeEvery=50
+height=172
+width=274
+
+[5-sand@-28,349|37]
+changeEnabled=false
+changeEvery=50
+height=312
+width=146
+
+[5-sand@23,345|42]
+changeEnabled=false
+changeEvery=50
+height=46
+width=54
+
+[5-sand@40,354|43]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[5-slope@156,152|19]
+grade=4
+gradient=Horizontal
+height=15
+reversed=true
+stuckOnGround=false
+width=48
+
+[5-slope@203,195|34]
+grade=2
+gradient=Vertical
+height=23
+reversed=true
+stuckOnGround=true
+width=70
+
+[5-slope@204,239|35]
+grade=2
+gradient=Vertical
+height=24
+reversed=false
+stuckOnGround=true
+width=68
+
+[5-slope@284,-2|32]
+grade=3
+gradient=Opposite Diagonal
+height=140
+reversed=true
+stuckOnGround=false
+width=114
+
+[5-slope@294,186|27]
+grade=2
+gradient=Vertical
+height=74
+reversed=true
+stuckOnGround=true
+width=36
+
+[5-slope@295,301|31]
+grade=8
+gradient=Diagonal
+height=99
+reversed=false
+stuckOnGround=false
+width=106
+
+[5-slope@56,225|19]
+grade=4
+gradient=Vertical
+height=58
+reversed=true
+stuckOnGround=true
+width=99
+
+[5-slope@57,39|16]
+grade=6
+gradient=Vertical
+height=33
+reversed=false
+stuckOnGround=true
+width=97
+
+[5-slope@58,112|17]
+grade=4
+gradient=Vertical
+height=40
+reversed=true
+stuckOnGround=true
+width=99
+
+[5-slope@59,152|18]
+grade=2
+gradient=Horizontal
+height=10
+reversed=true
+stuckOnGround=false
+width=95
+
+[5-slope@59,163|18]
+grade=5
+gradient=Vertical
+height=41
+reversed=false
+stuckOnGround=true
+width=95
+
+[5-slope@61,23|20]
+grade=2
+gradient=Horizontal
+height=16
+reversed=true
+stuckOnGround=true
+width=118
+
+[5-wall@0,0|10]
+endPoint=330,96
+startPoint=330,362
+
+[5-wall@0,0|11]
+endPoint=330,362
+startPoint=201,316
+
+[5-wall@0,0|12]
+endPoint=201,316
+startPoint=202,170
+
+[5-wall@0,0|14]
+endPoint=156,170
+startPoint=154,318
+
+[5-wall@0,0|22]
+endPoint=293,175
+startPoint=202,148
+
+[5-wall@0,0|23]
+endPoint=293,175
+startPoint=294,286
+
+[5-wall@0,0|24]
+endPoint=294,286
+startPoint=264,301
+
+[5-wall@0,0|25]
+endPoint=270,263
+startPoint=270,188
+
+[5-wall@0,0|26]
+endPoint=270,188
+startPoint=202,170
+
+[5-wall@0,0|28]
+endPoint=157,54
+startPoint=185,53
+
+[5-wall@0,0|29]
+endPoint=185,53
+startPoint=182,21
+
+[5-wall@0,0|4]
+endPoint=57,21
+startPoint=57,321
+
+[5-wall@0,0|5]
+endPoint=57,21
+startPoint=182,21
+
+[5-wall@0,0|6]
+endPoint=157,54
+startPoint=156,148
+
+[5-wall@0,0|8]
+endPoint=202,148
+startPoint=200,56
+
+[5-wall@0,0|9]
+endPoint=200,56
+startPoint=330,96
+
+[5-windmill@332,149|40]
+botWallVisible=false
+bottom=false
+height=71
+leftWallVisible=false
+rightWallVisible=false
+speed=9
+topWallVisible=false
+width=75
+
+[6-ball@82,356]
+dummykey=true
+
+[6-cup@258,84|3]
+dummykey=true
+
+[6-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[6-puddle@200,8|6]
+changeEnabled=false
+changeEvery=50
+height=166
+width=88
+
+[6-puddle@221,61|25]
+changeEnabled=false
+changeEvery=50
+height=24
+width=28
+
+[6-puddle@229,53|2]
+changeEnabled=false
+changeEvery=50
+height=28
+width=30
+
+[6-puddle@240,58|24]
+changeEnabled=false
+changeEvery=50
+height=14
+width=46
+
+[6-puddle@256,23|8]
+changeEnabled=false
+changeEvery=50
+height=80
+width=150
+
+[6-puddle@274,57|1]
+changeEnabled=false
+changeEvery=50
+height=16
+width=50
+
+[6-puddle@283,55|0]
+changeEnabled=false
+changeEvery=50
+height=26
+width=32
+
+[6-puddle@307,10|7]
+changeEnabled=false
+changeEvery=50
+height=166
+width=74
+
+[6-sand@102,98|26]
+changeEnabled=false
+changeEvery=50
+height=46
+width=38
+
+[6-sand@331,215|9]
+changeEnabled=false
+changeEvery=50
+height=62
+width=74
+
+[6-sand@333,148|11]
+changeEnabled=false
+changeEvery=50
+height=36
+width=46
+
+[6-sand@333,158|34]
+changeEnabled=false
+changeEvery=50
+height=20
+width=22
+
+[6-sand@335,184|32]
+changeEnabled=false
+changeEvery=50
+height=24
+width=26
+
+[6-sand@338,141|27]
+changeEnabled=false
+changeEvery=50
+height=38
+width=38
+
+[6-sand@342,207|28]
+changeEnabled=false
+changeEvery=50
+height=66
+width=78
+
+[6-sand@343,133|10]
+changeEnabled=false
+changeEvery=50
+height=54
+width=38
+
+[6-sand@344,161|33]
+changeEnabled=false
+changeEvery=50
+height=32
+width=34
+
+[6-sand@348,198|29]
+changeEnabled=false
+changeEvery=50
+height=54
+width=66
+
+[6-sand@350,148|35]
+changeEnabled=false
+changeEvery=50
+height=28
+width=18
+
+[6-sand@350,168|36]
+changeEnabled=false
+changeEvery=50
+height=32
+width=30
+
+[6-sand@350,179|31]
+changeEnabled=false
+changeEvery=50
+height=42
+width=46
+
+[6-sand@361,188|30]
+changeEnabled=false
+changeEvery=50
+height=52
+width=42
+
+[6-sand@59,94|23]
+changeEnabled=false
+changeEvery=50
+height=94
+width=64
+
+[6-sand@73,100|22]
+changeEnabled=false
+changeEvery=50
+height=94
+width=90
+
+[6-sand@77,84|21]
+changeEnabled=false
+changeEvery=50
+height=84
+width=102
+
+[6-slope@-1,115|4]
+grade=3
+gradient=Vertical
+height=74
+reversed=false
+stuckOnGround=false
+width=404
+
+[6-slope@-2,215|5]
+grade=3
+gradient=Vertical
+height=81
+reversed=true
+stuckOnGround=false
+width=407
+
+[6-slope@140,61|16]
+grade=4
+gradient=Opposite Diagonal
+height=43
+reversed=true
+stuckOnGround=false
+width=43
+
+[6-slope@142,-1|17]
+grade=4
+gradient=Horizontal
+height=64
+reversed=false
+stuckOnGround=false
+width=36
+
+[6-slope@182,79|14]
+grade=4
+gradient=Vertical
+height=26
+reversed=true
+stuckOnGround=false
+width=32
+
+[6-slope@208,61|15]
+grade=4
+gradient=Diagonal
+height=46
+reversed=true
+stuckOnGround=false
+width=49
+
+[6-slope@242,48|12]
+grade=4
+gradient=Vertical
+height=27
+reversed=true
+stuckOnGround=false
+width=29
+
+[6-slope@270,64|13]
+grade=4
+gradient=Opposite Diagonal
+height=44
+reversed=true
+stuckOnGround=false
+width=30
+
+[6-slope@299,59|18]
+grade=4
+gradient=Vertical
+height=48
+reversed=true
+stuckOnGround=false
+width=25
+
+[6-slope@322,57|19]
+grade=4
+gradient=Diagonal
+height=50
+reversed=true
+stuckOnGround=false
+width=37
+
+[6-slope@324,-2|20]
+grade=4
+gradient=Horizontal
+height=59
+reversed=true
+stuckOnGround=false
+width=35
+
+[7-ball@53,349]
+dummykey=true
+
+[7-blackhole@111,125|20]
+exit=31,3
+exitDeg=270
+maxspeed=5
+minspeed=3
+
+[7-blackhole@146,160|19]
+exit=197,312
+exitDeg=90
+maxspeed=3
+minspeed=2
+
+[7-blackhole@99,169|21]
+exit=3,359
+exitDeg=0
+maxspeed=4
+minspeed=2
+
+[7-cup@118,151|3]
+dummykey=true
+
+[7-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[7-sand@306,70|11]
+changeEnabled=false
+changeEvery=50
+height=30
+width=36
+
+[7-sand@307,45|10]
+changeEnabled=false
+changeEvery=50
+height=72
+width=78
+
+[7-sand@322,78|9]
+changeEnabled=false
+changeEvery=50
+height=102
+width=50
+
+[7-sand@330,103|8]
+changeEnabled=false
+changeEvery=50
+height=70
+width=74
+
+[7-sand@331,56|13]
+changeEnabled=false
+changeEvery=50
+height=26
+width=28
+
+[7-sand@339,84|12]
+changeEnabled=false
+changeEvery=50
+height=32
+width=30
+
+[7-sand@40,238|16]
+changeEnabled=false
+changeEvery=50
+height=70
+width=56
+
+[7-sand@49,222|18]
+changeEnabled=false
+changeEvery=50
+height=26
+width=38
+
+[7-sand@53,257|19]
+changeEnabled=false
+changeEvery=50
+height=46
+width=64
+
+[7-sand@56,243|15]
+changeEnabled=false
+changeEvery=50
+height=60
+width=54
+
+[7-sand@62,239|17]
+changeEnabled=false
+changeEvery=50
+height=42
+width=52
+
+[7-sand@69,254|14]
+changeEnabled=false
+changeEvery=50
+height=62
+width=70
+
+[7-slope@159,316|23]
+grade=4
+gradient=Horizontal
+height=85
+reversed=true
+stuckOnGround=true
+width=46
+
+[7-slope@242,228|7]
+grade=4
+gradient=Diagonal
+height=173
+reversed=false
+stuckOnGround=true
+width=158
+
+[7-slope@43,12|18]
+grade=2
+gradient=Elliptic
+height=214
+reversed=false
+stuckOnGround=true
+width=214
+
+[7-slope@86,317|22]
+grade=4
+gradient=Horizontal
+height=84
+reversed=false
+stuckOnGround=true
+width=48
+
+[7-wall@0,0|6]
+endPoint=248,316
+startPoint=-3,316
+
+[8-ball@272,147]
+dummykey=true
+
+[8-blackhole@296,38|9]
+exit=254,371
+exitDeg=139
+maxspeed=5
+minspeed=3
+
+[8-cup@296,64|3]
+dummykey=true
+
+[8-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[8-puddle@345,350|12]
+changeEnabled=false
+changeEvery=50
+height=32
+width=52
+
+[8-puddle@354,401|11]
+changeEnabled=false
+changeEvery=50
+height=124
+width=168
+
+[8-puddle@400,8|30]
+changeEnabled=false
+changeEvery=50
+height=40
+width=34
+
+[8-puddle@402,-8|30]
+changeEnabled=false
+changeEvery=50
+height=50
+width=56
+
+[8-puddle@405,106|30]
+changeEnabled=false
+changeEvery=50
+height=78
+width=32
+
+[8-puddle@405,135|30]
+changeEnabled=false
+changeEvery=50
+height=48
+width=20
+
+[8-puddle@405,86|30]
+changeEnabled=false
+changeEvery=50
+height=64
+width=36
+
+[8-puddle@411,49|30]
+changeEnabled=false
+changeEvery=50
+height=122
+width=62
+
+[8-puddle@423,170|30]
+changeEnabled=false
+changeEvery=50
+height=154
+width=58
+
+[8-puddle@447,406|10]
+changeEnabled=false
+changeEvery=50
+height=318
+width=268
+
+[8-sand@144,184|15]
+changeEnabled=false
+changeEvery=50
+height=42
+width=32
+
+[8-sand@145,176|14]
+changeEnabled=false
+changeEvery=50
+height=36
+width=30
+
+[8-sand@75,139|35]
+changeEnabled=false
+changeEvery=50
+height=34
+width=34
+
+[8-sand@77,135|13]
+changeEnabled=false
+changeEvery=50
+height=28
+width=38
+
+[8-sand@81,138|28]
+changeEnabled=false
+changeEvery=50
+height=32
+width=30
+
+[8-slope@188,16|7]
+grade=2
+gradient=Horizontal
+height=73
+reversed=true
+stuckOnGround=false
+width=81
+
+[8-slope@214,112|23]
+grade=4
+gradient=Elliptic
+height=171
+reversed=false
+stuckOnGround=false
+width=171
+
+[8-slope@253,11|22]
+grade=3
+gradient=Elliptic
+height=80
+reversed=false
+stuckOnGround=false
+width=80
+
+[8-slope@317,14|8]
+grade=3
+gradient=Horizontal
+height=76
+reversed=true
+stuckOnGround=false
+width=53
+
+[8-slope@41,253|41]
+grade=4
+gradient=Opposite Diagonal
+height=132
+reversed=false
+stuckOnGround=false
+width=145
+
+[8-slope@45,89|34]
+grade=3
+gradient=Vertical
+height=142
+reversed=true
+stuckOnGround=false
+width=132
+
+[8-wall@0,0|10]
+endPoint=379,154
+startPoint=364,135
+
+[8-wall@0,0|11]
+endPoint=364,135
+startPoint=346,123
+
+[8-wall@0,0|12]
+endPoint=346,123
+startPoint=324,111
+
+[8-wall@0,0|13]
+endPoint=324,111
+startPoint=292,109
+
+[8-wall@0,0|14]
+endPoint=292,109
+startPoint=262,116
+
+[8-wall@0,0|15]
+endPoint=262,116
+startPoint=243,128
+
+[8-wall@0,0|16]
+endPoint=243,128
+startPoint=225,144
+
+[8-wall@0,0|17]
+endPoint=225,144
+startPoint=218,158
+
+[8-wall@0,0|18]
+endPoint=218,158
+startPoint=212,180
+
+[8-wall@0,0|19]
+endPoint=212,180
+startPoint=211,202
+
+[8-wall@0,0|2]
+endPoint=41,388
+startPoint=246,386
+
+[8-wall@0,0|20]
+endPoint=211,202
+startPoint=218,229
+
+[8-wall@0,0|21]
+endPoint=218,229
+startPoint=231,250
+
+[8-wall@0,0|22]
+endPoint=231,250
+startPoint=246,264
+
+[8-wall@0,0|24]
+endPoint=384,218
+startPoint=378,238
+
+[8-wall@0,0|25]
+endPoint=378,238
+startPoint=368,256
+
+[8-wall@0,0|26]
+endPoint=368,256
+startPoint=351,268
+
+[8-wall@0,0|27]
+endPoint=256,274
+startPoint=246,264
+
+[8-wall@0,0|29]
+endPoint=369,90
+startPoint=177,89
+
+[8-wall@0,0|3]
+endPoint=247,289
+startPoint=224,294
+
+[8-wall@0,0|31]
+endPoint=369,14
+startPoint=41,14
+
+[8-wall@0,0|32]
+endPoint=41,14
+startPoint=41,388
+
+[8-wall@0,0|33]
+endPoint=246,386
+startPoint=351,268
+
+[8-wall@0,0|34]
+endPoint=175,256
+startPoint=177,89
+
+[8-wall@0,0|4]
+endPoint=224,294
+startPoint=203,287
+
+[8-wall@0,0|45]
+endPoint=247,289
+startPoint=256,274
+
+[8-wall@0,0|5]
+endPoint=203,287
+startPoint=186,275
+
+[8-wall@0,0|6]
+endPoint=186,275
+startPoint=175,256
+
+[8-wall@0,0|7]
+endPoint=384,218
+startPoint=387,196
+
+[8-wall@0,0|8]
+endPoint=387,196
+startPoint=384,176
+
+[8-wall@0,0|9]
+endPoint=384,176
+startPoint=379,154
+
+[9-ball@25,24]
+dummykey=true
+
+[9-blackhole@118,60|0]
+exit=269,90
+exitDeg=270
+maxspeed=5
+minspeed=5
+
+[9-blackhole@149,72|7]
+exit=364,381
+exitDeg=120
+maxspeed=8
+minspeed=6
+
+[9-blackhole@273,274|18]
+exit=116,237
+exitDeg=20
+maxspeed=5
+minspeed=3
+
+[9-blackhole@311,345|4]
+exit=196,54
+exitDeg=169
+maxspeed=3
+minspeed=3
+
+[9-blackhole@68,112|12]
+exit=373,150
+exitDeg=269
+maxspeed=5
+minspeed=4
+
+[9-blackhole@81,56|8]
+exit=385,373
+exitDeg=160
+maxspeed=8
+minspeed=6
+
+[9-bridge@125,329|0]
+botWallVisible=true
+height=38
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=true
+width=195
+
+[9-bridge@191,260|9]
+botWallVisible=false
+height=36
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=32
+
+[9-bridge@319,329|0]
+botWallVisible=false
+height=36
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=30
+
+[9-bridge@323,138|0]
+botWallVisible=false
+height=192
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=false
+width=27
+
+[9-bumper@129,201|8]
+dummykey=true
+
+[9-bumper@152,151|4]
+dummykey=true
+
+[9-bumper@374,137|14]
+dummykey=true
+
+[9-cup@146,175|17]
+dummykey=true
+
+[9-cup@156,193|18]
+dummykey=true
+
+[9-cup@168,176|3]
+dummykey=true
+
+[9-cup@207,272|9]
+dummykey=true
+
+[9-floater@54,43|21]
+botWallVisible=false
+endPoint=54,43
+height=1
+leftWallVisible=false
+rightWallVisible=false
+speed=4
+startPoint=42,69
+topWallVisible=true
+width=19
+
+[9-floater@80,296|9]
+botWallVisible=false
+endPoint=80,296
+height=28
+leftWallVisible=false
+rightWallVisible=true
+speed=2
+startPoint=191,299
+topWallVisible=false
+width=30
+
+[9-floater@82,99|0]
+botWallVisible=false
+endPoint=82,99
+height=1
+leftWallVisible=false
+rightWallVisible=false
+speed=9
+startPoint=49,98
+topWallVisible=true
+width=10
+
+[9-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=true
+maxstrokes=10
+par=2
+
+[9-puddle@179,312|33]
+changeEnabled=false
+changeEvery=50
+height=70
+width=22
+
+[9-puddle@193,296|32]
+changeEnabled=false
+changeEvery=50
+height=82
+width=60
+
+[9-puddle@217,292|0]
+changeEnabled=false
+changeEvery=50
+height=94
+width=114
+
+[9-puddle@256,317|34]
+changeEnabled=false
+changeEvery=50
+height=78
+width=38
+
+[9-puddle@257,325|35]
+changeEnabled=false
+changeEvery=50
+height=42
+width=42
+
+[9-puddle@265,297|37]
+changeEnabled=false
+changeEvery=50
+height=58
+width=22
+
+[9-puddle@266,333|36]
+changeEnabled=false
+changeEvery=50
+height=28
+width=32
+
+[9-puddle@65,133|30]
+changeEnabled=false
+changeEvery=50
+height=30
+width=22
+
+[9-puddle@73,140|29]
+changeEnabled=false
+changeEvery=50
+height=36
+width=32
+
+[9-puddle@75,129|13]
+changeEnabled=false
+changeEvery=50
+height=30
+width=48
+
+[9-puddle@79,142|31]
+changeEnabled=false
+changeEvery=50
+height=22
+width=26
+
+[9-puddle@84,133|28]
+changeEnabled=false
+changeEvery=50
+height=32
+width=32
+
+[9-sand@100,369|27]
+changeEnabled=false
+changeEvery=50
+height=26
+width=52
+
+[9-sand@108,343|24]
+changeEnabled=false
+changeEvery=50
+height=60
+width=96
+
+[9-sand@130,350|7]
+changeEnabled=false
+changeEvery=50
+height=62
+width=90
+
+[9-sand@83,363|26]
+changeEnabled=false
+changeEvery=50
+height=56
+width=62
+
+[9-sand@91,353|25]
+changeEnabled=false
+changeEvery=50
+height=66
+width=66
+
+[9-sign@146,329|23]
+Comment=Chaos
+Comment[ar]=فوضى
+Comment[bg]=Хаос
+Comment[bn]=গোলমাল
+Comment[bs]=Haos
+Comment[ca]=Caos
+Comment[cy]=Anhrefn
+Comment[da]=Kaos
+Comment[el]=Χάος
+Comment[es]=Caos
+Comment[et]=Kaos
+Comment[fi]=Kaaos
+Comment[gl]=Caos
+Comment[he]=תוהו ובוהו
+Comment[hi]=अव्यवस्था
+Comment[hr]=Kaos
+Comment[hu]=Káosz
+Comment[is]=Kaos
+Comment[it]=Caos
+Comment[ja]=カオス
+Comment[mk]=Хаос
+Comment[nb]=Kaos
+Comment[nn]=Kaos
+Comment[pt]=Caos
+Comment[pt_BR]=Caos
+Comment[ro]=Haos
+Comment[ru]=Хаос
+Comment[sl]=Kaos
+Comment[sr]=Хаос
+Comment[sr@Latn]=Haos
+Comment[sv]=Kaos
+Comment[ta]= குழப்பம்
+Comment[tg]=Бесарусомонӣ
+Comment[tr]=Karmaşa
+Comment[uk]=Хаос
+Comment[ven]=Thaidzo
+Comment[xh]=Ingxwaba ngxwaba
+Comment[xx]=xxChaosxx
+Comment[zh_CN]=混沌
+Comment[zh_TW]=混沌
+Comment[zu]=Inyakanyaka
+botWallVisible=true
+height=38
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=true
+width=151
+
+[9-slope@11,355|22]
+grade=4
+gradient=Horizontal
+height=32
+reversed=true
+stuckOnGround=false
+width=35
+
+[9-slope@12,325|16]
+grade=4
+gradient=Opposite Diagonal
+height=31
+reversed=false
+stuckOnGround=false
+width=33
+
+[9-slope@127,48|12]
+grade=4
+gradient=Elliptic
+height=45
+reversed=true
+stuckOnGround=false
+width=45
+
+[9-slope@320,43|3]
+grade=7
+gradient=Horizontal
+height=41
+reversed=false
+stuckOnGround=false
+width=32
+
+[9-slope@321,83|8]
+grade=4
+gradient=Horizontal
+height=53
+reversed=false
+stuckOnGround=false
+width=30
+
+[9-slope@326,9|17]
+grade=7
+gradient=Opposite Diagonal
+height=72
+reversed=true
+stuckOnGround=false
+width=62
+
+[9-slope@50,94|15]
+grade=5
+gradient=Elliptic
+height=35
+reversed=true
+stuckOnGround=false
+width=35
+
+[9-slope@93,119|9]
+grade=8
+gradient=Elliptic
+height=125
+reversed=true
+stuckOnGround=true
+width=125
+
+[9-wall@0,0|0]
+endPoint=350,330
+startPoint=396,361
+
+[9-wall@0,0|1]
+endPoint=350,394
+startPoint=320,356
+
+[9-wall@0,0|10]
+endPoint=225,151
+startPoint=205,239
+
+[9-wall@0,0|11]
+endPoint=225,151
+startPoint=147,108
+
+[9-wall@0,0|12]
+endPoint=147,108
+startPoint=77,170
+
+[9-wall@0,0|13]
+endPoint=77,170
+startPoint=118,254
+
+[9-wall@0,0|16]
+endPoint=205,239
+startPoint=118,254
+
+[9-wall@0,0|18]
+endPoint=122,83
+startPoint=82,99
+
+[9-wall@0,0|19]
+endPoint=352,43
+startPoint=73,43
+
+[9-wall@0,0|20]
+endPoint=49,98
+startPoint=46,352
+
+[9-wall@0,0|3]
+endPoint=392,85
+startPoint=177,85
+
+[9-wall@0,0|38]
+endPoint=177,85
+startPoint=158,93
+
+[9-wall@0,0|39]
+endPoint=158,93
+startPoint=136,93
+
+[9-wall@0,0|40]
+endPoint=136,93
+startPoint=122,83
+
+[9-windmill@235,171|3]
+botWallVisible=true
+bottom=false
+height=119
+leftWallVisible=true
+rightWallVisible=true
+speed=2
+topWallVisible=false
+width=75
diff --git a/kolf/courses/Makefile.am b/kolf/courses/Makefile.am
new file mode 100644
index 00000000..d9953953
--- /dev/null
+++ b/kolf/courses/Makefile.am
@@ -0,0 +1,4 @@
+coursedir = $(kde_datadir)/kolf/courses
+course_DATA = Classic.kolf Hard.kolf Medium.kolf Easy.kolf ReallyEasy Practice Impossible USApro
+
+EXTRA_DIST=$(course_DATA)
diff --git a/kolf/courses/Medium.kolf b/kolf/courses/Medium.kolf
new file mode 100644
index 00000000..b232a4d7
--- /dev/null
+++ b/kolf/courses/Medium.kolf
@@ -0,0 +1,2557 @@
+[0-course@-50,-50]
+Name=Medium Course
+Name[af]=Medium Natuurlik
+Name[bg]=Средно
+Name[bn]=মধ্যম কোর্স
+Name[bs]=Srednji teren
+Name[ca]=Camp medi
+Name[cs]=Střední kurz
+Name[da]=Middelsvær bane
+Name[de]=Mittel
+Name[el]=Μέτρια πίστα
+Name[es]=Campo medio
+Name[et]=Keskmiselt raske väljak
+Name[fi]=Keskivaikea kenttä
+Name[fr]=Parcours moyen
+Name[gl]=Campo medio
+Name[he]=מסלול בינוני
+Name[hi]=साधारण कोर्स
+Name[hu]=Közepes nehézségű pálya
+Name[is]=Meðal erfið leið
+Name[it]=Percorso medio
+Name[ja]=普通のコース
+Name[mk]=Среден терен
+Name[nb]=Middels vanskelig bane
+Name[nl]=Gemiddeld parcours
+Name[nn]=Middels vanskeleg bane
+Name[nso]=Course ya Magareng
+Name[pl]=Średni tor
+Name[pt]=Percurso Médio
+Name[pt_BR]=Curso Médio
+Name[ro]=Cale medie
+Name[ru]=Средняя площадка
+Name[sk]=Stredne ťažké ihrisko
+Name[sl]=Srednje igrišče
+Name[sr]=Средњи терен
+Name[sr@Latn]=Srednji teren
+Name[sv]=Normal bana
+Name[ta]=நடுத்தரமான மார்க்கம்
+Name[tg]=Майдони Миёна
+Name[tr]=Orta Seviye Pist
+Name[uk]=Середній майданчик
+Name[ven]= Thero yo Linganelaho
+Name[xh]=Indlela ephakathi
+Name[xx]=xxMedium Coursexx
+Name[zh_CN]=中级路线
+Name[zh_TW]=中等路線
+Name[zu]=Indlela ephakathi
+author=Jason Katz-Brown
+name=Medium Course
+
+[1-ball@342,322]
+dummykey=true
+
+[1-cup@325,52|14]
+dummykey=true
+
+[1-floater@100,208|24]
+botWallVisible=true
+endPoint=100,208
+height=10
+leftWallVisible=true
+rightWallVisible=true
+speed=2
+startPoint=16,189
+topWallVisible=true
+width=19
+
+[1-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=true
+maxstrokes=10
+par=3
+
+[1-puddle@282,195|9]
+changeEnabled=false
+changeEvery=50
+height=38
+width=66
+
+[1-puddle@350,189|8]
+changeEnabled=false
+changeEvery=50
+height=40
+width=192
+
+[1-puddle@378,206|11]
+changeEnabled=false
+changeEvery=50
+height=32
+width=232
+
+[1-slope@177,86|16]
+grade=4
+gradient=Diagonal
+height=115
+reversed=false
+stuckOnGround=false
+width=54
+
+[1-slope@193,240|22]
+grade=4
+gradient=Diagonal
+height=148
+reversed=false
+stuckOnGround=false
+width=194
+
+[1-slope@231,90|15]
+grade=4
+gradient=Vertical
+height=108
+reversed=false
+stuckOnGround=false
+width=156
+
+[1-slope@291,17|19]
+grade=2
+gradient=Elliptic
+height=67
+reversed=true
+stuckOnGround=false
+width=67
+
+[1-wall@0,0|13]
+endPoint=208,200
+startPoint=180,199
+
+[1-wall@0,0|17]
+endPoint=180,199
+startPoint=148,213
+
+[1-wall@0,0|21]
+endPoint=389,236
+startPoint=307,234
+
+[1-wall@0,0|22]
+endPoint=307,234
+startPoint=255,218
+
+[1-wall@0,0|23]
+endPoint=255,218
+startPoint=229,215
+
+[1-wall@0,0|24]
+endPoint=208,200
+startPoint=229,215
+
+[1-wall@0,0|3]
+endPoint=103,388
+startPoint=11,304
+
+[1-wall@0,0|4]
+endPoint=126,11
+startPoint=10,128
+
+[10-ball@343,254]
+dummykey=true
+
+[10-bumper@114,160|14]
+dummykey=true
+
+[10-bumper@151,79|11]
+dummykey=true
+
+[10-bumper@166,233|9]
+dummykey=true
+
+[10-bumper@179,304|15]
+dummykey=true
+
+[10-bumper@258,99|20]
+dummykey=true
+
+[10-bumper@356,86|19]
+dummykey=true
+
+[10-bumper@43,206|13]
+dummykey=true
+
+[10-bumper@54,90|10]
+dummykey=true
+
+[10-bumper@67,284|12]
+dummykey=true
+
+[10-cup@310,114|16]
+dummykey=true
+
+[10-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[10-slope@11,11|7]
+grade=4
+gradient=Vertical
+height=47
+reversed=true
+stuckOnGround=false
+width=379
+
+[10-slope@11,121|3]
+grade=4
+gradient=Vertical
+height=217
+reversed=false
+stuckOnGround=false
+width=213
+
+[10-slope@221,118|6]
+grade=2
+gradient=Opposite Diagonal
+height=63
+reversed=false
+stuckOnGround=false
+width=63
+
+[10-wall@0,0|17]
+endPoint=390,148
+startPoint=354,183
+
+[10-wall@0,0|18]
+endPoint=355,182
+startPoint=388,251
+
+[10-wall@0,0|4]
+endPoint=223,335
+startPoint=223,182
+
+[10-wall@0,0|5]
+endPoint=354,183
+startPoint=223,182
+
+[10-wall@0,0|8]
+endPoint=388,251
+startPoint=309,389
+
+[11-ball@36,295]
+dummykey=true
+
+[11-cup@74,372|72]
+dummykey=true
+
+[11-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[11-sand@270,47|71]
+changeEnabled=false
+changeEvery=50
+height=40
+width=50
+
+[11-sand@279,52|70]
+changeEnabled=false
+changeEvery=50
+height=46
+width=42
+
+[11-slope@12,103|73]
+grade=6
+gradient=Opposite Diagonal
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[11-slope@121,106|62]
+grade=1
+gradient=Elliptic
+height=222
+reversed=true
+stuckOnGround=false
+width=222
+
+[11-slope@31,91|73]
+grade=6
+gradient=Opposite Diagonal
+height=41
+reversed=false
+stuckOnGround=false
+width=35
+
+[11-slope@47,225|59]
+grade=6
+gradient=Vertical
+height=24
+reversed=false
+stuckOnGround=false
+width=37
+
+[11-slope@50,88|61]
+grade=6
+gradient=Horizontal
+height=42
+reversed=true
+stuckOnGround=false
+width=36
+
+[11-slope@55,352|74]
+grade=4
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[11-slope@8,153|60]
+grade=6
+gradient=Vertical
+height=28
+reversed=false
+stuckOnGround=false
+width=48
+
+[11-wall@0,0|10]
+endPoint=168,354
+startPoint=132,383
+
+[11-wall@0,0|11]
+endPoint=193,351
+startPoint=168,354
+
+[11-wall@0,0|12]
+endPoint=239,335
+startPoint=193,351
+
+[11-wall@0,0|13]
+endPoint=276,340
+startPoint=239,335
+
+[11-wall@0,0|14]
+endPoint=311,328
+startPoint=276,340
+
+[11-wall@0,0|15]
+endPoint=335,299
+startPoint=311,328
+
+[11-wall@0,0|16]
+endPoint=345,270
+startPoint=335,299
+
+[11-wall@0,0|17]
+endPoint=358,234
+startPoint=345,270
+
+[11-wall@0,0|18]
+endPoint=374,200
+startPoint=358,234
+
+[11-wall@0,0|19]
+endPoint=374,200
+startPoint=370,166
+
+[11-wall@0,0|20]
+endPoint=370,166
+startPoint=358,136
+
+[11-wall@0,0|21]
+endPoint=358,136
+startPoint=343,109
+
+[11-wall@0,0|22]
+endPoint=343,109
+startPoint=340,67
+
+[11-wall@0,0|23]
+endPoint=166,92
+startPoint=168,109
+
+[11-wall@0,0|24]
+endPoint=166,92
+startPoint=153,82
+
+[11-wall@0,0|25]
+endPoint=153,82
+startPoint=112,94
+
+[11-wall@0,0|26]
+endPoint=112,94
+startPoint=80,119
+
+[11-wall@0,0|27]
+endPoint=80,119
+startPoint=55,128
+
+[11-wall@0,0|28]
+endPoint=55,128
+startPoint=44,147
+
+[11-wall@0,0|29]
+endPoint=55,180
+startPoint=44,147
+
+[11-wall@0,0|3]
+endPoint=159,119
+startPoint=139,125
+
+[11-wall@0,0|30]
+endPoint=74,196
+startPoint=55,180
+
+[11-wall@0,0|32]
+endPoint=74,196
+startPoint=86,265
+
+[11-wall@0,0|33]
+endPoint=86,265
+startPoint=80,290
+
+[11-wall@0,0|34]
+endPoint=80,290
+startPoint=58,312
+
+[11-wall@0,0|35]
+endPoint=58,312
+startPoint=31,324
+
+[11-wall@0,0|36]
+endPoint=168,109
+startPoint=159,119
+
+[11-wall@0,0|37]
+endPoint=16,318
+startPoint=31,324
+
+[11-wall@0,0|38]
+endPoint=7,298
+startPoint=16,318
+
+[11-wall@0,0|39]
+endPoint=17,280
+startPoint=7,298
+
+[11-wall@0,0|4]
+endPoint=96,273
+startPoint=99,233
+
+[11-wall@0,0|40]
+endPoint=41,270
+startPoint=17,280
+
+[11-wall@0,0|41]
+endPoint=51,250
+startPoint=41,270
+
+[11-wall@0,0|42]
+endPoint=46,234
+startPoint=51,250
+
+[11-wall@0,0|43]
+endPoint=38,216
+startPoint=46,234
+
+[11-wall@0,0|44]
+endPoint=21,193
+startPoint=38,216
+
+[11-wall@0,0|45]
+endPoint=7,164
+startPoint=21,193
+
+[11-wall@0,0|46]
+endPoint=9,128
+startPoint=7,164
+
+[11-wall@0,0|47]
+endPoint=26,105
+startPoint=9,128
+
+[11-wall@0,0|48]
+endPoint=60,89
+startPoint=26,105
+
+[11-wall@0,0|49]
+endPoint=88,70
+startPoint=60,89
+
+[11-wall@0,0|5]
+endPoint=112,204
+startPoint=109,160
+
+[11-wall@0,0|50]
+endPoint=129,47
+startPoint=88,70
+
+[11-wall@0,0|51]
+endPoint=158,15
+startPoint=129,47
+
+[11-wall@0,0|52]
+endPoint=177,7
+startPoint=158,15
+
+[11-wall@0,0|53]
+endPoint=203,7
+startPoint=177,7
+
+[11-wall@0,0|54]
+endPoint=203,7
+startPoint=244,16
+
+[11-wall@0,0|55]
+endPoint=279,16
+startPoint=244,16
+
+[11-wall@0,0|56]
+endPoint=305,19
+startPoint=279,16
+
+[11-wall@0,0|57]
+endPoint=326,35
+startPoint=305,19
+
+[11-wall@0,0|58]
+endPoint=326,35
+startPoint=340,67
+
+[11-wall@0,0|6]
+endPoint=139,125
+startPoint=109,160
+
+[11-wall@0,0|63]
+endPoint=88,336
+startPoint=64,342
+
+[11-wall@0,0|64]
+endPoint=23,369
+startPoint=34,357
+
+[11-wall@0,0|65]
+endPoint=64,342
+startPoint=34,357
+
+[11-wall@0,0|66]
+endPoint=27,384
+startPoint=23,369
+
+[11-wall@0,0|67]
+endPoint=55,395
+startPoint=27,384
+
+[11-wall@0,0|68]
+endPoint=103,391
+startPoint=55,395
+
+[11-wall@0,0|69]
+endPoint=132,383
+startPoint=103,391
+
+[11-wall@0,0|7]
+endPoint=112,204
+startPoint=99,233
+
+[11-wall@0,0|8]
+endPoint=100,309
+startPoint=96,273
+
+[11-wall@0,0|9]
+endPoint=88,336
+startPoint=100,309
+
+[12-ball@47,53]
+dummykey=true
+
+[12-blackhole@375,294|12]
+exit=137,36
+exitDeg=30
+maxspeed=5
+minspeed=3
+
+[12-bumper@189,94|17]
+dummykey=true
+
+[12-cup@216,137|14]
+dummykey=true
+
+[12-cup@374,244|8]
+dummykey=true
+
+[12-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[12-sand@258,131|15]
+changeEnabled=false
+changeEvery=50
+height=54
+width=42
+
+[12-sand@260,123|16]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[12-slope@10,166|3]
+grade=4
+gradient=Vertical
+height=221
+reversed=false
+stuckOnGround=false
+width=381
+
+[12-slope@238,7|13]
+grade=4
+gradient=Opposite Diagonal
+height=124
+reversed=true
+stuckOnGround=false
+width=150
+
+[12-wall@0,0|4]
+endPoint=387,219
+startPoint=290,274
+
+[12-wall@0,0|5]
+endPoint=389,266
+startPoint=284,324
+
+[12-wall@0,0|6]
+endPoint=117,12
+startPoint=200,165
+
+[12-wall@0,0|7]
+endPoint=386,165
+startPoint=200,165
+
+[13-ball@332,39]
+dummykey=true
+
+[13-cup@44,365|9]
+dummykey=true
+
+[13-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[13-puddle@2,77|18]
+changeEnabled=false
+changeEvery=50
+height=86
+width=138
+
+[13-puddle@33,3|17]
+changeEnabled=false
+changeEvery=50
+height=134
+width=310
+
+[13-puddle@49,53|16]
+changeEnabled=false
+changeEvery=50
+height=64
+width=98
+
+[13-sand@352,347|10]
+changeEnabled=false
+changeEvery=50
+height=72
+width=104
+
+[13-sand@361,324|17]
+changeEnabled=false
+changeEvery=50
+height=34
+width=56
+
+[13-sand@370,408|11]
+changeEnabled=false
+changeEvery=50
+height=172
+width=290
+
+[13-sand@374,321|18]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[13-sand@389,336|13]
+changeEnabled=false
+changeEvery=50
+height=98
+width=62
+
+[13-slope@11,232|23]
+grade=1
+gradient=Opposite Diagonal
+height=155
+reversed=false
+stuckOnGround=false
+width=165
+
+[13-slope@128,111|5]
+grade=4
+gradient=Elliptic
+height=94
+reversed=true
+stuckOnGround=false
+width=94
+
+[13-slope@177,207|6]
+grade=3
+gradient=Elliptic
+height=113
+reversed=false
+stuckOnGround=false
+width=113
+
+[13-slope@235,79|7]
+grade=5
+gradient=Elliptic
+height=83
+reversed=false
+stuckOnGround=false
+width=83
+
+[13-slope@282,173|8]
+grade=4
+gradient=Elliptic
+height=65
+reversed=true
+stuckOnGround=false
+width=65
+
+[13-slope@47,105|4]
+grade=3
+gradient=Elliptic
+height=70
+reversed=false
+stuckOnGround=false
+width=70
+
+[13-slope@72,198|3]
+grade=6
+gradient=Elliptic
+height=96
+reversed=false
+stuckOnGround=false
+width=96
+
+[14-ball@154,39]
+dummykey=true
+
+[14-blackhole@236,51|5]
+exit=384,316
+exitDeg=180
+maxspeed=4
+minspeed=1
+
+[14-cup@55,38|8]
+dummykey=true
+
+[14-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[14-slope@10,160|9]
+grade=4
+gradient=Opposite Diagonal
+height=227
+reversed=false
+stuckOnGround=false
+width=243
+
+[14-slope@257,12|11]
+grade=2
+gradient=Opposite Diagonal
+height=91
+reversed=true
+stuckOnGround=false
+width=130
+
+[14-slope@31,67|10]
+grade=1
+gradient=Elliptic
+height=81
+reversed=false
+stuckOnGround=false
+width=81
+
+[14-wall@0,0|4]
+endPoint=387,251
+startPoint=174,251
+
+[14-wall@0,0|6]
+endPoint=174,251
+startPoint=115,199
+
+[14-wall@0,0|7]
+endPoint=115,199
+startPoint=115,9
+
+[14-windmill@192,12|3]
+botWallVisible=false
+height=166
+leftWallVisible=true
+rightWallVisible=false
+speed=8
+topWallVisible=false
+width=195
+
+[15-ball@358,361]
+dummykey=true
+
+[15-cup@43,43|4]
+dummykey=true
+
+[15-floater@180,204|3]
+botWallVisible=false
+endPoint=180,204
+height=118
+leftWallVisible=false
+rightWallVisible=false
+speed=5
+startPoint=40,103
+topWallVisible=false
+width=149
+
+[15-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=true
+maxstrokes=10
+par=2
+
+[15-puddle@176,217|8]
+changeEnabled=false
+changeEvery=50
+height=134
+width=190
+
+[15-puddle@192,185|10]
+changeEnabled=false
+changeEvery=50
+height=136
+width=168
+
+[15-puddle@210,196|9]
+changeEnabled=false
+changeEvery=50
+height=154
+width=132
+
+[15-slope@18,274|5]
+grade=3
+gradient=Elliptic
+height=101
+reversed=false
+stuckOnGround=true
+width=101
+
+[15-slope@93,16|6]
+grade=7
+gradient=Elliptic
+height=83
+reversed=false
+stuckOnGround=true
+width=83
+
+[15-wall@0,0|34]
+endPoint=255,11
+startPoint=391,102
+
+[16-ball@214,366]
+dummykey=true
+
+[16-cup@213,190|41]
+dummykey=true
+
+[16-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=4
+
+[16-puddle@116,184|40]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[16-puddle@117,179|39]
+changeEnabled=false
+changeEvery=50
+height=36
+width=34
+
+[16-puddle@127,161|37]
+changeEnabled=false
+changeEvery=50
+height=62
+width=48
+
+[16-puddle@140,140|33]
+changeEnabled=false
+changeEvery=50
+height=46
+width=54
+
+[16-puddle@158,378|25]
+changeEnabled=false
+changeEvery=50
+height=100
+width=58
+
+[16-puddle@165,328|27]
+changeEnabled=false
+changeEvery=50
+height=92
+width=58
+
+[16-puddle@187,296|23]
+changeEnabled=false
+changeEvery=50
+height=72
+width=72
+
+[16-puddle@196,297|26]
+changeEnabled=false
+changeEvery=50
+height=60
+width=98
+
+[16-puddle@217,270|24]
+changeEnabled=false
+changeEvery=50
+height=40
+width=88
+
+[16-puddle@220,124|32]
+changeEnabled=false
+changeEvery=50
+height=44
+width=190
+
+[16-puddle@237,289|22]
+changeEnabled=false
+changeEvery=50
+height=46
+width=40
+
+[16-puddle@238,269|21]
+changeEnabled=false
+changeEvery=50
+height=74
+width=70
+
+[16-puddle@257,257|28]
+changeEnabled=false
+changeEvery=50
+height=54
+width=82
+
+[16-puddle@262,134|34]
+changeEnabled=false
+changeEvery=50
+height=26
+width=34
+
+[16-puddle@275,253|41]
+changeEnabled=false
+changeEvery=50
+height=52
+width=84
+
+[16-puddle@283,136|35]
+changeEnabled=false
+changeEvery=50
+height=48
+width=48
+
+[16-puddle@291,238|31]
+changeEnabled=false
+changeEvery=50
+height=56
+width=58
+
+[16-puddle@294,156|36]
+changeEnabled=false
+changeEvery=50
+height=34
+width=34
+
+[16-puddle@309,170|29]
+changeEnabled=false
+changeEvery=50
+height=104
+width=46
+
+[16-puddle@310,202|30]
+changeEnabled=false
+changeEvery=50
+height=88
+width=42
+
+[16-sand@-2,322|58]
+changeEnabled=false
+changeEvery=50
+height=72
+width=94
+
+[16-sand@31,334|41]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[16-sand@41,399|59]
+changeEnabled=false
+changeEvery=50
+height=150
+width=196
+
+[16-slope@11,12|45]
+grade=5
+gradient=Diagonal
+height=123
+reversed=true
+stuckOnGround=false
+width=179
+
+[16-slope@129,64|50]
+grade=4
+gradient=Vertical
+height=69
+reversed=false
+stuckOnGround=false
+width=171
+
+[16-slope@174,261|54]
+grade=4
+gradient=Vertical
+height=48
+reversed=true
+stuckOnGround=false
+width=137
+
+[16-slope@178,153|53]
+grade=2
+gradient=Elliptic
+height=74
+reversed=true
+stuckOnGround=false
+width=74
+
+[16-slope@278,257|54]
+grade=7
+gradient=Diagonal
+height=129
+reversed=false
+stuckOnGround=false
+width=110
+
+[16-slope@299,64|51]
+grade=4
+gradient=Opposite Diagonal
+height=69
+reversed=false
+stuckOnGround=false
+width=55
+
+[16-slope@309,263|53]
+grade=4
+gradient=Diagonal
+height=42
+reversed=true
+stuckOnGround=false
+width=48
+
+[16-slope@311,133|52]
+grade=4
+gradient=Horizontal
+height=130
+reversed=true
+stuckOnGround=false
+width=44
+
+[16-slope@87,65|55]
+grade=4
+gradient=Diagonal
+height=73
+reversed=false
+stuckOnGround=false
+width=42
+
+[16-slope@89,136|38]
+grade=4
+gradient=Vertical
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[16-wall@0,0|42]
+endPoint=177,152
+startPoint=154,181
+
+[16-wall@0,0|43]
+endPoint=278,188
+startPoint=269,165
+
+[16-wall@0,0|44]
+endPoint=269,165
+startPoint=244,155
+
+[16-wall@0,0|52]
+endPoint=200,239
+startPoint=161,256
+
+[16-wall@0,0|56]
+endPoint=387,77
+startPoint=339,14
+
+[17-ball@44,230]
+dummykey=true
+
+[17-cup@307,67|3]
+dummykey=true
+
+[17-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[17-slope@131,218|12]
+grade=2
+gradient=Elliptic
+height=156
+reversed=true
+stuckOnGround=false
+width=156
+
+[17-slope@32,8|4]
+grade=1
+gradient=Opposite Diagonal
+height=349
+reversed=true
+stuckOnGround=false
+width=356
+
+[17-slope@58,127|11]
+grade=3
+gradient=Elliptic
+height=81
+reversed=false
+stuckOnGround=false
+width=81
+
+[17-wall@0,0|10]
+endPoint=323,221
+startPoint=260,219
+
+[17-wall@0,0|13]
+endPoint=296,81
+startPoint=278,12
+
+[17-wall@0,0|14]
+endPoint=335,85
+startPoint=296,81
+
+[17-wall@0,0|5]
+endPoint=185,40
+startPoint=190,111
+
+[17-wall@0,0|6]
+endPoint=236,121
+startPoint=190,111
+
+[17-wall@0,0|7]
+endPoint=304,135
+startPoint=313,164
+
+[17-wall@0,0|8]
+endPoint=389,183
+startPoint=313,164
+
+[17-wall@0,0|9]
+endPoint=260,219
+startPoint=247,173
+
+[18-ball@246,341]
+dummykey=true
+
+[18-cup@50,355|3]
+dummykey=true
+
+[18-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[18-slope@-183,-169|5]
+grade=2
+gradient=Elliptic
+height=400
+reversed=false
+stuckOnGround=false
+width=400
+
+[18-slope@11,297|7]
+grade=3
+gradient=Vertical
+height=91
+reversed=false
+stuckOnGround=false
+width=163
+
+[18-slope@4,10|8]
+grade=2
+gradient=Elliptic
+height=125
+reversed=true
+stuckOnGround=false
+width=125
+
+[18-windmill@9,179|6]
+botWallVisible=false
+bottom=false
+height=212
+leftWallVisible=false
+rightWallVisible=true
+speed=7
+topWallVisible=false
+width=166
+
+[2-ball@68,357]
+dummykey=true
+
+[2-blackhole@100,268|14]
+exit=19,50
+exitDeg=0
+maxspeed=6
+minspeed=3
+
+[2-cup@277,370|59]
+dummykey=true
+
+[2-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[2-puddle@133,167|39]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[2-puddle@144,202|32]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[2-puddle@145,190|34]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[2-puddle@163,163|53]
+changeEnabled=false
+changeEvery=50
+height=40
+width=88
+
+[2-puddle@170,203|40]
+changeEnabled=false
+changeEvery=50
+height=52
+width=62
+
+[2-puddle@182,219|56]
+changeEnabled=false
+changeEvery=50
+height=44
+width=42
+
+[2-puddle@184,190|43]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[2-puddle@188,233|36]
+changeEnabled=false
+changeEvery=50
+height=44
+width=34
+
+[2-puddle@190,366|38]
+changeEnabled=false
+changeEvery=50
+height=60
+width=34
+
+[2-puddle@192,387|44]
+changeEnabled=false
+changeEvery=50
+height=72
+width=54
+
+[2-puddle@194,288|45]
+changeEnabled=false
+changeEvery=50
+height=272
+width=42
+
+[2-puddle@195,212|51]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[2-puddle@204,149|54]
+changeEnabled=false
+changeEvery=50
+height=40
+width=90
+
+[2-puddle@205,200|37]
+changeEnabled=false
+changeEvery=50
+height=32
+width=32
+
+[2-puddle@206,391|46]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[2-puddle@207,185|55]
+changeEnabled=false
+changeEvery=50
+height=34
+width=34
+
+[2-puddle@211,154|35]
+changeEnabled=false
+changeEvery=50
+height=70
+width=92
+
+[2-puddle@211,399|47]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[2-puddle@215,180|33]
+changeEnabled=false
+changeEvery=50
+height=30
+width=36
+
+[2-puddle@79,180|31]
+changeEnabled=false
+changeEvery=50
+height=68
+width=272
+
+[2-sand@275,277|65]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[2-sand@363,352|66]
+changeEnabled=false
+changeEvery=50
+height=64
+width=32
+
+[2-sand@366,341|72]
+changeEnabled=false
+changeEvery=50
+height=44
+width=30
+
+[2-slope@113,9|57]
+grade=4
+gradient=Horizontal
+height=105
+reversed=true
+stuckOnGround=false
+width=81
+
+[2-slope@255,324|74]
+grade=4
+gradient=Vertical
+height=28
+reversed=false
+stuckOnGround=false
+width=54
+
+[2-slope@263,112|72]
+grade=6
+gradient=Diagonal
+height=22
+reversed=false
+stuckOnGround=false
+width=123
+
+[2-slope@269,134|71]
+grade=6
+gradient=Horizontal
+height=31
+reversed=false
+stuckOnGround=false
+width=117
+
+[2-slope@269,163|73]
+grade=6
+gradient=Opposite Diagonal
+height=36
+reversed=true
+stuckOnGround=false
+width=117
+
+[2-slope@308,322|75]
+grade=4
+gradient=Opposite Diagonal
+height=30
+reversed=false
+stuckOnGround=false
+width=27
+
+[2-slope@308,349|76]
+grade=4
+gradient=Horizontal
+height=38
+reversed=true
+stuckOnGround=false
+width=27
+
+[2-slope@313,228|77]
+grade=4
+gradient=Elliptic
+height=74
+reversed=false
+stuckOnGround=false
+width=74
+
+[2-slope@49,235|74]
+grade=4
+gradient=Elliptic
+height=107
+reversed=false
+stuckOnGround=false
+width=107
+
+[2-wall@0,0|10]
+endPoint=131,367
+startPoint=107,390
+
+[2-wall@0,0|11]
+endPoint=41,390
+startPoint=13,361
+
+[2-wall@0,0|12]
+endPoint=55,243
+startPoint=36,272
+
+[2-wall@0,0|13]
+endPoint=13,288
+startPoint=36,272
+
+[2-wall@0,0|15]
+endPoint=108,115
+startPoint=88,133
+
+[2-wall@0,0|16]
+endPoint=88,133
+startPoint=56,134
+
+[2-wall@0,0|17]
+endPoint=56,134
+startPoint=30,121
+
+[2-wall@0,0|18]
+endPoint=13,76
+startPoint=30,121
+
+[2-wall@0,0|19]
+endPoint=45,9
+startPoint=13,27
+
+[2-wall@0,0|20]
+endPoint=189,114
+startPoint=108,115
+
+[2-wall@0,0|21]
+endPoint=221,109
+startPoint=189,114
+
+[2-wall@0,0|22]
+endPoint=244,112
+startPoint=221,109
+
+[2-wall@0,0|23]
+endPoint=273,139
+startPoint=244,112
+
+[2-wall@0,0|24]
+endPoint=267,168
+startPoint=273,139
+
+[2-wall@0,0|25]
+endPoint=261,180
+startPoint=238,197
+
+[2-wall@0,0|26]
+endPoint=226,238
+startPoint=226,282
+
+[2-wall@0,0|27]
+endPoint=252,328
+startPoint=226,282
+
+[2-wall@0,0|28]
+endPoint=262,356
+startPoint=252,328
+
+[2-wall@0,0|29]
+endPoint=262,356
+startPoint=255,377
+
+[2-wall@0,0|30]
+endPoint=263,387
+startPoint=255,377
+
+[2-wall@0,0|4]
+endPoint=87,224
+startPoint=55,243
+
+[2-wall@0,0|5]
+endPoint=124,224
+startPoint=87,224
+
+[2-wall@0,0|57]
+endPoint=228,9
+startPoint=388,116
+
+[2-wall@0,0|58]
+endPoint=238,197
+startPoint=226,238
+
+[2-wall@0,0|6]
+endPoint=165,255
+startPoint=124,224
+
+[2-wall@0,0|64]
+endPoint=267,168
+startPoint=261,180
+
+[2-wall@0,0|67]
+endPoint=107,390
+startPoint=41,390
+
+[2-wall@0,0|68]
+endPoint=13,361
+startPoint=13,288
+
+[2-wall@0,0|69]
+endPoint=13,27
+startPoint=13,76
+
+[2-wall@0,0|7]
+endPoint=165,255
+startPoint=165,314
+
+[2-wall@0,0|70]
+endPoint=228,9
+startPoint=45,9
+
+[2-wall@0,0|71]
+endPoint=388,116
+startPoint=386,356
+
+[2-wall@0,0|72]
+endPoint=372,386
+startPoint=263,387
+
+[2-wall@0,0|73]
+endPoint=386,356
+startPoint=372,386
+
+[2-wall@0,0|8]
+endPoint=165,314
+startPoint=134,342
+
+[2-wall@0,0|9]
+endPoint=134,342
+startPoint=131,367
+
+[3-ball@59,317]
+dummykey=true
+
+[3-cup@299,103|5]
+dummykey=true
+
+[3-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[3-puddle@169,234|11]
+changeEnabled=false
+changeEvery=50
+height=34
+width=10
+
+[3-puddle@188,265|6]
+changeEnabled=false
+changeEvery=50
+height=50
+width=54
+
+[3-puddle@196,252|10]
+changeEnabled=false
+changeEvery=50
+height=72
+width=72
+
+[3-puddle@203,227|12]
+changeEnabled=false
+changeEvery=50
+height=86
+width=78
+
+[3-sand@255,57|14]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[3-sand@345,154|15]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[3-slope@170,168|4]
+grade=7
+gradient=Diagonal
+height=221
+reversed=false
+stuckOnGround=false
+width=219
+
+[3-windmill@12,12|13]
+botWallVisible=false
+bottom=true
+height=198
+leftWallVisible=false
+rightWallVisible=false
+speed=4
+topWallVisible=false
+width=180
+
+[4-ball@93,349]
+dummykey=true
+
+[4-bumper@262,91|20]
+dummykey=true
+
+[4-cup@338,325|5]
+dummykey=true
+
+[4-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[4-sand@100,150|11]
+changeEnabled=false
+changeEvery=50
+height=72
+width=46
+
+[4-sand@316,139|18]
+changeEnabled=false
+changeEvery=50
+height=74
+width=50
+
+[4-sand@324,153|14]
+changeEnabled=false
+changeEvery=50
+height=34
+width=38
+
+[4-sand@329,163|14]
+changeEnabled=false
+changeEvery=50
+height=30
+width=54
+
+[4-sand@72,127|10]
+changeEnabled=false
+changeEvery=50
+height=48
+width=78
+
+[4-sand@90,138|12]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@93,135|8]
+changeEnabled=false
+changeEvery=50
+height=50
+width=46
+
+[4-slope@152,20|21]
+grade=1
+gradient=Elliptic
+height=162
+reversed=true
+stuckOnGround=false
+width=162
+
+[4-slope@278,234|19]
+grade=5
+gradient=Elliptic
+height=57
+reversed=false
+stuckOnGround=false
+width=57
+
+[4-slope@290,14|20]
+grade=2
+gradient=Elliptic
+height=105
+reversed=false
+stuckOnGround=false
+width=105
+
+[4-wall@0,0|22]
+endPoint=257,355
+startPoint=257,387
+
+[4-wall@0,0|3]
+endPoint=257,355
+startPoint=221,223
+
+[4-wall@0,0|4]
+endPoint=10,10
+startPoint=136,109
+
+[5-ball@327,224]
+dummykey=true
+
+[5-cup@344,304|42]
+dummykey=true
+
+[5-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[5-puddle@210,199|33]
+changeEnabled=false
+changeEvery=50
+height=112
+width=110
+
+[5-puddle@212,227|35]
+changeEnabled=false
+changeEvery=50
+height=80
+width=74
+
+[5-puddle@224,220|34]
+changeEnabled=false
+changeEvery=50
+height=148
+width=86
+
+[5-puddle@306,368|46]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[5-puddle@331,361|32]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[5-puddle@365,367|45]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[5-sand@188,99|19]
+changeEnabled=true
+changeEvery=50
+height=40
+width=45
+
+[5-slope@21,134|48]
+grade=2
+gradient=Vertical
+height=84
+reversed=false
+stuckOnGround=false
+width=207
+
+[5-wall@0,0|10]
+endPoint=390,257
+startPoint=393,221
+
+[5-wall@0,0|11]
+endPoint=393,221
+startPoint=368,191
+
+[5-wall@0,0|12]
+endPoint=368,191
+startPoint=349,113
+
+[5-wall@0,0|13]
+endPoint=349,113
+startPoint=267,51
+
+[5-wall@0,0|14]
+endPoint=267,51
+startPoint=232,66
+
+[5-wall@0,0|15]
+endPoint=232,66
+startPoint=195,52
+
+[5-wall@0,0|16]
+endPoint=195,52
+startPoint=125,47
+
+[5-wall@0,0|17]
+endPoint=125,47
+startPoint=36,138
+
+[5-wall@0,0|18]
+endPoint=19,213
+startPoint=36,138
+
+[5-wall@0,0|28]
+endPoint=39,240
+startPoint=19,213
+
+[5-wall@0,0|3]
+endPoint=272,159
+startPoint=223,130
+
+[5-wall@0,0|36]
+endPoint=44,259
+startPoint=39,240
+
+[5-wall@0,0|37]
+endPoint=72,267
+startPoint=44,259
+
+[5-wall@0,0|38]
+endPoint=73,316
+startPoint=72,267
+
+[5-wall@0,0|39]
+endPoint=132,375
+startPoint=73,316
+
+[5-wall@0,0|4]
+endPoint=372,267
+startPoint=306,251
+
+[5-wall@0,0|40]
+endPoint=229,378
+startPoint=132,375
+
+[5-wall@0,0|41]
+endPoint=259,361
+startPoint=229,378
+
+[5-wall@0,0|43]
+endPoint=180,313
+startPoint=150,328
+
+[5-wall@0,0|5]
+endPoint=278,203
+startPoint=272,159
+
+[5-wall@0,0|6]
+endPoint=223,130
+startPoint=190,135
+
+[5-wall@0,0|8]
+endPoint=306,251
+startPoint=278,203
+
+[5-wall@0,0|9]
+endPoint=390,257
+startPoint=372,267
+
+[5-windmill@259,284|44]
+botWallVisible=false
+bottom=true
+height=77
+leftWallVisible=false
+rightWallVisible=true
+speed=5
+topWallVisible=true
+width=126
+
+[6-ball@28,86]
+dummykey=true
+
+[6-blackhole@169,41|36]
+exit=381,349
+exitDeg=200
+maxspeed=5
+minspeed=1
+
+[6-blackhole@266,293|43]
+exit=26,239
+exitDeg=290
+maxspeed=2
+minspeed=1
+
+[6-cup@103,354|44]
+dummykey=true
+
+[6-cup@104,110|3]
+dummykey=true
+
+[6-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[6-puddle@108,218|32]
+changeEnabled=false
+changeEvery=50
+height=56
+width=118
+
+[6-puddle@149,331|40]
+changeEnabled=false
+changeEvery=50
+height=92
+width=44
+
+[6-puddle@151,223|28]
+changeEnabled=false
+changeEvery=50
+height=44
+width=84
+
+[6-puddle@157,369|37]
+changeEnabled=false
+changeEvery=50
+height=90
+width=62
+
+[6-puddle@161,305|41]
+changeEnabled=false
+changeEvery=50
+height=98
+width=70
+
+[6-puddle@167,382|39]
+changeEnabled=false
+changeEvery=50
+height=52
+width=64
+
+[6-puddle@177,232|35]
+changeEnabled=false
+changeEvery=50
+height=30
+width=42
+
+[6-puddle@178,261|36]
+changeEnabled=false
+changeEvery=50
+height=104
+width=64
+
+[6-puddle@178,285|38]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[6-puddle@183,389|42]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[6-puddle@41,208|67]
+changeEnabled=false
+changeEvery=50
+height=56
+width=222
+
+[6-sand@290,48|63]
+changeEnabled=false
+changeEvery=50
+height=60
+width=58
+
+[6-sand@315,31|52]
+changeEnabled=false
+changeEvery=50
+height=34
+width=142
+
+[6-sand@331,73|62]
+changeEnabled=false
+changeEvery=50
+height=96
+width=76
+
+[6-sand@360,99|61]
+changeEnabled=false
+changeEvery=50
+height=172
+width=54
+
+[6-sand@369,24|64]
+changeEnabled=false
+changeEvery=50
+height=26
+width=24
+
+[6-sand@372,25|67]
+changeEnabled=false
+changeEvery=50
+height=28
+width=34
+
+[6-sand@373,40|65]
+changeEnabled=false
+changeEvery=50
+height=30
+width=24
+
+[6-sand@383,36|66]
+changeEnabled=false
+changeEvery=50
+height=18
+width=12
+
+[6-slope@10,11|63]
+grade=4
+gradient=Diagonal
+height=66
+reversed=true
+stuckOnGround=false
+width=142
+
+[6-slope@11,314|62]
+grade=4
+gradient=Opposite Diagonal
+height=74
+reversed=false
+stuckOnGround=false
+width=89
+
+[6-slope@13,145|66]
+grade=4
+gradient=Vertical
+height=67
+reversed=false
+stuckOnGround=false
+width=133
+
+[6-slope@141,14|60]
+grade=2
+gradient=Elliptic
+height=59
+reversed=true
+stuckOnGround=false
+width=59
+
+[6-slope@147,145|71]
+grade=4
+gradient=Opposite Diagonal
+height=82
+reversed=false
+stuckOnGround=false
+width=57
+
+[6-slope@182,222|25]
+grade=6
+gradient=Vertical
+height=168
+reversed=false
+stuckOnGround=false
+width=208
+
+[6-slope@21,275|61]
+grade=4
+gradient=Elliptic
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[6-slope@223,45|72]
+grade=4
+gradient=Opposite Diagonal
+height=158
+reversed=true
+stuckOnGround=false
+width=121
+
+[6-slope@344,181|73]
+grade=4
+gradient=Vertical
+height=22
+reversed=true
+stuckOnGround=false
+width=43
+
+[6-slope@58,69|75]
+grade=2
+gradient=Elliptic
+height=91
+reversed=true
+stuckOnGround=false
+width=91
+
+[6-wall@0,0|10]
+endPoint=362,260
+startPoint=388,272
+
+[6-wall@0,0|11]
+endPoint=344,292
+startPoint=290,254
+
+[6-wall@0,0|12]
+endPoint=309,302
+startPoint=282,312
+
+[6-wall@0,0|13]
+endPoint=102,60
+startPoint=70,74
+
+[6-wall@0,0|15]
+endPoint=360,284
+startPoint=344,292
+
+[6-wall@0,0|17]
+endPoint=121,66
+startPoint=102,60
+
+[6-wall@0,0|18]
+endPoint=258,309
+startPoint=229,298
+
+[6-wall@0,0|20]
+endPoint=176,78
+startPoint=150,88
+
+[6-wall@0,0|21]
+endPoint=150,88
+startPoint=132,81
+
+[6-wall@0,0|22]
+endPoint=132,81
+startPoint=121,66
+
+[6-wall@0,0|23]
+endPoint=200,310
+startPoint=189,350
+
+[6-wall@0,0|29]
+endPoint=217,80
+startPoint=197,83
+
+[6-wall@0,0|30]
+endPoint=203,11
+startPoint=225,46
+
+[6-wall@0,0|31]
+endPoint=38,114
+startPoint=11,114
+
+[6-wall@0,0|4]
+endPoint=227,46
+startPoint=226,63
+
+[6-wall@0,0|45]
+endPoint=309,356
+startPoint=277,364
+
+[6-wall@0,0|5]
+endPoint=38,114
+startPoint=57,98
+
+[6-wall@0,0|58]
+endPoint=226,63
+startPoint=217,80
+
+[6-wall@0,0|59]
+endPoint=254,347
+startPoint=231,353
+
+[6-wall@0,0|6]
+endPoint=197,83
+startPoint=176,78
+
+[6-wall@0,0|63]
+endPoint=213,388
+startPoint=189,350
+
+[6-wall@0,0|66]
+endPoint=213,285
+startPoint=200,310
+
+[6-wall@0,0|7]
+endPoint=57,98
+startPoint=70,74
+
+[6-wall@0,0|74]
+endPoint=251,10
+startPoint=225,46
+
+[6-wall@0,0|8]
+endPoint=232,273
+startPoint=213,285
+
+[6-wall@0,0|9]
+endPoint=116,271
+startPoint=76,296
+
+[7-ball@37,357]
+dummykey=true
+
+[7-blackhole@263,340|15]
+exit=323,359
+exitDeg=0
+maxspeed=2
+minspeed=2
+
+[7-cup@333,50|3]
+dummykey=true
+
+[7-cup@367,355|16]
+dummykey=true
+
+[7-floater@163,347|13]
+botWallVisible=true
+endPoint=163,347
+height=41
+leftWallVisible=true
+rightWallVisible=true
+speed=6
+startPoint=158,314
+topWallVisible=true
+width=10
+
+[7-floater@197,284|14]
+botWallVisible=true
+endPoint=197,284
+height=37
+leftWallVisible=true
+rightWallVisible=true
+speed=3
+startPoint=189,222
+topWallVisible=true
+width=9
+
+[7-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=true
+maxstrokes=10
+par=2
+
+[7-slope@111,21|6]
+grade=2
+gradient=Opposite Diagonal
+height=97
+reversed=false
+stuckOnGround=false
+width=151
+
+[7-slope@112,117|5]
+grade=2
+gradient=Diagonal
+height=141
+reversed=true
+stuckOnGround=false
+width=143
+
+[7-slope@193,145|10]
+grade=3
+gradient=Vertical
+height=57
+reversed=true
+stuckOnGround=false
+width=197
+
+[7-slope@239,326|17]
+grade=4
+gradient=Diagonal
+height=63
+reversed=false
+stuckOnGround=false
+width=73
+
+[7-slope@27,117|8]
+grade=2
+gradient=Opposite Diagonal
+height=136
+reversed=true
+stuckOnGround=false
+width=84
+
+[7-slope@27,18|7]
+grade=2
+gradient=Diagonal
+height=100
+reversed=false
+stuckOnGround=false
+width=87
+
+[7-wall@0,0|11]
+endPoint=387,326
+startPoint=313,326
+
+[7-wall@0,0|12]
+endPoint=312,327
+startPoint=312,387
+
+[7-wall@0,0|4]
+endPoint=313,326
+startPoint=194,204
+
+[7-wall@0,0|9]
+endPoint=194,148
+startPoint=194,204
+
+[8-ball@318,46]
+dummykey=true
+
+[8-blackhole@33,33|9]
+exit=384,321
+exitDeg=195
+maxspeed=3
+minspeed=3
+
+[8-cup@226,360|11]
+dummykey=true
+
+[8-floater@207,105|3]
+botWallVisible=true
+endPoint=207,105
+height=105
+leftWallVisible=false
+rightWallVisible=true
+speed=5
+startPoint=54,225
+topWallVisible=false
+width=168
+
+[8-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=true
+maxstrokes=10
+par=2
+
+[8-puddle@274,284|14]
+changeEnabled=false
+changeEvery=50
+height=36
+width=106
+
+[8-puddle@279,290|17]
+changeEnabled=false
+changeEvery=50
+height=44
+width=150
+
+[8-puddle@311,275|12]
+changeEnabled=false
+changeEvery=50
+height=58
+width=138
+
+[8-puddle@338,267|13]
+changeEnabled=false
+changeEvery=50
+height=54
+width=114
+
+[8-puddle@366,254|15]
+changeEnabled=false
+changeEvery=50
+height=58
+width=96
+
+[8-sand@236,61|20]
+changeEnabled=false
+changeEvery=50
+height=50
+width=74
+
+[8-sand@243,47|19]
+changeEnabled=false
+changeEvery=50
+height=64
+width=70
+
+[8-slope@12,70|10]
+grade=3
+gradient=Vertical
+height=195
+reversed=false
+stuckOnGround=true
+width=180
+
+[8-slope@131,25|22]
+grade=4
+gradient=Elliptic
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[8-slope@59,19|21]
+grade=4
+gradient=Elliptic
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[8-wall@0,0|18]
+endPoint=389,335
+startPoint=312,388
+
+[8-wall@0,0|22]
+endPoint=387,211
+startPoint=360,252
+
+[8-wall@0,0|5]
+endPoint=193,9
+startPoint=193,390
+
+[9-ball@38,290]
+dummykey=true
+
+[9-cup@318,61|3]
+dummykey=true
+
+[9-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[9-puddle@100,33|8]
+changeEnabled=false
+changeEvery=50
+height=32
+width=28
+
+[9-puddle@112,20|23]
+changeEnabled=false
+changeEvery=50
+height=10
+width=34
+
+[9-puddle@133,37|18]
+changeEnabled=false
+changeEvery=50
+height=46
+width=88
+
+[9-puddle@166,43|19]
+changeEnabled=false
+changeEvery=50
+height=40
+width=84
+
+[9-puddle@178,33|22]
+changeEnabled=false
+changeEvery=50
+height=38
+width=92
+
+[9-puddle@192,40|21]
+changeEnabled=false
+changeEvery=50
+height=30
+width=48
+
+[9-slope@156,-24|24]
+grade=4
+gradient=Elliptic
+height=160
+reversed=false
+stuckOnGround=false
+width=160
+
+[9-slope@189,216|7]
+grade=6
+gradient=Elliptic
+height=72
+reversed=false
+stuckOnGround=false
+width=72
+
+[9-wall@0,0|5]
+endPoint=220,214
+startPoint=145,131
+
+[9-wall@0,0|6]
+endPoint=219,293
+startPoint=219,389
+
+[9-wall@0,0|9]
+endPoint=74,11
+startPoint=11,40
+
+[9-windmill@249,13|4]
+botWallVisible=false
+bottom=true
+height=113
+leftWallVisible=true
+rightWallVisible=false
+speed=5
+topWallVisible=false
+width=137
diff --git a/kolf/courses/Practice b/kolf/courses/Practice
new file mode 100644
index 00000000..8ff077ec
--- /dev/null
+++ b/kolf/courses/Practice
@@ -0,0 +1,376 @@
+[0-course@-50,-50]
+Name=Slope Practice
+Name[bg]=Наклонено
+Name[bn]=ঢালুতে অনুশীলন
+Name[bs]=Vježba sa nagibom
+Name[ca]=Pràctica de baixada
+Name[da]=Træn skråninger
+Name[de]=Hügelpraxis
+Name[es]=Práctica de pendiente
+Name[et]=Kallakute treening
+Name[fi]=Kaltevuusharjoitus
+Name[fr]=Entraînement sur la pente
+Name[gl]=Práctica de pendente
+Name[he]=אימון מדרונים
+Name[hi]=ढाल अभ्यास
+Name[hr]=Nagibna vježba TODO
+Name[hu]=Gyakorlás lejtőn
+Name[is]=Æfing í halla
+Name[it]=Esercizio su pendio
+Name[ja]=斜面の練習
+Name[lv]=Trennēšanās uz nogāzes
+Name[mk]=Вежбање на наклони
+Name[nb]=Skråningsøvelse
+Name[nl]=Heuveloefening
+Name[nn]=Bakkeøving
+Name[pl]=Ćwiczenia na stoku
+Name[pt]=Praticar Inclinações
+Name[pt_BR]=Praticar Ladeira
+Name[ru]=Практика на горках
+Name[sk]=Nácvik slope
+Name[sl]=Vaja za strmine
+Name[sr]=Вежба нагиба
+Name[sr@Latn]=Vežba nagiba
+Name[sv]=Sluttningsövning
+Name[ta]=சரிவு பயிற்சி
+Name[tg]=Таҷриба дар Теппачаҳо
+Name[tr]=Eğim Çalışması
+Name[uk]=Тренування на схилах
+Name[ven]=Ndowendowe ya Mutshoni
+Name[xh]=Induli yokuzilolonga
+Name[xx]=xxSlope Practicexx
+Name[zh_CN]=斜坡练习
+Name[zh_TW]=斜坡練習
+Name[zu]=Ukuzejwayeza kokusantaba
+author=Jason Katz-Brown
+name=Slope Practice
+
+[1-ball@68,257]
+dummykey=true
+
+[1-cup@289,186|3]
+dummykey=true
+
+[1-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=4
+par=2
+
+[1-sand@327,8|5]
+changeEnabled=false
+changeEvery=50
+height=132
+width=310
+
+[1-sand@368,64|7]
+changeEnabled=false
+changeEvery=50
+height=96
+width=98
+
+[1-sand@426,127|6]
+changeEnabled=false
+changeEvery=50
+height=418
+width=162
+
+[1-slope@-1,88|4]
+grade=2
+gradient=Vertical
+height=247
+reversed=false
+stuckOnGround=false
+width=402
+
+[2-ball@306,324]
+dummykey=true
+
+[2-cup@262,69|3]
+dummykey=true
+
+[2-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[2-sand@-62,187|5]
+changeEnabled=false
+changeEvery=50
+height=300
+width=376
+
+[2-sand@11,-10|6]
+changeEnabled=false
+changeEvery=50
+height=174
+width=400
+
+[2-sand@41,74|7]
+changeEnabled=false
+changeEvery=50
+height=136
+width=158
+
+[2-slope@7,-6|4]
+grade=2
+gradient=Opposite Diagonal
+height=323
+reversed=true
+stuckOnGround=false
+width=395
+
+[3-ball@39,167]
+dummykey=true
+
+[3-cup@340,113|3]
+dummykey=true
+
+[3-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[3-slope@160,-1|4]
+grade=3
+gradient=Vertical
+height=237
+reversed=true
+stuckOnGround=false
+width=90
+
+[3-slope@247,-1|6]
+grade=3
+gradient=Diagonal
+height=237
+reversed=true
+stuckOnGround=false
+width=60
+
+[3-slope@87,-1|5]
+grade=3
+gradient=Opposite Diagonal
+height=236
+reversed=true
+stuckOnGround=false
+width=74
+
+[3-wall@0,0|7]
+endPoint=306,13
+startPoint=380,75
+
+[3-wall@0,0|8]
+endPoint=380,75
+startPoint=386,130
+
+[3-wall@0,0|9]
+endPoint=386,130
+startPoint=366,246
+
+[4-ball@344,292]
+dummykey=true
+
+[4-cup@74,281|3]
+dummykey=true
+
+[4-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[4-puddle@205,406|5]
+changeEnabled=false
+changeEvery=50
+height=444
+width=112
+
+[4-slope@11,43|4]
+grade=4
+gradient=Vertical
+height=155
+reversed=false
+stuckOnGround=false
+width=377
+
+[5-ball@328,312]
+dummykey=true
+
+[5-cup@96,73|3]
+dummykey=true
+
+[5-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[5-slope@-119,167|4]
+grade=2
+gradient=Elliptic
+height=345
+reversed=false
+stuckOnGround=false
+width=345
+
+[5-wall@0,0|10]
+endPoint=272,380
+startPoint=362,366
+
+[5-wall@0,0|11]
+endPoint=231,95
+startPoint=202,45
+
+[5-wall@0,0|5]
+endPoint=231,95
+startPoint=176,214
+
+[5-wall@0,0|6]
+endPoint=202,45
+startPoint=68,29
+
+[5-wall@0,0|7]
+endPoint=68,29
+startPoint=30,65
+
+[5-wall@0,0|8]
+endPoint=30,65
+startPoint=31,264
+
+[5-wall@0,0|9]
+endPoint=272,380
+startPoint=103,367
+
+[6-ball@338,348]
+dummykey=true
+
+[6-cup@80,52|3]
+dummykey=true
+
+[6-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[6-slope@11,226|5]
+grade=4
+gradient=Vertical
+height=66
+reversed=true
+stuckOnGround=false
+width=378
+
+[6-slope@12,125|4]
+grade=4
+gradient=Vertical
+height=70
+reversed=false
+stuckOnGround=false
+width=376
+
+[7-ball@370,174]
+dummykey=true
+
+[7-cup@161,231|3]
+dummykey=true
+
+[7-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[7-slope@8,73|4]
+grade=4
+gradient=Elliptic
+height=311
+reversed=true
+stuckOnGround=false
+width=311
+
+[7-wall@0,0|5]
+endPoint=330,174
+startPoint=330,297
+
+[7-wall@0,0|6]
+endPoint=330,297
+startPoint=406,297
+
+[8-ball@331,91]
+dummykey=true
+
+[8-cup@94,267|3]
+dummykey=true
+
+[8-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[8-sand@-8,157|5]
+changeEnabled=false
+changeEvery=50
+height=142
+width=372
+
+[8-slope@11,145|4]
+grade=6
+gradient=Vertical
+height=254
+reversed=false
+stuckOnGround=false
+width=391
+
+[8-wall@0,0|6]
+endPoint=13,398
+startPoint=13,228
+
+[9-ball@68,330]
+dummykey=true
+
+[9-cup@320,101|3]
+dummykey=true
+
+[9-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[9-puddle@210,356|6]
+changeEnabled=false
+changeEvery=50
+height=312
+width=170
+
+[9-puddle@305,237|5]
+changeEnabled=false
+changeEvery=50
+height=120
+width=270
+
+[9-puddle@394,360|7]
+changeEnabled=false
+changeEvery=50
+height=264
+width=300
+
+[9-slope@0,0|4]
+grade=6
+gradient=Diagonal
+height=230
+reversed=true
+stuckOnGround=false
+width=310
+
+[9-wall@0,0|8]
+endPoint=366,26
+startPoint=276,24
+
+[9-wall@0,0|9]
+endPoint=366,26
+startPoint=366,201
diff --git a/kolf/courses/ReallyEasy b/kolf/courses/ReallyEasy
new file mode 100644
index 00000000..5e044f97
--- /dev/null
+++ b/kolf/courses/ReallyEasy
@@ -0,0 +1,2404 @@
+[0-course@-50,-50]
+Name=Really Easy
+Name[af]=Rerig Maklike
+Name[ar]=سهل جدا
+Name[bg]=Елементарно
+Name[bn]=খুবই সহজ
+Name[br]=Aes-tre
+Name[bs]=Stvarno lagano
+Name[ca]=Realment fàcil
+Name[cs]=Opravdu jednoduchý
+Name[da]=Meget nem
+Name[de]=Sehr leicht
+Name[el]=Πραγματικά εύκολο
+Name[es]=Realmente fácil
+Name[et]=Imelihtne
+Name[fi]=Todella helppo
+Name[fr]=Vraiment facile
+Name[gl]=Moi fácil
+Name[he]=ממש קל
+Name[hi]=सचमुच आसान
+Name[hr]=Stvarno lagano
+Name[hu]=Nagyon könnyű
+Name[is]=Verulega auðveld
+Name[it]=Facilissimo
+Name[ja]=ホントにカンタン
+Name[lv]=Tiešām viegli
+Name[mk]=Навистина лесен
+Name[nb]=Veldig lett
+Name[nl]=Heel eenvoudig
+Name[nn]=Kjempelett
+Name[nso]=Bonolo Kannete
+Name[pl]=Naprawdę łatwy tor
+Name[pt]=Realmente Fácil
+Name[pt_BR]=Realmente Fácil
+Name[ro]=Foarte uşor
+Name[ru]=Проще некуда
+Name[sk]=Naozaj jednoduché
+Name[sl]=Zelo lahko
+Name[sr]=Баш лако
+Name[sr@Latn]=Baš lako
+Name[sv]=Mycket enkel
+Name[ta]=உண்மையிலேயே சுலபமானது
+Name[tg]=Дар Ҳақиқат Осон
+Name[tr]=Çok Kolay
+Name[uk]=Дуже просто
+Name[ven]=Zwi leluwa nga Maanda
+Name[wa]=Vormint åjhey
+Name[xh]=Ilula kakhulu
+Name[xx]=xxReally Easyxx
+Name[zh_CN]=真的很容易
+Name[zh_TW]=真的簡單
+Name[zu]=Kulula kakhulu
+author=Neil Stevens
+name=Really Easy
+
+[1-ball@79,203]
+dummykey=true
+
+[1-cup@301,184|7]
+dummykey=true
+
+[1-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=5
+par=2
+
+[1-slope@-3,238|9]
+grade=4
+gradient=Vertical
+height=170
+reversed=true
+stuckOnGround=false
+width=407
+
+[1-slope@-5,-4|8]
+grade=4
+gradient=Vertical
+height=146
+reversed=false
+stuckOnGround=false
+width=399
+
+[1-wall@0,0|3]
+endPoint=322,148
+startPoint=63,170
+
+[1-wall@0,0|4]
+endPoint=324,218
+startPoint=63,236
+
+[1-wall@0,0|5]
+endPoint=63,170
+startPoint=63,236
+
+[1-wall@0,0|6]
+endPoint=322,148
+startPoint=324,218
+
+[10-ball@171,205]
+dummykey=true
+
+[10-cup@209,158|0]
+dummykey=true
+
+[10-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=5
+
+[10-puddle@239,231|3]
+changeEnabled=false
+changeEvery=50
+height=60
+width=70
+
+[10-sand@137,128|4]
+changeEnabled=false
+changeEvery=50
+height=78
+width=90
+
+[10-wall@0,0|1]
+endPoint=190,39
+startPoint=190,349
+
+[10-wall@0,0|2]
+endPoint=324,183
+startPoint=72,183
+
+[11-ball@189,342]
+dummykey=true
+
+[11-bridge@109,184|4]
+botWallVisible=true
+height=172
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=false
+width=167
+
+[11-cup@190,138|0]
+dummykey=true
+
+[11-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=5
+par=3
+
+[11-wall@0,0|1]
+endPoint=308,48
+startPoint=71,48
+
+[11-wall@0,0|2]
+endPoint=190,51
+startPoint=190,124
+
+[11-windmill@109,119|3]
+botWallVisible=false
+bottom=true
+height=64
+leftWallVisible=true
+rightWallVisible=true
+speed=5
+topWallVisible=true
+width=165
+
+[12-ball@281,101]
+dummykey=true
+
+[12-cup@208,252|0]
+dummykey=true
+
+[12-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=7
+par=4
+
+[12-slope@170,111|2]
+grade=6
+gradient=Vertical
+height=226
+reversed=true
+stuckOnGround=false
+width=46
+
+[12-slope@80,43|1]
+grade=6
+gradient=Diagonal
+height=67
+reversed=true
+stuckOnGround=false
+width=208
+
+[12-slope@92,178|3]
+grade=6
+gradient=Horizontal
+height=42
+reversed=true
+stuckOnGround=false
+width=200
+
+[12-wall@0,0|4]
+endPoint=291,111
+startPoint=293,43
+
+[12-wall@0,0|6]
+endPoint=291,111
+startPoint=218,111
+
+[12-wall@0,0|7]
+endPoint=293,43
+startPoint=81,43
+
+[13-ball@48,40]
+dummykey=true
+
+[13-blackhole@348,38|8]
+exit=209,298
+exitDeg=90
+maxspeed=4
+minspeed=4
+
+[13-cup@207,143|0]
+dummykey=true
+
+[13-floater@276,198|9]
+botWallVisible=true
+endPoint=276,198
+height=2
+leftWallVisible=false
+rightWallVisible=false
+speed=11
+startPoint=45,199
+topWallVisible=false
+width=90
+
+[13-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=true
+maxstrokes=6
+par=3
+
+[13-wall@0,0|1]
+endPoint=348,72
+startPoint=48,72
+
+[13-wall@0,0|2]
+endPoint=150,75
+startPoint=151,125
+
+[13-wall@0,0|3]
+endPoint=150,123
+startPoint=120,291
+
+[13-wall@0,0|4]
+endPoint=120,291
+startPoint=54,359
+
+[13-wall@0,0|5]
+endPoint=151,125
+startPoint=281,123
+
+[13-wall@0,0|6]
+endPoint=281,123
+startPoint=244,332
+
+[13-wall@0,0|7]
+endPoint=244,332
+startPoint=221,359
+
+[14-ball@129,183]
+dummykey=true
+
+[14-cup@308,172|0]
+dummykey=true
+
+[14-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=5
+
+[14-puddle@1,10|7]
+changeEnabled=false
+changeEvery=50
+height=1242
+width=32
+
+[14-puddle@11,4|9]
+changeEnabled=false
+changeEvery=50
+height=24
+width=1062
+
+[14-puddle@158,394|8]
+changeEnabled=false
+changeEvery=50
+height=36
+width=846
+
+[14-puddle@407,192|10]
+changeEnabled=false
+changeEvery=50
+height=602
+width=38
+
+[14-wall@-15,1|4]
+endPoint=257,100
+startPoint=220,144
+
+[14-wall@0,0|1]
+endPoint=192,121
+startPoint=41,121
+
+[14-wall@0,0|2]
+endPoint=150,85
+startPoint=150,300
+
+[14-wall@0,0|3]
+endPoint=151,120
+startPoint=68,224
+
+[14-wall@0,0|5]
+endPoint=239,121
+startPoint=357,157
+
+[14-wall@0,0|6]
+endPoint=357,157
+startPoint=289,302
+
+[15-ball@208,165]
+dummykey=true
+
+[15-cup@140,158|0]
+dummykey=true
+
+[15-cup@341,156|10]
+dummykey=true
+
+[15-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=9
+par=4
+
+[15-puddle@306,15|14]
+changeEnabled=false
+changeEvery=50
+height=140
+width=626
+
+[15-puddle@386,81|15]
+changeEnabled=false
+changeEvery=50
+height=82
+width=190
+
+[15-puddle@386,97|16]
+changeEnabled=false
+changeEvery=50
+height=130
+width=114
+
+[15-puddle@404,165|17]
+changeEnabled=false
+changeEvery=50
+height=170
+width=100
+
+[15-puddle@408,273|18]
+changeEnabled=false
+changeEvery=50
+height=260
+width=76
+
+[15-puddle@420,348|19]
+changeEnabled=false
+changeEvery=50
+height=272
+width=98
+
+[15-wall@0,0|1]
+endPoint=169,30
+startPoint=169,295
+
+[15-wall@0,0|2]
+endPoint=169,295
+startPoint=93,342
+
+[15-wall@0,0|3]
+endPoint=167,175
+startPoint=109,213
+
+[15-wall@0,0|4]
+endPoint=155,141
+startPoint=115,110
+
+[15-wall@0,0|5]
+endPoint=235,29
+startPoint=239,337
+
+[15-wall@0,0|6]
+endPoint=239,337
+startPoint=314,336
+
+[15-wall@0,0|7]
+endPoint=314,336
+startPoint=314,291
+
+[15-wall@0,0|8]
+endPoint=290,213
+startPoint=237,175
+
+[15-wall@0,0|9]
+endPoint=237,175
+startPoint=292,122
+
+[15-windmill@10,105|13]
+botWallVisible=false
+bottom=true
+height=108
+leftWallVisible=false
+rightWallVisible=false
+speed=7
+topWallVisible=true
+width=99
+
+[16-ball@198,27]
+dummykey=true
+
+[16-cup@240,130|0]
+dummykey=true
+
+[16-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=4
+
+[16-puddle@197,81|2]
+changeEnabled=false
+changeEvery=50
+height=34
+width=14
+
+[16-puddle@197,93|1]
+changeEnabled=false
+changeEvery=50
+height=16
+width=236
+
+[16-wall@-10,-21|12]
+endPoint=289,249
+startPoint=229,210
+
+[16-wall@0,0|10]
+endPoint=199,332
+startPoint=179,330
+
+[16-wall@0,0|9]
+endPoint=199,197
+startPoint=199,332
+
+[16-wall@3,-18|11]
+endPoint=88,284
+startPoint=166,219
+
+[16-windmill@142,112|3]
+botWallVisible=false
+bottom=true
+height=81
+leftWallVisible=true
+rightWallVisible=true
+speed=5
+topWallVisible=true
+width=113
+
+[17-ball@21,143]
+dummykey=true
+
+[17-cup@255,163|0]
+dummykey=true
+
+[17-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=6
+
+[17-sand@100,136|3]
+changeEnabled=false
+changeEvery=50
+height=48
+width=18
+
+[17-sand@104,322|12]
+changeEnabled=false
+changeEvery=50
+height=32
+width=52
+
+[17-sand@112,173|6]
+changeEnabled=false
+changeEvery=50
+height=26
+width=50
+
+[17-sand@119,319|4]
+changeEnabled=false
+changeEvery=50
+height=28
+width=66
+
+[17-sand@126,173|10]
+changeEnabled=false
+changeEvery=50
+height=26
+width=42
+
+[17-sand@134,177|9]
+changeEnabled=false
+changeEvery=50
+height=32
+width=42
+
+[17-sand@137,315|11]
+changeEnabled=false
+changeEvery=50
+height=24
+width=54
+
+[17-sand@65,323|14]
+changeEnabled=false
+changeEvery=50
+height=20
+width=46
+
+[17-sand@75,329|5]
+changeEnabled=false
+changeEvery=50
+height=28
+width=86
+
+[17-sand@88,301|15]
+changeEnabled=false
+changeEvery=50
+height=78
+width=20
+
+[17-sand@90,319|13]
+changeEnabled=false
+changeEvery=50
+height=20
+width=72
+
+[17-sand@93,219|2]
+changeEnabled=false
+changeEvery=50
+height=238
+width=32
+
+[17-sand@94,182|1]
+changeEnabled=false
+changeEvery=50
+height=38
+width=114
+
+[17-sand@96,133|7]
+changeEnabled=false
+changeEvery=50
+height=44
+width=28
+
+[17-sand@97,122|8]
+changeEnabled=false
+changeEvery=50
+height=46
+width=26
+
+[17-slope@206,262|27]
+grade=4
+gradient=Opposite Diagonal
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[17-slope@215,253|28]
+grade=4
+gradient=Opposite Diagonal
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[17-slope@226,244|29]
+grade=4
+gradient=Opposite Diagonal
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[17-slope@234,236|30]
+grade=4
+gradient=Opposite Diagonal
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[17-slope@243,228|31]
+grade=4
+gradient=Opposite Diagonal
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[17-slope@250,219|32]
+grade=4
+gradient=Opposite Diagonal
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[17-slope@259,209|33]
+grade=4
+gradient=Opposite Diagonal
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[17-slope@268,202|34]
+grade=4
+gradient=Opposite Diagonal
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[17-wall@0,0|16]
+endPoint=266,213
+startPoint=201,273
+
+[17-wall@0,0|17]
+endPoint=266,213
+startPoint=292,174
+
+[17-wall@0,0|18]
+endPoint=289,253
+startPoint=200,344
+
+[17-wall@0,0|19]
+endPoint=289,253
+startPoint=328,202
+
+[17-wall@0,0|20]
+endPoint=347,324
+startPoint=279,264
+
+[17-wall@0,0|21]
+endPoint=347,324
+startPoint=366,347
+
+[17-wall@0,0|22]
+endPoint=205,168
+startPoint=252,111
+
+[17-wall@0,0|23]
+endPoint=205,168
+startPoint=252,222
+
+[17-wall@0,0|24]
+endPoint=295,108
+startPoint=163,113
+
+[17-wall@0,0|25]
+endPoint=295,108
+startPoint=374,114
+
+[17-wall@0,0|26]
+endPoint=263,110
+startPoint=243,62
+
+[17-wall@0,0|35]
+endPoint=200,344
+startPoint=170,355
+
+[17-wall@0,0|36]
+endPoint=328,202
+startPoint=333,166
+
+[17-wall@0,0|37]
+endPoint=201,273
+startPoint=172,283
+
+[18-ball@277,299]
+dummykey=true
+
+[18-cup@210,119|0]
+dummykey=true
+
+[18-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=7
+par=4
+
+[18-wall@-1,2|18]
+endPoint=152,106
+startPoint=89,106
+
+[18-wall@0,-3|9]
+endPoint=152,134
+startPoint=90,134
+
+[18-wall@0,0|1]
+endPoint=315,45
+startPoint=83,45
+
+[18-wall@0,0|10]
+endPoint=51,85
+startPoint=51,138
+
+[18-wall@0,0|11]
+endPoint=305,105
+startPoint=235,105
+
+[18-wall@0,0|12]
+endPoint=304,135
+startPoint=235,135
+
+[18-wall@0,0|13]
+endPoint=267,229
+startPoint=123,229
+
+[18-wall@0,0|14]
+endPoint=298,277
+startPoint=98,277
+
+[18-wall@0,0|15]
+endPoint=298,277
+startPoint=298,327
+
+[18-wall@0,0|16]
+endPoint=172,279
+startPoint=172,345
+
+[18-wall@0,0|17]
+endPoint=298,327
+startPoint=254,327
+
+[18-wall@0,0|2]
+endPoint=196,45
+startPoint=196,175
+
+[18-wall@0,0|3]
+endPoint=192,174
+startPoint=68,248
+
+[18-wall@0,0|4]
+endPoint=196,175
+startPoint=313,244
+
+[18-wall@0,0|5]
+endPoint=347,84
+startPoint=51,85
+
+[18-wall@0,0|6]
+endPoint=347,84
+startPoint=347,136
+
+[18-wall@0,0|7]
+endPoint=51,85
+startPoint=51,138
+
+[18-wall@0,0|8]
+endPoint=51,85
+startPoint=51,138
+
+[2-ball@252,137]
+dummykey=true
+
+[2-blackhole@155,137|12]
+exit=75,308
+exitDeg=0
+maxspeed=4
+minspeed=4
+
+[2-cup@353,305|7]
+dummykey=true
+
+[2-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=5
+par=2
+
+[2-puddle@-16,98|58]
+changeEnabled=false
+changeEvery=50
+height=52
+width=128
+
+[2-puddle@324,230|73]
+changeEnabled=false
+changeEvery=50
+height=40
+width=590
+
+[2-puddle@36,159|28]
+changeEnabled=false
+changeEvery=50
+height=148
+width=52
+
+[2-puddle@47,196|62]
+changeEnabled=false
+changeEvery=50
+height=42
+width=28
+
+[2-puddle@49,215|53]
+changeEnabled=false
+changeEvery=50
+height=38
+width=44
+
+[2-puddle@67,223|29]
+changeEnabled=false
+changeEvery=50
+height=22
+width=36
+
+[2-sand@13,296|15]
+changeEnabled=false
+changeEvery=50
+height=48
+width=64
+
+[2-sand@29,318|17]
+changeEnabled=false
+changeEvery=50
+height=84
+width=48
+
+[2-slope@283,1|20]
+grade=3
+gradient=Elliptic
+height=103
+reversed=false
+stuckOnGround=false
+width=103
+
+[2-wall@0,0|10]
+endPoint=60,355
+startPoint=58,283
+
+[2-wall@0,0|11]
+endPoint=374,263
+startPoint=372,338
+
+[2-wall@0,0|3]
+endPoint=265,101
+startPoint=138,110
+
+[2-wall@0,0|4]
+endPoint=279,161
+startPoint=133,171
+
+[2-wall@0,0|5]
+endPoint=133,171
+startPoint=138,110
+
+[2-wall@0,0|6]
+endPoint=265,101
+startPoint=279,161
+
+[2-wall@0,0|8]
+endPoint=374,263
+startPoint=58,283
+
+[2-wall@0,0|9]
+endPoint=372,338
+startPoint=60,355
+
+[3-ball@331,335]
+dummykey=true
+
+[3-cup@205,81|3]
+dummykey=true
+
+[3-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=8
+par=5
+
+[3-puddle@-5,168|49]
+changeEnabled=false
+changeEvery=50
+height=44
+width=126
+
+[3-puddle@373,294|31]
+changeEnabled=false
+changeEvery=50
+height=42
+width=76
+
+[3-puddle@38,156|32]
+changeEnabled=false
+changeEvery=50
+height=50
+width=72
+
+[3-puddle@397,277|33]
+changeEnabled=false
+changeEvery=50
+height=68
+width=56
+
+[3-puddle@52,143|27]
+changeEnabled=false
+changeEvery=50
+height=56
+width=52
+
+[3-puddle@57,133|19]
+changeEnabled=false
+changeEvery=50
+height=52
+width=50
+
+[3-puddle@64,114|25]
+changeEnabled=false
+changeEvery=50
+height=68
+width=40
+
+[3-puddle@67,111|21]
+changeEnabled=false
+changeEvery=50
+height=42
+width=48
+
+[3-puddle@74,34|41]
+changeEnabled=false
+changeEvery=50
+height=190
+width=58
+
+[3-slope@-188,220|24]
+grade=5
+gradient=Elliptic
+height=307
+reversed=false
+stuckOnGround=false
+width=307
+
+[3-wall@0,0|4]
+endPoint=348,359
+startPoint=59,359
+
+[3-wall@0,0|40]
+endPoint=123,54
+startPoint=117,109
+
+[3-wall@0,0|5]
+endPoint=325,298
+startPoint=98,298
+
+[3-wall@0,0|6]
+endPoint=264,222
+startPoint=153,225
+
+[3-wall@0,0|7]
+endPoint=264,165
+startPoint=151,172
+
+[3-wall@0,0|8]
+endPoint=295,51
+startPoint=123,54
+
+[3-wall@0,0|9]
+endPoint=313,113
+startPoint=117,109
+
+[4-ball@257,315]
+dummykey=true
+
+[4-cup@35,38|9]
+dummykey=true
+
+[4-floater@107,209|10]
+botWallVisible=false
+endPoint=107,209
+height=55
+leftWallVisible=false
+rightWallVisible=false
+speed=1
+startPoint=71,156
+topWallVisible=false
+width=59
+
+[4-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=true
+maxstrokes=6
+par=3
+
+[4-puddle@406,415|54]
+changeEnabled=false
+changeEvery=50
+height=222
+width=356
+
+[4-sand@105,164|35]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@112,151|22]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@115,129|38]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@116,136|34]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@117,119|11]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@118,119|45]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@120,101|10]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@121,84|42]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@122,90|46]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@123,62|44]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@123,72|9]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@124,48|8]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@125,31|7]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@125,36|33]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@238,32|20]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@238,99|29]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@239,118|26]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@239,67|25]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@239,80|18]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@240,50|24]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@241,137|21]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@242,157|23]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@242,174|22]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@245,196|19]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@251,190|36]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@264,199|30]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@278,195|28]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@290,199|14]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@299,194|40]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@313,196|27]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@323,197|31]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@33,209|39]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@332,192|47]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@334,198|12]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@347,192|21]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@364,196|37]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@364,200|15]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@367,194|32]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@41,208|13]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@55,204|41]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@67,198|17]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@77,189|43]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@84,190|48]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-sand@91,180|16]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[4-wall@0,0|49]
+endPoint=128,13
+startPoint=119,134
+
+[4-wall@0,0|50]
+endPoint=119,134
+startPoint=54,207
+
+[4-wall@0,0|51]
+endPoint=54,207
+startPoint=12,208
+
+[4-wall@0,0|52]
+endPoint=240,15
+startPoint=241,201
+
+[4-wall@0,0|53]
+endPoint=241,201
+startPoint=382,197
+
+[5-ball@352,289]
+dummykey=true
+
+[5-cup@203,277|3]
+dummykey=true
+
+[5-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=6
+par=3
+
+[5-wall@0,0|4]
+endPoint=331,331
+startPoint=43,331
+
+[5-wall@0,0|5]
+endPoint=267,205
+startPoint=102,204
+
+[5-wall@0,0|6]
+endPoint=267,205
+startPoint=262,327
+
+[5-wall@0,0|7]
+endPoint=307,59
+startPoint=82,62
+
+[5-wall@0,0|8]
+endPoint=164,66
+startPoint=146,329
+
+[5-windmill@170,186|9]
+botWallVisible=false
+bottom=false
+height=39
+leftWallVisible=true
+rightWallVisible=true
+speed=2
+topWallVisible=false
+width=88
+
+[6-ball@182,57]
+dummykey=true
+
+[6-blackhole@201,82|19]
+exit=312,177
+exitDeg=183
+maxspeed=2.32
+minspeed=2.32
+
+[6-bridge@90,199|21]
+botWallVisible=false
+height=79
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=false
+width=22
+
+[6-cup@314,309|20]
+dummykey=true
+
+[6-cup@95,295|3]
+dummykey=true
+
+[6-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[6-wall@0,0|10]
+endPoint=323,159
+startPoint=327,193
+
+[6-wall@0,0|11]
+endPoint=141,261
+startPoint=73,339
+
+[6-wall@0,0|12]
+endPoint=219,69
+startPoint=205,105
+
+[6-wall@0,0|13]
+endPoint=205,105
+startPoint=181,84
+
+[6-wall@0,0|14]
+endPoint=163,32
+startPoint=219,69
+
+[6-wall@0,0|15]
+endPoint=324,334
+startPoint=293,314
+
+[6-wall@0,0|16]
+endPoint=323,159
+startPoint=61,169
+
+[6-wall@0,0|17]
+endPoint=327,193
+startPoint=74,203
+
+[6-wall@0,0|18]
+endPoint=61,169
+startPoint=74,203
+
+[6-wall@0,0|4]
+endPoint=256,248
+startPoint=293,314
+
+[6-wall@0,0|5]
+endPoint=256,248
+startPoint=331,297
+
+[6-wall@0,0|6]
+endPoint=141,261
+startPoint=101,247
+
+[6-wall@0,0|7]
+endPoint=324,334
+startPoint=331,297
+
+[6-wall@0,0|8]
+endPoint=163,32
+startPoint=181,84
+
+[6-wall@0,0|9]
+endPoint=101,247
+startPoint=73,339
+
+[7-ball@31,371]
+dummykey=true
+
+[7-bridge@139,19|134]
+botWallVisible=true
+height=26
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=true
+width=94
+
+[7-bridge@332,221|135]
+botWallVisible=false
+height=119
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=false
+width=50
+
+[7-cup@368,30|3]
+dummykey=true
+
+[7-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=5
+
+[7-puddle@-1,72|6]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@-10,81|4]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@10,11|15]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@108,11|12]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@11,37|28]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@113,1|10]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@12,39|27]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@128,3|30]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@146,4|17]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@15,31|11]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@15,48|32]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@162,4|19]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@176,19|20]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@179,4|16]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@185,26|77]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@189,192|133]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@190,185|119]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@191,201|132]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@191,37|83]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@193,18|22]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@194,47|23]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@195,175|118]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@195,31|62]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@195,53|109]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@196,211|121]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@198,222|120]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@198,230|90]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@198,234|105]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@199,243|68]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@199,62|21]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@200,167|69]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@201,66|80]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@202,255|82]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@204,261|100]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@204,272|67]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@204,313|74]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@205,156|58]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@205,278|104]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@205,285|76]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@205,292|101]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@205,300|66]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@205,305|102]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@205,79|54]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@206,307|115]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@206,320|103]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@206,87|107]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@207,145|106]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@207,151|56]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@208,122|61]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@208,4|111]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@208,95|59]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@209,111|63]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@209,135|57]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@209,327|64]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@210,105|79]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@214,-4|60]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@216,321|117]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@219,7|112]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@223,332|99]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@236,330|116]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@242,335|72]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@247,0|65]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@25,36|35]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@263,334|78]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@265,340|98]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@267,-2|110]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@272,332|71]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@282,338|97]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@288,-8|108]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@297,320|96]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@300,332|55]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@303,323|95]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@305,314|93]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@306,-12|114]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@314,308|92]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@317,320|81]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@319,316|70]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@32,32|33]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@322,-14|113]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@323,305|89]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@325,301|88]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@328,306|75]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@330,295|73]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@335,284|94]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@344,281|87]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@350,273|91]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@361,269|86]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@37,5|18]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@385,263|85]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@4,63|7]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@403,260|84]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@43,26|31]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@49,16|13]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@54,22|34]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@59,-1|14]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@63,18|26]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@76,11|5]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@77,17|25]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@8,50|8]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@8,58|29]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@81,20|24]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-puddle@98,24|9]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[7-sand@114,219|45]
+changeEnabled=false
+changeEvery=50
+height=44
+width=62
+
+[7-sand@136,211|44]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@151,207|67]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@165,204|78]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@183,202|76]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@200,200|122]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@200,200|123]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@200,200|124]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@200,200|125]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@200,200|126]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@200,200|127]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@200,200|129]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@200,200|130]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@200,200|131]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@200,200|72]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@200,200|74]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@200,200|77]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@208,193|128]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@220,194|75]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@234,189|79]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@247,188|37]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@260,182|53]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@282,176|54]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@302,169|52]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@317,166|70]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@328,162|55]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@71,231|36]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[7-sand@90,226|64]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[8-ball@78,251]
+dummykey=true
+
+[8-blackhole@111,212|14]
+exit=32,30
+exitDeg=0
+maxspeed=1
+minspeed=1
+
+[8-blackhole@346,29|15]
+exit=96,252
+exitDeg=139
+maxspeed=1
+minspeed=0
+
+[8-blackhole@46,280|13]
+exit=203,114
+exitDeg=308
+maxspeed=3
+minspeed=3
+
+[8-cup@346,301|3]
+dummykey=true
+
+[8-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=6
+par=3
+
+[8-slope@23,11|16]
+grade=2
+gradient=Horizontal
+height=37
+reversed=true
+stuckOnGround=false
+width=342
+
+[8-wall@0,0|10]
+endPoint=345,272
+startPoint=212,101
+
+[8-wall@0,0|11]
+endPoint=388,311
+startPoint=345,272
+
+[8-wall@0,0|12]
+endPoint=388,311
+startPoint=335,328
+
+[8-wall@0,0|4]
+endPoint=146,204
+startPoint=54,312
+
+[8-wall@0,0|5]
+endPoint=19,283
+startPoint=54,312
+
+[8-wall@0,0|6]
+endPoint=104,195
+startPoint=19,283
+
+[8-wall@0,0|7]
+endPoint=146,204
+startPoint=104,195
+
+[8-wall@0,0|8]
+endPoint=335,328
+startPoint=163,88
+
+[8-wall@0,0|9]
+endPoint=212,101
+startPoint=163,88
+
+[9-ball@54,55]
+dummykey=true
+
+[9-bumper@103,164|8]
+dummykey=true
+
+[9-bumper@114,230|11]
+dummykey=true
+
+[9-bumper@128,197|9]
+dummykey=true
+
+[9-bumper@141,161|5]
+dummykey=true
+
+[9-bumper@152,123|0]
+dummykey=true
+
+[9-bumper@156,85|2]
+dummykey=true
+
+[9-bumper@162,25|13]
+dummykey=true
+
+[9-bumper@187,157|6]
+dummykey=true
+
+[9-bumper@221,153|12]
+dummykey=true
+
+[9-bumper@242,329|23]
+dummykey=true
+
+[9-bumper@243,290|18]
+dummykey=true
+
+[9-bumper@248,219|14]
+dummykey=true
+
+[9-bumper@248,254|17]
+dummykey=true
+
+[9-bumper@254,187|16]
+dummykey=true
+
+[9-bumper@257,149|3]
+dummykey=true
+
+[9-bumper@277,339|21]
+dummykey=true
+
+[9-bumper@317,338|20]
+dummykey=true
+
+[9-bumper@346,321|22]
+dummykey=true
+
+[9-bumper@371,295|24]
+dummykey=true
+
+[9-bumper@38,348|19]
+dummykey=true
+
+[9-bumper@58,324|15]
+dummykey=true
+
+[9-bumper@66,170|7]
+dummykey=true
+
+[9-bumper@77,296|10]
+dummykey=true
+
+[9-bumper@97,261|4]
+dummykey=true
+
+[9-cup@281,294|1]
+dummykey=true
+
+[9-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=6
+par=3
diff --git a/kolf/courses/USApro b/kolf/courses/USApro
new file mode 100644
index 00000000..bdc48566
--- /dev/null
+++ b/kolf/courses/USApro
@@ -0,0 +1,1982 @@
+[0-course@-50,-50]
+Name=USA Pro
+Name[bg]=Ю Ес Ей Про (USA Pro)
+Name[bn]=ইউএসএ প্রো
+Name[hi]=यूएसए प्रो
+Name[hu]=Amerikai profi
+Name[ja]=USAプロ
+Name[mk]=САД Профи
+Name[pt_BR]=Pro EUA
+Name[sr]=САД про
+Name[sr@Latn]=SAD pro
+Name[sv]=USA-proffs
+Name[xx]=xxUSA Proxx
+Name[zh_TW]=美國職業高球
+author=Rob Kaper
+
+[1-ball@22,350]
+dummykey=true
+
+[1-bridge@306,87|2]
+botWallVisible=false
+height=212
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=false
+width=68
+
+[1-cup@78,38|9]
+dummykey=true
+
+[1-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[1-puddle@-4,79|10]
+changeEnabled=false
+changeEvery=50
+height=218
+width=98
+
+[1-puddle@171,196|1]
+changeEnabled=false
+changeEvery=50
+height=130
+width=596
+
+[1-sand@14,207|0]
+changeEnabled=false
+changeEvery=50
+height=216
+width=840
+
+[1-sign@4,371|14]
+Comment=Daytona Beach, FL
+Comment[bg]=Дейтона бийч, Флорида
+Comment[bn]=ডেটোনা বীচ, ফ্লোরিডা
+Comment[hi]=डायटोना बीच, फ्लो.
+Comment[hu]=Daytona Beach, Florida
+Comment[ja]=デイトナビーチ, FL
+Comment[lv]=Daitonas pludmale, Florida
+Comment[mk]=Дејтона Бич, Флорида
+Comment[nb]=Daytona Beach, Florida
+Comment[nn]=Daytona Beach, Florida
+Comment[pt]=Praia de Daytona, FL
+Comment[pt_BR]=Praia de Daytona, FL
+Comment[ru]=Дайтон Бич
+Comment[sr]=Дејтона бич, Флорида
+Comment[sr@Latn]=Dejtona bič, Florida
+Comment[sv]=Daytona Beach, Florida
+Comment[ta]=டேடோனா கடல், FL
+Comment[tg]=Дайтон Бич, FL
+Comment[tr]=Daytona Plajı, FL
+Comment[uk]=Дайтона Біч
+Comment[wa]=Daytona Beach, Floride, USA
+Comment[xx]=xxDaytona Beach, FLxx
+Comment[zh_CN]=代托纳滩,佛罗里达州
+Comment[zh_TW]=Daytona 海灘,佛羅里達州
+botWallVisible=false
+height=34
+leftWallVisible=true
+rightWallVisible=false
+topWallVisible=true
+width=411
+
+[1-slope@-3,-4|11]
+grade=5
+gradient=Horizontal
+height=131
+reversed=true
+stuckOnGround=false
+width=222
+
+[1-slope@139,282|7]
+grade=6
+gradient=Vertical
+height=117
+reversed=true
+stuckOnGround=false
+width=53
+
+[1-slope@215,273|8]
+grade=4
+gradient=Vertical
+height=132
+reversed=false
+stuckOnGround=false
+width=53
+
+[1-slope@247,2|12]
+grade=5
+gradient=Horizontal
+height=152
+reversed=false
+stuckOnGround=false
+width=56
+
+[1-slope@60,281|6]
+grade=4
+gradient=Vertical
+height=119
+reversed=false
+stuckOnGround=false
+width=54
+
+[1-wall@0,0|13]
+endPoint=328,387
+startPoint=38,387
+
+[1-wall@0,0|3]
+endPoint=374,299
+startPoint=328,387
+
+[1-wall@0,0|4]
+endPoint=297,3
+startPoint=374,87
+
+[1-wall@0,0|5]
+endPoint=297,3
+startPoint=32,3
+
+[10-ball@364,364]
+dummykey=true
+
+[10-blackhole@142,54|9]
+exit=372,376
+exitDeg=170
+maxspeed=5
+minspeed=5
+
+[10-blackhole@193,248|7]
+exit=196,175
+exitDeg=220
+maxspeed=1
+minspeed=1
+
+[10-bridge@169,235|11]
+botWallVisible=false
+height=95
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=false
+width=49
+
+[10-bumper@122,163|17]
+dummykey=true
+
+[10-bumper@127,179|16]
+dummykey=true
+
+[10-bumper@135,151|19]
+dummykey=true
+
+[10-bumper@136,195|15]
+dummykey=true
+
+[10-bumper@145,212|14]
+dummykey=true
+
+[10-bumper@151,140|20]
+dummykey=true
+
+[10-bumper@157,227|13]
+dummykey=true
+
+[10-bumper@167,130|21]
+dummykey=true
+
+[10-bumper@175,228|12]
+dummykey=true
+
+[10-bumper@195,226|8]
+dummykey=true
+
+[10-bumper@214,227|22]
+dummykey=true
+
+[10-bumper@219,130|28]
+dummykey=true
+
+[10-bumper@232,227|34]
+dummykey=true
+
+[10-bumper@233,143|27]
+dummykey=true
+
+[10-bumper@242,212|18]
+dummykey=true
+
+[10-bumper@246,156|26]
+dummykey=true
+
+[10-bumper@253,198|23]
+dummykey=true
+
+[10-bumper@260,168|25]
+dummykey=true
+
+[10-bumper@262,184|24]
+dummykey=true
+
+[10-cup@193,121|32]
+dummykey=true
+
+[10-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[10-puddle@14,152|30]
+changeEnabled=false
+changeEvery=50
+height=302
+width=184
+
+[10-puddle@140,286|29]
+changeEnabled=false
+changeEvery=50
+height=86
+width=622
+
+[10-puddle@357,89|31]
+changeEnabled=false
+changeEvery=50
+height=422
+width=130
+
+[10-sand@101,111|10]
+changeEnabled=false
+changeEvery=50
+height=90
+width=146
+
+[10-sand@258,141|41]
+changeEnabled=false
+changeEvery=50
+height=38
+width=36
+
+[10-sign@-1,360|42]
+Comment=Washington DC (Pentagon)
+Comment[bg]=Вашингтон, окръг Колумбия (Пентагона)
+Comment[bn]=ওয়াশিংটন ডিসি (পেন্টাগন)
+Comment[es]=Washington DC (Pentágono)
+Comment[fr]=Washington DC (Pentagone)
+Comment[gl]=Washington DC (Pentágono)
+Comment[he]=הפנטגון בוושינגטון העיר
+Comment[hi]=वाशिंगटन डीसी (पेंटागन)
+Comment[it]=Washington DC (Pentagono)
+Comment[ja]=ワシントンDC (ペンタゴン)
+Comment[lv]=Wašingtona, Kolumbijas apgabals (Pentagons)
+Comment[mk]=Вашингтон ДЦ (Пентагон)
+Comment[pt]=Washington DC (Pentágono)
+Comment[pt_BR]=Washington DC (Pentágono)
+Comment[ru]=Пентагон
+Comment[sr]=Вашингтон ДЦ (Пентагон)
+Comment[sr@Latn]=Vašington DC (Pentagon)
+Comment[ta]= வாஷிங்டன் DC (பென்ட்டகன்)
+Comment[tg]=Вашингтон DC (Пентагон)
+Comment[uk]=Пентагон
+Comment[wa]=Washington DC (Pentagone), USA
+Comment[xx]=xxWashington DC (Pentagon)xx
+Comment[zh_CN]=华盛顿特区(五角大楼)
+Comment[zh_TW]=華盛頓特區(五角大廈)
+botWallVisible=true
+height=49
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=189
+
+[10-slope@136,173|4]
+grade=4
+gradient=Opposite Diagonal
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[10-slope@138,131|5]
+grade=4
+gradient=Diagonal
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[10-slope@177,173|2]
+grade=4
+gradient=Vertical
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[10-slope@178,133|1]
+grade=4
+gradient=Vertical
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[10-slope@217,174|3]
+grade=4
+gradient=Diagonal
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[10-slope@218,133|6]
+grade=4
+gradient=Opposite Diagonal
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[10-wall@0,0|33]
+endPoint=259,167
+startPoint=283,136
+
+[10-wall@0,0|35]
+endPoint=283,136
+startPoint=130,25
+
+[10-wall@0,0|36]
+endPoint=118,161
+startPoint=130,25
+
+[10-wall@0,0|37]
+endPoint=169,330
+startPoint=215,392
+
+[10-wall@0,0|38]
+endPoint=215,392
+startPoint=384,392
+
+[10-wall@0,0|39]
+endPoint=384,392
+startPoint=384,331
+
+[10-wall@0,0|40]
+endPoint=384,331
+startPoint=218,330
+
+[11-ball@36,41]
+dummykey=true
+
+[11-blackhole@140,181|15]
+exit=114,265
+exitDeg=30
+maxspeed=3
+minspeed=1
+
+[11-blackhole@225,184|13]
+exit=77,64
+exitDeg=0
+maxspeed=9
+minspeed=4
+
+[11-blackhole@256,272|18]
+exit=195,249
+exitDeg=110
+maxspeed=3
+minspeed=3
+
+[11-bridge@18,22|16]
+botWallVisible=false
+height=41
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=36
+
+[11-cup@183,170|0]
+dummykey=true
+
+[11-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=5
+
+[11-puddle@182,144|3]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[11-puddle@289,3|10]
+changeEnabled=false
+changeEvery=50
+height=58
+width=242
+
+[11-sand@17,316|17]
+changeEnabled=false
+changeEvery=50
+height=286
+width=318
+
+[11-sand@60,174|4]
+changeEnabled=false
+changeEvery=50
+height=174
+width=84
+
+[11-sign@-2,370|24]
+Comment=Palm Springs, CO
+Comment[bg]=Палм Спрингс, Колорадо
+Comment[bn]=পাম স্প্রিঙ্‌স, কলোরাডো
+Comment[hi]=पॉम स्प्रिंग्स, सीओ
+Comment[hu]=Palm Springs, Connecticut
+Comment[ja]=パームスプリング, CO
+Comment[lv]=Palmspringsa, Colorado
+Comment[mk]=Палм Спрингс, Колорадо
+Comment[nb]=Palm Springs, Colorado
+Comment[nn]=Palm Springs, Colorado
+Comment[ru]=Палм Спрингс
+Comment[sr]=Палм спрингс, Конектикат
+Comment[sr@Latn]=Palm springs, Konektikat
+Comment[sv]=Palm Springs, Colorado
+Comment[ta]=பாம் ஸ்பிரிங்ஸ், CO
+Comment[tg]=Палм Спрингс, CO
+Comment[uk]=Палм Спрінґс
+Comment[wa]=Palm Springs, Colorado, USA
+Comment[xx]=xxPalm Springs, COxx
+Comment[zh_CN]=棕榈泉,科罗拉多州
+Comment[zh_TW]=棕櫚泉,科羅拉多州
+botWallVisible=true
+height=36
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=146
+
+[11-slope@109,150|2]
+grade=4
+gradient=Elliptic
+height=63
+reversed=true
+stuckOnGround=false
+width=63
+
+[11-slope@120,-2|12]
+grade=4
+gradient=Opposite Diagonal
+height=51
+reversed=true
+stuckOnGround=false
+width=46
+
+[11-slope@163,191|23]
+grade=2
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[11-slope@165,8|11]
+grade=4
+gradient=Vertical
+height=40
+reversed=true
+stuckOnGround=false
+width=246
+
+[11-slope@195,152|1]
+grade=4
+gradient=Elliptic
+height=61
+reversed=true
+stuckOnGround=false
+width=61
+
+[11-slope@212,226|19]
+grade=8
+gradient=Elliptic
+height=91
+reversed=false
+stuckOnGround=false
+width=91
+
+[11-wall@0,0|14]
+endPoint=383,96
+startPoint=344,32
+
+[11-wall@0,0|20]
+endPoint=106,147
+startPoint=84,95
+
+[11-wall@0,0|21]
+endPoint=84,95
+startPoint=53,81
+
+[11-wall@0,0|22]
+endPoint=53,81
+startPoint=-1,88
+
+[11-wall@0,0|5]
+endPoint=189,108
+startPoint=106,147
+
+[11-wall@0,0|6]
+endPoint=189,108
+startPoint=277,180
+
+[11-wall@0,0|7]
+endPoint=277,180
+startPoint=181,283
+
+[11-wall@0,0|8]
+endPoint=181,283
+startPoint=345,375
+
+[11-wall@0,0|9]
+endPoint=345,375
+startPoint=383,96
+
+[2-ball@66,149]
+dummykey=true
+
+[2-bridge@-21,-1|1]
+botWallVisible=true
+height=19
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=428
+
+[2-bumper@103,124|33]
+dummykey=true
+
+[2-bumper@118,101|32]
+dummykey=true
+
+[2-bumper@275,32|23]
+dummykey=true
+
+[2-bumper@276,81|18]
+dummykey=true
+
+[2-bumper@307,139|19]
+dummykey=true
+
+[2-bumper@349,141|27]
+dummykey=true
+
+[2-bumper@88,146|34]
+dummykey=true
+
+[2-cup@272,132|0]
+dummykey=true
+
+[2-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=4
+
+[2-puddle@257,212|11]
+changeEnabled=false
+changeEvery=50
+height=50
+width=52
+
+[2-sand@161,120|9]
+changeEnabled=false
+changeEvery=50
+height=38
+width=40
+
+[2-sand@162,201|8]
+changeEnabled=false
+changeEvery=50
+height=40
+width=38
+
+[2-sign@26,345|31]
+Comment=Las Vegas, NV
+Comment[bg]=Лас Вегас, Невада
+Comment[bn]=লাস ভেগাস, নেভাডা
+Comment[el]=Λας Βέγκας, NV
+Comment[he]=לאס ווגס, NV
+Comment[hi]=लास वेगास, एनवी.
+Comment[hu]=Las Vegas, Nevada
+Comment[ja]=ラスベガス, NV
+Comment[lv]=Lasvegasa, Nevada
+Comment[mk]=Лас Вегас, Невада
+Comment[nb]=Las Vegas, Nevada
+Comment[nn]=Las Vegas, Nevada
+Comment[ru]=Лас Вегас
+Comment[sr]=Лас Вегас, Невада
+Comment[sr@Latn]=Las Vegas, Nevada
+Comment[sv]=Las Vegas, Nevada
+Comment[ta]= லாஸ் வேகாஸ்,NV
+Comment[tg]=Лас Вегас, NV
+Comment[uk]=Лас Вегас
+Comment[wa]=Las Vegas, Nevada, USA
+Comment[xx]=xxLas Vegas, NVxx
+Comment[zh_CN]=拉斯维加斯,内华达州
+Comment[zh_TW]=拉斯維加斯,內華達州
+botWallVisible=true
+height=34
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=157
+
+[2-slope@-1,296|25]
+grade=4
+gradient=Vertical
+height=109
+reversed=false
+stuckOnGround=false
+width=404
+
+[2-slope@140,140|4]
+grade=4
+gradient=Elliptic
+height=41
+reversed=true
+stuckOnGround=true
+width=41
+
+[2-slope@140,18|21]
+grade=6
+gradient=Horizontal
+height=84
+reversed=true
+stuckOnGround=false
+width=114
+
+[2-slope@181,98|5]
+grade=4
+gradient=Elliptic
+height=40
+reversed=true
+stuckOnGround=true
+width=40
+
+[2-slope@182,140|3]
+grade=2
+gradient=Elliptic
+height=40
+reversed=false
+stuckOnGround=true
+width=40
+
+[2-slope@182,181|7]
+grade=4
+gradient=Elliptic
+height=40
+reversed=true
+stuckOnGround=true
+width=40
+
+[2-slope@219,107|6]
+grade=4
+gradient=Elliptic
+height=73
+reversed=true
+stuckOnGround=true
+width=73
+
+[2-slope@25,19|30]
+grade=4
+gradient=Diagonal
+height=152
+reversed=true
+stuckOnGround=false
+width=43
+
+[2-slope@252,18|17]
+grade=4
+gradient=Opposite Diagonal
+height=64
+reversed=true
+stuckOnGround=false
+width=113
+
+[2-slope@27,166|24]
+grade=8
+gradient=Vertical
+height=28
+reversed=false
+stuckOnGround=false
+width=78
+
+[2-wall@0,0|10]
+endPoint=105,193
+startPoint=58,224
+
+[2-wall@0,0|12]
+endPoint=290,103
+startPoint=145,97
+
+[2-wall@0,0|13]
+endPoint=145,97
+startPoint=105,168
+
+[2-wall@0,0|14]
+endPoint=58,224
+startPoint=248,350
+
+[2-wall@0,0|15]
+endPoint=248,350
+startPoint=366,224
+
+[2-wall@0,0|16]
+endPoint=366,224
+startPoint=366,19
+
+[2-wall@0,0|2]
+endPoint=290,220
+startPoint=290,103
+
+[2-wall@0,0|20]
+endPoint=24,19
+startPoint=24,193
+
+[2-wall@0,0|22]
+endPoint=290,220
+startPoint=260,251
+
+[2-wall@0,0|26]
+endPoint=221,223
+startPoint=260,251
+
+[2-wall@0,0|28]
+endPoint=105,168
+startPoint=105,193
+
+[2-wall@0,0|29]
+endPoint=105,193
+startPoint=24,193
+
+[3-ball@334,12]
+dummykey=true
+
+[3-cup@366,146|1]
+dummykey=true
+
+[3-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[3-sign@-4,358|9]
+Comment=San Francisco, CA
+Comment[bg]=Сан Франциско, Калифорния
+Comment[bn]=সান ফ্রান্সিসকো, ক্যালিফোর্নিয়া
+Comment[el]=Σαν Φρανσίσκο, CA
+Comment[he]=סן פרנסיסקו,CA
+Comment[hi]=सान फ्रांसिस्को, सीए
+Comment[hu]=San Francisco, Kalifornia
+Comment[ja]=サンフランシスコ, CA
+Comment[lv]=Sanfrancisko, Kalifornija
+Comment[mk]=Сан Франциско, Калифорнија
+Comment[nn]=San Francisco, California
+Comment[pt]=São Francisco, CA
+Comment[pt_BR]=São Francisco, CA
+Comment[ru]=Сан-Франциско
+Comment[sr]=Сан Франциско, Калифорнија
+Comment[sr@Latn]=San Francisko, Kalifornija
+Comment[sv]=San Francisco, Kalifornien
+Comment[ta]= சான் ஃபிரான்ஸிஸ்கோ,CA
+Comment[tg]=Сан Франтсиско, CA
+Comment[uk]=Сан-Франциско
+Comment[wa]=San Francisco, Californeye, USA
+Comment[xx]=xxSan Francisco, CAxx
+Comment[zh_CN]=洛杉矶,加利福尼亚州
+Comment[zh_TW]=舊金山,加州
+botWallVisible=true
+height=48
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=436
+
+[3-slope@-163,-252|8]
+grade=4
+gradient=Elliptic
+height=384
+reversed=false
+stuckOnGround=false
+width=384
+
+[3-slope@1,139|6]
+grade=4
+gradient=Vertical
+height=104
+reversed=false
+stuckOnGround=false
+width=394
+
+[3-slope@195,-158|10]
+grade=4
+gradient=Elliptic
+height=273
+reversed=false
+stuckOnGround=false
+width=273
+
+[3-slope@32,280|7]
+grade=4
+gradient=Horizontal
+height=49
+reversed=true
+stuckOnGround=false
+width=232
+
+[3-wall@0,0|0]
+endPoint=389,383
+startPoint=389,27
+
+[3-wall@0,0|2]
+endPoint=389,27
+startPoint=261,279
+
+[3-wall@0,0|3]
+endPoint=261,279
+startPoint=99,279
+
+[3-wall@0,0|4]
+endPoint=32,329
+startPoint=261,329
+
+[3-wall@0,0|5]
+endPoint=32,329
+startPoint=32,24
+
+[4-ball@354,354]
+dummykey=true
+
+[4-bridge@-11,172|2]
+botWallVisible=false
+height=20
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=472
+
+[4-cup@182,182|0]
+dummykey=true
+
+[4-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[4-sign@201,171|8]
+Comment=Grand Canyon
+Comment[ar]=الوادي العظيم
+Comment[bg]=Гранд Каньон
+Comment[bn]=গ্র্যান্ড ক্যানিয়ন
+Comment[el]=Γραντ Κάνυον
+Comment[es]=Gran Cañón
+Comment[gl]=Gran Cañón
+Comment[he]=גרנד קניון
+Comment[hi]=ग्रांड केन्यॉन
+Comment[ja]=グランドキャニオン
+Comment[lv]=Lielais kanjons
+Comment[mk]=Гранд кањон
+Comment[ru]=Большой Каньон
+Comment[sr]=Велики кањон
+Comment[sr@Latn]=Veliki kanjon
+Comment[ta]= கிராண்ட் கேண்யான்
+Comment[tg]=Канёни Калон
+Comment[tr]=Büyük Kanyon
+Comment[uk]=Великий каньйон
+Comment[wa]=Grand Canyon, USA
+Comment[xx]=xxGrand Canyonxx
+Comment[zh_CN]=大峡谷
+Comment[zh_TW]=大峽谷
+botWallVisible=true
+height=23
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=223
+
+[4-slope@-1,129|1]
+grade=4
+gradient=Vertical
+height=61
+reversed=true
+stuckOnGround=false
+width=416
+
+[4-slope@-14,192|3]
+grade=4
+gradient=Vertical
+height=38
+reversed=false
+stuckOnGround=false
+width=422
+
+[4-slope@-2,273|6]
+grade=4
+gradient=Vertical
+height=45
+reversed=false
+stuckOnGround=false
+width=445
+
+[4-slope@-2,44|7]
+grade=4
+gradient=Vertical
+height=53
+reversed=true
+stuckOnGround=false
+width=502
+
+[4-slope@-45,230|5]
+grade=4
+gradient=Vertical
+height=42
+reversed=true
+stuckOnGround=false
+width=454
+
+[4-slope@0,87|4]
+grade=4
+gradient=Vertical
+height=42
+reversed=false
+stuckOnGround=false
+width=432
+
+[5-ball@23,348]
+dummykey=true
+
+[5-bridge@107,258|9]
+botWallVisible=true
+height=26
+leftWallVisible=true
+rightWallVisible=false
+topWallVisible=true
+width=112
+
+[5-cup@120,271|0]
+dummykey=true
+
+[5-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[5-puddle@117,135|2]
+changeEnabled=false
+changeEvery=50
+height=84
+width=110
+
+[5-puddle@165,297|8]
+changeEnabled=false
+changeEvery=50
+height=214
+width=102
+
+[5-puddle@173,168|7]
+changeEnabled=false
+changeEvery=50
+height=108
+width=62
+
+[5-puddle@180,140|6]
+changeEnabled=false
+changeEvery=50
+height=50
+width=144
+
+[5-sand@65,346|1]
+changeEnabled=false
+changeEvery=50
+height=438
+width=48
+
+[5-sign@219,344|10]
+Comment=Lake Tahoe, CA/NV
+Comment[bg]=Езерото Тахо, Калифорния/Невада
+Comment[bn]=লেক টাহো, ক্যালিফোর্নিয়া/নেভাডা
+Comment[el]=Λίμνη Tahoe, CA/NV
+Comment[es]=Lago Tahoe, CA/NV
+Comment[fr]=Lac Tahoe, CA/NV
+Comment[gl]=Lago Tahoe, CA/NV
+Comment[hi]=लेक ताहोए, सीए/एनवी
+Comment[hu]=Lake Tahoe, Kalifornia/Nevada
+Comment[it]=Lago Tahoe, CA/NV
+Comment[ja]=レイク・タホ, CA/NV
+Comment[lv]=Taho ezers, Kalifornija/Nevada
+Comment[mk]=Езеро Тахо, Калифорнија/Сев. Вирџинија
+Comment[nn]=Lake Tahoe, California/Nevada
+Comment[pt]=Lago Tahoe, CA/NV
+Comment[pt_BR]=Lago Tahoe, CA/NV
+Comment[ru]=Озеро Тахоэ
+Comment[sr]=Језеро Тахо, Кал./Нев.
+Comment[sr@Latn]=Jezero Taho, Kal./Nev.
+Comment[sv]=Lake Tahoe, Kalifornien/Nevada
+Comment[ta]= லேக் தஹோ,CA/NV
+Comment[tg]=Кӯли Таҳоэ, CA/NV
+Comment[uk]=Озеро Тахое
+Comment[wa]=Lak Tahoe, Californeye/Nevada, USA
+Comment[xx]=xxLake Tahoe, CA/NVxx
+Comment[zh_CN]=塔霍湖,加利福尼亚州/内华达州
+Comment[zh_TW]=太浩湖,加州/內華達州
+botWallVisible=true
+height=27
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=202
+
+[5-slope@10,9|4]
+grade=4
+gradient=Diagonal
+height=110
+reversed=true
+stuckOnGround=false
+width=113
+
+[5-slope@171,135|5]
+grade=4
+gradient=Horizontal
+height=268
+reversed=true
+stuckOnGround=false
+width=81
+
+[5-slope@261,9|11]
+grade=4
+gradient=Opposite Diagonal
+height=112
+reversed=true
+stuckOnGround=false
+width=129
+
+[5-slope@69,134|3]
+grade=5
+gradient=Horizontal
+height=281
+reversed=false
+stuckOnGround=false
+width=102
+
+[5-wall@0,0|12]
+endPoint=77,99
+startPoint=57,133
+
+[5-wall@0,0|13]
+endPoint=77,99
+startPoint=119,88
+
+[6-ball@102,35]
+dummykey=true
+
+[6-bridge@138,328|27]
+botWallVisible=false
+height=24
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=false
+width=23
+
+[6-bridge@168,127|8]
+botWallVisible=false
+height=92
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=false
+width=21
+
+[6-bridge@215,240|13]
+botWallVisible=false
+height=66
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=false
+width=21
+
+[6-bumper@182,182|15]
+dummykey=true
+
+[6-bumper@182,182|16]
+dummykey=true
+
+[6-cup@107,376|1]
+dummykey=true
+
+[6-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=4
+
+[6-puddle@-5,251|2]
+changeEnabled=false
+changeEvery=50
+height=296
+width=342
+
+[6-puddle@186,179|4]
+changeEnabled=false
+changeEvery=50
+height=68
+width=192
+
+[6-puddle@215,274|3]
+changeEnabled=false
+changeEvery=50
+height=64
+width=222
+
+[6-puddle@222,374|7]
+changeEnabled=false
+changeEvery=50
+height=76
+width=148
+
+[6-puddle@230,341|6]
+changeEnabled=false
+changeEvery=50
+height=20
+width=226
+
+[6-puddle@292,354|5]
+changeEnabled=false
+changeEvery=50
+height=152
+width=144
+
+[6-puddle@497,225|0]
+changeEnabled=false
+changeEvery=50
+height=508
+width=494
+
+[6-sand@86,349|34]
+changeEnabled=true
+changeEvery=51
+height=188
+width=368
+
+[6-sign@3,101|17]
+Comment=Florida Keys, FL
+Comment[bg]=Флорида Кийс, Флорида
+Comment[bn]=ফ্লোরিডা কিইস, ফ্লোরিডা
+Comment[hi]=फ्लोरिडा कीस, एफएल
+Comment[hu]=Florida Keys, Florida
+Comment[ja]=フロリダキーズ, FL
+Comment[mk]=Гребени на Флорида, Флорида
+Comment[nn]=Florida Keys, Florida
+Comment[pt_BR]=Flórida Keys, FL
+Comment[ru]=Флорида
+Comment[sr]=Флорида киз, Флорида
+Comment[sr@Latn]=Florida kiz, Florida
+Comment[sv]=Florida Keys, Florida
+Comment[ta]=ஃப்ளோரிடா கீஸ், FL
+Comment[tg]=Флорида, FL
+Comment[uk]=Флорида Кіз
+Comment[wa]=Florida Keys, Floride, USA
+Comment[xx]=xxFlorida Keys, FLxx
+Comment[zh_CN]=佛罗里达链岛,佛罗里达州
+Comment[zh_TW]=佛羅里達群島,佛羅里達州
+botWallVisible=true
+height=30
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=117
+
+[6-slope@128,300|14]
+grade=4
+gradient=Horizontal
+height=42
+reversed=false
+stuckOnGround=false
+width=115
+
+[6-slope@158,216|23]
+grade=4
+gradient=Horizontal
+height=32
+reversed=true
+stuckOnGround=false
+width=99
+
+[6-slope@165,23|21]
+grade=4
+gradient=Vertical
+height=104
+reversed=false
+stuckOnGround=false
+width=28
+
+[6-slope@193,23|22]
+grade=4
+gradient=Horizontal
+height=31
+reversed=false
+stuckOnGround=false
+width=149
+
+[6-slope@193,53|35]
+grade=4
+gradient=Elliptic
+height=101
+reversed=false
+stuckOnGround=false
+width=101
+
+[6-wall@0,0|10]
+endPoint=177,244
+startPoint=215,240
+
+[6-wall@0,0|11]
+endPoint=236,240
+startPoint=224,213
+
+[6-wall@0,0|12]
+endPoint=224,213
+startPoint=189,219
+
+[6-wall@0,0|18]
+endPoint=338,24
+startPoint=189,127
+
+[6-wall@0,0|19]
+endPoint=58,19
+startPoint=168,127
+
+[6-wall@0,0|20]
+endPoint=58,19
+startPoint=338,24
+
+[6-wall@0,0|24]
+endPoint=236,306
+startPoint=221,332
+
+[6-wall@0,0|25]
+endPoint=215,306
+startPoint=152,310
+
+[6-wall@0,0|26]
+endPoint=221,332
+startPoint=161,328
+
+[6-wall@0,0|28]
+endPoint=152,310
+startPoint=138,328
+
+[6-wall@0,0|29]
+endPoint=121,396
+startPoint=161,352
+
+[6-wall@0,0|30]
+endPoint=121,396
+startPoint=39,396
+
+[6-wall@0,0|31]
+endPoint=39,396
+startPoint=94,373
+
+[6-wall@0,0|32]
+endPoint=94,373
+startPoint=127,347
+
+[6-wall@0,0|33]
+endPoint=127,347
+startPoint=138,352
+
+[6-wall@0,0|9]
+endPoint=177,244
+startPoint=168,219
+
+[7-ball@89,370]
+dummykey=true
+
+[7-cup@375,24|0]
+dummykey=true
+
+[7-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[7-puddle@215,236|1]
+changeEnabled=false
+changeEvery=50
+height=340
+width=224
+
+[7-puddle@245,87|3]
+changeEnabled=false
+changeEvery=50
+height=44
+width=150
+
+[7-puddle@68,170|2]
+changeEnabled=false
+changeEvery=50
+height=78
+width=178
+
+[7-sign@11,116|7]
+Comment=Golden Gate
+Comment[ar]=البوابة الذهبية
+Comment[bg]=Голдън Гейт
+Comment[bn]=গোল্ডেন গেট
+Comment[cs]=Zlatá brána
+Comment[el]=Χρυσή πύλη
+Comment[gl]=Ponte de Rande
+Comment[he]=שער הזהב
+Comment[hi]=गोल्डन गेट
+Comment[ja]=ゴールデンゲート
+Comment[lv]=Zelta vārti
+Comment[mk]=Голден Гејт
+Comment[ru]=Золотые Ворота
+Comment[sr]=Голден гејт
+Comment[sr@Latn]=Golden gejt
+Comment[ta]=கோல்டன் கேட்
+Comment[tg]=Дарвозаҳои Тиллоӣ
+Comment[tr]=Altın Kapı
+Comment[uk]=Золоті ворота
+Comment[wa]=Golden Gate, USA
+Comment[xx]=xxGolden Gatexx
+Comment[zh_CN]=金门大桥
+Comment[zh_TW]=金門大橋
+botWallVisible=false
+height=97
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=false
+width=40
+
+[7-sign@135,364|25]
+Comment=San Francisco, CA
+Comment[bg]=Сан Франциско, Калифорния
+Comment[bn]=সান ফ্রান্সিসকো, ক্যালিফোর্নিয়া
+Comment[el]=Σαν Φρανσίσκο, CA
+Comment[he]=סן פרנסיסקו,CA
+Comment[hi]=सान फ्रांसिस्को, सीए
+Comment[hu]=San Francisco, Kalifornia
+Comment[ja]=サンフランシスコ, CA
+Comment[lv]=Sanfrancisko, Kalifornija
+Comment[mk]=Сан Франциско, Калифорнија
+Comment[nn]=San Francisco, California
+Comment[pt]=São Francisco, CA
+Comment[pt_BR]=São Francisco, CA
+Comment[ru]=Сан-Франциско
+Comment[sr]=Сан Франциско, Калифорнија
+Comment[sr@Latn]=San Francisko, Kalifornija
+Comment[sv]=San Francisco, Kalifornien
+Comment[ta]= சான் ஃபிரான்ஸிஸ்கோ,CA
+Comment[tg]=Сан Франтсиско, CA
+Comment[uk]=Сан-Франциско
+Comment[wa]=San Francisco, Californeye, USA
+Comment[xx]=xxSan Francisco, CAxx
+Comment[zh_CN]=洛杉矶,加利福尼亚州
+Comment[zh_TW]=舊金山,加州
+botWallVisible=true
+height=40
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=159
+
+[7-sign@98,209|24]
+Comment=Bay Bridge
+Comment[ar]=جسر الخليج (باي(
+Comment[bg]=Бей Бридж
+Comment[bn]=বে ব্রিজ
+Comment[de]=Bay-Brücke
+Comment[es]=Puente de la Bahía
+Comment[gl]=Ponte da Ría
+Comment[he]=גשר המפרץ
+Comment[hi]=बे-ब्रिज
+Comment[ja]=ベイブリッジ
+Comment[lv]=Beibridža
+Comment[mk]=Беј Бриџ
+Comment[pt_BR]=Ponte da Baía
+Comment[ru]=Залив Сан-Фрациско
+Comment[sr]=Беј бриџ
+Comment[sr@Latn]=Bej bridž
+Comment[ta]=விரிகுடா பாலம்
+Comment[tg]=Бэй Бриҷ
+Comment[tr]=Bay Köprüsü
+Comment[uk]=Бей-бридж
+Comment[xx]=xxBay Bridgexx
+Comment[zh_CN]=海湾大桥
+Comment[zh_TW]=海灣大橋
+botWallVisible=true
+height=24
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=true
+width=241
+
+[7-slope@11,212|4]
+grade=6
+gradient=Elliptic
+height=74
+reversed=false
+stuckOnGround=false
+width=74
+
+[7-slope@13,313|23]
+grade=4
+gradient=Vertical
+height=34
+reversed=true
+stuckOnGround=false
+width=150
+
+[7-slope@17,23|5]
+grade=2
+gradient=Elliptic
+height=108
+reversed=false
+stuckOnGround=false
+width=108
+
+[7-slope@293,9|22]
+grade=6
+gradient=Opposite Diagonal
+height=94
+reversed=true
+stuckOnGround=false
+width=95
+
+[7-slope@333,49|21]
+grade=5
+gradient=Vertical
+height=339
+reversed=false
+stuckOnGround=false
+width=56
+
+[7-slope@55,211|6]
+grade=4
+gradient=Horizontal
+height=73
+reversed=true
+stuckOnGround=false
+width=78
+
+[7-wall@0,0|10]
+endPoint=88,294
+startPoint=153,386
+
+[7-wall@0,0|11]
+endPoint=120,132
+startPoint=51,116
+
+[7-wall@0,0|12]
+endPoint=120,132
+startPoint=169,71
+
+[7-wall@0,0|13]
+endPoint=169,71
+startPoint=249,59
+
+[7-wall@0,0|14]
+endPoint=249,59
+startPoint=322,74
+
+[7-wall@0,0|15]
+endPoint=322,74
+startPoint=321,99
+
+[7-wall@0,0|16]
+endPoint=321,99
+startPoint=292,107
+
+[7-wall@0,0|17]
+endPoint=292,107
+startPoint=322,165
+
+[7-wall@0,0|18]
+endPoint=339,209
+startPoint=322,165
+
+[7-wall@0,0|19]
+endPoint=339,232
+startPoint=317,325
+
+[7-wall@0,0|20]
+endPoint=317,325
+startPoint=270,388
+
+[7-wall@0,0|8]
+endPoint=98,209
+startPoint=51,213
+
+[7-wall@0,0|9]
+endPoint=98,232
+startPoint=88,294
+
+[8-ball@202,333]
+dummykey=true
+
+[8-cup@203,88|1]
+dummykey=true
+
+[8-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[8-puddle@205,213|0]
+changeEnabled=false
+changeEvery=50
+height=210
+width=76
+
+[8-sand@201,35|3]
+changeEnabled=false
+changeEvery=50
+height=40
+width=45
+
+[8-sand@202,363|2]
+changeEnabled=false
+changeEvery=50
+height=34
+width=98
+
+[8-sign@147,370|19]
+Comment=Washington DC
+Comment[bg]=Вашингтон, окръг Колумбия
+Comment[bn]=ওয়াশিংটন ডিসি
+Comment[el]=Ουάσινγκτον
+Comment[he]=וושינגטון הבירה
+Comment[hi]=वाशिंगटन डीसी
+Comment[ja]=ワシントンDC
+Comment[lv]=Vašingtona, Kolumbijas apgabals
+Comment[mk]=Вашингтон ДЦ
+Comment[ru]=Вашингтон
+Comment[sr]=Вашингтон ДЦ
+Comment[sr@Latn]=Vašington DC
+Comment[ta]=வாஷிங்டன் DC
+Comment[tg]=Вашингтон DC
+Comment[uk]=Вашингтон
+Comment[wa]=Washington DC, USA
+Comment[xx]=xxWashington DCxx
+Comment[zh_CN]=华盛顿特区
+Comment[zh_TW]=華盛頓特區
+botWallVisible=true
+height=32
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=111
+
+[8-slope@101,291|8]
+grade=4
+gradient=Elliptic
+height=59
+reversed=true
+stuckOnGround=false
+width=59
+
+[8-slope@11,11|4]
+grade=2
+gradient=Vertical
+height=376
+reversed=false
+stuckOnGround=false
+width=88
+
+[8-slope@136,8|18]
+grade=5
+gradient=Elliptic
+height=137
+reversed=true
+stuckOnGround=false
+width=137
+
+[8-slope@174,46|10]
+grade=5
+gradient=Elliptic
+height=61
+reversed=false
+stuckOnGround=false
+width=61
+
+[8-slope@244,9|7]
+grade=3
+gradient=Vertical
+height=381
+reversed=true
+stuckOnGround=false
+width=65
+
+[8-slope@245,291|9]
+grade=4
+gradient=Elliptic
+height=59
+reversed=true
+stuckOnGround=false
+width=59
+
+[8-slope@311,11|5]
+grade=2
+gradient=Vertical
+height=378
+reversed=false
+stuckOnGround=false
+width=75
+
+[8-slope@99,11|6]
+grade=3
+gradient=Vertical
+height=377
+reversed=true
+stuckOnGround=false
+width=65
+
+[8-wall@0,0|11]
+endPoint=140,296
+startPoint=175,108
+
+[8-wall@0,0|12]
+endPoint=172,108
+startPoint=235,108
+
+[8-wall@0,0|13]
+endPoint=237,108
+startPoint=264,297
+
+[8-wall@0,0|14]
+endPoint=70,54
+startPoint=33,91
+
+[8-wall@0,0|15]
+endPoint=175,108
+startPoint=142,123
+
+[8-wall@0,0|16]
+endPoint=271,128
+startPoint=235,108
+
+[8-wall@0,0|17]
+endPoint=368,128
+startPoint=326,182
+
+[9-ball@361,37]
+dummykey=true
+
+[9-bridge@184,29|27]
+botWallVisible=false
+height=26
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=true
+width=80
+
+[9-bridge@218,278|26]
+botWallVisible=true
+height=28
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=80
+
+[9-cup@125,313|0]
+dummykey=true
+
+[9-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[9-puddle@128,389|12]
+changeEnabled=false
+changeEvery=50
+height=26
+width=28
+
+[9-puddle@166,379|13]
+changeEnabled=false
+changeEvery=50
+height=10
+width=88
+
+[9-puddle@216,370|14]
+changeEnabled=false
+changeEvery=50
+height=22
+width=30
+
+[9-puddle@220,8|25]
+changeEnabled=false
+changeEvery=50
+height=40
+width=18
+
+[9-puddle@222,46|24]
+changeEnabled=false
+changeEvery=50
+height=58
+width=20
+
+[9-puddle@223,356|15]
+changeEnabled=false
+changeEvery=50
+height=28
+width=18
+
+[9-puddle@225,82|23]
+changeEnabled=false
+changeEvery=50
+height=44
+width=18
+
+[9-puddle@227,121|22]
+changeEnabled=false
+changeEvery=50
+height=74
+width=14
+
+[9-puddle@231,152|21]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[9-puddle@240,335|16]
+changeEnabled=false
+changeEvery=50
+height=30
+width=45
+
+[9-puddle@254,171|20]
+changeEnabled=false
+changeEvery=50
+height=52
+width=36
+
+[9-puddle@256,302|17]
+changeEnabled=false
+changeEvery=50
+height=100
+width=24
+
+[9-puddle@257,205|19]
+changeEnabled=false
+changeEvery=50
+height=42
+width=22
+
+[9-puddle@262,246|18]
+changeEnabled=false
+changeEvery=50
+height=66
+width=20
+
+[9-sign@291,368|28]
+Comment=Niagara Falls, NY
+Comment[bg]=Ниагара, Ню Йорк
+Comment[bn]=নায়েগ্রা জলপ্রপাত, নিউ ইয়র্ক
+Comment[de]=Niagara-Fälle, NY
+Comment[el]=Καταρράχτες Νιαγάρα, NY
+Comment[es]=Cataratas del Niágara, NY
+Comment[fr]=Chutes du Niagara, NY
+Comment[gl]=Fervenzas do Niagara, NY
+Comment[he]=מפלי ניאגרה, NY
+Comment[hi]=नियाग्रा फाल्स, एनवाय
+Comment[hu]=Niagara Falls, New York
+Comment[ja]=ナイアガラの滝, NY
+Comment[lv]=Niagāras ūdenskritums, Ņujorka
+Comment[mk]=Нијагарини водопади, НЈ
+Comment[nn]=Niagara Falls, New York
+Comment[pt]=Catarata do Niagara, NY
+Comment[pt_BR]=Cataratas do Niágara, NY
+Comment[ru]=Ниагарский водопад
+Comment[sk]=Niagarské vodopády, NY
+Comment[sr]=Нијагарини водопади, Њујорк
+Comment[sr@Latn]=Nijagarini vodopadi, Njujork
+Comment[sv]=Niagarafallen, Staten New York
+Comment[ta]=நயாகரா நீர்வீழ்ச்சி,NY
+Comment[tg]=Шаршараи Ниагара, NY
+Comment[tr]=Niagara Şelalesi, NY
+Comment[uk]=Ніагарський водоспад
+Comment[wa]=Niagara, Noû York, USA
+Comment[xx]=xxNiagara Falls, NYxx
+Comment[zh_CN]=尼亚加拉大瀑布,纽约州
+Comment[zh_TW]=尼加拉瀑布,紐約州
+botWallVisible=true
+height=36
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=112
+
+[9-slope@-3,148|11]
+grade=4
+gradient=Vertical
+height=57
+reversed=false
+stuckOnGround=false
+width=191
+
+[9-slope@-5,92|1]
+grade=4
+gradient=Vertical
+height=45
+reversed=false
+stuckOnGround=false
+width=232
+
+[9-slope@103,215|5]
+grade=4
+gradient=Vertical
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[9-slope@143,214|6]
+grade=4
+gradient=Opposite Diagonal
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[9-slope@144,254|2]
+grade=4
+gradient=Horizontal
+height=136
+reversed=true
+stuckOnGround=false
+width=42
+
+[9-slope@189,147|10]
+grade=4
+gradient=Opposite Diagonal
+height=63
+reversed=false
+stuckOnGround=false
+width=70
+
+[9-slope@226,93|7]
+grade=4
+gradient=Opposite Diagonal
+height=44
+reversed=false
+stuckOnGround=false
+width=184
+
+[9-slope@252,171|9]
+grade=4
+gradient=Diagonal
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[9-slope@291,172|8]
+grade=4
+gradient=Vertical
+height=41
+reversed=false
+stuckOnGround=false
+width=117
+
+[9-slope@333,9|29]
+grade=4
+gradient=Elliptic
+height=59
+reversed=true
+stuckOnGround=true
+width=59
+
+[9-slope@62,254|3]
+grade=4
+gradient=Opposite Diagonal
+height=133
+reversed=true
+stuckOnGround=false
+width=46
+
+[9-slope@63,214|4]
+grade=4
+gradient=Diagonal
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[9-wall@0,0|30]
+endPoint=138,14
+startPoint=4,65
+
+[9-wall@0,0|31]
+endPoint=138,14
+startPoint=184,29
+
+[9-wall@0,0|32]
+endPoint=264,29
+startPoint=361,8
+
+[9-wall@0,0|33]
+endPoint=361,8
+startPoint=393,22
+
+[9-wall@0,0|34]
+endPoint=393,22
+startPoint=393,129
+
+[9-wall@0,0|35]
+endPoint=393,129
+startPoint=393,171
+
+[9-wall@0,0|36]
+endPoint=393,171
+startPoint=392,214
+
+[9-wall@0,0|37]
+endPoint=337,305
+startPoint=392,214
+
+[9-wall@0,0|38]
+endPoint=337,305
+startPoint=298,305
+
+[9-wall@0,0|39]
+endPoint=4,208
+startPoint=4,65
+
+[9-wall@0,0|40]
+endPoint=4,208
+startPoint=46,394
+
+[9-wall@0,0|41]
+endPoint=46,394
+startPoint=108,385
+
+[9-wall@0,0|42]
+endPoint=108,385
+startPoint=124,371
+
+[9-wall@0,0|43]
+endPoint=124,371
+startPoint=142,370
+
+[9-wall@0,0|44]
+endPoint=190,371
+startPoint=218,305
diff --git a/kolf/editor.cpp b/kolf/editor.cpp
new file mode 100644
index 00000000..4cf6e61a
--- /dev/null
+++ b/kolf/editor.cpp
@@ -0,0 +1,60 @@
+#include <kdialog.h>
+#include <klistbox.h>
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qwidget.h>
+#include <qframe.h>
+
+#include "editor.h"
+#include "game.h"
+
+Editor::Editor(ObjectList *list, QWidget *parent, const char *name)
+ : QWidget(parent, name)
+{
+ this->list = list;
+ config = 0;
+
+ hlayout = new QHBoxLayout(this, KDialog::marginHint(), KDialog::spacingHint());
+
+ QVBoxLayout *vlayout = new QVBoxLayout(hlayout, KDialog::spacingHint());
+ vlayout->addWidget(new QLabel(i18n("Add object:"), this));
+ listbox = new KListBox(this, "Listbox");
+ vlayout->addWidget(listbox);
+ hlayout->setStretchFactor(vlayout, 2);
+
+ QStringList items;
+ Object *obj = 0;
+ for (obj = list->first(); obj; obj = list->next())
+ items.append(obj->name());
+
+ listbox->insertStringList(items);
+
+ connect(listbox, SIGNAL(executed(QListBoxItem *)), SLOT(listboxExecuted(QListBoxItem *)));
+}
+
+void Editor::listboxExecuted(QListBoxItem * /*item*/)
+{
+ int curItem = listbox->currentItem();
+ if (curItem < 0)
+ return;
+
+ emit addNewItem(list->at(curItem));
+}
+
+void Editor::setItem(CanvasItem *item)
+{
+ delete config;
+ config = item->config(this);
+ if (!config)
+ return;
+ config->ctorDone();
+ hlayout->addWidget(config);
+ hlayout->setStretchFactor(config, 2);
+ config->setFrameStyle(QFrame::Box | QFrame::Raised);
+ config->setLineWidth(1);
+ config->show();
+ connect(config, SIGNAL(modified()), this, SIGNAL(changed()));
+}
+
+#include "editor.moc"
diff --git a/kolf/editor.h b/kolf/editor.h
new file mode 100644
index 00000000..4d5a3ac5
--- /dev/null
+++ b/kolf/editor.h
@@ -0,0 +1,37 @@
+#ifndef EDITOR_H_INCLUDED
+#define EDITOR_H_INCLUDED
+
+#include <qwidget.h>
+
+#include "game.h"
+
+class KListBox;
+class QHBoxLayout;
+class QListBoxItem;
+class Config;
+
+class Editor : public QWidget
+{
+ Q_OBJECT
+
+public:
+ Editor(ObjectList *list, QWidget * = 0, const char * = 0);
+
+signals:
+ void changed();
+ void addNewItem(Object *);
+
+public slots:
+ void setItem(CanvasItem *);
+
+private slots:
+ void listboxExecuted(QListBoxItem *);
+
+private:
+ ObjectList *list;
+ QHBoxLayout *hlayout;
+ KListBox *listbox;
+ Config *config;
+};
+
+#endif
diff --git a/kolf/floater.cpp b/kolf/floater.cpp
new file mode 100644
index 00000000..73612bc8
--- /dev/null
+++ b/kolf/floater.cpp
@@ -0,0 +1,277 @@
+#include <qlabel.h>
+#include <qslider.h>
+
+#include <kconfig.h>
+
+#include "floater.h"
+
+void FloaterGuide::aboutToDelete()
+{
+ game->removeItem(floater);
+ aboutToDie();
+ floater->aboutToDie();
+ delete floater;
+ almostDead = true;
+}
+
+void FloaterGuide::aboutToDie()
+{
+ if (almostDead)
+ return;
+ else
+ Wall::aboutToDie();
+}
+
+void FloaterGuide::moveBy(double dx, double dy)
+{
+ Wall::moveBy(dx, dy);
+ if (floater)
+ floater->reset();
+}
+
+void FloaterGuide::setPoints(int xa, int ya, int xb, int yb)
+{
+ if (fabs(xa - xb) > 0 || fabs(ya - yb) > 0)
+ {
+ Wall::setPoints(xa, ya, xb, yb);
+ if (floater)
+ floater->reset();
+ }
+}
+
+Config *FloaterGuide::config(QWidget *parent)
+{
+ return floater->config(parent);
+}
+
+/////////////////////////
+
+Floater::Floater(QRect rect, QCanvas *canvas)
+ : Bridge(rect, canvas), speedfactor(16)
+{
+ wall = 0;
+ setEnabled(true);
+ noUpdateZ = false;
+ haventMoved = true;
+ wall = new FloaterGuide(this, canvas);
+ wall->setPoints(100, 100, 200, 200);
+ wall->setPen(QPen(wall->pen().color().light(), wall->pen().width() - 1));
+ move(wall->endPoint().x(), wall->endPoint().y());
+
+ setTopWallVisible(false);
+ setBotWallVisible(false);
+ setLeftWallVisible(false);
+ setRightWallVisible(false);
+
+ newSize(width(), height());
+ moveBy(0, 0);
+ setSpeed(0);
+
+ editModeChanged(false);
+ reset();
+}
+
+void Floater::setGame(KolfGame *game)
+{
+ Bridge::setGame(game);
+
+ wall->setGame(game);
+}
+
+void Floater::editModeChanged(bool changed)
+{
+ if (changed)
+ wall->editModeChanged(true);
+ Bridge::editModeChanged(changed);
+ wall->setVisible(changed);
+}
+
+void Floater::advance(int phase)
+{
+ if (!isEnabled())
+ return;
+
+ Bridge::advance(phase);
+
+ if (phase == 1 && (xVelocity() || yVelocity()))
+ {
+ if (Vector(origin, QPoint(x(), y())).magnitude() > vector.magnitude())
+ {
+ vector.setDirection(vector.direction() + M_PI);
+ origin = (origin == wall->startPoint()? wall->endPoint() : wall->startPoint());
+
+ setVelocity(-xVelocity(), -yVelocity());
+ }
+ }
+}
+
+void Floater::reset()
+{
+ QPoint start = wall->startPoint() + QPoint(wall->x(), wall->y());
+ QPoint end = wall->endPoint() + QPoint(wall->x(), wall->y());
+
+ vector = Vector(end, start);
+ origin = end;
+
+ move(origin.x(), origin.y());
+ setSpeed(speed);
+}
+
+QPtrList<QCanvasItem> Floater::moveableItems() const
+{
+ QPtrList<QCanvasItem> ret(wall->moveableItems());
+ ret.append(wall);
+ ret.append(point);
+ return ret;
+}
+
+void Floater::aboutToDie()
+{
+ if (wall)
+ wall->setVisible(false);
+ Bridge::aboutToDie();
+ setEnabled(false);
+}
+
+void Floater::setSpeed(int news)
+{
+ if (!wall || news < 0)
+ return;
+ speed = news;
+
+ if (news == 0)
+ {
+ setVelocity(0, 0);
+ return;
+ }
+
+ const double factor = (double)speed / 3.5;
+ setVelocity(-cos(vector.direction()) * factor, -sin(vector.direction()) * factor);
+}
+
+void Floater::aboutToSave()
+{
+ setVelocity(0, 0);
+ noUpdateZ = true;
+ move(wall->endPoint().x() + wall->x(), wall->endPoint().y() + wall->y());
+ noUpdateZ = false;
+}
+
+void Floater::savingDone()
+{
+ setSpeed(speed);
+}
+
+void Floater::moveBy(double dx, double dy)
+{
+ if (!isEnabled())
+ return;
+
+ QCanvasItemList l = collisions(false);
+ for (QCanvasItemList::Iterator it = l.begin(); it != l.end(); ++it)
+ {
+ CanvasItem *item = dynamic_cast<CanvasItem *>(*it);
+
+ if (!noUpdateZ && item && item->canBeMovedByOthers())
+ item->updateZ(this);
+
+ if ((*it)->z() >= z())
+ {
+ if (item && item->canBeMovedByOthers() && collidesWith(*it))
+ {
+ if ((*it)->rtti() == Rtti_Ball)
+ {
+ //((Ball *)(*it))->setState(Rolling);
+ (*it)->moveBy(dx, dy);
+ if (game && game->hasFocus() && !game->isEditing() && game->curBall() == (Ball *)(*it))
+ game->ballMoved();
+ }
+ else if ((*it)->rtti() != Rtti_Putter)
+ (*it)->moveBy(dx, dy);
+ }
+ }
+ }
+
+ point->dontMove();
+ point->move(x() + width(), y() + height());
+
+ // this call must come after we have tested for collisions, otherwise we skip them when saving!
+ // that's a bad thing
+ QCanvasRectangle::moveBy(dx, dy);
+
+ // because we don't do Bridge::moveBy();
+ topWall->move(x(), y());
+ botWall->move(x(), y() - 1);
+ leftWall->move(x(), y());
+ rightWall->move(x(), y());
+
+ if (game && game->isEditing())
+ game->updateHighlighter();
+}
+
+void Floater::saveState(StateDB *db)
+{
+ db->setPoint(QPoint(x(), y()));
+}
+
+void Floater::loadState(StateDB *db)
+{
+ const QPoint moveTo = db->point();
+ move(moveTo.x(), moveTo.y());
+}
+
+void Floater::save(KConfig *cfg)
+{
+ cfg->writeEntry("speed", speed);
+ cfg->writeEntry("startPoint", QPoint(wall->startPoint().x() + wall->x(), wall->startPoint().y() + wall->y()));
+ cfg->writeEntry("endPoint", QPoint(wall->endPoint().x() + wall->x(), wall->endPoint().y() + wall->y()));
+
+ doSave(cfg);
+}
+
+void Floater::load(KConfig *cfg)
+{
+ move(firstPoint.x(), firstPoint.y());
+
+ QPoint start(wall->startPoint() + QPoint(wall->x(), wall->y()));
+ start = cfg->readPointEntry("startPoint", &start);
+ QPoint end(wall->endPoint() + QPoint(wall->x(), wall->y()));
+ end = cfg->readPointEntry("endPoint", &end);
+ wall->setPoints(start.x(), start.y(), end.x(), end.y());
+ wall->move(0, 0);
+
+ setSpeed(cfg->readNumEntry("speed", -1));
+
+ doLoad(cfg);
+ reset();
+}
+
+void Floater::firstMove(int x, int y)
+{
+ firstPoint = QPoint(x, y);
+}
+
+/////////////////////////
+
+FloaterConfig::FloaterConfig(Floater *floater, QWidget *parent)
+ : BridgeConfig(floater, parent)
+{
+ this->floater = floater;
+ m_vlayout->addStretch();
+
+ m_vlayout->addWidget(new QLabel(i18n("Moving speed"), this));
+ QHBoxLayout *hlayout = new QHBoxLayout(m_vlayout, spacingHint());
+ hlayout->addWidget(new QLabel(i18n("Slow"), this));
+ QSlider *slider = new QSlider(0, 20, 2, floater->curSpeed(), Qt::Horizontal, this);
+ hlayout->addWidget(slider);
+ hlayout->addWidget(new QLabel(i18n("Fast"), this));
+ connect(slider, SIGNAL(valueChanged(int)), this, SLOT(speedChanged(int)));
+}
+
+void FloaterConfig::speedChanged(int news)
+{
+ floater->setSpeed(news);
+ changed();
+}
+
+#include "floater.moc"
diff --git a/kolf/floater.h b/kolf/floater.h
new file mode 100644
index 00000000..486f3bc0
--- /dev/null
+++ b/kolf/floater.h
@@ -0,0 +1,81 @@
+#ifndef FLOATER_H
+#define FLOATER_H
+
+#include "game.h"
+
+class Floater;
+class FloaterConfig : public BridgeConfig
+{
+ Q_OBJECT
+
+public:
+ FloaterConfig(Floater *floater, QWidget *parent);
+
+private slots:
+ void speedChanged(int news);
+
+private:
+ Floater *floater;
+};
+
+class FloaterGuide : public Wall
+{
+public:
+ FloaterGuide(Floater *floater, QCanvas *canvas) : Wall(canvas) { this->floater = floater; almostDead = false; }
+ virtual void setPoints(int xa, int ya, int xb, int yb);
+ virtual void moveBy(double dx, double dy);
+ virtual Config *config(QWidget *parent);
+ virtual void aboutToDelete();
+ virtual void aboutToDie();
+
+private:
+ Floater *floater;
+ bool almostDead;
+};
+
+class Floater : public Bridge
+{
+public:
+ Floater(QRect rect, QCanvas *canvas);
+ virtual bool collision(Ball *ball, long int id) { Bridge::collision(ball, id); return false; }
+ virtual void saveState(StateDB *db);
+ virtual void loadState(StateDB *db);
+ virtual void save(KConfig *cfg);
+ virtual void load(KConfig *cfg);
+ virtual bool loadLast() const { return true; }
+ virtual void firstMove(int x, int y);
+ virtual void aboutToSave();
+ virtual void aboutToDie();
+ virtual void savingDone();
+ virtual void setGame(KolfGame *game);
+ virtual void editModeChanged(bool changed);
+ virtual bool moveable() const { return false; }
+ virtual void moveBy(double dx, double dy);
+ virtual Config *config(QWidget *parent) { return new FloaterConfig(this, parent); }
+ virtual QPtrList<QCanvasItem> moveableItems() const;
+ virtual void advance(int phase);
+ void setSpeed(int news);
+ int curSpeed() const { return speed; }
+
+ // called by floaterguide when changed;
+ void reset();
+
+private:
+ int speedfactor;
+ int speed;
+ FloaterGuide *wall;
+ QPoint origin;
+ Vector vector;
+ bool noUpdateZ;
+ bool haventMoved;
+ QPoint firstPoint;
+};
+
+class FloaterObj : public Object
+{
+public:
+ FloaterObj() { m_name = i18n("Floater"); m__name = "floater"; }
+ virtual QCanvasItem *newObject(QCanvas *canvas) { return new Floater(QRect(0, 0, 80, 40), canvas); }
+};
+
+#endif
diff --git a/kolf/game.cpp b/kolf/game.cpp
new file mode 100644
index 00000000..55b324ce
--- /dev/null
+++ b/kolf/game.cpp
@@ -0,0 +1,4302 @@
+#include <arts/kmedia2.h>
+#include <arts/kplayobject.h>
+#include <arts/kplayobjectfactory.h>
+
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kcursor.h>
+#include <kdebug.h>
+#include <knuminput.h>
+#include <kfiledialog.h>
+#include <kglobal.h>
+#include <klineedit.h>
+#include <kmessagebox.h>
+#include <kpixmapeffect.h>
+#include <kprinter.h>
+#include <kstandarddirs.h>
+
+#include <qbrush.h>
+#include <qcanvas.h>
+#include <qcheckbox.h>
+#include <qcolor.h>
+#include <qcursor.h>
+#include <qevent.h>
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qimage.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qmap.h>
+#include <qpainter.h>
+#include <qpaintdevicemetrics.h>
+#include <qpen.h>
+#include <qpixmap.h>
+#include <qpixmapcache.h>
+#include <qpoint.h>
+#include <qpointarray.h>
+#include <qrect.h>
+#include <qsimplerichtext.h>
+#include <qsize.h>
+#include <qslider.h>
+#include <qspinbox.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qtimer.h>
+#include <qtooltip.h>
+#include <qvaluelist.h>
+#include <qwhatsthis.h>
+
+#include <math.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "kcomboboxdialog.h"
+#include "kvolumecontrol.h"
+#include "vector.h"
+#include "game.h"
+
+
+inline QString makeGroup(int id, int hole, QString name, int x, int y)
+{
+ return QString("%1-%2@%3,%4|%5").arg(hole).arg(name).arg(x).arg(y).arg(id);
+}
+
+inline QString makeStateGroup(int id, const QString &name)
+{
+ return QString("%1|%2").arg(name).arg(id);
+}
+
+/////////////////////////
+
+RectPoint::RectPoint(QColor color, RectItem *rect, QCanvas *canvas)
+ : QCanvasEllipse(canvas)
+{
+ setZ(9999);
+ setSize(10, 10);
+ this->rect = rect;
+ setBrush(QBrush(color));
+ setSizeFactor(1.0);
+ dontmove = false;
+}
+
+void RectPoint::moveBy(double dx, double dy)
+{
+ QCanvasEllipse::moveBy(dx, dy);
+
+ if (dontmove)
+ {
+ dontmove = false;
+ return;
+ }
+
+ QCanvasItem *qitem = dynamic_cast<QCanvasItem *>(rect);
+ if (!qitem)
+ return;
+
+ double nw = m_sizeFactor * fabs(x() - qitem->x());
+ double nh = m_sizeFactor * fabs(y() - qitem->y());
+ if (nw <= 0 || nh <= 0)
+ return;
+
+ rect->newSize(nw, nh);
+}
+
+Config *RectPoint::config(QWidget *parent)
+{
+ CanvasItem *citem = dynamic_cast<CanvasItem *>(rect);
+ if (citem)
+ return citem->config(parent);
+ else
+ return CanvasItem::config(parent);
+}
+
+/////////////////////////
+
+Arrow::Arrow(QCanvas *canvas)
+ : QCanvasLine(canvas)
+{
+ line1 = new QCanvasLine(canvas);
+ line2 = new QCanvasLine(canvas);
+
+ m_angle = 0;
+ m_length = 20;
+ m_reversed = false;
+
+ setPen(black);
+
+ updateSelf();
+ setVisible(false);
+}
+
+void Arrow::setPen(QPen p)
+{
+ QCanvasLine::setPen(p);
+ line1->setPen(p);
+ line2->setPen(p);
+}
+
+void Arrow::setZ(double newz)
+{
+ QCanvasLine::setZ(newz);
+ line1->setZ(newz);
+ line2->setZ(newz);
+}
+
+void Arrow::setVisible(bool yes)
+{
+ QCanvasLine::setVisible(yes);
+ line1->setVisible(yes);
+ line2->setVisible(yes);
+}
+
+void Arrow::moveBy(double dx, double dy)
+{
+ QCanvasLine::moveBy(dx, dy);
+ line1->moveBy(dx, dy);
+ line2->moveBy(dx, dy);
+}
+
+void Arrow::aboutToDie()
+{
+ delete line1;
+ delete line2;
+}
+
+void Arrow::updateSelf()
+{
+ QPoint start = startPoint();
+ QPoint end(m_length * cos(m_angle), m_length * sin(m_angle));
+
+ if (m_reversed)
+ {
+ QPoint tmp(start);
+ start = end;
+ end = tmp;
+ }
+
+ setPoints(start.x(), start.y(), end.x(), end.y());
+
+ const double lineLen = m_length / 2;
+
+ const double angle1 = m_angle - M_PI / 2 - 1;
+ line1->move(end.x() + x(), end.y() + y());
+ start = end;
+ end = QPoint(lineLen * cos(angle1), lineLen * sin(angle1));
+ line1->setPoints(0, 0, end.x(), end.y());
+
+ const double angle2 = m_angle + M_PI / 2 + 1;
+ line2->move(start.x() + x(), start.y() + y());
+ end = QPoint(lineLen * cos(angle2), lineLen * sin(angle2));
+ line2->setPoints(0, 0, end.x(), end.y());
+}
+
+/////////////////////////
+
+BridgeConfig::BridgeConfig(Bridge *bridge, QWidget *parent)
+ : Config(parent)
+{
+ this->bridge = bridge;
+
+ m_vlayout = new QVBoxLayout(this, marginHint(), spacingHint());
+ QGridLayout *layout = new QGridLayout(m_vlayout, 2, 3, spacingHint());
+ layout->addWidget(new QLabel(i18n("Walls on:"), this), 0, 0);
+ top = new QCheckBox(i18n("&Top"), this);
+ layout->addWidget(top, 0, 1);
+ connect(top, SIGNAL(toggled(bool)), this, SLOT(topWallChanged(bool)));
+ top->setChecked(bridge->topWallVisible());
+ bot = new QCheckBox(i18n("&Bottom"), this);
+ layout->addWidget(bot, 1, 1);
+ connect(bot, SIGNAL(toggled(bool)), this, SLOT(botWallChanged(bool)));
+ bot->setChecked(bridge->botWallVisible());
+ left = new QCheckBox(i18n("&Left"), this);
+ layout->addWidget(left, 1, 0);
+ connect(left, SIGNAL(toggled(bool)), this, SLOT(leftWallChanged(bool)));
+ left->setChecked(bridge->leftWallVisible());
+ right = new QCheckBox(i18n("&Right"), this);
+ layout->addWidget(right, 1, 2);
+ connect(right, SIGNAL(toggled(bool)), this, SLOT(rightWallChanged(bool)));
+ right->setChecked(bridge->rightWallVisible());
+}
+
+void BridgeConfig::topWallChanged(bool yes)
+{
+ bridge->setTopWallVisible(yes);
+ changed();
+}
+
+void BridgeConfig::botWallChanged(bool yes)
+{
+ bridge->setBotWallVisible(yes);
+ changed();
+}
+
+void BridgeConfig::leftWallChanged(bool yes)
+{
+ bridge->setLeftWallVisible(yes);
+ changed();
+}
+
+void BridgeConfig::rightWallChanged(bool yes)
+{
+ bridge->setRightWallVisible(yes);
+ changed();
+}
+
+/////////////////////////
+
+Bridge::Bridge(QRect rect, QCanvas *canvas)
+ : QCanvasRectangle(rect, canvas)
+{
+ QColor color("#92772D");
+ setBrush(QBrush(color));
+ setPen(NoPen);
+ setZ(998);
+
+ topWall = new Wall(canvas);
+ topWall->setAlwaysShow(true);
+ botWall = new Wall(canvas);
+ botWall->setAlwaysShow(true);
+ leftWall = new Wall(canvas);
+ leftWall->setAlwaysShow(true);
+ rightWall = new Wall(canvas);
+ rightWall->setAlwaysShow(true);
+
+ setWallZ(z() + 0.01);
+ setWallColor(color);
+
+ topWall->setVisible(false);
+ botWall->setVisible(false);
+ leftWall->setVisible(false);
+ rightWall->setVisible(false);
+
+ point = new RectPoint(color, this, canvas);
+ editModeChanged(false);
+
+ newSize(width(), height());
+}
+
+bool Bridge::collision(Ball *ball, long int /*id*/)
+{
+ ball->setFrictionMultiplier(.63);
+ return false;
+}
+
+void Bridge::setWallZ(double newz)
+{
+ topWall->setZ(newz);
+ botWall->setZ(newz);
+ leftWall->setZ(newz);
+ rightWall->setZ(newz);
+}
+
+void Bridge::setGame(KolfGame *game)
+{
+ CanvasItem::setGame(game);
+ topWall->setGame(game);
+ botWall->setGame(game);
+ leftWall->setGame(game);
+ rightWall->setGame(game);
+}
+
+void Bridge::setWallColor(QColor color)
+{
+ topWall->setPen(QPen(color.dark(), 3));
+ botWall->setPen(topWall->pen());
+ leftWall->setPen(topWall->pen());
+ rightWall->setPen(topWall->pen());
+}
+
+void Bridge::aboutToDie()
+{
+ delete point;
+ topWall->aboutToDie();
+ delete topWall;
+ botWall->aboutToDie();
+ delete botWall;
+ leftWall->aboutToDie();
+ delete leftWall;
+ rightWall->aboutToDie();
+ delete rightWall;
+}
+
+void Bridge::editModeChanged(bool changed)
+{
+ point->setVisible(changed);
+ moveBy(0, 0);
+}
+
+void Bridge::moveBy(double dx, double dy)
+{
+ QCanvasRectangle::moveBy(dx, dy);
+
+ point->dontMove();
+ point->move(x() + width(), y() + height());
+
+ topWall->move(x(), y());
+ botWall->move(x(), y() - 1);
+ leftWall->move(x(), y());
+ rightWall->move(x(), y());
+
+ QCanvasItemList list = collisions(true);
+ for (QCanvasItemList::Iterator it = list.begin(); it != list.end(); ++it)
+ {
+ CanvasItem *citem = dynamic_cast<CanvasItem *>(*it);
+ if (citem)
+ citem->updateZ();
+ }
+}
+
+void Bridge::load(KConfig *cfg)
+{
+ doLoad(cfg);
+}
+
+void Bridge::doLoad(KConfig *cfg)
+{
+ newSize(cfg->readNumEntry("width", width()), cfg->readNumEntry("height", height()));
+ setTopWallVisible(cfg->readBoolEntry("topWallVisible", topWallVisible()));
+ setBotWallVisible(cfg->readBoolEntry("botWallVisible", botWallVisible()));
+ setLeftWallVisible(cfg->readBoolEntry("leftWallVisible", leftWallVisible()));
+ setRightWallVisible(cfg->readBoolEntry("rightWallVisible", rightWallVisible()));
+}
+
+void Bridge::save(KConfig *cfg)
+{
+ doSave(cfg);
+}
+
+void Bridge::doSave(KConfig *cfg)
+{
+ cfg->writeEntry("width", width());
+ cfg->writeEntry("height", height());
+ cfg->writeEntry("topWallVisible", topWallVisible());
+ cfg->writeEntry("botWallVisible", botWallVisible());
+ cfg->writeEntry("leftWallVisible", leftWallVisible());
+ cfg->writeEntry("rightWallVisible", rightWallVisible());
+}
+
+QPtrList<QCanvasItem> Bridge::moveableItems() const
+{
+ QPtrList<QCanvasItem> ret;
+ ret.append(point);
+ return ret;
+}
+
+void Bridge::newSize(int width, int height)
+{
+ setSize(width, height);
+}
+
+void Bridge::setSize(int width, int height)
+{
+ QCanvasRectangle::setSize(width, height);
+
+ topWall->setPoints(0, 0, width, 0);
+ botWall->setPoints(0, height, width, height);
+ leftWall->setPoints(0, 0, 0, height);
+ rightWall->setPoints(width, 0, width, height);
+
+ moveBy(0, 0);
+}
+
+/////////////////////////
+
+WindmillConfig::WindmillConfig(Windmill *windmill, QWidget *parent)
+ : BridgeConfig(windmill, parent)
+{
+ this->windmill = windmill;
+ m_vlayout->addStretch();
+
+ QCheckBox *check = new QCheckBox(i18n("Windmill on bottom"), this);
+ check->setChecked(windmill->bottom());
+ connect(check, SIGNAL(toggled(bool)), this, SLOT(endChanged(bool)));
+ m_vlayout->addWidget(check);
+
+ QHBoxLayout *hlayout = new QHBoxLayout(m_vlayout, spacingHint());
+ hlayout->addWidget(new QLabel(i18n("Slow"), this));
+ QSlider *slider = new QSlider(1, 10, 1, windmill->curSpeed(), Qt::Horizontal, this);
+ hlayout->addWidget(slider);
+ hlayout->addWidget(new QLabel(i18n("Fast"), this));
+ connect(slider, SIGNAL(valueChanged(int)), this, SLOT(speedChanged(int)));
+
+ endChanged(check->isChecked());
+}
+
+void WindmillConfig::speedChanged(int news)
+{
+ windmill->setSpeed(news);
+ changed();
+}
+
+void WindmillConfig::endChanged(bool bottom)
+{
+ windmill->setBottom(bottom);
+ changed();
+
+ bot->setEnabled(!bottom);
+ if (startedUp)
+ {
+ bot->setChecked(!bottom);
+ botWallChanged(bot->isChecked());
+ }
+ top->setEnabled(bottom);
+ if (startedUp)
+ {
+ top->setChecked(bottom);
+ topWallChanged(top->isChecked());
+ }
+}
+
+/////////////////////////
+
+Windmill::Windmill(QRect rect, QCanvas *canvas)
+ : Bridge(rect, canvas), speedfactor(16), m_bottom(true)
+{
+ guard = new WindmillGuard(canvas);
+ guard->setPen(QPen(black, 5));
+ guard->setVisible(true);
+ guard->setAlwaysShow(true);
+ setSpeed(5);
+ guard->setZ(wallZ() + .1);
+
+ left = new Wall(canvas);
+ left->setPen(wallPen());
+ left->setAlwaysShow(true);
+ right = new Wall(canvas);
+ right->setPen(wallPen());
+ right->setAlwaysShow(true);
+ left->setZ(wallZ());
+ right->setZ(wallZ());
+ left->setVisible(true);
+ right->setVisible(true);
+
+ setTopWallVisible(false);
+ setBotWallVisible(false);
+ setLeftWallVisible(true);
+ setRightWallVisible(true);
+
+ newSize(width(), height());
+ moveBy(0, 0);
+}
+
+void Windmill::aboutToDie()
+{
+ Bridge::aboutToDie();
+ guard->aboutToDie();
+ delete guard;
+ left->aboutToDie();
+ delete left;
+ right->aboutToDie();
+ delete right;
+}
+
+void Windmill::setSpeed(int news)
+{
+ if (news < 0)
+ return;
+ speed = news;
+ guard->setXVelocity(((double)news / (double)3) * (guard->xVelocity() > 0? 1 : -1));
+}
+
+void Windmill::setGame(KolfGame *game)
+{
+ Bridge::setGame(game);
+ guard->setGame(game);
+ left->setGame(game);
+ right->setGame(game);
+}
+
+void Windmill::save(KConfig *cfg)
+{
+ cfg->writeEntry("speed", speed);
+ cfg->writeEntry("bottom", m_bottom);
+
+ doSave(cfg);
+}
+
+void Windmill::load(KConfig *cfg)
+{
+ setSpeed(cfg->readNumEntry("speed", -1));
+
+ doLoad(cfg);
+
+ left->editModeChanged(false);
+ right->editModeChanged(false);
+ guard->editModeChanged(false);
+
+ setBottom(cfg->readBoolEntry("bottom", true));
+}
+
+void Windmill::moveBy(double dx, double dy)
+{
+ Bridge::moveBy(dx, dy);
+
+ left->move(x(), y());
+ right->move(x(), y());
+
+ guard->moveBy(dx, dy);
+ guard->setBetween(x(), x() + width());
+
+ update();
+}
+
+void Windmill::setSize(int width, int height)
+{
+ newSize(width, height);
+}
+
+void Windmill::setBottom(bool yes)
+{
+ m_bottom = yes;
+ newSize(width(), height());
+}
+
+void Windmill::newSize(int width, int height)
+{
+ Bridge::newSize(width, height);
+
+ const int indent = width / 4;
+
+ double indentY = m_bottom? height : 0;
+ left->setPoints(0, indentY, indent, indentY);
+ right->setPoints(width - indent, indentY, width, indentY);
+
+ guard->setBetween(x(), x() + width);
+ double guardY = m_bottom? height + 4 : -4;
+ guard->setPoints(0, guardY, (double)indent / (double)1.07 - 2, guardY);
+}
+
+/////////////////////////
+
+void WindmillGuard::advance(int phase)
+{
+ Wall::advance(phase);
+
+ if (phase == 1)
+ {
+ if (x() + startPoint().x() <= min)
+ setXVelocity(fabs(xVelocity()));
+ else if (x() + endPoint().x() >= max)
+ setXVelocity(-fabs(xVelocity()));
+ }
+}
+
+/////////////////////////
+
+Sign::Sign(QCanvas *canvas)
+ : Bridge(QRect(0, 0, 110, 40), canvas)
+{
+ setZ(998.8);
+ m_text = m_untranslatedText = i18n("New Text");
+ setBrush(QBrush(white));
+ setWallColor(black);
+ setWallZ(z() + .01);
+
+ setTopWallVisible(true);
+ setBotWallVisible(true);
+ setLeftWallVisible(true);
+ setRightWallVisible(true);
+}
+
+void Sign::load(KConfig *cfg)
+{
+ m_text = cfg->readEntry("Comment", m_text);
+ m_untranslatedText = cfg->readEntryUntranslated("Comment", m_untranslatedText);
+
+ doLoad(cfg);
+}
+
+void Sign::save(KConfig *cfg)
+{
+ cfg->writeEntry("Comment", m_untranslatedText);
+
+ doSave(cfg);
+}
+
+void Sign::setText(const QString &text)
+{
+ m_text = text;
+ m_untranslatedText = text;
+
+ update();
+}
+
+void Sign::draw(QPainter &painter)
+{
+ Bridge::draw(painter);
+
+ painter.setPen(QPen(black, 1));
+ QSimpleRichText txt(m_text, kapp->font());
+ const int indent = wallPen().width() + 3;
+ txt.setWidth(width() - 2*indent);
+ QColorGroup colorGroup;
+ colorGroup.setColor(QColorGroup::Foreground, black);
+ colorGroup.setColor(QColorGroup::Text, black);
+ colorGroup.setColor(QColorGroup::Background, black);
+ colorGroup.setColor(QColorGroup::Base, black);
+ txt.draw(&painter, x() + indent, y(), QRect(x() + indent, y(), width() - indent, height() - indent), colorGroup);
+}
+
+/////////////////////////
+
+SignConfig::SignConfig(Sign *sign, QWidget *parent)
+ : BridgeConfig(sign, parent)
+{
+ this->sign = sign;
+ m_vlayout->addStretch();
+ m_vlayout->addWidget(new QLabel(i18n("Sign HTML:"), this));
+ KLineEdit *name = new KLineEdit(sign->text(), this);
+ m_vlayout->addWidget(name);
+ connect(name, SIGNAL(textChanged(const QString &)), this, SLOT(textChanged(const QString &)));
+}
+
+void SignConfig::textChanged(const QString &text)
+{
+ sign->setText(text);
+ changed();
+}
+
+/////////////////////////
+
+EllipseConfig::EllipseConfig(Ellipse *ellipse, QWidget *parent)
+ : Config(parent), slow1(0), fast1(0), slow2(0), fast2(0), slider1(0), slider2(0)
+{
+ this->ellipse = ellipse;
+
+ m_vlayout = new QVBoxLayout(this, marginHint(), spacingHint());
+
+ QCheckBox *check = new QCheckBox(i18n("Enable show/hide"), this);
+ m_vlayout->addWidget(check);
+ connect(check, SIGNAL(toggled(bool)), this, SLOT(check1Changed(bool)));
+ check->setChecked(ellipse->changeEnabled());
+
+ QHBoxLayout *hlayout = new QHBoxLayout(m_vlayout, spacingHint());
+ slow1 = new QLabel(i18n("Slow"), this);
+ hlayout->addWidget(slow1);
+ slider1 = new QSlider(1, 100, 5, 100 - ellipse->changeEvery(), Qt::Horizontal, this);
+ hlayout->addWidget(slider1);
+ fast1 = new QLabel(i18n("Fast"), this);
+ hlayout->addWidget(fast1);
+
+ connect(slider1, SIGNAL(valueChanged(int)), this, SLOT(value1Changed(int)));
+
+ check1Changed(ellipse->changeEnabled());
+
+ // TODO add slider2 and friends and make it possible for ellipses to grow and contract
+
+ m_vlayout->addStretch();
+}
+
+void EllipseConfig::value1Changed(int news)
+{
+ ellipse->setChangeEvery(100 - news);
+ changed();
+}
+
+void EllipseConfig::value2Changed(int /*news*/)
+{
+ changed();
+}
+
+void EllipseConfig::check1Changed(bool on)
+{
+ ellipse->setChangeEnabled(on);
+ if (slider1)
+ slider1->setEnabled(on);
+ if (slow1)
+ slow1->setEnabled(on);
+ if (fast1)
+ fast1->setEnabled(on);
+
+ changed();
+}
+
+void EllipseConfig::check2Changed(bool on)
+{
+ //ellipse->setChangeEnabled(on);
+ if (slider2)
+ slider2->setEnabled(on);
+ if (slow2)
+ slow2->setEnabled(on);
+ if (fast2)
+ fast2->setEnabled(on);
+
+ changed();
+}
+
+/////////////////////////
+
+Ellipse::Ellipse(QCanvas *canvas)
+ : QCanvasEllipse(canvas)
+{
+ savingDone();
+ setChangeEnabled(false);
+ setChangeEvery(50);
+ count = 0;
+ setVisible(true);
+
+ point = new RectPoint(black, this, canvas);
+ point->setSizeFactor(2.0);
+}
+
+void Ellipse::aboutToDie()
+{
+ delete point;
+}
+
+void Ellipse::setChangeEnabled(bool changeEnabled)
+{
+ m_changeEnabled = changeEnabled;
+ setAnimated(m_changeEnabled);
+
+ if (!m_changeEnabled)
+ setVisible(true);
+}
+
+QPtrList<QCanvasItem> Ellipse::moveableItems() const
+{
+ QPtrList<QCanvasItem> ret;
+ ret.append(point);
+ return ret;
+}
+
+void Ellipse::newSize(int width, int height)
+{
+ QCanvasEllipse::setSize(width, height);
+}
+
+void Ellipse::moveBy(double dx, double dy)
+{
+ QCanvasEllipse::moveBy(dx, dy);
+
+ point->dontMove();
+ point->move(x() + width() / 2, y() + height() / 2);
+}
+
+void Ellipse::editModeChanged(bool changed)
+{
+ point->setVisible(changed);
+ moveBy(0, 0);
+}
+
+void Ellipse::advance(int phase)
+{
+ QCanvasEllipse::advance(phase);
+
+ if (phase == 1 && m_changeEnabled && !dontHide)
+ {
+ if (count > (m_changeEvery + 10) * 1.8)
+ count = 0;
+ if (count == 0)
+ setVisible(!isVisible());
+
+ count++;
+ }
+}
+
+void Ellipse::load(KConfig *cfg)
+{
+ setChangeEnabled(cfg->readBoolEntry("changeEnabled", changeEnabled()));
+ setChangeEvery(cfg->readNumEntry("changeEvery", changeEvery()));
+ double newWidth = width(), newHeight = height();
+ newWidth = cfg->readNumEntry("width", newWidth);
+ newHeight = cfg->readNumEntry("height", newHeight);
+ newSize(newWidth, newHeight);
+}
+
+void Ellipse::save(KConfig *cfg)
+{
+ cfg->writeEntry("changeEvery", changeEvery());
+ cfg->writeEntry("changeEnabled", changeEnabled());
+ cfg->writeEntry("width", width());
+ cfg->writeEntry("height", height());
+}
+
+Config *Ellipse::config(QWidget *parent)
+{
+ return new EllipseConfig(this, parent);
+}
+
+void Ellipse::aboutToSave()
+{
+ setVisible(true);
+ dontHide = true;
+}
+
+void Ellipse::savingDone()
+{
+ dontHide = false;
+}
+
+/////////////////////////
+
+Puddle::Puddle(QCanvas *canvas)
+ : Ellipse(canvas)
+{
+ setSize(45, 30);
+
+ QBrush brush;
+ QPixmap pic;
+
+ if (!QPixmapCache::find("puddle", pic))
+ {
+ pic.load(locate("appdata", "pics/puddle.png"));
+ QPixmapCache::insert("puddle", pic);
+ }
+
+ brush.setPixmap(pic);
+ setBrush(brush);
+
+ KPixmap pointPic(pic);
+ KPixmapEffect::intensity(pointPic, .45);
+ brush.setPixmap(pointPic);
+ point->setBrush(brush);
+
+ setZ(-25);
+}
+
+bool Puddle::collision(Ball *ball, long int /*id*/)
+{
+ if (ball->isVisible())
+ {
+ QCanvasRectangle i(QRect(ball->x(), ball->y(), 1, 1), canvas());
+ i.setVisible(true);
+
+ // is center of ball in?
+ if (i.collidesWith(this)/* && ball->curVector().magnitude() < 4*/)
+ {
+ playSound("puddle");
+ ball->setAddStroke(ball->addStroke() + 1);
+ ball->setPlaceOnGround(true);
+ ball->setVisible(false);
+ ball->setState(Stopped);
+ ball->setVelocity(0, 0);
+ if (game && game->curBall() == ball)
+ game->stoppedBall();
+ }
+ else
+ return true;
+ }
+
+ return false;
+}
+
+/////////////////////////
+
+Sand::Sand(QCanvas *canvas)
+ : Ellipse(canvas)
+{
+ setSize(45, 40);
+
+ QBrush brush;
+ QPixmap pic;
+
+ if (!QPixmapCache::find("sand", pic))
+ {
+ pic.load(locate("appdata", "pics/sand.png"));
+ QPixmapCache::insert("sand", pic);
+ }
+
+ brush.setPixmap(pic);
+ setBrush(brush);
+
+ KPixmap pointPic(pic);
+ KPixmapEffect::intensity(pointPic, .45);
+ brush.setPixmap(pointPic);
+ point->setBrush(brush);
+
+ setZ(-26);
+}
+
+bool Sand::collision(Ball *ball, long int /*id*/)
+{
+ QCanvasRectangle i(QRect(ball->x(), ball->y(), 1, 1), canvas());
+ i.setVisible(true);
+
+ // is center of ball in?
+ if (i.collidesWith(this)/* && ball->curVector().magnitude() < 4*/)
+ {
+ if (ball->curVector().magnitude() > 0)
+ ball->setFrictionMultiplier(7);
+ else
+ {
+ ball->setVelocity(0, 0);
+ ball->setState(Stopped);
+ }
+ }
+
+ return true;
+}
+
+/////////////////////////
+
+Putter::Putter(QCanvas *canvas)
+ : QCanvasLine(canvas)
+{
+ m_showGuideLine = true;
+ oneDegree = M_PI / 180;
+ len = 9;
+ angle = 0;
+
+ guideLine = new QCanvasLine(canvas);
+ guideLine->setPen(QPen(white, 1, QPen::DotLine));
+ guideLine->setZ(998.8);
+
+ setPen(QPen(black, 4));
+ putterWidth = 11;
+ maxAngle = 2 * M_PI;
+
+ hideInfo();
+
+ // this also sets Z
+ resetAngles();
+}
+
+void Putter::showInfo()
+{
+ guideLine->setVisible(isVisible());
+}
+
+void Putter::hideInfo()
+{
+ guideLine->setVisible(m_showGuideLine? isVisible() : false);
+}
+
+void Putter::moveBy(double dx, double dy)
+{
+ QCanvasLine::moveBy(dx, dy);
+ guideLine->move(x(), y());
+}
+
+void Putter::setShowGuideLine(bool yes)
+{
+ m_showGuideLine = yes;
+ setVisible(isVisible());
+}
+
+void Putter::setVisible(bool yes)
+{
+ QCanvasLine::setVisible(yes);
+ guideLine->setVisible(m_showGuideLine? yes : false);
+}
+
+void Putter::setOrigin(int _x, int _y)
+{
+ setVisible(true);
+ move(_x, _y);
+ len = 9;
+ finishMe();
+}
+
+void Putter::setAngle(Ball *ball)
+{
+ angle = angleMap.contains(ball)? angleMap[ball] : 0;
+ finishMe();
+}
+
+void Putter::go(Direction d, Amount amount)
+{
+ double addition = (amount == Amount_More? 6 * oneDegree : amount == Amount_Less? .5 * oneDegree : 2 * oneDegree);
+
+ switch (d)
+ {
+ case Forwards:
+ len -= 1;
+ guideLine->setVisible(false);
+ break;
+ case Backwards:
+ len += 1;
+ guideLine->setVisible(false);
+ break;
+ case D_Left:
+ angle += addition;
+ if (angle > maxAngle)
+ angle -= maxAngle;
+ break;
+ case D_Right:
+ angle -= addition;
+ if (angle < 0)
+ angle = maxAngle - fabs(angle);
+ break;
+ }
+
+ finishMe();
+}
+
+void Putter::finishMe()
+{
+ midPoint.setX(cos(angle) * len);
+ midPoint.setY(-sin(angle) * len);
+
+ QPoint start;
+ QPoint end;
+
+ if (midPoint.y() || !midPoint.x())
+ {
+ start.setX(midPoint.x() - putterWidth * sin(angle));
+ start.setY(midPoint.y() - putterWidth * cos(angle));
+ end.setX(midPoint.x() + putterWidth * sin(angle));
+ end.setY(midPoint.y() + putterWidth * cos(angle));
+ }
+ else
+ {
+ start.setX(midPoint.x());
+ start.setY(midPoint.y() + putterWidth);
+ end.setY(midPoint.y() - putterWidth);
+ end.setX(midPoint.x());
+ }
+
+ guideLine->setPoints(midPoint.x(), midPoint.y(), -cos(angle) * len * 4, sin(angle) * len * 4);
+
+ setPoints(start.x(), start.y(), end.x(), end.y());
+}
+
+/////////////////////////
+
+Bumper::Bumper(QCanvas *canvas)
+ : QCanvasEllipse(20, 20, canvas)
+{
+ setZ(-25);
+
+ firstColor = QColor("#E74804");
+ secondColor = firstColor.light();
+
+ count = 0;
+ setBrush(firstColor);
+ setAnimated(false);
+
+ inside = new Inside(this, canvas);
+ inside->setBrush(firstColor.light(109));
+ inside->setSize(width() / 2.6, height() / 2.6);
+ inside->show();
+}
+
+void Bumper::aboutToDie()
+{
+ delete inside;
+}
+
+void Bumper::moveBy(double dx, double dy)
+{
+ QCanvasEllipse::moveBy(dx, dy);
+ //const double insideLen = (double)(width() - inside->width()) / 2.0;
+ inside->move(x(), y());
+}
+
+void Bumper::editModeChanged(bool changed)
+{
+ inside->setVisible(!changed);
+}
+
+void Bumper::advance(int phase)
+{
+ QCanvasEllipse::advance(phase);
+
+ if (phase == 1)
+ {
+ count++;
+ if (count > 2)
+ {
+ count = 0;
+ setBrush(firstColor);
+ update();
+ setAnimated(false);
+ }
+ }
+}
+
+bool Bumper::collision(Ball *ball, long int /*id*/)
+{
+ setBrush(secondColor);
+
+ double speed = 1.8 + ball->curVector().magnitude() * .9;
+ if (speed > 8)
+ speed = 8;
+
+ const QPoint start(x(), y());
+ const QPoint end(ball->x(), ball->y());
+
+ Vector betweenVector(start, end);
+ betweenVector.setMagnitude(speed);
+
+ // add some randomness so we don't go indefinetely
+ betweenVector.setDirection(betweenVector.direction() + deg2rad((kapp->random() % 3) - 1));
+
+ ball->setVector(betweenVector);
+ // for some reason, x is always switched...
+ ball->setXVelocity(-ball->xVelocity());
+ ball->setState(Rolling);
+
+ setAnimated(true);
+
+ return true;
+}
+
+/////////////////////////
+
+Hole::Hole(QColor color, QCanvas *canvas)
+ : QCanvasEllipse(15, 15, canvas)
+{
+ setZ(998.1);
+ setPen(black);
+ setBrush(color);
+}
+
+bool Hole::collision(Ball *ball, long int /*id*/)
+{
+ bool wasCenter = false;
+
+ switch (result(QPoint(ball->x(), ball->y()), ball->curVector().magnitude(), &wasCenter))
+ {
+ case Result_Holed:
+ place(ball, wasCenter);
+ return false;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+HoleResult Hole::result(QPoint p, double s, bool * /*wasCenter*/)
+{
+ const double longestRadius = width() > height()? width() : height();
+ if (s > longestRadius / 5.0)
+ return Result_Miss;
+
+ QCanvasRectangle i(QRect(p, QSize(1, 1)), canvas());
+ i.setVisible(true);
+
+ // is center of ball in cup?
+ if (i.collidesWith(this))
+ {
+ return Result_Holed;
+ }
+ else
+ return Result_Miss;
+}
+
+/////////////////////////
+
+Cup::Cup(QCanvas *canvas)
+ : Hole(QColor("#808080"), canvas)
+{
+ if (!QPixmapCache::find("cup", pixmap))
+ {
+ pixmap.load(locate("appdata", "pics/cup.png"));
+ QPixmapCache::insert("cup", pixmap);
+ }
+}
+
+void Cup::draw(QPainter &p)
+{
+ p.drawPixmap(QPoint(x() - width() / 2, y() - height() / 2), pixmap);
+}
+
+bool Cup::place(Ball *ball, bool /*wasCenter*/)
+{
+ ball->setState(Holed);
+ playSound("holed");
+
+ // the picture's center is a little different
+ ball->move(x() - 1, y());
+ ball->setVelocity(0, 0);
+ if (game && game->curBall() == ball)
+ game->stoppedBall();
+ return true;
+}
+
+void Cup::save(KConfig *cfg)
+{
+ cfg->writeEntry("dummykey", true);
+}
+
+/////////////////////////
+
+BlackHole::BlackHole(QCanvas *canvas)
+ : Hole(black, canvas), exitDeg(0)
+{
+ infoLine = 0;
+ m_minSpeed = 3.0;
+ m_maxSpeed = 5.0;
+ runs = 0;
+
+ const QColor myColor((QRgb)(kapp->random() % 0x01000000));
+
+ outside = new QCanvasEllipse(canvas);
+ outside->setZ(z() - .001);
+
+ outside->setBrush(QBrush(myColor));
+ setBrush(black);
+
+ exitItem = new BlackHoleExit(this, canvas);
+ exitItem->setPen(QPen(myColor, 6));
+ exitItem->setX(300);
+ exitItem->setY(100);
+
+ setSize(width(), width() / .8);
+ const float factor = 1.3;
+ outside->setSize(width() * factor, height() * factor);
+ outside->setVisible(true);
+
+ moveBy(0, 0);
+
+ finishMe();
+}
+
+void BlackHole::showInfo()
+{
+ delete infoLine;
+ infoLine = new QCanvasLine(canvas());
+ infoLine->setVisible(true);
+ infoLine->setPen(QPen(exitItem->pen().color(), 2));
+ infoLine->setZ(10000);
+ infoLine->setPoints(x(), y(), exitItem->x(), exitItem->y());
+
+ exitItem->showInfo();
+}
+
+void BlackHole::hideInfo()
+{
+ delete infoLine;
+ infoLine = 0;
+
+ exitItem->hideInfo();
+}
+
+void BlackHole::aboutToDie()
+{
+ Hole::aboutToDie();
+ delete outside;
+ exitItem->aboutToDie();
+ delete exitItem;
+}
+
+void BlackHole::updateInfo()
+{
+ if (infoLine)
+ {
+ infoLine->setVisible(true);
+ infoLine->setPoints(x(), y(), exitItem->x(), exitItem->y());
+ exitItem->showInfo();
+ }
+}
+
+void BlackHole::moveBy(double dx, double dy)
+{
+ QCanvasEllipse::moveBy(dx, dy);
+ outside->move(x(), y());
+ updateInfo();
+}
+
+void BlackHole::setExitDeg(int newdeg)
+{
+ exitDeg = newdeg;
+ if (game && game->isEditing() && game->curSelectedItem() == exitItem)
+ game->updateHighlighter();
+
+ exitItem->updateArrowAngle();
+ finishMe();
+}
+
+QPtrList<QCanvasItem> BlackHole::moveableItems() const
+{
+ QPtrList<QCanvasItem> ret;
+ ret.append(exitItem);
+ return ret;
+}
+
+BlackHoleTimer::BlackHoleTimer(Ball *ball, double speed, int msec)
+ : m_speed(speed), m_ball(ball)
+{
+ QTimer::singleShot(msec, this, SLOT(mySlot()));
+ QTimer::singleShot(msec / 2, this, SLOT(myMidSlot()));
+}
+
+void BlackHoleTimer::mySlot()
+{
+ emit eject(m_ball, m_speed);
+ delete this;
+}
+
+void BlackHoleTimer::myMidSlot()
+{
+ emit halfway();
+}
+
+bool BlackHole::place(Ball *ball, bool /*wasCenter*/)
+{
+ // most number is 10
+ if (runs > 10 && game && game->isInPlay())
+ return false;
+
+ playSound("blackholeputin");
+
+ const double diff = (m_maxSpeed - m_minSpeed);
+ const double speed = m_minSpeed + ball->curVector().magnitude() * (diff / 3.75);
+
+ ball->setVelocity(0, 0);
+ ball->setState(Stopped);
+ ball->setVisible(false);
+ ball->setForceStillGoing(true);
+
+ double magnitude = Vector(QPoint(x(), y()), QPoint(exitItem->x(), exitItem->y())).magnitude();
+ BlackHoleTimer *timer = new BlackHoleTimer(ball, speed, magnitude * 2.5 - speed * 35 + 500);
+
+ connect(timer, SIGNAL(eject(Ball *, double)), this, SLOT(eject(Ball *, double)));
+ connect(timer, SIGNAL(halfway()), this, SLOT(halfway()));
+
+ playSound("blackhole");
+ return false;
+}
+
+void BlackHole::eject(Ball *ball, double speed)
+{
+ ball->move(exitItem->x(), exitItem->y());
+
+ Vector v;
+ v.setMagnitude(10);
+ v.setDirection(deg2rad(exitDeg));
+ ball->setVector(v);
+
+ // advance ball 10
+ ball->doAdvance();
+
+ v.setMagnitude(speed);
+ ball->setVector(v);
+
+ ball->setForceStillGoing(false);
+ ball->setVisible(true);
+ ball->setState(Rolling);
+
+ runs++;
+
+ playSound("blackholeeject");
+}
+
+void BlackHole::halfway()
+{
+ playSound("blackhole");
+}
+
+void BlackHole::load(KConfig *cfg)
+{
+ QPoint exit = cfg->readPointEntry("exit", &exit);
+ exitItem->setX(exit.x());
+ exitItem->setY(exit.y());
+ exitDeg = cfg->readNumEntry("exitDeg", exitDeg);
+ m_minSpeed = cfg->readDoubleNumEntry("minspeed", m_minSpeed);
+ m_maxSpeed = cfg->readDoubleNumEntry("maxspeed", m_maxSpeed);
+ exitItem->updateArrowAngle();
+ exitItem->updateArrowLength();
+
+ finishMe();
+}
+
+void BlackHole::finishMe()
+{
+ double radians = deg2rad(exitDeg);
+ QPoint midPoint(0, 0);
+ QPoint start;
+ QPoint end;
+ const int width = 15;
+
+ if (midPoint.y() || !midPoint.x())
+ {
+ start.setX(midPoint.x() - width*sin(radians));
+ start.setY(midPoint.y() - width*cos(radians));
+ end.setX(midPoint.x() + width*sin(radians));
+ end.setY(midPoint.y() + width*cos(radians));
+ }
+ else
+ {
+ start.setX(midPoint.x());
+ start.setY(midPoint.y() + width);
+ end.setY(midPoint.y() - width);
+ end.setX(midPoint.x());
+ }
+
+ exitItem->setPoints(start.x(), start.y(), end.x(), end.y());
+ exitItem->setVisible(true);
+}
+
+void BlackHole::save(KConfig *cfg)
+{
+ cfg->writeEntry("exit", QPoint(exitItem->x(), exitItem->y()));
+ cfg->writeEntry("exitDeg", exitDeg);
+ cfg->writeEntry("minspeed", m_minSpeed);
+ cfg->writeEntry("maxspeed", m_maxSpeed);
+}
+
+/////////////////////////
+
+BlackHoleExit::BlackHoleExit(BlackHole *blackHole, QCanvas *canvas)
+ : QCanvasLine(canvas)
+{
+ this->blackHole = blackHole;
+ arrow = new Arrow(canvas);
+ setZ(blackHole->z());
+ arrow->setZ(z() - .00001);
+ updateArrowLength();
+ arrow->setVisible(false);
+}
+
+void BlackHoleExit::aboutToDie()
+{
+ arrow->aboutToDie();
+ delete arrow;
+}
+
+void BlackHoleExit::moveBy(double dx, double dy)
+{
+ QCanvasLine::moveBy(dx, dy);
+ arrow->move(x(), y());
+ blackHole->updateInfo();
+}
+
+void BlackHoleExit::setPen(QPen p)
+{
+ QCanvasLine::setPen(p);
+ arrow->setPen(QPen(p.color(), 1));
+}
+
+void BlackHoleExit::updateArrowAngle()
+{
+ // arrows work in a different angle system
+ arrow->setAngle(-deg2rad(blackHole->curExitDeg()));
+ arrow->updateSelf();
+}
+
+void BlackHoleExit::updateArrowLength()
+{
+ arrow->setLength(10.0 + 5.0 * (double)(blackHole->minSpeed() + blackHole->maxSpeed()) / 2.0);
+ arrow->updateSelf();
+}
+
+void BlackHoleExit::editModeChanged(bool editing)
+{
+ if (editing)
+ showInfo();
+ else
+ hideInfo();
+}
+
+void BlackHoleExit::showInfo()
+{
+ arrow->setVisible(true);
+}
+
+void BlackHoleExit::hideInfo()
+{
+ arrow->setVisible(false);
+}
+
+Config *BlackHoleExit::config(QWidget *parent)
+{
+ return blackHole->config(parent);
+}
+
+/////////////////////////
+
+BlackHoleConfig::BlackHoleConfig(BlackHole *blackHole, QWidget *parent)
+ : Config(parent)
+{
+ this->blackHole = blackHole;
+ QVBoxLayout *layout = new QVBoxLayout(this, marginHint(), spacingHint());
+ layout->addWidget(new QLabel(i18n("Exiting ball angle:"), this));
+ QSpinBox *deg = new QSpinBox(0, 359, 10, this);
+ deg->setSuffix(QString(" ") + i18n("degrees"));
+ deg->setValue(blackHole->curExitDeg());
+ deg->setWrapping(true);
+ layout->addWidget(deg);
+ connect(deg, SIGNAL(valueChanged(int)), this, SLOT(degChanged(int)));
+
+ layout->addStretch();
+
+ QHBoxLayout *hlayout = new QHBoxLayout(layout, spacingHint());
+ hlayout->addWidget(new QLabel(i18n("Minimum exit speed:"), this));
+ KDoubleNumInput *min = new KDoubleNumInput(this);
+ min->setRange(0, 8, 1, true);
+ hlayout->addWidget(min);
+ connect(min, SIGNAL(valueChanged(double)), this, SLOT(minChanged(double)));
+ min->setValue(blackHole->minSpeed());
+
+ hlayout = new QHBoxLayout(layout, spacingHint());
+ hlayout->addWidget(new QLabel(i18n("Maximum:"), this));
+ KDoubleNumInput *max = new KDoubleNumInput(this);
+ max->setRange(1, 10, 1, true);
+ hlayout->addWidget(max);
+ connect(max, SIGNAL(valueChanged(double)), this, SLOT(maxChanged(double)));
+ max->setValue(blackHole->maxSpeed());
+}
+
+void BlackHoleConfig::degChanged(int newdeg)
+{
+ blackHole->setExitDeg(newdeg);
+ changed();
+}
+
+void BlackHoleConfig::minChanged(double news)
+{
+ blackHole->setMinSpeed(news);
+ changed();
+}
+
+void BlackHoleConfig::maxChanged(double news)
+{
+ blackHole->setMaxSpeed(news);
+ changed();
+}
+
+/////////////////////////
+
+WallPoint::WallPoint(bool start, Wall *wall, QCanvas *canvas)
+ : QCanvasEllipse(canvas)
+{
+ this->wall = wall;
+ this->start = start;
+ alwaysShow = false;
+ editing = false;
+ visible = true;
+ lastId = INT_MAX - 10;
+ dontmove = false;
+
+ move(0, 0);
+ QPoint p;
+ if (start)
+ p = wall->startPoint();
+ else
+ p = wall->endPoint();
+ setX(p.x());
+ setY(p.y());
+}
+
+void WallPoint::clean()
+{
+ int oldWidth = width();
+ setSize(7, 7);
+ update();
+
+ QCanvasItem *onPoint = 0;
+ QCanvasItemList l = collisions(true);
+ for (QCanvasItemList::Iterator it = l.begin(); it != l.end(); ++it)
+ if ((*it)->rtti() == rtti())
+ onPoint = (*it);
+
+ if (onPoint)
+ move(onPoint->x(), onPoint->y());
+
+ setSize(oldWidth, oldWidth);
+}
+
+void WallPoint::moveBy(double dx, double dy)
+{
+ QCanvasEllipse::moveBy(dx, dy);
+ if (!editing)
+ updateVisible();
+
+ if (dontmove)
+ {
+ dontmove = false;
+ return;
+ }
+
+ if (!wall)
+ return;
+
+ if (start)
+ {
+ wall->setPoints(x(), y(), wall->endPoint().x() + wall->x(), wall->endPoint().y() + wall->y());
+ }
+ else
+ {
+ wall->setPoints(wall->startPoint().x() + wall->x(), wall->startPoint().y() + wall->y(), x(), y());
+ }
+ wall->move(0, 0);
+}
+
+void WallPoint::updateVisible()
+{
+ if (!wall->isVisible())
+ {
+ visible = false;
+ return;
+ }
+
+ if (alwaysShow)
+ visible = true;
+ else
+ {
+ visible = true;
+ QCanvasItemList l = collisions(true);
+ for (QCanvasItemList::Iterator it = l.begin(); it != l.end(); ++it)
+ if ((*it)->rtti() == rtti())
+ visible = false;
+ }
+}
+
+void WallPoint::editModeChanged(bool changed)
+{
+ editing = changed;
+ setVisible(true);
+ if (!editing)
+ updateVisible();
+}
+
+bool WallPoint::collision(Ball *ball, long int id)
+{
+ if (ball->curVector().magnitude() <= 0)
+ return false;
+
+ long int tempLastId = lastId;
+ lastId = id;
+ QCanvasItemList l = collisions(true);
+ for (QCanvasItemList::Iterator it = l.begin(); it != l.end(); ++it)
+ {
+ if ((*it)->rtti() == rtti())
+ {
+ WallPoint *point = (WallPoint *)(*it);
+ point->lastId = id;
+ }
+ }
+
+ //kdDebug(12007) << "WallPoint::collision id: " << id << ", tempLastId: " << tempLastId << endl;
+ Vector ballVector(ball->curVector());
+
+ //kdDebug(12007) << "Wall::collision ball speed: " << ball->curVector().magnitude() << endl;
+ int allowableDifference = 1;
+ if (ballVector.magnitude() < .30)
+ allowableDifference = 8;
+ else if (ballVector.magnitude() < .50)
+ allowableDifference = 6;
+ else if (ballVector.magnitude() < .65)
+ allowableDifference = 4;
+ else if (ballVector.magnitude() < .95)
+ allowableDifference = 2;
+
+ if (abs(id - tempLastId) <= allowableDifference)
+ {
+ //kdDebug(12007) << "WallPoint::collision - SKIP\n";
+ }
+ else
+ {
+ bool weirdBounce = visible;
+
+ QPoint relStart(start? wall->startPoint() : wall->endPoint());
+ QPoint relEnd(start? wall->endPoint() : wall->startPoint());
+ Vector wallVector(relStart, relEnd);
+ wallVector.setDirection(-wallVector.direction());
+
+ // find the angle between vectors, between 0 and PI
+ {
+ double difference = fabs(wallVector.direction() - ballVector.direction());
+ while (difference > 2 * M_PI)
+ difference -= 2 * M_PI;
+
+ if (difference < M_PI / 2 || difference > 3 * M_PI / 2)
+ weirdBounce = false;
+ }
+
+ playSound("wall", ball->curVector().magnitude() / 10.0);
+
+ ballVector /= wall->dampening;
+ const double ballAngle = ballVector.direction();
+
+ double wallAngle = wallVector.direction();
+
+ // opposite bounce, because we're the endpoint
+ if (weirdBounce)
+ wallAngle += M_PI / 2;
+
+ const double collisionAngle = ballAngle - wallAngle;
+ const double leavingAngle = wallAngle - collisionAngle;
+
+ ballVector.setDirection(leavingAngle);
+ ball->setVector(ballVector);
+ wall->lastId = id;
+
+ //kdDebug(12007) << "WallPoint::collision - NOT skip, weirdBounce is " << weirdBounce << endl;
+ } // end if that skips
+
+ wall->lastId = id;
+ return false;
+}
+
+/////////////////////////
+
+Wall::Wall(QCanvas *canvas)
+ : QCanvasLine(canvas)
+{
+ editing = false;
+ lastId = INT_MAX - 10;
+
+ dampening = 1.2;
+
+ startItem = 0;
+ endItem = 0;
+
+ moveBy(0, 0);
+ setZ(50);
+
+ startItem = new WallPoint(true, this, canvas);
+ endItem = new WallPoint(false, this, canvas);
+ startItem->setVisible(true);
+ endItem->setVisible(true);
+ setPen(QPen(darkRed, 3));
+
+ setPoints(-15, 10, 15, -5);
+
+ moveBy(0, 0);
+
+ editModeChanged(false);
+}
+
+void Wall::selectedItem(QCanvasItem *item)
+{
+ if (item->rtti() == Rtti_WallPoint)
+ {
+ WallPoint *wallPoint = dynamic_cast<WallPoint *>(item);
+ if (wallPoint) {
+ setPoints(startPoint().x(), startPoint().y(), wallPoint->x() - x(), wallPoint->y() - y());
+ }
+ }
+}
+
+void Wall::clean()
+{
+ startItem->clean();
+ endItem->clean();
+}
+
+void Wall::setAlwaysShow(bool yes)
+{
+ startItem->setAlwaysShow(yes);
+ endItem->setAlwaysShow(yes);
+}
+
+void Wall::setVisible(bool yes)
+{
+ QCanvasLine::setVisible(yes);
+
+ startItem->setVisible(yes);
+ endItem->setVisible(yes);
+ startItem->updateVisible();
+ endItem->updateVisible();
+}
+
+void Wall::setZ(double newz)
+{
+ QCanvasLine::setZ(newz);
+ if (startItem)
+ startItem->setZ(newz + .002);
+ if (endItem)
+ endItem->setZ(newz + .001);
+}
+
+void Wall::setPen(QPen p)
+{
+ QCanvasLine::setPen(p);
+
+ if (startItem)
+ startItem->setBrush(QBrush(p.color()));
+ if (endItem)
+ endItem->setBrush(QBrush(p.color()));
+}
+
+void Wall::aboutToDie()
+{
+ delete startItem;
+ delete endItem;
+}
+
+void Wall::setGame(KolfGame *game)
+{
+ CanvasItem::setGame(game);
+ startItem->setGame(game);
+ endItem->setGame(game);
+}
+
+QPtrList<QCanvasItem> Wall::moveableItems() const
+{
+ QPtrList<QCanvasItem> ret;
+ ret.append(startItem);
+ ret.append(endItem);
+ return ret;
+}
+
+void Wall::moveBy(double dx, double dy)
+{
+ QCanvasLine::moveBy(dx, dy);
+
+ if (!startItem || !endItem)
+ return;
+
+ startItem->dontMove();
+ endItem->dontMove();
+ startItem->move(startPoint().x() + x(), startPoint().y() + y());
+ endItem->move(endPoint().x() + x(), endPoint().y() + y());
+}
+
+void Wall::setVelocity(double vx, double vy)
+{
+ QCanvasLine::setVelocity(vx, vy);
+ /*
+ startItem->setVelocity(vx, vy);
+ endItem->setVelocity(vx, vy);
+ */
+}
+
+QPointArray Wall::areaPoints() const
+{
+ // editing we want full width for easy moving
+ if (editing)
+ return QCanvasLine::areaPoints();
+
+ // lessen width, for QCanvasLine::areaPoints() likes
+ // to make lines _very_ fat :(
+ // from qcanvas.cpp, only the stuff for a line width of 1 taken
+
+ // it's all squished because I don't want my
+ // line counts to count code I didn't write!
+ QPointArray p(4); const int xi = int(x()); const int yi = int(y()); const QPoint start = startPoint(); const QPoint end = endPoint(); const int x1 = start.x(); const int x2 = end.x(); const int y1 = start.y(); const int y2 = end.y(); const int dx = QABS(x1-x2); const int dy = QABS(y1-y2); if ( dx > dy ) { p[0] = QPoint(x1+xi,y1+yi-1); p[1] = QPoint(x2+xi,y2+yi-1); p[2] = QPoint(x2+xi,y2+yi+1); p[3] = QPoint(x1+xi,y1+yi+1); } else { p[0] = QPoint(x1+xi-1,y1+yi); p[1] = QPoint(x2+xi-1,y2+yi); p[2] = QPoint(x2+xi+1,y2+yi); p[3] = QPoint(x1+xi+1,y1+yi); } return p;
+}
+
+void Wall::editModeChanged(bool changed)
+{
+ // make big for debugging?
+ const bool debugPoints = false;
+
+ editing = changed;
+
+ startItem->setZ(z() + .002);
+ endItem->setZ(z() + .001);
+ startItem->editModeChanged(editing);
+ endItem->editModeChanged(editing);
+
+ int neww = 0;
+ if (changed || debugPoints)
+ neww = 10;
+ else
+ neww = pen().width();
+
+ startItem->setSize(neww, neww);
+ endItem->setSize(neww, neww);
+
+ moveBy(0, 0);
+}
+
+bool Wall::collision(Ball *ball, long int id)
+{
+ if (ball->curVector().magnitude() <= 0)
+ return false;
+
+ long int tempLastId = lastId;
+ lastId = id;
+ startItem->lastId = id;
+ endItem->lastId = id;
+
+ //kdDebug(12007) << "Wall::collision id: " << id << ", tempLastId: " << tempLastId << endl;
+ Vector ballVector(ball->curVector());
+
+ //kdDebug(12007) << "Wall::collision ball speed: " << ball->curVector().magnitude() << endl;
+ int allowableDifference = 1;
+ if (ballVector.magnitude() < .30)
+ allowableDifference = 8;
+ else if (ballVector.magnitude() < .50)
+ allowableDifference = 6;
+ else if (ballVector.magnitude() < .75)
+ allowableDifference = 4;
+ else if (ballVector.magnitude() < .95)
+ allowableDifference = 2;
+ //kdDebug(12007) << "Wall::collision allowableDifference is " << allowableDifference << endl;
+ if (abs(id - tempLastId) <= allowableDifference)
+ {
+ //kdDebug(12007) << "Wall::collision - SKIP\n";
+ return false;
+ }
+
+ playSound("wall", ball->curVector().magnitude() / 10.0);
+
+ ballVector /= dampening;
+ const double ballAngle = ballVector.direction();
+
+ const double wallAngle = -Vector(startPoint(), endPoint()).direction();
+ const double collisionAngle = ballAngle - wallAngle;
+ const double leavingAngle = wallAngle - collisionAngle;
+
+ ballVector.setDirection(leavingAngle);
+ ball->setVector(ballVector);
+
+ //kdDebug(12007) << "Wall::collision - NOT skip\n";
+ return false;
+}
+
+void Wall::load(KConfig *cfg)
+{
+ QPoint start(startPoint());
+ start = cfg->readPointEntry("startPoint", &start);
+ QPoint end(endPoint());
+ end = cfg->readPointEntry("endPoint", &end);
+
+ setPoints(start.x(), start.y(), end.x(), end.y());
+
+ moveBy(0, 0);
+ startItem->move(start.x(), start.y());
+ endItem->move(end.x(), end.y());
+}
+
+void Wall::save(KConfig *cfg)
+{
+ cfg->writeEntry("startPoint", QPoint(startItem->x(), startItem->y()));
+ cfg->writeEntry("endPoint", QPoint(endItem->x(), endItem->y()));
+}
+
+/////////////////////////
+
+HoleConfig::HoleConfig(HoleInfo *holeInfo, QWidget *parent)
+ : Config(parent)
+{
+ this->holeInfo = holeInfo;
+
+ QVBoxLayout *layout = new QVBoxLayout(this, marginHint(), spacingHint());
+
+ QHBoxLayout *hlayout = new QHBoxLayout(layout, spacingHint());
+ hlayout->addWidget(new QLabel(i18n("Course name: "), this));
+ KLineEdit *nameEdit = new KLineEdit(holeInfo->untranslatedName(), this);
+ hlayout->addWidget(nameEdit);
+ connect(nameEdit, SIGNAL(textChanged(const QString &)), this, SLOT(nameChanged(const QString &)));
+
+ hlayout = new QHBoxLayout(layout, spacingHint());
+ hlayout->addWidget(new QLabel(i18n("Course author: "), this));
+ KLineEdit *authorEdit = new KLineEdit(holeInfo->author(), this);
+ hlayout->addWidget(authorEdit);
+ connect(authorEdit, SIGNAL(textChanged(const QString &)), this, SLOT(authorChanged(const QString &)));
+
+ layout->addStretch();
+
+ hlayout = new QHBoxLayout(layout, spacingHint());
+ hlayout->addWidget(new QLabel(i18n("Par:"), this));
+ QSpinBox *par = new QSpinBox(1, 15, 1, this);
+ par->setValue(holeInfo->par());
+ hlayout->addWidget(par);
+ connect(par, SIGNAL(valueChanged(int)), this, SLOT(parChanged(int)));
+ hlayout->addStretch();
+
+ hlayout->addWidget(new QLabel(i18n("Maximum:"), this));
+ QSpinBox *maxstrokes = new QSpinBox(holeInfo->lowestMaxStrokes(), 30, 1, this);
+ QWhatsThis::add(maxstrokes, i18n("Maximum number of strokes player can take on this hole."));
+ QToolTip::add(maxstrokes, i18n("Maximum number of strokes"));
+ maxstrokes->setSpecialValueText(i18n("Unlimited"));
+ maxstrokes->setValue(holeInfo->maxStrokes());
+ hlayout->addWidget(maxstrokes);
+ connect(maxstrokes, SIGNAL(valueChanged(int)), this, SLOT(maxStrokesChanged(int)));
+
+ QCheckBox *check = new QCheckBox(i18n("Show border walls"), this);
+ check->setChecked(holeInfo->borderWalls());
+ layout->addWidget(check);
+ connect(check, SIGNAL(toggled(bool)), this, SLOT(borderWallsChanged(bool)));
+}
+
+void HoleConfig::authorChanged(const QString &newauthor)
+{
+ holeInfo->setAuthor(newauthor);
+ changed();
+}
+
+void HoleConfig::nameChanged(const QString &newname)
+{
+ holeInfo->setName(newname);
+ holeInfo->setUntranslatedName(newname);
+ changed();
+}
+
+void HoleConfig::parChanged(int newpar)
+{
+ holeInfo->setPar(newpar);
+ changed();
+}
+
+void HoleConfig::maxStrokesChanged(int newms)
+{
+ holeInfo->setMaxStrokes(newms);
+ changed();
+}
+
+void HoleConfig::borderWallsChanged(bool yes)
+{
+ holeInfo->borderWallsChanged(yes);
+ changed();
+}
+
+/////////////////////////
+
+StrokeCircle::StrokeCircle(QCanvas *canvas)
+ : QCanvasItem(canvas)
+{
+ dvalue = 0;
+ dmax = 360;
+ iwidth = 100;
+ iheight = 100;
+ ithickness = 8;
+ setZ(10000);
+}
+
+void StrokeCircle::setValue(double v)
+{
+ dvalue = v;
+ if (dvalue > dmax)
+ dvalue = dmax;
+
+ update();
+}
+
+double StrokeCircle::value()
+{
+ return dvalue;
+}
+
+bool StrokeCircle::collidesWith(const QCanvasItem*) const { return false; }
+
+bool StrokeCircle::collidesWith(const QCanvasSprite*, const QCanvasPolygonalItem*, const QCanvasRectangle*, const QCanvasEllipse*, const QCanvasText*) const { return false; }
+
+QRect StrokeCircle::boundingRect() const { return QRect(x(), y(), iwidth, iheight); }
+
+void StrokeCircle::setMaxValue(double m)
+{
+ dmax = m;
+ if (dvalue > dmax)
+ dvalue = dmax;
+
+ update();
+}
+void StrokeCircle::setSize(int w, int h)
+{
+ if (w > 0)
+ iwidth = w;
+ if (h > 0)
+ iheight = h;
+
+ update();
+}
+void StrokeCircle::setThickness(int t)
+{
+ if (t > 0)
+ ithickness = t;
+
+ update();
+}
+
+int StrokeCircle::thickness() const
+{
+ return ithickness;
+}
+
+int StrokeCircle::width() const
+{
+ return iwidth;
+}
+
+int StrokeCircle::height() const
+{
+ return iheight;
+}
+
+void StrokeCircle::draw(QPainter &p)
+{
+ int al = (int)((dvalue * 360 * 16) / dmax);
+ int length, deg;
+ if (al < 0)
+ {
+ deg = 270 * 16;
+ length = -al;
+ }
+ else if (al <= (270 * 16))
+ {
+ deg = 270 * 16 - al;
+ length = al;
+ }
+ else
+ {
+ deg = (360 * 16) - (al - (270 * 16));
+ length = al;
+ }
+
+ p.setBrush(QBrush(black, Qt::NoBrush));
+ p.setPen(QPen(white, ithickness / 2));
+ p.drawEllipse(x() + ithickness / 2, y() + ithickness / 2, iwidth - ithickness, iheight - ithickness);
+ p.setPen(QPen(QColor((int)(0xff * dvalue) / dmax, 0, 0xff - (int)(0xff * dvalue) / dmax), ithickness));
+ p.drawArc(x() + ithickness / 2, y() + ithickness / 2, iwidth - ithickness, iheight - ithickness, deg, length);
+
+ p.setPen(QPen(white, 1));
+ p.drawEllipse(x(), y(), iwidth, iheight);
+ p.drawEllipse(x() + ithickness, y() + ithickness, iwidth - ithickness * 2, iheight - ithickness * 2);
+ p.setPen(QPen(white, 3));
+ p.drawLine(x() + iwidth / 2, y() + iheight - ithickness * 1.5, x() + iwidth / 2, y() + iheight);
+ p.drawLine(x() + iwidth / 4 - iwidth / 20, y() + iheight - iheight / 4 + iheight / 20, x() + iwidth / 4 + iwidth / 20, y() + iheight - iheight / 4 - iheight / 20);
+ p.drawLine(x() + iwidth - iwidth / 4 + iwidth / 20, y() + iheight - iheight / 4 + iheight / 20, x() + iwidth - iwidth / 4 - iwidth / 20, y() + iheight - iheight / 4 - iheight / 20);
+}
+
+/////////////////////////////////////////
+
+KolfGame::KolfGame(ObjectList *obj, PlayerList *players, QString filename, QWidget *parent, const char *name )
+ : QCanvasView(parent, name)
+{
+ // for mouse control
+ setMouseTracking(true);
+ viewport()->setMouseTracking(true);
+ setFrameShape(NoFrame);
+
+ regAdv = false;
+ curHole = 0; // will get ++'d
+ cfg = 0;
+ setFilename(filename);
+ this->players = players;
+ this->obj = obj;
+ curPlayer = players->end();
+ curPlayer--; // will get ++'d to end and sent back
+ // to beginning
+ paused = false;
+ modified = false;
+ inPlay = false;
+ putting = false;
+ stroking = false;
+ editing = false;
+ strict = false;
+ lastDelId = -1;
+ m_showInfo = false;
+ ballStateList.canUndo = false;
+ fastAdvancedExist = false;
+ soundDir = locate("appdata", "sounds/");
+ dontAddStroke = false;
+ addingNewHole = false;
+ scoreboardHoles = 0;
+ infoShown = false;
+ m_useMouse = true;
+ m_useAdvancedPutting = false;
+ m_useAdvancedPutting = true;
+ m_sound = true;
+ m_ignoreEvents = false;
+ soundedOnce = false;
+ oldPlayObjects.setAutoDelete(true);
+ highestHole = 0;
+ recalcHighestHole = false;
+
+ holeInfo.setGame(this);
+ holeInfo.setAuthor(i18n("Course Author"));
+ holeInfo.setName(i18n("Course Name"));
+ holeInfo.setUntranslatedName(i18n("Course Name"));
+ holeInfo.setMaxStrokes(10);
+ holeInfo.borderWallsChanged(true);
+
+ // width and height are the width and height of the canvas
+ // in easy storage
+ width = 400;
+ height = 400;
+ grass = QColor("#35760D");
+
+ margin = 10;
+
+ setFocusPolicy(QWidget::StrongFocus);
+ setFixedSize(width + 2 * margin, height + 2 * margin);
+
+ setMargins(margin, margin, margin, margin);
+
+ course = new QCanvas(this);
+ course->setBackgroundColor(white);
+ course->resize(width, height);
+
+ QPixmap pic;
+ if (!QPixmapCache::find("grass", pic))
+ {
+ pic.load(locate("appdata", "pics/grass.png"));
+ QPixmapCache::insert("grass", pic);
+ }
+ course->setBackgroundPixmap(pic);
+
+ setCanvas(course);
+ move(0, 0);
+ adjustSize();
+
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ (*it).ball()->setCanvas(course);
+
+ // highlighter shows current item
+ highlighter = new QCanvasRectangle(course);
+ highlighter->setPen(QPen(yellow, 1));
+ highlighter->setBrush(QBrush(NoBrush));
+ highlighter->setVisible(false);
+ highlighter->setZ(10000);
+
+ // shows some info about hole
+ infoText = new QCanvasText(course);
+ infoText->setText("");
+ infoText->setColor(white);
+ QFont font = kapp->font();
+ font.setPixelSize(12);
+ infoText->move(15, width/2);
+ infoText->setZ(10001);
+ infoText->setFont(font);
+ infoText->setVisible(false);
+
+ // create the advanced putting indicator
+ strokeCircle = new StrokeCircle(course);
+ strokeCircle->move(width - 90, height - 90);
+ strokeCircle->setSize(80, 80);
+ strokeCircle->setThickness(8);
+ strokeCircle->setVisible(false);
+ strokeCircle->setValue(0);
+ strokeCircle->setMaxValue(360);
+
+ // whiteBall marks the spot of the whole whilst editing
+ whiteBall = new Ball(course);
+ whiteBall->setGame(this);
+ whiteBall->setColor(white);
+ whiteBall->setVisible(false);
+ whiteBall->setDoDetect(false);
+
+ int highestLog = 0;
+
+ // if players have scores from loaded game, move to last hole
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ {
+ if ((int)(*it).scores().count() > highestLog)
+ highestLog = (*it).scores().count();
+
+ (*it).ball()->setGame(this);
+ (*it).ball()->setAnimated(true);
+ }
+
+ // here only for saved games
+ if (highestLog)
+ curHole = highestLog;
+
+ putter = new Putter(course);
+
+ // border walls:
+
+ // horiz
+ addBorderWall(QPoint(margin, margin), QPoint(width - margin, margin));
+ addBorderWall(QPoint(margin, height - margin - 1), QPoint(width - margin, height - margin - 1));
+
+ // vert
+ addBorderWall(QPoint(margin, margin), QPoint(margin, height - margin));
+ addBorderWall(QPoint(width - margin - 1, margin), QPoint(width - margin - 1, height - margin));
+
+ timer = new QTimer(this);
+ connect(timer, SIGNAL(timeout()), this, SLOT(timeout()));
+ timerMsec = 300;
+
+ fastTimer = new QTimer(this);
+ connect(fastTimer, SIGNAL(timeout()), this, SLOT(fastTimeout()));
+ fastTimerMsec = 11;
+
+ autoSaveTimer = new QTimer(this);
+ connect(autoSaveTimer, SIGNAL(timeout()), this, SLOT(autoSaveTimeout()));
+ autoSaveMsec = 5 * 1000 * 60; // 5 min autosave
+
+ // setUseAdvancedPutting() sets maxStrength!
+ setUseAdvancedPutting(false);
+
+ putting = false;
+ putterTimer = new QTimer(this);
+ connect(putterTimer, SIGNAL(timeout()), this, SLOT(putterTimeout()));
+ putterTimerMsec = 20;
+}
+
+void KolfGame::startFirstHole(int hole)
+{
+ if (curHole > 0) // if there was saved game, sync scoreboard
+ // with number of holes
+ {
+ for (; scoreboardHoles < curHole; ++scoreboardHoles)
+ {
+ cfg->setGroup(QString("%1-hole@-50,-50|0").arg(scoreboardHoles + 1));
+ emit newHole(cfg->readNumEntry("par", 3));
+ }
+
+ // lets load all of the scores from saved game if there are any
+ for (int hole = 1; hole <= curHole; ++hole)
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ emit scoreChanged((*it).id(), hole, (*it).score(hole));
+ }
+
+ curHole = hole - 1;
+
+ // this increments curHole, etc
+ recalcHighestHole = true;
+ startNextHole();
+ paused = true;
+ unPause();
+}
+
+void KolfGame::setFilename(const QString &filename)
+{
+ this->filename = filename;
+ delete cfg;
+ cfg = new KConfig(filename, false, false);
+}
+
+KolfGame::~KolfGame()
+{
+ oldPlayObjects.clear();
+ delete cfg;
+}
+
+void KolfGame::setModified(bool mod)
+{
+ modified = mod;
+ emit modifiedChanged(mod);
+}
+
+void KolfGame::pause()
+{
+ if (paused)
+ {
+ // play along with people who call pause() again, instead of unPause()
+ unPause();
+ return;
+ }
+
+ paused = true;
+ timer->stop();
+ fastTimer->stop();
+ putterTimer->stop();
+}
+
+void KolfGame::unPause()
+{
+ if (!paused)
+ return;
+
+ paused = false;
+
+ timer->start(timerMsec);
+ fastTimer->start(fastTimerMsec);
+
+ if (putting || stroking)
+ putterTimer->start(putterTimerMsec);
+}
+
+void KolfGame::addBorderWall(QPoint start, QPoint end)
+{
+ Wall *wall = new Wall(course);
+ wall->setPoints(start.x(), start.y(), end.x(), end.y());
+ wall->setVisible(true);
+ wall->setGame(this);
+ wall->setZ(998.7);
+ borderWalls.append(wall);
+}
+
+void KolfGame::updateHighlighter()
+{
+ if (!selectedItem)
+ return;
+ QRect rect = selectedItem->boundingRect();
+ highlighter->move(rect.x() + 1, rect.y() + 1);
+ highlighter->setSize(rect.width(), rect.height());
+}
+
+void KolfGame::handleMouseDoubleClickEvent(QMouseEvent *e)
+{
+ // allow two fast single clicks
+ handleMousePressEvent(e);
+}
+
+void KolfGame::handleMousePressEvent(QMouseEvent *e)
+{
+ if (m_ignoreEvents)
+ return;
+
+ if (editing)
+ {
+ if (inPlay)
+ return;
+
+ storedMousePos = e->pos();
+
+ QCanvasItemList list = course->collisions(e->pos());
+ if (list.first() == highlighter)
+ list.pop_front();
+
+ moving = false;
+ highlighter->setVisible(false);
+ selectedItem = 0;
+ movingItem = 0;
+
+ if (list.count() < 1)
+ {
+ emit newSelectedItem(&holeInfo);
+ return;
+ }
+ // only items we keep track of
+ if ((!(items.containsRef(list.first()) || list.first() == whiteBall || extraMoveable.containsRef(list.first()))))
+ {
+ emit newSelectedItem(&holeInfo);
+ return;
+ }
+
+ CanvasItem *citem = dynamic_cast<CanvasItem *>(list.first());
+ if (!citem || !citem->moveable())
+ {
+ emit newSelectedItem(&holeInfo);
+ return;
+ }
+
+ switch (e->button())
+ {
+ // select AND move now :)
+ case LeftButton:
+ {
+ selectedItem = list.first();
+ movingItem = selectedItem;
+ moving = true;
+
+ if (citem->cornerResize())
+ setCursor(KCursor::sizeFDiagCursor());
+ else
+ setCursor(KCursor::sizeAllCursor());
+
+ emit newSelectedItem(citem);
+ highlighter->setVisible(true);
+ QRect rect = selectedItem->boundingRect();
+ highlighter->move(rect.x() + 1, rect.y() + 1);
+ highlighter->setSize(rect.width(), rect.height());
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ else
+ {
+ if (m_useMouse)
+ {
+ if (!inPlay && e->button() == LeftButton)
+ puttPress();
+ else if (e->button() == RightButton)
+ toggleShowInfo();
+ }
+ }
+
+ setFocus();
+}
+
+QPoint KolfGame::viewportToViewport(const QPoint &p)
+{
+ // for some reason viewportToContents doesn't work right
+ return p - QPoint(margin, margin);
+}
+
+// the following four functions are needed to handle both
+// border presses and regular in-course presses
+
+void KolfGame::mouseReleaseEvent(QMouseEvent * e)
+{
+ QMouseEvent fixedEvent (QEvent::MouseButtonRelease, viewportToViewport(viewportToContents(e->pos())), e->button(), e->state());
+ handleMouseReleaseEvent(&fixedEvent);
+}
+
+void KolfGame::mousePressEvent(QMouseEvent * e)
+{
+ QMouseEvent fixedEvent (QEvent::MouseButtonPress, viewportToViewport(viewportToContents(e->pos())), e->button(), e->state());
+ handleMousePressEvent(&fixedEvent);
+}
+
+void KolfGame::mouseDoubleClickEvent(QMouseEvent * e)
+{
+ QMouseEvent fixedEvent (QEvent::MouseButtonDblClick, viewportToViewport(viewportToContents(e->pos())), e->button(), e->state());
+ handleMouseDoubleClickEvent(&fixedEvent);
+}
+
+void KolfGame::mouseMoveEvent(QMouseEvent * e)
+{
+ QMouseEvent fixedEvent (QEvent::MouseMove, viewportToViewport(viewportToContents(e->pos())), e->button(), e->state());
+ handleMouseMoveEvent(&fixedEvent);
+}
+
+void KolfGame::handleMouseMoveEvent(QMouseEvent *e)
+{
+ if (inPlay || !putter || m_ignoreEvents)
+ return;
+
+ QPoint mouse = e->pos();
+
+ // mouse moving of putter
+ if (!editing)
+ {
+ updateMouse();
+ return;
+ }
+
+ if (!moving)
+ {
+ // lets change the cursor to a hand
+ // if we're hovering over something
+
+ QCanvasItemList list = course->collisions(e->pos());
+ if (list.count() > 0)
+ setCursor(KCursor::handCursor());
+ else
+ setCursor(KCursor::arrowCursor());
+ return;
+ }
+
+ int moveX = storedMousePos.x() - mouse.x();
+ int moveY = storedMousePos.y() - mouse.y();
+
+ // moving counts as modifying
+ if (moveX || moveY)
+ setModified(true);
+
+ highlighter->moveBy(-(double)moveX, -(double)moveY);
+ movingItem->moveBy(-(double)moveX, -(double)moveY);
+ QRect brect = movingItem->boundingRect();
+ emit newStatusText(QString("%1x%2").arg(brect.x()).arg(brect.y()));
+ storedMousePos = mouse;
+}
+
+void KolfGame::updateMouse()
+{
+ // don't move putter if in advanced putting sequence
+ if (!m_useMouse || ((stroking || putting) && m_useAdvancedPutting))
+ return;
+
+ const QPoint cursor = viewportToViewport(viewportToContents(mapFromGlobal(QCursor::pos())));
+ const QPoint ball((*curPlayer).ball()->x(), (*curPlayer).ball()->y());
+ putter->setAngle(-Vector(cursor, ball).direction());
+}
+
+void KolfGame::handleMouseReleaseEvent(QMouseEvent *e)
+{
+ setCursor(KCursor::arrowCursor());
+
+ if (editing)
+ {
+ emit newStatusText(QString::null);
+ moving = false;
+ }
+
+ if (m_ignoreEvents)
+ return;
+
+ if (!editing && m_useMouse)
+ {
+ if (!inPlay && e->button() == LeftButton)
+ puttRelease();
+ else if (e->button() == RightButton)
+ toggleShowInfo();
+ }
+
+ setFocus();
+}
+
+void KolfGame::keyPressEvent(QKeyEvent *e)
+{
+ if (inPlay || editing || m_ignoreEvents)
+ return;
+
+ switch (e->key())
+ {
+ case Key_Up:
+ if (!e->isAutoRepeat())
+ toggleShowInfo();
+ break;
+
+ case Key_Escape:
+ putting = false;
+ stroking = false;
+ finishStroking = false;
+ strokeCircle->setVisible(false);
+ putterTimer->stop();
+ putter->setOrigin((*curPlayer).ball()->x(), (*curPlayer).ball()->y());
+ break;
+
+ case Key_Left:
+ case Key_Right:
+ // don't move putter if in advanced putting sequence
+ if ((!stroking && !putting) || !m_useAdvancedPutting)
+ putter->go(e->key() == Key_Left? D_Left : D_Right, e->state() & ShiftButton? Amount_More : e->state() & ControlButton? Amount_Less : Amount_Normal);
+ break;
+
+ case Key_Space: case Key_Down:
+ puttPress();
+ break;
+
+ default:
+ break;
+ }
+}
+
+void KolfGame::toggleShowInfo()
+{
+ setShowInfo(!m_showInfo);
+}
+
+void KolfGame::updateShowInfo()
+{
+ setShowInfo(m_showInfo);
+}
+
+void KolfGame::setShowInfo(bool yes)
+{
+ m_showInfo = yes;
+
+ if (m_showInfo)
+ {
+ QCanvasItem *item = 0;
+ for (item = items.first(); item; item = items.next())
+ {
+ CanvasItem *citem = dynamic_cast<CanvasItem *>(item);
+ if (citem)
+ citem->showInfo();
+ }
+
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ (*it).ball()->showInfo();
+
+ showInfo();
+ }
+ else
+ {
+ QCanvasItem *item = 0;
+ for (item = items.first(); item; item = items.next())
+ {
+ CanvasItem *citem = dynamic_cast<CanvasItem *>(item);
+ if (citem)
+ citem->hideInfo();
+ }
+
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ (*it).ball()->hideInfo();
+
+ hideInfo();
+ }
+}
+
+void KolfGame::puttPress()
+{
+ // Advanced putting: 1st click start putting sequence, 2nd determine strength, 3rd determine precision
+
+ if (!putting && !stroking && !inPlay)
+ {
+ puttCount = 0;
+ puttReverse = false;
+ putting = true;
+ stroking = false;
+ strength = 0;
+ if (m_useAdvancedPutting)
+ {
+ strokeCircle->setValue(0);
+ int pw = putter->endPoint().x() - putter->startPoint().x();
+ if (pw < 0) pw = -pw;
+ int px = (int)putter->x() + pw / 2;
+ int py = (int)putter->y();
+ if (px > width / 2 && py < height / 2)
+ strokeCircle->move(px - pw / 2 - 10 - strokeCircle->width(), py + 10);
+ else if (px > width / 2)
+ strokeCircle->move(px - pw / 2 - 10 - strokeCircle->width(), py - 10 - strokeCircle->height());
+ else if (py < height / 2)
+ strokeCircle->move(px + pw / 2 + 10, py + 10);
+ else
+ strokeCircle->move(px + pw / 2 + 10, py - 10 - strokeCircle->height());
+ strokeCircle->setVisible(true);
+ }
+ putterTimer->start(putterTimerMsec);
+ }
+ else if (m_useAdvancedPutting && putting && !editing)
+ {
+ putting = false;
+ stroking = true;
+ puttReverse = false;
+ finishStroking = false;
+ }
+ else if (m_useAdvancedPutting && stroking)
+ {
+ finishStroking = true;
+ putterTimeout();
+ }
+}
+
+void KolfGame::keyReleaseEvent(QKeyEvent *e)
+{
+ if (e->isAutoRepeat() || m_ignoreEvents)
+ return;
+
+ if (e->key() == Key_Space || e->key() == Key_Down)
+ puttRelease();
+ else if ((e->key() == Key_Backspace || e->key() == Key_Delete) && !(e->state() & ControlButton))
+ {
+ if (editing && !moving && selectedItem)
+ {
+ CanvasItem *citem = dynamic_cast<CanvasItem *>(selectedItem);
+ if (!citem)
+ return;
+ citem = citem->itemToDelete();
+ if (!citem)
+ return;
+ QCanvasItem *item = dynamic_cast<QCanvasItem *>(citem);
+ if (citem && citem->deleteable())
+ {
+ lastDelId = citem->curId();
+
+ highlighter->setVisible(false);
+ items.removeRef(item);
+ citem->hideInfo();
+ citem->aboutToDelete();
+ citem->aboutToDie();
+ delete citem;
+ selectedItem = 0;
+ emit newSelectedItem(&holeInfo);
+
+ setModified(true);
+ }
+ }
+ }
+ else if (e->key() == Key_I || e->key() == Key_Up)
+ toggleShowInfo();
+}
+
+void KolfGame::puttRelease()
+{
+ if (!m_useAdvancedPutting && putting && !editing)
+ {
+ putting = false;
+ stroking = true;
+ }
+}
+
+void KolfGame::stoppedBall()
+{
+ if (!inPlay)
+ {
+ inPlay = true;
+ dontAddStroke = true;
+ }
+}
+
+void KolfGame::timeout()
+{
+ Ball *curBall = (*curPlayer).ball();
+
+ // test if the ball is gone
+ // in this case we want to stop the ball and
+ // later undo the shot
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ {
+ if (!course->rect().contains(QPoint((*it).ball()->x(), (*it).ball()->y())))
+ {
+ (*it).ball()->setState(Stopped);
+
+ // don't do it if he's past maxStrokes
+ if ((*it).score(curHole) < holeInfo.maxStrokes() - 1 || !holeInfo.hasMaxStrokes())
+ {
+ loadStateList();
+ }
+ shotDone();
+
+ return;
+ }
+ }
+
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ if ((*it).ball()->forceStillGoing() || ((*it).ball()->curState() == Rolling && (*it).ball()->curVector().magnitude() > 0 && (*it).ball()->isVisible()))
+ return;
+
+ int curState = curBall->curState();
+ if (curState == Stopped && inPlay)
+ {
+ inPlay = false;
+ QTimer::singleShot(0, this, SLOT(shotDone()));
+ }
+
+ if (curState == Holed && inPlay)
+ {
+ emit inPlayEnd();
+ emit playerHoled(&(*curPlayer));
+
+ int curScore = (*curPlayer).score(curHole);
+ if (!dontAddStroke)
+ curScore++;
+
+ if (curScore == 1)
+ {
+ playSound("holeinone");
+ }
+ else if (curScore <= holeInfo.par())
+ {
+ // I don't have a sound!!
+ // *sob*
+ // playSound("woohoo");
+ }
+
+ (*curPlayer).ball()->setZ((*curPlayer).ball()->z() + .1 - (.1)/(curScore));
+
+ if (allPlayersDone())
+ {
+ inPlay = false;
+
+ if (curHole > 0 && !dontAddStroke)
+ {
+ (*curPlayer).addStrokeToHole(curHole);
+ emit scoreChanged((*curPlayer).id(), curHole, (*curPlayer).score(curHole));
+ }
+ QTimer::singleShot(600, this, SLOT(holeDone()));
+ }
+ else
+ {
+ inPlay = false;
+ QTimer::singleShot(0, this, SLOT(shotDone()));
+ }
+ }
+}
+
+void KolfGame::fastTimeout()
+{
+ // do regular advance every other time
+ if (regAdv)
+ course->advance();
+ regAdv = !regAdv;
+
+ if (!editing)
+ {
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ (*it).ball()->doAdvance();
+
+ if (fastAdvancedExist)
+ {
+ CanvasItem *citem = 0;
+ for (citem = fastAdvancers.first(); citem; citem = fastAdvancers.next())
+ citem->doAdvance();
+ }
+
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ (*it).ball()->fastAdvanceDone();
+
+ if (fastAdvancedExist)
+ {
+ CanvasItem *citem = 0;
+ for (citem = fastAdvancers.first(); citem; citem = fastAdvancers.next())
+ citem->fastAdvanceDone();
+ }
+ }
+}
+
+void KolfGame::ballMoved()
+{
+ if (putter->isVisible())
+ {
+ putter->move((*curPlayer).ball()->x(), (*curPlayer).ball()->y());
+ updateMouse();
+ }
+}
+
+void KolfGame::putterTimeout()
+{
+ if (inPlay || editing)
+ return;
+
+ if (m_useAdvancedPutting)
+ {
+ if (putting)
+ {
+ const float base = 2.0;
+
+ if (puttReverse && strength <= 0)
+ {
+ // aborted
+ putting = false;
+ strokeCircle->setVisible(false);
+ }
+ else if (strength > maxStrength || puttReverse)
+ {
+ // decreasing strength as we've reached the top
+ puttReverse = true;
+ strength -= pow(base, strength / maxStrength) - 1.8;
+ if ((int)strength < puttCount * 2)
+ {
+ puttCount--;
+ if (puttCount >= 0)
+ putter->go(Forwards);
+ }
+ }
+ else
+ {
+ // make the increase at high strength faster
+ strength += pow(base, strength / maxStrength) - .3;
+ if ((int)strength > puttCount * 2)
+ {
+ putter->go(Backwards);
+ puttCount++;
+ }
+ }
+ // make the visible steps at high strength smaller
+ strokeCircle->setValue(pow(strength / maxStrength, 0.8) * 360);
+ }
+ else if (stroking)
+ {
+ double al = strokeCircle->value();
+ if (al >= 45)
+ al -= 0.2 + strength / 50 + al / 100;
+ else
+ al -= 0.2 + strength / 50;
+
+ if (puttReverse)
+ {
+ // show the stroke
+ puttCount--;
+ if (puttCount >= 0)
+ putter->go(Forwards);
+ else
+ {
+ strokeCircle->setVisible(false);
+ finishStroking = false;
+ putterTimer->stop();
+ putting = false;
+ stroking = false;
+ shotStart();
+ }
+ }
+ else if (al < -45 || finishStroking)
+ {
+ strokeCircle->setValue(al);
+ int deg;
+ // if > 45 or < -45 then bad stroke
+ if (al > 45)
+ {
+ deg = putter->curDeg() - 45 + rand() % 90;
+ strength -= rand() % (int)strength;
+ }
+ else if (!finishStroking)
+ {
+ deg = putter->curDeg() - 45 + rand() % 90;
+ strength -= rand() % (int)strength;
+ }
+ else
+ deg = putter->curDeg() + (int)(strokeCircle->value() / 3);
+
+ if (deg < 0)
+ deg += 360;
+ else if (deg > 360)
+ deg -= 360;
+
+ putter->setDeg(deg);
+ puttReverse = true;
+ }
+ else
+ {
+ strokeCircle->setValue(al);
+ putterTimer->changeInterval(putterTimerMsec/10);
+ }
+ }
+
+ }
+ else
+ {
+ if (putting)
+ {
+ putter->go(Backwards);
+ puttCount++;
+ strength += 1.5;
+ if (strength > maxStrength)
+ {
+ putting = false;
+ stroking = true;
+ }
+ }
+ else if (stroking)
+ {
+ if (putter->curLen() < (*curPlayer).ball()->height() + 2)
+ {
+ stroking = false;
+ putterTimer->stop();
+ putting = false;
+ stroking = false;
+ shotStart();
+ }
+
+ putter->go(Forwards);
+ putterTimer->changeInterval(putterTimerMsec/10);
+ }
+ }
+}
+
+void KolfGame::autoSaveTimeout()
+{
+ // this should be a config option
+ // until it is i'll disable it
+ if (editing)
+ {
+ //save();
+ }
+}
+
+void KolfGame::recreateStateList()
+{
+ stateDB.clear();
+
+ QCanvasItem *item = 0;
+
+ for (item = items.first(); item; item = items.next())
+ {
+ CanvasItem *citem = dynamic_cast<CanvasItem *>(item);
+ if (citem)
+ {
+ stateDB.setName(makeStateGroup(citem->curId(), citem->name()));
+ citem->saveState(&stateDB);
+ }
+ }
+
+ ballStateList.clear();
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ ballStateList.append((*it).stateInfo(curHole));
+
+ ballStateList.canUndo = true;
+}
+
+void KolfGame::undoShot()
+{
+ if (ballStateList.canUndo)
+ loadStateList();
+}
+
+void KolfGame::loadStateList()
+{
+ QCanvasItem *item = 0;
+
+ for (item = items.first(); item; item = items.next())
+ {
+ CanvasItem *citem = dynamic_cast<CanvasItem *>(item);
+ if (citem)
+ {
+ stateDB.setName(makeStateGroup(citem->curId(), citem->name()));
+ citem->loadState(&stateDB);
+ }
+ }
+
+ for (BallStateList::Iterator it = ballStateList.begin(); it != ballStateList.end(); ++it)
+ {
+ BallStateInfo info = (*it);
+ Player &player = (*players->at(info.id - 1));
+ player.ball()->move(info.spot.x(), info.spot.y());
+ player.ball()->setBeginningOfHole(info.beginningOfHole);
+ if ((*curPlayer).id() == info.id)
+ ballMoved();
+ else
+ player.ball()->setVisible(!info.beginningOfHole);
+ player.setScoreForHole(info.score, curHole);
+ player.ball()->setState(info.state);
+ emit scoreChanged(info.id, curHole, info.score);
+ }
+}
+
+void KolfGame::shotDone()
+{
+ inPlay = false;
+ emit inPlayEnd();
+ setFocus();
+
+ Ball *ball = (*curPlayer).ball();
+ double oldx = ball->x(), oldy = ball->y();
+
+ if (!dontAddStroke && (*curPlayer).numHoles())
+ (*curPlayer).addStrokeToHole(curHole);
+
+ dontAddStroke = false;
+
+ // do hack stuff, shouldn't be done here
+
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ {
+ if ((*it).ball()->addStroke())
+ {
+ for (int i = 1; i <= (*it).ball()->addStroke(); ++i)
+ (*it).addStrokeToHole(curHole);
+
+ // emit that we have a new stroke count
+ emit scoreChanged((*it).id(), curHole, (*it).score(curHole));
+ }
+ (*it).ball()->setAddStroke(0);
+ }
+
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ {
+ Ball *ball = (*it).ball();
+
+ if (ball->curState() == Holed)
+ continue;
+
+ Vector v;
+ if (ball->placeOnGround(v))
+ {
+ ball->setPlaceOnGround(false);
+
+ QStringList options;
+ const QString placeOutside = i18n("Drop Outside of Hazard");
+ const QString rehit = i18n("Rehit From Last Location");
+ options << placeOutside << rehit;
+ const QString choice = KComboBoxDialog::getItem(i18n("What would you like to do for your next shot?"), i18n("%1 is in a Hazard").arg((*it).name()), options, placeOutside, "hazardOptions");
+
+ if (choice == placeOutside)
+ {
+ (*it).ball()->setDoDetect(false);
+
+ double x = ball->x(), y = ball->y();
+
+ while (1)
+ {
+ QCanvasItemList list = ball->collisions(true);
+ bool keepMoving = false;
+ while (!list.isEmpty())
+ {
+ QCanvasItem *item = list.first();
+ if (item->rtti() == Rtti_DontPlaceOn)
+ keepMoving = true;
+
+ list.pop_front();
+ }
+ if (!keepMoving)
+ break;
+
+ const float movePixel = 3.0;
+ x -= cos(v.direction()) * movePixel;
+ y += sin(v.direction()) * movePixel;
+
+ ball->move(x, y);
+ }
+
+ // move another two pixels away
+ x -= cos(v.direction()) * 2;
+ y += sin(v.direction()) * 2;
+ }
+ else if (choice == rehit)
+ {
+ for (BallStateList::Iterator it = ballStateList.begin(); it != ballStateList.end(); ++it)
+ {
+ if ((*it).id == (*curPlayer).id())
+ {
+ if ((*it).beginningOfHole)
+ ball->move(whiteBall->x(), whiteBall->y());
+ else
+ ball->move((*it).spot.x(), (*it).spot.y());
+
+ break;
+ }
+ }
+ }
+
+ ball->setVisible(true);
+ ball->setState(Stopped);
+
+ (*it).ball()->setDoDetect(true);
+ ball->collisionDetect(oldx, oldy);
+ }
+ }
+
+ // emit again
+ emit scoreChanged((*curPlayer).id(), curHole, (*curPlayer).score(curHole));
+
+ ball->setVelocity(0, 0);
+
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ {
+ Ball *ball = (*it).ball();
+
+ int curStrokes = (*it).score(curHole);
+ if (curStrokes >= holeInfo.maxStrokes() && holeInfo.hasMaxStrokes())
+ {
+ ball->setState(Holed);
+ ball->setVisible(false);
+
+ // move to center in case he/she hit out
+ ball->move(width / 2, height / 2);
+ playerWhoMaxed = (*it).name();
+
+ if (allPlayersDone())
+ {
+ startNextHole();
+ QTimer::singleShot(100, this, SLOT(emitMax()));
+ return;
+ }
+
+ QTimer::singleShot(100, this, SLOT(emitMax()));
+ }
+ }
+
+ // change player to next player
+ // skip player if he's Holed
+ do
+ {
+ curPlayer++;
+ if (curPlayer == players->end())
+ curPlayer = players->begin();
+ }
+ while ((*curPlayer).ball()->curState() == Holed);
+
+ emit newPlayersTurn(&(*curPlayer));
+
+ (*curPlayer).ball()->setVisible(true);
+
+ putter->setAngle((*curPlayer).ball());
+ putter->setOrigin((*curPlayer).ball()->x(), (*curPlayer).ball()->y());
+ updateMouse();
+
+ inPlay = false;
+ (*curPlayer).ball()->collisionDetect(oldx, oldy);
+}
+
+void KolfGame::emitMax()
+{
+ emit maxStrokesReached(playerWhoMaxed);
+}
+
+void KolfGame::startBall(const Vector &vector)
+{
+ playSound("hit");
+
+ emit inPlayStart();
+ putter->setVisible(false);
+
+ (*curPlayer).ball()->setState(Rolling);
+ (*curPlayer).ball()->setVector(vector);
+
+ QCanvasItem *item = 0;
+ for (item = items.first(); item; item = items.next())
+ {
+ CanvasItem *citem = dynamic_cast<CanvasItem *>(item);
+ if (citem)
+ citem->shotStarted();
+ }
+
+ inPlay = true;
+}
+
+void KolfGame::shotStart()
+{
+ // ensure we never hit the ball back into the hole which
+ // can cause hole skippage
+ if ((*curPlayer).ball()->curState() == Holed)
+ return;
+
+ // save state
+ recreateStateList();
+
+ putter->saveAngle((*curPlayer).ball());
+ strength /= 8;
+ if (!strength)
+ strength = 1;
+
+ startBall(Vector(strength, putter->curAngle() + M_PI));
+
+ addHoleInfo(ballStateList);
+}
+
+void KolfGame::addHoleInfo(BallStateList &list)
+{
+ list.player = (*curPlayer).id();
+ list.vector = (*curPlayer).ball()->curVector();
+ list.hole = curHole;
+}
+
+void KolfGame::sayWhosGoing()
+{
+ if (players->count() >= 2)
+ {
+ KMessageBox::information(this, i18n("%1 will start off.").arg((*curPlayer).name()), i18n("New Hole"), "newHole");
+ }
+}
+
+void KolfGame::holeDone()
+{
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ (*it).ball()->setVisible(false);
+ startNextHole();
+ sayWhosGoing();
+}
+
+// this function is WAY too smart for it's own good
+// ie, bad design :-(
+void KolfGame::startNextHole()
+{
+ setFocus();
+
+ bool reset = true;
+ if (askSave(true))
+ {
+ if (allPlayersDone())
+ {
+ // we'll reload this hole, but not reset
+ curHole--;
+ reset = false;
+ }
+ else
+ return;
+ }
+ else
+ setModified(false);
+
+ pause();
+
+ dontAddStroke = false;
+
+ inPlay = false;
+ timer->stop();
+ putter->resetAngles();
+
+ int oldCurHole = curHole;
+ curHole++;
+ emit currentHole(curHole);
+
+ if (reset)
+ {
+ whiteBall->move(width/2, height/2);
+ holeInfo.borderWallsChanged(true);
+ }
+
+ int leastScore = INT_MAX;
+
+ // to get the first player to go first on every hole,
+ // don't do the score stuff below
+ curPlayer = players->begin();
+ double oldx=(*curPlayer).ball()->x(), oldy=(*curPlayer).ball()->y();
+
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ {
+ if (curHole > 1)
+ {
+ bool ahead = false;
+ if ((*it).lastScore() != 0)
+ {
+ if ((*it).lastScore() < leastScore)
+ ahead = true;
+ else if ((*it).lastScore() == leastScore)
+ {
+ for (int i = curHole - 1; i > 0; --i)
+ {
+ const int thisScore = (*it).score(i);
+ const int thatScore = (*curPlayer).score(i);
+ if (thisScore < thatScore)
+ {
+ ahead = true;
+ break;
+ }
+ else if (thisScore > thatScore)
+ break;
+ }
+ }
+ }
+
+ if (ahead)
+ {
+ curPlayer = it;
+ leastScore = (*it).lastScore();
+ }
+ }
+
+ if (reset)
+ (*it).ball()->move(width / 2, height / 2);
+ else
+ (*it).ball()->move(whiteBall->x(), whiteBall->y());
+
+ (*it).ball()->setState(Stopped);
+
+ // this gets set to false when the ball starts
+ // to move by the Mr. Ball himself.
+ (*it).ball()->setBeginningOfHole(true);
+ if ((int)(*it).scores().count() < curHole)
+ (*it).addHole();
+ (*it).ball()->setVelocity(0, 0);
+ (*it).ball()->setVisible(false);
+ }
+
+ emit newPlayersTurn(&(*curPlayer));
+
+ if (reset)
+ openFile();
+
+ inPlay = false;
+ timer->start(timerMsec);
+
+ // if (false) { we're done with the round! }
+ if (oldCurHole != curHole)
+ {
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ (*it).ball()->setPlaceOnGround(false);
+
+ // here we have to make sure the scoreboard shows
+ // all of the holes up until now;
+
+ for (; scoreboardHoles < curHole; ++scoreboardHoles)
+ {
+ cfg->setGroup(QString("%1-hole@-50,-50|0").arg(scoreboardHoles + 1));
+ emit newHole(cfg->readNumEntry("par", 3));
+ }
+
+ resetHoleScores();
+ updateShowInfo();
+
+ // this is from shotDone()
+ (*curPlayer).ball()->setVisible(true);
+ putter->setOrigin((*curPlayer).ball()->x(), (*curPlayer).ball()->y());
+ updateMouse();
+
+ ballStateList.canUndo = false;
+
+ (*curPlayer).ball()->collisionDetect(oldx, oldy);
+ }
+
+ unPause();
+}
+
+void KolfGame::showInfo()
+{
+ QString text = i18n("Hole %1: par %2, maximum %3 strokes").arg(curHole).arg(holeInfo.par()).arg(holeInfo.maxStrokes());
+ infoText->move((width - QFontMetrics(infoText->font()).width(text)) / 2, infoText->y());
+ infoText->setText(text);
+ // I hate this text! Let's not show it
+ //infoText->setVisible(true);
+
+ emit newStatusText(text);
+}
+
+void KolfGame::showInfoDlg(bool addDontShowAgain)
+{
+ KMessageBox::information(parentWidget(),
+ i18n("Course name: %1").arg(holeInfo.name()) + QString("\n")
+ + i18n("Created by %1").arg(holeInfo.author()) + QString("\n")
+ + i18n("%1 holes").arg(highestHole),
+ i18n("Course Information"),
+ addDontShowAgain? holeInfo.name() + QString(" ") + holeInfo.author() : QString::null);
+}
+
+void KolfGame::hideInfo()
+{
+ infoText->setText("");
+ infoText->setVisible(false);
+
+ emit newStatusText(QString::null);
+}
+
+void KolfGame::openFile()
+{
+ Object *curObj = 0;
+
+ QCanvasItem *item = 0;
+ for (item = items.first(); item; item = items.next())
+ {
+ CanvasItem *citem = dynamic_cast<CanvasItem *>(item);
+ if (citem)
+ {
+ // sometimes info is still showing
+ citem->hideInfo();
+ citem->aboutToDie();
+ }
+ }
+
+ items.setAutoDelete(true);
+ items.clear();
+ items.setAutoDelete(false);
+
+ extraMoveable.setAutoDelete(false);
+ extraMoveable.clear();
+ fastAdvancers.setAutoDelete(false);
+ fastAdvancers.clear();
+ selectedItem = 0;
+
+ // will tell basic course info
+ // we do this here for the hell of it.
+ // there is no fake id, by the way,
+ // because it's old and when i added ids i forgot to change it.
+ cfg->setGroup("0-course@-50,-50");
+ holeInfo.setAuthor(cfg->readEntry("author", holeInfo.author()));
+ holeInfo.setName(cfg->readEntry("Name", holeInfo.name()));
+ holeInfo.setUntranslatedName(cfg->readEntryUntranslated("Name", holeInfo.untranslatedName()));
+ emit titleChanged(holeInfo.name());
+
+ cfg->setGroup(QString("%1-hole@-50,-50|0").arg(curHole));
+ curPar = cfg->readNumEntry("par", 3);
+ holeInfo.setPar(curPar);
+ holeInfo.borderWallsChanged(cfg->readBoolEntry("borderWalls", holeInfo.borderWalls()));
+ holeInfo.setMaxStrokes(cfg->readNumEntry("maxstrokes", 10));
+ bool hasFinalLoad = cfg->readBoolEntry("hasFinalLoad", true);
+
+ QStringList missingPlugins;
+ QStringList groups = cfg->groupList();
+
+ int numItems = 0;
+ int _highestHole = 0;
+
+ for (QStringList::Iterator it = groups.begin(); it != groups.end(); ++it)
+ {
+ // [<holeNum>-<name>@<x>,<y>|<id>]
+ cfg->setGroup(*it);
+
+ const int len = (*it).length();
+ const int dashIndex = (*it).find("-");
+ const int holeNum = (*it).left(dashIndex).toInt();
+ if (holeNum > _highestHole)
+ _highestHole = holeNum;
+
+ const int atIndex = (*it).find("@");
+ const QString name = (*it).mid(dashIndex + 1, atIndex - (dashIndex + 1));
+
+ if (holeNum != curHole)
+ {
+ // if we've had one, break, cause list is sorted
+ // erps, no, cause we need to know highest hole!
+ if (numItems && !recalcHighestHole)
+ break;
+ continue;
+ }
+ numItems++;
+
+
+ const int commaIndex = (*it).find(",");
+ const int pipeIndex = (*it).find("|");
+ const int x = (*it).mid(atIndex + 1, commaIndex - (atIndex + 1)).toInt();
+ const int y = (*it).mid(commaIndex + 1, pipeIndex - (commaIndex + 1)).toInt();
+
+ // will tell where ball is
+ if (name == "ball")
+ {
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ (*it).ball()->move(x, y);
+ whiteBall->move(x, y);
+ continue;
+ }
+
+ const int id = (*it).right(len - (pipeIndex + 1)).toInt();
+
+ bool loaded = false;
+
+ for (curObj = obj->first(); curObj; curObj = obj->next())
+ {
+ if (name != curObj->_name())
+ continue;
+
+ QCanvasItem *newItem = curObj->newObject(course);
+ items.append(newItem);
+
+ CanvasItem *canvasItem = dynamic_cast<CanvasItem *>(newItem);
+ if (!canvasItem)
+ continue;
+
+ canvasItem->setId(id);
+ canvasItem->setGame(this);
+ canvasItem->editModeChanged(editing);
+ canvasItem->setName(curObj->_name());
+ addItemsToMoveableList(canvasItem->moveableItems());
+ if (canvasItem->fastAdvance())
+ addItemToFastAdvancersList(canvasItem);
+
+ newItem->move(x, y);
+ canvasItem->firstMove(x, y);
+
+ newItem->setVisible(true);
+
+ // make things actually show
+ if (!hasFinalLoad)
+ {
+ cfg->setGroup(makeGroup(id, curHole, canvasItem->name(), x, y));
+ canvasItem->load(cfg);
+ course->update();
+ }
+
+ // we don't allow multiple items for the same thing in
+ // the file!
+
+ loaded = true;
+ break;
+ }
+
+ if (!loaded && name != "hole" && missingPlugins.contains(name) <= 0)
+ missingPlugins.append(name);
+ }
+
+ if (!missingPlugins.empty())
+ {
+ KMessageBox::informationList(this, QString("<p>&lt;http://katzbrown.com/kolf/Plugins/&gt;</p><p>") + i18n("This hole uses the following plugins, which you do not have installed:") + QString("</p>"), missingPlugins, QString::null, QString("%1 warning").arg(holeInfo.untranslatedName() + QString::number(curHole)));
+ }
+
+ lastDelId = -1;
+
+ // if it's the first hole let's not
+ if (!numItems && curHole > 1 && !addingNewHole && curHole >= _highestHole)
+ {
+ // we're done, let's quit
+ curHole--;
+ pause();
+ emit holesDone();
+
+ // tidy things up
+ setBorderWalls(false);
+ clearHole();
+ setModified(false);
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ (*it).ball()->setVisible(false);
+
+ return;
+ }
+
+ // do it down here; if !hasFinalLoad, do it up there!
+ QCanvasItem *qcanvasItem = 0;
+ QPtrList<CanvasItem> todo;
+ QPtrList<QCanvasItem> qtodo;
+ if (hasFinalLoad)
+ {
+ for (qcanvasItem = items.first(); qcanvasItem; qcanvasItem = items.next())
+ {
+ CanvasItem *item = dynamic_cast<CanvasItem *>(qcanvasItem);
+ if (item)
+ {
+ if (item->loadLast())
+ {
+ qtodo.append(qcanvasItem);
+ todo.append(item);
+ }
+ else
+ {
+ QString group = makeGroup(item->curId(), curHole, item->name(), (int)qcanvasItem->x(), (int)qcanvasItem->y());
+ cfg->setGroup(group);
+ item->load(cfg);
+ }
+ }
+ }
+
+ CanvasItem *citem = 0;
+ qcanvasItem = qtodo.first();
+ for (citem = todo.first(); citem; citem = todo.next())
+ {
+ cfg->setGroup(makeGroup(citem->curId(), curHole, citem->name(), (int)qcanvasItem->x(), (int)qcanvasItem->y()));
+ citem->load(cfg);
+
+ qcanvasItem = qtodo.next();
+ }
+ }
+
+ for (qcanvasItem = items.first(); qcanvasItem; qcanvasItem = items.next())
+ {
+ CanvasItem *citem = dynamic_cast<CanvasItem *>(qcanvasItem);
+ if (citem)
+ citem->updateZ();
+ }
+
+ if (curHole > _highestHole)
+ _highestHole = curHole;
+
+ if (recalcHighestHole)
+ {
+ highestHole = _highestHole;
+ recalcHighestHole = false;
+ emit largestHole(highestHole);
+ }
+
+ if (curHole == 1 && !filename.isNull() && !infoShown)
+ {
+ // let's not now, because they see it when they choose course
+ //showInfoDlg(true);
+ infoShown = true;
+ }
+
+ setModified(false);
+}
+
+void KolfGame::addItemsToMoveableList(QPtrList<QCanvasItem> list)
+{
+ QCanvasItem *item = 0;
+ for (item = list.first(); item; item = list.next())
+ extraMoveable.append(item);
+}
+
+void KolfGame::addItemToFastAdvancersList(CanvasItem *item)
+{
+ fastAdvancers.append(item);
+ fastAdvancedExist = fastAdvancers.count() > 0;
+}
+
+void KolfGame::addNewObject(Object *newObj)
+{
+ QCanvasItem *newItem = newObj->newObject(course);
+ items.append(newItem);
+ newItem->setVisible(true);
+
+ CanvasItem *canvasItem = dynamic_cast<CanvasItem *>(newItem);
+ if (!canvasItem)
+ return;
+
+ // we need to find a number that isn't taken
+ int i = lastDelId > 0? lastDelId : items.count() - 30;
+ if (i <= 0)
+ i = 0;
+
+ for (;; ++i)
+ {
+ bool found = false;
+ QCanvasItem *item = 0;
+ for (item = items.first(); item; item = items.next())
+ {
+ CanvasItem *citem = dynamic_cast<CanvasItem *>(item);
+ if (citem)
+ {
+ if (citem->curId() == i)
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+
+
+ if (!found)
+ break;
+ }
+ canvasItem->setId(i);
+
+ canvasItem->setGame(this);
+
+ if (m_showInfo)
+ canvasItem->showInfo();
+ else
+ canvasItem->hideInfo();
+
+ canvasItem->editModeChanged(editing);
+
+ canvasItem->setName(newObj->_name());
+ addItemsToMoveableList(canvasItem->moveableItems());
+
+ if (canvasItem->fastAdvance())
+ addItemToFastAdvancersList(canvasItem);
+
+ newItem->move(width/2 - 18, height / 2 - 18);
+
+ if (selectedItem)
+ canvasItem->selectedItem(selectedItem);
+
+ setModified(true);
+}
+
+bool KolfGame::askSave(bool noMoreChances)
+{
+ if (!modified)
+ // not cancel, don't save
+ return false;
+
+ int result = KMessageBox::warningYesNoCancel(this, i18n("There are unsaved changes to current hole. Save them?"), i18n("Unsaved Changes"), KStdGuiItem::save(), noMoreChances? KStdGuiItem::discard() : i18n("Save &Later"), noMoreChances? "DiscardAsk" : "SaveAsk", true);
+ switch (result)
+ {
+ case KMessageBox::Yes:
+ save();
+ // fallthrough
+
+ case KMessageBox::No:
+ return false;
+ break;
+
+ case KMessageBox::Cancel:
+ return true;
+ break;
+
+ default:
+ break;
+ }
+
+ return false;
+}
+
+void KolfGame::addNewHole()
+{
+ if (askSave(true))
+ return;
+
+ // either it's already false
+ // because it was saved by askSave(),
+ // or the user pressed the 'discard' button
+ setModified(false);
+
+ // find highest hole num, and create new hole
+ // now openFile makes highest hole for us
+
+ addingNewHole = true;
+ curHole = highestHole;
+ recalcHighestHole = true;
+ startNextHole();
+ addingNewHole = false;
+ emit currentHole(curHole);
+
+ // make sure even the current player isn't showing
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ (*it).ball()->setVisible(false);
+
+ whiteBall->setVisible(editing);
+ highlighter->setVisible(false);
+ putter->setVisible(!editing);
+ inPlay = false;
+
+ // add default objects
+ Object *curObj = 0;
+ for (curObj = obj->first(); curObj; curObj = obj->next())
+ if (curObj->addOnNewHole())
+ addNewObject(curObj);
+
+ save();
+}
+
+// kantan deshou ;-)
+void KolfGame::resetHole()
+{
+ if (askSave(true))
+ return;
+ setModified(false);
+ curHole--;
+ startNextHole();
+ resetHoleScores();
+}
+
+void KolfGame::resetHoleScores()
+{
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ {
+ (*it).resetScore(curHole);
+ emit scoreChanged((*it).id(), curHole, 0);
+ }
+}
+
+void KolfGame::clearHole()
+{
+ QCanvasItem *qcanvasItem = 0;
+ for (qcanvasItem = items.first(); qcanvasItem; qcanvasItem = items.next())
+ {
+ CanvasItem *citem = dynamic_cast<CanvasItem *>(qcanvasItem);
+ if (citem)
+ citem->aboutToDie();
+ }
+ items.setAutoDelete(true);
+ items.clear();
+ items.setAutoDelete(false);
+ emit newSelectedItem(&holeInfo);
+
+ // add default objects
+ Object *curObj = 0;
+ for (curObj = obj->first(); curObj; curObj = obj->next())
+ if (curObj->addOnNewHole())
+ addNewObject(curObj);
+
+ setModified(true);
+}
+
+void KolfGame::switchHole(int hole)
+{
+ if (inPlay)
+ return;
+ if (hole < 1 || hole > highestHole)
+ return;
+
+ bool wasEditing = editing;
+ if (editing)
+ toggleEditMode();
+
+ if (askSave(true))
+ return;
+ setModified(false);
+
+ curHole = hole;
+ resetHole();
+
+ if (wasEditing)
+ toggleEditMode();
+}
+
+void KolfGame::switchHole(const QString &holestring)
+{
+ bool ok;
+ int hole = holestring.toInt(&ok);
+ if (!ok)
+ return;
+ switchHole(hole);
+}
+
+void KolfGame::nextHole()
+{
+ switchHole(curHole + 1);
+}
+
+void KolfGame::prevHole()
+{
+ switchHole(curHole - 1);
+}
+
+void KolfGame::firstHole()
+{
+ switchHole(1);
+}
+
+void KolfGame::lastHole()
+{
+ switchHole(highestHole);
+}
+
+void KolfGame::randHole()
+{
+ int newHole = 1 + (int)((double)kapp->random() * ((double)(highestHole - 1) / (double)RAND_MAX));
+ switchHole(newHole);
+}
+
+void KolfGame::save()
+{
+ if (filename.isNull())
+ {
+ QString newfilename = KFileDialog::getSaveFileName(":kourses", "application/x-kourse", this, i18n("Pick Kolf Course to Save To"));
+ if (newfilename.isNull())
+ return;
+
+ setFilename(newfilename);
+ }
+
+ emit parChanged(curHole, holeInfo.par());
+ emit titleChanged(holeInfo.name());
+
+ // we use this bool for optimization
+ // in openFile().
+ bool hasFinalLoad = false;
+ fastAdvancedExist = false;
+
+ QCanvasItem *item = 0;
+ for (item = items.first(); item; item = items.next())
+ {
+ CanvasItem *citem = dynamic_cast<CanvasItem *>(item);
+ if (citem)
+ {
+ citem->aboutToSave();
+ if (citem->loadLast())
+ hasFinalLoad = true;
+ }
+ }
+
+ QStringList groups = cfg->groupList();
+
+ // wipe out all groups from this hole
+ for (QStringList::Iterator it = groups.begin(); it != groups.end(); ++it)
+ {
+ int holeNum = (*it).left((*it).find("-")).toInt();
+ if (holeNum == curHole)
+ cfg->deleteGroup(*it);
+ }
+ for (item = items.first(); item; item = items.next())
+ {
+ CanvasItem *citem = dynamic_cast<CanvasItem *>(item);
+ if (citem)
+ {
+ citem->clean();
+
+ cfg->setGroup(makeGroup(citem->curId(), curHole, citem->name(), (int)item->x(), (int)item->y()));
+ citem->save(cfg);
+ }
+ }
+
+ // save where ball starts (whiteBall tells all)
+ cfg->setGroup(QString("%1-ball@%2,%3").arg(curHole).arg((int)whiteBall->x()).arg((int)whiteBall->y()));
+ cfg->writeEntry("dummykey", true);
+
+ cfg->setGroup("0-course@-50,-50");
+ cfg->writeEntry("author", holeInfo.author());
+ cfg->writeEntry("Name", holeInfo.untranslatedName());
+
+ // save hole info
+ cfg->setGroup(QString("%1-hole@-50,-50|0").arg(curHole));
+ cfg->writeEntry("par", holeInfo.par());
+ cfg->writeEntry("maxstrokes", holeInfo.maxStrokes());
+ cfg->writeEntry("borderWalls", holeInfo.borderWalls());
+ cfg->writeEntry("hasFinalLoad", hasFinalLoad);
+
+ cfg->sync();
+
+ for (item = items.first(); item; item = items.next())
+ {
+ CanvasItem *citem = dynamic_cast<CanvasItem *>(item);
+ if (citem)
+ citem->savingDone();
+ }
+
+ setModified(false);
+}
+
+void KolfGame::toggleEditMode()
+{
+ // won't be editing anymore, and user wants to cancel, we return
+ // this is pretty useless. when the person leaves the hole,
+ // he gets asked again
+ /*
+ if (editing && modified)
+ {
+ if (askSave(false))
+ {
+ emit checkEditing();
+ return;
+ }
+ }
+ */
+
+ moving = false;
+ selectedItem = 0;
+
+ editing = !editing;
+
+ if (editing)
+ {
+ emit editingStarted();
+ emit newSelectedItem(&holeInfo);
+ }
+ else
+ {
+ emit editingEnded();
+ setCursor(KCursor::arrowCursor());
+ }
+
+ // alert our items
+ QCanvasItem *item = 0;
+ for (item = items.first(); item; item = items.next())
+ {
+ CanvasItem *citem = dynamic_cast<CanvasItem *>(item);
+ if (citem)
+ citem->editModeChanged(editing);
+ }
+
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ {
+ // curplayer shouldn't be hidden no matter what
+ if ((*it).ball()->beginningOfHole() && it != curPlayer)
+ (*it).ball()->setVisible(false);
+ else
+ (*it).ball()->setVisible(!editing);
+ }
+
+ whiteBall->setVisible(editing);
+ highlighter->setVisible(false);
+
+ // shouldn't see putter whilst editing
+ putter->setVisible(!editing);
+
+ if (editing)
+ autoSaveTimer->start(autoSaveMsec);
+ else
+ autoSaveTimer->stop();
+
+ inPlay = false;
+}
+
+void KolfGame::playSound(QString file, double vol)
+{
+ if (m_sound)
+ {
+ KPlayObject *oldPlayObject = 0;
+ for (oldPlayObject = oldPlayObjects.first(); oldPlayObject; oldPlayObject = oldPlayObjects.next())
+ {
+ if (oldPlayObject && oldPlayObject->state() != Arts::posPlaying)
+ {
+ oldPlayObjects.remove();
+
+ // because we will go to next() next time
+ // and after remove current item is one after
+ // removed item
+ (void) oldPlayObjects.prev();
+ }
+ }
+
+ file = soundDir + file + QString::fromLatin1(".wav");
+
+ // not needed when all of the files are in the distribution
+ //if (!QFile::exists(file))
+ //return;
+
+ KPlayObjectFactory factory(artsServer.server());
+ KPlayObject *playObject = factory.createPlayObject(KURL(file), true);
+
+ if (playObject && !playObject->isNull())
+ {
+ if (vol > 1)
+ vol = 1;
+ else if (vol <= .01)
+ {
+ delete playObject;
+ return;
+ }
+
+ if (vol < .99)
+ {
+ //new KVolumeControl(vol, artsServer.server(), playObject);
+ }
+
+ playObject->play();
+ oldPlayObjects.append(playObject);
+ }
+ }
+}
+
+void HoleInfo::borderWallsChanged(bool yes)
+{
+ m_borderWalls = yes;
+ game->setBorderWalls(yes);
+}
+
+void KolfGame::print(KPrinter &pr)
+{
+ QPainter p(&pr);
+
+ QPaintDeviceMetrics metrics(&pr);
+
+ // translate to center
+ p.translate(metrics.width() / 2 - course->rect().width() / 2, metrics.height() / 2 - course->rect().height() / 2);
+
+ QPixmap pix(width, height);
+ QPainter pixp(&pix);
+ course->drawArea(course->rect(), &pixp);
+ p.drawPixmap(0, 0, pix);
+
+ p.setPen(QPen(black, 2));
+ p.drawRect(course->rect());
+
+ p.resetXForm();
+
+ if (pr.option("kde-kolf-title") == "true")
+ {
+ QString text = i18n("%1 - Hole %2; by %3").arg(holeInfo.name()).arg(curHole).arg(holeInfo.author());
+ QFont font(kapp->font());
+ font.setPointSize(18);
+ QRect rect = QFontMetrics(font).boundingRect(text);
+ p.setFont(font);
+
+ p.drawText(metrics.width() / 2 - rect.width() / 2, metrics.height() / 2 - course->rect().height() / 2 -20 - rect.height(), text);
+ }
+}
+
+bool KolfGame::allPlayersDone()
+{
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ if ((*it).ball()->curState() != Holed)
+ return false;
+
+ return true;
+}
+
+void KolfGame::setBorderWalls(bool showing)
+{
+ Wall *wall = 0;
+ for (wall = borderWalls.first(); wall; wall = borderWalls.next())
+ wall->setVisible(showing);
+}
+
+void KolfGame::setUseAdvancedPutting(bool yes)
+{
+ m_useAdvancedPutting = yes;
+
+ // increase maxStrength in advanced putting mode
+ if (yes)
+ maxStrength = 65;
+ else
+ maxStrength = 55;
+}
+
+void KolfGame::setShowGuideLine(bool yes)
+{
+ putter->setShowGuideLine(yes);
+}
+
+void KolfGame::setSound(bool yes)
+{
+ m_sound = yes;
+}
+
+void KolfGame::courseInfo(CourseInfo &info, const QString& filename)
+{
+ KConfig cfg(filename);
+ cfg.setGroup("0-course@-50,-50");
+ info.author = cfg.readEntry("author", info.author);
+ info.name = cfg.readEntry("Name", cfg.readEntry("name", info.name));
+ info.untranslatedName = cfg.readEntryUntranslated("Name", cfg.readEntryUntranslated("name", info.name));
+
+ unsigned int hole = 1;
+ unsigned int par= 0;
+ while (1)
+ {
+ QString group = QString("%1-hole@-50,-50|0").arg(hole);
+ if (!cfg.hasGroup(group))
+ {
+ hole--;
+ break;
+ }
+
+ cfg.setGroup(group);
+ par += cfg.readNumEntry("par", 3);
+
+ hole++;
+ }
+
+ info.par = par;
+ info.holes = hole;
+}
+
+void KolfGame::scoresFromSaved(KConfig *config, PlayerList &players)
+{
+ config->setGroup("0 Saved Game");
+ int numPlayers = config->readNumEntry("Players", 0);
+ if (numPlayers <= 0)
+ return;
+
+ for (int i = 1; i <= numPlayers; ++i)
+ {
+ // this is same as in kolf.cpp, but we use saved game values
+ config->setGroup(QString::number(i));
+ players.append(Player());
+ players.last().ball()->setColor(config->readEntry("Color", "#ffffff"));
+ players.last().setName(config->readEntry("Name"));
+ players.last().setId(i);
+
+ QStringList scores(config->readListEntry("Scores"));
+ QValueList<int> intscores;
+ for (QStringList::Iterator it = scores.begin(); it != scores.end(); ++it)
+ intscores.append((*it).toInt());
+
+ players.last().setScores(intscores);
+ }
+}
+
+void KolfGame::saveScores(KConfig *config)
+{
+ // wipe out old player info
+ QStringList groups = config->groupList();
+ for (QStringList::Iterator it = groups.begin(); it != groups.end(); ++it)
+ {
+ // this deletes all int groups, ie, the player info groups
+ bool ok = false;
+ (*it).toInt(&ok);
+ if (ok)
+ config->deleteGroup(*it);
+ }
+
+ config->setGroup("0 Saved Game");
+ config->writeEntry("Players", players->count());
+ config->writeEntry("Course", filename);
+ config->writeEntry("Current Hole", curHole);
+
+ for (PlayerList::Iterator it = players->begin(); it != players->end(); ++it)
+ {
+ config->setGroup(QString::number((*it).id()));
+ config->writeEntry("Name", (*it).name());
+ config->writeEntry("Color", (*it).ball()->color().name());
+
+ QStringList scores;
+ QValueList<int> intscores = (*it).scores();
+ for (QValueList<int>::Iterator it = intscores.begin(); it != intscores.end(); ++it)
+ scores.append(QString::number(*it));
+
+ config->writeEntry("Scores", scores);
+ }
+}
+
+CourseInfo::CourseInfo()
+: name(i18n("Course Name")), author(i18n("Course Author")), holes(0), par(0)
+{
+}
+
+#include "game.moc"
diff --git a/kolf/game.h b/kolf/game.h
new file mode 100644
index 00000000..afb13b3c
--- /dev/null
+++ b/kolf/game.h
@@ -0,0 +1,1026 @@
+#ifndef GAME_H
+#define GAME_H
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kpixmap.h>
+#include <arts/kplayobject.h>
+#include <arts/kartsserver.h>
+#include <arts/kartsdispatcher.h>
+
+#include <math.h>
+
+#include <qcanvas.h>
+#include <qpainter.h>
+#include <qcolor.h>
+#include <qframe.h>
+#include <qlayout.h>
+#include <qmap.h>
+#include <qpen.h>
+#include <qpoint.h>
+#include <qpointarray.h>
+#include <qrect.h>
+#include <qstringlist.h>
+#include <qvaluelist.h>
+
+#include "object.h"
+#include "config.h"
+#include "canvasitem.h"
+#include "ball.h"
+#include "statedb.h"
+#include "rtti.h"
+#include <kdemacros.h>
+
+class QLabel;
+class QSlider;
+class QCheckBox;
+class QTimer;
+class QKeyEvent;
+class QMouseEvent;
+class QPainter;
+class KConfig;
+class KPrinter;
+class KolfGame;
+
+enum Direction { D_Left, D_Right, Forwards, Backwards };
+enum Amount { Amount_Less, Amount_Normal, Amount_More };
+enum HoleResult { Result_Holed, Result_Miss, Result_LipOut };
+
+class Player;
+
+class BallStateInfo
+{
+public:
+ void saveState(KConfig *cfg);
+ void loadState(KConfig *cfg);
+
+ int id;
+ QPoint spot;
+ BallState state;
+ bool beginningOfHole;
+ int score;
+};
+class BallStateList : public QValueList<BallStateInfo>
+{
+public:
+ int hole;
+ int player;
+ bool canUndo;
+ Vector vector;
+};
+
+class Player
+{
+public:
+ Player() : m_ball(new Ball(0)) {};
+ Ball *ball() const { return m_ball; }
+ void setBall(Ball *ball) { m_ball = ball; }
+ BallStateInfo stateInfo(int hole) const { BallStateInfo ret; ret.spot = QPoint(m_ball->x(), m_ball->y()); ret.state = m_ball->curState(); ret.score = score(hole); ret.beginningOfHole = m_ball->beginningOfHole(); ret.id = m_id; return ret; }
+
+ QValueList<int> scores() const { return m_scores; }
+ void setScores(const QValueList<int> &newScores) { m_scores = newScores; }
+ int score(int hole) const { return (*m_scores.at(hole - 1)); }
+ int lastScore() const { return m_scores.last(); }
+ int firstScore() const { return m_scores.first(); }
+
+ void addStrokeToHole(int hole) { (*m_scores.at(hole - 1))++; }
+ void setScoreForHole(int score, int hole) { (*m_scores.at(hole - 1)) = score; }
+ void subtractStrokeFromHole(int hole) { (*m_scores.at(hole - 1))--; }
+ void resetScore(int hole) { (*m_scores.at(hole - 1)) = 0; }
+ void addHole() { m_scores.append(0); }
+ unsigned int numHoles() const { return m_scores.count(); }
+
+ QString name() const { return m_name; }
+ void setName(const QString &name) { m_name = name; m_ball->setName(name); }
+
+ void setId(int id) { m_id = id; }
+ int id() const { return m_id; }
+
+private:
+ Ball *m_ball;
+ QValueList<int> m_scores;
+ QString m_name;
+ int m_id;
+};
+typedef QValueList<Player> PlayerList;
+
+class Arrow : public QCanvasLine
+{
+public:
+ Arrow(QCanvas *canvas);
+ void setAngle(double newAngle) { m_angle = newAngle; }
+ double angle() const { return m_angle; }
+ void setLength(double newLength) { m_length = newLength; }
+ double length() const { return m_length; }
+ void setReversed(bool yes) { m_reversed = yes; }
+ bool reversed() const { return m_reversed; }
+ virtual void setVisible(bool);
+ virtual void setPen(QPen p);
+ void aboutToDie();
+ virtual void moveBy(double, double);
+ void updateSelf();
+ virtual void setZ(double newz);
+
+private:
+ double m_angle;
+ double m_length;
+ bool m_reversed;
+ QCanvasLine *line1;
+ QCanvasLine *line2;
+};
+
+class RectPoint;
+class RectItem
+{
+public:
+ virtual void newSize(int /*width*/, int /*height*/) {};
+};
+
+class RectPoint : public QCanvasEllipse, public CanvasItem
+{
+public:
+ RectPoint(QColor color, RectItem *, QCanvas *canvas);
+ void dontMove() { dontmove = true; }
+ virtual void moveBy(double dx, double dy);
+ virtual Config *config(QWidget *parent);
+ virtual bool deleteable() const { return false; }
+ virtual bool cornerResize() const { return true; }
+ virtual CanvasItem *itemToDelete() { return dynamic_cast<CanvasItem *>(rect); }
+ void setSizeFactor(double newFactor) { m_sizeFactor = newFactor; }
+
+protected:
+ RectItem *rect;
+ double m_sizeFactor;
+
+private:
+ bool dontmove;
+};
+
+class Ellipse : public QCanvasEllipse, public CanvasItem, public RectItem
+{
+public:
+ Ellipse(QCanvas *canvas);
+ virtual void advance(int phase);
+
+ int changeEvery() const { return m_changeEvery; }
+ void setChangeEvery(int news) { m_changeEvery = news; }
+ bool changeEnabled() const { return m_changeEnabled; }
+ void setChangeEnabled(bool news);
+
+ virtual void aboutToDie();
+ virtual void aboutToSave();
+ virtual void savingDone();
+
+ virtual QPtrList<QCanvasItem> moveableItems() const;
+
+ virtual void newSize(int width, int height);
+ virtual void moveBy(double dx, double dy);
+
+ virtual void editModeChanged(bool changed);
+
+ virtual void save(KConfig *cfg);
+ virtual void load(KConfig *cfg);
+
+ virtual Config *config(QWidget *parent);
+
+protected:
+ RectPoint *point;
+ int m_changeEvery;
+ bool m_changeEnabled;
+
+private:
+ int count;
+ bool dontHide;
+};
+class EllipseConfig : public Config
+{
+ Q_OBJECT
+
+public:
+ EllipseConfig(Ellipse *ellipse, QWidget *);
+
+private slots:
+ void value1Changed(int news);
+ void value2Changed(int news);
+ void check1Changed(bool on);
+ void check2Changed(bool on);
+
+protected:
+ QVBoxLayout *m_vlayout;
+
+private:
+ QLabel *slow1;
+ QLabel *fast1;
+ QLabel *slow2;
+ QLabel *fast2;
+ QSlider *slider1;
+ QSlider *slider2;
+ Ellipse *ellipse;
+};
+
+class Puddle : public Ellipse
+{
+public:
+ Puddle(QCanvas *canvas);
+ virtual bool collision(Ball *ball, long int id);
+ virtual int rtti() const { return Rtti_DontPlaceOn; }
+};
+class PuddleObj : public Object
+{
+public:
+ PuddleObj() { m_name = i18n("Puddle"); m__name = "puddle"; }
+ virtual QCanvasItem *newObject(QCanvas *canvas) { return new Puddle(canvas); }
+};
+
+class Sand : public Ellipse
+{
+public:
+ Sand(QCanvas *canvas);
+ virtual bool collision(Ball *ball, long int id);
+};
+class SandObj : public Object
+{
+public:
+ SandObj() { m_name = i18n("Sand"); m__name = "sand"; }
+ virtual QCanvasItem *newObject(QCanvas *canvas) { return new Sand(canvas); }
+};
+
+class Inside : public QCanvasEllipse, public CanvasItem
+{
+public:
+ Inside(CanvasItem *item, QCanvas *canvas) : QCanvasEllipse(canvas) { this->item = item; }
+ virtual bool collision(Ball *ball, long int id) { return item->collision(ball, id); }
+
+protected:
+ CanvasItem *item;
+};
+
+class Bumper : public QCanvasEllipse, public CanvasItem
+{
+public:
+ Bumper(QCanvas *canvas);
+
+ virtual void advance(int phase);
+ virtual void aboutToDie();
+ virtual void moveBy(double dx, double dy);
+ virtual void editModeChanged(bool changed);
+
+ virtual bool collision(Ball *ball, long int id);
+
+protected:
+ QColor firstColor;
+ QColor secondColor;
+ Inside *inside;
+
+private:
+ int count;
+};
+class BumperObj : public Object
+{
+public:
+ BumperObj() { m_name = i18n("Bumper"); m__name = "bumper"; }
+ virtual QCanvasItem *newObject(QCanvas *canvas) { return new Bumper(canvas); }
+};
+
+class Hole : public QCanvasEllipse, public CanvasItem
+{
+public:
+ Hole(QColor color, QCanvas *canvas);
+ virtual bool place(Ball * /*ball*/, bool /*wasCenter*/) { return true; };
+
+ virtual bool collision(Ball *ball, long int id);
+
+protected:
+ virtual HoleResult result(const QPoint, double, bool *wasCenter);
+};
+
+class Cup : public Hole
+{
+public:
+ Cup(QCanvas *canvas);
+ virtual bool place(Ball *ball, bool wasCenter);
+ virtual void save(KConfig *cfg);
+ virtual bool canBeMovedByOthers() const { return true; }
+ virtual void draw(QPainter &painter);
+
+protected:
+ QPixmap pixmap;
+};
+class CupObj : public Object
+{
+public:
+ CupObj() { m_name = i18n("Cup"); m__name = "cup"; m_addOnNewHole = true; }
+ virtual QCanvasItem *newObject(QCanvas *canvas) { return new Cup(canvas); }
+};
+
+class BlackHole;
+class BlackHoleConfig : public Config
+{
+ Q_OBJECT
+
+public:
+ BlackHoleConfig(BlackHole *blackHole, QWidget *parent);
+
+private slots:
+ void degChanged(int);
+ void minChanged(double);
+ void maxChanged(double);
+
+private:
+ BlackHole *blackHole;
+};
+class BlackHoleExit : public QCanvasLine, public CanvasItem
+{
+public:
+ BlackHoleExit(BlackHole *blackHole, QCanvas *canvas);
+ virtual int rtti() const { return Rtti_NoCollision; }
+ virtual void aboutToDie();
+ virtual void moveBy(double dx, double dy);
+ virtual bool deleteable() const { return false; }
+ virtual bool canBeMovedByOthers() const { return true; }
+ virtual void editModeChanged(bool editing);
+ virtual void setPen(QPen p);
+ virtual void showInfo();
+ virtual void hideInfo();
+ void updateArrowAngle();
+ void updateArrowLength();
+ virtual Config *config(QWidget *parent);
+ BlackHole *blackHole;
+
+protected:
+ Arrow *arrow;
+};
+class BlackHoleTimer : public QObject
+{
+Q_OBJECT
+
+public:
+ BlackHoleTimer(Ball *ball, double speed, int msec);
+
+signals:
+ void eject(Ball *ball, double speed);
+ void halfway();
+
+protected slots:
+ void mySlot();
+ void myMidSlot();
+
+protected:
+ double m_speed;
+ Ball *m_ball;
+};
+class BlackHole : public QObject, public Hole
+{
+Q_OBJECT
+
+public:
+ BlackHole(QCanvas *canvas);
+ virtual bool canBeMovedByOthers() const { return true; }
+ virtual void aboutToDie();
+ virtual void showInfo();
+ virtual void hideInfo();
+ virtual bool place(Ball *ball, bool wasCenter);
+ virtual void save(KConfig *cfg);
+ virtual void load(KConfig *cfg);
+ virtual Config *config(QWidget *parent) { return new BlackHoleConfig(this, parent); }
+ virtual QPtrList<QCanvasItem> moveableItems() const;
+ double minSpeed() const { return m_minSpeed; }
+ double maxSpeed() const { return m_maxSpeed; }
+ void setMinSpeed(double news) { m_minSpeed = news; exitItem->updateArrowLength(); }
+ void setMaxSpeed(double news) { m_maxSpeed = news; exitItem->updateArrowLength(); }
+
+ int curExitDeg() const { return exitDeg; }
+ void setExitDeg(int newdeg);
+
+ virtual void editModeChanged(bool editing) { exitItem->editModeChanged(editing); }
+ void updateInfo();
+
+ virtual void shotStarted() { runs = 0; };
+
+ virtual void moveBy(double dx, double dy);
+
+public slots:
+ void eject(Ball *ball, double speed);
+ void halfway();
+
+protected:
+ int exitDeg;
+ BlackHoleExit *exitItem;
+ double m_minSpeed;
+ double m_maxSpeed;
+
+private:
+ int runs;
+ QCanvasLine *infoLine;
+ QCanvasEllipse *outside;
+ void finishMe();
+};
+class BlackHoleObj : public Object
+{
+public:
+ BlackHoleObj() { m_name = i18n("Black Hole"); m__name = "blackhole"; }
+ virtual QCanvasItem *newObject(QCanvas *canvas) { return new BlackHole(canvas); }
+};
+
+class WallPoint;
+class Wall : public QCanvasLine, public CanvasItem
+{
+public:
+ Wall(QCanvas *canvas);
+ virtual void aboutToDie();
+ double dampening;
+
+ void setAlwaysShow(bool yes);
+ virtual void setZ(double newz);
+ virtual void setPen(QPen p);
+ virtual bool collision(Ball *ball, long int id);
+ virtual void save(KConfig *cfg);
+ virtual void load(KConfig *cfg);
+ virtual void selectedItem(QCanvasItem *item);
+ virtual void editModeChanged(bool changed);
+ virtual void moveBy(double dx, double dy);
+ virtual void setVelocity(double vx, double vy);
+ virtual void clean();
+
+ // must reimp because we gotta move the end items,
+ // and we do that in moveBy()
+ virtual void setPoints(int xa, int ya, int xb, int yb) { QCanvasLine::setPoints(xa, ya, xb, yb); moveBy(0, 0); }
+
+ virtual int rtti() const { return Rtti_Wall; }
+ virtual QPtrList<QCanvasItem> moveableItems() const;
+ virtual void setGame(KolfGame *game);
+ virtual void setVisible(bool);
+
+ virtual QPointArray areaPoints() const;
+
+protected:
+ WallPoint *startItem;
+ WallPoint *endItem;
+ bool editing;
+
+private:
+ long int lastId;
+
+ friend class WallPoint;
+};
+class WallPoint : public QCanvasEllipse, public CanvasItem
+{
+public:
+ WallPoint(bool start, Wall *wall, QCanvas *canvas);
+ void setAlwaysShow(bool yes) { alwaysShow = yes; updateVisible(); }
+ virtual void editModeChanged(bool changed);
+ virtual void moveBy(double dx, double dy);
+ virtual int rtti() const { return Rtti_WallPoint; }
+ virtual bool deleteable() const { return false; }
+ virtual bool collision(Ball *ball, long int id);
+ virtual CanvasItem *itemToDelete() { return wall; }
+ virtual void clean();
+ virtual Config *config(QWidget *parent) { return wall->config(parent); }
+ void dontMove() { dontmove = true; };
+ void updateVisible();
+
+ Wall *parentWall() { return wall; }
+
+protected:
+ Wall *wall;
+ bool editing;
+ bool visible;
+
+private:
+ bool alwaysShow;
+ bool start;
+ bool dontmove;
+ long int lastId;
+
+ friend class Wall;
+};
+class WallObj : public Object
+{
+public:
+ WallObj() { m_name = i18n("Wall"); m__name = "wall"; }
+ virtual QCanvasItem *newObject(QCanvas *canvas) { return new Wall(canvas); }
+};
+
+class Putter : public QCanvasLine, public CanvasItem
+{
+public:
+ Putter(QCanvas *canvas);
+ void go(Direction, Amount amount = Amount_Normal);
+ void setOrigin(int x, int y);
+ int curLen() const { return len; }
+ double curAngle() const { return angle; }
+ int curDeg() const { return rad2deg(angle); }
+ virtual void showInfo();
+ virtual void hideInfo();
+ void setAngle(double news) { angle = news; finishMe(); }
+ void setDeg(int news) { angle = deg2rad(news); finishMe(); }
+ double curMaxAngle() const { return maxAngle; }
+ virtual int rtti() const { return Rtti_Putter; }
+ virtual void setVisible(bool yes);
+ void saveAngle(Ball *ball) { angleMap[ball] = angle; }
+ void setAngle(Ball *ball);
+ void resetAngles() { angleMap.clear(); setZ(999999); }
+ virtual bool canBeMovedByOthers() const { return true; }
+ virtual void moveBy(double dx, double dy);
+ void setShowGuideLine(bool yes);
+
+private:
+ QPoint midPoint;
+ double maxAngle;
+ double angle;
+ double oneDegree;
+ QMap<Ball *, double> angleMap;
+ int len;
+ void finishMe();
+ int putterWidth;
+ QCanvasLine *guideLine;
+ bool m_showGuideLine;
+};
+
+class Bridge;
+class BridgeConfig : public Config
+{
+ Q_OBJECT
+
+public:
+ BridgeConfig(Bridge *bridge, QWidget *);
+
+protected slots:
+ void topWallChanged(bool);
+ void botWallChanged(bool);
+ void leftWallChanged(bool);
+ void rightWallChanged(bool);
+
+protected:
+ QVBoxLayout *m_vlayout;
+ QCheckBox *top;
+ QCheckBox *bot;
+ QCheckBox *left;
+ QCheckBox *right;
+
+private:
+ Bridge *bridge;
+};
+class Bridge : public QCanvasRectangle, public CanvasItem, public RectItem
+{
+public:
+ Bridge(QRect rect, QCanvas *canvas);
+ virtual bool collision(Ball *ball, long int id);
+ virtual void aboutToDie();
+ virtual void editModeChanged(bool changed);
+ virtual void moveBy(double dx, double dy);
+ virtual void load(KConfig *cfg);
+ virtual void save(KConfig *cfg);
+ virtual bool vStrut() const { return true; }
+ void doLoad(KConfig *cfg);
+ void doSave(KConfig *cfg);
+ virtual void newSize(int width, int height);
+ virtual void setGame(KolfGame *game);
+ virtual Config *config(QWidget *parent) { return new BridgeConfig(this, parent); }
+ void setSize(int width, int height);
+ virtual QPtrList<QCanvasItem> moveableItems() const;
+
+ void setWallColor(QColor color);
+ QPen wallPen() const { return topWall->pen(); }
+
+ double wallZ() const { return topWall->z(); }
+ void setWallZ(double);
+
+ void setTopWallVisible(bool yes) { topWall->setVisible(yes); }
+ void setBotWallVisible(bool yes) { botWall->setVisible(yes); }
+ void setLeftWallVisible(bool yes) { leftWall->setVisible(yes); }
+ void setRightWallVisible(bool yes) { rightWall->setVisible(yes); }
+ bool topWallVisible() const { return topWall->isVisible(); }
+ bool botWallVisible() const { return botWall->isVisible(); }
+ bool leftWallVisible() const { return leftWall->isVisible(); }
+ bool rightWallVisible() const { return rightWall->isVisible(); }
+
+protected:
+ Wall *topWall;
+ Wall *botWall;
+ Wall *leftWall;
+ Wall *rightWall;
+ RectPoint *point;
+};
+class BridgeObj : public Object
+{
+public:
+ BridgeObj() { m_name = i18n("Bridge"); m__name = "bridge"; }
+ virtual QCanvasItem *newObject(QCanvas *canvas) { return new Bridge(QRect(0, 0, 80, 40), canvas); }
+};
+
+class Sign;
+class SignConfig : public BridgeConfig
+{
+ Q_OBJECT
+
+public:
+ SignConfig(Sign *sign, QWidget *parent);
+
+private slots:
+ void textChanged(const QString &);
+
+private:
+ Sign *sign;
+};
+class Sign : public Bridge
+{
+public:
+ Sign(QCanvas *canvas);
+ void setText(const QString &text);
+ QString text() const { return m_text; }
+ virtual void draw(QPainter &painter);
+ virtual bool vStrut() const { return false; }
+ virtual Config *config(QWidget *parent) { return new SignConfig(this, parent); }
+ virtual void save(KConfig *cfg);
+ virtual void load(KConfig *cfg);
+
+protected:
+ QString m_text;
+ QString m_untranslatedText;
+};
+class SignObj : public Object
+{
+public:
+ SignObj() { m_name = i18n("Sign"); m__name = "sign"; }
+ virtual QCanvasItem *newObject(QCanvas *canvas) { return new Sign(canvas); }
+};
+
+class Windmill;
+class WindmillGuard : public Wall
+{
+public:
+ WindmillGuard(QCanvas *canvas) : Wall(canvas) {};
+ void setBetween(int newmin, int newmax) { max = newmax; min = newmin; }
+ virtual void advance(int phase);
+
+protected:
+ int max;
+ int min;
+};
+class WindmillConfig : public BridgeConfig
+{
+ Q_OBJECT
+
+public:
+ WindmillConfig(Windmill *windmill, QWidget *parent);
+
+private slots:
+ void speedChanged(int news);
+ void endChanged(bool yes);
+
+private:
+ Windmill *windmill;
+};
+class Windmill : public Bridge
+{
+public:
+ Windmill(QRect rect, QCanvas *canvas);
+ virtual void aboutToDie();
+ virtual void newSize(int width, int height);
+ virtual void save(KConfig *cfg);
+ virtual void load(KConfig *cfg);
+ virtual void setGame(KolfGame *game);
+ virtual Config *config(QWidget *parent) { return new WindmillConfig(this, parent); }
+ void setSize(int width, int height);
+ virtual void moveBy(double dx, double dy);
+ void setSpeed(int news);
+ int curSpeed() const { return speed; }
+ void setBottom(bool yes);
+ bool bottom() const { return m_bottom; }
+
+protected:
+ WindmillGuard *guard;
+ Wall *left;
+ Wall *right;
+ int speedfactor;
+ int speed;
+ bool m_bottom;
+};
+class WindmillObj : public Object
+{
+public:
+ WindmillObj() { m_name = i18n("Windmill"); m__name = "windmill"; }
+ virtual QCanvasItem *newObject(QCanvas *canvas) { return new Windmill(QRect(0, 0, 80, 40), canvas); }
+};
+
+class HoleInfo;
+class HoleConfig : public Config
+{
+ Q_OBJECT
+
+public:
+ HoleConfig(HoleInfo *holeInfo, QWidget *);
+
+private slots:
+ void authorChanged(const QString &);
+ void parChanged(int);
+ void maxStrokesChanged(int);
+ void nameChanged(const QString &);
+ void borderWallsChanged(bool);
+
+private:
+ HoleInfo *holeInfo;
+};
+class HoleInfo : public CanvasItem
+{
+public:
+ HoleInfo() { m_lowestMaxStrokes = 4; }
+ virtual ~HoleInfo() {}
+ void setPar(int newpar) { m_par = newpar; }
+ int par() const { return m_par; }
+ void setMaxStrokes(int newMaxStrokes) { m_maxStrokes = newMaxStrokes; }
+ int lowestMaxStrokes() const { return m_lowestMaxStrokes; }
+ int maxStrokes() const { return m_maxStrokes; }
+ bool hasMaxStrokes() const { return m_maxStrokes != m_lowestMaxStrokes; }
+ void setAuthor(QString newauthor) { m_author = newauthor; }
+ QString author() const { return m_author; }
+
+ void setName(QString newname) { m_name = newname; }
+ void setUntranslatedName(QString newname) { m_untranslatedName = newname; }
+ QString name() const { return m_name; }
+ QString untranslatedName() const { return m_untranslatedName; }
+
+ virtual Config *config(QWidget *parent) { return new HoleConfig(this, parent); }
+ void borderWallsChanged(bool yes);
+ bool borderWalls() const { return m_borderWalls; }
+
+private:
+ QString m_author;
+ QString m_name;
+ QString m_untranslatedName;
+ bool m_borderWalls;
+ int m_par;
+ int m_maxStrokes;
+ int m_lowestMaxStrokes;
+};
+
+class StrokeCircle : public QCanvasItem
+{
+public:
+ StrokeCircle(QCanvas *canvas);
+
+ void setValue(double v);
+ double value();
+ void setMaxValue(double m);
+ void setSize(int w, int h);
+ void setThickness(int t);
+ int thickness() const;
+ int width() const;
+ int height() const;
+ virtual void draw(QPainter &p);
+ virtual QRect boundingRect() const;
+ virtual bool collidesWith(const QCanvasItem*) const;
+ virtual bool collidesWith(const QCanvasSprite*, const QCanvasPolygonalItem*, const QCanvasRectangle*, const QCanvasEllipse*, const QCanvasText*) const;
+
+private:
+ double dvalue, dmax;
+ int ithickness, iwidth, iheight;
+};
+
+struct KDE_EXPORT CourseInfo
+{
+ CourseInfo(const QString &_name, const QString &_untranslatedName, const QString &_author, unsigned int _holes, unsigned int _par) { name = _name; untranslatedName = _untranslatedName, author = _author; holes = _holes; par = _par; }
+ CourseInfo();
+
+ QString name;
+ QString untranslatedName;
+ QString author;
+ unsigned int holes;
+ unsigned int par;
+};
+
+class KDE_EXPORT KolfGame : public QCanvasView
+{
+ Q_OBJECT
+
+public:
+ KolfGame(ObjectList *obj, PlayerList *players, QString filename, QWidget *parent=0, const char *name=0 );
+ ~KolfGame();
+ void setObjects(ObjectList *obj) { this->obj = obj; }
+ void setFilename(const QString &filename);
+ QString curFilename() const { return filename; }
+ void emitLargestHole() { emit largestHole(highestHole); }
+ QCanvas *canvas() const { return course; }
+ void removeItem(QCanvasItem *item) { items.setAutoDelete(false); items.removeRef(item); }
+ // returns whether it was a cancel
+ bool askSave(bool);
+ bool isEditing() const { return editing; }
+ int currentHole() { return curHole; }
+ void setStrict(bool yes) { strict = yes; }
+ // returns true when you shouldn't do anything
+ bool isPaused() const { return paused; }
+ Ball *curBall() const { return (*curPlayer).ball(); }
+ void updateMouse();
+ void ballMoved();
+ void updateHighlighter();
+ void updateCourse() { course->update(); }
+ QCanvasItem *curSelectedItem() const { return selectedItem; }
+ void setBorderWalls(bool);
+ void setInPlay(bool yes) { inPlay = yes; }
+ bool isInPlay() { return inPlay; }
+ bool isInfoShowing() { return m_showInfo; }
+ void stoppedBall();
+ QString courseName() const { return holeInfo.name(); }
+ void hidePutter() { putter->setVisible(false); }
+ void ignoreEvents(bool ignore) { m_ignoreEvents = ignore; }
+
+ static void scoresFromSaved(KConfig *, PlayerList &players);
+ static void courseInfo(CourseInfo &info, const QString &filename);
+
+public slots:
+ void pause();
+ void unPause();
+ void save();
+ void toggleEditMode();
+ void setModified(bool mod = true);
+ void addNewObject(Object *newObj);
+ void addNewHole();
+ void switchHole(int);
+ void switchHole(const QString &);
+ void nextHole();
+ void prevHole();
+ void firstHole();
+ void lastHole();
+ void randHole();
+ void playSound(QString file, double vol = 1);
+ void showInfoDlg(bool = false);
+ void resetHole();
+ void clearHole();
+ void print(KPrinter &);
+ void setShowInfo(bool yes);
+ void toggleShowInfo();
+ void updateShowInfo();
+ void setUseMouse(bool yes) { m_useMouse = yes; }
+ void setUseAdvancedPutting(bool yes);
+ void setShowGuideLine(bool yes);
+ void setSound(bool yes);
+ void undoShot();
+ void timeout();
+ void saveScores(KConfig *);
+ void startFirstHole(int hole);
+ void sayWhosGoing();
+
+signals:
+ void holesDone();
+ void newHole(int);
+ void parChanged(int, int);
+ void titleChanged(const QString &);
+ void largestHole(int);
+ void scoreChanged(int, int, int);
+ void newPlayersTurn(Player *);
+ void playerHoled(Player *);
+ void newSelectedItem(CanvasItem *);
+ void checkEditing();
+ void editingStarted();
+ void editingEnded();
+ void inPlayStart();
+ void inPlayEnd();
+ void maxStrokesReached(const QString &);
+ void currentHole(int);
+ void modifiedChanged(bool);
+ void newStatusText(const QString &);
+
+private slots:
+ void shotDone();
+ void holeDone();
+ void startNextHole();
+ void fastTimeout();
+ void putterTimeout();
+ void autoSaveTimeout();
+ void addItemsToMoveableList(QPtrList<QCanvasItem>);
+ void addItemToFastAdvancersList(CanvasItem *);
+ void hideInfo();
+
+ void emitMax();
+
+protected:
+ void mouseMoveEvent(QMouseEvent *e);
+ void mousePressEvent(QMouseEvent *e);
+ void mouseReleaseEvent(QMouseEvent *e);
+ void mouseDoubleClickEvent(QMouseEvent *e);
+
+ void handleMousePressEvent(QMouseEvent *e);
+ void handleMouseDoubleClickEvent(QMouseEvent *e);
+ void handleMouseMoveEvent(QMouseEvent *e);
+ void handleMouseReleaseEvent(QMouseEvent *e);
+ void keyPressEvent(QKeyEvent *e);
+ void keyReleaseEvent(QKeyEvent *e);
+
+ QPoint viewportToViewport(const QPoint &p);
+
+private:
+ QCanvas *course;
+ Putter *putter;
+ PlayerList *players;
+ PlayerList::Iterator curPlayer;
+ Ball *whiteBall;
+ StrokeCircle *strokeCircle;
+
+ QTimer *timer;
+ QTimer *autoSaveTimer;
+ QTimer *fastTimer;
+ QTimer *putterTimer;
+ bool regAdv;
+
+ ObjectList *obj;
+ QPtrList<QCanvasItem> items;
+ QPtrList<QCanvasItem> extraMoveable;
+ QPtrList<Wall> borderWalls;
+
+ int timerMsec;
+ int autoSaveMsec;
+ int fastTimerMsec;
+ int putterTimerMsec;
+
+ void puttPress();
+ void puttRelease();
+ bool inPlay;
+ bool putting;
+ bool stroking;
+ bool finishStroking;
+ double strength;
+ double maxStrength;
+ int puttCount;
+ bool puttReverse;
+
+ int curHole;
+ int highestHole;
+ int curPar;
+
+ int wallWidth;
+ int height;
+ int width;
+ int margin;
+ QColor grass;
+
+ int advancePeriod;
+
+ int lastDelId;
+
+ bool paused;
+
+ QString filename;
+ bool recalcHighestHole;
+ void openFile();
+
+ bool strict;
+
+ bool editing;
+ QPoint storedMousePos;
+ bool moving;
+ bool dragging;
+ QCanvasItem *movingItem;
+ QCanvasItem *selectedItem;
+ QCanvasRectangle *highlighter;
+
+ // sound
+ KArtsDispatcher artsDispatcher;
+ KArtsServer artsServer;
+ QPtrList<KPlayObject> oldPlayObjects;
+ bool m_sound;
+ bool soundedOnce;
+ QString soundDir;
+
+ bool m_ignoreEvents;
+
+ HoleInfo holeInfo;
+ QCanvasText *infoText;
+ void showInfo();
+ StateDB stateDB;
+
+ BallStateList ballStateList;
+ void loadStateList();
+ void recreateStateList();
+ void addHoleInfo(BallStateList &list);
+
+ bool dontAddStroke;
+
+ bool addingNewHole;
+ int scoreboardHoles;
+ inline void resetHoleScores();
+
+ bool m_showInfo;
+
+ bool infoShown;
+
+ KConfig *cfg;
+
+ inline void addBorderWall(QPoint start, QPoint end);
+ void shotStart();
+ void startBall(const Vector &vector);
+
+ bool modified;
+
+ inline bool allPlayersDone();
+
+ bool m_useMouse;
+ bool m_useAdvancedPutting;
+
+ QPtrList<CanvasItem> fastAdvancers;
+ bool fastAdvancedExist;
+
+ QString playerWhoMaxed;
+};
+
+#endif
diff --git a/kolf/graphics/Makefile.am b/kolf/graphics/Makefile.am
new file mode 100644
index 00000000..45bda9e0
--- /dev/null
+++ b/kolf/graphics/Makefile.am
@@ -0,0 +1,4 @@
+picdir = $(kde_datadir)/kolf/pics/
+pic_DATA = cup.png grass.png puddle.png sand.png
+
+EXTRA_DIST=$(pic_DATA)
diff --git a/kolf/graphics/cup.png b/kolf/graphics/cup.png
new file mode 100644
index 00000000..2da6c70a
--- /dev/null
+++ b/kolf/graphics/cup.png
Binary files differ
diff --git a/kolf/graphics/grass.png b/kolf/graphics/grass.png
new file mode 100644
index 00000000..c49475c7
--- /dev/null
+++ b/kolf/graphics/grass.png
Binary files differ
diff --git a/kolf/graphics/puddle.png b/kolf/graphics/puddle.png
new file mode 100644
index 00000000..f8ecd7f7
--- /dev/null
+++ b/kolf/graphics/puddle.png
Binary files differ
diff --git a/kolf/graphics/sand.png b/kolf/graphics/sand.png
new file mode 100644
index 00000000..860e1099
--- /dev/null
+++ b/kolf/graphics/sand.png
Binary files differ
diff --git a/kolf/intro b/kolf/intro
new file mode 100644
index 00000000..6377a8a0
--- /dev/null
+++ b/kolf/intro
@@ -0,0 +1,283 @@
+[0-course@-50,-50]
+Name=Intro
+author=Jason Katz-Brown
+name=Intro
+
+[1-ball@258,281]
+dummykey=true
+
+[1-blackhole@126,146|6]
+exit=385,3
+exitDeg=270
+maxspeed=5
+minspeed=5
+
+[1-blackhole@258,282|5]
+exit=3,19
+exitDeg=0
+maxspeed=3.7
+minspeed=3.7
+
+[1-blackhole@48,356|7]
+exit=3,281
+exitDeg=0
+maxspeed=4
+minspeed=4
+
+[1-bridge@-15,261|13]
+botWallVisible=true
+height=43
+leftWallVisible=false
+rightWallVisible=true
+topWallVisible=true
+width=300
+
+[1-bridge@23,339|14]
+botWallVisible=true
+height=35
+leftWallVisible=true
+rightWallVisible=false
+topWallVisible=true
+width=232
+
+[1-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=4
+par=3
+
+[1-puddle@-121,390|12]
+changeEnabled=false
+changeEvery=50
+height=354
+width=750
+
+[1-sand@268,-7|16]
+changeEnabled=false
+changeEvery=50
+height=110
+width=124
+
+[1-sand@296,60|15]
+changeEnabled=false
+changeEvery=50
+height=186
+width=90
+
+[1-sand@300,107|17]
+changeEnabled=false
+changeEvery=50
+height=134
+width=104
+
+[1-sand@329,6|24]
+changeEnabled=false
+changeEvery=50
+height=118
+width=22
+
+[1-sand@340,83|25]
+changeEnabled=false
+changeEvery=50
+height=54
+width=10
+
+[1-slope@132,237|28]
+grade=4
+gradient=Vertical
+height=23
+reversed=false
+stuckOnGround=false
+width=150
+
+[1-slope@147,263|10]
+grade=5
+gradient=Horizontal
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[1-slope@16,22|3]
+grade=6
+gradient=Elliptic
+height=221
+reversed=true
+stuckOnGround=true
+width=221
+
+[1-slope@163,-9|18]
+grade=5
+gradient=Opposite Diagonal
+height=118
+reversed=true
+stuckOnGround=true
+width=105
+
+[1-slope@193,263|11]
+grade=4
+gradient=Horizontal
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[1-slope@206,305|32]
+grade=4
+gradient=Opposite Diagonal
+height=19
+reversed=true
+stuckOnGround=false
+width=27
+
+[1-slope@222,143|20]
+grade=5
+gradient=Opposite Diagonal
+height=64
+reversed=true
+stuckOnGround=true
+width=60
+
+[1-slope@225,63|19]
+grade=5
+gradient=Horizontal
+height=80
+reversed=false
+stuckOnGround=true
+width=34
+
+[1-slope@231,304|30]
+grade=4
+gradient=Vertical
+height=21
+reversed=true
+stuckOnGround=false
+width=53
+
+[1-slope@232,224|6]
+grade=7
+gradient=Diagonal
+height=177
+reversed=false
+stuckOnGround=true
+width=170
+
+[1-slope@281,234|27]
+grade=4
+gradient=Opposite Diagonal
+height=28
+reversed=false
+stuckOnGround=false
+width=43
+
+[1-slope@283,262|26]
+grade=4
+gradient=Horizontal
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[1-slope@284,301|29]
+grade=4
+gradient=Diagonal
+height=23
+reversed=true
+stuckOnGround=false
+width=37
+
+[1-slope@286,141|21]
+grade=5
+gradient=Vertical
+height=67
+reversed=true
+stuckOnGround=true
+width=40
+
+[1-slope@322,-13|23]
+grade=5
+gradient=Horizontal
+height=127
+reversed=true
+stuckOnGround=true
+width=51
+
+[1-slope@325,110|22]
+grade=5
+gradient=Diagonal
+height=98
+reversed=true
+stuckOnGround=true
+width=48
+
+[1-slope@43,263|8]
+grade=5
+gradient=Horizontal
+height=40
+reversed=false
+stuckOnGround=false
+width=40
+
+[1-slope@88,263|9]
+grade=4
+gradient=Horizontal
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[1-slope@92,236|31]
+grade=4
+gradient=Diagonal
+height=23
+reversed=false
+stuckOnGround=false
+width=40
+
+[1-wall@0,0|33]
+endPoint=276,8
+startPoint=276,42
+
+[1-wall@0,0|34]
+endPoint=279,26
+startPoint=301,9
+
+[1-wall@0,0|35]
+endPoint=306,43
+startPoint=279,26
+
+[1-wall@0,0|36]
+endPoint=298,46
+startPoint=273,65
+
+[1-wall@0,0|37]
+endPoint=298,46
+startPoint=313,69
+
+[1-wall@0,0|38]
+endPoint=313,69
+startPoint=291,85
+
+[1-wall@0,0|39]
+endPoint=291,85
+startPoint=273,65
+
+[1-wall@0,0|40]
+endPoint=278,82
+startPoint=279,109
+
+[1-wall@0,0|41]
+endPoint=279,109
+startPoint=310,108
+
+[1-wall@0,0|42]
+endPoint=279,120
+startPoint=281,155
+
+[1-wall@0,0|43]
+endPoint=279,120
+startPoint=309,119
+
+[1-wall@0,0|44]
+endPoint=282,136
+startPoint=305,136
diff --git a/kolf/kcomboboxdialog.cpp b/kolf/kcomboboxdialog.cpp
new file mode 100644
index 00000000..03e0701b
--- /dev/null
+++ b/kolf/kcomboboxdialog.cpp
@@ -0,0 +1,152 @@
+// Copyright (C) 2002 Jason Katz-Brown <jason@katzbrown.com>
+// Copyright (C) 2002 Neil Stevens <neil@qualityassistant.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// THE AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+// AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// Except as contained in this notice, the name(s) of the author(s) shall not be
+// used in advertising or otherwise to promote the sale, use or other dealings
+// in this Software without prior written authorization from the author(s).
+
+#include <qcheckbox.h>
+#include <qlabel.h>
+#include <qlayout.h>
+
+#include <klocale.h>
+#include <kcombobox.h>
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kglobal.h>
+
+#include "kcomboboxdialog.h"
+
+KComboBoxDialog::KComboBoxDialog( const QString &_text, const QStringList &_items, const QString& _value, bool showDontAskAgain, QWidget *parent )
+ : KDialogBase( Plain, QString::null, Ok, Ok, parent, 0L, true, true )
+{
+ QVBoxLayout *topLayout = new QVBoxLayout( plainPage(), marginHint(), spacingHint() );
+ QLabel *label = new QLabel(_text, plainPage() );
+ topLayout->addWidget( label, 1 );
+
+ combo = new KHistoryCombo( plainPage() );
+ combo->setEditable(false);
+ combo->insertStringList( _items );
+ topLayout->addWidget( combo, 1 );
+
+ if (showDontAskAgain)
+ {
+ dontAskAgainCheckBox = new QCheckBox( i18n("&Do not ask again"), plainPage() );
+ topLayout->addWidget( dontAskAgainCheckBox, 1 );
+ }
+ else
+ dontAskAgainCheckBox = 0;
+
+ if ( !_value.isNull() )
+ combo->setCurrentText( _value );
+ combo->setFocus();
+}
+
+KComboBoxDialog::~KComboBoxDialog()
+{
+}
+
+QString KComboBoxDialog::text() const
+{
+ return combo->currentText();
+}
+
+bool KComboBoxDialog::dontAskAgainChecked()
+{
+ if (dontAskAgainCheckBox)
+ return dontAskAgainCheckBox->isChecked();
+
+ return false;
+}
+
+QString KComboBoxDialog::getItem( const QString &_text, const QStringList &_items, const QString& _value, const QString &dontAskAgainName, QWidget *parent )
+{
+ return getItem( _text, QString::null, _items, _value, dontAskAgainName, parent );
+}
+
+QString KComboBoxDialog::getItem( const QString &_text, const QString &_caption, const QStringList &_items, const QString& _value, const QString &dontAskAgainName, QWidget *parent )
+{
+ QString prevAnswer;
+ if ( !dontAskAgainName.isEmpty() )
+ {
+ KConfig *config = KGlobal::config();
+ config->setGroup( "Notification Messages" );
+ prevAnswer = config->readEntry( dontAskAgainName );
+ if ( !prevAnswer.isEmpty() )
+ if ( _items.contains( prevAnswer ) > 0 )
+ return prevAnswer;
+ }
+
+ KComboBoxDialog dlg( _text, _items, _value, !dontAskAgainName.isNull(), parent );
+ if ( !_caption.isNull() )
+ dlg.setCaption( _caption );
+
+ dlg.exec();
+
+ const QString text = dlg.text();
+
+ if (dlg.dontAskAgainChecked())
+ {
+ if ( !dontAskAgainName.isEmpty() && !text.isEmpty() )
+ {
+ KConfig *config = KGlobal::config();
+ config->setGroup ( "Notification Messages" );
+ config->writeEntry( dontAskAgainName, text );
+ }
+ }
+
+ return text;
+}
+
+QString KComboBoxDialog::getText(const QString &_caption, const QString &_text, const QString &_value, bool *ok, QWidget *parent, const QString &configName, KConfig *config)
+{
+ KComboBoxDialog dlg(_text, QStringList(), _value, false, parent);
+ if ( !_caption.isNull() )
+ dlg.setCaption( _caption );
+
+ KHistoryCombo * const box = dlg.comboBox();
+ box->setEditable(true);
+
+ const QString historyItem = QString("%1History").arg(configName);
+ const QString completionItem = QString("%1Completion").arg(configName);
+
+ if(!configName.isNull())
+ {
+ config->setGroup("KComboBoxDialog");
+ box->setHistoryItems(config->readListEntry(historyItem));
+ box->completionObject()->setItems(config->readListEntry(completionItem));
+ }
+
+ bool result = dlg.exec();
+ if(ok) *ok = result;
+
+ if(!configName.isNull() && result)
+ {
+ box->addToHistory(dlg.text());
+ box->completionObject()->addItem(dlg.text());
+ config->setGroup("KComboBoxDialog");
+ config->writeEntry(historyItem, box->historyItems());
+ config->writeEntry(completionItem, box->completionObject()->items());
+ }
+
+ return dlg.text();
+}
+
+#include "kcomboboxdialog.moc"
diff --git a/kolf/kcomboboxdialog.h b/kolf/kcomboboxdialog.h
new file mode 100644
index 00000000..e835c0aa
--- /dev/null
+++ b/kolf/kcomboboxdialog.h
@@ -0,0 +1,114 @@
+// Copyright (C) 2002 Jason Katz-Brown <jason@katzbrown.com>
+// Copyright (C) 2002 Neil Stevens <neil@qualityassistant.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// THE AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+// AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// Except as contained in this notice, the name(s) of the author(s) shall not be
+// used in advertising or otherwise to promote the sale, use or other dealings
+// in this Software without prior written authorization from the author(s).
+
+#ifndef KCOMBOBOX_DIALOG_H
+#define KCOMBOBOX_DIALOG_H
+
+#include <qstringlist.h>
+
+#include <kdialogbase.h>
+#include <kglobal.h>
+
+class QCheckBox;
+class KHistoryCombo;
+
+/**
+ * Dialog for user to choose an item from a QStringList.
+ */
+
+class KComboBoxDialog : public KDialogBase
+{
+Q_OBJECT
+
+public:
+ /**
+ * Create a dialog that asks for a single line of text. _value is
+ * the initial value of the line. _text appears as the current text
+ * of the combobox.
+ *
+ * @param _items Items in the combobox
+ * @param _text Text of the label
+ * @param _value Initial value of the combobox
+ */
+ KComboBoxDialog( const QString &_text, const QStringList& _items, const QString& _value = QString::null, bool showDontAskAgain = false, QWidget *parent = 0 );
+ virtual ~KComboBoxDialog();
+
+ /**
+ * @return the value the user chose
+ */
+ QString text() const;
+
+ /**
+ * @return the line edit widget
+ */
+ KHistoryCombo *comboBox() const { return combo; }
+
+ /**
+ * Static convenience function to get input from the user.
+ *
+ * @param _text Text of the label
+ * @param _items Items in the combobox
+ * @param _value Initial value of the inputline
+ * @param dontAskAgainName Name for saving whether the user doesn't want to be asked again; use QString::null to disable
+ */
+ static QString getItem( const QString &_text, const QStringList &_items, const QString& _value = QString::null, const QString &dontAskAgainName = QString::null, QWidget *parent = 0 );
+
+ /**
+ * Static convenience function to get input from the user.
+ * This method includes a caption.
+ *
+ * @param _caption Caption of the dialog
+ * @param _text Text of the label
+ * @param _items Items in the combobox
+ * @param _value Initial value of the inputline
+ * @param dontAskAgainName Name for saving whether the user doesn't want to be asked again; use QString::null to disable
+ */
+ static QString getItem( const QString &_text, const QString &_caption, const QStringList &_items, const QString& _value = QString::null, const QString &dontAskAgainName = QString::null, QWidget *parent = 0 );
+
+ /**
+ * Static convenience method.
+ * This method is meant as a replacement for KLineEditDlg::getText() for cases
+ * when a history and autocompletion are desired.
+ *
+ * @param _caption Caption of the dialog
+ * @param _text Text of the label
+ * @param _value Initial value of the inputline
+ * @param ok Variable to store whether the user hit OK
+ * @param parent Parent widget for the dialog
+ * @param configName Name of the dialog for saving the completion and history
+ * @parma config KConfig for saving the completion and history
+ */
+ static QString getText(const QString &_caption, const QString &_text,
+ const QString &_value = QString::null,
+ bool *ok = 0, QWidget *parent = 0,
+ const QString &configName = QString::null,
+ KConfig *config = KGlobal::config());
+
+protected:
+ KHistoryCombo *combo;
+ QCheckBox *dontAskAgainCheckBox;
+ bool dontAskAgainChecked();
+};
+
+#endif
diff --git a/kolf/kolf.cpp b/kolf/kolf.cpp
new file mode 100644
index 00000000..33155b6a
--- /dev/null
+++ b/kolf/kolf.cpp
@@ -0,0 +1,815 @@
+#include <kaction.h>
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kdeversion.h>
+#include <kfiledialog.h>
+#include <kglobal.h>
+#include <kio/netaccess.h>
+#include <klocale.h>
+#include <kmainwindow.h>
+#include <kmimetype.h>
+#include <kmessagebox.h>
+#include <kprinter.h>
+#include <kscoredialog.h>
+#include <kstandarddirs.h>
+#include <kstatusbar.h>
+#include <kstdaccel.h>
+#include <kstdaction.h>
+#include <kstdgameaction.h>
+#include <kstdguiitem.h>
+
+#include <qcolor.h>
+#include <qevent.h>
+#include <qfile.h>
+#include <qobject.h>
+#include <qmap.h>
+#include <qpoint.h>
+#include <qtimer.h>
+#include <qptrlist.h>
+#include <qpixmap.h>
+#include <qpixmapcache.h>
+#include <qfileinfo.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qlayout.h>
+#include <qwidget.h>
+
+#include <stdlib.h>
+
+#include "game.h"
+#include "floater.h"
+#include "slope.h"
+#include "newgame.h"
+#include "scoreboard.h"
+#include "editor.h"
+#include "pluginloader.h"
+#include "printdialogpage.h"
+#include "kolf.h"
+
+Kolf::Kolf()
+ : KMainWindow(0, "Kolf")
+{
+ competition = false;
+ game = 0;
+ editor = 0;
+ spacer = 0;
+ scoreboard = 0;
+ isTutorial = false;
+
+ initGUI();
+
+ obj = new ObjectList;
+ initPlugins();
+
+ filename = QString::null;
+ dummy = new QWidget(this);
+ setCentralWidget(dummy);
+ layout = new QGridLayout(dummy, 3, 1);
+
+ resize(420, 480);
+}
+
+Kolf::~Kolf()
+{
+ // wipe out our objects
+ obj->setAutoDelete(true);
+ delete obj;
+}
+
+void Kolf::initGUI()
+{
+ newAction = KStdGameAction::gameNew(this, SLOT(newGame()), actionCollection());
+ newAction->setText(newAction->text() + QString("..."));
+
+ endAction = KStdGameAction::end(this, SLOT(closeGame()), actionCollection());
+ printAction = KStdGameAction::print(this, SLOT(print()), actionCollection());
+
+ (void) KStdGameAction::quit(this, SLOT(close()), actionCollection());
+ saveAction = KStdAction::save(this, SLOT(save()), actionCollection(), "game_save");
+ saveAction->setText(i18n("Save &Course"));
+ saveAsAction = KStdAction::saveAs(this, SLOT(saveAs()), actionCollection(), "game_save_as");
+ saveAsAction->setText(i18n("Save &Course As..."));
+
+ saveGameAction = new KAction(i18n("&Save Game"), 0, this, SLOT(saveGame()), actionCollection(), "savegame");
+ saveGameAsAction = new KAction(i18n("&Save Game As..."), 0, this, SLOT(saveGameAs()), actionCollection(), "savegameas");
+
+ loadGameAction = KStdGameAction::load(this, SLOT(loadGame()), actionCollection());
+ loadGameAction->setText(i18n("Load Saved Game..."));
+
+ highScoreAction = KStdGameAction::highscores(this, SLOT(showHighScores()), actionCollection());
+
+ editingAction = new KToggleAction(i18n("&Edit"), "pencil", CTRL+Key_E, this, SLOT(emptySlot()), actionCollection(), "editing");
+ newHoleAction = new KAction(i18n("&New"), "filenew", CTRL+SHIFT+Key_N, this, SLOT(emptySlot()), actionCollection(), "newhole");
+ clearHoleAction = new KAction(KStdGuiItem::clear().text(), "locationbar_erase", CTRL+Key_Delete, this, SLOT(emptySlot()), actionCollection(), "clearhole");
+ resetHoleAction = new KAction(i18n("&Reset"), CTRL+Key_R, this, SLOT(emptySlot()), actionCollection(), "resethole");
+ undoShotAction = KStdAction::undo(this, SLOT(emptySlot()), actionCollection(), "undoshot");
+ undoShotAction->setText(i18n("&Undo Shot"));
+ //replayShotAction = new KAction(i18n("&Replay Shot"), 0, this, SLOT(emptySlot()), actionCollection(), "replay");
+
+ holeAction = new KListAction(i18n("Switch to Hole"), 0, this, SLOT(emptySlot()), actionCollection(), "switchhole");
+ nextAction = new KAction(i18n("&Next Hole"), "forward", KStdAccel::shortcut(KStdAccel::Forward), this, SLOT(emptySlot()), actionCollection(), "nexthole");
+ prevAction = new KAction(i18n("&Previous Hole"), "back", KStdAccel::shortcut(KStdAccel::Back), this, SLOT(emptySlot()), actionCollection(), "prevhole");
+ firstAction = new KAction(i18n("&First Hole"), "gohome", KStdAccel::shortcut(KStdAccel::Home), this, SLOT(emptySlot()), actionCollection(), "firsthole");
+ lastAction = new KAction(i18n("&Last Hole"), CTRL+SHIFT+Key_End, this, SLOT(emptySlot()), actionCollection(), "lasthole");
+ randAction = new KAction(i18n("&Random Hole"), "goto", 0, this, SLOT(emptySlot()), actionCollection(), "randhole");
+
+ useMouseAction = new KToggleAction(i18n("Enable &Mouse for Moving Putter"), 0, this, SLOT(emptySlot()), actionCollection(), "usemouse");
+ useMouseAction->setCheckedState(i18n("Disable &Mouse for Moving Putter"));
+ connect(useMouseAction, SIGNAL(toggled(bool)), this, SLOT(useMouseChanged(bool)));
+ KConfig *config = kapp->config();
+ config->setGroup("Settings");
+ useMouseAction->setChecked(config->readBoolEntry("useMouse", true));
+
+ useAdvancedPuttingAction = new KToggleAction(i18n("Enable &Advanced Putting"), 0, this, SLOT(emptySlot()), actionCollection(), "useadvancedputting");
+ useAdvancedPuttingAction->setCheckedState(i18n("Disable &Advanced Putting"));
+ connect(useAdvancedPuttingAction, SIGNAL(toggled(bool)), this, SLOT(useAdvancedPuttingChanged(bool)));
+ useAdvancedPuttingAction->setChecked(config->readBoolEntry("useAdvancedPutting", false));
+
+ showInfoAction = new KToggleAction(i18n("Show &Info"), "info", CTRL+Key_I, this, SLOT(emptySlot()), actionCollection(), "showinfo");
+ showInfoAction->setCheckedState(i18n("Hide &Info"));
+ connect(showInfoAction, SIGNAL(toggled(bool)), this, SLOT(showInfoChanged(bool)));
+ showInfoAction->setChecked(config->readBoolEntry("showInfo", false));
+
+ showGuideLineAction = new KToggleAction(i18n("Show Putter &Guideline"), 0, this, SLOT(emptySlot()), actionCollection(), "showguideline");
+ showGuideLineAction->setCheckedState(i18n("Hide Putter &Guideline"));
+ connect(showGuideLineAction, SIGNAL(toggled(bool)), this, SLOT(showGuideLineChanged(bool)));
+ showGuideLineAction->setChecked(config->readBoolEntry("showGuideLine", true));
+
+ KToggleAction *act=new KToggleAction(i18n("Enable All Dialog Boxes"), 0, this, SLOT(enableAllMessages()), actionCollection(), "enableAll");
+ act->setCheckedState(i18n("Disable All Dialog Boxes"));
+
+ soundAction = new KToggleAction(i18n("Play &Sounds"), 0, this, SLOT(emptySlot()), actionCollection(), "sound");
+ connect(soundAction, SIGNAL(toggled(bool)), this, SLOT(soundChanged(bool)));
+ soundAction->setChecked(config->readBoolEntry("sound", true));
+
+ (void) new KAction(i18n("&Reload Plugins"), 0, this, SLOT(initPlugins()), actionCollection(), "reloadplugins");
+ (void) new KAction(i18n("Show &Plugins"), 0, this, SLOT(showPlugins()), actionCollection(), "showplugins");
+
+ aboutAction = new KAction(i18n("&About Course"), 0, this, SLOT(emptySlot()), actionCollection(), "aboutcourse");
+ tutorialAction = new KAction(i18n("&Tutorial"), 0, this, SLOT(tutorial()), actionCollection(), "tutorial");
+
+ statusBar();
+ setupGUI();
+}
+
+bool Kolf::queryClose()
+{
+ if (game)
+ if (game->askSave(true))
+ return false;
+ return true;
+}
+
+void Kolf::startNewGame()
+{
+ NewGameDialog *dialog = 0;
+ int firstHole = 1;
+
+ if (loadedGame.isNull())
+ {
+ dialog = new NewGameDialog(filename.isNull(), dummy, "New Game Dialog");
+ if (dialog->exec() != QDialog::Accepted)
+ goto end;
+ }
+
+ players.clear();
+ delete scoreboard;
+ scoreboard = new ScoreBoard(dummy, "Score Board");
+ layout->addWidget(scoreboard, 1, 0);
+ scoreboard->show();
+
+ if (loadedGame.isNull())
+ {
+ PlayerEditor *curEditor = 0;
+ int newId = 1;
+ for (curEditor = dialog->players()->first(); curEditor; curEditor = dialog->players()->next(), ++newId)
+ {
+ players.append(Player());
+ players.last().ball()->setColor(curEditor->color());
+ players.last().setName(curEditor->name());
+ players.last().setId(newId);
+ }
+
+ competition = dialog->competition();
+ filename = filename.isNull()? dialog->course() : filename;
+ }
+ else
+ {
+ KConfig config(loadedGame);
+ config.setGroup("0 Saved Game");
+
+ if (isTutorial)
+ filename = KGlobal::dirs()->findResource("appdata", "tutorial.kolf");
+ else
+ filename = config.readEntry("Course", QString::null);
+
+ if (filename.isNull())
+ return;
+
+ competition = config.readBoolEntry("Competition", false);
+ firstHole = config.readNumEntry("Current Hole", 1);
+
+ players.clear();
+ KolfGame::scoresFromSaved(&config, players);
+ }
+
+ for (PlayerList::Iterator it = players.begin(); it != players.end(); ++it)
+ scoreboard->newPlayer((*it).name());
+
+ delete spacer;
+ spacer = 0;
+ delete game;
+ game = new KolfGame(obj, &players, filename, dummy);
+ game->setStrict(competition);
+
+ connect(game, SIGNAL(newHole(int)), scoreboard, SLOT(newHole(int)));
+ connect(game, SIGNAL(scoreChanged(int, int, int)), scoreboard, SLOT(setScore(int, int, int)));
+ connect(game, SIGNAL(parChanged(int, int)), scoreboard, SLOT(parChanged(int, int)));
+ connect(game, SIGNAL(modifiedChanged(bool)), this, SLOT(updateModified(bool)));
+ connect(game, SIGNAL(newPlayersTurn(Player *)), this, SLOT(newPlayersTurn(Player *)));
+ connect(game, SIGNAL(holesDone()), this, SLOT(gameOver()));
+ connect(game, SIGNAL(checkEditing()), this, SLOT(checkEditing()));
+ connect(game, SIGNAL(editingStarted()), this, SLOT(editingStarted()));
+ connect(game, SIGNAL(editingEnded()), this, SLOT(editingEnded()));
+ connect(game, SIGNAL(inPlayStart()), this, SLOT(inPlayStart()));
+ connect(game, SIGNAL(inPlayEnd()), this, SLOT(inPlayEnd()));
+ connect(game, SIGNAL(maxStrokesReached(const QString &)), this, SLOT(maxStrokesReached(const QString &)));
+ connect(game, SIGNAL(largestHole(int)), this, SLOT(updateHoleMenu(int)));
+ connect(game, SIGNAL(titleChanged(const QString &)), this, SLOT(titleChanged(const QString &)));
+ connect(game, SIGNAL(newStatusText(const QString &)), this, SLOT(newStatusText(const QString &)));
+ connect(game, SIGNAL(currentHole(int)), this, SLOT(setCurrentHole(int)));
+ connect(holeAction, SIGNAL(activated(const QString &)), game, SLOT(switchHole(const QString &)));
+ connect(nextAction, SIGNAL(activated()), game, SLOT(nextHole()));
+ connect(prevAction, SIGNAL(activated()), game, SLOT(prevHole()));
+ connect(firstAction, SIGNAL(activated()), game, SLOT(firstHole()));
+ connect(lastAction, SIGNAL(activated()), game, SLOT(lastHole()));
+ connect(randAction, SIGNAL(activated()), game, SLOT(randHole()));
+ connect(editingAction, SIGNAL(activated()), game, SLOT(toggleEditMode()));
+ connect(newHoleAction, SIGNAL(activated()), game, SLOT(addNewHole()));
+ connect(clearHoleAction, SIGNAL(activated()), game, SLOT(clearHole()));
+ connect(resetHoleAction, SIGNAL(activated()), game, SLOT(resetHole()));
+ connect(undoShotAction, SIGNAL(activated()), game, SLOT(undoShot()));
+ //connect(replayShotAction, SIGNAL(activated()), game, SLOT(replay()));
+ connect(aboutAction, SIGNAL(activated()), game, SLOT(showInfoDlg()));
+ connect(useMouseAction, SIGNAL(toggled(bool)), game, SLOT(setUseMouse(bool)));
+ connect(useAdvancedPuttingAction, SIGNAL(toggled(bool)), game, SLOT(setUseAdvancedPutting(bool)));
+ connect(soundAction, SIGNAL(toggled(bool)), game, SLOT(setSound(bool)));
+ connect(showGuideLineAction, SIGNAL(toggled(bool)), game, SLOT(setShowGuideLine(bool)));
+ connect(showInfoAction, SIGNAL(toggled(bool)), game, SLOT(setShowInfo(bool)));
+
+ game->setUseMouse(useMouseAction->isChecked());
+ game->setUseAdvancedPutting(useAdvancedPuttingAction->isChecked());
+ game->setShowInfo(showInfoAction->isChecked());
+ game->setShowGuideLine(showGuideLineAction->isChecked());
+ game->setSound(soundAction->isChecked());
+
+ layout->addWidget(game, 0, 0, AlignCenter);
+
+ game->show();
+ game->setFocus();
+
+ setEditingEnabled(true);
+ endAction->setEnabled(true);
+ setHoleMovementEnabled(true);
+ setHoleOtherEnabled(true);
+ aboutAction->setEnabled(true);
+ highScoreAction->setEnabled(true);
+ printAction->setEnabled(true);
+ saveAction->setEnabled(true);
+ saveAsAction->setEnabled(true);
+ saveGameAction->setEnabled(true);
+ saveGameAsAction->setEnabled(true);
+
+ clearHoleAction->setEnabled(false);
+ newHoleAction->setEnabled(false);
+ newAction->setEnabled(false);
+ loadGameAction->setEnabled(false);
+ tutorialAction->setEnabled(false);
+
+
+ // so game can do stuff that needs to be done
+ // after things above are connected
+ game->startFirstHole(firstHole);
+
+ end:
+ delete dialog;
+}
+
+void Kolf::newGame()
+{
+ isTutorial = false;
+ filename = QString::null;
+ startNewGame();
+}
+
+void Kolf::tutorial()
+{
+ QString newfilename = KGlobal::dirs()->findResource("appdata", "tutorial.kolfgame");
+ if (newfilename.isNull())
+ return;
+
+ filename = QString::null;
+ loadedGame = newfilename;
+ isTutorial = true;
+
+ startNewGame();
+
+ loadedGame = QString::null;
+}
+
+void Kolf::closeGame()
+{
+ if (game)
+ {
+ if (game->askSave(true))
+ return;
+ game->pause();
+ }
+
+ filename = QString::null;
+
+ editingEnded();
+ delete game;
+ game = 0;
+ loadedGame = QString::null;
+
+ editingAction->setChecked(false);
+ setEditingEnabled(false);
+ endAction->setEnabled(false);
+ aboutAction->setEnabled(false);
+ highScoreAction->setEnabled(false);
+ printAction->setEnabled(false);
+ saveAction->setEnabled(false);
+ saveAsAction->setEnabled(false);
+ saveGameAction->setEnabled(false);
+ saveGameAsAction->setEnabled(false);
+ setHoleMovementEnabled(false);
+ setHoleOtherEnabled(false);
+
+ clearHoleAction->setEnabled(false);
+ newHoleAction->setEnabled(false);
+ newAction->setEnabled(true);
+ loadGameAction->setEnabled(true);
+ tutorialAction->setEnabled(true);
+
+ titleChanged(QString::null);
+ updateModified(false);
+
+ QTimer::singleShot(100, this, SLOT(createSpacer()));
+}
+
+void Kolf::createSpacer()
+{
+ // make a player to play the spacer hole
+ spacerPlayers.clear();
+ spacerPlayers.append(Player());
+ spacerPlayers.last().ball()->setColor(yellow);
+ spacerPlayers.last().setName("player");
+ spacerPlayers.last().setId(1);
+
+ delete spacer;
+ spacer = new KolfGame(obj, &spacerPlayers, KGlobal::dirs()->findResource("appdata", "intro"), dummy);
+ spacer->setSound(false);
+ spacer->startFirstHole(1);
+ layout->addWidget(spacer, 0, 0, AlignCenter);
+ spacer->hidePutter();
+ spacer->ignoreEvents(true);
+
+ spacer->show();
+}
+
+void Kolf::gameOver()
+{
+ int curPar = 0;
+ int lowScore = INT_MAX; // let's hope it doesn't stay this way!
+ int curScore = 1;
+
+ // names of people who had the lowest score
+ QStringList names;
+
+ HighScoreList highScores;
+ int scoreBoardIndex = 1;
+
+ while (curScore != 0)
+ {
+ QString curName;
+
+ // name taken as a reference and filled out
+ curScore = scoreboard->total(scoreBoardIndex, curName);
+
+ scoreBoardIndex++;
+
+ if (curName == i18n("Par"))
+ {
+ curPar = curScore;
+ continue;
+ }
+
+ if (curScore == 0)
+ continue;
+
+ // attempt to add everybody to the highscore list
+ // (ignored if we aren't competing down below)
+ highScores.append(HighScore(curName, curScore));
+
+ if (curScore < lowScore)
+ {
+ names.clear();
+ lowScore = curScore;
+ names.append(curName);
+ }
+ else if (curScore == lowScore)
+ names.append(curName);
+ }
+
+ // only announce a winner if more than two entries
+ // (player and par) are on the scoreboard + one to go past end
+ // + 1 for koodoo
+ if (scoreBoardIndex > 4)
+ {
+ if (names.count() > 1)
+ {
+ QString winners = names.join(i18n(" and "));
+ KMessageBox::information(this, i18n("%1 tied").arg(winners));
+ }
+ else
+ KMessageBox::information(this, i18n("%1 won!").arg(names.first()));
+ }
+
+ if (competition)
+ {
+ // deal with highscores
+ // KScoreDialog makes it very easy :-))
+
+ KScoreDialog *scoreDialog = new KScoreDialog(KScoreDialog::Name | KScoreDialog::Custom1 | KScoreDialog::Score, this);
+ scoreDialog->addField(KScoreDialog::Custom1, i18n("Par"), "Par");
+
+ CourseInfo courseInfo;
+ game->courseInfo(courseInfo, game->curFilename());
+
+ scoreDialog->setConfigGroup(courseInfo.untranslatedName + QString(" Highscores"));
+
+ for (HighScoreList::Iterator it = highScores.begin(); it != highScores.end(); ++it)
+ {
+ KScoreDialog::FieldInfo info;
+ info[KScoreDialog::Name] = (*it).name;
+ info[KScoreDialog::Custom1] = QString::number(curPar);
+
+ scoreDialog->addScore((*it).score, info, false, true);
+ }
+
+ scoreDialog->setComment(i18n("High Scores for %1").arg(courseInfo.name));
+ scoreDialog->show();
+ }
+
+ QTimer::singleShot(700, this, SLOT(closeGame()));
+}
+
+void Kolf::showHighScores()
+{
+ KScoreDialog *scoreDialog = new KScoreDialog(KScoreDialog::Name | KScoreDialog::Custom1 | KScoreDialog::Score, this);
+ scoreDialog->addField(KScoreDialog::Custom1, i18n("Par"), "Par");
+
+ CourseInfo courseInfo;
+ game->courseInfo(courseInfo, game->curFilename());
+
+ scoreDialog->setConfigGroup(courseInfo.untranslatedName + QString(" Highscores"));
+ scoreDialog->setComment(i18n("High Scores for %1").arg(courseInfo.name));
+ scoreDialog->show();
+}
+
+void Kolf::save()
+{
+ if (filename.isNull())
+ {
+ saveAs();
+ return;
+ }
+
+ if (game)
+ game->save();
+
+ game->setFocus();
+}
+
+void Kolf::saveAs()
+{
+ QString newfilename = KFileDialog::getSaveFileName(":kourses", "application/x-kourse", this, i18n("Pick Kolf Course to Save To"));
+ if (!newfilename.isNull())
+ {
+ filename = newfilename;
+ game->setFilename(filename);
+ game->save();
+ game->setFocus();
+ }
+}
+
+void Kolf::saveGameAs()
+{
+ QString newfilename = KFileDialog::getSaveFileName(":savedkolf", "application/x-kolf", this, i18n("Pick Saved Game to Save To"));
+ if (newfilename.isNull())
+ return;
+
+ loadedGame = newfilename;
+
+ saveGame();
+}
+
+void Kolf::saveGame()
+{
+ if (loadedGame.isNull())
+ {
+ saveGameAs();
+ return;
+ }
+
+ KConfig config(loadedGame);
+ config.setGroup("0 Saved Game");
+
+ config.writeEntry("Competition", competition);
+ config.writeEntry("Course", filename);
+
+ game->saveScores(&config);
+
+ config.sync();
+}
+
+void Kolf::loadGame()
+{
+ loadedGame = KFileDialog::getOpenFileName(":savedkolf", QString::fromLatin1("application/x-kolf"), this, i18n("Pick Kolf Saved Game"));
+
+ if (loadedGame.isNull())
+ return;
+
+ isTutorial = false;
+ startNewGame();
+}
+
+// called by main for commmand line files
+void Kolf::openURL(KURL url)
+{
+ QString target;
+ if (KIO::NetAccess::download(url, target, this))
+ {
+ isTutorial = false;
+ QString mimeType = KMimeType::findByPath(target)->name();
+ if (mimeType == "application/x-kourse")
+ filename = target;
+ else if (mimeType == "application/x-kolf")
+ loadedGame = target;
+ else
+ {
+ closeGame();
+ return;
+ }
+
+ QTimer::singleShot(10, this, SLOT(startNewGame()));
+ }
+ else
+ closeGame();
+}
+
+void Kolf::newPlayersTurn(Player *player)
+{
+ tempStatusBarText = i18n("%1's turn").arg(player->name());
+
+ if (showInfoAction->isChecked())
+ statusBar()->message(tempStatusBarText, 5 * 1000);
+ else
+ statusBar()->message(tempStatusBarText);
+
+ scoreboard->setCurrentCell(player->id() - 1, game->currentHole() - 1);
+}
+
+void Kolf::newStatusText(const QString &text)
+{
+ if (text.isEmpty())
+ statusBar()->message(tempStatusBarText);
+ else
+ statusBar()->message(text);
+}
+
+void Kolf::editingStarted()
+{
+ delete editor;
+ editor = new Editor(obj, dummy, "Editor");
+ connect(editor, SIGNAL(addNewItem(Object *)), game, SLOT(addNewObject(Object *)));
+ connect(editor, SIGNAL(changed()), game, SLOT(setModified()));
+ connect(editor, SIGNAL(addNewItem(Object *)), this, SLOT(setHoleFocus()));
+ connect(game, SIGNAL(newSelectedItem(CanvasItem *)), editor, SLOT(setItem(CanvasItem *)));
+
+ scoreboard->hide();
+
+ layout->addWidget(editor, 1, 0);
+ editor->show();
+
+ clearHoleAction->setEnabled(true);
+ newHoleAction->setEnabled(true);
+ setHoleOtherEnabled(false);
+
+ game->setFocus();
+}
+
+void Kolf::editingEnded()
+{
+ delete editor;
+ editor = 0;
+
+ if (scoreboard)
+ scoreboard->show();
+
+ clearHoleAction->setEnabled(false);
+ newHoleAction->setEnabled(false);
+ setHoleOtherEnabled(true);
+
+ if (game)
+ game->setFocus();
+}
+
+void Kolf::inPlayStart()
+{
+ setEditingEnabled(false);
+ setHoleOtherEnabled(false);
+ setHoleMovementEnabled(false);
+}
+
+void Kolf::inPlayEnd()
+{
+ setEditingEnabled(true);
+ setHoleOtherEnabled(true);
+ setHoleMovementEnabled(true);
+}
+
+void Kolf::maxStrokesReached(const QString &name)
+{
+ KMessageBox::sorry(this, i18n("%1's score has reached the maximum for this hole.").arg(name));
+}
+
+void Kolf::updateHoleMenu(int largest)
+{
+ QStringList items;
+ for (int i = 1; i <= largest; ++i)
+ items.append(QString::number(i));
+
+ // setItems for some reason enables the action
+ bool shouldbe = holeAction->isEnabled();
+ holeAction->setItems(items);
+ holeAction->setEnabled(shouldbe);
+}
+
+void Kolf::setHoleMovementEnabled(bool yes)
+{
+ if (competition)
+ yes = false;
+
+ holeAction->setEnabled(yes);
+
+ nextAction->setEnabled(yes);
+ prevAction->setEnabled(yes);
+ firstAction->setEnabled(yes);
+ lastAction->setEnabled(yes);
+ randAction->setEnabled(yes);
+}
+
+void Kolf::setHoleOtherEnabled(bool yes)
+{
+ if (competition)
+ yes = false;
+
+ resetHoleAction->setEnabled(yes);
+ undoShotAction->setEnabled(yes);
+ //replayShotAction->setEnabled(yes);
+}
+
+void Kolf::setEditingEnabled(bool yes)
+{
+ editingAction->setEnabled(competition? false : yes);
+}
+
+void Kolf::checkEditing()
+{
+ editingAction->setChecked(true);
+}
+
+void Kolf::print()
+{
+ KPrinter pr;
+ pr.addDialogPage(new PrintDialogPage());
+
+ if (pr.setup(this, i18n("Print %1 - Hole %2").arg(game->courseName()).arg(game->currentHole())))
+ {
+ pr.newPage();
+ if (game)
+ game->print(pr);
+ }
+}
+
+void Kolf::updateModified(bool mod)
+{
+ courseModified = mod;
+ titleChanged(title);
+}
+
+void Kolf::titleChanged(const QString &newTitle)
+{
+ title = newTitle;
+ setCaption(title, courseModified);
+}
+
+void Kolf::useMouseChanged(bool yes)
+{
+ KConfig *config = kapp->config(); config->setGroup("Settings"); config->writeEntry("useMouse", yes); config->sync();
+}
+
+void Kolf::useAdvancedPuttingChanged(bool yes)
+{
+ KConfig *config = kapp->config(); config->setGroup("Settings"); config->writeEntry("useAdvancedPutting", yes); config->sync();
+}
+
+void Kolf::showInfoChanged(bool yes)
+{
+ KConfig *config = kapp->config(); config->setGroup("Settings"); config->writeEntry("showInfo", yes); config->sync();
+}
+
+void Kolf::showGuideLineChanged(bool yes)
+{
+ KConfig *config = kapp->config(); config->setGroup("Settings"); config->writeEntry("showGuideLine", yes); config->sync();
+}
+
+void Kolf::soundChanged(bool yes)
+{
+ KConfig *config = kapp->config(); config->setGroup("Settings"); config->writeEntry("sound", yes); config->sync();
+}
+
+void Kolf::initPlugins()
+{
+ //kdDebug(12007) << "initPlugins" << endl;
+ if (game)
+ game->pause();
+
+ obj->setAutoDelete(true);
+ obj->clear();
+ plugins.setAutoDelete(false);
+ plugins.clear();
+
+ // add prefab objects
+ obj->append(new SlopeObj());
+ obj->append(new PuddleObj());
+ obj->append(new WallObj());
+ obj->append(new CupObj());
+ obj->append(new SandObj());
+ obj->append(new WindmillObj());
+ obj->append(new BlackHoleObj());
+ obj->append(new FloaterObj());
+ obj->append(new BridgeObj());
+ obj->append(new SignObj());
+ obj->append(new BumperObj());
+
+ ObjectList *other = PluginLoader::loadAll();
+ Object *object = 0;
+ for (object = other->first(); object; object = other->next())
+ {
+ obj->append(object);
+ plugins.append(object);
+ }
+
+ if (game)
+ {
+ game->setObjects(obj);
+ game->unPause();
+ }
+
+ //kdDebug(12007) << "end of initPlugins" << endl;
+}
+
+void Kolf::showPlugins()
+{
+ QString text = QString("<h2>%1</h2><ol>").arg(i18n("Currently Loaded Plugins"));
+ Object *object = 0;
+ for (object = plugins.first(); object; object = plugins.next())
+ {
+ text.append("<li>");
+ text.append(object->name());
+ text.append(" - ");
+ text.append(i18n("by %1").arg(object->author()));
+ text.append("</li>");
+ }
+ text.append("</ol>");
+ KMessageBox::information(this, text, i18n("Plugins"));
+}
+
+void Kolf::enableAllMessages()
+{
+ KMessageBox::enableAllMessages();
+}
+
+void Kolf::setCurrentHole(int hole)
+{
+ if (!holeAction)
+ return;
+ // Golf is 1-based, KListAction is 0-based
+ holeAction->setCurrentItem(hole - 1);
+}
+
+#include "kolf.moc"
diff --git a/kolf/kolf.desktop b/kolf/kolf.desktop
new file mode 100644
index 00000000..454dcfda
--- /dev/null
+++ b/kolf/kolf.desktop
@@ -0,0 +1,73 @@
+[Desktop Entry]
+DocPath=kolf/index.html
+Name=Kolf
+Name[ar]=لعبة الجولف (Kolf)
+Name[be]=Гольф
+Name[bn]=কল্‌ফ
+Name[hi]=कोल्फ
+Name[ne]=कोल्फ
+Name[pa]=ਕੇ-ਗੋਲਫ਼
+Name[ta]=கால்ஃப்
+Name[tg]=Колф
+
+Type=Application
+Exec=kolf %U
+Icon=kolf
+MimeType=application/x-kourse;application/x-kolf;
+
+GenericName=Miniature Golf
+GenericName[be]=Маленькі гольф
+GenericName[bg]=Миниатюрен голф
+GenericName[bn]=ছোটখাটো গলফ
+GenericName[bs]=Minijaturni golf
+GenericName[ca]=Golf en miniatura
+GenericName[cs]=Miniaturní golf
+GenericName[cy]=Golff Bach
+GenericName[da]=Miniaturegolf
+GenericName[de]=Minigolf
+GenericName[el]=Μίνι γκολφ
+GenericName[eo]=Eta golfludo
+GenericName[es]=Golf en miniatura
+GenericName[et]=Miniatuurne golf
+GenericName[eu]=Golf txikia
+GenericName[fi]=Pienoisgolf
+GenericName[fr]=Golf miniature
+GenericName[gl]=Golf en miniatura
+GenericName[he]=מיני־גולף
+GenericName[hi]=लघु गोल्फ
+GenericName[hr]=Mini-golf
+GenericName[hu]=Minigolf
+GenericName[is]=Minigolf
+GenericName[it]=Minigolf
+GenericName[ja]=ミニチュアゴルフ
+GenericName[ko]=모형 골프
+GenericName[lt]=Miniatiūrinis golfas
+GenericName[lv]=Miniatūrs golfs
+GenericName[mk]=Минијатурен голф
+GenericName[nb]=Minigolf
+GenericName[nds]=Minigolf
+GenericName[ne]=सानो गल्फ
+GenericName[nl]=Miniatuurgolf
+GenericName[nn]=Minigolf
+GenericName[pa]=ਛੋਟੀ ਗੋਲਫ਼ ਭੇਜੋ
+GenericName[pl]=Miniaturowy Golf
+GenericName[pt]=Golfe em Miniatura
+GenericName[pt_BR]=Mini-Golf
+GenericName[ro]=Golf în miniatură
+GenericName[ru]=Гольф
+GenericName[se]=Minigolfa
+GenericName[sk]=Miniatúrny golf
+GenericName[sl]=Mini golf
+GenericName[sr]=Минијатурни голф
+GenericName[sr@Latn]=Minijaturni golf
+GenericName[sv]=Minigolf
+GenericName[ta]=சிறிய கால்ஃப்
+GenericName[tg]=Колф дар шакли хурд
+GenericName[tr]=Minyatür Golf
+GenericName[uk]=Мініатюрний гольф
+GenericName[wa]=Golf miniateure
+GenericName[xh]=Igalufa encinci
+GenericName[zh_CN]=微型高尔夫
+GenericName[zh_TW]=迷你高爾夫
+GenericName[zu]=Igalofu elincanyana
+Categories=Qt;KDE;Game;ArcadeGame;
diff --git a/kolf/kolf.h b/kolf/kolf.h
new file mode 100644
index 00000000..8a2c6d78
--- /dev/null
+++ b/kolf/kolf.h
@@ -0,0 +1,147 @@
+#ifndef KOLF_H
+#define KOLF_H
+
+#include <kmainwindow.h>
+#include <kurl.h>
+
+#include <qmap.h>
+#include <qptrlist.h>
+#include <qstring.h>
+#include <qwidget.h>
+#include <qvaluelist.h>
+#include <kdemacros.h>
+#include "game.h"
+
+class KolfGame;
+class KToggleAction;
+class KListAction;
+class KAction;
+class QGridLayout;
+class ScoreBoard;
+class QCloseEvent;
+class QEvent;
+class Player;
+class QWidget;
+class Editor;
+
+class KDE_EXPORT Kolf : public KMainWindow
+{
+ Q_OBJECT
+
+public:
+ Kolf();
+ ~Kolf();
+
+ void openURL(KURL url);
+
+public slots:
+ void closeGame();
+ void updateModified(bool);
+
+protected:
+ virtual bool queryClose();
+
+protected slots:
+ void startNewGame();
+ void loadGame();
+ void tutorial();
+ void newGame();
+ void save();
+ void saveAs();
+ void saveGame();
+ void saveGameAs();
+ void print();
+ void newPlayersTurn(Player *);
+ void gameOver();
+ void editingStarted();
+ void editingEnded();
+ void checkEditing();
+ void setHoleFocus() { game->setFocus(); }
+ void inPlayStart();
+ void inPlayEnd();
+ void maxStrokesReached(const QString &);
+ void updateHoleMenu(int);
+ void titleChanged(const QString &);
+ void newStatusText(const QString &);
+ void showInfoChanged(bool);
+ void useMouseChanged(bool);
+ void useAdvancedPuttingChanged(bool);
+ void showGuideLineChanged(bool);
+ void soundChanged(bool);
+ void initPlugins();
+ void showPlugins();
+ void showHighScores();
+ void enableAllMessages();
+ void createSpacer();
+
+ void emptySlot() {};
+
+ void setCurrentHole(int);
+
+private:
+ QWidget *dummy;
+ KolfGame *game;
+ Editor *editor;
+ KolfGame *spacer;
+ void initGUI();
+ QString filename;
+ PlayerList players;
+ PlayerList spacerPlayers;
+ QGridLayout *layout;
+ ScoreBoard *scoreboard;
+ KToggleAction *editingAction;
+ KAction *newHoleAction;
+ KAction *resetHoleAction;
+ KAction *undoShotAction;
+ //KAction *replayShotAction;
+ KAction *clearHoleAction;
+ KAction *tutorialAction;
+ KAction *newAction;
+ KAction *endAction;
+ KAction *printAction;
+ KAction *saveAction;
+ KAction *saveAsAction;
+ KAction *saveGameAction;
+ KAction *saveGameAsAction;
+ KAction *loadGameAction;
+ KAction *aboutAction;
+ KListAction *holeAction;
+ KAction *highScoreAction;
+ KAction *nextAction;
+ KAction *prevAction;
+ KAction *firstAction;
+ KAction *lastAction;
+ KAction *randAction;
+ KToggleAction *showInfoAction;
+ KToggleAction *useMouseAction;
+ KToggleAction *useAdvancedPuttingAction;
+ KToggleAction *showGuideLineAction;
+ KToggleAction *soundAction;
+ void setHoleMovementEnabled(bool);
+ void setHoleOtherEnabled(bool);
+ inline void setEditingEnabled(bool);
+ bool competition;
+
+ // contains everything
+ ObjectList *obj;
+ // contains subset of obj
+ ObjectList plugins;
+
+ QString loadedGame;
+
+ bool isTutorial;
+ bool courseModified;
+ QString title;
+ QString tempStatusBarText;
+};
+
+struct HighScore
+{
+ HighScore() {}
+ HighScore(const QString &name, int score) { this->name = name; this->score = score; }
+ QString name;
+ int score;
+};
+typedef QValueList<HighScore> HighScoreList;
+
+#endif
diff --git a/kolf/kolf.magic b/kolf/kolf.magic
new file mode 100644
index 00000000..01bc5070
--- /dev/null
+++ b/kolf/kolf.magic
@@ -0,0 +1,2 @@
+0 string [0-course@-50,-50] application/x-kourse
+0 string [0\ Saved\ Game] application/x-kolf
diff --git a/kolf/kolfui.rc b/kolf/kolfui.rc
new file mode 100644
index 00000000..b38d243d
--- /dev/null
+++ b/kolf/kolfui.rc
@@ -0,0 +1,71 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui name="Kolf" version="11">
+ <MenuBar>
+ <Menu name="game">
+ <Action name="savegame" append="save_merge" />
+ <Action name="savegameas" append="save_merge" />
+ <Action name="saveshotas" append="save_merge" />
+
+ <Action name="aboutcourse" />
+ </Menu>
+
+ <Menu name="hole"><text>Ho&amp;le</text>
+ <Action name="editing" />
+ <Separator />
+ <Action name="newhole" />
+ <Action name="clearhole" />
+ <Separator />
+ <Action name="resethole" />
+ <Action name="restarthole" />
+ <Separator />
+ <Action name="showinfo" />
+ <Action name="undoshot" />
+ <Action name="replay" />
+ </Menu>
+
+ <Menu name="go_course"><text>&amp;Go</text>
+ <Action name="switchhole" />
+ <Separator />
+ <Action name="nexthole" />
+ <Action name="prevhole" />
+ <Action name="firsthole" />
+ <Action name="lasthole" />
+ <Separator />
+ <Action name="randhole" />
+ </Menu>
+
+ <Menu name="settings">
+ <Action name="usemouse" append="save_merge"/>
+ <Action name="useadvancedputting" append="save_merge"/>
+ <Action name="showguideline" append="save_merge"/>
+ <Action name="sound" append="save_merge"/>
+ <Separator append="save_merge"/>
+ <Action name="enableAll" append="save_merge"/>
+ <Separator append="save_merge"/>
+ <Action name="reloadplugins" append="save_merge"/>
+ <Action name="showplugins" append="save_merge"/>
+ </Menu>
+
+ <Menu name="help">
+ <Action name="tutorial" />
+ </Menu>
+ </MenuBar>
+
+ <ToolBar name="mainToolBar" fullWidth="true">
+ <Action name="game_new" />
+ <Action name="game_save" />
+ <Separator />
+ <Action name="game_end" />
+ <Separator />
+ <Action name="showinfo" />
+ <Action name="undoshot" />
+ <Action name="editing" />
+ <Separator />
+ <Action name="prevhole" />
+ <Action name="nexthole" />
+ <Separator />
+ <Action name="switchhole" />
+ </ToolBar>
+
+ <StatusBar />
+</kpartgui>
diff --git a/kolf/kvolumecontrol.cpp b/kolf/kvolumecontrol.cpp
new file mode 100644
index 00000000..11d0be15
--- /dev/null
+++ b/kolf/kvolumecontrol.cpp
@@ -0,0 +1,67 @@
+#include <kdebug.h>
+
+#include <arts/soundserver.h>
+#include <arts/flowsystem.h>
+
+#include "kvolumecontrol.h"
+
+KVolumeControl::KVolumeControl(Arts::SoundServerV2 server, KPlayObject *parent)
+ : QObject(parent)
+{
+ init(server);
+}
+
+KVolumeControl::KVolumeControl(double vol, Arts::SoundServerV2 server, KPlayObject *parent)
+ : QObject(parent)
+{
+ init(server);
+ setVolume(vol);
+}
+
+KVolumeControl::~KVolumeControl()
+{
+ manager.stop();
+ volumeControl.stop();
+}
+
+void KVolumeControl::init(Arts::SoundServerV2 server)
+{
+ manager = Arts::DynamicCast(server.createObject("Arts::Synth_AMAN_PLAY"));
+ if (manager.isNull())
+ {
+ kdError() << "Your OS is broken. Get an OS that installs KDE decently." << endl;
+ return;
+ }
+ manager.start();
+
+ volumeControl = Arts::DynamicCast(server.createObject("Arts::StereoVolumeControl"));
+ if (volumeControl.isNull())
+ {
+ kdError() << "Your OS is broken. Get an OS that installs KDE decently." << endl;
+ return;
+ }
+ volumeControl.start();
+
+ Arts::connect((static_cast<KPlayObject *>(parent()))->object(), "left", volumeControl, "inleft");
+ Arts::connect((static_cast<KPlayObject *>(parent()))->object(), "right", volumeControl, "inright");
+
+ Arts::connect(volumeControl, manager);
+}
+
+void KVolumeControl::setVolume(double d)
+{
+ if (volumeControl.isNull())
+ return;
+
+ volumeControl.scaleFactor(d);
+}
+
+double KVolumeControl::volume(void)
+{
+ if (volumeControl.isNull())
+ return -1;
+
+ return volumeControl.scaleFactor();
+}
+
+#include "kvolumecontrol.moc"
diff --git a/kolf/kvolumecontrol.h b/kolf/kvolumecontrol.h
new file mode 100644
index 00000000..3f0306a8
--- /dev/null
+++ b/kolf/kvolumecontrol.h
@@ -0,0 +1,28 @@
+#ifndef KVOLUMECONTROL_H
+#define KVOLUMECONTROL_H
+
+#include <arts/kplayobject.h>
+#include <arts/artsflow.h>
+#include <kdebug.h>
+#include <qobject.h>
+
+class KVolumeControl : public QObject
+{
+Q_OBJECT
+
+public:
+ KVolumeControl(Arts::SoundServerV2 server, KPlayObject *parent);
+ KVolumeControl(double vol, Arts::SoundServerV2 server, KPlayObject *parent);
+ ~KVolumeControl();
+
+ void setVolume(double);
+ double volume(void);
+
+ void init(Arts::SoundServerV2 server);
+
+private:
+ Arts::StereoVolumeControl volumeControl;
+ Arts::Synth_AMAN_PLAY manager;
+};
+
+#endif
diff --git a/kolf/main.cpp b/kolf/main.cpp
new file mode 100644
index 00000000..3a63c1f1
--- /dev/null
+++ b/kolf/main.cpp
@@ -0,0 +1,93 @@
+#include <qcstring.h>
+#include <qfile.h>
+
+#include <kapplication.h>
+#include <klocale.h>
+#include <kcmdlineargs.h>
+#include <kaboutdata.h>
+#include <kdebug.h>
+#include <kurl.h>
+
+#include "kolf.h"
+
+#include <iostream>
+#include <kdemacros.h>
+using namespace std;
+
+static const char description[] =
+I18N_NOOP("KDE Minigolf Game");
+
+static const char version[] = "1.1.1";
+
+static KCmdLineOptions options[] =
+{
+ { "+file", I18N_NOOP("File"), 0 },
+ { "course-info ", I18N_NOOP("Print course information and exit"), 0 },
+ KCmdLineLastOption
+};
+
+
+extern "C" KDE_EXPORT int kdemain(int argc, char **argv)
+{
+ KAboutData aboutData( "kolf", I18N_NOOP("Kolf"), version, description, KAboutData::License_GPL, "(c) 2002-2005, Jason Katz-Brown", 0, "http://www.katzbrown.com/kolf/");
+
+ aboutData.addAuthor("Jason Katz-Brown", I18N_NOOP("Main author"), "jason@katzbrown.com");
+ aboutData.addAuthor("Niklas Knutsson", I18N_NOOP("Advanced putting mode"), 0);
+ aboutData.addAuthor("Rik Hemsley", I18N_NOOP("Border around course"), 0);
+ aboutData.addAuthor("Ryan Cumming", I18N_NOOP("Vector class"), 0);
+ aboutData.addAuthor("Daniel Matza-Brown", I18N_NOOP("Working wall-bouncing algorithm"), 0);
+ aboutData.addAuthor("Timo A. Hummel", I18N_NOOP("Some good sound effects"), "timo.hummel@gmx.net");
+
+ aboutData.addCredit("Rob Renaud", I18N_NOOP("Wall-bouncing help"), 0);
+ aboutData.addCredit("Aaron Seigo", I18N_NOOP("Suggestions, bug reports"), 0);
+
+ KCmdLineArgs::init(argc, argv, &aboutData);
+ KCmdLineArgs::addCmdLineOptions(options);
+
+ // I've actually added this for my web site uploaded courses display
+ KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
+ if (args->isSet("course-info"))
+ {
+ KCmdLineArgs::enable_i18n();
+
+ QString filename(QFile::decodeName(args->getOption("course-info")));
+ if (QFile::exists(filename))
+ {
+ CourseInfo info;
+ KolfGame::courseInfo(info, filename);
+
+ cout << info.name.latin1()
+ << " - " << i18n("By %1").arg(info.author).latin1()
+ << " - " << i18n("%1 holes").arg(info.holes).latin1()
+ << " - " << i18n("par %1").arg(info.par).latin1()
+ << endl;
+
+ return 0;
+ }
+ else
+ {
+ KCmdLineArgs::usage(i18n("Course %1 does not exist.").arg(filename.latin1()));
+ }
+ }
+
+ QApplication::setColorSpec(QApplication::ManyColor);
+ KApplication a;
+ KGlobal::locale()->insertCatalogue("libkdegames");
+
+ Kolf *top = new Kolf;
+
+ if (args->count() >= 1)
+ {
+ KURL url = args->url(args->count() - 1);
+ top->openURL(url);
+ args->clear();
+ }
+ else
+ top->closeGame();
+
+ a.setMainWidget(top);
+ top->show();
+
+ return a.exec();
+}
+
diff --git a/kolf/newgame.cpp b/kolf/newgame.cpp
new file mode 100644
index 00000000..d038185e
--- /dev/null
+++ b/kolf/newgame.cpp
@@ -0,0 +1,347 @@
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kdialogbase.h>
+#include <kmessagebox.h>
+#include <kglobal.h>
+#include <kscoredialog.h>
+#include <kstandarddirs.h>
+#include <kseparator.h>
+#include <klineedit.h>
+#include <klocale.h>
+#include <kfiledialog.h>
+#include <kurllabel.h>
+
+#include <qcheckbox.h>
+#include <qevent.h>
+#include <qframe.h>
+#include <qpen.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <klistbox.h>
+#include <qstyle.h>
+#include <qrect.h>
+#include <qmap.h>
+#include <qpainter.h>
+#include <qpixmapcache.h>
+#include <qwidget.h>
+#include <qscrollview.h>
+#include <qvaluelist.h>
+#include <qptrlist.h>
+#include <qstringlist.h>
+#include <qstring.h>
+#include <qvbox.h>
+
+#include "newgame.h"
+#include "game.h"
+
+NewGameDialog::NewGameDialog(bool enableCourses, QWidget *parent, const char *_name)
+ : KDialogBase(KDialogBase::TreeList, i18n("New Game"), Ok | Cancel, Ok, parent, _name)
+{
+ this->enableCourses = enableCourses;
+
+ editors.setAutoDelete(true);
+ KConfig *config = kapp->config();
+
+ // lots o' colors :)
+ startColors << yellow << blue << red << lightGray << cyan << darkBlue << magenta << darkGray << darkMagenta << darkYellow;
+
+ playerPage = addPage(i18n("Players"));
+ QVBoxLayout *bigLayout = new QVBoxLayout(playerPage, marginHint(), spacingHint());
+
+ addButton = new KPushButton(i18n("&New Player"), playerPage);
+ bigLayout->addWidget(addButton);
+
+ connect(addButton, SIGNAL(clicked()), this, SLOT(addPlayer()));
+
+ scroller = new QScrollView(playerPage);
+ bigLayout->addWidget(scroller);
+ layout = new QVBox(scroller->viewport());
+ if (!QPixmapCache::find("grass", grass))
+ {
+ grass.load(locate("appdata", "pics/grass.png"));
+ QPixmapCache::insert("grass", grass);
+ }
+ scroller->viewport()->setBackgroundPixmap(grass);
+ scroller->addChild(layout);
+
+ QMap<QString, QString> entries = config->entryMap("New Game Dialog");
+ unsigned int i = 0;
+ for (QMap<QString, QString>::Iterator it = entries.begin(); it != entries.end(); ++it)
+ {
+ if (i > startColors.count())
+ return;
+
+ addPlayer();
+ editors.last()->setName(it.key().right(it.key().length() - 1));
+ editors.last()->setColor(QColor(it.data()));
+ ++i;
+ }
+
+ if (editors.isEmpty())
+ {
+ addPlayer();
+ addPlayer();
+ }
+
+ enableButtons();
+
+ if (enableCourses)
+ {
+ coursePage = addPage(i18n("Course"), i18n("Choose Course to Play"));
+ QVBoxLayout *coursePageLayout = new QVBoxLayout(coursePage, marginHint(), spacingHint());
+
+ KURLLabel *coursesLink = new KURLLabel("http://web.mit.edu/~jasonkb/www/kolf/", "http://web.mit.edu/~jasonkb/www/kolf/", coursePage);
+ connect(coursesLink, SIGNAL(leftClickedURL(const QString &)), kapp, SLOT(invokeBrowser(const QString &)));
+ coursePageLayout->addWidget(coursesLink);
+
+ QHBoxLayout *hlayout = new QHBoxLayout(coursePageLayout, spacingHint());
+
+ // following use this group
+ config->setGroup("New Game Dialog Mode");
+
+ // find other courses
+ externCourses = config->readListEntry("extra");
+
+ /// course loading
+ QStringList items = externCourses + KGlobal::dirs()->findAllResources("appdata", "courses/*");
+ QStringList nameList;
+ const QString lastCourse(config->readEntry("course", ""));
+ int curItem = 0;
+ i = 0;
+ for (QStringList::Iterator it = items.begin(); it != items.end(); ++it, ++i)
+ {
+ QString file = *it;
+ CourseInfo curinfo;
+ KolfGame::courseInfo(curinfo, file);
+ info[file] = curinfo;
+ names.append(file);
+ nameList.append(curinfo.name);
+
+ if (lastCourse == file)
+ curItem = i;
+ }
+
+ const QString newName(i18n("Create New"));
+ info[QString::null] = CourseInfo(newName, newName, i18n("You"), 0, 0);
+ names.append(QString::null);
+ nameList.append(newName);
+
+ courseList = new KListBox(coursePage);
+ hlayout->addWidget(courseList);
+ courseList->insertStringList(nameList);
+ courseList->setCurrentItem(curItem);
+ connect(courseList, SIGNAL(highlighted(int)), this, SLOT(courseSelected(int)));
+ connect(courseList, SIGNAL(selectionChanged()), this, SLOT(selectionChanged()));
+
+ QVBoxLayout *detailLayout = new QVBoxLayout(hlayout, spacingHint());
+ name = new QLabel(coursePage);
+ detailLayout->addWidget(name);
+ author = new QLabel(coursePage);
+ detailLayout->addWidget(author);
+
+ QHBoxLayout *minorLayout = new QHBoxLayout(detailLayout, spacingHint());
+ par = new QLabel(coursePage);
+ minorLayout->addWidget(par);
+ holes = new QLabel(coursePage);
+ minorLayout->addWidget(holes);
+
+ detailLayout->addStretch();
+ KPushButton *scores = new KPushButton(i18n("Highscores"), coursePage);
+ connect(scores, SIGNAL(clicked()), this, SLOT(showHighscores()));
+ detailLayout->addWidget(scores);
+
+ detailLayout->addStretch();
+ detailLayout->addWidget(new KSeparator(coursePage));
+
+ minorLayout = new QHBoxLayout(detailLayout, spacingHint());
+
+ KPushButton *addCourseButton = new KPushButton(i18n("Add..."), coursePage);
+ minorLayout->addWidget(addCourseButton);
+ connect(addCourseButton, SIGNAL(clicked()), this, SLOT(addCourse()));
+
+ remove = new KPushButton(i18n("Remove"), coursePage);
+ minorLayout->addWidget(remove);
+ connect(remove, SIGNAL(clicked()), this, SLOT(removeCourse()));
+
+ courseSelected(curItem);
+ selectionChanged();
+ }
+
+ // options page
+ optionsPage = addPage(i18n("Options"), i18n("Game Options"));
+ QVBoxLayout *vlayout = new QVBoxLayout(optionsPage, marginHint(), spacingHint());
+
+ mode = new QCheckBox(i18n("&Strict mode"), optionsPage);
+ vlayout->addWidget(mode);
+ mode->setChecked(config->readBoolEntry("competition", false));
+
+ QLabel *desc = new QLabel(i18n("In strict mode, undo, editing, and switching holes is not allowed. This is generally for competition. Only in strict mode are highscores kept."), optionsPage);
+ desc->setTextFormat(RichText);
+ vlayout->addWidget(desc);
+}
+
+void NewGameDialog::slotOk()
+{
+ KConfig *config = kapp->config();
+
+ config->setGroup("New Game Dialog Mode");
+ config->writeEntry("competition", mode->isChecked());
+ if (enableCourses)
+ {
+ config->writeEntry("course", currentCourse);
+ config->writeEntry("extra", externCourses);
+ }
+
+ config->deleteGroup("New Game Dialog");
+ config->setGroup("New Game Dialog");
+
+ PlayerEditor *curEditor = 0;
+ int i = 0;
+ for (curEditor = editors.first(); curEditor; curEditor = editors.next(), ++i)
+ config->writeEntry(QString::number(i) + curEditor->name(), curEditor->color().name());
+
+ config->sync();
+
+ KDialogBase::slotOk();
+}
+
+void NewGameDialog::courseSelected(int index)
+{
+ currentCourse = *names.at(index);
+
+ CourseInfo &curinfo = info[currentCourse];
+
+ name->setText(QString("<strong>%1</strong>").arg(curinfo.name));
+
+ author->setText(i18n("By %1").arg(curinfo.author));
+ par->setText(i18n("Par %1").arg(curinfo.par));
+ holes->setText(i18n("%1 Holes").arg(curinfo.holes));
+}
+
+void NewGameDialog::showHighscores()
+{
+ KScoreDialog *scoreDialog = new KScoreDialog(KScoreDialog::Name | KScoreDialog::Custom1 | KScoreDialog::Score, this);
+ scoreDialog->addField(KScoreDialog::Custom1, i18n("Par"), "Par");
+ scoreDialog->setConfigGroup(info[currentCourse].untranslatedName + QString(" Highscores"));
+ scoreDialog->setComment(i18n("High Scores for %1").arg(info[currentCourse].name));
+ scoreDialog->show();
+}
+
+void NewGameDialog::removeCourse()
+{
+ int curItem = courseList->currentItem();
+ if (curItem < 0)
+ return;
+
+ QString file = *names.at(curItem);
+ if (externCourses.contains(file) < 1)
+ return;
+
+ names.remove(file);
+ externCourses.remove(file);
+ courseList->removeItem(curItem);
+
+ selectionChanged();
+}
+
+void NewGameDialog::selectionChanged()
+{
+ const int curItem = courseList->currentItem();
+ remove->setEnabled(!(curItem < 0 || externCourses.contains(*names.at(curItem)) < 1));
+}
+
+void NewGameDialog::addCourse()
+{
+ QStringList files = KFileDialog::getOpenFileNames(":kourses", QString::fromLatin1("application/x-kourse"), this, i18n("Pick Kolf Course"));
+
+ bool hasDuplicates = false;
+
+ for (QStringList::Iterator fileIt = files.begin(); fileIt != files.end(); ++fileIt)
+ {
+ if (names.contains(*fileIt) > 0)
+ {
+ hasDuplicates = true;
+ continue;
+ }
+
+ CourseInfo curinfo;
+ KolfGame::courseInfo(curinfo, *fileIt);
+ info[*fileIt] = curinfo;
+ names.prepend(*fileIt);
+ externCourses.prepend(*fileIt);
+
+ courseList->insertItem(curinfo.name, 0);
+ }
+
+ if (hasDuplicates)
+ KMessageBox::information(this, i18n("Chosen course is already on course list."));
+
+ courseList->setCurrentItem(0);
+ courseSelected(0);
+ selectionChanged();
+}
+
+void NewGameDialog::addPlayer()
+{
+ if (editors.count() >= startColors.count())
+ return;
+
+ editors.append(new PlayerEditor(i18n("Player %1").arg(editors.count() + 1), *startColors.at(editors.count()), layout));
+ editors.last()->show();
+ connect(editors.last(), SIGNAL(deleteEditor(PlayerEditor *)), this, SLOT(deleteEditor(PlayerEditor *)));
+
+ enableButtons();
+}
+
+void NewGameDialog::deleteEditor(PlayerEditor *editor)
+{
+ if (editors.count() < 2)
+ return;
+
+ editors.removeRef(editor);
+
+ enableButtons();
+}
+
+void NewGameDialog::enableButtons()
+{
+ addButton->setEnabled(!(editors.count() >= startColors.count()));
+}
+
+/////////////////////////
+
+PlayerEditor::PlayerEditor(QString startName, QColor startColor, QWidget *parent, const char *_name)
+ : QWidget(parent, _name)
+{
+ QHBoxLayout *layout = new QHBoxLayout(this, KDialogBase::spacingHint());
+
+ if (!QPixmapCache::find("grass", grass))
+ {
+ grass.load(locate("appdata", "pics/grass.png"));
+ QPixmapCache::insert("grass", grass);
+ }
+ setBackgroundPixmap(grass);
+
+ editor = new KLineEdit(this);
+ layout->addWidget(editor);
+ editor->setFrame(false);
+ editor->setText(startName);
+ layout->addStretch();
+ layout->addWidget(colorButton = new KColorButton(startColor, this));
+ colorButton->setAutoMask(true);
+ colorButton->setBackgroundPixmap(grass);
+
+ KPushButton *remove = new KPushButton(i18n("Remove"), this);
+ remove->setAutoMask(true);
+ layout->addWidget(remove);
+ remove->setBackgroundPixmap(grass);
+ connect(remove, SIGNAL(clicked()), this, SLOT(removeMe()));
+}
+
+void PlayerEditor::removeMe()
+{
+ emit deleteEditor(this);
+}
+
+#include "newgame.moc"
diff --git a/kolf/newgame.h b/kolf/newgame.h
new file mode 100644
index 00000000..b9770a80
--- /dev/null
+++ b/kolf/newgame.h
@@ -0,0 +1,106 @@
+#ifndef NEWGAME_H
+#define NEWGAME_H
+
+#include <kdialogbase.h>
+#include <klineedit.h>
+#include <kcolorbutton.h>
+
+#include <qcheckbox.h>
+#include <qcolor.h>
+#include <qptrlist.h>
+#include <qstring.h>
+#include <kpushbutton.h>
+#include <qstringlist.h>
+#include <qvaluelist.h>
+#include <qwidget.h>
+
+#include "game.h"
+
+class KLineEdit;
+class QFrame;
+class QVBoxLayout;
+class QVBox;
+class QPainter;
+class KListBox;
+class QEvent;
+
+class PlayerEditor : public QWidget
+{
+ Q_OBJECT
+
+public:
+ PlayerEditor(QString name = QString::null, QColor = red, QWidget *parent = 0, const char *_name = 0);
+ QColor color() { return colorButton->color(); }
+ QString name() { return editor->text(); }
+ void setColor(QColor col) { colorButton->setColor(col); }
+ void setName(const QString &newname) { editor->setText(newname); }
+
+signals:
+ void deleteEditor(PlayerEditor *editor);
+
+private slots:
+ void removeMe();
+
+private:
+ KLineEdit *editor;
+ KColorButton *colorButton;
+ QPixmap grass;
+};
+
+class NewGameDialog : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ NewGameDialog(bool enableCourses, QWidget *parent, const char *_name = 0);
+ QPtrList<PlayerEditor> *players() { return &editors; }
+ bool competition() { return mode->isChecked(); }
+ QString course() { return currentCourse; }
+
+public slots:
+ void deleteEditor(PlayerEditor *);
+
+protected slots:
+ void slotOk();
+
+private slots:
+ void addPlayer();
+ void courseSelected(int);
+ void addCourse();
+ void removeCourse();
+ void selectionChanged();
+ void showHighscores();
+
+private:
+ QVBox *layout;
+ KPushButton *addButton;
+ QFrame *playerPage;
+ QScrollView *scroller;
+ QFrame *coursePage;
+ QFrame *optionsPage;
+ QValueList<QColor> startColors;
+ QPtrList<PlayerEditor> editors;
+ KPushButton *remove;
+ QCheckBox *mode;
+
+ QPixmap grass;
+
+ QStringList names;
+ QStringList externCourses;
+ QMap<QString, CourseInfo> info;
+
+ QStringList extraCourses;
+
+ KListBox *courseList;
+ QLabel *name;
+ QLabel *author;
+ QLabel *par;
+ QLabel *holes;
+
+ QString currentCourse;
+
+ void enableButtons();
+ bool enableCourses;
+};
+
+#endif
diff --git a/kolf/object.cpp b/kolf/object.cpp
new file mode 100644
index 00000000..aea33e01
--- /dev/null
+++ b/kolf/object.cpp
@@ -0,0 +1,2 @@
+#include "object.h"
+#include "object.moc"
diff --git a/kolf/object.h b/kolf/object.h
new file mode 100644
index 00000000..a2bf2ef1
--- /dev/null
+++ b/kolf/object.h
@@ -0,0 +1,30 @@
+// it seems that OBJECT_H is used by something else
+
+#ifndef KOLF_OBJECT_H
+#define KOLF_OBJECT_H
+
+#include <qcanvas.h>
+#include <qstring.h>
+#include <qobject.h>
+
+class Object : public QObject
+{
+ Q_OBJECT
+
+public:
+ Object(QObject *parent = 0, const char *name = 0) : QObject(parent, name) { m_addOnNewHole = false; }
+ virtual QCanvasItem *newObject(QCanvas * /*canvas*/) { return 0; }
+ QString name() { return m_name; }
+ QString _name() { return m__name; }
+ QString author() { return m_author; }
+ bool addOnNewHole() { return m_addOnNewHole; }
+
+protected:
+ QString m_name;
+ QString m__name;
+ QString m_author;
+ bool m_addOnNewHole;
+};
+typedef QPtrList<Object> ObjectList;
+
+#endif
diff --git a/kolf/objects/Makefile.am b/kolf/objects/Makefile.am
new file mode 100644
index 00000000..6410a006
--- /dev/null
+++ b/kolf/objects/Makefile.am
@@ -0,0 +1 @@
+# SUBDIRS = test poolball
diff --git a/kolf/objects/poolball/Makefile.am b/kolf/objects/poolball/Makefile.am
new file mode 100644
index 00000000..0388f4dd
--- /dev/null
+++ b/kolf/objects/poolball/Makefile.am
@@ -0,0 +1,13 @@
+INCLUDES= $(all_includes)
+lib_LTLIBRARIES = libkolfpoolball.la
+
+libkolfpoolball_la_SOURCES = poolball.cpp
+
+libkolfpoolball_la_LDFLAGS = $(all_libraries) $(LIB_KIO) -lkolf -module -avoid-version
+
+libkolfpoolball_la_METASOURCES = AUTO
+
+noinst_HEADERS = poolball.h
+
+kolf_DATA = poolball.plugin
+kolfdir = $(kde_datadir)/kolf
diff --git a/kolf/objects/poolball/poolball.cpp b/kolf/objects/poolball/poolball.cpp
new file mode 100644
index 00000000..a5ca80ec
--- /dev/null
+++ b/kolf/objects/poolball/poolball.cpp
@@ -0,0 +1,86 @@
+#include <qbrush.h>
+#include <qcolor.h>
+#include <qcanvas.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qpainter.h>
+
+#include <klocale.h>
+#include <klibloader.h>
+#include <kapplication.h>
+#include <kdebug.h>
+#include <knuminput.h>
+#include <kconfig.h>
+
+#include <kolf/statedb.h>
+#include <kolf/canvasitem.h>
+#include "poolball.h"
+
+K_EXPORT_COMPONENT_FACTORY(libkolfpoolball, PoolBallFactory)
+QObject *PoolBallFactory::createObject (QObject *, const char *, const char *, const QStringList &) { return new PoolBallObj; }
+
+PoolBall::PoolBall(QCanvas *canvas)
+ : Ball(canvas)
+{
+ setBrush(black);
+ m_number = 1;
+}
+
+void PoolBall::save(KConfig *cfg)
+{
+ cfg->writeEntry("number", number());
+}
+
+void PoolBall::saveState(StateDB *db)
+{
+ db->setPoint(QPoint(x(), y()));
+}
+
+void PoolBall::load(KConfig *cfg)
+{
+ setNumber(cfg->readNumEntry("number", 1));
+}
+
+void PoolBall::loadState(StateDB *db)
+{
+ move(db->point().x(), db->point().y());
+ setVelocity(0, 0);
+ setState(Stopped);
+}
+
+void PoolBall::draw(QPainter &p)
+{
+ // we should draw the number here
+ Ball::draw(p);
+}
+
+PoolBallConfig::PoolBallConfig(PoolBall *poolBall, QWidget *parent)
+ : Config(parent), m_poolBall(poolBall)
+{
+ QVBoxLayout *layout = new QVBoxLayout(this, marginHint(), spacingHint());
+
+ layout->addStretch();
+
+ QLabel *num = new QLabel(i18n("Number:"), this);
+ layout->addWidget(num);
+ KIntNumInput *slider = new KIntNumInput(m_poolBall->number(), this);
+ slider->setRange(1, 15);
+ layout->addWidget(slider);
+
+ layout->addStretch();
+
+ connect(slider, SIGNAL(valueChanged(int)), this, SLOT(numberChanged(int)));
+}
+
+void PoolBallConfig::numberChanged(int newNumber)
+{
+ m_poolBall->setNumber(newNumber);
+ changed();
+}
+
+Config *PoolBall::config(QWidget *parent)
+{
+ return new PoolBallConfig(this, parent);
+}
+
+#include "poolball.moc"
diff --git a/kolf/objects/poolball/poolball.h b/kolf/objects/poolball/poolball.h
new file mode 100644
index 00000000..eeb851b2
--- /dev/null
+++ b/kolf/objects/poolball/poolball.h
@@ -0,0 +1,63 @@
+#ifndef KOLFPOOLBALL_H
+#define KOLFPOOLBALL_H
+
+#include <qcanvas.h>
+#include <qobject.h>
+#include <qpainter.h>
+
+#include <klibloader.h>
+
+#include <kolf/ball.h>
+#include <kolf/canvasitem.h>
+#include <kolf/config.h>
+#include <kolf/object.h>
+
+class StateDB;
+class KConfig;
+
+class PoolBallFactory : KLibFactory { Q_OBJECT public: QObject *createObject(QObject *, const char *, const char *, const QStringList & = QStringList()); };
+
+class PoolBall : public Ball
+{
+public:
+ PoolBall(QCanvas *canvas);
+
+ virtual bool deleteable() const { return true; }
+
+ virtual Config *config(QWidget *parent);
+ virtual void saveState(StateDB *);
+ virtual void save(KConfig *cfg);
+ virtual void loadState(StateDB *);
+ virtual void load(KConfig *cfg);
+ virtual void draw(QPainter &);
+ virtual bool fastAdvance() const { return true; }
+
+ int number() const { return m_number; }
+ void setNumber(int newNumber) { m_number = newNumber; update(); }
+
+private:
+ int m_number;
+};
+
+class PoolBallConfig : public Config
+{
+ Q_OBJECT
+
+public:
+ PoolBallConfig(PoolBall *poolBall, QWidget *parent);
+
+private slots:
+ void numberChanged(int);
+
+private:
+ PoolBall *m_poolBall;
+};
+
+class PoolBallObj : public Object
+{
+public:
+ PoolBallObj() { m_name = i18n("Pool Ball"); m__name = "poolball"; m_author = "Jason Katz-Brown"; }
+ virtual QCanvasItem *newObject(QCanvas *canvas) { return new PoolBall(canvas); }
+};
+
+#endif
diff --git a/kolf/objects/poolball/poolball.plugin b/kolf/objects/poolball/poolball.plugin
new file mode 100644
index 00000000..c305251f
--- /dev/null
+++ b/kolf/objects/poolball/poolball.plugin
@@ -0,0 +1 @@
+Filename=libkolfpoolball
diff --git a/kolf/objects/test/Makefile.am b/kolf/objects/test/Makefile.am
new file mode 100644
index 00000000..4e9bb33f
--- /dev/null
+++ b/kolf/objects/test/Makefile.am
@@ -0,0 +1,13 @@
+INCLUDES= $(all_includes)
+lib_LTLIBRARIES = libkolftest.la
+
+libkolftest_la_SOURCES = test.cpp
+
+libkolftest_la_LDFLAGS = $(all_libraries) $(LIB_KIO) -lkolf -module -avoid-version
+
+libkolftest_la_METASOURCES = AUTO
+
+noinst_HEADERS = test.h
+
+kolf_DATA = test.plugin
+kolfdir = $(kde_datadir)/kolf
diff --git a/kolf/objects/test/test.cpp b/kolf/objects/test/test.cpp
new file mode 100644
index 00000000..2c3d564f
--- /dev/null
+++ b/kolf/objects/test/test.cpp
@@ -0,0 +1,104 @@
+#include <qbrush.h>
+#include <qcolor.h>
+#include <qcanvas.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qslider.h>
+
+#include <klocale.h>
+#include <klibloader.h>
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kconfig.h>
+
+#include "test.h"
+
+K_EXPORT_COMPONENT_FACTORY(libkolftest, TestFactory)
+QObject *TestFactory::createObject (QObject * /*parent*/, const char * /*name*/, const char * /*classname*/, const QStringList & /*args*/)
+{ return new TestObj; }
+
+Test::Test(QCanvas *canvas)
+ : QCanvasEllipse(60, 40, canvas), count(0), m_switchEvery(20)
+{
+ // force to the bottom of other objects
+ setZ(-100000);
+
+ // we want calls to advance() even though we have no velocity
+ setAnimated(true);
+}
+
+void Test::advance(int phase)
+{
+ QCanvasEllipse::advance(phase);
+
+ // phase is either 0 or 1, only calls with 1 should be handled
+ if (phase == 1)
+ {
+ // this makes it so the body is called every
+ // m_switchEvery times
+ if (count % m_switchEvery == 0)
+ {
+ // random color
+ const QColor myColor((QRgb)(kapp->random() % 0x01000000));
+
+ // set the brush, so our shape is drawn
+ // with the random color
+ setBrush(QBrush(myColor));
+
+ count = 0;
+ }
+
+ count++;
+ }
+}
+
+void Test::save(KConfig *cfg)
+{
+ // save our option from the course
+ // (courses are represented as KConfig files)
+ cfg->writeEntry("switchEvery", switchEvery());
+}
+
+void Test::load(KConfig *cfg)
+{
+ // load our option
+ setSwitchEvery(cfg->readNumEntry("switchEvery", 50));
+}
+
+TestConfig::TestConfig(Test *test, QWidget *parent)
+ : Config(parent), m_test(test)
+{
+ QVBoxLayout *layout = new QVBoxLayout(this, marginHint(), spacingHint());
+
+ layout->addStretch();
+
+ layout->addWidget(new QLabel(i18n("Flash speed"), this));
+
+ QHBoxLayout *hlayout = new QHBoxLayout(layout, spacingHint());
+ QLabel *slow = new QLabel(i18n("Slow"), this);
+ hlayout->addWidget(slow);
+ QSlider *slider = new QSlider(1, 100, 5, 101 - m_test->switchEvery(), Qt::Horizontal, this);
+ hlayout->addWidget(slider);
+ QLabel *fast = new QLabel(i18n("Fast"), this);
+ hlayout->addWidget(fast);
+
+ layout->addStretch();
+
+ connect(slider, SIGNAL(valueChanged(int)), this, SLOT(switchEveryChanged(int)));
+}
+
+void TestConfig::switchEveryChanged(int news)
+{
+ // update our object
+ m_test->setSwitchEvery((101 - news));
+
+ // tells Kolf the hole was modified
+ changed();
+}
+
+Config *Test::config(QWidget *parent)
+{
+ return new TestConfig(this, parent);
+}
+
+#include "test.moc"
diff --git a/kolf/objects/test/test.h b/kolf/objects/test/test.h
new file mode 100644
index 00000000..3086a578
--- /dev/null
+++ b/kolf/objects/test/test.h
@@ -0,0 +1,56 @@
+#ifndef KOLFTEST_H
+#define KOLFTEST_H
+
+#include <qcanvas.h>
+#include <qobject.h>
+
+#include <klibloader.h>
+
+#include <kolf/canvasitem.h>
+#include <kolf/object.h>
+
+class KConfig;
+
+class TestFactory : KLibFactory { Q_OBJECT public: QObject *createObject(QObject *, const char *, const char *, const QStringList & = QStringList()); };
+
+class Test : public QCanvasEllipse, public CanvasItem
+{
+public:
+ Test(QCanvas *canvas);
+
+ virtual Config *config(QWidget *parent);
+ virtual void save(KConfig *cfg);
+ virtual void load(KConfig *cfg);
+
+ virtual void advance(int phase);
+
+ int switchEvery() const { return m_switchEvery / 2; }
+ void setSwitchEvery(int news) { m_switchEvery = news * 2; }
+
+private:
+ int count;
+ int m_switchEvery;
+};
+
+class TestConfig : public Config
+{
+ Q_OBJECT
+
+public:
+ TestConfig(Test *test, QWidget *parent);
+
+private slots:
+ void switchEveryChanged(int news);
+
+private:
+ Test *m_test;
+};
+
+class TestObj : public Object
+{
+public:
+ TestObj() { m_name = i18n("Flash"); m__name = "flash"; m_author = "Jason Katz-Brown"; }
+ virtual QCanvasItem *newObject(QCanvas *canvas) { return new Test(canvas); }
+};
+
+#endif
diff --git a/kolf/objects/test/test.plugin b/kolf/objects/test/test.plugin
new file mode 100644
index 00000000..c58bec40
--- /dev/null
+++ b/kolf/objects/test/test.plugin
@@ -0,0 +1 @@
+Filename=libkolftest
diff --git a/kolf/pics/Makefile.am b/kolf/pics/Makefile.am
new file mode 100644
index 00000000..e5515a85
--- /dev/null
+++ b/kolf/pics/Makefile.am
@@ -0,0 +1 @@
+KDE_ICON = AUTO
diff --git a/kolf/pics/hi128-app-kolf.png b/kolf/pics/hi128-app-kolf.png
new file mode 100644
index 00000000..e11f930c
--- /dev/null
+++ b/kolf/pics/hi128-app-kolf.png
Binary files differ
diff --git a/kolf/pics/hi16-app-kolf.png b/kolf/pics/hi16-app-kolf.png
new file mode 100644
index 00000000..3dfe8c86
--- /dev/null
+++ b/kolf/pics/hi16-app-kolf.png
Binary files differ
diff --git a/kolf/pics/hi22-app-kolf.png b/kolf/pics/hi22-app-kolf.png
new file mode 100644
index 00000000..0a06094c
--- /dev/null
+++ b/kolf/pics/hi22-app-kolf.png
Binary files differ
diff --git a/kolf/pics/hi32-app-kolf.png b/kolf/pics/hi32-app-kolf.png
new file mode 100644
index 00000000..06bf8809
--- /dev/null
+++ b/kolf/pics/hi32-app-kolf.png
Binary files differ
diff --git a/kolf/pics/hi48-app-kolf.png b/kolf/pics/hi48-app-kolf.png
new file mode 100644
index 00000000..422a8139
--- /dev/null
+++ b/kolf/pics/hi48-app-kolf.png
Binary files differ
diff --git a/kolf/pics/hi64-app-kolf.png b/kolf/pics/hi64-app-kolf.png
new file mode 100644
index 00000000..e6e7bccb
--- /dev/null
+++ b/kolf/pics/hi64-app-kolf.png
Binary files differ
diff --git a/kolf/pluginloader.cpp b/kolf/pluginloader.cpp
new file mode 100644
index 00000000..23d1a494
--- /dev/null
+++ b/kolf/pluginloader.cpp
@@ -0,0 +1,65 @@
+#include <qstring.h>
+#include <qvaluelist.h>
+#include <qstringlist.h>
+#include <qfile.h>
+#include <qobject.h>
+
+#include <kdebug.h>
+#include <kglobal.h>
+#include <kstandarddirs.h>
+#include <ksimpleconfig.h>
+#include <klibloader.h>
+
+#include "pluginloader.h"
+
+ObjectList *PluginLoader::loadAll()
+{
+ ObjectList *ret = new ObjectList;
+
+ QStringList libs;
+ QStringList files = KGlobal::dirs()->findAllResources("appdata", "*.plugin", false, true);
+
+ for (QStringList::Iterator it = files.begin(); it != files.end(); ++it)
+ {
+ KSimpleConfig cfg(*it);
+ QString filename(cfg.readEntry("Filename", ""));
+
+ libs.append(filename);
+ }
+
+ for (QStringList::Iterator it = libs.begin(); it != libs.end(); ++it)
+ {
+ Object *newObject = load(*it);
+ if (newObject)
+ ret->append(newObject);
+ }
+
+ return ret;
+}
+
+Object *PluginLoader::load(const QString &filename)
+{
+ KLibFactory *factory = KLibLoader::self()->factory(filename.latin1());
+
+ if (!factory)
+ {
+ kdWarning() << "no factory for " << filename << "!" << endl;
+ return 0;
+ }
+
+ QObject *newObject = factory->create(0, "objectInstance", "Object");
+
+ if (!newObject)
+ {
+ kdWarning() << "no newObject for " << filename << "!" << endl;
+ return 0;
+ }
+
+ Object *ret = dynamic_cast<Object *>(newObject);
+
+ if (!ret)
+ kdWarning() << "no ret for " << filename << "!" << endl;
+
+ return ret;
+}
+
diff --git a/kolf/pluginloader.h b/kolf/pluginloader.h
new file mode 100644
index 00000000..8d43db66
--- /dev/null
+++ b/kolf/pluginloader.h
@@ -0,0 +1,13 @@
+#ifndef PLUGINLOADER_H
+#define PLUGINLOADER_H
+
+#include <object.h>
+#include <qstring.h>
+
+namespace PluginLoader
+{
+ ObjectList *loadAll();
+ Object *load(const QString &);
+}
+
+#endif
diff --git a/kolf/pool.kolf b/kolf/pool.kolf
new file mode 100644
index 00000000..bd63df93
--- /dev/null
+++ b/kolf/pool.kolf
@@ -0,0 +1,303 @@
+[0-course@-50,-50]
+author=Jason Katz-Brown
+name=Pool
+
+[1-ball@205,302]
+dummykey=true
+
+[1-blackhole@103,191|25]
+exit=67,195
+exitDeg=90
+maxspeed=3
+minspeed=3
+
+[1-blackhole@110,336|22]
+exit=66,395
+exitDeg=90
+maxspeed=4
+minspeed=4
+
+[1-blackhole@115,54|21]
+exit=129,25
+exitDeg=0
+maxspeed=2
+minspeed=2
+
+[1-blackhole@223,26|55]
+exit=121,97
+exitDeg=0
+maxspeed=2
+minspeed=2
+
+[1-blackhole@332,335|23]
+exit=382,393
+exitDeg=90
+maxspeed=4
+minspeed=4
+
+[1-blackhole@335,53|20]
+exit=329,19
+exitDeg=180
+maxspeed=2
+minspeed=2
+
+[1-blackhole@348,189|24]
+exit=381,170
+exitDeg=90
+maxspeed=3
+minspeed=3
+
+[1-hole@-50,-50|0]
+borderWalls=false
+hasFinalLoad=false
+maxstrokes=4
+par=3
+
+[1-poolball@-215,24|33]
+number=1
+
+[1-poolball@225,138|31]
+number=1
+
+[1-poolball@229,133|65]
+number=1
+
+[1-poolball@230,143|28]
+number=1
+
+[1-poolball@233,149|35]
+number=1
+
+[1-poolball@234,128|64]
+number=1
+
+[1-poolball@235,138|3]
+number=1
+
+[1-poolball@238,144|30]
+number=1
+
+[1-poolball@239,133|34]
+number=1
+
+[1-poolball@243,139|29]
+number=1
+
+[1-poolball@851,-273|32]
+number=1
+
+[1-slope@-2,0|57]
+grade=5
+gradient=Diagonal
+height=120
+reversed=true
+stuckOnGround=false
+width=127
+
+[1-slope@194,2|54]
+grade=4
+gradient=Elliptic
+height=50
+reversed=true
+stuckOnGround=false
+width=50
+
+[1-slope@314,315|61]
+grade=4
+gradient=Elliptic
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[1-slope@318,32|60]
+grade=4
+gradient=Elliptic
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[1-slope@329,170|59]
+grade=4
+gradient=Elliptic
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[1-slope@332,0|56]
+grade=8
+gradient=Opposite Diagonal
+height=66
+reversed=true
+stuckOnGround=false
+width=68
+
+[1-slope@78,168|58]
+grade=4
+gradient=Elliptic
+height=44
+reversed=true
+stuckOnGround=false
+width=44
+
+[1-slope@88,315|63]
+grade=4
+gradient=Elliptic
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[1-slope@94,32|62]
+grade=4
+gradient=Elliptic
+height=40
+reversed=true
+stuckOnGround=false
+width=40
+
+[1-wall@0,0|10]
+endPoint=352,56
+startPoint=337,70
+
+[1-wall@0,0|11]
+endPoint=114,72
+startPoint=114,174
+
+[1-wall@0,0|12]
+endPoint=124,40
+startPoint=135,55
+
+[1-wall@0,0|13]
+endPoint=337,206
+startPoint=337,317
+
+[1-wall@0,0|14]
+endPoint=315,332
+startPoint=326,350
+
+[1-wall@0,0|15]
+endPoint=129,332
+startPoint=117,349
+
+[1-wall@0,0|16]
+endPoint=114,72
+startPoint=100,65
+
+[1-wall@0,0|17]
+endPoint=347,330
+startPoint=337,317
+
+[1-wall@0,0|18]
+endPoint=116,316
+startPoint=91,332
+
+[1-wall@0,0|19]
+endPoint=337,206
+startPoint=355,213
+
+[1-wall@0,0|26]
+endPoint=116,206
+startPoint=101,211
+
+[1-wall@0,0|27]
+endPoint=114,174
+startPoint=99,174
+
+[1-wall@0,0|36]
+endPoint=336,352
+startPoint=326,350
+
+[1-wall@0,0|37]
+endPoint=347,330
+startPoint=349,343
+
+[1-wall@0,0|38]
+endPoint=117,349
+startPoint=102,353
+
+[1-wall@0,0|39]
+endPoint=93,347
+startPoint=91,332
+
+[1-wall@0,0|4]
+endPoint=324,43
+startPoint=318,55
+
+[1-wall@0,0|40]
+endPoint=100,65
+startPoint=101,47
+
+[1-wall@0,0|41]
+endPoint=110,38
+startPoint=101,47
+
+[1-wall@0,0|42]
+endPoint=352,42
+startPoint=352,56
+
+[1-wall@0,0|43]
+endPoint=324,43
+startPoint=343,36
+
+[1-wall@0,0|44]
+endPoint=352,42
+startPoint=343,36
+
+[1-wall@0,0|45]
+endPoint=124,40
+startPoint=110,38
+
+[1-wall@0,0|46]
+endPoint=102,353
+startPoint=93,347
+
+[1-wall@0,0|47]
+endPoint=336,352
+startPoint=349,343
+
+[1-wall@0,0|48]
+endPoint=99,174
+startPoint=83,185
+
+[1-wall@0,0|49]
+endPoint=101,211
+startPoint=84,201
+
+[1-wall@0,0|5]
+endPoint=116,206
+startPoint=116,316
+
+[1-wall@0,0|50]
+endPoint=84,201
+startPoint=83,185
+
+[1-wall@0,0|51]
+endPoint=365,178
+startPoint=351,167
+
+[1-wall@0,0|52]
+endPoint=368,195
+startPoint=355,213
+
+[1-wall@0,0|53]
+endPoint=365,178
+startPoint=368,195
+
+[1-wall@0,0|6]
+endPoint=315,332
+startPoint=129,332
+
+[1-wall@0,0|7]
+endPoint=337,70
+startPoint=337,175
+
+[1-wall@0,0|8]
+endPoint=351,167
+startPoint=337,175
+
+[1-wall@0,0|9]
+endPoint=318,55
+startPoint=135,55
diff --git a/kolf/printdialogpage.cpp b/kolf/printdialogpage.cpp
new file mode 100644
index 00000000..1fc6ad37
--- /dev/null
+++ b/kolf/printdialogpage.cpp
@@ -0,0 +1,35 @@
+#include <qcheckbox.h>
+#include <qlayout.h>
+#include <qwidget.h>
+
+#include <klocale.h>
+#include <kdialog.h>
+#include <kdebug.h>
+
+#include "printdialogpage.h"
+
+PrintDialogPage::PrintDialogPage(QWidget *parent, const char *name)
+ : KPrintDialogPage( parent, name )
+{
+ setTitle(i18n("Kolf Options"));
+
+ QVBoxLayout *layout = new QVBoxLayout(this, KDialog::marginHint(), KDialog::spacingHint());
+
+ titleCheck = new QCheckBox(i18n("Draw title text"), this);
+ titleCheck->setChecked(true);
+ layout->addWidget(titleCheck);
+}
+
+void PrintDialogPage::getOptions(QMap<QString, QString> &opts, bool /*incldef*/)
+{
+ opts["kde-kolf-title"] = titleCheck->isChecked()? "true" : "false";
+}
+
+void PrintDialogPage::setOptions(const QMap<QString, QString> &opts)
+{
+ QString setting = opts["kde-kolf-title"];
+ if (!!setting)
+ titleCheck->setChecked(setting == "true");
+}
+
+#include "printdialogpage.moc"
diff --git a/kolf/printdialogpage.h b/kolf/printdialogpage.h
new file mode 100644
index 00000000..047454a2
--- /dev/null
+++ b/kolf/printdialogpage.h
@@ -0,0 +1,27 @@
+#ifndef PRINTDIALOGPAGE_H
+#define PRINTDIALOGPAGE_H
+
+#include <kdeprint/kprintdialogpage.h>
+#include <qmap.h>
+#include <qstring.h>
+
+class QCheckBox;
+class QWidget;
+
+class PrintDialogPage : public KPrintDialogPage
+{
+ Q_OBJECT
+
+ public:
+ PrintDialogPage(QWidget *parent = 0, const char *name = 0);
+
+ //reimplement virtual functions
+ void getOptions(QMap<QString, QString> &opts, bool incldef = false);
+ void setOptions(const QMap<QString, QString> &opts);
+
+ private:
+ QCheckBox *bgCheck;
+ QCheckBox *titleCheck;
+};
+
+#endif
diff --git a/kolf/rtti.h b/kolf/rtti.h
new file mode 100644
index 00000000..00f58536
--- /dev/null
+++ b/kolf/rtti.h
@@ -0,0 +1,6 @@
+#ifndef KOLF_RTTI_H
+#define KOLF_RTTI_H
+
+enum RttiCodes { Rtti_NoCollision = 1001, Rtti_DontPlaceOn = 1002, Rtti_Ball = 1003, Rtti_Putter = 1004, Rtti_WallPoint = 1005, Rtti_Wall = 1006 };
+
+#endif
diff --git a/kolf/scoreboard.cpp b/kolf/scoreboard.cpp
new file mode 100644
index 00000000..a750156f
--- /dev/null
+++ b/kolf/scoreboard.cpp
@@ -0,0 +1,94 @@
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <qlayout.h>
+#include <qtable.h>
+#include <qwidget.h>
+#include <qheader.h>
+#include <qstring.h>
+
+#include "scoreboard.h"
+
+ScoreBoard::ScoreBoard(QWidget *parent, const char *name)
+ : QTable(1, 1, parent, name)
+{
+ vh = verticalHeader();
+ hh = horizontalHeader();
+ vh->setLabel(numRows() - 1, i18n("Par"));
+ hh->setLabel(numCols() - 1, i18n("Total"));
+
+ setFocusPolicy(QWidget::NoFocus);
+ setRowReadOnly(0, true);
+ setRowReadOnly(1, true);
+}
+
+void ScoreBoard::newHole(int par)
+{
+ int _numCols = numCols();
+ insertColumns(_numCols - 1);
+ hh->setLabel(numCols() - 2, QString::number(numCols() - 1));
+ setText(numRows() - 1, numCols() - 2, QString::number(par));
+ setColumnWidth(numCols() - 2, 40);
+
+ // update total
+ int tot = 0;
+ for (int i = 0; i < numCols() - 1; ++i)
+ tot += text(numRows() - 1, i).toInt();
+ setText(numRows() - 1, numCols() - 1, QString::number(tot));
+
+ // shrink cell...
+ setColumnWidth(numCols() - 2, 3);
+ // and make it big enough for the numbers
+ adjustColumn(numCols() - 2);
+}
+
+void ScoreBoard::newPlayer(const QString &name)
+{
+ //kdDebug(12007) << "name of new player is " << name << endl;
+ insertRows(numRows() - 1);
+ vh->setLabel(numRows() - 2, name);
+ setRowReadOnly(numRows() - 2, true);
+}
+
+void ScoreBoard::setScore(int id, int hole, int score)
+{
+ //kdDebug(12007) << "set score\n";
+ setText(id - 1, hole - 1, score > 0? QString::number(score) : QString(""));
+
+ QString name;
+ setText(id - 1, numCols() - 1, QString::number(total(id, name)));
+ if (hole >= numCols() - 2)
+ ensureCellVisible(id - 1, numCols() - 1);
+ else
+ ensureCellVisible(id - 1, hole - 1);
+
+ // shrink cell...
+ setColumnWidth(hole - 1, 3);
+ // and make it big enough for the numbers
+ adjustColumn(hole - 1);
+
+ setCurrentCell(id - 1, hole - 1);
+}
+
+void ScoreBoard::parChanged(int hole, int par)
+{
+ setText(numRows() - 1, hole - 1, QString::number(par));
+
+ // update total
+ int tot = 0;
+ for (int i = 0; i < numCols() - 1; ++i)
+ tot += text(numRows() - 1, i).toInt();
+ setText(numRows() - 1, numCols() - 1, QString::number(tot));
+}
+
+int ScoreBoard::total(int id, QString &name)
+{
+ int tot = 0;
+ for (int i = 0; i < numCols() - 1; i++)
+ tot += text(id - 1, i).toInt();
+ name = vh->label(id - 1);
+ //kdDebug(12007) << "tot is " << tot << endl;
+ return tot;
+}
+
+#include "scoreboard.moc"
diff --git a/kolf/scoreboard.h b/kolf/scoreboard.h
new file mode 100644
index 00000000..b9dc78f4
--- /dev/null
+++ b/kolf/scoreboard.h
@@ -0,0 +1,29 @@
+#ifndef SCOREBOARD_H
+#define SCOREBOARD_H
+
+#include <qtable.h>
+
+class QWidget;
+class QHeader;
+
+class ScoreBoard : public QTable
+{
+ Q_OBJECT
+
+public:
+ ScoreBoard(QWidget *parent = 0, const char *name = 0);
+ int total(int id, QString &name);
+
+public slots:
+ void newHole(int);
+ void newPlayer(const QString &name);
+ void setScore(int id, int hole, int score);
+ void parChanged(int hole, int par);
+
+private:
+ QTable *table;
+ QHeader *vh;
+ QHeader *hh;
+};
+
+#endif
diff --git a/kolf/slope.cpp b/kolf/slope.cpp
new file mode 100644
index 00000000..e2becea1
--- /dev/null
+++ b/kolf/slope.cpp
@@ -0,0 +1,585 @@
+#include <qbitmap.h>
+#include <qcheckbox.h>
+#include <qlabel.h>
+#include <qimage.h>
+#include <qpixmapcache.h>
+#include <qwhatsthis.h>
+
+#include <kapplication.h>
+#include <kcombobox.h>
+#include <kconfig.h>
+#include <knuminput.h>
+#include <kpixmapeffect.h>
+#include <kstandarddirs.h>
+
+#include "slope.h"
+
+Slope::Slope(QRect rect, QCanvas *canvas)
+ : QCanvasRectangle(rect, canvas), type(KImageEffect::VerticalGradient), grade(4), reversed(false), color(QColor("#327501"))
+{
+ stuckOnGround = false;
+ showingInfo = false;
+
+ gradientKeys[KImageEffect::VerticalGradient] = "Vertical";
+ gradientKeys[KImageEffect::HorizontalGradient] = "Horizontal";
+ gradientKeys[KImageEffect::DiagonalGradient] = "Diagonal";
+ gradientKeys[KImageEffect::CrossDiagonalGradient] = "Opposite Diagonal";
+ gradientKeys[KImageEffect::EllipticGradient] = "Elliptic";
+
+ gradientI18nKeys[KImageEffect::VerticalGradient] = i18n("Vertical");
+ gradientI18nKeys[KImageEffect::HorizontalGradient] = i18n("Horizontal");
+ gradientI18nKeys[KImageEffect::DiagonalGradient] = i18n("Diagonal");
+ gradientI18nKeys[KImageEffect::CrossDiagonalGradient] = i18n("Opposite Diagonal");
+ gradientI18nKeys[KImageEffect::EllipticGradient] = i18n("Circular");
+
+ setZ(-50);
+
+ if (!QPixmapCache::find("grass", grass))
+ {
+ grass.load(locate("appdata", "pics/grass.png"));
+ QPixmapCache::insert("grass", grass);
+ }
+
+ point = new RectPoint(color.light(), this, canvas);
+
+ QFont font(kapp->font());
+ font.setPixelSize(18);
+ text = new QCanvasText(canvas);
+ text->setZ(99999.99);
+ text->setFont(font);
+ text->setColor(white);
+
+ editModeChanged(false);
+ hideInfo();
+
+ // this does updatePixmap
+ setGradient("Vertical");
+}
+
+bool Slope::terrainCollisions() const
+{
+ // we are a terrain collision
+ return true;
+}
+
+void Slope::showInfo()
+{
+ showingInfo = true;
+ Arrow *arrow = 0;
+ for (arrow = arrows.first(); arrow; arrow = arrows.next())
+ {
+ arrow->setZ(z() + .01);
+ arrow->setVisible(true);
+ }
+ text->setVisible(true);
+}
+
+void Slope::hideInfo()
+{
+ showingInfo = false;
+ Arrow *arrow = 0;
+ for (arrow = arrows.first(); arrow; arrow = arrows.next())
+ arrow->setVisible(false);
+ text->setVisible(false);
+}
+
+void Slope::aboutToDie()
+{
+ delete point;
+ clearArrows();
+ delete text;
+}
+
+void Slope::clearArrows()
+{
+ Arrow *arrow = 0;
+ for (arrow = arrows.first(); arrow; arrow = arrows.next())
+ {
+ arrow->setVisible(false);
+ arrow->aboutToDie();
+ }
+ arrows.setAutoDelete(true);
+ arrows.clear();
+ arrows.setAutoDelete(false);
+}
+
+QPtrList<QCanvasItem> Slope::moveableItems() const
+{
+ QPtrList<QCanvasItem> ret;
+ ret.append(point);
+ return ret;
+}
+
+void Slope::setGrade(double newGrade)
+{
+ if (newGrade >= 0 && newGrade < 11)
+ {
+ grade = newGrade;
+ updatePixmap();
+ }
+}
+
+void Slope::setSize(int width, int height)
+{
+ newSize(width, height);
+}
+
+void Slope::newSize(int width, int height)
+{
+ if (type == KImageEffect::EllipticGradient)
+ {
+ QCanvasRectangle::setSize(width, width);
+ // move point back to good spot
+ moveBy(0, 0);
+
+ if (game && game->isEditing())
+ game->updateHighlighter();
+ }
+ else
+ QCanvasRectangle::setSize(width, height);
+
+ updatePixmap();
+ updateZ();
+}
+
+void Slope::moveBy(double dx, double dy)
+{
+ QCanvasRectangle::moveBy(dx, dy);
+
+ point->dontMove();
+ point->move(x() + width(), y() + height());
+
+ moveArrow();
+ updateZ();
+}
+
+void Slope::moveArrow()
+{
+ int xavg = 0, yavg = 0;
+ QPointArray r = areaPoints();
+ for (unsigned int i = 0; i < r.size(); ++i)
+ {
+ xavg += r[i].x();
+ yavg += r[i].y();
+ }
+ xavg /= r.size();
+ yavg /= r.size();
+
+ Arrow *arrow = 0;
+ for (arrow = arrows.first(); arrow; arrow = arrows.next())
+ arrow->move((double)xavg, (double)yavg);
+
+ if (showingInfo)
+ showInfo();
+ else
+ hideInfo();
+
+ text->move((double)xavg - text->boundingRect().width() / 2, (double)yavg - text->boundingRect().height() / 2);
+}
+
+void Slope::editModeChanged(bool changed)
+{
+ point->setVisible(changed);
+ moveBy(0, 0);
+}
+
+void Slope::updateZ(QCanvasRectangle *vStrut)
+{
+ const int area = (height() * width());
+ const int defaultz = -50;
+
+ double newZ = 0;
+
+ QCanvasRectangle *rect = 0;
+ if (!stuckOnGround)
+ rect = vStrut? vStrut : onVStrut();
+
+ if (rect)
+ {
+ if (area > (rect->width() * rect->height()))
+ newZ = defaultz;
+ else
+ newZ = rect->z();
+ }
+ else
+ newZ = defaultz;
+
+ setZ(((double)1 / (area == 0? 1 : area)) + newZ);
+}
+
+void Slope::load(KConfig *cfg)
+{
+ stuckOnGround = cfg->readBoolEntry("stuckOnGround", stuckOnGround);
+ grade = cfg->readDoubleNumEntry("grade", grade);
+ reversed = cfg->readBoolEntry("reversed", reversed);
+
+ // bypass updatePixmap which newSize normally does
+ QCanvasRectangle::setSize(cfg->readNumEntry("width", width()), cfg->readNumEntry("height", height()));
+ updateZ();
+
+ QString gradientType = cfg->readEntry("gradient", gradientKeys[type]);
+ setGradient(gradientType);
+}
+
+void Slope::save(KConfig *cfg)
+{
+ cfg->writeEntry("reversed", reversed);
+ cfg->writeEntry("width", width());
+ cfg->writeEntry("height", height());
+ cfg->writeEntry("gradient", gradientKeys[type]);
+ cfg->writeEntry("grade", grade);
+ cfg->writeEntry("stuckOnGround", stuckOnGround);
+}
+
+void Slope::draw(QPainter &painter)
+{
+ painter.drawPixmap(x(), y(), pixmap);
+}
+
+QPointArray Slope::areaPoints() const
+{
+ switch (type)
+ {
+ case KImageEffect::CrossDiagonalGradient:
+ {
+ QPointArray ret(3);
+ ret[0] = QPoint((int)x(), (int)y());
+ ret[1] = QPoint((int)x() + width(), (int)y() + height());
+ ret[2] = reversed? QPoint((int)x() + width(), y()) : QPoint((int)x(), (int)y() + height());
+
+ return ret;
+ }
+
+ case KImageEffect::DiagonalGradient:
+ {
+ QPointArray ret(3);
+ ret[0] = QPoint((int)x() + width(), (int)y());
+ ret[1] = QPoint((int)x(), (int)y() + height());
+ ret[2] = !reversed? QPoint((int)x() + width(), y() + height()) : QPoint((int)x(), (int)y());
+
+ return ret;
+ }
+
+ case KImageEffect::EllipticGradient:
+ {
+ QPointArray ret;
+ ret.makeEllipse((int)x(), (int)y(), width(), height());
+ return ret;
+ }
+
+ default:
+ return QCanvasRectangle::areaPoints();
+ }
+}
+
+bool Slope::collision(Ball *ball, long int /*id*/)
+{
+ if (grade <= 0)
+ return false;
+
+ double vx = ball->xVelocity();
+ double vy = ball->yVelocity();
+ double addto = 0.013 * grade;
+
+ const bool diag = type == KImageEffect::DiagonalGradient || type == KImageEffect::CrossDiagonalGradient;
+ const bool circle = type == KImageEffect::EllipticGradient;
+
+ double slopeAngle = 0;
+
+ if (diag)
+ slopeAngle = atan((double)width() / (double)height());
+ else if (circle)
+ {
+ const QPoint start(x() + (int)width() / 2.0, y() + (int)height() / 2.0);
+ const QPoint end((int)ball->x(), (int)ball->y());
+
+ Vector betweenVector(start, end);
+ const double factor = betweenVector.magnitude() / ((double)width() / 2.0);
+ slopeAngle = betweenVector.direction();
+
+ // this little bit by Daniel
+ addto *= factor * M_PI / 2;
+ addto = sin(addto);
+ }
+
+ switch (type)
+ {
+ case KImageEffect::HorizontalGradient:
+ reversed? vx += addto : vx -= addto;
+ break;
+
+ case KImageEffect::VerticalGradient:
+ reversed? vy += addto : vy -= addto;
+ break;
+
+ case KImageEffect::DiagonalGradient:
+ case KImageEffect::EllipticGradient:
+ reversed? vx += cos(slopeAngle) * addto : vx -= cos(slopeAngle) * addto;
+ reversed? vy += sin(slopeAngle) * addto : vy -= sin(slopeAngle) * addto;
+ break;
+
+ case KImageEffect::CrossDiagonalGradient:
+ reversed? vx -= cos(slopeAngle) * addto : vx += cos(slopeAngle) * addto;
+ reversed? vy += sin(slopeAngle) * addto : vy -= sin(slopeAngle) * addto;
+ break;
+
+ default:
+ break;
+ }
+
+ ball->setVelocity(vx, vy);
+ // check if the ball is at the center of a pit or mound
+ // or has otherwise stopped.
+ if (vx == 0 && vy ==0)
+ ball->setState(Stopped);
+ else
+ ball->setState(Rolling);
+
+ // do NOT do terrain collisions
+ return false;
+}
+
+void Slope::setGradient(QString text)
+{
+ for (QMap<KImageEffect::GradientType, QString>::Iterator it = gradientKeys.begin(); it != gradientKeys.end(); ++it)
+ {
+ if (it.data() == text)
+ {
+ setType(it.key());
+ return;
+ }
+ }
+
+ // extra forgiveness ;-) (note it's i18n keys)
+ for (QMap<KImageEffect::GradientType, QString>::Iterator it = gradientI18nKeys.begin(); it != gradientI18nKeys.end(); ++it)
+ {
+ if (it.data() == text)
+ {
+ setType(it.key());
+ return;
+ }
+ }
+}
+
+void Slope::setType(KImageEffect::GradientType type)
+{
+ this->type = type;
+
+ if (type == KImageEffect::EllipticGradient)
+ {
+ // calls updatePixmap
+ newSize(width(), height());
+ }
+ else
+ updatePixmap();
+}
+
+void Slope::updatePixmap()
+{
+ // make a gradient, make grass that's bright or dim
+ // merge into this->pixmap. This is drawn in draw()
+
+ // we update the arrows in this function
+ clearArrows();
+
+ const bool diag = type == KImageEffect::DiagonalGradient || type == KImageEffect::CrossDiagonalGradient;
+ const bool circle = type == KImageEffect::EllipticGradient;
+
+ const QColor darkColor = color.dark(100 + grade * (circle? 20 : 10));
+ const QColor lightColor = diag || circle? color.light(110 + (diag? 5 : .5) * grade) : color;
+ // hack only for circles
+ const bool _reversed = circle? !reversed : reversed;
+ QImage gradientImage = KImageEffect::gradient(QSize(width(), height()), _reversed? darkColor : lightColor, _reversed? lightColor : darkColor, type);
+
+ QPixmap qpixmap(width(), height());
+ QPainter p(&qpixmap);
+ p.drawTiledPixmap(QRect(0, 0, width(), height()), grass);
+ p.end();
+
+ const double length = sqrt(width() * width() + height() * height()) / 4;
+
+ if (circle)
+ {
+ const QColor otherLightColor = color.light(110 + 15 * grade);
+ const QColor otherDarkColor = darkColor.dark(110 + 20 * grade);
+ QImage otherGradientImage = KImageEffect::gradient(QSize(width(), height()), reversed? otherDarkColor : otherLightColor, reversed? otherLightColor : otherDarkColor, KImageEffect::DiagonalGradient);
+
+ QImage grassImage(qpixmap.convertToImage());
+
+ QImage finalGradientImage = KImageEffect::blend(otherGradientImage, gradientImage, .60);
+ pixmap.convertFromImage(KImageEffect::blend(grassImage, finalGradientImage, .40));
+
+ // make arrows
+ double angle = 0;
+ for (int i = 0; i < 4; ++i)
+ {
+ angle += M_PI / 2;
+ Arrow *arrow = new Arrow(canvas());
+ arrow->setLength(length);
+ arrow->setAngle(angle);
+ arrow->setReversed(reversed);
+ arrow->updateSelf();
+ arrows.append(arrow);
+ }
+ }
+ else
+ {
+ Arrow *arrow = new Arrow(canvas());
+
+ float ratio = 0;
+ float factor = 1;
+
+ double angle = 0;
+
+ switch (type)
+ {
+ case KImageEffect::HorizontalGradient:
+ angle = 0;
+ factor = .32;
+ break;
+
+ case KImageEffect::VerticalGradient:
+ angle = M_PI / 2;
+ factor = .32;
+ break;
+
+ case KImageEffect::DiagonalGradient:
+ angle = atan((double)width() / (double)height());
+
+ factor = .45;
+ break;
+
+ case KImageEffect::CrossDiagonalGradient:
+ angle = atan((double)width() / (double)height());
+ angle = M_PI - angle;
+
+ factor = .05;
+ break;
+
+ default:
+ break;
+ }
+
+ float factorPart = factor * 2;
+ // gradePart is out of 1
+ float gradePart = grade / 8.0;
+
+ ratio = factorPart * gradePart;
+
+ // reverse the reversed ones
+ if (reversed)
+ ratio *= -1;
+ else
+ angle += M_PI;
+
+ KPixmap kpixmap = qpixmap;
+ (void) KPixmapEffect::intensity(kpixmap, ratio);
+
+ QImage grassImage(kpixmap.convertToImage());
+
+ // okay, now we have a grass image that's
+ // appropriately lit, and a gradient;
+ // lets blend..
+ pixmap.convertFromImage(KImageEffect::blend(gradientImage, grassImage, .42));
+ arrow->setAngle(angle);
+ arrow->setLength(length);
+ arrow->updateSelf();
+
+ arrows.append(arrow);
+ }
+
+ text->setText(QString::number(grade));
+
+ if (diag || circle)
+ {
+ // make cleared bitmap
+ QBitmap bitmap(pixmap.width(), pixmap.height(), true);
+ QPainter bpainter(&bitmap);
+ bpainter.setBrush(color1);
+ QPointArray r = areaPoints();
+
+ // shift all the points
+ for (unsigned int i = 0; i < r.count(); ++i)
+ {
+ QPoint &p = r[i];
+ p.setX(p.x() - x());
+ p.setY(p.y() - y());
+ }
+ bpainter.drawPolygon(r);
+
+ // mask is drawn
+ pixmap.setMask(bitmap);
+ }
+
+ moveArrow();
+ update();
+}
+
+/////////////////////////
+
+SlopeConfig::SlopeConfig(Slope *slope, QWidget *parent)
+ : Config(parent)
+{
+ this->slope = slope;
+ QVBoxLayout *layout = new QVBoxLayout(this, marginHint(), spacingHint());
+ KComboBox *gradient = new KComboBox(this);
+ QStringList items;
+ QString curText;
+ for (QMap<KImageEffect::GradientType, QString>::Iterator it = slope->gradientI18nKeys.begin(); it != slope->gradientI18nKeys.end(); ++it)
+ {
+ if (it.key() == slope->curType())
+ curText = it.data();
+ items.append(it.data());
+ }
+ gradient->insertStringList(items);
+ gradient->setCurrentText(curText);
+ layout->addWidget(gradient);
+ connect(gradient, SIGNAL(activated(const QString &)), this, SLOT(setGradient(const QString &)));
+
+ layout->addStretch();
+
+ QCheckBox *reversed = new QCheckBox(i18n("Reverse direction"), this);
+ reversed->setChecked(slope->isReversed());
+ layout->addWidget(reversed);
+ connect(reversed, SIGNAL(toggled(bool)), this, SLOT(setReversed(bool)));
+
+ QHBoxLayout *hlayout = new QHBoxLayout(layout, spacingHint());
+ hlayout->addWidget(new QLabel(i18n("Grade:"), this));
+ KDoubleNumInput *grade = new KDoubleNumInput(this);
+ grade->setRange(0, 8, 1, true);
+ grade->setValue(slope->curGrade());
+ hlayout->addWidget(grade);
+ connect(grade, SIGNAL(valueChanged(double)), this, SLOT(gradeChanged(double)));
+
+ QCheckBox *stuck = new QCheckBox(i18n("Unmovable"), this);
+ QWhatsThis::add(stuck, i18n("Whether or not this slope can be moved by other objects, like floaters."));
+ stuck->setChecked(slope->isStuckOnGround());
+ layout->addWidget(stuck);
+ connect(stuck, SIGNAL(toggled(bool)), this, SLOT(setStuckOnGround(bool)));
+}
+
+void SlopeConfig::setGradient(const QString &text)
+{
+ slope->setGradient(text);
+ changed();
+}
+
+void SlopeConfig::setReversed(bool yes)
+{
+ slope->setReversed(yes);
+ changed();
+}
+
+void SlopeConfig::setStuckOnGround(bool yes)
+{
+ slope->setStuckOnGround(yes);
+ changed();
+}
+
+void SlopeConfig::gradeChanged(double newgrade)
+{
+ slope->setGrade(newgrade);
+ changed();
+}
+
+#include "slope.moc"
diff --git a/kolf/slope.h b/kolf/slope.h
new file mode 100644
index 00000000..d638354f
--- /dev/null
+++ b/kolf/slope.h
@@ -0,0 +1,98 @@
+#ifndef SLOPE_H
+#define SLOPE_H
+
+#include <kimageeffect.h>
+
+#include "game.h"
+
+class Slope;
+class SlopeConfig : public Config
+{
+ Q_OBJECT
+
+public:
+ SlopeConfig(Slope *slope, QWidget *parent);
+
+private slots:
+ void setGradient(const QString &text);
+ void setReversed(bool);
+ void setStuckOnGround(bool);
+ void gradeChanged(double);
+
+private:
+ Slope *slope;
+};
+
+class Slope : public QCanvasRectangle, public CanvasItem, public RectItem
+{
+public:
+ Slope(QRect rect, QCanvas *canvas);
+ virtual void aboutToDie();
+ virtual int rtti() const { return 1031; }
+
+ virtual void showInfo();
+ virtual void hideInfo();
+ virtual void editModeChanged(bool changed);
+ virtual bool canBeMovedByOthers() const { return !stuckOnGround; }
+ virtual QPtrList<QCanvasItem> moveableItems() const;
+ virtual Config *config(QWidget *parent) { return new SlopeConfig(this, parent); }
+ void setSize(int, int);
+ virtual void newSize(int width, int height);
+
+ virtual void moveBy(double dx, double dy);
+
+ virtual void draw(QPainter &painter);
+ virtual QPointArray areaPoints() const;
+
+ void setGradient(QString text);
+ KImageEffect::GradientType curType() const { return type; }
+ void setGrade(double grade);
+
+ double curGrade() const { return grade; }
+ void setColor(QColor color) { this->color = color; updatePixmap(); }
+ void setReversed(bool reversed) { this->reversed = reversed; updatePixmap(); }
+ bool isReversed() const { return reversed; }
+
+ bool isStuckOnGround() const { return stuckOnGround; }
+ void setStuckOnGround(bool yes) { stuckOnGround = yes; updateZ(); }
+
+ virtual void load(KConfig *cfg);
+ virtual void save(KConfig *cfg);
+
+ virtual bool collision(Ball *ball, long int id);
+ virtual bool terrainCollisions() const;
+
+ QMap<KImageEffect::GradientType, QString> gradientI18nKeys;
+ QMap<KImageEffect::GradientType, QString> gradientKeys;
+
+ virtual void updateZ(QCanvasRectangle *vStrut = 0);
+
+ void moveArrow();
+
+private:
+ KImageEffect::GradientType type;
+ inline void setType(KImageEffect::GradientType type);
+ bool showingInfo;
+ double grade;
+ bool reversed;
+ QColor color;
+ QPixmap pixmap;
+ void updatePixmap();
+ bool stuckOnGround;
+ QPixmap grass;
+
+ void clearArrows();
+
+ QPtrList<Arrow> arrows;
+ QCanvasText *text;
+ RectPoint *point;
+};
+
+class SlopeObj : public Object
+{
+public:
+ SlopeObj() { m_name = i18n("Slope"); m__name = "slope"; }
+ virtual QCanvasItem *newObject(QCanvas *canvas) { return new Slope(QRect(0, 0, 40, 40), canvas); }
+};
+
+#endif
diff --git a/kolf/sounds/FROM b/kolf/sounds/FROM
new file mode 100644
index 00000000..6c472d5b
--- /dev/null
+++ b/kolf/sounds/FROM
@@ -0,0 +1,11 @@
+blackhole.wav, blackholeputin.wav, blackholeeject.wav, and hit.wav:
+ Timo A. Hummel
+
+holed.wav
+ Jason Katz-Brown (drop ball into mug :)
+
+holeinone.wav
+ KReversi (kdegames/kreversi/sounds/reversi-won.wav)
+
+puddle.wav, wall.wav
+ Free sounds sites
diff --git a/kolf/sounds/Makefile.am b/kolf/sounds/Makefile.am
new file mode 100644
index 00000000..48609f33
--- /dev/null
+++ b/kolf/sounds/Makefile.am
@@ -0,0 +1,4 @@
+sounddir = $(kde_datadir)/kolf/sounds
+sound_DATA = wall.wav puddle.wav holeinone.wav holed.wav blackhole.wav blackholeputin.wav blackholeeject.wav hit.wav
+
+EXTRA_DIST=$(sound_DATA)
diff --git a/kolf/sounds/blackhole.wav b/kolf/sounds/blackhole.wav
new file mode 100644
index 00000000..30997110
--- /dev/null
+++ b/kolf/sounds/blackhole.wav
Binary files differ
diff --git a/kolf/sounds/blackholeeject.wav b/kolf/sounds/blackholeeject.wav
new file mode 100644
index 00000000..1c397dc2
--- /dev/null
+++ b/kolf/sounds/blackholeeject.wav
Binary files differ
diff --git a/kolf/sounds/blackholeputin.wav b/kolf/sounds/blackholeputin.wav
new file mode 100644
index 00000000..3b3bcc4d
--- /dev/null
+++ b/kolf/sounds/blackholeputin.wav
Binary files differ
diff --git a/kolf/sounds/hit.wav b/kolf/sounds/hit.wav
new file mode 100644
index 00000000..dd8bd048
--- /dev/null
+++ b/kolf/sounds/hit.wav
Binary files differ
diff --git a/kolf/sounds/holed.wav b/kolf/sounds/holed.wav
new file mode 100644
index 00000000..d8ccd4af
--- /dev/null
+++ b/kolf/sounds/holed.wav
Binary files differ
diff --git a/kolf/sounds/holeinone.wav b/kolf/sounds/holeinone.wav
new file mode 100644
index 00000000..8f5c0703
--- /dev/null
+++ b/kolf/sounds/holeinone.wav
Binary files differ
diff --git a/kolf/sounds/puddle.wav b/kolf/sounds/puddle.wav
new file mode 100644
index 00000000..d9c9d4ac
--- /dev/null
+++ b/kolf/sounds/puddle.wav
Binary files differ
diff --git a/kolf/sounds/wall.wav b/kolf/sounds/wall.wav
new file mode 100644
index 00000000..65591bb4
--- /dev/null
+++ b/kolf/sounds/wall.wav
Binary files differ
diff --git a/kolf/statedb.h b/kolf/statedb.h
new file mode 100644
index 00000000..522d147f
--- /dev/null
+++ b/kolf/statedb.h
@@ -0,0 +1,23 @@
+#ifndef KOLF_STATEDB_H
+#define KOLF_STATEDB_H
+
+#include <qmap.h>
+#include <qstring.h>
+#include <qpoint.h>
+
+// items can save their per-game states here
+// most don't have to do anything
+class StateDB
+{
+public:
+ void setPoint(const QPoint &point) { points[curName] = point; }
+ QPoint point() { return points[curName]; }
+ void setName(const QString &name) { curName = name; }
+ void clear() { points.clear(); }
+
+private:
+ QMap<QString, QPoint> points;
+ QString curName;
+};
+
+#endif
diff --git a/kolf/tutorial.kolf b/kolf/tutorial.kolf
new file mode 100644
index 00000000..cfc5f5af
--- /dev/null
+++ b/kolf/tutorial.kolf
@@ -0,0 +1,1245 @@
+[0-course@-50,-50]
+Name=Tutorial Course
+Name[af]=Tutoriaal Natuurlik
+Name[bg]=Обучение
+Name[bn]=টিউটোরিয়াল কোর্স
+Name[bs]=Teren za obuku
+Name[ca]=Tutorial de camp
+Name[cs]=Výukový kurz
+Name[da]=Øvebane
+Name[de]=Einstieg
+Name[el]=Πίστα εκμάθησης
+Name[es]=Campo de tutorial
+Name[et]=Õppeväljak
+Name[fi]=Harjoituskenttä
+Name[fr]=Parcours didacticiel
+Name[gl]=Campo de adestramento
+Name[he]=מסלול לימוד
+Name[hi]=शिक्षण कोर्स
+Name[hu]=Gyakorlópálya
+Name[is]=Kennslubraut
+Name[it]=Percorso scuola
+Name[ja]=チュートリアルコース
+Name[lv]=Apmācības kurss
+Name[mk]=Терен за учење
+Name[nb]=Øvingsbane
+Name[nl]=Lesparcours
+Name[nn]=Øvingsbane
+Name[nso]=Course ya Tutorial
+Name[pl]=Tor ćwiczebny
+Name[pt]=Percurso de Aprendizagem
+Name[pt_BR]=Curso Tutorial
+Name[ru]=Учебный курс
+Name[sk]=Výučba
+Name[sl]=Začetniško igrišče
+Name[sr]=Терен за упознавање
+Name[sr@Latn]=Teren za upoznavanje
+Name[sv]=Övningsbana
+Name[ta]=போதக மார்க்கம்
+Name[tg]=Курси Омӯзиш
+Name[tr]=Öğretici Pist
+Name[uk]=Курс навчання
+Name[ven]=Thero dza Ngudo
+Name[xh]=Imbuyekezo yokufundisiweyo
+Name[xx]=xxTutorial Coursexx
+Name[zh_CN]=教学路线
+Name[zh_TW]=教學的路線
+Name[zu]=Isifundo sokubuyekeza
+author=Jason Katz-Brown
+
+[1-ball@34,267]
+dummykey=true
+
+[1-cup@52,159|6]
+dummykey=true
+
+[1-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[1-sign@2,2|3]
+Comment=<h3>Welcome</h3> to the tutorial course for Kolf!
+Comment[bg]=<h3>Добре дошли</h3> на обучаващите нива по голф!
+Comment[bn]=কল্‌ফ এর টিউটোরিয়াল কোর্সে <h3>স্বাগতম</h3> !
+Comment[bs]=<h3>Dobrodošli</h3> u Kolf teren za obuku!
+Comment[ca]=<h3>Benvingut</h3> al tutorial de camp Kolf!
+Comment[da]=<h3>Velkommen</h3> til Kolfs øvebane!
+Comment[de]=<h3>Willkommen</h3> zur Einstiegsbahn in Kolf
+Comment[el]=<h3>Καλώς ήρθατε</h3> στην πίστα εκμάθησης για το Kolf!
+Comment[es]=¡<h3>Bienvenido</h3> al curso de Kolf!
+Comment[et]=<h3>Tere tulemast</h3> Kolfi õppeväljakule!
+Comment[fi]=<h3>Tervetuloa</h3> Kolfin harjoituskierrokselle!
+Comment[fr]=<h3>Bienvenue</h3> dans le didacticiel de Kolf !
+Comment[gl]=<h3>Benvido</h3> ao campo de adestramento de Kolf!
+Comment[he]=<h3>ברוכים הבאים</h3> למסלול הלימוד של Kolf!
+Comment[hi]=<h3>स्वागत</h3> कोल्फ के शिक्षण कोर्स में!
+Comment[hu]=<h3>Üdvözöljük</h3> a Kolf gyakorlópályáján!
+Comment[is]=<h3>Velkomin(n)</h3> á kennslubraut Kolf!
+Comment[it]=<h3>Benvenuto</h3> nel percorso-scuola per Kolf!
+Comment[ja]=<h3>ようこそ</h3> Kolf チュートリアルコースへ!
+Comment[lv]=<h3>Laipni lūgti</h3> ieks Kolf apmācības kursa!
+Comment[mk]=<h3>Добредојдовте</h3> во теренот за вежбање на Kolf!
+Comment[nb]=<h3>Velkommen</h3> til øvingsbanen i Kolf!
+Comment[nl]=<h3>Welkom</h3> bij het lesparcours voor Kolf!
+Comment[nn]=<h3>Velkomen</h3> til øvingsbanen i Kolf!
+Comment[pl]=<h3>Witamy</h3> na torze ćwiczebnym Kolfa!
+Comment[pt]=<h3>Bem-vindo</h3> ao percurso de aprendizagem do Kolf!
+Comment[pt_BR]=<h3>Bem-vindo(a)</h3> ao curso tutorial do Kolf!
+Comment[ru]=<h3>Добро пожаловать</h3> на учебный курс гольфа!
+Comment[sk]=<h3>Víta vás</h3> výučba v hre Kolf!
+Comment[sl]=<h3>Dobrodošli</h3> na začetniško stezo za Kolf!
+Comment[sr]=<h3>Добродошли</h3> у терен за упознавање Kolf-а!
+Comment[sr@Latn]=<h3>Dobrodošli</h3> u teren za upoznavanje Kolf-a!
+Comment[sv]=<h3>Välkommen</h3> till Kolfs övningsbana
+Comment[ta]=கோல்ஃப் போதக மார்க்கத்திற்கு<h3>வரவேற்பு</h3>!
+Comment[tg]=<h3>Марҳамат намоед</h3> ба курси омӯзиши Колф!
+Comment[tr]=<h3>Hoşgeldiniz</h3> Kolf'ün deneme pistine hoşgeldiniz!
+Comment[uk]=<h3>Ласкаво просимо</h3> до курсу навчання Kolf!
+Comment[ven]=<h3>Vhotanganedzwa</h3> kha thero dza ngudo ya Kolf!
+Comment[xh]=<h3>Wamkelekile</h3> kwimbuyiselo yokufundiweyo ye Kolf!
+Comment[xx]=xx<h3>Welcome</h3> to the tutorial course for Kolf!xx
+Comment[zh_CN]=<h3>欢迎</h3>加入 Kolf 教学路线!
+Comment[zh_TW]=<h3>歡迎</h3>來到 Kolf 的教學路線!
+Comment[zu]=<h3>Wamukelekile</h3> esifundweni sokubuyekeza se-Kolf!
+botWallVisible=true
+height=74
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=319
+
+[1-sign@73,81|4]
+Comment=To hit the ball, press and hold the Down Arrow or left mouse button. How long you hold down the mouse button or key determines strength of the shot.
+Comment[af]=Na getref die bal, druk en hou die Ondertoe Pyl van links muis knoppie. Hoe lang jy hou Ondertoe die muis knoppie van sleutel bepaal sterkte van die geskiet.
+Comment[bg]=Натиснете и задръжте клавиша "долна стрелка" или левия бутон на мишката, за да ударите топката. Силата на удара зависи от продължителността на задържане на бутона на мишката или клавиатурата.
+Comment[bn]=বলে আঘাত করার জন্য ডাউন অ্যারো অথবা মাউসের বাম বাটন চেপে ধরে রাখুন। আঘাতের ফলে বল কী পরিমাণ দূরত্ব অতিক্রম করবে তা নির্ভর করে আপনি কতক্ষণ যাবত্‍ কীবোর্ড বা মাউসের বাটন চেপে ধরে রাখছেন তার ওপর।
+Comment[bs]=Da biste udarili lopticu, pritisnite i držite dugme Strelica dole ili lijevo dugme miša. Snagu udarca određujete dužinom pritiska na tipku odnosno dugme miša.
+Comment[ca]=Per a picar la bola, prémer i mantindre la tecla de la fletxa cap avall o el botó esquerra del ratolí. El temps que el mantingueu premut determinarà la força del tir.
+Comment[da]=For at slå til bolden, tryk og hold ned-pilen eller venstre museknap nede. Slagets kraft afhænger af hvor lang tid du holder knappen i bund.
+Comment[de]=Halten Sie die linke Maustaste oder die Pfeiltaste abwärts gedrückt, um den Ball zu schlagen. Die Dauer des Tastendrucks entscheidet über die Wucht.
+Comment[el]=Για να χτυπήσετε τη μπάλα κρατήστε πατημένο το πλήκτρο του κάτω βέλους ή το αριστερό πλήκτρο του ποντικιού. Το πόσο κρατάτε πατημένο το πλήκτρο καθορίζει τη δύναμη της βολής.
+Comment[es]=Para golpear la bola, pulsar y mantener la tecla de dirección abajo o el botón izquierdo del ratón. El tiempo que mantiene pulsado el botón del ratón o la tecla determinan la intensidad del disparo.
+Comment[et]=Löömiseks vajuta ja hoia all klahvi "Nool alla" või vasakut hiirenuppu. Mida kauem nuppu all hoiad, seda tugevam löök.
+Comment[fi]=Lyödäksesi palloa, paina ja pidä alhaalla nuolta alas tai hiiren vasenta nappia. Painalluksen pituus määrittelee lyönnin voiman.
+Comment[fr]=Pour frapper la balle, actionnez la flèche Bas ou le bouton gauche de la souris. Le temps pendant lequel vous maintenez le bouton ou la touche enfoncée détermine la force de la frappe.
+Comment[gl]=Para golpear a bola, manteña premida a tecla 'Abaixo' ou o botón esquerdo do rato . O tempo que a manteña premida determinará o forza do golpe.
+Comment[he]=כדי לחבוט בכדור, לחץ והחזק את מקש החץ למטה או את הלחצן השמאלי של העכבר. משך הלחיצה על לחצן העכבר או המקש קובע את עוצמת החבטה.
+Comment[hi]=गेंद पर प्रहार करने के लिए नीचे तीर कुंजी या बायाँ माउस बटन दबाकर रखें. कितनी देर आप बटन या कुंजी दबाकर रखे रहते हैं, यह आपके प्रहार की शक्ति निर्धारित करता है.
+Comment[hu]=A labda elütéséhez tartsa benyomva a lefelé nyilat vagy a bal egérgombot. Annál erősebb lesz az ütés, minél tovább tartja lenyomva a billentyűt vagy az egérgombot.
+Comment[is]=Tila að slá boltann, ýttu og haltu niðri 'ör-niður' eða vinstri músahnappi. Hversu lengi þú heldur hnappnum niðri ákvarðar höggkraftinn.
+Comment[it]=Per colpire la pallina, premi a lungo la "freccia destra" o il bottone sinistro del mouse. Quanto più a lungo li tieni premuti, tanto più forte sarà il colpo.
+Comment[ja]=ボールを打つには、下キーか左のマウスボタンを押し続けます。押す時間が長いと、ショットが強くなります。
+Comment[lv]=Lai sistu pa bumbiņu, nospiediet un turiet nospiestu bulti'na uz leju taustiņu vai kreiso peles taustiņu. Taustiņa nospiešanas ilgums noteiks sitiena spēku.
+Comment[mk]=За да ја удрите топката притиснете ја и држете ја долната стрелка, или левото копче од глушецот. Јачината на ударот зависи од тоа колку долго го држите копчето на глушецот или стрелката.
+Comment[nb]=Trykk og hold nedoverpila eller venstre museknapp for å slå ballen. Styrken på slaget bestemmes av hvor lenge du holder knappen inne.
+Comment[nl]=Om een bal te raken, houdt u de pijltoets naar beneden of de linkermuisknop ingedrukt. De tijd dat u de muisknop of toets ingedrukt houdt bepaalt de kracht van de slag.
+Comment[nn]=Trykk og hald nede pil ned eller den venstre museknappen for å slå. Styrken på slaget vert avgjort av kor lenge du held nede knappen.
+Comment[pl]=Aby uderzyć piłeczkę naciśnij i przytrzymaj klawisz Strzałki w dół lub lewy przycisk myszy. Długość czasu przytrzymania przycisku myszy lub klawisza określa siłę strzału.
+Comment[pt]=Para acertar na bola, carregue e mantenha a tecla Baixo ou no botão esquerdo do rato. O tempo que mantiver carregado o botão do rato ou a tecla determina a força da tacada.
+Comment[pt_BR]=Para arremessar a bola, pressione e mantenha pressionada a seta para baixo ou o botão esquerdo do mouse. A quantidade de tempo que você segura este botão determina a força do arremesso.
+Comment[ru]=Чтобы ударить по мячу, нажмите стрелку вниз или левую кнопку мыши. Сила удара будет зависеть от того, как долго вы удерживаете нажатие.
+Comment[sk]=Úder do loptičky sa ovláda stlačením a podržaním šipky dolu alebo ľavého tlačidla myši. Podľa toho, ako dlho ho držíte, určíte silu úderu.
+Comment[sl]=Da udarite žogico, pritisnite in držite spodnjo puščico ali levi miškin gumb. Moč udarca je določena z dolžino trajanja pritiska miškinega gumba ali tipke.
+Comment[sr]=Да бисте ударили лоптицу притисните и држите стрелицу доле или лево дугме миша. Јачину ударца одређује колико дуго држите дугме миша или тастер притиснутим.
+Comment[sr@Latn]=Da biste udarili lopticu pritisnite i držite strelicu dole ili levo dugme miša. Jačinu udarca određuje koliko dugo držite dugme miša ili taster pritisnutim.
+Comment[sv]=För att slå bollen, håll nere och släpp neråtpilen eller vänster musknapp. Hur länge du håller nere musknappen eller tangenten bestämmer hur hårt slaget blir.
+Comment[ta]=பந்தை அடிக்க, கீழ்நோக்க அம்பையோ அல்லது சுட்டியின் இடது பொத்தானை அழுத்தி கொண்டிருக்கவும்.எவ்வளவு நேரம் அழுத்தி கொண்டிருக்கிறீர்களோ அவ்வளவு பலம் உங்கள் அடிக்கு கிடைக்கும்.
+Comment[tg]=Барои задан ба тӯб, тирчаи поёнро ё тугмаи чапи мушро пахш кунед, Қувваи Задан аз он вобаста мешавад, ки шумо чӣ қадар тугмаи муш ё калидро паахш кунед.
+Comment[tr]=Topa vurmak için aşağı ok ya da sol fare tuşuna basılı tutun. Basılı tutma süresi, topa vuruş hızınızı belirler.
+Comment[uk]=Щоб вдарити м'ячик, натисніть і тримайте стрілку вниз або ліву кнопку мишки. Сила удару буде залежати від того, наскільки довго ви будете тримати клавішу стрілки або кнопку мишки.
+Comment[xh]= Ukubetha ibhola cofa ubambe umkhonto ojonge phantsi okanye icala lasekhohlo lemouse. Ixesha olithathayo xa ucinezela iqhosha lemouse okanye iqhosa lekeyboard lithetha amandla obethe ngawo.
+Comment[xx]=xxTo hit the ball, press and hold the Down Arrow or left mouse button. How long you hold down the mouse button or key determines strength of the shot.xx
+Comment[zh_CN]=要击球,请按住下方向键或鼠标左键。您按住多久决定了击球的力量。
+Comment[zh_TW]=要擊球,持續按著 下 方向鍵或滑鼠左鍵。 您按著滑鼠或鍵盤按鍵的時間長短決定擊出的強度。
+Comment[zu]=Ukushaya ibhola, bamba umcibisholo obheke phansi noma inkinobho ye-mouse engakwesokunxele. Ubude besikhathi osithathayo ucindezele inkinobho ye-mouse noma ukhiye isho amandla okushaya ibhola.
+botWallVisible=true
+height=163
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=319
+
+[1-sign@74,251|7]
+Comment=To aim the putter, press the Left (rotate counterclockwise) and Right (rotate clockwise) Arrow keys or use the mouse.
+Comment[bg]=За да насочите стика, натиснете клавиш "лява стрелка" (за завъртане обратно на часовниковата стрелка) и "дясна стрелка" (по часовниковата стрелка) или използвайте мишката.
+Comment[bn]=পুটারের দিকে লক্ষ্যস্থির করার জন্য কীবোর্ডের বাম ( ঘড়ির কাটার বিপরীতে) এবং ডান ( ঘড়ির কাটার দিকে ) অ্যারো কী (Key) অথবা মাউস ব্যবহার করুন।
+Comment[bs]=Da nanišanite palicom, pritisnite strelicu lijevo (rotacija suprotno kazaljci sata) ili desno (rotacija u smijeru kazaljke sata) ili koristite miš.
+Comment[ca]=Per a dirigir el disparador, premeu les tecles de la fletxa esquerra (gira en el sentit contrari al rellotge) i dreta (gira en el sentit horari) o useu el ratolí.
+Comment[da]=For at sigte mod hullet brug venstrepil (rotér mod uret) og højrepil (rotér med uret), eller brug musen.
+Comment[de]=Um den Putter auszurichten, verwendet man entweder die linke bzw. rechte Pfeiltaste oder die Maus.
+Comment[es]=Para dirigir el empujador, pulse las teclas de dirección izquierda (rotar en sentido antihorario) y derecha (sentido horario) o use el ratón.
+Comment[et]=Putteri suunamiseks kasuta klahve "Nool vasakule" (pöörab vastupäeva) või "Nool paremale" (päripäeva) või hiirt.
+Comment[fi]=Suunnataksesi putteria paina vasenta (kierrä myötäpäivään) ja oikeaa (kierrä vastapäivään) kursorinäppäintä tai käytä hiirtä.
+Comment[fr]=Pour viser le trou, actionnez la touche Gauche (rotation anti-horaire) ou droite (rotation horaire) ou utilisez la souris.
+Comment[gl]=Para dirixir o 'empuxador' prema as teclas das frechas esquerda (sentido antihorario) e dereita (sentido horario) ou ben use o rato.
+Comment[he]=כדי לכוון את המחבט, הקש על מקש החץ הימני (סיבוב עם כיוון השעון) והשמאלי (סיבוב נגד כיוון השעון) או השתמש בעכבר.
+Comment[hi]=पुटर पर निशाना साधने के लिए बायाँ और दायाँ तीर कुंजी का प्रयोग करें या माउस को घड़ी की दिशा में या उलट दिशा में घुमाएँ
+Comment[hu]=Célzáshoz a balra (balra forgatás) és jobbra (jobbra forgatás) nyilak ill. az egér használható.
+Comment[is]=Til að breyta stefnu púttersins, ýttu á vinstri (Vinstri snúningur) og Hægri (Hægri snúningur) örvunum eða notaðu músina.
+Comment[it]=Per dirigere il putter, premi i tasti delle frecce sinistra (rotazione antioraria) e destra (rotazione oraria), oppure usa il mouse.
+Comment[ja]=左のキーを押すと、パターが左(反時計回り)に回り、右のキーを押すと パターが右(時計回り)に回ります。マウスも使えます。
+Comment[mk]=За да ја наместите палката, притиснете ги стрелките за лево (вртење обратно од стрелката на часовникот) и десно (вртење во правец на стрелката на часовникот) или употребете го глушецот.
+Comment[nb]=Trykk venstrepil (med klokka), høyrepil (mot klokka) eller bruk musa for å treffe hullet.
+Comment[nl]=Om op de putter te richten drukt u op de linker pijltjestoets (tegen de klok draaien), of op de rechter pijltjestoets (met de klok mee draaien), of gebruikt u de muis.
+Comment[nn]=Bruk musa eller venstre og høgre piltast for å sikta.
+Comment[pl]=Aby wycelować kij, wciśnij klawisz z lewą lub prawą strzałką (aby przekręcić odpowiednio przeciwnie lub zgodnie z ruchem wskazówek zegara) lub użyj myszy.
+Comment[pt]=Para apontar o taco, carregue nas teclas Esquerda (rodar no sentido contrário aos ponteiros do relógio) e Direita (rodar nos ponteiros do relógio) ou use o rato.
+Comment[pt_BR]=Para direcionar a mira, pressione a seta para Esquerda (rotação no sentido anti-horário) e Direita (sentido horário) ou use o mouse.
+Comment[ru]=Чтобы прицелиться, нажмите стрелку влево (поворот против часовой стрелки) или вправо (по часовой) или используйте мышь.
+Comment[sk]=Smer určíte pomocou šipky vľavo (otočenie proti smeru hodinových ručičiek) a vpravo (otočenie po smere) alebo použite myš.
+Comment[sl]=Da namerite palico, pritisnite puščici levo (zasuk v nasprotni smeri urinega kazalca) ali desno (zasuk v smeri urinega kazalca) ali uporabite miško.
+Comment[sr]=Да бисте нациљали путер притисните леву стрелицу (окреће супротно смеру казаљке на часовнику) или десну (у смеру казаљке на часовнику), или користите миша.
+Comment[sr@Latn]=Da biste naciljali puter pritisnite levu strelicu (okreće suprotno smeru kazaljke na časovniku) ili desnu (u smeru kazaljke na časovniku), ili koristite miša.
+Comment[sv]=För att sikta med puttern, tryck på vänster (rotera moturs) eller höger (rotera medurs) piltangent eller använd musen.
+Comment[ta]=எறிபவரைக் குறி வைக்க, இடது(கடிகாரசுழற்சிக்கு எதிராக சுற்று)மற்றும் வலது(கடிகாரசுழற்சியாக சுற்று) அம்பு விசைகள் அல்லது சுட்டியை பயன்படுத்தவும்.
+Comment[tg]=Барои нишон гирифтан тирчаҳои Чап (гардиш ба муқобили ақрабаки соат) ва Рост (гардиш ба самти ақрабаки соат) ё мушро истифода баред.
+Comment[tr]=Mekanizmayı ayarlamak için Sol ve Sağ klavye yön tuşlarını veya fareyi kullanın
+Comment[uk]=щоб прицілитись, натисніть клавішу лівої стрілки (повернути проти годинникової стрілки) або правої стрілки (повернути за годинниковою стрілкою), або скористайтесь мишкою.
+Comment[xh]=Jolisa induku ye golf, ncinizela Ekohlo (ukujikelezisa ngokwe kloko)ockwise) Nasekunene (jikelezisa ikloko) Itolo lezitshixo zebhodi okanye usebenzise i mouse.
+Comment[xx]=xxTo aim the putter, press the Left (rotate counterclockwise) and Right (rotate clockwise) Arrow keys or use the mouse.xx
+Comment[zh_CN]=要瞄准球推,请按住左方向键(逆时针)或右方向键(顺时针)或使用鼠标。
+Comment[zh_TW]=想要推球入洞,按左(逆時針旋轉)與 右(順時針旋轉)方向鍵或使用滑鼠。
+Comment[zu]=Ukuqondisa induku yokushaya, cindezela umcibisholo wangakwesokunxele (jikelezisa ngokuphambene newashi) kanye nowangakwesokudla (jikelezisa ngokwewashi) noma usebenzise i-mouse.
+botWallVisible=true
+height=142
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=320
+
+[10-ball@27,177]
+dummykey=true
+
+[10-bridge@280,206|20]
+botWallVisible=true
+height=109
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=false
+width=84
+
+[10-bridge@30,193|3]
+botWallVisible=false
+height=58
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=62
+
+[10-bridge@40,227|10]
+botWallVisible=false
+height=55
+leftWallVisible=false
+rightWallVisible=false
+topWallVisible=false
+width=58
+
+[10-cup@331,253|6]
+dummykey=true
+
+[10-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[10-puddle@-6,246|18]
+changeEnabled=false
+changeEvery=50
+height=56
+width=242
+
+[10-puddle@324,386|47]
+changeEnabled=false
+changeEvery=50
+height=296
+width=206
+
+[10-puddle@95,137|12]
+changeEnabled=false
+changeEvery=50
+height=238
+width=112
+
+[10-sign@7,9|4]
+Comment=<h3>Bridges</h3>Bridges can have walls on the top, bottom, left, or right.
+Comment[bg]= <h3>Мостове</h3>Мостовете могат да имат стени отгоре, отдолу, отляво или отдясно.
+Comment[bn]=<h3>ব্রীজ</h3> ব্রীজের উপরে, নীচে, ডানে, অথবা বামে দেয়াল থাকতে পারে।
+Comment[bs]=<h3>Mostovi</h3>Mostovi mogu imati zidove iznad, ispod, lijevo ili desno.
+Comment[ca]=<h3>Ponts</h3>Els ponts poden tindre baranes a dalt, a baix, a l'esquerra o a la dreta.
+Comment[da]=<h3>Broer</h3>Broer kan have vægge øverst, nederst, til venstre og til højre.
+Comment[de]=<h3>Brücken</h3>Brücken können oben, links, rechts oder unten Wände haben.
+Comment[el]=<h3>Γέφυρες</h3>Οι γέφυρες μπορεί να έχουν τοίχους πάνω, κάτω, δεξιά ή αριστερά.
+Comment[es]=<h3>Puentes</h3>Los puentes pueden tener paredes, en la parte superior, fondo, izquierdo o derecho.
+Comment[et]=<h3>Sillad</h3>Sildadel võivad olla seinad üleval, all, vasakul või paremal.
+Comment[fi]=<h3>Sillat</h3>Silloissa voi olla seinät ylhäällä, alhaalla, vasemmalla tai oikealla.
+Comment[fr]=<h3>Ponts</h3>Les ponts peuvent avoir des murs en haut, en bas, à droite ou à gauche.
+Comment[gl]=<h3>Pontes</h3>As pontes poden ter valados na parte superior, inferior,esquerda ou dereita.
+Comment[he]=<h3>גשרים</h3>לגשרים יכולים להיות קירות מלמעלה, מלמטה, משמאל או מימין.
+Comment[hi]=<h3>पुल</h3>पुलों के ऊपर, नीचे, बाएँ और दाएँ दीवार हो सकते हैं.
+Comment[hu]=<h3>Hidak</h3>A hidakhoz fal tartozhat felül, alul, jobbról vagy balról.
+Comment[is]= <h3>Brýr</h3>Brýr geta haft veggi að ofan,að neðan, til vinstri eða hægri,
+Comment[it]=<h3>Ponti</h3>. I ponti possono avere muri in alto, in basso. a destra o a sinistra.
+Comment[ja]=<h3>橋</h3>壁は橋の上と下と左と右端に存在することが出来ます。
+Comment[mk]=<h3>Мостови<h3>Мостовите може да имаат ѕидови одгоре, оддолу, одлево или оддесно.
+Comment[nb]=<h3>Broer</h3>Broer kan ha vegger på toppen, bunne, venstre, eller høyre.
+Comment[nl]= <h3>Bruggen</h3>Bruggen kunnen boven, links, rechts of onder muren hebben.
+Comment[nn]=<h3>Bruer</h3>Bruer kan ha rekkverk oppe, nede, til venstre eller til høgre.
+Comment[pl]=<h3>Mosty</h3>Mosty mogą mieć ściany na górze, na dole, po lewej lub po prawej.
+Comment[pt]=<h3>Pontes</h3>As pontes podem ter paredes no topo, no fundo, na esquerda ou na direita.
+Comment[pt_BR]=<h3>Pontes</h3>Pontes podem ter muros no topo, base, esquerda ou direita.
+Comment[ru]=<h3>Мосты</h3>Мосты могут иметь стены сверху, снизу, слева или справа.
+Comment[sk]=<h3>Mosty</h3>Mosty môžu mať hore, dole, vľavo a vpravo múry.
+Comment[sl]=<h3>Mostovi</h3>Mostovi imajo lahko zidove zgoraj, spodaj, levo ali desno.
+Comment[sr]=<h3>Мостови</h3>Мостови могу да имају зидове на врху, дну, лево или десно.
+Comment[sr@Latn]=<h3>Mostovi</h3>Mostovi mogu da imaju zidove na vrhu, dnu, levo ili desno.
+Comment[sv]=<h3>Broar</h3>Broar kan ha väggar över, under, till vänster eller till höger.
+Comment[ta]=<h3>பாலங்கள்</h3>பாலங்கள் அதன் மேல்,கீழ்,இடபுறம் அல்லது வலபுறத்தில் சுவருகளை கொண்டிருக்கலாம்.
+Comment[tg]=<h3>Кӯпрукҳо</h3>Кӯпрукҳо деворҳоро дар боло, поён, чап ё рост дошта метавонад.
+Comment[tr]=<h3>Köprüler</h3>Köprülerin üst, alt, sol veya sağ taraflarında duvarlar olabilir.
+Comment[uk]=<h3>Мости</h3>Мости можуть мати стіни зверху, знизу, з правого і лівого боків.
+Comment[ven]=<h3>Maburoho</h3>Maburoho a ngavha na dzi mbondo nga ntha kana nga fhasi kana nga matungo.
+Comment[xh]=<h3>Iibhulorho</h3>Iibhulorho zingaba neendonga ngaphezulu, ngezantsi, ekhohlo okanye ekunene.
+Comment[xx]=xx<h3>Bridges</h3>Bridges can have walls on the top, bottom, left, or right.xx
+Comment[zh_CN]=<h3>桥</h3>桥可以在上、下、左、右布置短墙。
+Comment[zh_TW]=<h3>橋</h3>橋可以在頂端、底部、左方、右方有牆。
+Comment[zu]=<h3>Amabhuloho</h3>Amabhuloho angaba nezindonga phezulu, phansi, kwesokunxele, noma kwesokudla.
+botWallVisible=true
+height=115
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=387
+
+[11-ball@41,51]
+dummykey=true
+
+[11-blackhole@55,332|11]
+exit=137,371
+exitDeg=10
+maxspeed=3
+minspeed=2
+
+[11-bridge@287,158|16]
+botWallVisible=false
+height=34
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=37
+
+[11-cup@305,173|17]
+dummykey=true
+
+[11-floater@42,211|9]
+botWallVisible=false
+endPoint=42,211
+height=74
+leftWallVisible=true
+rightWallVisible=true
+speed=5
+startPoint=38,69
+topWallVisible=false
+width=67
+
+[11-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=true
+maxstrokes=10
+par=5
+
+[11-puddle@162,25|21]
+changeEnabled=false
+changeEvery=50
+height=280
+width=116
+
+[11-puddle@189,186|19]
+changeEnabled=false
+changeEvery=50
+height=154
+width=72
+
+[11-sand@155,72|24]
+changeEnabled=false
+changeEvery=50
+height=214
+width=108
+
+[11-sand@294,384|35]
+changeEnabled=false
+changeEvery=50
+height=120
+width=112
+
+[11-sand@295,399|34]
+changeEnabled=false
+changeEvery=50
+height=90
+width=172
+
+[11-sign@145,4|3]
+Comment=<h3>Everything</h3>Here's a hole that has it all. Have fun with Kolf!<br><center>-- Jason Katz-Brown</center>
+Comment[bg]=<h3>Всичко накуп</h3>Това е дупка, която притежава всичко. Приятна игра!<br><center>-- Jason Katz-Brown</center>
+Comment[bn]=<h3>সবকিছু</h3> এই গর্তে সবকিছুই আছে। কে-ওলফ খেলে মজা করুন! <br><center>-- জেসন কাট্‌জ-ব্রাউন</center>
+Comment[bs]=<h3>Svega pomalo</h3>Ovo je rupa koja ima sve. Zabavite se s Kolfom!<br><center>-- Jason Katz-Brown</center>
+Comment[ca]=<h3>Tot</h3>Aquest és un forat que ho té tot. Que el gaudiu amb kolf!<br><center>-- Jason Katz-Brown</center>
+Comment[da]=<h3>Det hele</h3>Her er en bane der har det hele. God fornøjelse med Kolf!<br><center>-- Jason Katz-Brown</center>
+Comment[de]=<h3>Alles</h3>Hier ist ein Loch, das hat alles in sich. Viel Vergnügen mit Kolf.<br> <center>-- Jason Katz-Brown</center>
+Comment[el]=<h3>Τα πάντα</h3>Εδώ είναι μια τρύπα που τα έχει όλαl. Να περάσετε καλά με το Kolf!<br><center>-- Jason Katz-Brown</center>
+Comment[es]=<h3>Todo</h3>Aquí hay un agujero que lo tiene todo. Disfrute con Kolf<br> <center>-- Jason Katz-Brown</center>
+Comment[et]=<h3>Täisvärk</h3>See on kõikvõimalike takistustega rada. Naudi Kolfi!<br><center>-- Jason Katz-Brown</center>
+Comment[fi]=<h3>Kaikki</h3> Tässä reiässä on kaikki mahdollinen. Pidä hauskaa Kolf pelin kanssa! <br><center>-- Jason Katz-Brown</center>
+Comment[fr]=<h3>Tout</h3>Voici un trou qui a tout. Amusez-vous avec Kolf !<br><center>-- Jason Katz-Brown</center>
+Comment[gl]=<h3>Todo</h3>Aquí hai un burato que o ten todo. Paseo ben con Kolf!<br><center>-- Jason Katz-Brown</center>
+Comment[he]=<h3>הכל</h3>הנה גומה שיש בה הכל. שיהיה לך כיף עם Kolf!<br><center>-- ג'ייסון קאטס־בראון</center>
+Comment[hi]=<h3>सबकुछ</h3>यहाँ एक होल है जिसमें सब है. कोल्फ के साथ मजा करें!<br><center>-- जेसन कात्ज-ब्राउन</center>
+Comment[hu]=<h3>Minden</h3>Itt egy mindennel ellátott lyuk. Kellemes szórakozást kívánunk a játékhoz!<br><center>-- Jason Katz-Brown</center>
+Comment[is]= <h3>Allt</h3>Hér er hola sem hefur það allt. Skemmtu þér með Kolf! <br><center>-- Jason Katz-Brown</center>
+Comment[it]=<h3>Tutto</h3> Ecco una buca che ha tutto. Divertiti con Kolf!<br><center>-- Jason Katz-Brown</center>
+Comment[ja]=<h3>全部</h3>こちらは全てのオブジェクトがあるホールです。Kolf を楽しんで下さい!<br><center>-- Jason Katz-Brown</center>
+Comment[mk]=<h3>Сѐ</h3>Еве дупка која што има СЀ. Забавувајте се со Колф!<br> <center>-- Jason Katz-Brown</center>
+Comment[nb]=<h3>Alt</h3>Her er et hull som har alt. Kos deg med Kolf!<br><center>-- Jason Katz-Brown</center>
+Comment[nl]=<h3>Alles</h3>Hier is een hole dat alles in zich heeft. Veel plezier met Kolf!<br> <center>-- Jason Katz-Brown</center>
+Comment[nn]=<h3>Alt saman</h3>Dette holet har alt saman. Ha det kjekt med Kolf!<br><center>-- Jason Katz-Brown</center>
+Comment[pl]=<h3>Wszystko</h3>Ten dołek ma wszystkie atrakcje. Wesołej zabawy z Kolf! <br><center>-- Jason Katz-Brown</center>
+Comment[pt]=<h3>Tudo</h3>Existe um buraco que tem tudo. Divirta-se com o Kolf!<br><center>-- Jason Katz-Brown</center>
+Comment[pt_BR]=<h3>Tudo</h3>Aqui está um buracoque tem de tudo. Divirta-se com o Kolf!<br><center>-- Jason Katz-Brown</center>
+Comment[ru]=<h3>Все</h3>Вот лунка, где все это есть. Играйте в гольф!<br><center>-- Jason Katz-Brown</center>
+Comment[sk]=<h3>Všetko</h3>Tu je jamka, kde je všetko. Užite si Kolf!<br><center>-- Jason Katz-Brown</center>
+Comment[sl]= <h3>Vse</h3>Tukaj je luknja, ki ima vse. Uživajte v Kolfu! <br><center>--Jason Katz-Brown</center>
+Comment[sr]=<h3>Све</h3>Ево рупе која има свега. Забављајте се Kolf-ом!<br><center>— Џејсон Кец-Браун (Jason Katz-Brown)</center>
+Comment[sr@Latn]=<h3>Sve</h3>Evo rupe koja ima svega. Zabavljajte se Kolf-om!<br><center>— Džejson Kec-Braun (Jason Katz-Brown)</center>
+Comment[sv]=<h3>Alltihop</h3>Här är ett hål som har alltihop. Hoppas du får roligt med Kolf!<br><center>-- Jason Katz-Brown</center>
+Comment[ta]=<h3>எல்லாவற்றிலும்</h3>இங்கு ஓட்டை உள்ளது. கோல்ஃப்யை வைத்து வேடிக்கை செய்!<br><center>-- ஜேஸன் கேட்ஸ் பிரவுன்</center>
+Comment[tg]=<h3>Ҳама чиз</h3>Ин сӯрохие, ки ҳамаи чизҳоро дар он ҷо дорост. Бо бозии Голф дилхушӣ кунед!<br><center>-- Jason Katz-Brown</center>
+Comment[tr]=<h3>Herşey</h3>Burada tümünü barındıran bir delik var. Kolf ile eğlenin <br><center>-- Jason Katz-Brown</center>
+Comment[uk]=<h3>Все</h3>Це лунка, яка все це має. Удачі вам з Kolf!<br><center>-- Jason Katz-Brown</center>
+Comment[ven]=<h3>Zwithu zwothe</h3>Henefha buli li re nazwo zwothe.Diphineni nga Kolf!<br><center>-- Jason Katz-Brown</center>
+Comment[xh]=<h3>Yonke into</h3>Nanku umngxunya oqulathe konke. Yiba nolonwabo ne Kolf!<br><center>-- Jason Katz-Brown</center>
+Comment[xx]=xx<h3>Everything</h3>Here's a hole that has it all. Have fun with Kolf!<br><center>-- Jason Katz-Brown</center>xx
+Comment[zh_CN]=<h3>各种障碍</h3>这是一个有各种障碍的球洞。祝您玩得愉快!<br><center>-- Jason Katz-Brown</center>
+Comment[zh_TW]=<h3>每樣東西</h3>這是個什麼都有的洞。希望您在 Kolf 玩得開心!<br><center>-- Jason Katz-Brown</center>
+Comment[zu]=<h3>Yonke into</h3>Nansi imbobo enako konke. Zijabulise nge-Kolf!<br> <center>-- Jason Katz-Brown</center>
+botWallVisible=true
+height=132
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=253
+
+[11-slope@13,101|10]
+grade=7
+gradient=Horizontal
+height=157
+reversed=true
+stuckOnGround=true
+width=125
+
+[11-slope@231,141|15]
+grade=3
+gradient=Vertical
+height=122
+reversed=false
+stuckOnGround=false
+width=156
+
+[11-slope@309,264|23]
+grade=3
+gradient=Opposite Diagonal
+height=124
+reversed=true
+stuckOnGround=false
+width=77
+
+[11-wall@0,0|12]
+endPoint=231,255
+startPoint=93,391
+
+[11-wall@0,0|13]
+endPoint=229,118
+startPoint=231,255
+
+[11-wall@0,0|22]
+endPoint=390,389
+startPoint=311,295
+
+[11-windmill@231,255|14]
+botWallVisible=false
+bottom=true
+height=40
+leftWallVisible=true
+rightWallVisible=true
+speed=2
+topWallVisible=false
+width=80
+
+[2-ball@38,373]
+dummykey=true
+
+[2-cup@129,209|6]
+dummykey=true
+
+[2-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[2-sign@1,5|3]
+Comment=<h3>Slopes</h3>Slopes are slanted areas of ground that push the ball in the direction that they slope. This direction is shown when you choose <u>Hole->Show Info</u>.
+Comment[bg]=<h3>Склонове</h3>Това са наклонени земни участъци, които изтласкват топката по посока на техния наклон. За да видите посоката изберете от менюто <u>Дупка/Информация</u>.
+Comment[bn]=<h3>ঢাল</h3> ঢাল হল মাটির মধ্যে নিচু স্থান যা বলকে কোন নির্দিষ্ট দিকে নিয়ে যায়। বলের এই গতিপথ দেখার জন্য <u>গর্ত->তথ্য প্রদর্শন</u> চাপুন।
+Comment[bs]=<h3>Nagibi</h3>Nagibi su nakošene oblasti tla koje guraju lopticu u smijeru u kojem padaju. Ovaj smjer se može vidjeti kada izaberete <u>Rupa->Prikaži informacije</u>.
+Comment[ca]=<h3>Baixada</h3>Les baixades són àrees ratllades en el terra que empenyen la bola en la direcció de la baixada. Aquesta direcció se selecciona quan escolliu <u>Forat->Mostra informació</u>.
+Comment[da]=<h3>Skråninger</h3>Skråninger på banen skubber bolden i den retning de hælder. Denne retning vises når du vælger <u>Hul -> Vis info</u>.
+Comment[de]= <h3>Hügel</h3>Hügel sind kurvige Bodenstücke, die den Ball in die eine oder andere Richtung ablenken. Diese Richtung ist sichtbar, wenn man <u>Loch->Informationen anzeigen</u> auswählt.
+Comment[es]=<h3>Pendientes</h3>Las pendientes son áreas rayadas de suelo que empujan la bola en la dirección de la pendiente. Esta dirección se muestra cuando se selecciona <u>Hoyo->Mostrar información</u>.
+Comment[et]=<h3>Kallakud</h3>Kallakud on sellised maapinna osad, mis lasevad pallil veereda oma kalde suunas. Seda suunda võid näha, kui valid <u>Rada->Näita infot</u>.
+Comment[fi]=<h3>Kallistukset</h3> Kallistukset ovat alueita, jotka painavat palloa mäen suuntaan. Tämä suunta näytetään, kun valitset <u>Reikä->Näytä tiedot</u>.
+Comment[fr]=<h3>Pentes</h3>Les pentes sont des zones inclinées du sol qui poussent la balle dans la direction où elles penchent. Cette direction est montrée quand vous choisissez <u>Trou / Afficher les informations</u>.
+Comment[gl]=<h3>Pendentes</h3>As pendentes son zonas raiadas do chan que empuxan a bola no sentido da pendente. Este sentido amósase cando se selecciona <u>Buraco->Amosar Información</u>.
+Comment[he]=<h3>מדרונים</h3>מדרונים הם משטחים משופעים שדוחפים את הכדור לפי כיוון השיפוע שלהם. כיוון זה מוצג כאשר אתה בוחר ב<u>גומה->הצג מידע</u>.
+Comment[hi]=<h3>ढलान</h3>ढलान भूमि के झुके हुए क्षेत्र होते हैं जो गेंद को ढलान की ओर ढकेलते हैं. यह दिशा दिखाई जाती है जब आप चुनते हैं<u>होल->जानकारी दिखाएँ</u>.
+Comment[hu]=<h3>Lejtők</h3>A lejtős területeken a golyók a lejtő irányában gurulnak tovább. Az irány megjelenítéséhez válassza a <u>Lyuk->Információk</u> menüpontot.
+Comment[is]= <h3>Hallar</h3>Hallar eru hallandi svæði í brautinni sem ýta boltanum í þá átt sem hún hallar. Þessi stefna er sýnd þegar þú velur <u>Hola->Sýna upplýsingar</u>
+Comment[it]=<h3>Pendii</h3>I pendii sono zone di prato in pendenza che fanno scendere la pallina lungo la discesa. La direzione di discesa viene mostrata scegliendo <u>Buca->Mostra informazioni</u>.
+Comment[ja]=<h3>斜面</h3> 斜面はボールを動かす傾いている地面です。<u>ホール->情報を表示</u> とすると、斜面の勾配の方向が表示されます。
+Comment[mk]=<h3>Наклони</h3>Наклоните се закривени површини на земјата кои што ја туркаат топката во насоката во која се спуштаат. Оваа насока се прикажува кога ќе изберете <u>Дупка->Прикажи информации</u>
+Comment[nb]=<h3>Skråninger</h3>Skråninger er områder som får ballen til å rulle i den retningen de skråner. Du kan se retningen ved å velge <u>Hull->Vis info</u>.
+Comment[nl]= <h3>Heuvels</h3>Heuvels zijn hellende stukken grond die de bal in een bepaalde richting afbuigen. De richting ervan wordt getoond als u kiest voor <u>Hole->Info tonen</u>.
+Comment[nn]=<h3>Bakkar</h3>I ein bakke vil ballen rulla i ein viss retning. Du kan sjå retningen med <u>Hol->Vis info</u>.
+Comment[pl]=<h3>Zbocza</h3>Zbocza to nachylone obszary spychające piłkę w kierunku w którym opadają. Kierunek opadania zostanie pokazany, gdy wybierzesz <u>Dołek->Pokaż informacje</u>.
+Comment[pt]=<h3>Rampas</h3>As rampas são zonas inclinadas de terra que empurram a bola na direcção da inclinação. Esta direcção é mostrada quando escolhe o <u>Buraco->Mostrar a Informação</u>.
+Comment[pt_BR]=<h3>Ladeiras</h3>Ladeiras são as áreas inclinadas de terra que empurram a bola na direção que inclinam. Esta direção é mostrada quando você escolhe <u>Buraco-> Mostrar Informação</u>.
+Comment[ru]=<h3>Горки</h3>На горке мяч будет катиться под уклон. Направление будет показано, если вы выберите <u>Лунка->Сведения</u>.
+Comment[sk]=<h3>Svahy</h3>Svahy sú naklonené miesta, ktoré usmerňujú loptičku v smere, v ktorom sú sklonené. Smer sa dá zobraziť výberom <u>Jamka->Zobraziť informácie</u>.
+Comment[sl]= <h3>Strmine</h3> Strmine so nagnjene površine, ki porinejo žogico v smer strmine. Ta smer je prikazana, ko izberete <u>Luknja -> Pokaži informacije</u>.
+Comment[sr]=<h3>Нагиби</h3>Нагиби су закошене области тла које гурају лоптицу у смеру у коме су нагнуте. Тај смер се приказује када изаберете <u>Рупа->Прикажи информације</u>.
+Comment[sr@Latn]=<h3>Nagibi</h3>Nagibi su zakošene oblasti tla koje guraju lopticu u smeru u kome su nagnute. Taj smer se prikazuje kada izaberete <u>Rupa->Prikaži informacije</u>.
+Comment[sv]=<h3>Sluttningar</h3>Sluttningar är lutande markområden som rullar bollen i riktningen de lutar. Riktningen visas när du väljer <u>Hål->Visa information</u>.
+Comment[ta]=<h3>சரிவுகள்</h3>சரிவுகள் ஒரு நிலத்தின் சாய்ந்த பரப்பளவு. சரிவின் திசையில் அது பந்தைத் தள்ளும்.நீங்கள் தகவலை காட்டு என்பதை தேர்ந்தெடுக்கும் போது இதன் திசை <u>Hole-> காட்டப்படும். </u>.
+Comment[tg]=<h3>Теппаҳо</h3>Дар теппачаҳо тӯб ба хамӣ меғелад. Ин самт нишон дода мешавад, агар шумо <u>-Сӯрохӣ->Нишондиҳии Маълумот</u>-ро интихоб кунед.
+Comment[tr]=<h3>Eğimler</h3>Eğimler yerin değişik eğimlerde bulunmasını sağlar böylece top eğimlerin yönünde hareket eder. Bu yön <u>Delik->Bilgi Göster</u> seçtiğinizde gösterilir.
+Comment[uk]=<h3>Схили</h3>Схили - це похилі ділянки землі6 які підштовхують м'яч у напрямку нахилу. Цей напрямок видно, коли ви вибираєте<u>Лунка->Показати інформацію</u>.
+Comment[ven]=<h3>Zwivhanga</h3>Zwivhanga ndi vhethu ho tshingamaho hune ha sukumedza bola uri i yele kha siya le ha tshingamela hone.Siya heli li sumbedzwa musi ni tshi nanga <u>Buli->Sumbedza zwidodombedzwa</u>.
+Comment[xh]=<h3>Amathambeka</h3>Amathambeka ngamacala lalele kwiindawo zebala alityhala ibhola kwicala athambekele kulo. Le ndlela iboniswe when you choose <u>Hole->Show Info</u>.
+Comment[xx]=xx<h3>Slopes</h3>Slopes are slanted areas of ground that push the ball in the direction that they slope. This direction is shown when you choose <u>Hole->Show Info</u>.xx
+Comment[zh_CN]=<h3>斜坡</h3>斜坡就是场地上的倾斜区域,这会使球朝它们倾斜的方向运动。该方向会在您选择<u>球洞->显示信息</u>时显示出来。
+Comment[zh_TW]=<h3>斜坡</h3>斜坡是場地中傾斜的區域,並且會將球推向它們傾斜的方向。這個方向會在您選擇<u>洞->顯示資訊</u>時顯示。
+Comment[zu]=<h3>Okusantaba</h3>Okusantaba izindawo ezitshekile zomhlaba ezidudula ibhola liye endleleni lapho zitshekele ngakhona. Lendlela iyakhombiswa uma ukhetha <u> Imbobo->Bonisa ulwazi</u>.
+botWallVisible=true
+height=187
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=392
+
+[2-sign@197,198|5]
+Comment=Hit the ball onto the slope and let it roll into cup.
+Comment[bg]=Ако сте на склон, ударете топката леко и я оставете да се изтъркаля до дупката.
+Comment[bn]=বলে আঘাত করে ঢালের মধ্যে ঠেলে দিন এবং তা নিজে থেকেই গড়িয়ে গড়িয়ে কাপের দিকে চলে যাবে।
+Comment[bs]=Šutnite loptu prema nagibu i pustite da se spusti u rupu.
+Comment[ca]=Pica la pilota en la baixada i deixa'l·la arribar fins a la copa.
+Comment[da]=Slå bolden op på skråningen og lad den rulle ned i fordybningen.
+Comment[de]=Spielen Sie den Ball aufs Grün und lassen Sie ihn ins Loch rollen
+Comment[es]=Golpear la bola en la pendiente y dejarla rodar hasta la copa.
+Comment[et]=Löö pall kallakule ja lase tal auku veereda.
+Comment[fi]=Lyä pallo rinteeseen ja anna sen vieriä kuppiin.
+Comment[fr]=Frappez la balle dans la pente et laissez-la rouler dans le trou.
+Comment[gl]=Golpee a bola na pendente e déixea rodar ata a copa.
+Comment[he]=חבוט בכדור כך שיטפס על המדרון, ותן לו להתגלגל לתוך הגומה.
+Comment[hi]=गेंद पर प्रहार करें ताकि ढलान पर लुढ़कता हूआ कप में चला जाए.
+Comment[hu]=Üsse a golyót a lejtő irányában és hagyja lefelé gurulni.
+Comment[is]=Sláðu á boltann í brekkuna og láttu renna í bollann.
+Comment[it]=Batti la pallina verso il pendio e lasciala rotolare in buca.
+Comment[ja]=カップに入るように斜面にボールを打って下さい。
+Comment[lv]=Uzsitiet bumbiņu uz nogāzes un ļaujiet tai ierpot caurumā.
+Comment[mk]=Удрете ја топката во наклонот и оставете ја да се дотркала до дупката.
+Comment[nb]=Slå ballen ned skråningen og la den rulle ned i koppen.
+Comment[nl]=Speel de bal op de heuvel en laat deze in het gat rollen.
+Comment[nn]=Slå ballen opp bakken og lat han trilla ned i holet.
+Comment[pl]=Uderz piłkę na zbocze i pozwól jej stoczyć się do dołka.
+Comment[pt]=Acerta na bola para a rampa e deixe-a rolar para o buraco.
+Comment[pt_BR]=Bata na bola em direção à ladeira e deixe-a rolar para dentro do buraco.
+Comment[ru]=Забросьте мяч на склон и дайте ему закатиться в лунку.
+Comment[sk]=Trafte svah a nechajte loptičku dokotúľať do jamky.
+Comment[sl]=Udarite žogico na strmino in jo spravite v luknjico.
+Comment[sr]=Пошаљите лоптицу на нагиб и пустите је да се откотрља близу рупе.
+Comment[sr@Latn]=Pošaljite lopticu na nagib i pustite je da se otkotrlja blizu rupe.
+Comment[sv]=Slå bollen över sluttningen och låt den rulla i hålet.
+Comment[ta]=சரிவில் பந்தை உதைத்தால் அது கோப்பைக்குள் உருளும்.
+Comment[tg]=Тӯбро ба теппача партоед ва ба он имконият диҳед, ки ба сӯрохи дарояд.
+Comment[tr]=Eğimdeki topa vurun ve fincana düşmesini sağlayın.
+Comment[uk]=Вдарити м'ячик на схил і дати йому закотитись в лунку.
+Comment[ven]=Rwisani bola na mutsiso ni i tendele i kunguwele nga ngomu bigirini.
+Comment[xh]=Betha ibhola edongeni uphinde uyeke iqengqelekele ekhaphini.
+Comment[xx]=xxHit the ball onto the slope and let it roll into cup.xx
+Comment[zh_CN]=把球打到斜坡上,让它滚到杯子里。
+Comment[zh_TW]=將球擊到斜坡上並讓它滾進球洞。
+Comment[zu]=Shaya ibhola liye kokusantaba ebese uliyeka ligingqikele enkomishini.
+botWallVisible=true
+height=195
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=197
+
+[2-slope@11,237|4]
+grade=4
+gradient=Vertical
+height=113
+reversed=false
+stuckOnGround=false
+width=181
+
+[3-ball@86,353]
+dummykey=true
+
+[3-cup@229,307|3]
+dummykey=true
+
+[3-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[3-sign@4,4|4]
+Comment=Try out these different types of slopes.
+Comment[bg]=Тук може, да изпробвате различните видове наклони.
+Comment[bn]=বিভিন্ন ধরনের এই ঢালগুলো ব্যবহার করে দেখুন।
+Comment[bs]=Isprobajte ove razne vrste nagiba.
+Comment[ca]=Intenta aquests diferents tipus de baixades.
+Comment[da]=Afprøv disse forskellige typer skråninger.
+Comment[de]=Probieren Sie diese verschiedenen Typen von Grüns aus
+Comment[es]=Intente estos diferentes tipos de pendiente.
+Comment[et]=Proovi erinevat tüüpi kallakuid.
+Comment[fi]=Kokeile näitä eri kaltevuuksia
+Comment[fr]=Essayez ces différents types de pente.
+Comment[gl]=Probe estes distintos tipos de pendentes.
+Comment[he]=נסה את סוגי המדרונים השונים.
+Comment[hi]=ये भिन्न प्रकार के ढलान आजमा देखें.
+Comment[hu]=Válogatni lehet a különféle lejtők között.
+Comment[is]=Reyndu þig við þessar mismunandi brekkur.
+Comment[it]= Prova questi diversi tipi di pendio.
+Comment[ja]=このタイプの違う斜面にボールを打ってみて下さい。
+Comment[lv]=Izmēģiniet šīs dažādās nogāzes.
+Comment[mk]=Испробајте ги различните типови на наклони.
+Comment[nb]=Prøv disse forskjellige skråningene.
+Comment[nl]=Probeer de verschillende typen heuvels uit.
+Comment[nn]=Prøv desse ulike bakketypane.
+Comment[pl]=Wypróbuj różne typy zboczy.
+Comment[pt]=Experimente estes diferentes tipos de inclinação.
+Comment[pt_BR]=Experimente estes diferentes tipos de ladeiras.
+Comment[ru]=Попробуйте разные типы склонов.
+Comment[sk]=Skúste tieto rôzne sklony.
+Comment[sl]=Poskusite različne vrste strmin.
+Comment[sr]=Испробајте ове различите типове нагиба.
+Comment[sr@Latn]=Isprobajte ove različite tipove nagiba.
+Comment[sv]=Prova med de här olika typerna av sluttning.
+Comment[ta]=இந்த பலவகைச் சரிவுகளை முயற்சிக்கவும்.
+Comment[tg]=Навъҳои гуногуни теппачаҳоро истифода баред.
+Comment[tr]=Farklı tür eğimleri de denemelisiniz.
+Comment[uk]=Спробуйте ці різні типи схилів.
+Comment[ven]=Lingedzani dzinwe tshaka dza mitsitso.
+Comment[xh]=Zama ezintlobo zahlukeneyo zezlopi.
+Comment[xx]=xxTry out these different types of slopes.xx
+Comment[zh_CN]=试遍这些各种不同类型的斜坡。
+Comment[zh_TW]=試驗這些不同類型的斜坡。
+Comment[zu]=Zama lezizinhlobo ezahlukene zokusantaba
+botWallVisible=true
+height=87
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=343
+
+[3-slope@148,292|5]
+grade=4
+gradient=Vertical
+height=79
+reversed=true
+stuckOnGround=false
+width=66
+
+[3-slope@15,264|9]
+grade=4
+gradient=Diagonal
+height=76
+reversed=true
+stuckOnGround=false
+width=93
+
+[3-slope@190,147|6]
+grade=4
+gradient=Horizontal
+height=80
+reversed=false
+stuckOnGround=false
+width=71
+
+[3-slope@209,239|11]
+grade=4
+gradient=Opposite Diagonal
+height=42
+reversed=false
+stuckOnGround=false
+width=46
+
+[3-slope@231,315|10]
+grade=4
+gradient=Opposite Diagonal
+height=67
+reversed=true
+stuckOnGround=false
+width=92
+
+[3-slope@266,240|7]
+grade=4
+gradient=Horizontal
+height=63
+reversed=true
+stuckOnGround=false
+width=74
+
+[3-slope@285,144|13]
+grade=4
+gradient=Elliptic
+height=87
+reversed=true
+stuckOnGround=false
+width=87
+
+[3-slope@32,129|8]
+grade=4
+gradient=Diagonal
+height=72
+reversed=false
+stuckOnGround=false
+width=90
+
+[3-slope@99,204|12]
+grade=4
+gradient=Elliptic
+height=84
+reversed=false
+stuckOnGround=false
+width=84
+
+[4-ball@34,271]
+dummykey=true
+
+[4-cup@367,353|6]
+dummykey=true
+
+[4-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=3
+
+[4-sign@6,3|3]
+Comment=The steepness of a slope is shown when you choose <u>Hole->Show Info</u>. Steepness goes from 8 (steepest) to 1 (shallowest).
+Comment[bg]=За да видите стръмнината на склона, изберете от менюто <u>Дупка/Информация</u>. Наклонът се изменя от 8 (най-стръмен) до 1 (най-полегат).
+Comment[bn]=ঢালের বক্রতা দেখার জন্য <u>গর্ত->তথ্য প্রদর্শন</u> চাপুন। বক্রতার ব্যাপ্তি ১ ( অগভীরতম ) থেকে ৮ ( বক্রতম ) পর্যন্ত।
+Comment[bs]=Stupanj nagiba možete dobiti ako izaberete <u>Rupa->Prikaži informacije</u>. Nagnutost se kreće od 8 (najveća) do 1 (najmanja).
+Comment[ca]=La intensitat de la baixada es mostra quan escolliu <u>Forat->Mostra informació</u>. La força va des de 8 (màxim) a 1 (mínim).
+Comment[da]=Du kan se hvor stejl en skråning er ved at vælge <u>Hul -> Vis info</u>. Stejlhed går fra 8 (stejlest) til 1 (fladest).
+Comment[de]=Die Steigung eines Grüns bekommen Sie angezeigt, wenn Sie <u>Loch->Informationen anzeigen</u> auswählen. Steigungen gehen von 8 (sehr steil) bis 1 (sehr flach).
+Comment[es]=La intensidad de la pendiente se muestra cuando se selecciona <u>Hoyo->Mostrar información</u>. La intensidad va de 8 (máximo) a 1 (mínimo).
+Comment[et]=Kallaku kallet näeb, kui valid <u>Rada->Näita infot</u>. Kalle jääb 8 (kõige järsem) ja 1 (laugeim) vahele.
+Comment[fi]=Mäen kaltevuus näytetään kun valitset <u>Reikä->Näytä tiedot</u>. Kaltevuus on välillä 8 (jyrkin) - 1 (loivin)
+Comment[fr]=L'inclinaison d'une pente est montrée quand vous choisissez <u>Trou / Afficher les informations</u>. L'inclinaison va de 8 (le plus pentu) à 1 (le moins pentu).
+Comment[gl]=A inclinación dunha pendente amósase cando se elixe <u>Burato->Amosar Información</u>. A inclinación vai dende 8 (máximo) ata 1 (mínimo).
+Comment[he]=השיפוע של המדרון מוצג כאשר אתה בוחר ב<u>גומה->הצג מידע</u>. השיפוע נע בין 8 (התלול ביותר) ל־1 (המתון ביותר).
+Comment[hi]=ढलान की ढाल दिखाई देगी जब आप चुनेंगे<u>होल->जानकारी दिखाएँ</u>. ढाल 8 (सबसे तेज ढाल) से 1 (सबसे कम ढाल) तक है.
+Comment[hu]=A lejtő meredeksége megtudható a <u>Lyuk->Információ</u> menüponttal. A meredekség értéke 8-tól (legmeredekebb) 1-ig (leglaposabb) terjedhet.
+Comment[is]=Halli brekkunnar er sýndur þegar þú velur <u>Hola->Sýna upplýsingar</u>. Hallinn fer frá 8 (brattast) til 1 (flatast).
+Comment[it]= Il grado di inclinazione di un pendio viene mostrato quando scegli <u>Buca->Mostra Infornazioni</u> nel menu. La ripidezza va da 8 (il più scosceso) a 1 (il più dolce).
+Comment[ja]=<u>ホール->情報を表示</u> とすると、 斜面の勾配が表示されます。勾配は 8 (急) から 1 (緩やか) までのスケールがあります。
+Comment[lv]=Nogāzes slīpumu var apskatīties, ja izvēlas <u>Caurums->Rādīt informāciju</u>. Slīpums ir no 8 (slīpākais) līdz 1 (lēzenākais).
+Comment[mk]=Стрмноста на наклонот е прикажана кога избирате <u>Дупка->Прикажи информација</u>. Стрмноста се движи од 8 (најстрмна) до 1 (најблага).
+Comment[nb]=Brattheten på en skråning vises hvis du velger <u>Hull->Vis info</u>. Brattheten går fra 8 (brattest) til 1 (slakest).
+Comment[nl]=De stijging van een een heuvel wordt getoond als u kiest voor <u>Hole->info tonen</u>. De stijging gaat van 8 (steilste) tot 1 (vlakste).
+Comment[nn]=Du kan sjå kor bratt bakken er med <u>Hol->Vis info</u>. Den brattaste bakken har verdien 8.
+Comment[pl]=Gdy wybierzesz <u>Dołek->Pokaż informacje</u> pokazany zostanie kąt nachylenia zbocza. Kąt nachylenia może przybierać wartości od 8 (najbardziej stromy) do 1 (najbardziej płaski).
+Comment[pt]=A inclinação de uma rampa é mostrada quando escolhe o <u>Buraco->Mostrar a Informação</u>. A inclinação vai de 8 (a pique) até 1 (liso).
+Comment[pt_BR]=A inclinação de uma ladeira é mostrada quando você escolhe<u> Buraco-> Mostrar Informação</u>. O valor da inclinação da ladeira vai de 8 (mais íngreme) para 1 (mais baixo/plano).
+Comment[ru]=Крутизна склона показывается при нажатии <u>Лунка->Сведения</u>. Она меняется от 8 (крутые) до 1 (пологие).
+Comment[sk]=Sklon svahu sa zobrazí po výbere <u>Jamka->Zobraziť informácie</u>. Sklon môže byť od 8 (najstrmšie) až po 1 (najrovnejšie).
+Comment[sl]=Nagib strmine je prikazan, ko izberete <u>Luknja -> Pokaži informacije</u>. Nagib gre od 8 (najbolj strmo) do 1 (najmanj strmo).
+Comment[sr]=Стрмина нагиба се приказује када изаберете <u>Рупа->Прикажи информације</u>. Стрмина иде од 8 (најстрмије) до 1 (најплиће).
+Comment[sr@Latn]=Strmina nagiba se prikazuje kada izaberete <u>Rupa->Prikaži informacije</u>. Strmina ide od 8 (najstrmije) do 1 (najpliće).
+Comment[sv]=En sluttnings lutning visas när du väljer <u>Hål->Visa information</u>. Lutningen sträcker sig från 8 (brantast) till 1 (minst brant).
+Comment[ta]=தகவலை காட்டு என்பதை நீங்கள் தேர்வு செய்யும் பொழுது சரிவின் செங்குத்து காட்டப்படும்.<u></u>. ஆழம் 8(அதிக அளவு செங்குத்து)லிருந்து to 1(குறைந்த ஆழம்)வரை செல்கிறது.
+Comment[tg]=Мушкилоти теппачаҳо дар натиҷаи интихоби <u>Сӯрохӣ->Нишондиҳии Маълумот</u>, нишон дода мешаванд. Он аз 8 (шитоб) то 1 (ҳамвор) иваз мешавад.
+Comment[uk]=Нахил схилу видно, коли ви вибираєте <u>Лунка->Показати інформацію</u>. Нахил йде від 8 (найкрутіший) до 1 (пологий).
+Comment[xh]=Ukwenyukela kwethambeka kuboniswa xa ukhetha <u>Umngxuma->Bonisa ulwazi</u>. Ukwenyukela buqala kwisibhozo (eyona enyukeleyo) ukuya kwenye (Eyona enganzulwanga).
+Comment[xx]=xxThe steepness of a slope is shown when you choose <u>Hole->Show Info</u>. Steepness goes from 8 (steepest) to 1 (shallowest).xx
+Comment[zh_CN]=斜坡的坡度会在您选择<u>球洞->显示信息</u>时显示出来。坡度值为 8 (陡峭) 到 1 (平缓)。
+Comment[zh_TW]=斜坡的傾斜度會在您選擇<u>洞->顯示資訊</u>時顯示。傾斜度可以從 8 (最陡)到 1 (最緩)。
+Comment[zu]=Izinga lokutsheka kokusantaba kuyakhonjiswa uma ukhetha <u>Imbobo-> Khombisa ulwazi</u>. Izinga lokutsheka lisuka ku-8 (okutsheke kakhulu) kuya ku 1 (okutsheke kancane).
+botWallVisible=true
+height=158
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=384
+
+[4-slope@214,271|5]
+grade=6
+gradient=Vertical
+height=106
+reversed=false
+stuckOnGround=false
+width=103
+
+[4-slope@61,255|4]
+grade=2
+gradient=Vertical
+height=122
+reversed=false
+stuckOnGround=false
+width=111
+
+[5-ball@200,200]
+dummykey=true
+
+[5-cup@204,356|7]
+dummykey=true
+
+[5-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[5-sign@3,5|3]
+Comment=<h3>Walls</h3>Bounce the ball off of the red walls.
+Comment[bg]=<h3>Стени</h3>Стените изтласкват топката, когато се удари в тях.
+Comment[bn]=<h3>দেয়াল</h3> লাল দেয়ালে আঘাত করে বলকে ফিরিয়ে আনুন।
+Comment[bs]=<h3>Zidovi</h3>Lopticu možete odbijati od crvene zidove.
+Comment[ca]=<h3>Baranes</h3>Rebota la bola fora de les baranes vermelles.
+Comment[da]=<h3>Vægge</h3>Få bolden til at støde ind i de røde vægge og hoppe tilbage.
+Comment[de]=<h3>Wände</h3>Schlagen Sie den Ball von den roten Wänden weg
+Comment[es]=<h3>Paredes</h3>Rebota la pelota fuera de las paredes rojas.
+Comment[et]=<h3>Seinad</h3>Pall põrkab punastelt seintelt tagasi.
+Comment[fi]=<h3>Seinät</h3>Lyö kimmoke punaisista seinistä.
+Comment[fr]=<h3>Murs</h3>La balle rebondit sur les murs rouges.
+Comment[gl]=<h3>Balados</h3>.Rebote a bola fora dos valados vermellos.
+Comment[he]=<h3>קירות</h3>גרום לכדור לקפוץ חזרה מהקירות האדומים.
+Comment[hi]=<h3>दीवारें</h3>गेंद को लाल दीवार पर से उछालें
+Comment[hu]=<h3>Falak</h3>Pattintsa vissza a golyót a piros falakról.
+Comment[is]=<h3>Veggir</h3>Boltarnir skoppa af rauðu veggjunum.
+Comment[it]=<h3>Muri</h3> Fai rimbalzare la pallina contro i muri di mattoni.
+Comment[ja]=<h3>壁</h3>赤い壁にボールをバウンスさせて下さい。
+Comment[lv]=<h3>Sienas</h3> Bumbiņa atlec no sarkanajām sienām.
+Comment[mk]=<h3>Ѕидови</h3>Одбијте ја топката од црвените ѕидови.
+Comment[nb]=<h3>Vegger</h3>Sprett ballen vekk fra de røde veggene.
+Comment[nl]=<h3>Muren</h3>Leid de bal weg van de rode muren.
+Comment[nn]=<h3>Veggar</h3>Ballen kan spretta mot dei raude veggane.
+Comment[pl]=<h3>Ściany</h3>Odbijają piłkę od czerwonych ścian.
+Comment[pt]=<h3>Paredes</h3>Faça tabelas com a bola nas paredes vermelhas.
+Comment[pt_BR]=<h3>Paredes</h3>Arremesse a bola fora das paredes vermelhas.
+Comment[ru]=<h3>Стены</h3>Шарик отскакивает от красных стен.
+Comment[sk]=<h3>Steny</h3>Použite odraz loptičky od červených stien.
+Comment[sl]=<h3>Zidovi</h3>Žogica se odbije od rdečih zidov.
+Comment[sr]=<h3>Зидови</h3>Одбијте лоптицу од црвених зидова.
+Comment[sr@Latn]=<h3>Zidovi</h3>Odbijte lopticu od crvenih zidova.
+Comment[sv]=<h3>Väggar</h3>Studsa bollen från de röda väggarna.
+Comment[ta]=<h3>சுவர்கள்</h3>சிவப்பு சுவரில் பந்தை தட்டவும்.
+Comment[tg]=<h3>Деворҳо</h3>Тӯбча ба деворҳои сурх зада пасмегардад.
+Comment[tr]=<h3>Duvarlar</h3> Topları kırmızı duvardan sektirin.
+Comment[uk]=<h3>Стіни</h3>М'ячик відскакує від червоних стін.
+Comment[ven]=<h3>Mbondo</h3>Bammbisani bola kha luvhondo lutswuku.
+Comment[xh]=<h3>Amadonga</h3>Betha phantsi ibhola ngaphandle kwamadonga abomvu.
+Comment[xx]=xx<h3>Walls</h3>Bounce the ball off of the red walls.xx
+Comment[zh_CN]=<h3>短墙</h3>通过红墙将球反弹。
+Comment[zh_TW]=<h3>牆</h3>球碰到紅色的牆彈回。
+Comment[zu]=<h3>Izindonga</h3>Bampisa ibhola ezindongeni ezibomvu.
+botWallVisible=true
+height=117
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=371
+
+[5-wall@0,0|4]
+endPoint=104,201
+startPoint=55,329
+
+[5-wall@0,0|5]
+endPoint=293,388
+startPoint=345,252
+
+[5-wall@0,0|6]
+endPoint=278,266
+startPoint=139,283
+
+[6-ball@65,361]
+dummykey=true
+
+[6-cup@362,248|31]
+dummykey=true
+
+[6-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=4
+
+[6-puddle@122,326|3]
+changeEnabled=false
+changeEvery=50
+height=44
+width=138
+
+[6-puddle@319,408|14]
+changeEnabled=false
+changeEvery=50
+height=120
+width=208
+
+[6-puddle@369,394|12]
+changeEnabled=false
+changeEvery=50
+height=240
+width=198
+
+[6-sand@202,221|33]
+changeEnabled=false
+changeEvery=50
+height=134
+width=74
+
+[6-sand@213,260|11]
+changeEnabled=false
+changeEvery=50
+height=60
+width=78
+
+[6-sign@3,152|37]
+Comment=<h3>Sand</h3>Sand is yellow, and slows your ball down.
+Comment[bg]=<h3>Пясъци</h3>Пясъците са жълти и забавят топката.
+Comment[bn]=<h3>বালি</h3> বালির রং হলুদ, এবং এটি বলের গতি হ্রাস করে।
+Comment[bs]=<h3>Pijesak</h3>Pijesak je žute boje i usporava vašu lopticu.
+Comment[ca]=<h3>Sorra</h3>La sorra és grogra i frena a la pilota.
+Comment[da]=<h3>Sand</h3>Sand er gult og tager farten af din bold.
+Comment[de]=<h3>Sand</h3>Sand ist gelb, und verlangsamt Ihren Ball.
+Comment[el]=<h3>Άμμος</h3>Η άμμος είναι κίτρινη και επιβραδύνει την μπάλα σας.
+Comment[es]=<h3>Arena</h3>La arena es amarilla, y ralentiza su pelota.
+Comment[et]=<h3>Liiv</h3>Liiv on kollane ja aeglustab palli liikumist.
+Comment[fi]=<h3>Hiekka</h3>Hiekka on keltaista, ja hidastaa palloasi.
+Comment[fr]=<h3>Sable</h3>Le sable est jaune et ralentit votre balle.
+Comment[gl]=<h3>Area</h3>A area é amarela, e fai que a bola vaia máis a modo.
+Comment[he]=<h3>חול</h3>חול, המוצג בצבע צהוב, מאט את הכדור שלך.
+Comment[hi]=<h3>रेत</h3>रेत पीली है, तथा आपके गेंद को धीमा करती है.
+Comment[hu]=<h3>Homok</h3>A sárga színű homok lelassítja a golyó mozgását.
+Comment[is]=<h3>Sandur</h3>Sandur er gulur og hægir á boltanum.
+Comment[it]=<h3>Bunker</h3> il bunker di sabbia è giallo e frena la pallina.
+Comment[ja]=<h3>砂</h3>砂は黄色で、ボールと接すると速度を落します。
+Comment[lv]=<h3>Smiltis</h3> Smiltis ir dzeltenas un palēnina Jūsu bumbas ripojumu.
+Comment[mk]=<h3>Песок</h3>Песокот е жолт и ја успорува топката.
+Comment[nb]=<h3>Sand</h3>Sand er gul, og sakker farten på ballen.
+Comment[nl]=<h3>Zand</h3>Zand is geel, en vertraagt uw bal.
+Comment[nn]=<h3>Sand</h3>Dei gule felta er sand som seinkar ballen.
+Comment[pl]=<h3>Piasek</h3>Piasek jest żółty i spowalnia twoją piłkę.
+Comment[pt]=<h3>Areia</h3>Areia é amarela e reduz a velocidades das bolas.
+Comment[pt_BR]=<h3>Areia</h3>A areia é amarela, e faz sua bola andar mais devagar.
+Comment[ru]=<h3>Песок</h3>Песок жёлтый, и шарик на нем тормозит.
+Comment[sk]=<h3>Piesok</h3>Piesok je žltý a spomaľuje loptičku.
+Comment[sl]=<h3>Pesek</h3>Pesek je rumen in upočasni vašo žogico.
+Comment[sr]=<h3>Песак</h3>Песак је жут и успорава вашу лоптицу.
+Comment[sr@Latn]=<h3>Pesak</h3>Pesak je žut i usporava vašu lopticu.
+Comment[sv]=<h3>Bunkrar</h3>Bunkrar är gula, och saktar ner bollen.
+Comment[ta]=<h3>மணல்</h3>மஞ்சளான மணல் உங்கள் பந்தின் வேகத்தை குறைக்கும்.
+Comment[tg]=<h3>Қум</h3>Қум зард аст ва тӯби шуморо суст мегардонад.
+Comment[tr]=<h3>Kum</h3> Sarı renkli kumluk bölgeler topunuzu yavaşlatır.
+Comment[uk]=<h3>Пісок</h3>Пісок жовтий; він сповільняє рух м'ячика.
+Comment[ven]=<h3>Mutavha</h3>Mutavha ndi yellow, fhungudzani luvhilo lwa bola yanu.
+Comment[xh]=<h3>Isanti</h3>Isanti etyheli, ucothise ibhola phantsi.
+Comment[xx]=xx<h3>Sand</h3>Sand is yellow, and slows your ball down.xx
+Comment[zh_CN]=<h3>沙地</h3>沙地是黄色的,可以减慢球速。
+Comment[zh_TW]=<h3>沙</h3>沙是黃色的,而且會減慢您球的速度。
+Comment[zu]=<h3>Isihlabathi</h3>Isihlabathi siqanda, kanye sihambisa ibhola kancane.
+botWallVisible=true
+height=147
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=138
+
+[6-sign@3,5|4]
+Comment=<h3>Puddles (Water)</h3>Hitting into a puddle (blue) adds a penalty stroke to your score, and your ball is placed outside the puddle.
+Comment[bg]=<h3>Локви</h3>Падането на топката в локва, прибавя наказателен удар към резултата и топката се изважда от локвата за следващия удар.
+Comment[bn]=<h3>ডোবা</h3> ডোবার (নীল রঙের) মধ্যে বল ফেললে আপনার স্কোর কমে যাবে, তবে বলটি ডোবা থেকে তুলে দেওয়া হবে।
+Comment[bs]=<h3>Bare (Voda)</h3>Pogodak u baru (plava boja) dodaje kaznene bodove na vaš rezultat, a loptica se premješta izvan bare.
+Comment[ca]=<h3>Basses</h3>Passar per una bassa (blau) afegirà una penalització a la vostra puntuació i la pilotarà serà emplaçada fora de la bassa.
+Comment[da]=<h3>Vandhuller</h3>Rammer du et vandhul (blåt) får du strafpoint, og din bold placeres uden for vandhullet.
+Comment[de]= <h3>Pfützen (Wasser)</h3>Wenn Sie in eine Pfütze hineinspielen (blau dargestellt), bekommen Sie einen zusätzlichen Strafpunkt, und Ihr Ball wird außerhalb der Pfütze auf dem Spielfeld platziert.
+Comment[es]=<h3>Charcos (agua)</h3>Pisar un charco (azul) añade una penalización a su puntuación, y su pelota es situada fuera del charco.
+Comment[et]=<h3>Lombid (vesi)</h3>Kui sul õnnestub pall vette (sinine) lüüa, karistatakse sind ühe löögi lisamisega ja pall paigutatakse uueks löögiks lombi äärde.
+Comment[fi]=<h3>Lammet (Vesi)</h3> Lampeen pyöminen (sininen) lisää rangaistuslyönnin tulokseesi ja pallosi asetetaan lammen ulkopuolelle.
+Comment[fr]=<h3>Rivières</h3>Tomber dans une mare (bleue) ajoute une pénalité à votre score, et votre balle est placée hors de la rivière.
+Comment[gl]=<h3>Pozas (Auga)</h3>Caer nunha poza (azul) engade un golpe de penalización á súa puntuación, e a bola sitúase fora da poza.
+Comment[he]=<h3>שלוליות (מים)</h3>כניסה לשלולית (בכחול) מוסיפה חבטת עונשין לניקוד שלך. הכדור שלך יונח מחוץ לשלולית.
+Comment[hi]=<h3>डबरी (पानी)</h3>डबरी(नीली) में प्रहार करने पर आपके अंक में पेनाल्टी स्ट्रोक जोड़ता है तथा आपकी गेंद डबरी के बाहर रख दी जाती है.
+Comment[hu]=<h3>Pocsolyák</h3>Ha a golyó pocsolyába esik (a víz kék színű), akkor pontlevonást kap, és a golyót a pocsolya mellé helyezik.
+Comment[is]= <h3>Pollar (vatn)</h3>Ef þú hittir í pollinn (blár) þá færð þú refsistig, og boltinn verðu settur við hlið pollsins.
+Comment[it]=<h3>Laghetti (acqua)</h3> Se mandi la pallina in un laghetto, ti viene aggiunto un colpo di penalità al punteggio e la tua pallina viene collocata fuori dal laghetto.
+Comment[ja]=<h3>池 (水)</h3>青い池に入ると、1ショットを罰して、 池の近くにボールが置かれます。
+Comment[mk]=<h3>Бари (Вода)</h3>Удирањето во барата (сино) додава казнен удар на вашите поени, а вашата топка е поставена надвор од барата.
+Comment[nb]=<h3>Pytter (Vann)</h3>Hvis du treffer en pytt (blå) gir deg et straffeslag, og ballen din plasseres utenfor pytten.
+Comment[nl]= <h3>Poelen (water)</h3> Als u de bal in een poel (blauw gebied) slaat, dan krijgt u er een strafpunt bij. Uw bal wordt buiten de poel op het speelveld geplaatst.
+Comment[nn]=<h3>Pyttar (vatn)</h3> Dersom du treff ein pytt (blå), får du eit straffeslag og ballen vert plassert utanfor pytten.
+Comment[pl]=<h3>Oczka wodne</h3>Trafienie w oczko wodne (niebieski kolor) dolicza karne uderzenie do twojego wyniku i powoduje umieszczenie piłki na brzegu oczka.
+Comment[pt]=<h3>Poças (Água)</h3>Se acertar numa poça (azul) adiciona uma tacada de penalização à sua pontuação, e a sua bola é posta fora da poça.
+Comment[pt_BR]=<h3>Poças (Água)</h3>Acertar a bola numa poça (azul) adiciona uma tacada de penalidade à sua pontuação, e sua bola é colocada fora da poça.
+Comment[ru]=<h3>Лужи (вода)</h3>Если мяч попадает в лужу (синюю), вам начисляется штрафной удар, а мяч помещается в стороне от лужи.
+Comment[sk]=<h3>Puddles (Voda)</h3>Ak zasiahnete puddle (modrá), pripočíta sa vám penalizačný úder a vaša loptička sa objaví vedľa puddle.
+Comment[sl]= <h3>Ribniki (Voda)</h3>Če zadenete ribnik (modro), dobite kazenski udarec k točkam, žogica pa je postavljena zunaj ribnika.
+Comment[sr]=<h3>Баре (вода)</h3>Убацивањем лоптице у бару добијате казнени поен у резултату, а ваша лоптица се поставља изван баре.
+Comment[sr@Latn]=<h3>Bare (voda)</h3>Ubacivanjem loptice u baru dobijate kazneni poen u rezultatu, a vaša loptica se postavlja izvan bare.
+Comment[sv]=<h3>Vattenhinder</h3>Att slå i ett vattenhinder (blå) lägger till ett extra slag till poängen, och bollen placeras utanför hindret.
+Comment[ta]=<h3>சேற்றுமடு(தண்ணீர்)</h3>சேற்றுமடுவில் உதைப்பதால் (நீலம்) உங்கள் மதிப்பெண்ணில் அபராத குறி சேர்க்கப்படும். உங்கள் பந்து சேற்றுமடுவின் வெளிப்புறத்தில் வைக்கப்படும்.
+Comment[tg]=<h3>Кӯлмак (Об)</h3>Агар тӯбча ба кӯлак афтад (кабуд) шумо соҳиби задани ҷаримавӣ мегардед ва тӯбча дар канори кӯлмак ҷойгир карда мешавад.
+Comment[tr]= <h3>Birikinti (Su)</h3>Bir birikintiye (mavi) çarpmak skorunuza bir penaltı puanı eklenmesine yol açar, ve topunuz birikinti dışına çıkartılır.
+Comment[uk]=<h3>Калюжі (Вода)</h3>Якщо м'ячик попадає в калюжу (синя), вам нараховується штрафний удар і ваш м'ячик поміщається поза калюжею.
+Comment[xh]=<h3>Ichityana (Amanzi)</h3>Bethela kwi chityana (bhlowu) yongeza umvumbo wesohlwayo kwinqaku lakho, nebhola yakho ibekwe ngaphandle kwechityana.
+Comment[xx]=xx<h3>Puddles (Water)</h3>Hitting into a puddle (blue) adds a penalty stroke to your score, and your ball is placed outside the puddle.xx
+Comment[zh_CN]=<h3>水坑(水)</h3>打到水坑里(蓝色)会在您的成绩中记一次惩罚,然后您的球会被放在水坑的外边。
+Comment[zh_TW]=<h3>水坑(水)</h3>將球擊入水坑(藍色)會在您的分數中增加懲罰性的桿數,而您的球會被放在水坑外面。
+Comment[zu]=<h3>Amachibi (amanzi)</h3>Ukushayela echibini (liluhlaza) kufaka isigwebo emaphuzwini akho, ebese ibhola libekwa ngaphandle kwechibi.
+botWallVisible=true
+height=145
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=394
+
+[6-wall@0,0|38]
+endPoint=264,354
+startPoint=167,390
+
+[7-ball@48,355]
+dummykey=true
+
+[7-cup@104,54|5]
+dummykey=true
+
+[7-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[7-sign@130,2|4]
+Comment=<h3>Windmills</h3>Windmills (brown base with moving arm) have brown walls (or half walls). The windmill arm's speed may vary by hole.
+Comment[bg]=<h3>Вятърни мелници</h3>Вятърните мелници имат кафяви стени с подвижни части, между които трябва да мине топката. Скоростта им на движение може да е различна за различните дупки.
+Comment[bn]=<h3>বায়ুকল</h3> বায়ুকলের ( বাদামী ভিত্তির ওপর চলন্ত পাখা ) দেয়ালের ( বা অর্ধেক দেয়ালের ) রং বাদামী। গর্ত ভেদে বায়ুকলের পাখার গতি ভিন্ন হতে পারে।
+Comment[bs]=<h3>Vjetrenjače</h3>Vjetrenjača (smeđi stub sa pokretnom rukom) ima smeđe zidove (ili polu-zidove). Ruka vjetrenjače može biti različita za svaku rupu.
+Comment[ca]=<h3>Molins de vent</h3>Els molins de vent (base marró amb braç mòbil) tenen parets marrons (o mitja paret). La velocitat de rotació del braç no és constant.
+Comment[da]=<h3>Vindmøller</h3>Vindmøller (brun base med bevægende vinge) har brune vægge (eller halve vægge). Vindmøllevingens fart kan variere fra bane til bane.
+Comment[de]= <h3>Windmühlen</h3>Windmühlen (braune Gebäude mit sich bewegenden "Armen") haben braune Wände (oder Halbwände). Die Geschwindigkeit der "Arme" kann sich von Loch zu Loch ändern.
+Comment[es]=<h3>Molinos de viento</h3>Los molinos de viento (base marrón con brazo móvil) tienen paredes marrones (o medias paredes). La velocidad del brazo del molino puede variar completamente.
+Comment[et]=<h3>Tuuleveskid</h3>Tuuleveskid (pruun korpus, keerlevad tiivikud) on pruunide seinte (või poolseintega). Tiiviku liikumise kiirus võib olla rajati erinev.
+Comment[fi]=<h3>Tuulimyllyt</h3>Tuulimyllyissä (ruskea jalusta, pyörivät siivet) on ruskeat seinät (tai puoliseinät). Tuulimyllyn siiven vauhti voi vaihdella reikien välillä.
+Comment[fr]=<h3>Moulins à vent</h3>Les moulins à vent (base marron avec des pales qui remuent) ont des murs marrons (ou des demi-murs). La vitesse des bras des moulins peut varier à chaque tour.
+Comment[gl]=<h3>Muíños de vento</h3>Os muiños de vento (marróns con aspas en movemento) teñen valados marróns (ou medios valados). A velocidade das aspas pode variar dependendo do burato.
+Comment[he]=<h3>טחנות רוח</h3>לטחנות רוח (בסיס חום עם זרוע נעה) יש קירות (או חצאי קירות) בצבע חום. המהירות של זרוע הטחנה עשויה להיות שונה מגומה לגומה.
+Comment[hi]=<h3>पवनचक्कियाँ</h3>पवनचक्कियाँ (भूरे आधार युक्त घूमती भुजा लिए हुए) भूरे रंग की दीवार (या आधी दीवार) युक्त हैं. होल के अनुसार पवनचक्कियों के भुजाओं की गतियों में अन्तर होगा.
+Comment[hu]=<h3>Szélmalmok</h3>A szélmalmok (barna alap, mozgó karok) falai barna színűek (vagy felezve vannak). A szélmalmok lapátjainak sebessége lyukanként változhat.
+Comment[is]= <h3>Vindmyllur</h3>Vindmyllur (Brúnnn flötur með breyfiarmi) hafa brúna veggi (eða hálf-veggi). Hraði arma vindmyllunnar getur verið breytilegur.
+Comment[it]=<h3>Mulini a vento</h3> I mulini a vento (una base marrone con delle pale mobili) hanno murature marroni (o mezzi muri). La velocità dellepale del mulino può cambiare da una buca all'altra.
+Comment[ja]=<h3>風車</h3>風車(茶色で、動いているアーム) は、 茶色の壁がボーダーにあります。ホールによってアームは違う速度で 動くかもしれません。
+Comment[mk]=<h3>Ветерници</h3>Ветерниците (кафеава подлога со подвижна преграда) имаат кафеави ѕидови (или половина ѕидови). Брзината на преградата на ветерницата зависи од дупка до дупка.
+Comment[nb]=<h3>Vindmøller</h3>Vindmøller (brun base med bevegelig arm) har brune vegger (eller halv-vegger). Vindmøllas fart kan variere fra hull til hull.
+Comment[nl]= <h3>Windmolens</h3>Windmolens (bruine basis met bewegende wieken) hebben bruine muren (of halve muren). De bewegingssnelheid van de wieken varieert van hole tot hole.
+Comment[nn]=<h3>Vindmøller</h3>Vindmøller (brun, med arm som rører seg) har brune veggar (eller halvveggar). Farten på armen kan vera ulik frå hol til hol.
+Comment[pl]=<h3>Wiatraki</h3>Wiatraki (brązowa podstawa z ruchomym ramieniem) mają brązowe ściany (lub pół-ściany). Szybkość ramienia wiatraka może być różna dla różnych dołków.
+Comment[pt]=<h3>Moinhos</h3>Os moinhos (base castanha com um braço móvel) têm paredes (ou meias-paredes) castanhas. A velocidade do moinho pode variar de acordo com o buraco.
+Comment[pt_BR]=<h3>Moinhos</h3>Os moinhos (base marrom com braço móvel) possuem paredes marrons (ou meias-paredes). A velocidade do braço do moinho pode variar de um buraco a outro.
+Comment[ru]=<h3>Мельницы</h3>Мельницы (коричневое основание и движущиеся лопасти) имеют коричневые стены (или ворота). Скорость лопастей может меняться от лунки к лунке.
+Comment[sk]=<h3>Veterné mlyny</h3>Veterné mlyny (hnedá základňa s pohybujúcim sa ramenom) má hnedé steny (alebo polo-steny). Rýchlosť ramena môže byť rôzna.
+Comment[sl]= <h3>Mlini na veter</h3>Mlini na veter (rjava osnova z gibajočo roko) imajo rjave zidove (ali polzidove). Hitrost roke mlina na veter se lahko spreminja.
+Comment[sr]=<h3>Ветрењаче</h3>Ветрењаче (смеђа база са покретним краком) имају смеђе зидове (или полузидове). Брзина крака ветрењаче може варирати од рупе до рупе.
+Comment[sr@Latn]=<h3>Vetrenjače</h3>Vetrenjače (smeđa baza sa pokretnim krakom) imaju smeđe zidove (ili poluzidove). Brzina kraka vetrenjače može varirati od rupe do rupe.
+Comment[sv]=<h3>Väderkvarnar</h3>Väderkvarnar (brun bas med vinge som flyttas) har bruna väggar (eller halva väggar). Hastigheten på väderkvarnens vinge kan variera för olika hål.
+Comment[ta]=<h3>வின்ட்மில்ஸ்</h3>வின்ட்மில்ஸ்(பிரவுன் அடித்தளத்தோடு சுற்றும் கரங்கள்)பிரவுன் சுவர்கள் உள்ளன(அல்லது பாதி சுவர்கள்) இந்த வின்ட்மில்ஸ் கரங்களின் வேகம் ஒரு ஓட்டையளவு வேறுபடும்.
+Comment[tg]=<h3>Осиёҳо</h3>Осиёҳо (асоси ҷигарранг ва дастаҳои ҳаракаткунанда) деворҳои ҷигарранг доранд (ё нисфи деворҳо). Аз сӯрох то ба сӯрох суръати дастаҳо тағир ёфта метавонанд.
+Comment[tr]= <h3>Yel Değirmenleri</h3>Yel değirmenleri (hareketli kolu olan kahverengi cisimler) kahverengi duvarlara (veya yarım duvarlara) sahiptir. Yel değirmeninin kolu topun hızını artırır.
+Comment[uk]=<h3>Млини</h3>Млини (коричневі основи з вітряками) мають коричневі стіни (або напів-стіни). Швидкість вітряка млина може бути різною для різних лунок.
+Comment[xh]=<h3>Amaphiko omoya</h3>Amaphiko omoya (Isiseko esimdaka nebala kunye nengalo ehambayo) Yiba namadonga amdaka ngebala (Okanye isiqingatha samadonga). amendu engalo yephikomoya angahluka ngomngxunya.
+Comment[xx]=xx<h3>Windmills</h3>Windmills (brown base with moving arm) have brown walls (or half walls). The windmill arm's speed may vary by hole.xx
+Comment[zh_CN]=<h3>风车</h3>风车(棕色的底座和运动的转臂)有棕色的墙(或半墙)。不同球洞风车转臂的速度可以不同。
+Comment[zh_TW]=<h3>風車</h3>風車(褐色基礎並有移動的風扇)有褐色的牆(或一半的牆)。風車的風扇速度可能隨著洞而改變。
+Comment[zu]=<h3>Iphiko lomoya</h3>Iphiko lomoya (Isiqu esinsundu sinengalo enyakazayo) inezindonga ezinsundu (noma izindonga ezinguhhafu). Ijubane lengalo yephiko lomoya lingashiyana ngezimbobo.
+botWallVisible=true
+height=206
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=267
+
+[7-windmill@15,145|3]
+botWallVisible=false
+bottom=true
+height=71
+leftWallVisible=true
+rightWallVisible=true
+speed=5
+topWallVisible=false
+width=107
+
+[8-ball@39,234]
+dummykey=true
+
+[8-blackhole@60,343|4]
+exit=195,287
+exitDeg=0
+maxspeed=3
+minspeed=1
+
+[8-cup@333,287|5]
+dummykey=true
+
+[8-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=false
+maxstrokes=10
+par=2
+
+[8-sign@5,5|3]
+Comment=<h3>Black Holes</h3>Black Holes transport the ball to their exit, and eject it at a speed directly relational to the speed your ball was going. Choose <u>Hole->Show Info</u> to see which Black Hole goes to which exit and the direction the ball will come out at.
+Comment[af]=<h3>Swart Gate</h3>Swart Gate vervoerder die bal na hulle beïendig, en uitskiet dit na 'n spoed direk verwante na die spoed jou bal was gaan. Kies <u>Gat->Show Inligting</u> na sien wat Swart Gat gaan na wat beïendig en die rigting die bal sal kom uit na.
+Comment[bg]=<h3>Черни дупки</h3>Черните дупки пренасят топката до даден изхода и я изхвърлят със скорост, пропорционална на скоростта на движение на топката. Изберете от менюто <u>Дупка/Информация</u>, за да видите коя черна дупка до кой изход води и посоката, от която ще се появи топката.
+Comment[bn]=<h3>কালো গর্ত</h3> কালো গর্ত দিয়ে বল তার বহির্গমনের দিকে চলে যায়; কী গতিতে বহির্গমন দিয়ে বল বের হবে তা বলের গতির সাথে সরাসরি সম্পর্কিত। কোন কালো গর্ত কোন বহির্গমনের সাথে যুক্ত এবং কোন দিক দিয়ে বল বের হয়ে আসবে তা জানার জন্য <u>গর্ত-তথ্য প্রদর্শন</u> চাপুন।
+Comment[bs]=<h3>Crne rupe</h3>Crne rupe transportuju lopticu do svog izlaza, te je izbacuju brzinom proporcionalnom brzini kojom je loptica ranije išla. Izaberite <u>Rupa->Pokaži informacije</u> da vidite koja Crna rupa odgovara kojem izlazu kao i smjer kojim će loptica biti izbačena.
+Comment[ca]=<h3>Forats negres</h3>Els forats negres transporten la pilota cap a la sortida i la disparen a una velocitat directament proporcional a la que ja tenien. Seleccioneu <u>Forat->Mostra informació</u> per a veure quin forat negre porta a cada sortida i al direcció en la sortirà la bola.
+Comment[da]=<h3>Sorte huller</h3>Sorte huller transporterer bolden til deres udgang og affyrer bolden med en fart i direkte relation til boldens oprindelige fart. Vælg<u>Hul -> Vis info</u> for at se hvilket sort hul der fører til hvilken udgang og den retning bolden vil komme ud i.
+Comment[de]= <h3>Schwarze Löcher</h3>Schwarze Löcher teleportieren den Ball zu ihrem Ausgang, und werfen den Ball mit einer Geschwindigkeit, die zu seiner ursprünglichen Geschwindigkeit direkt im Verhältnis steht, wieder auf das Spielfeld. Wählen Sie <u>Loch->Info anzeigen</u> um zu sehen, welches Schwarze Loch zu welchem Ausgang gehört, und in welcher Richtung der Ball herauskommt.
+Comment[es]=<h3>Agujeros negros</h3>Los agujeros negros transportan la pelota a la salida y la disparan a una velocidad directamente proporcional a la que llevaban. Seleccione <u>Hoyo->Mostrar información</u> para ver qué agujero negro lleva a cada salida y la dirección en la que saldrá la bola.
+Comment[et]=<h3>Mustad augud</h3>Mustad augud toimetavad palli väljumisavani ning paiskavad selle sealt välja sama kiirusega, millega pall musta auku löödi. Vali <u>Rada->Näita infot</u>, mis näitab, kus asub musta augu väljumisava ja millises suunas pall sealt väljub.
+Comment[fi]=<h3>Mustat aukot</h3> Mustat aukot kuljettavat pallon ulostuloonsa, ja ampuvat pallon nopeudella, joka on verranollinen pallon entiseen nopeuteen. Valitse <u>Reikä->Näytä tiedot</u> nähdäksesi mikä musta aukko on yhteydessä mihinkin ulostuloon ja mihin suuntaan pallo lähtee.
+Comment[fr]=<h3>Trous noirs</h3>Les trous noirs transportent la balle vers leur sortie et l'éjectent à une vitesse directement en relation avec la vitesse à laquelle votre balle est entrée. Choisissez <u>Trou / Afficher les informations</u> pour voir quel trou noir va à quelle sortie et la direction dans laquelle la balle sortira.
+Comment[gl]=<h3>Buracos negros</h3>Os buracos negros levan a bola ata a súa saída, e expúlsana a unha velocidade directamente proporcional á que tiña a bola. Elixe <u>Burato->Mostrar Información</u> para ver cara onde vai saír un burato negro e a dirección na que sairá a bola.
+Comment[he]=<h3>חורים שחורים</h3>חורים שחורים מעבירים את הכדור ליציאה שלהם, ופולטים אותם במהירות העומדת ביחס ישר למהירות שבה הכדור שלך נע כאשר הוא נכנס לחור השחור. בחר ב<u>גומה->הצג מידע</u> כדי לראות לאיזו יציאה מוביל כל חור שחור ובאיזה כיוון ייצא הכדור.
+Comment[hi]=<h3>ब्लेक होल्स</h3>ब्लेक होल्स गेंद को उनके निर्गम पर ले जाती हैं, तथा आपकी गेंद की गति के सीधे अनुपात की गति से निकाल बाहर फेंकती है. चुनें <u>होल->जानकारी दिखाएँ</u> यह देखने के लिए कि कौन सा ब्लेक होल किस निर्गम पर जाता है तथा गेंद किस दिशा से बाहर आएगा.
+Comment[hu]=<h3>Fekete lyukak</h3>A fekete lyukba esett golyó a lyuk kijáratához kerül, a kilépési sebesség arányos lesz a belépésivel. Válassza a <u>Lyuk->Információ</u> menüpontot, ha tudni szeretné, melyik fekete lyuk hová vezet és milyen a kilépési irány.
+Comment[is]= <h3>Svarthol</h3>Svartar holur flytja boltann um ormagöng að úttaki, og spýta boltanum út í réttu hlutfalli við hraðann sem þú sendir á inn í svartholið. Veldu <u>Hola->Sýna upplýsingar</u> til að sjá ormagöngin og þá hvar boltinn mun koma út.
+Comment[it]=<h3>Buchi neri</h3> I buchi neri trasportano la pallina alla loro uscita e la espellono con una velocità proporzionale a quella con cui è entrata. Scegli <u>Buca->Mostra informazioni</u> per vedere la corrispondenza tra buchi neri e uscite e la direzione con cui vengono espulse le palline.
+Comment[ja]=<h3>ブラックホール</h3>ブラックホールはボールを 入った時の速度と同じ速度で出口に運んで出します。 <u>ホール->情報を表示</u> とすると、ブラックホールと出口の連係と 出口の方向が表示されます。
+Comment[mk]=<h3>Црни дупки</h3>Црните дупки ги пренесуваат топките до нивниот излез и ги исфрлаат со брзина која е директно во релација со брзината со која вашата топка предходно се движела. Изберете <u>Дупка->Прикажи информација</u> за да видите која црна дупка оди со кој излез и насоката во која топката ќе излезе.
+Comment[nb]=<h3>Svarte hull</h3>Svarte hull sender ballen til utgangen, og kaster den ut i samme fart som den kom inn. Velg <u>Hull->Vis info</u> for å se hvilket svart hull som går til hvilken utgang og retningen ballen vil komme ut av hullet.
+Comment[nl]= <h3>Zwarte gaten</h3>Zwarte gaten teleporteren de bal naar hun uitgang, en werpen de bal met een snelheid die overeenkomt met de oorspronkelijke snelheid terug op het speelveld. Kies <u>Hole->Info tonen</u> om te zien welk zwart gat naar welke uitgang leidt, en in welke richting de bal de uitgang zal verlaten.
+Comment[nn]=<h3>Svarte hol</h3>Svarte hol flyttar ballen til utgangen, og kastar han ut med ein fart som svarar til farten han hadde. Vel <u>Hol->Vis info</u> for å sjå kva for svarte hol som går til kva for utgang, og kva for retning ballen vil få.
+Comment[pl]=<h3>Czarne dziury</h3>Czarne dziury przenoszą piłkę do swojego wyjścia i wyrzucają ją z prędkością proporcjonalną do prędkości z jaką leciała. Wybierz <u>Dołek->Pokaż informacje</u>, aby zobaczyć które wyjście odpowiada której Czarnej Dziurze i w jakim kierunku piłka zostanie wyrzucona.
+Comment[pt]=<h3>Buracos Negros</h3>Os buracos negros transportam a bola para a sua saída e expulsam-na a uma velocidade proporcional à velocidade com que a sua bola estava a ir. Escolha o <u>Buraco->Mostrar a Informação</u> para ver para onde vai um determinado buraco negro e a direcção com que a bola irá sair.
+Comment[pt_BR]=<h3>Buracos negros</h3>Os buracos negros transportam a bola para sua saída, e ejetam-na com uma velocidade diretamente relacionada com a velocidade em que sua bola estava entrou. Escolha <u>Buraco-> Mostrar Informação</u> para ver qual Buraco Negro dá em qual saída e qual a direção da bola ao sair dele.
+Comment[ru]=<h3>Чёрные дыры</h3>Чёрные дыры ведут мяч к выходу и выбрасывают его со скоростью, прямо зависящей от его собственной скорости. См. <u>Лунка->Сведения</u>, чтобы узнать, к какому выходу ведёт чёрная дыра и в каком направлении вылетит мяч.
+Comment[sk]=<h3>Čierne diery</h3>Čierne diery presúvajú loptičku na svoj druhý koniec a tam ju vystrelia rovnakou rýchlosťou, ktorou do diery vletela. Výberom <u>Jamka-Zobraziť informácie</u> môžete zistiť, kde je druhý koniec a ktorým smerom loptička vyletí.
+Comment[sl]=<h3>Črne luknje</h3>Črne luknje prestavijo žogico do svojega izhoda in jo izvržejo s hitrostjo, ki je odvisna od prvotne hitrosti žogice. Izberite <u>Luknja -> Pokaži informacije</u>, da vidite, katera črna luknja vodi do katerega izhoda in smer izvržene žogice.
+Comment[sr]=<h3>Црне рупе</h3>Црне рупе транспортују лоптицу до свог излаза и избацују је брзином директно пропорционалном брзини којом је лоптица претходно ишла. Изаберите <u>Рупа->Прикажи информације</u> да бисте видели која црна рупа има који излаз и смер који ће лоптица имати када изађе.
+Comment[sr@Latn]=<h3>Crne rupe</h3>Crne rupe transportuju lopticu do svog izlaza i izbacuju je brzinom direktno proporcionalnom brzini kojom je loptica prethodno išla. Izaberite <u>Rupa->Prikaži informacije</u> da biste videli koja crna rupa ima koji izlaz i smer koji će loptica imati kada izađe.
+Comment[sv]=<h3>Svarta hål</h3>Svarta hål transporterar bollen till sina utgångar, och skickar ut den med en hastighet som är direkt proportionell mot hastigheten som bollen hade. Välj <u>Hål->Visa information</u> för att se vilket svart hål som går till vilken utgång, och i vilken riktning bollen kommer ut.
+Comment[ta]=<h3>கருப்பு ஓட்டைகள்</h3>கருப்பு ஓட்டைகள் பந்தை வெளியேற்றத்தில் நிறுத்தி பந்து எந்த வேகத்தில் வருகிறதோ அதே வேகத்தில் வெளியேற்றும். தேர்வு <u>ஓட்டை-></u> எந்த கருப்பு ஓட்டை எந்த வழியாக வெளியேறுகிறது என்பதையும் பந்து வெளியேறும் திசையையும் பார்ப்பதற்கு தகவலை காட்டவும்.
+Comment[tr]= <h3>Karadelikler</h3>Kara delikler topu çıkışlarına taşırlar ve topuzun hızı ile orantılı bir hız ile dışarı atarlar. <u>Delik->Bilgi Göster</u> seçerek hangi karadeliğin hangi çıkışa sahip olduğunu ve topun çıkış yönünü öğrenebilirsiniz.
+Comment[uk]=<h3>Чорні діри</h3>Чорні діри переносять м'ячик до їх виходу і викидають його із швидкість прямо залежною від його власної. Виберіть <u>Лунка->Показати інформацію</u>, щоб подивитись яка чорна діра веде до якого виходу і в якому напрямку вилетить м'ячик.
+Comment[xh]=<h3>Imingxuma Emnyama</h3>Imingxuma Emnyama ihambisa ibhola iyokutsho kwindawo yazo yokuphuma, ize iyikhuphe ngqo ngesantya esihlobene kwisantya ibihamba ngaso ibhola yakho. Khetha <u>Umngxuma->Bonisa Ulwazi</u> ze bone ukuba ngowuphi Umngxuma Omnyama ohamba ayokutsho kweliphi icala lendlela nakweyiphi indawo yokuphuma eza kuza kulo ibhola yakho.
+Comment[xx]=xx<h3>Black Holes</h3>Black Holes transport the ball to their exit, and eject it at a speed directly relational to the speed your ball was going. Choose <u>Hole->Show Info</u> to see which Black Hole goes to which exit and the direction the ball will come out at.xx
+Comment[zh_CN]=<h3>黑洞</h3>黑洞将球运送到出口处,并根据您的球速按成比例的速度将其弹出。选择<u>球洞->显示信息</u>可查看哪个黑洞对应哪个出口,以及球从出口弹出的方向。
+Comment[zh_TW]=<h3>黑洞</h3>黑洞會將球傳送到它們的出口,而將它們射出的速度與您的球進入的速度有直接關係。選擇<u>洞->顯示資訊</u>來看看從哪個黑洞進入會從從哪裡出來以及球出現的方向。
+Comment[zu]=<h3>Izimbobo ezimnyama</h3>Izimbobo ezimnyama zithatha ibhola ziliyise endaweni yazo yokuphuma, zililahle ngejubane elihambisana nejubane lebhola lakho. Khetha<u>Imbobo->Khombisa ulwazi</u>ukubona ukuthi yiphi imobo emnyama iya kuyiphi indawo yokuphuma nendlela ibhola elizophuma kuyo.
+botWallVisible=true
+height=206
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=390
+
+[8-wall@0,0|6]
+endPoint=145,132
+startPoint=147,317
+
+[8-wall@0,0|7]
+endPoint=147,317
+startPoint=69,390
+
+[9-ball@57,307]
+dummykey=true
+
+[9-cup@360,255|6]
+dummykey=true
+
+[9-floater@236,298|4]
+botWallVisible=false
+endPoint=236,298
+height=40
+leftWallVisible=false
+rightWallVisible=false
+speed=3
+startPoint=105,248
+topWallVisible=false
+width=80
+
+[9-hole@-50,-50|0]
+borderWalls=true
+hasFinalLoad=true
+maxstrokes=10
+par=2
+
+[9-sign@5,6|3]
+Comment=<h3>Floaters</h3>Floaters are moving platforms that carry a ball that lands on it. Floaters' speeds vary.
+Comment[bg]=<h3>Хвърчащи килимчета</h3>Хвърчащите килимчета са подвижни платформи, които носят падналите върху тях топки. Скоростта им може да варира.
+Comment[bn]=<h3>ফ্লোটার</h3> ফ্লোটার হল একটি ভাসমান প্লাটফর্ম যার ওপর বল পড়লে সে তা নিয়ে ভেসে বেড়ায়। বিভিন্ন ফ্লোটারের গতি বিভিন্ন রকম।
+Comment[bs]=<h3>Plutači</h3>Plutači su pokretne platforme koje prenose lopticu koja padne na njih. Brzina plutača je promjenljiva.
+Comment[ca]=<h3>Flotadors</h3>Els flotadors són plataformes mòbils que transporten a una pilota que aterra a sobre seu. La velocitat dels flotadors no és constant.
+Comment[da]=<h3>Svævere</h3>Svævere er bevægende platforme som flytter en bold der lander på dem. Svæveres fart varierer.
+Comment[de]= <h3>Floß</h3>Flöße sind bewegliche Plattformen, die einen Ball weitertragen, wenn er auf ihnen landet. Es gibt Flöße mit unterschiedlichen Geschwindigkeiten.
+Comment[es]=<h3>Flotadores</h3>Los flotadores son plataformas móviles que transportan una pelota que aterrice en ellas. La velocidad de los flotadores es variable.
+Comment[et]=<h3>Hõljukid</h3>Hõljukid on liikuvad platvormid, mis suudavad neil peatunud palli liigutada. Hõljukite kiirused erinevad.
+Comment[fi]=<h3>Kellujat</h3> Kellujat ovat tasoja, jotka siirtävät palloa joka on tasolla. Tasojen vauhti vaihtelee.
+Comment[fr]=<h3>Flotteurs</h3>Les flotteurs sont des plates-formes qui portent une balle qui atterrit dessus. La vitesse des flotteurs varie.
+Comment[gl]=<h3>Flotadores</h3>Os Flotadores son plataformas móbiles que transportan á pelota que aterrou nelas. A velocidade dos flotadores é variable.
+Comment[he]=<h3>משטחים צפים</h3>אלה משטחים נעים שנושאים איתם כדור שנוחת עליהם. מהירותם עשויה להשתנות ממשטח למשטח.
+Comment[hi]=<h3>तैराक</h3>तैराक चलते हुए चबूतरे होते हैं जो अपने ऊपर अवतरित गेंद को लिए हुए होते हैं. तैराक की गति में अन्तर होते रहता है.
+Comment[hu]=<h3>Lebegők</h3>Ezek a vízen úszó platformok elbírják a rájuk eső golyókat. Sebességük különböző lehet.
+Comment[is]= <h3>Prammar</h3>Prammar geta flutt bolta sem lenda á honum. hraði er breytilegur.
+Comment[it]=<h3>Floater</h3> I floaters sono piattaforme mobili che trasportano la pallina che atterra su di essi. La velocità dei floater è variabile.
+Comment[ja]=<h3>フローター</h3>フローターはボールを運ぶ、 動くプラットフォームです。ホールによって違う速度で動くかもしれません。
+Comment[lv]=<h3>Slīdošās platformas</h3> Slīdošās platformas pārvietojas un pārvieto bumbiņu, kas atordas uz tās. Platformu ātrums variē.
+Comment[mk]=<h3>Лебдачи</h3>Лебдачите се подвижни платформи кои ја носат топката која ќе слета на нив. Брзините на лебдачите се различни.
+Comment[nb]=<h3>Fløterne</h3>Fløterne er bevegelige plattformer som kan bære en ball som lander på den. Farten kan variere.
+Comment[nl]= <h3>Drijvers</h3>Drijvers zijn bewegende platforms die een bal meenemen als deze er op landt. De snelheid van drijvers kan vari�en.
+Comment[nn]=<h3>Plattformar</h3>Plattformar flyttar seg og tek med seg ballar som landar på dei. Farten til plattformane kan variera frå hol til hol.
+Comment[pl]=<h3>Promy</h3>Promy to poruszające się platformy, które przewożą piłkę, która na nich wyląduje. Prędkość promów może być różna.
+Comment[pt]=<h3>Bóias</h3>As bóias são plataformas móveis que carregam uma bola que pare em cima delas. As velocidades das bóias variam.
+Comment[pt_BR]=<h3>Bóias</h3>As bóias são plataformas em movimento que têm uma bola sobre ela. A velocidade das bóias pode variar.
+Comment[ru]=<h3>Ползуны</h3>Ползуны движутся и увлекают мяч, который приземлился на них. Скорость ползунов может быть разной.
+Comment[sk]=<h3>Floaters</h3>Floaters sú presúvajúce sa plochy, ktoré odnášajú loptičky. Ich rýchlosť sa mení.
+Comment[sl]=<h3>Plovci</h3>Plovci so premikajoče se ploščadi, ki bodo nosili žogico, ki pristane na njej. Hitrost plovcev se lahko spreminja.
+Comment[sr]=<h3>Плутачи</h3>Плутачи су покретне платформе које носе лоптицу која падне на њих. Брзина плутача може варирати.
+Comment[sr@Latn]=<h3>Plutači</h3>Plutači su pokretne platforme koje nose lopticu koja padne na njih. Brzina plutača može varirati.
+Comment[sv]=<h3>Flottar</h3>Flottar är flyttbara plattformar som kan bära en boll som landar på dem. Flottarnas hastighet varierar.
+Comment[ta]=<h3>மிதப்பவர்கள்</h3>மிதப்பவர்கள் எல்லோரும் நகரும் தளமேடை. அது தன் மேலிருக்கும் பந்துகளை எடுத்துச் செல்லும். மிதப்பவர்களின் வேகம் மாறுபடும்.
+Comment[tg]=<h3>Хазандаҳо</h3>Хазандаҳо тӯберо, ки ба онҳо афтидааст, гирифта мебаранд. Суръати Хазандаҳо гуногун буда метавонад.
+Comment[tr]= <h3>Yüzücüler</h3>Yüzücüler üstüne konan topları taşıyan platformlardır. Yüzücülerin değişik hızları vardır.
+Comment[uk]=<h3>Плавуни</h3>Плавуни - це рухомі платформи, які несуть м'ячик, котрий на них попадає. Швидкість плавунів може бути різною.
+Comment[xh]=<h3>Izinto ezidadayo</h3>Floaters are moving platforms that carry a ball that lands on it. Floaters' speeds vary.
+Comment[xx]=xx<h3>Floaters</h3>Floaters are moving platforms that carry a ball that lands on it. Floaters' speeds vary.xx
+Comment[zh_CN]=<h3>浮台</h3>浮台是可以载着球移动的平台。浮台的速度是可变的。
+Comment[zh_TW]=<h3>浮標</h3>浮標是會移動的平台,可以承載落在它上面的球。浮標的速度是多變的。
+Comment[zu]=<h3>Okuntantayo</h3>Okuntantayo amabala anyakazayo aphatha ibhola elihlala kuwo.Okuntantayo kunamajubane ashiyanayo.
+botWallVisible=true
+height=127
+leftWallVisible=true
+rightWallVisible=true
+topWallVisible=true
+width=391
+
+[9-wall@0,0|5]
+endPoint=177,126
+startPoint=171,391
diff --git a/kolf/tutorial.kolfgame b/kolf/tutorial.kolfgame
new file mode 100644
index 00000000..30736700
--- /dev/null
+++ b/kolf/tutorial.kolfgame
@@ -0,0 +1,10 @@
+[0 Saved Game]
+Competition=false
+Course=/opt/kde3/share/apps/kolf/tutorial.kolf
+Current Hole=1
+Players=1
+
+[1]
+Color=#bbffd4
+Name=Player 1
+Scores=0
diff --git a/kolf/vector.cpp b/kolf/vector.cpp
new file mode 100644
index 00000000..f4c05262
--- /dev/null
+++ b/kolf/vector.cpp
@@ -0,0 +1,106 @@
+#include <kdebug.h>
+
+#include "vector.h"
+
+// this and vector.h by Ryan Cummings
+
+// Creates a vector with between two points
+Vector::Vector(const QPoint &source, const QPoint &dest) {
+ _magnitude = sqrt(pow(source.x() - dest.x(), 2) + pow(source.y() - dest.y(), 2));
+ _direction = atan2(source.y() - dest.y(), source.x() - dest.x());
+}
+
+// Creates a vector with between two points
+Vector::Vector(const Point &source, const Point &dest) {
+ _magnitude = sqrt(pow(source.x - dest.x, 2) + pow(source.y - dest.y, 2));
+ _direction = atan2(source.y - dest.y, source.x - dest.x);
+}
+
+// Creates an empty Vector
+Vector::Vector() {
+ _magnitude = 0.0;
+ _direction = 0.0;
+}
+
+// Copy another Vector object
+Vector::Vector(const Vector& v) {
+ _magnitude = v._magnitude;
+ _direction = v._direction;
+}
+
+// Set the X component
+void Vector::setComponentX(double x) {
+ setComponents(x, componentY());
+}
+
+// Set the Y component
+void Vector::setComponentY(double y) {
+ setComponents(componentX(), y);
+}
+
+// Operations with another Vector performs vector math
+Vector Vector::operator+(const Vector& v) {
+ double x = componentX() + v.componentX();
+ double y = componentY() + v.componentY();
+
+ return Vector(sqrt((x * x) + (y * y)), atan2(y, x));
+}
+
+Vector Vector::operator-(const Vector& v) {
+ double x = componentX() - v.componentX();
+ double y = componentY() - v.componentY();
+
+ return Vector(sqrt((x * x) + (y * y)), atan2(y, x));
+}
+
+Vector& Vector::operator+=(const Vector& v) {
+ setComponents(componentX() + v.componentX(), componentY() + v.componentY());
+ return *this;
+}
+
+Vector& Vector::operator-=(const Vector& v) {
+ setComponents(componentX() - v.componentX(), componentY() - v.componentY());
+ return *this;
+}
+
+double Vector::operator*(const Vector& v) {
+ return ((componentX() * v.componentX()) + (componentY() * v.componentY()));
+}
+
+// Operations with a single double value affects the magnitude
+Vector& Vector::operator+= (double m) {
+ _magnitude += m;
+ return *this;
+}
+
+Vector& Vector::operator-= (double m) {
+ _magnitude -= m;
+ return *this;
+}
+
+Vector& Vector::operator*= (double m) {
+ _magnitude *= m;
+ return *this;
+}
+
+Vector& Vector::operator/= (double m) {
+ _magnitude /= m;
+ return *this;
+}
+
+// Sets both components at once (the only way to do it efficiently)
+void Vector::setComponents(double x, double y) {
+ _direction = atan2(y, x);
+ _magnitude = sqrt((x * x) + (y * y));
+}
+
+void debugPoint(const QString &text, const Point &p)
+{
+ kdDebug(12007) << text << " (" << p.x << ", " << p.y << ")" << endl;
+}
+
+void debugVector(const QString &text, const Vector &p)
+{
+ // debug degrees
+ kdDebug(12007) << text << " (magnitude: " << p.magnitude() << ", direction: " << p.direction() << ", direction (deg): " << (360L / (2L * M_PI)) * p.direction() << ")" << endl;
+}
diff --git a/kolf/vector.h b/kolf/vector.h
new file mode 100644
index 00000000..1da7328b
--- /dev/null
+++ b/kolf/vector.h
@@ -0,0 +1,92 @@
+#ifndef KOLF_VECTOR_H
+#define KOLF_VECTOR_H
+
+#include <math.h>
+
+#include <qpoint.h>
+
+class Point
+{
+public:
+ Point(double _x, double _y)
+ {
+ x = _x;
+ y = _y;
+ }
+
+ Point()
+ {
+ x = 0;
+ y = 0;
+ }
+
+ double x;
+ double y;
+};
+
+void debugPoint(const QString &, const Point &);
+
+// This and vector.cpp by Ryan Cummings
+
+// Implements a vector in 2D
+class Vector {
+ public:
+ // Normal constructors
+ Vector(double magnitude, double direction) { _magnitude = magnitude; _direction = direction; }
+ Vector(const QPoint& source, const QPoint& dest);
+ Vector(const Point& source, const Point& dest);
+ Vector();
+
+ // Copy constructor
+ Vector(const Vector&);
+
+ // Accessors, sorta
+ double componentX() const { return (_magnitude * cos(_direction)); };
+ double componentY() const { return (_magnitude * sin(_direction)); };
+
+ // Sets individual components
+ // Wrappers around setComponents(double, double) - below
+ void setComponentX(double x);
+ void setComponentY(double y);
+
+ // Sets both components at once
+ void setComponents(double x, double y);
+
+ // Accessors
+ double magnitude() const { return _magnitude; }
+ double direction() const { return _direction; }
+ void setMagnitude(double m) { _magnitude = m; }
+ void setDirection(double d) { _direction = d; }
+
+ // Vector math
+ Vector operator+(const Vector&);
+ Vector operator-(const Vector&);
+
+ Vector& operator+=(const Vector&);
+ Vector& operator-=(const Vector&);
+
+ // Dot product
+ double operator*(const Vector&);
+
+ // Magnitude math
+ Vector operator+(double m) { return Vector(_magnitude + m, _direction); }
+ Vector operator-(double m) { return Vector(_magnitude - m, _direction); }
+ Vector operator*(double m) { return Vector(_magnitude * m, _direction); }
+ Vector operator/(double m) { return Vector(_magnitude / m, _direction); }
+
+ Vector& operator+=(double m);
+ Vector& operator-=(double m);
+ Vector& operator*=(double m);
+ Vector& operator/=(double m);
+
+ // Return the vector's equalivent on the unit circle
+ Vector unit() const { return Vector(1.0, _direction); }
+
+ protected:
+ double _magnitude;
+ double _direction;
+};
+
+void debugVector(const QString &, const Vector &);
+
+#endif
diff --git a/kolf/x-kolf.desktop b/kolf/x-kolf.desktop
new file mode 100644
index 00000000..e2c622e1
--- /dev/null
+++ b/kolf/x-kolf.desktop
@@ -0,0 +1,60 @@
+# KDE Config File
+[Desktop Entry]
+MimeType=application/x-kolf
+Type=MimeType
+Comment=Kolf Saved Game
+Comment[ar]=لعبة Kolf محفوظة
+Comment[be]=Захаваная гульня ў гольф
+Comment[bg]=Записана игра на Kolf
+Comment[bn]=কল্‌ফ খেলা সংরক্ষণ করেছে
+Comment[br]=C'hoari enrollet Kolf
+Comment[bs]=Kolf snimljena igra
+Comment[ca]=Partida de Kolf desada
+Comment[cs]=Uložená hra Kolfu
+Comment[cy]=Gêm Kolf wedi'i Gadw
+Comment[da]=Gemt Kolf-spil
+Comment[de]=Gespeichertes Kolf-Spiel
+Comment[el]=Αποθηκευμένο παιχνίδι Kolf
+Comment[eo]=Kolf konservita ludo
+Comment[es]=Juego de Kolf guardado
+Comment[et]=Kolfi salvestatud mäng
+Comment[eu]=Kolf-en gordetako jokoa
+Comment[fa]=بازی ذخیره‌شدۀ Kolf
+Comment[fi]=Kolfi-pelitallennus
+Comment[fr]=Partie enregistrée de Kolf
+Comment[gl]=Xogo de Kolf gravado
+Comment[he]=משחק שמור של Kolf
+Comment[hi]=सहेजा गया कोल्फ खेल
+Comment[hr]=Spremljena Kolf igra
+Comment[hu]=Kolf elmentett játék
+Comment[is]=Vistaður Kolf leikur
+Comment[it]=Partita di Kolf salvata
+Comment[ja]=Kolf の保存されたゲーム
+Comment[km]=ល្បែង Kolf ដែល​បាន​រក្សាទុក
+Comment[lt]=Kolf išsaugotas žaidimas
+Comment[lv]=Kolf saglabāta spēle
+Comment[mk]=Зачувана игра од Kolf
+Comment[nb]=Kolf lagret spill
+Comment[nds]=Sekert Kolf-Speel
+Comment[ne]=कोल्फले खेल बचत गर्यो
+Comment[nl]=Opgeslagen Kolf-spel
+Comment[nn]=Kolf lagra spel
+Comment[pa]=ਗੋਲਫ਼ ਸੰਭਾਲੀ ਖੇਡ
+Comment[pl]=Zapisana gra w Kolfa
+Comment[pt]=Jogo Gravado do Kolf
+Comment[pt_BR]=Jogo Kolf Salvo
+Comment[ru]=Сохранённая игра в гольф
+Comment[se]=Kolf vurkii spealu
+Comment[sk]=Uložená hra Kolf
+Comment[sl]=Shranjena igra Kolf
+Comment[sr]=Снимљена игра Kolf-а
+Comment[sr@Latn]=Snimljena igra Kolf-a
+Comment[sv]=Sparat Kolfspel
+Comment[ta]=கோல்ஃப் சேமிக்கப்பட்ட விளையாட்டு
+Comment[tg]=Бозии Захиракардаи Голф
+Comment[tr]=Kayıtlı Kolf Oyunu
+Comment[uk]=Збережена гра в гольф
+Comment[zh_CN]=Kolf 保存的游戏
+Comment[zh_TW]=Kolf 儲存的遊戲
+Icon=kolf
+Patterns=*.kolfgame
diff --git a/kolf/x-kourse.desktop b/kolf/x-kourse.desktop
new file mode 100644
index 00000000..19951038
--- /dev/null
+++ b/kolf/x-kourse.desktop
@@ -0,0 +1,53 @@
+# KDE Config File
+[Desktop Entry]
+MimeType=application/x-kourse
+Comment=Kolf Course
+Comment[be]=Пляцоўка для гольфа
+Comment[bg]=Курс на Kolf
+Comment[bn]=কল্‌ফ কোর্স
+Comment[bs]=Kolf teren
+Comment[ca]=Camp de Kolf
+Comment[cs]=Kurs Kolfu
+Comment[cy]=Cwrs Kolf
+Comment[da]=Kolf-bane
+Comment[de]=Kolf-Platz
+Comment[el]=Πίστα kolf
+Comment[eo]=Golftereno
+Comment[es]=Partida de Kolf
+Comment[et]=Kolfi väljak
+Comment[eu]=Kolf-en zelaia
+Comment[fa]=مسابقۀ Kolf
+Comment[fi]=Golf-rata
+Comment[fr]=Parcours de Kolf
+Comment[he]=מסלול Kolf
+Comment[hr]=Kolf teren
+Comment[hu]=Kolf-pálya
+Comment[is]=Kolf Braut
+Comment[it]=Percorso di Kolf
+Comment[ja]=Kolf コース
+Comment[km]=វគ្គ Kolf
+Comment[lt]=Kolf trasa
+Comment[lv]=Kolf kurss
+Comment[mk]=Терен на Kolf
+Comment[nb]=Kolf-bane
+Comment[nds]=Kolf-Platz
+Comment[ne]=कोल्फ स्रोत
+Comment[nl]=Kolf-baan
+Comment[nn]=Kolf-bane
+Comment[pa]=ਗੁਲਫ਼ ਕੋਰਸ
+Comment[pl]=Kurs Kolfa
+Comment[pt]=Percurso do Kolf
+Comment[pt_BR]=Curso do Kolf
+Comment[ru]=Площадка для гольфа
+Comment[se]=Kolf-bána
+Comment[sk]=Kolf kurz
+Comment[sl]=Igrišče za Kolf
+Comment[sr]=Kolf-ов терен
+Comment[sr@Latn]=Kolf-ov teren
+Comment[sv]=Kolfbana
+Comment[ta]=கால்ஃப் கோர்ஸ்
+Comment[uk]=Майданчик для гольфа
+Comment[zh_TW]=Kolf 路線
+Icon=kolf
+Type=MimeType
+Patterns=*.kolf;*.course;*.kourse;