[gnome-klotski] initial vala version
- From: Thomas Hindoe Paaboel Andersen <thomashpa src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-klotski] initial vala version
- Date: Mon, 5 Nov 2012 19:56:00 +0000 (UTC)
commit bb374fb52e35dca6ff7f8b1e16543b149b888f49
Author: John Cheetham <developer johncheetham com>
Date: Sun Nov 4 11:49:05 2012 +0000
initial vala version
Makefile.am | 10 -
configure.ac | 47 +-
data/Makefile.am | 21 +-
data/org.gnome.klotski.gschema.xml.in | 16 +
src/Makefile.am | 55 +-
src/config.vapi | 4 +
src/games-fullscreen-action.c | 228 -----
src/games-fullscreen-action.h | 66 --
src/games-gridframe.c | 278 -------
src/games-gridframe.h | 63 --
src/games-preimage.c | 480 -----------
src/games-preimage.h | 90 --
src/games-score.c | 191 -----
src/games-score.h | 70 --
src/games-scores-backend.c | 352 --------
src/games-scores-backend.h | 67 --
src/games-scores-dialog.c | 610 --------------
src/games-scores-dialog.h | 81 --
src/games-scores.c | 525 ------------
src/games-scores.h | 92 --
src/games-setgid-io.c | 558 -------------
src/games-setgid-io.h | 38 -
src/games-stock.c | 357 --------
src/games-stock.h | 62 --
src/gnome-klotski.c | 1462 ---------------------------------
src/gnome-klotski.vala | 1154 ++++++++++++++++++++++++++
src/history.vala | 102 +++
src/pieces.h | 76 --
src/puzzle-view.vala | 234 ++++++
src/puzzle.vala | 282 +++++++
30 files changed, 1823 insertions(+), 5848 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index f31d101..f039a6f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,13 +1,3 @@
SUBDIRS = data help po src
-SCOREFILES = 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
-
-install-data-local:
- -$(mkinstalldirs) $(DESTDIR)$(scoredir)
- -for i in ${SCOREFILES} ; do \
- touch $(DESTDIR)$(scoredir)/gnotski.$$i.scores; \
- chown $(scores_user):$(scores_group) $(DESTDIR)$(scoredir)/gnotski.$$i.scores; \
- chmod 664 $(DESTDIR)$(scoredir)/gnotski.$$i.scores; \
- done
-
-include $(top_srcdir)/git.mk
diff --git a/configure.ac b/configure.ac
index a3d7ea2..0654f75 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3,57 +3,13 @@ AM_INIT_AUTOMAKE([1.11 no-dist-gzip dist-xz foreign])
AM_SILENT_RULES([yes])
AM_MAINTAINER_MODE
GNOME_MAINTAINER_MODE_DEFINES
-AC_CONFIG_HEADERS([config.h])
+AM_PROG_VALAC([0.16.0])
AM_PROG_CC_C_O
GLIB_GSETTINGS
dnl ###########################################################################
-dnl setgid checks
-dnl ###########################################################################
-
-AC_ARG_ENABLE([setgid],
- [AS_HELP_STRING([--disable-setgid],
- [Disable the use of setgid binaries])],
- [case "${enableval}" in
- yes) setgid=true ;;
- no) setgid=false ;;
- *) AC_MSG_ERROR([bad value ${enableval} for --disable-setgid]) ;;
- esac],
- [if test "$platform_win32" = "yes"; then
- enable_setgid=no
- setgid=false
- else
- enable_setgid=yes
- setgid=true
- fi])
-
-scoredir='${localstatedir}/games'
-scores_group=games
-scores_user=games
-
-if test "$enable_setgid" = "yes"; then
- AC_DEFINE([ENABLE_SETGID],[1],[Define if use of setgid binaries is enabled])
-
- AC_ARG_WITH(scores-group,
- AS_HELP_STRING([--with-scores-group=group],
- [Group for the high score tables and binaries]),
- scores_group="$withval",scores_group="games")
- AC_ARG_WITH(scores-user,
- AS_HELP_STRING([--with-scores-user=user],
- [User for the high score tables]),
- scores_user="$withval",scores_user="games")
-fi
-
-AM_CONDITIONAL([ENABLE_SETGID],[test "$enable_setgid" = "yes"])
-
-AC_SUBST(setgid)
-AC_SUBST(scores_group)
-AC_SUBST(scores_user)
-AC_SUBST(scoredir)
-
-dnl ###########################################################################
dnl Dependencies
dnl ###########################################################################
@@ -71,7 +27,6 @@ dnl ###########################################################################
IT_PROG_INTLTOOL([0.35.0])
AC_SUBST(GETTEXT_PACKAGE, gnome-klotski)
-AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE,"$GETTEXT_PACKAGE", [The gettext package name])
dnl ###########################################################################
dnl Documentation
diff --git a/data/Makefile.am b/data/Makefile.am
index f531f87..470ca79 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -1,9 +1,5 @@
SUBDIRS = icons
-pixmapdir = $(datadir)/gnome-klotski
-pixmap_DATA = \
- gnome-klotski.svg
-
gsettings_in_file = org.gnome.klotski.gschema.xml.in
gsettings_SCHEMAS = $(gsettings_in_file:.xml.in=.xml)
@INTLTOOL_XML_NOMERGE_RULE@
@@ -11,21 +7,22 @@ gsettings_SCHEMAS = $(gsettings_in_file:.xml.in=.xml)
man_MANS = gnome-klotski.6
-desktop_in_files = gnome-klotski.desktop.in.in
+pixmapdir = $(datadir)/gnome-klotski
+pixmap_DATA = \
+ gnome-klotski.svg
+
desktopdir = $(datadir)/applications
+desktop_in_files = gnome-klotski.desktop.in.in
desktop_DATA = $(desktop_in_files:.desktop.in.in=.desktop)
-
-SCOREFILES = 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
+ INTLTOOL_DESKTOP_RULE@
EXTRA_DIST = \
- $(pixmap_DATA) \
$(gsettings_in_file) \
- $(man_MANS) \
- $(desktop_in_files)
+ $(desktop_in_files) \
+ $(man_MANS) \
+ $(pixmap_DATA)
CLEANFILES = $(desktop_DATA) $(gsettings_SCHEMAS)
DISTCLEANFILES = $(desktop_DATA) $(gsettings_SCHEMAS)
- INTLTOOL_DESKTOP_RULE@
-
-include $(top_srcdir)/git.mk
diff --git a/data/org.gnome.klotski.gschema.xml.in b/data/org.gnome.klotski.gschema.xml.in
index 7e44baf..73f8775 100644
--- a/data/org.gnome.klotski.gschema.xml.in
+++ b/data/org.gnome.klotski.gschema.xml.in
@@ -5,5 +5,21 @@
<_summary>The puzzle in play</_summary>
<_description>The number of the puzzle being played.</_description>
</key>
+ <key name="window-width" type="i">
+ <default>600</default>
+ <_summary>Width of the window in pixels</_summary>
+ </key>
+ <key name="window-height" type="i">
+ <default>400</default>
+ <_summary>Height of the window in pixels</_summary>
+ </key>
+ <key name="window-is-maximized" type="b">
+ <default>false</default>
+ <_summary>true if the window is maximized</_summary>
+ </key>
+ <key name="window-is-fullscreen" type="b">
+ <default>false</default>
+ <_summary>true if the window is fullscren</_summary>
+ </key>
</schema>
</schemalist>
diff --git a/src/Makefile.am b/src/Makefile.am
index 8a9587c..806b609 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,48 +1,35 @@
bin_PROGRAMS = gnome-klotski
gnome_klotski_SOURCES = \
- gnome-klotski.c \
- games-fullscreen-action.c \
- games-fullscreen-action.h \
- games-gridframe.c \
- games-gridframe.h \
- games-preimage.c \
- games-preimage.h \
- games-score.h \
- games-score.c \
- games-scores.c \
- games-scores.h \
- games-scores-dialog.c \
- games-scores-dialog.h \
- games-scores-backend.c \
- games-scores-backend.h \
- games-stock.c \
- games-stock.h \
- pieces.h
-
-if ENABLE_SETGID
-gnome_klotski_SOURCES += \
- games-setgid-io.c \
- games-setgid-io.h
-endif # ENABLE_SETGID
-
-gnome_klotski_CPPFLAGS = \
- -I$(top_srcdir) \
- $(AM_CPPFLAGS)
+ config.vapi \
+ gnome-klotski.vala \
+ history.vala \
+ puzzle.vala \
+ puzzle-view.vala
gnome_klotski_CFLAGS = \
+ -DVERSION=\"$(VERSION)\" \
+ -DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \
-DDATA_DIRECTORY=\"$(datadir)/gnome-klotski\" \
-DLOCALEDIR=\"$(datadir)/locale\" \
- -DICON_THEME_DIRECTORY="\"$(datadir)/icons\"" \
- -DSCORESDIR="\"$(scoredir)\"" \
$(GNOME_KLOTSKI_CFLAGS)
+gnome_klotski_VALAFLAGS = \
+ --pkg posix \
+ --pkg gtk+-3.0 \
+ --pkg gmodule-2.0 \
+ --pkg librsvg-2.0 \
+ --pkg pango \
+ --pkg pangocairo
+
gnome_klotski_LDADD = \
$(GNOME_KLOTSKI_LIBS)
-install-exec-hook:
- -if test "$(setgid)" = "true"; then \
- chgrp $(scores_group) $(DESTDIR)$(bindir)/gnome-klotski && chmod 2555 $(DESTDIR)$(bindir)/gnome-klotski ;\
- fi
+CLEANFILES = \
+ $(patsubst %.vala,%.c,$(filter %.vala, $(SOURCES))) \
+ *_vala.stamp
+
+DISTCLEANFILES = \
+ Makefile.in
-include $(top_srcdir)/git.mk
diff --git a/src/config.vapi b/src/config.vapi
new file mode 100644
index 0000000..8efa3e5
--- /dev/null
+++ b/src/config.vapi
@@ -0,0 +1,4 @@
+public const string VERSION;
+public const string GETTEXT_PACKAGE;
+public const string DATA_DIRECTORY;
+public const string LOCALEDIR;
diff --git a/src/gnome-klotski.vala b/src/gnome-klotski.vala
new file mode 100644
index 0000000..8267b59
--- /dev/null
+++ b/src/gnome-klotski.vala
@@ -0,0 +1,1154 @@
+/* Puzzle Info */
+private struct LevelInfo
+{
+ string name;
+ int group;
+ int width;
+ int height;
+ string data;
+}
+
+public class Klotski : Gtk.Application
+{
+ private Settings settings;
+ private const int MINWIDTH = 250;
+ private const int MINHEIGHT = 250;
+ private const int SPACE_PADDING = 5;
+
+ private const string KEY_LEVEL = "level";
+
+ /* Main window */
+ private Gtk.Window window;
+ private int window_width;
+ private int window_height;
+ private bool is_fullscreen;
+ private bool is_maximized;
+
+ private PuzzleView view;
+
+ private Gtk.ToolButton fullscreen_button;
+
+ private Gtk.Label messagewidget;
+ private Gtk.Label moves_label;
+
+ private Gtk.ActionGroup gaction_group;
+
+ private Puzzle puzzle;
+
+ private int current_level = -1;
+
+ private History history;
+
+ /* The "puzzle name" remarks provide context for translation. */
+ private Gtk.SizeGroup groups[3];
+ private Gtk.Image[] level_image;
+ private Gtk.ToggleAction[] level_action;
+ public const LevelInfo level[] =
+ {
+ /* puzzle name */
+ {N_("Only 18 Steps"), 0,
+ 6, 9,
+ "######" +
+ "#a**b#" +
+ "#m**n#" +
+ "#cdef#" +
+ "#ghij#" +
+ "#k l#" +
+ "##--##" +
+ " .." +
+ " .."},
+
+ /* puzzle name */
+ {N_("Daisy"), 0,
+ 6, 9,
+ "######" +
+ "#a**b#" +
+ "#a**b#" +
+ "#cdef#" +
+ "#zghi#" +
+ "#j k#" +
+ "##--##" +
+ " .." +
+ " .."},
+
+ /* puzzle name */
+ {N_("Violet"), 0,
+ 6, 9,
+ "######" +
+ "#a**b#" +
+ "#a**b#" +
+ "#cdef#" +
+ "#cghi#" +
+ "#j k#" +
+ "##--##" +
+ " .." +
+ " .."},
+
+ /* puzzle name */
+ {N_("Poppy"), 0,
+ 6, 9,
+ "######" +
+ "#a**b#" +
+ "#a**b#" +
+ "#cdde#" +
+ "#fghi#" +
+ "#j k#" +
+ "##--##" +
+ " .." +
+ " .."},
+
+ /* puzzle name */
+ {N_("Pansy"), 0,
+ 6, 9,
+ "######" +
+ "#a**b#" +
+ "#a**b#" +
+ "#cdef#" +
+ "#cghf#" +
+ "#i j#" +
+ "##--##" +
+ " .." +
+ " .."},
+
+ /* puzzle name */
+ {N_("Snowdrop"), 0,
+ 6, 9,
+ "######" +
+ "#a**b#" +
+ "#a**b#" +
+ "#cdde#" +
+ "#cfgh#" +
+ "#i j#" +
+ "##--##" +
+ " .." +
+ " .."},
+
+ /* puzzle name - sometimes called "Le'Ane Rouge" */
+ {N_("Red Donkey"), 0,
+ 6, 9,
+ "######" +
+ "#a**b#" +
+ "#a**b#" +
+ "#cdde#" +
+ "#cfge#" +
+ "#h i#" +
+ "##--##" +
+ " .." +
+ " .."},
+
+ /* puzzle name */
+ {N_("Trail"), 0,
+ 6, 9,
+ "######" +
+ "#a**c#" +
+ "#a**c#" +
+ "#eddg#" +
+ "#hffj#" +
+ "# ii #" +
+ "##--##" +
+ " .." +
+ " .."},
+
+ /* puzzle name */
+ {N_("Ambush"), 0,
+ 6, 9,
+ "######" +
+ "#a**c#" +
+ "#d**e#" +
+ "#dffe#" +
+ "#ghhi#" +
+ "# jj #" +
+ "##--##" +
+ " .." +
+ " .."},
+
+ /* puzzle name */
+ {N_("Agatka"), 1,
+ 7, 7,
+ ".. " +
+ ". " +
+ "#####--" +
+ "#**aab-" +
+ "#*ccde#" +
+ "#fgh #" +
+ "#######"},
+
+ /* puzzle name */
+ {N_("Success"), 1,
+ 9, 6,
+ "####### " +
+ "#**bbc# " +
+ "#defgh# " +
+ "#ijkgh- " +
+ "#llk # " +
+ "#######.."},
+
+ /* puzzle name */
+ {N_("Bone"), 1,
+ 6, 9,
+ "######" +
+ "#abc*#" +
+ "# dd*#" +
+ "# ee*#" +
+ "# fgh#" +
+ "##-###" +
+ " ." +
+ " ." +
+ " ."},
+
+ /* puzzle name */
+ {N_("Fortune"), 1,
+ 7, 10,
+ " .." +
+ " . " +
+ "####-. " +
+ "#ab - " +
+ "#ccd # " +
+ "#ccd # " +
+ "#**ee# " +
+ "#*fgh# " +
+ "#*iih# " +
+ "###### "},
+
+ /* puzzle name */
+ {N_("Fool"), 1,
+ 10, 6,
+ " ########" +
+ " -aabc #" +
+ " #aabdef#" +
+ " #ijggef#" +
+ " #klhh**#" +
+ "..########"},
+
+ /* puzzle name */
+ {N_("Solomon"), 1,
+ 7, 9,
+ " . " +
+ ".. " +
+ "#--####" +
+ "# aab#" +
+ "# cdfb#" +
+ "#hcefg#" +
+ "#hijk*#" +
+ "#hll**#" +
+ "#######"},
+
+ /* puzzle name */
+ {N_("Cleopatra"), 1,
+ 6, 8,
+ "######" +
+ "#abcd#" +
+ "#**ee#" +
+ "#f*g #" +
+ "#fh i-" +
+ "####--" +
+ " .." +
+ " ."},
+
+ /* puzzle name */
+ {N_("Shark"), 1,
+ 11, 8,
+ "######## " +
+ "#nrr s # " +
+ "#n*op q# " +
+ "#***jml# " +
+ "#hhijkl# " +
+ "#ffcddg- " +
+ "#abcdde- . " +
+ "########..."},
+
+ /* puzzle name */
+ {N_("Rome"), 1,
+ 8, 8,
+ "########" +
+ "#abcc**#" +
+ "#ddeef*#" +
+ "#ddghfi#" +
+ "# jki#" +
+ "#--#####" +
+ " .. " +
+ " . "},
+
+ /* puzzle name */
+ {N_("Pennant Puzzle"), 1,
+ 6, 9,
+ "######" +
+ "#**aa#" +
+ "#**bb#" +
+ "#de #" +
+ "#fghh#" +
+ "#fgii#" +
+ "#--###" +
+ " .." +
+ " .."},
+
+ /* puzzle name */
+ {N_("Ithaca"), 2,
+ 19, 19,
+ ".aaaaaaaaaaaaaaaaab" +
+ ".. cddeffffffffffb" +
+ " .. cddeffffffffffb" +
+ " . cddeffffffffffb" +
+ "ggg-############hhb" +
+ "ggg- ABCDEFFGH#hhb" +
+ "ggg- FFIJ#hhb" +
+ "ggg# KLMJ#hhb" +
+ "ggg#NNNNOOOPQMJ#hhb" +
+ "ggg#NNNNOOOP*RS#hhb" +
+ "ggg#TTTTTUVW**X#hhb" +
+ "ggg#YZ12222W3**#hhb" +
+ "ggg#YZ12222W34*#iib" +
+ "jjj#YZ155555367#klb" +
+ "jjj#############mmb" +
+ "jjjnooooooooooppppb" +
+ "jjjqooooooooooppppb" +
+ " rrrssssppppb" +
+ "ttttttuvvvvvvvwwwwx"},
+
+ /* puzzle name */
+ {N_("Pelopones"), 2,
+ 9, 8,
+ "#########" +
+ "#abbb***#" +
+ "#abbb*c*#" +
+ "#adeefgg#" +
+ "# eefhh#" +
+ "#... ihh#" +
+ "#. . ihh#" +
+ "#########"},
+
+ /* puzzle name */
+ {N_("Transeuropa"), 2,
+ 15, 8,
+ " ###########" +
+ " -AAAAABBCC#" +
+ " - DEFGHI#" +
+ " # DEFGJI#" +
+ " # KEFGLI#" +
+ " # KEFG*I#" +
+ " . # MM****#" +
+ "....###########"},
+
+ /* puzzle name */
+ {N_("Lodzianka"), 2,
+ 9, 7,
+ "#########" +
+ "#**abbcc#" +
+ "#**abbdd#" +
+ "#eefgh #" +
+ "#iiijk..#" +
+ "#iiijk..#" +
+ "#########"},
+
+ /* puzzle name */
+ {N_("Polonaise"), 2,
+ 7, 7,
+ "#######" +
+ "#aab**#" +
+ "#aabc*#" +
+ "#defgg#" +
+ "#..fhh#" +
+ "# .ihh#" +
+ "#######"},
+
+ /* puzzle name */
+ {N_("Baltic Sea"), 2,
+ 6, 8,
+ "######" +
+ "#.abc#" +
+ "#.dec#" +
+ "#fggc#" +
+ "#fhhi#" +
+ "#fjk*#" +
+ "#flk*#" +
+ "######"},
+
+ /* puzzle name */
+ {N_("American Pie"), 2,
+ 10, 12,
+ "##########" +
+ "#a*bcdefg#" +
+ "#**bhhhhg#" +
+ "#*iijjkkg#" +
+ "#liimnoop#" +
+ "#qiirrr #" +
+ "#qstuvv #" +
+ "#qwwxvv #" +
+ "######--##" +
+ " ." +
+ " .." +
+ " . "},
+
+ /* puzzle name */
+ {N_("Traffic Jam"), 2,
+ 10, 7,
+ "######## " +
+ "#** ffi# " +
+ "#** fgh# " +
+ "#aacehh# " +
+ "#bbdjlm- " +
+ "#bddklm-.." +
+ "########.."},
+
+ /* puzzle name */
+ {N_("Sunshine"), 2,
+ 17, 22,
+ " ... " +
+ " .. .. " +
+ " . . " +
+ " .. .. " +
+ " ... " +
+ "######-----######" +
+ "#hh0iilltmmpp;qq#" +
+ "#hh,iill mmpp:qq#" +
+ "#2y{45v s w89x/z#" +
+ "#jj6kkaa nnoo<rr#" +
+ "#jj7kkaaunnoo>rr#" +
+ "#33333TTJWW11111#" +
+ "#33333TTJWW11111#" +
+ "#33333GG HH11111#" +
+ "#33333YYIgg11111#" +
+ "#33333YYIgg11111#" +
+ "#ddFeeA***BffOZZ#" +
+ "#ddFee** **ffOZZ#" +
+ "#MMKQQ* *PPS^^#" +
+ "#VVLXX** **bbRcc#" +
+ "#VVLXXD***EbbRcc#" +
+ "#################"}
+ };
+
+ const string[] pack_uipath =
+ {
+ "/ui/MainMenu/GameMenu/HuaRongTrail",
+ "/ui/MainMenu/GameMenu/ChallengePack",
+ "/ui/MainMenu/GameMenu/SkillPack"
+ };
+
+ private const GLib.ActionEntry[] action_entries =
+ {
+ { "new-game", restart_level_cb },
+ { "fullscreen", fullscreen_cb },
+ { "scores", scores_cb },
+ { "help", help_cb },
+ { "about", about_cb },
+ { "quit", quit_cb }
+ };
+
+ const Gtk.ActionEntry[] entries =
+ {
+ {"GameMenu", null, N_("_Game")},
+ /* set of puzzles */
+ {"HuaRongTrail", null, N_("HuaRong Trail")},
+ /* set of puzzles */
+ {"ChallengePack", null, N_("Challenge Pack")},
+ /* set of puzzles */
+ {"SkillPack", null, N_("Skill Pack")},
+ {"RestartPuzzle", Gtk.Stock.REFRESH, N_("_Restart Puzzle"), "<control>R", null, restart_level_cb},
+ {"NextPuzzle", Gtk.Stock.GO_FORWARD, N_("Next Puzzle"), "Page_Down", null, next_level_cb},
+ {"PrevPuzzle", Gtk.Stock.GO_BACK, N_("Previous Puzzle"), "Page_Up", null, prev_level_cb}
+ };
+
+ const string ui_description =
+ "<ui>" +
+ " <menubar name='MainMenu'>" +
+ " <menu action='GameMenu'>" +
+ " <menuitem action='RestartPuzzle'/>" +
+ " <menuitem action='NextPuzzle'/>" +
+ " <menuitem action='PrevPuzzle'/>" +
+ " <separator/>" +
+ " <menu action='HuaRongTrail'/>" +
+ " <menu action='ChallengePack'/>" +
+ " <menu action='SkillPack'/>" +
+ " </menu>" +
+ " </menubar>" +
+ "</ui>";
+
+ public Klotski ()
+ {
+ Object (application_id: "org.gnome.klotski", flags: ApplicationFlags.FLAGS_NONE);
+ }
+
+ protected override void startup ()
+ {
+ base.startup ();
+
+ Environment.set_application_name (_("Klotski"));
+
+ settings = new Settings ("org.gnome.klotski");
+
+ Gtk.Window.set_default_icon_name ("gnome-klotski");
+
+ add_action_entries (action_entries, this);
+ add_accelerator ("F11", "app.fullscreen", null);
+
+ level_action = new Gtk.ToggleAction[level.length];
+ level_image = new Gtk.Image[level.length];
+
+ string histfile = Path.build_filename (Environment.get_user_data_dir (), "gnome-klotski", "history");
+
+ history = new History (histfile);
+ history.load ();
+
+ window = new Gtk.ApplicationWindow (this);
+ window.set_title (_("Klotski"));
+ window.configure_event.connect (window_configure_event_cb);
+ window.window_state_event.connect (window_state_event_cb);
+ int ww = settings.get_int ("window-width");
+ int wh = settings.get_int ("window-height");
+ if (ww < MINWIDTH)
+ ww = MINWIDTH;
+ if (wh < MINHEIGHT)
+ wh = MINHEIGHT;
+
+ window.set_default_size (ww, wh);
+ if (settings.get_boolean ("window-is-fullscreen"))
+ window.fullscreen ();
+ else if (settings.get_boolean ("window-is-maximized"))
+ window.maximize ();
+
+ var vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
+ window.add (vbox);
+ vbox.show ();
+
+ /* Create the menu */
+
+ var builder_str =
+ "<interface>" +
+ /* string for menu */
+ """
+ <menu id='app-menu'>
+ <section>
+ <item>
+ <attribute name='label' translatable='yes'>_New Game</attribute>
+ <attribute name='action'>app.new-game</attribute>
+ <attribute name='accel'><Primary>n</attribute>
+ </item>
+ <item>
+ <attribute name='label' translatable='yes'>_Scores</attribute>
+ <attribute name='action'>app.scores</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name='label' translatable='yes'>_Help</attribute>
+ <attribute name='action'>app.help</attribute>
+ <attribute name='accel'>F1</attribute>
+ </item>
+ <item>
+ <attribute name='label' translatable='yes'>_About</attribute>
+ <attribute name='action'>app.about</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name='label' translatable='yes'>_Quit</attribute>
+ <attribute name='action'>app.quit</attribute>
+ <attribute name='accel'><Primary>q</attribute>
+ </item>
+ </section>
+ </menu>
+ </interface>
+ """;
+
+ Gtk.Builder builder = new Gtk.Builder ();
+
+ try
+ {
+ builder.add_from_string (builder_str, -1);
+ }
+ catch (GLib.Error e)
+ {
+ stderr.printf ("%s\n", "Error in gnome-klotski.vala function startup() - builder.add_from_string failed");
+ GLib.error(e.message);
+ }
+
+ set_app_menu (builder.get_object ("app-menu") as MenuModel);
+
+ gaction_group = new Gtk.ActionGroup ("MenuActions");
+ gaction_group.set_translation_domain (GETTEXT_PACKAGE);
+ gaction_group.add_actions (entries, this);
+
+ var ui_manager = new Gtk.UIManager ();
+ ui_manager.insert_action_group (gaction_group, 0);
+ try
+ {
+ ui_manager.add_ui_from_string (ui_description, -1);
+ }
+ catch (Error e)
+ {
+ }
+ add_puzzle_menu (ui_manager);
+
+
+ window.add_accel_group (ui_manager.get_accel_group ());
+
+ var menubar = ui_manager.get_widget ("/MainMenu");
+ vbox.pack_start (menubar, false, false, 0);
+
+ var status_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 10);
+ status_box.show ();
+
+ /* show the puzzle name and number of moves */
+ messagewidget = new Gtk.Label ("");
+ messagewidget.show ();
+ status_box.pack_start (messagewidget, false, false, 0);
+
+ moves_label = new Gtk.Label ("");
+ moves_label.show ();
+
+ status_box.pack_start (moves_label, false, false, 0);
+
+ /* game clock */
+ //clock_label = new Gtk.Label ("");
+ //clock_label.show ();
+ //status_box.pack_start (clock_label, false, false, 0);
+
+ var toolbar = new Gtk.Toolbar ();
+ toolbar.show ();
+ toolbar.show_arrow = false;
+ toolbar.get_style_context ().add_class (Gtk.STYLE_CLASS_PRIMARY_TOOLBAR);
+
+ var new_game_button = new Gtk.ToolButton (null, N_("_New"));
+ new_game_button.icon_name = "document-new";
+ new_game_button.use_underline = true;
+ new_game_button.action_name = "app.new-game";
+ new_game_button.is_important = true;
+ new_game_button.show ();
+ toolbar.insert (new_game_button, -1);
+
+ fullscreen_button = new Gtk.ToolButton (null, _("_Fullscreen"));
+ fullscreen_button.icon_name = "view-fullscreen";
+ fullscreen_button.use_underline = true;
+ fullscreen_button.action_name = "app.fullscreen";
+ fullscreen_button.show ();
+ toolbar.insert (fullscreen_button, -1);
+
+ var status_alignment = new Gtk.Alignment (1.0f, 0.5f, 0.0f, 0.0f);
+ status_alignment.add (status_box);
+ status_alignment.show ();
+
+ var status_item = new Gtk.ToolItem ();
+ status_item.set_expand (true);
+ status_item.add (status_alignment);
+ status_item.show ();
+
+ toolbar.insert (status_item, -1);
+
+ vbox.pack_start (toolbar, false, false, 0);
+
+ view = new PuzzleView ();
+ view.show ();
+ vbox.pack_start (view, true, true, 0);
+
+ vbox.pack_start (new Gtk.HSeparator (), false, false, 0);
+
+ load_solved_state ();
+
+ var startup_level = settings.get_int (KEY_LEVEL);
+ new_game (startup_level);
+ }
+
+ private bool window_configure_event_cb (Gdk.EventConfigure event)
+ {
+ if (!is_maximized && !is_fullscreen)
+ {
+ window_width = event.width;
+ window_height = event.height;
+ }
+
+ return false;
+ }
+
+ private bool window_state_event_cb (Gdk.EventWindowState event)
+ {
+ if ((event.changed_mask & Gdk.WindowState.MAXIMIZED) != 0)
+ is_maximized = (event.new_window_state & Gdk.WindowState.MAXIMIZED) != 0;
+ if ((event.changed_mask & Gdk.WindowState.FULLSCREEN) != 0)
+ {
+ is_fullscreen = (event.new_window_state & Gdk.WindowState.FULLSCREEN) != 0;
+ if (is_fullscreen)
+ {
+ fullscreen_button.label = _("_Leave Fullscreen");
+ fullscreen_button.icon_name = "view-restore";
+ }
+ else
+ {
+ fullscreen_button.label = _("_Fullscreen");
+ fullscreen_button.icon_name = "view-fullscreen";
+ }
+ }
+ return false;
+ }
+
+ private void scores_cb ()
+ {
+ show_scores (null, false);
+ }
+
+ private void game_score ()
+ {
+ /* Level is complete */
+ var key = get_level_key (current_level);
+ var keyfile = new KeyFile ();
+ var filename = Path.build_filename (Environment.get_user_data_dir (), "gnome-klotski", "levels"); // filename:~/.local/share/gnome-klotski/levels
+
+ try
+ {
+ keyfile.load_from_file (filename, KeyFileFlags.NONE);
+ }
+ catch (Error e)
+ {
+ }
+
+ keyfile.set_boolean (key, "solved", true);
+
+ try
+ {
+ FileUtils.set_contents (filename, keyfile.to_data ());
+ }
+ catch (Error e)
+ {
+ }
+
+ level_image[current_level].set_from_stock (Gtk.Stock.YES, Gtk.IconSize.MENU);
+
+ var date = new DateTime.now_local ();
+ var entry = new HistoryEntry (date, current_level, puzzle.moves);
+ history.add (entry);
+ history.save ();
+
+ if (show_scores (entry, true) == Gtk.ResponseType.CLOSE)
+ window.destroy ();
+ else
+ new_game (current_level);
+
+ }
+
+ private int show_scores (HistoryEntry? selected_entry = null, bool show_quit = false)
+ {
+ var dialog = new ScoreDialog (history, selected_entry, show_quit, gaction_group);
+ dialog.modal = true;
+ dialog.transient_for = window;
+
+ var result = dialog.run ();
+ dialog.destroy ();
+
+ return result;
+ }
+
+
+ /* Add puzzles to the game menu. */
+ private void add_puzzle_menu (Gtk.UIManager ui_manager)
+ {
+ unowned SList group = null;
+ Gtk.RadioAction? top_action = null;
+ for (var i = level.length - 1; i >= 0; i--)
+ {
+ var label = gaction_group.translate_string (level[i].name);
+
+ var action = new Gtk.RadioAction (level[i].name, "", null, null, i);
+ top_action = action;
+
+ action.set_group (group);
+ group = action.get_group ();
+
+ gaction_group.add_action (action);
+
+ ui_manager.add_ui (ui_manager.new_merge_id (),
+ pack_uipath[level[i].group],
+ level[i].name, level[i].name,
+ Gtk.UIManagerItemType.MENUITEM, true);
+
+ /* Unfortunately Gtk.UIManager only supports labels for items, so remove the label it creates and
+ * replace it with our own widget */
+ Gtk.Bin item = (Gtk.Bin) ui_manager.get_widget (pack_uipath[level[i].group] + "/" + level[i].name);
+
+ /* Create a label and image for the menu item */
+ var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
+ var labelw = new Gtk.Label (label);
+ labelw.set_alignment (0.0f, 0.5f);
+ var image = new Gtk.Image ();
+ box.pack_start (labelw, true, true, 0);
+ box.pack_start (image, false, true, 0);
+
+ /* Keep all elements the same size */
+ if (groups[level[i].group] == null)
+ groups[level[i].group] = new Gtk.SizeGroup (Gtk.SizeGroupMode.BOTH);
+ groups[level[i].group].add_widget (box);
+
+ /* Replace the label with the new one */
+ item.remove (item.get_child ());
+ item.add (box);
+ box.show_all ();
+
+ level_image[i] = image;
+ level_action[i] = action;
+ }
+
+ top_action.changed.connect (level_cb);
+ }
+
+ private string get_level_key (int level_number)
+ {
+ /* Calculate the CRC of the level data */
+ uint32 result = 0xFFFFFFFFu;
+ var data = level[level_number].data;
+ for (var i = 0; data[i] != '\0'; i++)
+ {
+ var octet = data[i];
+ for (var j = 0; j < 8; j++)
+ {
+ if (((octet >> 7) ^ (result >> 31)) != 0)
+ result = (result << 1) ^ 0x04c11db7;
+ else
+ result = (result << 1);
+ result &= 0xFFFFFFFF;
+ octet <<= 1;
+ }
+ }
+
+ return "%08X".printf (~result);
+ }
+
+ private void load_solved_state ()
+ {
+ var keyfile = new KeyFile ();
+ var filename = Path.build_filename (Environment.get_user_data_dir (), "gnome-klotski", "levels");
+ try
+ {
+ keyfile.load_from_file (filename, KeyFileFlags.NONE);
+ }
+ catch (Error e)
+ {
+ }
+
+ for (var i = 0; i < level.length; i++)
+ {
+ var key = get_level_key (i);
+ var value = false;
+ try
+ {
+ value = keyfile.get_boolean (key, "solved");
+ }
+ catch (Error e)
+ {
+ }
+ if (value)
+ level_image[i].set_from_stock (Gtk.Stock.YES, Gtk.IconSize.MENU);
+ }
+ }
+
+ private void update_menu_state ()
+ {
+ /* Puzzle Radio Action */
+ level_action[current_level].active = true;
+
+ /* Next Puzzle Sensitivity */
+ var action = gaction_group.get_action ("NextPuzzle");
+ action.sensitive = current_level < level.length - 1;
+
+ /* Previous Puzzle Sensitivity */
+ action = gaction_group.get_action ("PrevPuzzle");
+ action.sensitive = current_level > 0;
+
+ update_moves_label ();
+ }
+
+ private void new_game (int requested_level)
+ {
+ current_level = requested_level.clamp (0, level.length - 1);
+
+ settings.set_int (KEY_LEVEL, current_level);
+
+ messagewidget.set_text (N_("Puzzle: ") + _(level[current_level].name));
+ puzzle = new Puzzle (level[current_level].width, level[current_level].height, level[current_level].data);
+ puzzle.moved.connect (puzzle_moved_cb);
+ view.puzzle = puzzle;
+ update_menu_state ();
+ }
+
+ private void puzzle_moved_cb ()
+ {
+ update_moves_label ();
+ }
+
+ private void update_moves_label ()
+ {
+ moves_label.set_text (_("Moves: %d").printf (puzzle.moves));
+ if (puzzle.game_over ())
+ {
+ messagewidget.set_text (_("Level completed."));
+ game_score ();
+ }
+ }
+
+ private void quit_cb ()
+ {
+ window.destroy ();
+ }
+
+ private void level_cb (Gtk.Action action, Gtk.RadioAction current)
+ {
+ int requested_level = current.get_current_value ();
+ if (requested_level != current_level)
+ new_game (requested_level);
+ }
+
+ private void restart_level_cb ()
+ {
+ new_game (current_level);
+ }
+
+ private void next_level_cb ()
+ {
+ new_game (current_level + 1);
+ }
+
+ private void prev_level_cb ()
+ {
+ new_game (current_level - 1);
+ }
+
+ private void help_cb ()
+ {
+ try
+ {
+ Gtk.show_uri (window.get_screen (), "help:gnotski", Gtk.get_current_event_time ());
+ }
+ catch (Error e)
+ {
+ warning ("Failed to show help: %s", e.message);
+ }
+ }
+
+ protected override void shutdown ()
+ {
+ base.shutdown ();
+
+ /* Save window state */
+ settings.set_int ("window-width", window_width);
+ settings.set_int ("window-height", window_height);
+ settings.set_boolean ("window-is-maximized", is_maximized);
+ settings.set_boolean ("window-is-fullscreen", is_fullscreen);
+ }
+
+ protected override void activate ()
+ {
+ window.present ();
+ }
+
+ private void fullscreen_cb ()
+ {
+ if (is_fullscreen)
+ {
+ window.unfullscreen ();
+ }
+ else
+ {
+ window.fullscreen ();
+ }
+ }
+
+ private void about_cb ()
+ {
+ const string authors[] = { "Lars Rydlinge (original author)", "Robert Ancell (port to vala)", "John Cheetham (port to vala)", null };
+ const string documenters[] = { "Andrew Sobala", null };
+ var license = "Klotski 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.\n\nKlotski 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.\n\nYou should have received a copy of the GNU General Public License along with Klotski; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA";
+
+ Gtk.show_about_dialog (window,
+ "program-name", _("Klotski"),
+ "version", VERSION,
+ "comments", _("Sliding Block Puzzles\n\nKlotski is a part of GNOME Games."),
+ "copyright",
+ "Copyright \xc2\xa9 1999-2008 Lars Rydlinge",
+ "license", license,
+ "wrap-license", true,
+ "authors", authors,
+ "documenters", documenters,
+ "translator-credits", _("translator-credits"),
+ "logo-icon-name", "gnome-klotski",
+ "website", "http://www.gnome.org/projects/gnome-games",
+ "website-label", _("GNOME Games web site"),
+ null);
+
+ }
+
+ public static int main (string[] args)
+ {
+ Intl.setlocale (LocaleCategory.ALL, "");
+ Intl.bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+ Intl.bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ Intl.textdomain (GETTEXT_PACKAGE);
+
+ var context = new OptionContext ("");
+ context.set_translation_domain (GETTEXT_PACKAGE);
+ context.add_group (Gtk.get_option_group (true));
+
+ try
+ {
+ context.parse (ref args);
+ }
+ catch (Error e)
+ {
+ stderr.printf ("%s\n", e.message);
+ Posix.exit (Posix.EXIT_FAILURE);
+ }
+
+ var app = new Klotski ();
+ return app.run (args);
+ }
+}
+
+public class ScoreDialog : Gtk.Dialog
+{
+ private History history;
+ private HistoryEntry? selected_entry = null;
+ private Gtk.ListStore level_model;
+ private Gtk.ListStore score_model;
+ private Gtk.ComboBox level_combo;
+ private Gtk.ActionGroup action_group;
+
+ public ScoreDialog (History history, HistoryEntry? selected_entry = null, bool show_quit = false, Gtk.ActionGroup action_group)
+ {
+ this.history = history;
+ history.entry_added.connect (entry_added_cb);
+ this.selected_entry = selected_entry;
+ this.action_group = action_group;
+
+ if (show_quit)
+ {
+ add_button (Gtk.Stock.QUIT, Gtk.ResponseType.CLOSE);
+ add_button (_("New Game"), Gtk.ResponseType.OK);
+ }
+ else
+ add_button (Gtk.Stock.OK, Gtk.ResponseType.DELETE_EVENT);
+ set_size_request (200, 300);
+
+ var vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 5);
+ vbox.border_width = 6;
+ vbox.show ();
+ get_content_area ().pack_start (vbox, true, true, 0);
+
+ var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6);
+ hbox.show ();
+ vbox.pack_start (hbox, false, false, 0);
+
+ var label = new Gtk.Label (_("Puzzle:"));
+ label.show ();
+ hbox.pack_start (label, false, false, 0);
+
+ level_model = new Gtk.ListStore (2, typeof (string), typeof (int)); // puzzle name, level
+
+ level_combo = new Gtk.ComboBox ();
+ level_combo.changed.connect (level_changed_cb);
+ level_combo.model = level_model;
+ var renderer = new Gtk.CellRendererText ();
+ level_combo.pack_start (renderer, true);
+ level_combo.add_attribute (renderer, "text", 0);
+ level_combo.show ();
+ hbox.pack_start (level_combo, true, true, 0);
+
+ var scroll = new Gtk.ScrolledWindow (null, null);
+ scroll.shadow_type = Gtk.ShadowType.ETCHED_IN;
+ scroll.set_policy (Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
+ scroll.show ();
+ vbox.pack_start (scroll, true, true, 0);
+
+ score_model = new Gtk.ListStore (3, typeof (string), typeof (string), typeof (int));
+
+ var scores = new Gtk.TreeView ();
+ renderer = new Gtk.CellRendererText ();
+ scores.insert_column_with_attributes (-1, _("Date"), renderer, "text", 0, "weight", 2);
+ renderer = new Gtk.CellRendererText ();
+ renderer.xalign = 1.0f;
+ scores.insert_column_with_attributes (-1, _("Moves"), renderer, "text", 1, "weight", 2);
+ scores.model = score_model;
+ scores.show ();
+ scroll.add (scores);
+
+ foreach (var entry in history.entries)
+ entry_added_cb (entry);
+ }
+
+ public void set_level (uint level)
+ {
+ score_model.clear ();
+
+ var entries = history.entries.copy ();
+ entries.sort (compare_entries);
+
+ foreach (var entry in entries)
+ {
+ if (entry.level != level)
+ continue;
+
+ var date_label = entry.date.format ("%d/%m/%Y");
+
+ var moves_label = "%u".printf (entry.moves);
+
+ int weight = Pango.Weight.NORMAL;
+ if (entry == selected_entry)
+ weight = Pango.Weight.BOLD;
+
+ Gtk.TreeIter iter;
+ score_model.append (out iter);
+ score_model.set (iter, 0, date_label, 1, moves_label, 2, weight);
+ }
+ }
+
+ private static int compare_entries (HistoryEntry a, HistoryEntry b)
+ {
+ if (a.level != b.level)
+ return (int) a.level - (int) b.level;
+ if (a.moves != b.moves)
+ return (int) a.moves - (int) b.moves;
+ return a.date.compare (b.date);
+ }
+
+ private void level_changed_cb (Gtk.ComboBox combo)
+ {
+ Gtk.TreeIter iter;
+ if (!combo.get_active_iter (out iter))
+ return;
+
+ int level;
+ combo.model.get (iter, 1, out level);
+ set_level ((uint) level);
+ }
+
+ private void entry_added_cb (HistoryEntry entry)
+ {
+ /* Ignore if already have an entry for this */
+ Gtk.TreeIter iter;
+ var have_level_entry = false;
+ if (level_model.get_iter_first (out iter))
+ {
+ do
+ {
+ uint level;
+ level_model.get (iter, 1, out level);
+ if (level == entry.level)
+ {
+ have_level_entry = true;
+ break;
+ }
+ } while (level_model.iter_next (ref iter));
+ }
+
+ if (!have_level_entry)
+ {
+ var label = action_group.translate_string (Klotski.level[entry.level].name);
+ level_model.append (out iter);
+ level_model.set (iter, 0, label, 1, entry.level, -1);
+
+ /* Select this entry if don't have any */
+ if (level_combo.get_active () == -1)
+ level_combo.set_active_iter (iter);
+
+ /* Select this entry if the same category as the selected one */
+ if (selected_entry != null && entry.level == selected_entry.level)
+ level_combo.set_active_iter (iter);
+
+ }
+ }
+}
diff --git a/src/history.vala b/src/history.vala
new file mode 100644
index 0000000..84ce652
--- /dev/null
+++ b/src/history.vala
@@ -0,0 +1,102 @@
+public class History
+{
+ public string filename;
+ public List<HistoryEntry> entries;
+
+ public signal void entry_added (HistoryEntry entry);
+
+ public History (string filename)
+ {
+ this.filename = filename;
+ entries = new List<HistoryEntry> ();
+ }
+
+ public void add (HistoryEntry entry)
+ {
+ entries.append (entry);
+ entry_added (entry);
+ }
+
+ public void load ()
+ {
+ entries = new List<HistoryEntry> ();
+
+ var contents = "";
+ try
+ {
+ FileUtils.get_contents (filename, out contents);
+ }
+ catch (FileError e)
+ {
+ if (!(e is FileError.NOENT))
+ warning ("Failed to load history: %s", e.message);
+ return;
+ }
+
+ foreach (var line in contents.split ("\n"))
+ {
+ var tokens = line.split (" ");
+ if (tokens.length != 3)
+ continue;
+
+ var date = parse_date (tokens[0]);
+ if (date == null)
+ continue;
+ var level = int.parse (tokens[1]);
+ var moves = int.parse (tokens[2]);
+
+ add (new HistoryEntry (date, level, moves));
+ }
+ }
+
+ public void save ()
+ {
+ var contents = "";
+
+ foreach (var entry in entries)
+ {
+ var line = "%s %u %u\n".printf (entry.date.to_string (), entry.level, entry.moves);
+ contents += line;
+ }
+
+ try
+ {
+ DirUtils.create_with_parents (Path.get_dirname (filename), 0775);
+ FileUtils.set_contents (filename, contents);
+ }
+ catch (FileError e)
+ {
+ warning ("Failed to save history: %s", e.message);
+ }
+ }
+
+ private DateTime? parse_date (string date)
+ {
+ if (date.length < 19 || date[4] != '-' || date[7] != '-' || date[10] != 'T' || date[13] != ':' || date[16] != ':')
+ return null;
+
+ var year = int.parse (date.substring (0, 4));
+ var month = int.parse (date.substring (5, 2));
+ var day = int.parse (date.substring (8, 2));
+ var hour = int.parse (date.substring (11, 2));
+ var minute = int.parse (date.substring (14, 2));
+ var seconds = int.parse (date.substring (17, 2));
+ var timezone = date.substring (19);
+
+ return new DateTime (new TimeZone (timezone), year, month, day, hour, minute, seconds);
+ }
+}
+
+public class HistoryEntry
+{
+ public DateTime date;
+ public uint level;
+ public uint moves;
+
+ public HistoryEntry (DateTime date, uint level, uint moves)
+ {
+ this.date = date;
+ this.level = level;
+ this.moves = moves;
+ }
+}
diff --git a/src/puzzle-view.vala b/src/puzzle-view.vala
new file mode 100644
index 0000000..27edd32
--- /dev/null
+++ b/src/puzzle-view.vala
@@ -0,0 +1,234 @@
+public class PuzzleView : Gtk.DrawingArea
+{
+ private const int SPACE_OFFSET = 4;
+ private const int SPACE_PADDING = 5;
+ private const int THEME_OVERLAY_SIZE = 8;
+ private const int THEME_TILE_SEGMENTS = 27;
+ private const int THEME_TILE_CENTER = 14;
+ private const int THEME_TILE_SIZE = 34;
+
+ private int render_size = 0;
+
+ private Gdk.Pixbuf tiles_pixbuf = null;
+ private Gdk.Pixbuf tiles_image = null;
+
+ private int piece_x = 0;
+ private int piece_y = 0;
+
+ private char piece_id = '\0';
+ private char last_piece_id = '\0';
+
+ private double kx = 0;
+ private double ky = 0;
+
+ private Puzzle? _puzzle = null;
+ public Puzzle? puzzle
+ {
+ get { return _puzzle; }
+ set
+ {
+ if (_puzzle != null)
+ SignalHandler.disconnect_by_func (_puzzle, null, this);
+ _puzzle = value;
+ _puzzle.changed.connect (puzzle_changed_cb);
+ piece_x = 0;
+ piece_y = 0;
+ piece_id = '\0';
+ queue_draw ();
+ }
+ }
+
+ private int tile_size
+ {
+ get
+ {
+ var s = int.min ((get_allocated_width () - SPACE_PADDING) / puzzle.width, (get_allocated_height () - SPACE_PADDING) / puzzle.height);
+ /* SVG theme renders best when tile size is multiple of 2 */
+ if (s % 2 != 0)
+ s--;
+ return s;
+ }
+ }
+
+ public PuzzleView ()
+ {
+ set_events (Gdk.EventMask.EXPOSURE_MASK | Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK);
+ load_image ();
+ }
+
+ private void load_image ()
+ {
+ var path = Path.build_filename (DATA_DIRECTORY, "gnome-klotski.svg", null);
+
+ try
+ {
+ tiles_image = Rsvg.pixbuf_from_file (path);
+ }
+ catch (Error e)
+ {
+ /*var dialog = new Gtk.MessageDialog (window,
+ Gtk.DialogFlags.MODAL,
+ Gtk.MessageType.ERROR,
+ Gtk.ButtonsType.OK,
+ _("Could not find the image:\n%s\n\nPlease check that Klotski is installed correctly."),
+ e.message);
+ dialog.run ();*/
+ stderr.printf ("%s %s\n", "Error in puzzle-view.vala load image:", e.message);
+ stderr.printf ( "%s %s\n", "image path:", path);
+ Posix.exit (Posix.EXIT_FAILURE);
+ }
+ }
+
+ protected override bool draw (Cairo.Context cr)
+ {
+
+ if (tile_size != render_size)
+ {
+ render_size = tile_size;
+
+ tiles_pixbuf = null;
+
+ if (tiles_image != null)
+ tiles_pixbuf = tiles_image.scale_simple(tile_size * THEME_TILE_SEGMENTS, tile_size * 2, Gdk.InterpType.BILINEAR);
+ }
+
+ var style = get_style_context ();
+ var fg = style.get_color (Gtk.StateFlags.NORMAL);
+ var bg = style.get_background_color (Gtk.StateFlags.NORMAL);
+
+ Gdk.cairo_set_source_rgba (cr, bg);
+ cr.paint ();
+
+ int width = this.get_allocated_width ();
+ int height = this.get_allocated_height ();
+
+ Gdk.cairo_set_source_rgba (cr, fg);
+ cr.set_line_width (1.0);
+
+ double kwidth = puzzle.width * tile_size + SPACE_PADDING - 2.0;
+ double kheight = puzzle.height * tile_size + SPACE_PADDING - 2.0;
+ kx = (width - kwidth) / 2.0;
+ ky = (height - kheight) / 2.0;
+
+ cr.rectangle (kx, ky, kwidth, kheight);
+ cr.stroke ();
+
+ for (var y = 0; y < puzzle.height; y++)
+ for (var x = 0; x < puzzle.width; x++)
+ draw_square (cr, x, y, kx, ky);
+
+ return false;
+ }
+
+ private void draw_square (Cairo.Context cr, int x, int y, double kx, double ky)
+ {
+ var rect = Gdk.Rectangle ();
+ rect.x = x * tile_size + SPACE_OFFSET + (int)kx - 1;
+ rect.y = y * tile_size + SPACE_OFFSET + (int)ky - 1;
+ rect.width = tile_size;
+ rect.height = tile_size;
+
+ var style = get_style_context ();
+ var bg = style.get_background_color (Gtk.StateFlags.NORMAL);
+
+ Gdk.cairo_rectangle (cr, rect);
+ Gdk.cairo_set_source_rgba (cr, bg);
+
+ cr.fill ();
+
+ if (puzzle.get_piece_id (puzzle.map, x, y) != ' ')
+ {
+ Gdk.cairo_rectangle (cr, rect);
+ Gdk.cairo_set_source_pixbuf (cr, tiles_pixbuf, rect.x - puzzle.get_piece_nr (x, y) * tile_size, rect.y - tile_size / 2);
+ cr.fill ();
+ }
+
+ if (puzzle.get_piece_id (puzzle.map, x, y) == '*')
+ {
+ var value = 22;
+ if (puzzle.get_piece_id (puzzle.orig_map, x, y) == '.')
+ value = 20;
+
+ var overlay_size = THEME_OVERLAY_SIZE * tile_size / THEME_TILE_SIZE;
+ var overlay_offset = THEME_TILE_CENTER * tile_size / THEME_TILE_SIZE - overlay_size / 2;
+
+ cr.rectangle (rect.x + overlay_offset, rect.y + overlay_offset,
+ overlay_size, overlay_size);
+
+ Gdk.cairo_set_source_pixbuf (cr, tiles_pixbuf, rect.x - value * tile_size, rect.y - tile_size / 2);
+ cr.fill ();
+ }
+ }
+
+ protected override bool button_press_event (Gdk.EventButton event)
+ {
+ if (event.button == 1)
+ {
+ if (puzzle.game_over ())
+ return false;
+ piece_x = (int) (event.x - kx) / tile_size;
+ piece_y = (int) (event.y - ky) / tile_size;
+ piece_id = puzzle.get_piece_id (puzzle.map, piece_x, piece_y);
+ puzzle.move_map = puzzle.map;
+ }
+
+ return false;
+ }
+
+ protected override bool button_release_event (Gdk.EventButton event)
+ {
+ if (event.button == 1 && piece_id != '\0')
+ {
+ if (puzzle.movable (piece_id) && puzzle.mapcmp (puzzle.move_map, puzzle.map))
+ {
+ if (last_piece_id == '\0' || last_piece_id != piece_id)
+ {
+ puzzle.undomove_map = puzzle.lastmove_map;
+ if (puzzle.moves < 999)
+ puzzle.moves++;
+ }
+
+ if (puzzle.moves > 0 && !puzzle.mapcmp (puzzle.undomove_map, puzzle.map))
+ {
+ puzzle.moves--;
+ last_piece_id = '\0';
+ }
+ else
+ last_piece_id = piece_id;
+
+ puzzle.lastmove_map = puzzle.map;
+
+ puzzle.moved ();
+ }
+ piece_id = '\0';
+ }
+
+ return false;
+ }
+
+ protected override bool motion_notify_event (Gdk.EventMotion event)
+ {
+ int new_piece_x, new_piece_y;
+
+ if (piece_id != '\0')
+ {
+ new_piece_x = (int) (event.x - kx) / tile_size;
+ new_piece_y = (int) (event.y - ky) / tile_size;
+ if (new_piece_x >= puzzle.width || event.x < 0 || new_piece_y >= puzzle.height || event.y < 0)
+ return false;
+ if (puzzle.move_piece (piece_id, piece_x, piece_y, new_piece_x, new_piece_y))
+ {
+ piece_x = new_piece_x;
+ piece_y = new_piece_y;
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ private void puzzle_changed_cb ()
+ {
+ queue_draw ();
+ }
+}
diff --git a/src/puzzle.vala b/src/puzzle.vala
new file mode 100644
index 0000000..ee74303
--- /dev/null
+++ b/src/puzzle.vala
@@ -0,0 +1,282 @@
+/*
+ 1 2 4
+
+ 8 * 16
+
+ 32 64 128
+*/
+private const int[] image_map =
+{
+ 0, 0,
+
+ 64, 1,
+ 66, 2,
+ 2, 3,
+
+ 16, 4,
+ 24, 5,
+ 8, 6,
+
+ 208, 7,
+ 248, 8,
+ 104, 9,
+
+ 214, 10,
+ 255, 11,
+ 107, 12,
+
+ 22, 13,
+ 31, 14,
+ 11, 15,
+
+ 18, 16,
+ 10, 17,
+ 80, 18,
+ 72, 19,
+
+
+ /* Misc */
+ 56, 5,
+ 152, 5,
+ 70, 2,
+ 67, 2,
+ 194, 2,
+ 98, 2,
+ 9, 6,
+ 20, 4,
+ 144, 4,
+ 3, 3,
+ 40, 6,
+ 25, 5,
+ 28, 5,
+ 96, 1,
+ 19, 16,
+ 201, 19,
+ 146, 16,
+ 198, 2,
+ 84, 18,
+ 46, 17,
+ 112, 18,
+ 6, 3,
+ 184, 5,
+ 192, 1,
+ 147, 16,
+ 73, 19,
+ 42, 17,
+ 200, 19,
+ 99, 2,
+ 116, 18,
+ 29, 5,
+ 14, 17,
+ 26, 25,
+ 224, 1,
+
+ -1, -1
+};
+
+public class Puzzle
+{
+ public int width;
+ public int height;
+
+ public char[] map;
+ public char[] move_map;
+ public char[] orig_map;
+ public char[] lastmove_map;
+ public char[] undomove_map;
+
+ public int moves = 0;
+
+ public signal void changed ();
+ public signal void moved ();
+
+ public Puzzle (int width, int height, string? data)
+ {
+ this.width = width;
+ this.height = height;
+ map = new char[(width + 2) * (height + 2)];
+ move_map = map;
+ undomove_map = map;
+ if (data != null)
+ {
+ var i = 0;
+ for (var y = 0; y < height; y++)
+ {
+ for (var x = 0; x < width; x++)
+ {
+ set_piece_id (map, x, y, data[i]);
+ i++;
+ }
+ }
+ }
+ orig_map = map;
+ lastmove_map = map;
+ }
+
+ public char get_piece_id (char[] src, int x, int y)
+ {
+ return src[x + 1 + (y + 1) * (width + 2)];
+ }
+
+ private void set_piece_id (char[] src, int x, int y, char id)
+ {
+ src[x + 1 + (y + 1) * (width + 2)] = id;
+ }
+
+ public int get_piece_nr (int x, int y)
+ {
+ x++;
+ y++;
+
+ var c = map[x + y * (width + 2)];
+ if (c == '-')
+ return 23;
+ if (c == ' ')
+ return 21;
+ if (c == '.')
+ return 20;
+
+ var nr = 0;
+ if (map[(x - 1) + (y - 1) * (width + 2)] == c)
+ nr += 1;
+ if (map[(x - 0) + (y - 1) * (width + 2)] == c)
+ nr += 2;
+ if (map[(x + 1) + (y - 1) * (width + 2)] == c)
+ nr += 4;
+ if (map[(x - 1) + (y - 0) * (width + 2)] == c)
+ nr += 8;
+ if (map[(x + 1) + (y - 0) * (width + 2)] == c)
+ nr += 16;
+ if (map[(x - 1) + (y + 1) * (width + 2)] == c)
+ nr += 32;
+ if (map[(x - 0) + (y + 1) * (width + 2)] == c)
+ nr += 64;
+ if (map[(x + 1) + (y + 1) * (width + 2)] == c)
+ nr += 128;
+
+ var i = 0;
+ while (nr != image_map[i] && image_map[i] != -1)
+ i += 2;
+
+ return image_map[i + 1];
+ }
+
+ public bool game_over ()
+ {
+ var over = true;
+ for (var y = 0; y < height; y++)
+ for (var x = 0; x < width; x++)
+ if (get_piece_id (map, x, y) == '*' && get_piece_id (orig_map, x, y) != '.')
+ over = false;
+ return over;
+ }
+
+ public bool mapcmp (char[] m1, char[] m2)
+ {
+ for (var y = 0; y < height; y++)
+ for (var x = 0; x < width; x++)
+ if (get_piece_id (m1, x, y) != get_piece_id (m2, x, y))
+ return true;
+ return false;
+ }
+
+ public bool movable (int id)
+ {
+ if (id == '#' || id == '.' || id == ' ' || id == '-')
+ return false;
+ return true;
+ }
+
+ public bool move_piece (char id, int x1, int y1, int x2, int y2)
+ {
+ var return_value = false;
+
+ if (!movable (id))
+ return false;
+
+ if (get_piece_id (map, x2, y2) == id)
+ return_value = true;
+
+ if (!((y1 == y2 && (x1 - x2).abs () == 1) || (x1 == x2 && (y1 - y2).abs () == 1)))
+ return false;
+
+ if ((y1 - y2).abs () == 1)
+ {
+ if (y1 - y2 < 0)
+ if (check_valid_move (id, 0, 1))
+ return do_move_piece (id, 0, 1);
+ if (y1 - y2 > 0)
+ if (check_valid_move (id, 0, -1))
+ return do_move_piece (id, 0, -1);
+ }
+
+ if ((x1 - x2).abs () == 1)
+ {
+ if (x1 - x2 < 0)
+ if (check_valid_move (id, 1, 0))
+ return do_move_piece (id, 1, 0);
+ if (x1 - x2 > 0)
+ if (check_valid_move (id, -1, 0))
+ return do_move_piece (id, -1, 0);
+ }
+
+ return return_value;
+ }
+
+ private bool check_valid_move (int id, int dx, int dy)
+ {
+ for (var y = 0; y < height; y++)
+ {
+ for (var x = 0; x < width; x++)
+ {
+ if (get_piece_id (map, x, y) == id)
+ {
+ var z = get_piece_id (map, x + dx, y + dy);
+ if (!(z == ' ' || z == '.' || z == id || (id == '*' && z == '-')))
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private bool do_move_piece (char id, int dx, int dy)
+ {
+ var tmpmap = map;
+
+ /* Move pieces */
+ for (var y = 0; y < height; y++)
+ for (var x = 0; x < width; x++)
+ if (get_piece_id (tmpmap, x, y) == id)
+ set_piece_id (tmpmap, x, y, ' ');
+
+ for (var y = 0; y < height; y++)
+ for (var x = 0; x < width; x++)
+ if (get_piece_id (map, x, y) == id)
+ set_piece_id (tmpmap, (x + dx), (y + dy), id);
+
+ /* Preserve some from original map */
+ for (var y = 0; y < height; y++)
+ {
+ for (var x = 0; x < width; x++)
+ {
+ if (get_piece_id (tmpmap, x, y) == ' ' && get_piece_id (orig_map, x, y) == '.')
+ set_piece_id (tmpmap, x, y, '.');
+ if (get_piece_id (tmpmap, x, y) == ' ' && get_piece_id (orig_map, x, y) == '-')
+ set_piece_id (tmpmap, x, y, '-');
+ }
+ }
+
+ /* Paint changes */
+ for (var y = 0; y < height; y++)
+ for (var x = 0; x < width; x++)
+ if (get_piece_id (map, x, y) != get_piece_id (tmpmap, x, y) || get_piece_id (tmpmap, x, y) == id)
+ ; // FIXME: Just redraw the required space
+ changed ();
+
+ map = tmpmap;
+
+ return true;
+ }
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]