[gnome-games] quadrapassel: Port from C++ to Vala
- From: Robert Ancell <rancell src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-games] quadrapassel: Port from C++ to Vala
- Date: Tue, 31 Jan 2012 11:09:36 +0000 (UTC)
commit 44c53b0b3841cee1b6d06ebca88026ef51d29af7
Author: Robert Ancell <robert ancell canonical com>
Date: Wed Jan 4 16:03:56 2012 +1100
quadrapassel: Port from C++ to Vala
configure.ac | 20 +-
libgames-support/GnomeGamesSupport-1.0.vapi | 8 +
quadrapassel/data/7blocks-gw.png | Bin 2157 -> 0 bytes
quadrapassel/data/7blocks-tig.png | Bin 9079 -> 0 bytes
quadrapassel/data/Makefile.am | 17 +-
quadrapassel/data/gameover.ogg | Bin 0 -> 8175 bytes
{sounds => quadrapassel/data}/land.ogg | Bin 5520 -> 5520 bytes
{sounds => quadrapassel/data}/lines1.ogg | Bin 7916 -> 7916 bytes
{sounds => quadrapassel/data}/lines2.ogg | Bin 8315 -> 8315 bytes
{sounds => quadrapassel/data}/lines3.ogg | Bin 9457 -> 9457 bytes
.../data/org.gnome.quadrapassel.gschema.xml.in | 15 +-
.../data/quadrapassel.ogg | Bin 13542 -> 13542 bytes
quadrapassel/data/quadrapassel.svg | 99 --
sounds/land.ogg => quadrapassel/data/slide.ogg | Bin 5520 -> 4036 bytes
{sounds => quadrapassel/data}/turn.ogg | Bin 6091 -> 6091 bytes
quadrapassel/src/Makefile.am | 54 +-
quadrapassel/src/blockops.cpp | 894 --------------
quadrapassel/src/blockops.h | 145 ---
quadrapassel/src/blocks-cache.cpp | 379 ------
quadrapassel/src/blocks-cache.h | 70 --
quadrapassel/src/blocks.cpp | 398 ------
quadrapassel/src/blocks.h | 73 --
quadrapassel/src/config.vapi | 5 +
quadrapassel/src/game-view.vala | 592 +++++++++
quadrapassel/src/game.vala | 745 +++++++++++
quadrapassel/src/highscores.cpp | 53 -
quadrapassel/src/highscores.h | 41 -
quadrapassel/src/main.cpp | 89 --
quadrapassel/src/preview.cpp | 154 ---
quadrapassel/src/preview.h | 66 -
quadrapassel/src/preview.vala | 111 ++
quadrapassel/src/quadrapassel.vala | 696 +++++++++++
quadrapassel/src/renderer.cpp | 296 -----
quadrapassel/src/renderer.h | 64 -
quadrapassel/src/scoreframe.cpp | 163 ---
quadrapassel/src/scoreframe.h | 75 --
quadrapassel/src/sound.cpp | 61 -
quadrapassel/src/sound.h | 32 -
quadrapassel/src/tetris.cpp | 1304 --------------------
quadrapassel/src/tetris.h | 175 ---
sounds/Makefile.am | 5 -
41 files changed, 2199 insertions(+), 4700 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 757b5b1..040ffb8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -107,7 +107,6 @@ AC_SUBST([gamelist])
# Feature matrix
-need_cxx=no
need_vala=no
need_rsvg=no
need_sqlite=no
@@ -118,11 +117,7 @@ need_clutter=no
for game in $gamelist; do
case $game in
- quadrapassel) need_cxx=yes ;;
- *) ;;
- esac
- case $game in
- glchess|gnomine|gnotravex|iagno|lightsoff|mahjongg) need_vala=yes ;;
+ glchess|gnomine|gnotravex|iagno|lightsoff|mahjongg|quadrapassel) need_vala=yes ;;
*) ;;
esac
case $game in
@@ -189,16 +184,6 @@ if test "$need_vala" = "yes"; then
AM_PROG_VALAC([0.13.0])
fi
-if test "$need_cxx" = "yes"; then
- AC_PROG_CXX
-
- # Check whether a C++ was found (AC_PROG_CXX sets $CXX to "g++" even when it
- # doesn't exist)
- AC_LANG_PUSH([C++])
- AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],[],[AC_MSG_ERROR([No C++ compiler found])])
- AC_LANG_POP([C++])
-fi
-
AM_PROG_CC_C_O
LT_INIT
@@ -206,7 +191,6 @@ LT_INIT
GNOME_COMMON_INIT
GNOME_DEBUG_CHECK
GNOME_COMPILE_WARNINGS([maximum])
-GNOME_CXX_WARNINGS([yes])
GNOME_MAINTAINER_MODE_DEFINES
dnl ****************************************************************************
@@ -412,7 +396,6 @@ PKG_CHECK_MODULES([CANBERRA_GTK],[libcanberra-gtk3 >= $LIBCANBERRA_GTK_REQUIRED]
# ********
AM_CFLAGS="$AM_CFLAGS $WARN_CFLAGS"
-AM_CXXFLAGS="$AM_CXXFLAGS $WARN_CXXFLAGS"
# ****
# i18n
@@ -485,7 +468,6 @@ GOBJECT_INTROSPECTION_CHECK([0.6.3])
AC_SUBST([AM_CPPFLAGS])
AC_SUBST([AM_CFLAGS])
-AC_SUBST([AM_CXXFLAGS])
AC_SUBST([AM_LDFLAGS])
##############################################
diff --git a/libgames-support/GnomeGamesSupport-1.0.vapi b/libgames-support/GnomeGamesSupport-1.0.vapi
index 4a9b154..3eda331 100644
--- a/libgames-support/GnomeGamesSupport-1.0.vapi
+++ b/libgames-support/GnomeGamesSupport-1.0.vapi
@@ -195,5 +195,13 @@ namespace GnomeGamesSupport
public unowned string? get_nth (int n);
public Gtk.Widget create_widget (string selection, uint flags);
}
+
+ [CCode (cheader_filename = "games-controls.h")]
+ public class ControlsList : Gtk.ScrolledWindow
+ {
+ public ControlsList (GLib.Settings settings);
+ public void add_control (string conf_key, string label, uint default_keyval);
+ public void add_controls (string first_conf_key, ...);
+ }
}
diff --git a/quadrapassel/data/Makefile.am b/quadrapassel/data/Makefile.am
index aabbc4b..ddd1670 100644
--- a/quadrapassel/data/Makefile.am
+++ b/quadrapassel/data/Makefile.am
@@ -5,11 +5,16 @@ gsettings_SCHEMAS = $(gsettings_in_file:.xml.in=.xml)
@INTLTOOL_XML_NOMERGE_RULE@
@GSETTINGS_RULES@
-pixmapdir = $(datadir)/quadrapassel/pixmaps
-pixmap_DATA = \
- quadrapassel.svg \
- 7blocks-tig.png \
- 7blocks-gw.png
+soundsdir = $(datadir)/quadrapassel/sounds
+sounds_DATA = \
+ gameover.ogg \
+ land.ogg \
+ lines1.ogg \
+ lines2.ogg \
+ lines3.ogg \
+ slide.ogg \
+ turn.ogg \
+ quadrapassel.ogg
desktopdir = $(datadir)/applications
desktop_in_files = quadrapassel.desktop.in.in
@@ -20,7 +25,7 @@ man_MANS = quadrapassel.6
EXTRA_DIST = \
$(gsettings_in_file) \
- $(pixmap_DATA) \
+ $(sounds_DATA) \
$(desktop_in_files) \
$(man_MANS)
diff --git a/quadrapassel/data/gameover.ogg b/quadrapassel/data/gameover.ogg
new file mode 100644
index 0000000..4858088
Binary files /dev/null and b/quadrapassel/data/gameover.ogg differ
diff --git a/sounds/land.ogg b/quadrapassel/data/land.ogg
similarity index 100%
copy from sounds/land.ogg
copy to quadrapassel/data/land.ogg
diff --git a/sounds/lines1.ogg b/quadrapassel/data/lines1.ogg
similarity index 100%
rename from sounds/lines1.ogg
rename to quadrapassel/data/lines1.ogg
diff --git a/sounds/lines2.ogg b/quadrapassel/data/lines2.ogg
similarity index 100%
rename from sounds/lines2.ogg
rename to quadrapassel/data/lines2.ogg
diff --git a/sounds/lines3.ogg b/quadrapassel/data/lines3.ogg
similarity index 100%
rename from sounds/lines3.ogg
rename to quadrapassel/data/lines3.ogg
diff --git a/quadrapassel/data/org.gnome.quadrapassel.gschema.xml.in b/quadrapassel/data/org.gnome.quadrapassel.gschema.xml.in
index 80adf4d..a318800 100644
--- a/quadrapassel/data/org.gnome.quadrapassel.gschema.xml.in
+++ b/quadrapassel/data/org.gnome.quadrapassel.gschema.xml.in
@@ -6,25 +6,16 @@
<_description>Image to use for drawing blocks.</_description>
</key>
<key name="theme" type="s">
- <default>'tangoshaded'</default>
+ <default>'clean'</default>
<_summary>The theme used for rendering the blocks</_summary>
<_description>The name of the theme used for rendering the blocks and the background.</_description>
</key>
<key name="starting-level" type="i">
<default>1</default>
+ <range min="1" max="20"/>
<_summary>Level to start with</_summary>
<_description>Level to start with.</_description>
</key>
- <key name="use-bg-image" type="b">
- <default>true</default>
- <_summary>Whether to use the background image</_summary>
- <_description>This selects whether or not to draw the background image over the background color.</_description>
- </key>
- <key name="bg-color" type="s">
- <default>'Black'</default>
- <_summary>The background color</_summary>
- <_description>The background color, in a format gdk_color_parse understands.</_description>
- </key>
<key name="do-preview" type="b">
<default>true</default>
<_summary>Whether to preview the next block</_summary>
@@ -47,11 +38,13 @@
</key>
<key name="line-fill-height" type="i">
<default>0</default>
+ <range min="0" max="19"/>
<_summary>The number of rows to fill</_summary>
<_description>The number of rows that are filled with random blocks at the start of the game.</_description>
</key>
<key name="line-fill-probability" type="i">
<default>5</default>
+ <range min="0" max="10"/>
<_summary>The density of filled rows</_summary>
<_description>The density of blocks in rows filled at the start of the game. The value is between 0 (for no blocks) and 10 (for a completely filled row).</_description>
</key>
diff --git a/sounds/gnometris.ogg b/quadrapassel/data/quadrapassel.ogg
similarity index 100%
rename from sounds/gnometris.ogg
rename to quadrapassel/data/quadrapassel.ogg
diff --git a/sounds/land.ogg b/quadrapassel/data/slide.ogg
similarity index 57%
rename from sounds/land.ogg
rename to quadrapassel/data/slide.ogg
index e322fb6..029d6db 100644
Binary files a/sounds/land.ogg and b/quadrapassel/data/slide.ogg differ
diff --git a/sounds/turn.ogg b/quadrapassel/data/turn.ogg
similarity index 100%
rename from sounds/turn.ogg
rename to quadrapassel/data/turn.ogg
diff --git a/quadrapassel/src/Makefile.am b/quadrapassel/src/Makefile.am
index 32fc56d..5e31bfa 100644
--- a/quadrapassel/src/Makefile.am
+++ b/quadrapassel/src/Makefile.am
@@ -1,39 +1,37 @@
bin_PROGRAMS = quadrapassel
quadrapassel_SOURCES = \
- main.cpp \
- blocks.cpp \
- blocks.h \
- highscores.cpp \
- highscores.h \
- scoreframe.cpp \
- scoreframe.h \
- tetris.cpp \
- tetris.h \
- preview.cpp \
- preview.h \
- blockops.cpp \
- blockops.h \
- renderer.cpp \
- renderer.h \
- blocks-cache.cpp \
- blocks-cache.h \
- sound.cpp \
- sound.h
+ config.vapi \
+ fixes.vapi \
+ quadrapassel.vala \
+ preview.vala \
+ game.vala \
+ game-view.vala
-quadrapassel_CPPFLAGS = \
- -I$(top_srcdir) \
- $(AM_CPPFLAGS)
-
-quadrapassel_CXXFLAGS = \
+quadrapassel_CFLAGS = \
+ -I$(top_srcdir)/libgames-support \
+ -DVERSION=\"$(VERSION)\" \
+ -DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \
-DDATA_DIRECTORY=\"$(datadir)/quadrapassel\" \
- -DSOUND_DIRECTORY=\"$(pkgdatadir)/sounds\" \
+ -DSOUND_DIRECTORY=\"$(datadir)/quadrapassel/sounds\" \
-DLOCALEDIR=\"$(datadir)/locale\" \
$(GTK_CFLAGS) \
$(CANBERRA_GTK_CFLAGS) \
$(CLUTTER_GTK_CFLAGS) \
- $(CLUTTER_CFLAGS) \
- $(AM_CXXFLAGS)
+ $(CLUTTER_CFLAGS)
+
+quadrapassel_VALAFLAGS = \
+ --pkg posix \
+ --pkg gtk+-3.0 \
+ --pkg pango \
+ --pkg pangocairo \
+ --pkg clutter-1.0 \
+ --pkg clutter-gtk-1.0 \
+ --pkg cogl-1.0 \
+ --pkg libcanberra \
+ --pkg libcanberra-gtk \
+ --vapidir $(top_srcdir)/libgames-support \
+ --pkg GnomeGamesSupport-1.0
quadrapassel_LDADD = \
$(top_builddir)/libgames-support/libgames-support.la \
@@ -44,7 +42,7 @@ quadrapassel_LDADD = \
$(INTLLIBS)
if HAVE_RSVG
-quadrapassel_CXXFLAGS += $(RSVG_CFLAGS)
+quadrapassel_CFLAGS += $(RSVG_CFLAGS)
quadrapassel_LDADD += $(RSVG_LIBS)
endif
diff --git a/quadrapassel/src/config.vapi b/quadrapassel/src/config.vapi
new file mode 100644
index 0000000..3e558f2
--- /dev/null
+++ b/quadrapassel/src/config.vapi
@@ -0,0 +1,5 @@
+public const string VERSION;
+public const string GETTEXT_PACKAGE;
+public const string DATA_DIRECTORY;
+public const string SOUND_DIRECTORY;
+public const string LOCALEDIR;
diff --git a/quadrapassel/src/game-view.vala b/quadrapassel/src/game-view.vala
new file mode 100644
index 0000000..ef42a06
--- /dev/null
+++ b/quadrapassel/src/game-view.vala
@@ -0,0 +1,592 @@
+public class GameView : GtkClutter.Embed
+{
+ /* Game being played */
+ private Game? _game = null;
+ public Game? game
+ {
+ get { return _game; }
+ set
+ {
+ if (_game != null)
+ SignalHandler.disconnect_matched (_game, SignalMatchType.DATA, 0, 0, null, null, this);
+ _game = value;
+ _game.shape_added.connect (shape_added_cb);
+ _game.shape_moved.connect (shape_moved_cb);
+ _game.shape_dropped.connect (shape_dropped_cb);
+ _game.shape_rotated.connect (shape_rotated_cb);
+ _game.shape_landed.connect (shape_landed_cb);
+ _game.pause_changed.connect (pause_changed_cb);
+ _game.complete.connect (game_complete_cb);
+
+ /* Remove any existing block */
+ blocks.remove_all ();
+ playing_field.remove_all ();
+
+ /* Add in the current blocks */
+ if (game.shape != null)
+ shape_added_cb ();
+ for (var x = 0; x < _game.width; x++)
+ {
+ for (var y = 0; y < _game.height; y++)
+ {
+ var block = _game.blocks[x, y];
+ if (block != null)
+ {
+ var actor = new BlockActor (block, block_textures[block.color]);
+ blocks.insert (block, actor);
+ actor.set_size (cell_size, cell_size);
+ actor.set_position (block.x * cell_size, block.y * cell_size);
+ playing_field.add (actor);
+ }
+ }
+ }
+
+ set_size_request (_game.width * 190 / _game.height, 190);
+ update_message ();
+ }
+ }
+
+ /* false to play sound effects */
+ public bool mute;
+
+ /* Theme to use */
+ public string theme
+ {
+ set
+ {
+ foreach (var texture in block_textures)
+ texture.theme = value;
+ }
+ }
+
+ private Clutter.Group playing_field;
+
+ /* The shape currently falling */
+ private Clutter.Group? shape = null;
+
+ /* Overlay to draw messages on */
+ private TextOverlay text_overlay;
+
+ /* Textures used to draw blocks */
+ private BlockTexture[] block_textures;
+
+ /* Blocks */
+ private HashTable<Block, BlockActor> blocks;
+ private HashTable<Block, BlockActor> shape_blocks;
+
+ /* Number of lines destroyed (required for earthquake effect) */
+ private int n_lines_destroyed;
+
+ private int cell_size
+ {
+ get
+ {
+ if (game != null)
+ return int.min (get_allocated_width () / game.width, get_allocated_height () / game.height);
+ else
+ return 0;
+ }
+ }
+
+ public GameView ()
+ {
+ blocks = new HashTable<Block, BlockActor> (direct_hash, direct_equal);
+ shape_blocks = new HashTable<Block, BlockActor> (direct_hash, direct_equal);
+
+ size_allocate.connect (size_allocate_cb);
+
+ var stage = (Clutter.Stage) get_stage ();
+ Clutter.Color stage_color = { 0x0, 0x0, 0x0, 0xff };
+ stage.set_color (stage_color);
+
+ playing_field = new Clutter.Group ();
+ stage.add_actor (playing_field);
+
+ text_overlay = new TextOverlay ();
+ stage.add (text_overlay);
+
+ block_textures = new BlockTexture[NCOLORS];
+ for (var i = 0; i < block_textures.length; i++)
+ {
+ // FIXME: Have to set a size to avoid an assertion in Clutter
+ block_textures[i] = new BlockTexture (i, 1);
+ block_textures[i].hide ();
+ stage.add_actor (block_textures[i]);
+ }
+ }
+
+ private void play_sound (string name)
+ {
+ if (!mute)
+ CanberraGtk.play_for_widget (this, 0,
+ Canberra.PROP_MEDIA_NAME, name,
+ Canberra.PROP_MEDIA_FILENAME, Path.build_filename (SOUND_DIRECTORY, "%s.ogg".printf (name)));
+ }
+
+ private void shape_added_cb ()
+ {
+ shape = new Clutter.Group ();
+ playing_field.add (shape);
+ shape.set_position (game.shape.x * cell_size, game.shape.y * cell_size);
+ foreach (var block in game.shape.blocks)
+ {
+ var actor = new BlockActor (block, block_textures[block.color]);
+ shape_blocks.insert (block, actor);
+ shape.add (actor);
+ actor.set_size (cell_size, cell_size);
+ actor.set_position (block.x * cell_size, block.y * cell_size);
+ }
+ }
+
+ private void shape_moved_cb ()
+ {
+ play_sound ("slide");
+ shape.animate (Clutter.AnimationMode.EASE_IN_QUAD, 30, "x", (float) game.shape.x * cell_size);
+ }
+
+ private void shape_dropped_cb ()
+ {
+ shape.animate (Clutter.AnimationMode.EASE_IN_QUAD, 60, "y", (float) game.shape.y * cell_size);
+ }
+
+ private void shape_rotated_cb ()
+ {
+ play_sound ("turn");
+ foreach (var block in game.shape.blocks)
+ {
+ var actor = shape_blocks.lookup (block);
+ actor.set_position (block.x * cell_size, block.y * cell_size);
+ }
+ }
+
+ private void shape_landed_cb (int[] lines, List<Block> line_blocks)
+ {
+ switch (lines.length)
+ {
+ default:
+ play_sound ("land");
+ break;
+ case 1:
+ play_sound ("lines1");
+ break;
+ case 2:
+ play_sound ("lines2");
+ break;
+ case 3:
+ case 4:
+ play_sound ("lines3");
+ break;
+ }
+
+ /* Remove the moving shape */
+ shape.destroy ();
+ shape = null;
+ shape_blocks.remove_all ();
+
+ /* Land the shape blocks */
+ foreach (var block in game.shape.blocks)
+ {
+ var actor = new BlockActor (block, block_textures[block.color]);
+ playing_field.add (actor);
+ blocks.insert (block, actor);
+ actor.set_size (cell_size, cell_size);
+ actor.set_position (block.x * cell_size, block.y * cell_size);
+ }
+
+ /* Explode blocks */
+ foreach (var block in line_blocks)
+ {
+ var actor = blocks.lookup (block);
+ actor.explode ();
+ blocks.remove (block);
+ }
+
+ /* Drop blocks that have moved */
+ if (lines.length > 0)
+ {
+ var timeline = new Clutter.Timeline (60);
+ n_lines_destroyed = lines.length;
+ timeline.completed.connect (fall_completed_cb);
+ for (var x = 0; x < game.width; x++)
+ {
+ for (var y = 0; y < game.height; y++)
+ {
+ var block = game.blocks[x, y];
+ if (block == null)
+ continue;
+
+ var actor = blocks.lookup (block);
+ actor.animate_with_timeline (Clutter.AnimationMode.EASE_IN_QUAD, timeline, "x", (float) block.x * cell_size, "y", (float) block.y * cell_size);
+ }
+ }
+ }
+ }
+
+ private void fall_completed_cb (Clutter.Timeline timeline)
+ {
+ /* Do an earthquake effect */
+ float x, y;
+ playing_field.get_position (out x, out y);
+ playing_field.set_position (x, y + cell_size * n_lines_destroyed * 0.25f);
+ playing_field.animate (Clutter.AnimationMode.EASE_OUT_BOUNCE, 720 / (5 - n_lines_destroyed), "x", x, "y", y);
+ }
+
+ private void size_allocate_cb (Gtk.Widget widget, Gtk.Allocation allocation)
+ {
+ if (game == null)
+ return;
+
+ foreach (var texture in block_textures)
+ texture.set_size (cell_size, cell_size);
+
+ var iter = HashTableIter<Block, BlockActor> (blocks);
+ while (true)
+ {
+ Block block;
+ BlockActor actor;
+ if (!iter.next (out block, out actor))
+ break;
+ actor.set_size (cell_size, cell_size);
+ actor.set_position (block.x * cell_size, block.y * cell_size);
+ }
+ var shape_iter = HashTableIter<Block, BlockActor> (shape_blocks);
+ while (true)
+ {
+ Block block;
+ BlockActor actor;
+ if (!shape_iter.next (out block, out actor))
+ break;
+ actor.set_size (cell_size, cell_size);
+ actor.set_position (block.x * cell_size, block.y * cell_size);
+ }
+ if (shape != null)
+ shape.set_position (game.shape.x * cell_size, game.shape.y * cell_size);
+
+ text_overlay.set_size (get_allocated_width (), get_allocated_height ());
+ text_overlay.raise_top ();
+
+ playing_field.set_size (game.width * cell_size, game.height * cell_size);
+ playing_field.set_position ((get_allocated_width () - playing_field.get_width ()) * 0.5f,
+ get_allocated_height () - playing_field.get_height ());
+ }
+
+ private void pause_changed_cb ()
+ {
+ update_message ();
+ }
+
+ private void game_complete_cb ()
+ {
+ play_sound ("gameover");
+ update_message ();
+ }
+
+ private void update_message ()
+ {
+ if (game.paused)
+ text_overlay.text = _("Paused");
+ else if (game.game_over)
+ text_overlay.text = _("Game Over");
+ else
+ text_overlay.text = null;
+ }
+}
+
+private class BlockActor : Clutter.Clone
+{
+ public Block block;
+
+ public BlockActor (Block block, Clutter.Actor texture)
+ {
+ Object (source: texture);
+ this.block = block;
+ }
+
+ public void explode ()
+ {
+ raise_top ();
+ var timeline = new Clutter.Timeline (720);
+ timeline.completed.connect (explode_complete_cb);
+ animate_with_timeline (Clutter.AnimationMode.EASE_OUT_QUINT, timeline, "opacity", 0, "scale-x", 2f, "scale-y", 2f);
+ }
+
+ private void explode_complete_cb ()
+ {
+ destroy ();
+ }
+}
+
+private class TextOverlay : Clutter.CairoTexture
+{
+ private string? _text = null;
+ public string text
+ {
+ get { return _text; }
+ set { _text = value; invalidate (); }
+ }
+
+ public TextOverlay ()
+ {
+ auto_resize = true;
+ }
+
+ protected override bool draw (Cairo.Context cr)
+ {
+ clear ();
+
+ if (text == null)
+ return false;
+
+ /* Center coordinates */
+ uint w, h;
+ get_surface_size (out w, out h);
+ cr.translate (w / 2, h / 2);
+
+ var desc = Pango.FontDescription.from_string ("Sans");
+
+ var layout = Pango.cairo_create_layout (cr);
+ layout.set_text (text, -1);
+
+ var dummy_layout = layout.copy ();
+ dummy_layout.set_font_description (desc);
+ int lw, lh;
+ dummy_layout.get_size (out lw, out lh);
+
+ desc.set_absolute_size (((float) lh / lw) * Pango.SCALE * w * 0.7);
+ layout.set_font_description (desc);
+
+ layout.get_size (out lw, out lh);
+ cr.move_to (-((double)lw / Pango.SCALE) / 2, -((double)lh / Pango.SCALE) / 2);
+ Pango.cairo_layout_path (cr, layout);
+ cr.set_source_rgb (0.333333333333, 0.341176470588, 0.32549019607);
+
+ /* A linewidth of 2 pixels at the default size. */
+ cr.set_line_width (width / 100.0);
+ cr.stroke_preserve ();
+
+ cr.set_source_rgb (1.0, 1.0, 1.0);
+ cr.fill ();
+
+ return false;
+ }
+}
+
+public class BlockTexture : Clutter.CairoTexture
+{
+ private int color;
+ private string? _theme = null;
+ public string? theme
+ {
+ get { return _theme; }
+ set
+ {
+ if (_theme == value)
+ return;
+ _theme = value;
+ invalidate ();
+ }
+ }
+
+ public BlockTexture (int color, int size)
+ {
+ auto_resize = true;
+ set_surface_size (size, size);
+ this.color = color.clamp (0, 6);
+ }
+
+ protected override bool draw (Cairo.Context cr)
+ {
+ clear ();
+
+ uint w, h;
+ get_surface_size (out w, out h);
+ cr.scale (w, h);
+
+ switch (theme)
+ {
+ default:
+ case "plain":
+ draw_plain (cr);
+ break;
+ case "clean":
+ draw_clean (cr);
+ break;
+ case "tangoflat":
+ draw_tango (cr, false);
+ break;
+ case "tangoshaded":
+ draw_tango (cr, true);
+ break;
+ }
+
+ return false;
+ }
+
+ private void draw_plain (Cairo.Context cr)
+ {
+ const double colors[32] =
+ {
+ 1.0, 0.0, 0.0,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0,
+ 1.0, 1.0, 1.0,
+ 1.0, 1.0, 0.0,
+ 1.0, 0.0, 1.0,
+ 0.0, 1.0, 1.0
+ };
+
+ cr.set_source_rgb(colors[color * 3], colors[color * 3 + 1], colors[color * 3 + 2]);
+ cr.paint ();
+ }
+
+ private void draw_rounded_rectangle (Cairo.Context cr, double x, double y, double w, double h, double r)
+ {
+ cr.move_to (x + r, y);
+ cr.line_to (x + w - r, y);
+ cr.curve_to (x + w - (r/2), y, x + w, y + r / 2, x + w, y + r);
+ cr.line_to (x + w, y + h - r);
+ cr.curve_to (x + w, y + h - r / 2, x + w - r / 2, y + h, x + w - r, y + h);
+ cr.line_to (x + r, y + h);
+ cr.curve_to (x + r / 2, y + h, x, y + h - r / 2, x, y + h - r);
+ cr.line_to (x, y + r);
+ cr.curve_to (x, y + r / 2, x + r / 2, y, x + r, y);
+ }
+
+ private void draw_clean (Cairo.Context cr)
+ {
+ /* The colors, first the lighter then the darker fill (for the gradient)
+ and then the stroke color */
+ const double colors[72] =
+ {
+ 0.780392156863, 0.247058823529, 0.247058823529,
+ 0.713725490196, 0.192156862745, 0.192156862745,
+ 0.61568627451, 0.164705882353, 0.164705882353, /* red */
+
+ 0.552941176471, 0.788235294118, 0.32549019607,
+ 0.474509803922, 0.713725490196, 0.243137254902,
+ 0.388235294118, 0.596078431373, 0.18431372549, /* green */
+
+ 0.313725490196, 0.450980392157, 0.623529411765,
+ 0.239215686275, 0.345098039216, 0.474509803922,
+ 0.21568627451, 0.313725490196, 0.435294117647, /* blue */
+
+ 1.0, 1.0, 1.0,
+ 0.909803921569, 0.909803921569, 0.898039215686,
+ 0.701960784314, 0.701960784314, 0.670588235294, /* white */
+
+ 0.945098039216, 0.878431372549, 0.321568627451,
+ 0.929411764706, 0.839215686275, 0.113725490196,
+ 0.760784313725, 0.682352941176, 0.0274509803922, /* yellow */
+
+ 0.576470588235, 0.364705882353, 0.607843137255,
+ 0.443137254902, 0.282352941176, 0.46666666666,
+ 0.439215686275, 0.266666666667, 0.46666666666, /* purple */
+
+ 0.890196078431, 0.572549019608, 0.258823529412,
+ 0.803921568627, 0.450980392157, 0.101960784314,
+ 0.690196078431, 0.388235294118, 0.0901960784314, /* orange */
+
+ 0.392156862745, 0.392156862745, 0.392156862745,
+ 0.262745098039, 0.262745098039, 0.262745098039,
+ 0.21568627451, 0.235294117647, 0.23921568627 /* grey */
+ };
+
+ /* Layout the block */
+ draw_rounded_rectangle (cr, 0.05, 0.05, 0.9, 0.9, 0.05);
+
+ /* Draw outline */
+ cr.set_source_rgb (colors[color * 9 + 6], colors[color * 9 + 7], colors[color * 9 + 8]);
+ cr.set_line_width (0.1);
+ cr.stroke_preserve ();
+
+ /* Fill with gradient */
+ var pat = new Cairo.Pattern.linear (0.35, 0, 0.55, 0.9);
+ pat.add_color_stop_rgb (0.0, colors[color * 9], colors[color * 9 + 1], colors[color * 9 + 2]);
+ pat.add_color_stop_rgb (1.0, colors[color * 9 + 3], colors[color * 9 + 4], colors[color * 9 + 5]);
+ cr.set_source (pat);
+ cr.fill ();
+ }
+
+ private void draw_tango (Cairo.Context cr, bool use_gradients)
+ {
+ /* The following garbage is derived from the official tango style guide */
+ const double colors[72] =
+ {
+ 0.93725490196078431, 0.16078431372549021, 0.16078431372549021,
+ 0.8, 0.0, 0.0,
+ 0.64313725490196083, 0.0, 0.0, /* red */
+
+ 0.54117647058823526, 0.88627450980392153, 0.20392156862745098,
+ 0.45098039215686275, 0.82352941176470584, 0.086274509803921567,
+ 0.30588235294117649, 0.60392156862745094, 0.023529411764705882, /* green */
+
+ 0.44705882352941179, 0.62352941176470589, 0.81176470588235294,
+ 0.20392156862745098, 0.396078431372549, 0.64313725490196083,
+ 0.12549019607843137, 0.29019607843137257, 0.52941176470588236, /* blue */
+
+ 0.93333333333333335, 0.93333333333333335, 0.92549019607843142,
+ 0.82745098039215681, 0.84313725490196079, 0.81176470588235294,
+ 0.72941176470588232, 0.74117647058823533, 0.71372549019607845, /* white */
+
+ 0.9882352941176471, 0.9137254901960784, 0.30980392156862746,
+ 0.92941176470588238, 0.83137254901960789, 0.0,
+ 0.7686274509803922, 0.62745098039215685, 0.0, /* yellow */
+
+ 0.67843137254901964, 0.49803921568627452, 0.6588235294117647,
+ 0.45882352941176469, 0.31372549019607843, 0.4823529411764706,
+ 0.36078431372549019, 0.20784313725490197, 0.4, /* purple */
+
+ 0.9882352941176471, 0.68627450980392157, 0.24313725490196078,
+ 0.96078431372549022, 0.47450980392156861, 0.0,
+ 0.80784313725490198, 0.36078431372549019, 0.0, /* orange (replacing cyan) */
+
+ 0.33, 0.34, 0.32,
+ 0.18, 0.2, 0.21,
+ 0.10, 0.12, 0.13 /* grey */
+ };
+
+ if (use_gradients)
+ {
+ var pat = new Cairo.Pattern.linear (0.35, 0, 0.55, 0.9);
+ pat.add_color_stop_rgb (0.0, colors[color * 9], colors[color * 9 + 1], colors[color * 9 + 2]);
+ pat.add_color_stop_rgb (1.0, colors[color * 9 + 3], colors[color * 9 + 4], colors[color * 9 + 5]);
+ cr.set_source (pat);
+ }
+ else
+ cr.set_source_rgb (colors[color * 9], colors[color * 9 + 1], colors[color * 9 + 2]);
+
+ draw_rounded_rectangle (cr, 0.05, 0.05, 0.9, 0.9, 0.2);
+ cr.fill_preserve (); /* fill with shaded gradient */
+
+ cr.set_source_rgb (colors[color * 9 + 6], colors[color * 9 + 7], colors[color * 9 + 8]);
+
+ /* Add darker outline */
+ cr.set_line_width (0.1);
+ cr.stroke ();
+
+ draw_rounded_rectangle (cr, 0.15, 0.15, 0.7, 0.7, 0.08);
+ if (use_gradients)
+ {
+ var pat = new Cairo.Pattern.linear (-0.3, -0.3, 0.8, 0.8);
+ /* yellow and white blocks need a brighter highlight */
+ switch (color)
+ {
+ case 3:
+ case 4:
+ pat.add_color_stop_rgba (0.0, 1.0, 1.0, 1.0, 1.0);
+ pat.add_color_stop_rgba (1.0, 1.0, 1.0, 1.0, 0.0);
+ break;
+ default:
+ pat.add_color_stop_rgba (0.0, 0.9295, 0.9295, 0.9295, 1.0);
+ pat.add_color_stop_rgba (1.0, 0.9295, 0.9295, 0.9295, 0.0);
+ break;
+ }
+ cr.set_source (pat);
+ }
+ else
+ cr.set_source_rgba (1.0, 1.0, 1.0, 0.35);
+
+ /* Add inner edge highlight */
+ cr.stroke ();
+ }
+}
diff --git a/quadrapassel/src/game.vala b/quadrapassel/src/game.vala
new file mode 100644
index 0000000..d0a2d7b
--- /dev/null
+++ b/quadrapassel/src/game.vala
@@ -0,0 +1,745 @@
+const int NCOLORS = 7;
+
+private const int block_table[448] =
+{
+ /* *** */
+ /* * */
+ 0, 0, 0, 0,
+ 1, 1, 1, 0,
+ 1, 0, 0, 0,
+ 0, 0, 0, 0,
+
+ 0, 1, 0, 0,
+ 0, 1, 0, 0,
+ 0, 1, 1, 0,
+ 0, 0, 0, 0,
+
+ 0, 0, 1, 0,
+ 1, 1, 1, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+
+ 1, 1, 0, 0,
+ 0, 1, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 0, 0,
+
+ /* *** */
+ /* * */
+ 0, 0, 0, 0,
+ 1, 1, 1, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 0,
+
+ 0, 1, 1, 0,
+ 0, 1, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 0, 0,
+
+ 1, 0, 0, 0,
+ 1, 1, 1, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+
+ 0, 1, 0, 0,
+ 0, 1, 0, 0,
+ 1, 1, 0, 0,
+ 0, 0, 0, 0,
+
+ /* *** */
+ /* * */
+ 0, 0, 0, 0,
+ 1, 1, 1, 0,
+ 0, 1, 0, 0,
+ 0, 0, 0, 0,
+
+ 0, 1, 0, 0,
+ 0, 1, 1, 0,
+ 0, 1, 0, 0,
+ 0, 0, 0, 0,
+
+ 0, 1, 0, 0,
+ 1, 1, 1, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+
+ 0, 1, 0, 0,
+ 1, 1, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 0, 0,
+
+ /* ** */
+ /* ** */
+
+ 0, 0, 0, 0,
+ 0, 1, 1, 0,
+ 1, 1, 0, 0,
+ 0, 0, 0, 0,
+
+ 0, 1, 0, 0,
+ 0, 1, 1, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 0,
+
+ 0, 1, 1, 0,
+ 1, 1, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+
+ 1, 0, 0, 0,
+ 1, 1, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 0, 0,
+
+ /* ** */
+ /* ** */
+
+ 0, 0, 0, 0,
+ 1, 1, 0, 0,
+ 0, 1, 1, 0,
+ 0, 0, 0, 0,
+
+ 0, 0, 1, 0,
+ 0, 1, 1, 0,
+ 0, 1, 0, 0,
+ 0, 0, 0, 0,
+
+ 1, 1, 0, 0,
+ 0, 1, 1, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+
+ 0, 1, 0, 0,
+ 1, 1, 0, 0,
+ 1, 0, 0, 0,
+ 0, 0, 0, 0,
+
+ /* **** */
+ 0, 0, 0, 0,
+ 1, 1, 1, 1,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+
+ 0, 1, 0, 0,
+ 0, 1, 0, 0,
+ 0, 1, 0, 0,
+ 0, 1, 0, 0,
+
+ 0, 0, 0, 0,
+ 1, 1, 1, 1,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+
+ 0, 1, 0, 0,
+ 0, 1, 0, 0,
+ 0, 1, 0, 0,
+ 0, 1, 0, 0,
+
+ /* ** */
+ /* ** */
+ 0, 0, 0, 0,
+ 0, 1, 1, 0,
+ 0, 1, 1, 0,
+ 0, 0, 0, 0,
+
+ 0, 0, 0, 0,
+ 0, 1, 1, 0,
+ 0, 1, 1, 0,
+ 0, 0, 0, 0,
+
+ 0, 0, 0, 0,
+ 0, 1, 1, 0,
+ 0, 1, 1, 0,
+ 0, 0, 0, 0,
+
+ 0, 0, 0, 0,
+ 0, 1, 1, 0,
+ 0, 1, 1, 0,
+ 0, 0, 0, 0
+};
+
+public class Block
+{
+ /* Location of block */
+ public int x;
+ public int y;
+
+ /* Color of block */
+ public int color;
+
+ public Block copy ()
+ {
+ var b = new Block ();
+ b.x = x;
+ b.y = y;
+ b.color = color;
+ return b;
+ }
+}
+
+public class Shape
+{
+ /* Location of shape */
+ public int x;
+ public int y;
+
+ /* Rotation angle */
+ public int rotation;
+
+ /* Piece type */
+ public int type;
+
+ /* Blocks that make up this shape */
+ public List<Block> blocks = null;
+
+ public Shape copy ()
+ {
+ var s = new Shape ();
+ s.x = x;
+ s.y = y;
+ s.rotation = rotation;
+ s.type = type;
+ foreach (var b in blocks)
+ s.blocks.append (b.copy ());
+ return s;
+ }
+}
+
+public class Game : Object
+{
+ /* Falling shape */
+ public Shape? shape = null;
+
+ /* Next shape to be used */
+ public Shape? next_shape = null;
+
+ /* Placed blocks */
+ public Block[,] blocks;
+
+ public int width { get { return blocks.length[0]; } }
+ public int height { get { return blocks.length[1]; } }
+
+ /* Number of lines that have been destroyed */
+ public int n_lines_destroyed = 0;
+
+ /* Game score */
+ public int score = 0;
+
+ /* Level play started on */
+ private int starting_level = 1;
+
+ /* true if should pick difficult blocks to place */
+ private bool pick_difficult_blocks = false;
+
+ /* The current level */
+ public int level { get { return starting_level + n_lines_destroyed / 10; } }
+
+ /* true if we are in fast forward mode */
+ private bool fast_forward = false;
+
+ /* Timer to animate block drops */
+ private uint drop_timeout = 0;
+
+ /* true if the game has started */
+ private bool has_started = false;
+
+ /* true if games is paused */
+ private bool _paused = false;
+ public bool paused
+ {
+ get { return _paused; }
+ set
+ {
+ _paused = value;
+ if (has_started)
+ setup_drop_timer ();
+ pause_changed ();
+ }
+ }
+
+ public bool game_over = false;
+
+ public signal void started ();
+ public signal void shape_added ();
+ public signal void shape_moved ();
+ public signal void shape_dropped ();
+ public signal void shape_rotated ();
+ public signal void shape_landed (int[] lines, List<Block> line_blocks);
+ public signal void pause_changed ();
+ public signal void complete ();
+
+ public Game (int lines = 20, int columns = 14, int starting_level = 1, int filled_lines = 0, int fill_prob = 5, bool pick_difficult_blocks = false)
+ {
+ this.starting_level = starting_level;
+ this.pick_difficult_blocks = pick_difficult_blocks;
+
+ blocks = new Block[columns, lines];
+ /* Start with some pre-filled spaces */
+ for (var y = 0; y < height; y++)
+ {
+ /* Pick at least one column to be empty */
+ var blank = Random.int_range (0, width);
+
+ for (var x = 0; x < width; x++)
+ {
+ if (y >= (height - filled_lines) && x != blank && Random.int_range (0, 10) < fill_prob)
+ {
+ blocks[x, y] = new Block ();
+ blocks[x, y].x = x;
+ blocks[x, y].y = y;
+ blocks[x, y].color = Random.int_range (0, NCOLORS);
+ }
+ else
+ blocks[x, y] = null;
+ }
+ }
+
+ if (!pick_difficult_blocks)
+ next_shape = pick_random_shape ();
+ }
+
+ public Game copy ()
+ {
+ var g = new Game ();
+ if (shape != null)
+ g.shape = shape.copy ();
+ if (next_shape != null)
+ g.next_shape = next_shape.copy ();
+ for (var x = 0; x < width; x++)
+ {
+ for (var y = 0; y < height; y++)
+ {
+ if (blocks[x, y] != null)
+ g.blocks[x, y] = blocks[x, y].copy ();
+ }
+ }
+ g.n_lines_destroyed = n_lines_destroyed;
+ g.score = score;
+ g.starting_level = starting_level;
+ g.pick_difficult_blocks = pick_difficult_blocks;
+ g.fast_forward = fast_forward;
+ g.has_started = has_started;
+ g._paused = _paused;
+ g.game_over = game_over;
+
+ return g;
+ }
+
+ public void start ()
+ {
+ has_started = true;
+ add_shape ();
+ setup_drop_timer ();
+ started ();
+ }
+
+ public bool move_left ()
+ {
+ return move_shape (-1, 0, 0);
+ }
+
+ public bool move_right ()
+ {
+ return move_shape (1, 0, 0);
+ }
+
+ public bool rotate_left ()
+ {
+ return move_shape (0, 0, -1);
+ }
+
+ public bool rotate_right ()
+ {
+ return move_shape (0, 0, 1);
+ }
+
+ public void set_fast_forward (bool enable)
+ {
+ fast_forward = enable;
+ setup_drop_timer ();
+ }
+
+ public void drop ()
+ {
+ if (shape == null)
+ return;
+
+ while (move_shape (0, 1, 0));
+ fall_timeout_cb ();
+ }
+
+ public void stop ()
+ {
+ if (drop_timeout != 0)
+ Source.remove (drop_timeout);
+ }
+
+ private void setup_drop_timer ()
+ {
+ var timestep = (int) Math.round (80 + 800.0 * Math.pow (0.75, level - 1));
+ timestep = int.max (10, timestep);
+
+ /* In fast forward mode drop at the fastest rate */
+ if (fast_forward)
+ timestep = 80;
+
+ if (drop_timeout != 0)
+ Source.remove (drop_timeout);
+ drop_timeout = 0;
+ if (!paused)
+ drop_timeout = Timeout.add (timestep, fall_timeout_cb);
+ }
+
+ private bool fall_timeout_cb ()
+ {
+ /* Drop the shape down, and create a new one when it can't move */
+ if (!move_shape (0, 1, 0))
+ {
+ /* Destroy any lines created */
+ land_shape ();
+
+ /* Add a new shape */
+ add_shape ();
+ }
+
+ return true;
+ }
+
+ private void add_shape ()
+ {
+ if (pick_difficult_blocks)
+ shape = pick_difficult_shape ();
+ else
+ {
+ shape = (owned) next_shape;
+ next_shape = pick_random_shape ();
+ }
+
+ foreach (var b in shape.blocks)
+ {
+ var x = shape.x + b.x;
+ var y = shape.y + b.y;
+
+ /* Abort if can't place there */
+ if (y >= 0 && blocks[x, y] != null)
+ {
+ // FIXME: Place it where it can fit
+
+ if (drop_timeout != 0)
+ Source.remove (drop_timeout);
+ drop_timeout = 0;
+ shape = null;
+ game_over = true;
+ complete ();
+ return;
+ }
+ }
+
+ shape_added ();
+ }
+
+ private Shape pick_random_shape ()
+ {
+ return make_shape (Random.int_range (0, NCOLORS), Random.int_range (0, 4));
+ }
+
+ private Shape pick_difficult_shape ()
+ {
+ var metrics = new int[NCOLORS];
+ for (var type = 0; type < NCOLORS; type++)
+ {
+ metrics[type] = -32000;
+ for (var rotation = 0; rotation < 4; rotation++)
+ {
+ for (var pos = 0; pos < width; pos++)
+ {
+ /* Copy the current game and create a block of the given type */
+ var g = copy ();
+ g.pick_difficult_blocks = false;
+ g.shape = make_shape (type, rotation);
+
+ /* Move tile to position from the left */
+ var valid_position = true;
+ while (g.move_left ());
+ for (var x = 0; x < pos; x++)
+ {
+ if (!g.move_right ())
+ {
+ valid_position = false;
+ break;
+ }
+ }
+
+ if (!valid_position)
+ break;
+
+ /* Drop the tile here and check the metric */
+ var orig_lines = g.n_lines_destroyed;
+ g.drop ();
+
+ /* High metric for each line destroyed */
+ var metric = (g.n_lines_destroyed - orig_lines) * 5000;
+
+ /* Low metric for large columns */
+ for (var x = 0; x < width; x++)
+ {
+ int y;
+ for (y = 0; y < height; y++)
+ {
+ if (g.blocks[x, y] != null)
+ break;
+ }
+
+ metric -= 5 * (height - y);
+ }
+
+ if (metric > metrics[type])
+ metrics[type] = metric;
+
+ /* Destroy this copy */
+ g.stop ();
+ }
+ }
+ }
+
+ /* Perturb score (-2 to +2), to avoid stupid tie handling */
+ for (var i = 0; i < NCOLORS; i++)
+ metrics[i] += Random.int_range (-2, 2);
+
+ /* Sorts possible_types by priorities, worst (interesting to us) first */
+ var possible_types = new int[NCOLORS];
+ for (var i = 0; i < NCOLORS; i++)
+ possible_types[i] = i;
+ for (var i = 0; i < NCOLORS; i++)
+ {
+ for (var j = 0; j < NCOLORS - 1; j++)
+ {
+ if (metrics[possible_types[j]] > metrics[possible_types[j + 1]])
+ {
+ int t = possible_types[j];
+ possible_types[j] = possible_types[j + 1];
+ possible_types[j + 1] = t;
+ }
+ }
+ }
+
+ /* Actually choose a piece */
+ var rnd = Random.int_range (0, 99);
+ if (rnd < 75)
+ return make_shape (possible_types[0], Random.int_range (0, 4));
+ else if (rnd < 92)
+ return make_shape (possible_types[1], Random.int_range (0, 4));
+ else if (rnd < 98)
+ return make_shape (possible_types[2], Random.int_range (0, 4));
+ else
+ return make_shape (possible_types[3], Random.int_range (0, 4));
+ }
+
+ private Shape make_shape (int type, int rotation)
+ {
+ var shape = new Shape ();
+ shape.type = type;
+ shape.rotation = rotation;
+
+ /* Place this block at top of the field */
+ var offset = shape.type * 64 + shape.rotation * 16;
+ var min_width = 4, max_width = 0, min_height = 4, max_height = 0;
+ for (var x = 0; x < 4; x++)
+ {
+ for (var y = 0; y < 4; y++)
+ {
+ if (block_table[offset + y * 4 + x] == 0)
+ continue;
+
+ min_width = int.min (x, min_width);
+ max_width = int.max (x + 1, max_width);
+ min_height = int.min (y, min_height);
+ max_height = int.max (y + 1, max_height);
+
+ var b = new Block ();
+ b.color = shape.type;
+ b.x = x;
+ b.y = y;
+ shape.blocks.append (b);
+ }
+ }
+ var block_width = max_width - min_width;
+ shape.x = (width - block_width) / 2 - min_width;
+ shape.y = -min_height;
+
+ return shape;
+ }
+
+ private void land_shape ()
+ {
+ /* Leave these blocks here */
+ foreach (var b in shape.blocks)
+ {
+ b.x += shape.x;
+ b.y += shape.y;
+ blocks[b.x, b.y] = b;
+ }
+
+ var fall_distance = 0;
+ var lines = new int[4];
+ var n_lines = 0;
+ var base_line_destroyed = false;
+ for (var y = height - 1; y >= 0; y--)
+ {
+ var explode = true;
+ for (var x = 0; x < width; x++)
+ {
+ if (blocks[x, y] == null)
+ {
+ explode = false;
+ break;
+ }
+ }
+
+ if (explode)
+ {
+ if (y == height - 1)
+ base_line_destroyed = true;
+ lines[n_lines] = y;
+ n_lines++;
+ }
+ }
+ lines.resize (n_lines);
+
+ List<Block> line_blocks = null;
+ for (var y = height - 1; y >= 0; y--)
+ {
+ var explode = true;
+ for (var x = 0; x < width; x++)
+ {
+ if (blocks[x, y] == null)
+ {
+ explode = false;
+ break;
+ }
+ }
+
+ if (explode)
+ {
+ for (var x = 0; x < width; x++)
+ {
+ line_blocks.append (blocks[x, y]);
+ blocks[x, y] = null;
+ }
+ fall_distance++;
+ }
+ else if (fall_distance > 0)
+ {
+ for (var x = 0; x < width; x++)
+ {
+ var b = blocks[x, y];
+ if (b != null)
+ {
+ b.y += fall_distance;
+ blocks[b.x, b.y] = b;
+ blocks[x, y] = null;
+ }
+ }
+ }
+ }
+
+ var old_level = level;
+
+ /* Score points */
+ n_lines_destroyed += n_lines;
+ switch (n_lines)
+ {
+ case 0:
+ break;
+ case 1:
+ score += 40 * level;
+ break;
+ case 2:
+ score += 100 * level;
+ break;
+ case 3:
+ score += 300 * level;
+ break;
+ case 4:
+ score += 1200 * level;
+ break;
+ }
+ /* You get a bonus for getting back to the base */
+ if (base_line_destroyed)
+ score += 10000 * level;
+
+ /* Increase speed if level has changed */
+ if (level != old_level)
+ setup_drop_timer ();
+
+ shape_landed (lines, line_blocks);
+ shape = null;
+ }
+
+ private bool move_shape (int x_step, int y_step, int r_step)
+ {
+ if (shape == null)
+ return false;
+
+ /* Check it can fit into the new location */
+ rotate_shape (r_step);
+ var can_move = true;
+ foreach (var b in shape.blocks)
+ {
+ var x = shape.x + x_step + b.x;
+ var y = shape.y + y_step + b.y;
+ if (x < 0 || x >= width || y >= height || blocks[x, y] != null)
+ {
+ can_move = false;
+ break;
+ }
+ }
+
+ /* Place in the new location or put it back where it was */
+ if (can_move)
+ {
+ shape.x += x_step;
+ shape.y += y_step;
+
+ if (x_step != 0)
+ shape_moved ();
+ else if (y_step > 0)
+ shape_dropped ();
+ else
+ shape_rotated ();
+ }
+ else
+ rotate_shape (-r_step);
+
+ return can_move;
+ }
+
+ private void rotate_shape (int r_step)
+ {
+ var r = shape.rotation + r_step;
+ if (r < 0)
+ r += 4;
+ if (r >= 4)
+ r -= 4;
+
+ if (r == shape.rotation)
+ return;
+ shape.rotation = r;
+
+ /* Rearrange current blocks */
+ unowned List<Block> b = shape.blocks;
+ var offset = shape.type * 64 + r * 16;
+ for (var x = 0; x < 4; x++)
+ {
+ for (var y = 0; y < 4; y++)
+ {
+ if (block_table[offset + y * 4 + x] != 0)
+ {
+ b.data.x = x;
+ b.data.y = y;
+ b = b.next;
+ }
+ }
+ }
+ }
+}
diff --git a/quadrapassel/src/preview.vala b/quadrapassel/src/preview.vala
new file mode 100644
index 0000000..5fcd81e
--- /dev/null
+++ b/quadrapassel/src/preview.vala
@@ -0,0 +1,111 @@
+public class Preview : GtkClutter.Embed
+{
+ /* Textures used to draw blocks */
+ private BlockTexture[] block_textures;
+
+ /* Clutter representation of a piece */
+ private Clutter.Group? piece = null;
+
+ public string theme
+ {
+ set
+ {
+ foreach (var texture in block_textures)
+ texture.theme = value;
+ update_block ();
+ }
+ }
+
+ private int cell_size
+ {
+ get { return (get_allocated_width () + get_allocated_height ()) / 2 / 5; }
+ }
+
+ private Game? _game = null;
+ public Game? game
+ {
+ get { return _game; }
+ set
+ {
+ if (_game != null)
+ SignalHandler.disconnect_matched (_game, SignalMatchType.DATA, 0, 0, null, null, this);
+ _game = value;
+ _game.shape_added.connect (shape_added_cb);
+ update_block ();
+ }
+ }
+
+ private bool _enabled = true;
+ public bool enabled
+ {
+ get { return _enabled; }
+ set { _enabled = value; update_block (); }
+ }
+
+ public Preview ()
+ {
+ size_allocate.connect (size_allocate_cb);
+
+ /* FIXME: We should scale with the rest of the UI, but that requires
+ * changes to the widget layout - i.e. wrap the preview in an
+ * fixed-aspect box. */
+ set_size_request (120, 120);
+ var stage = (Clutter.Stage) get_stage ();
+
+ Clutter.Color stage_color = { 0x0, 0x0, 0x0, 0xff };
+ stage.set_color (stage_color);
+
+ block_textures = new BlockTexture[NCOLORS];
+ for (var i = 0; i < block_textures.length; i++)
+ {
+ // FIXME: Have to set a size to avoid an assertion in Clutter
+ block_textures[i] = new BlockTexture (i, 1);
+ block_textures[i].hide ();
+ stage.add_actor (block_textures[i]);
+ }
+ }
+
+ private void shape_added_cb ()
+ {
+ update_block ();
+ }
+
+ private void update_block ()
+ {
+ if (piece != null)
+ piece.destroy ();
+
+ if (game == null || game.next_shape == null || !enabled)
+ return;
+
+ piece = new Clutter.Group ();
+ var stage = (Clutter.Stage) get_stage ();
+ stage.add_actor (piece);
+
+ var min_width = 4, max_width = 0, min_height = 4, max_height = 0;
+ foreach (var b in game.next_shape.blocks)
+ {
+ min_width = int.min (b.x, min_width);
+ max_width = int.max (b.x + 1, max_width);
+ min_height = int.min (b.y, min_height);
+ max_height = int.max (b.y + 1, max_height);
+
+ var a = new Clutter.Clone (block_textures[b.color]);
+ a.set_size (cell_size, cell_size);
+ a.set_position (b.x * cell_size, b.y * cell_size);
+ piece.add_actor (a);
+ }
+
+ piece.set_anchor_point ((min_width + max_width) * 0.5f * cell_size, (min_height + max_height) * 0.5f * cell_size);
+ piece.set_position (get_allocated_width () / 2, get_allocated_height () / 2);
+ piece.set_scale (0.6, 0.6);
+ piece.animate (Clutter.AnimationMode.EASE_IN_OUT_SINE, 180, "scale-x", 1.0, "scale-y", 1.0);
+ }
+
+ private void size_allocate_cb (Gtk.Allocation allocation)
+ {
+ foreach (var texture in block_textures)
+ texture.set_size (cell_size, cell_size);
+ update_block ();
+ }
+}
diff --git a/quadrapassel/src/quadrapassel.vala b/quadrapassel/src/quadrapassel.vala
new file mode 100644
index 0000000..824b95f
--- /dev/null
+++ b/quadrapassel/src/quadrapassel.vala
@@ -0,0 +1,696 @@
+public class Quadrapassel
+{
+ /* Application settings */
+ private Settings settings;
+
+ /* Main window */
+ private Gtk.Window main_window;
+
+ /* Game being played */
+ private Game? game = null;
+
+ /* Rendering of game */
+ private GameView view;
+
+ /* Preview of the next shape */
+ private Preview preview;
+
+ /* Label showing current score */
+ private Gtk.Label score_label;
+
+ /* Label showing the number of lines destroyed */
+ private Gtk.Label n_destroyed_label;
+
+ /* Label showing the current level */
+ private Gtk.Label level_label;
+
+ private GnomeGamesSupport.Scores high_scores;
+
+ private GnomeGamesSupport.PauseAction pause_action;
+
+ private Gtk.Dialog preferences_dialog;
+ private Gtk.SpinButton starting_level_spin;
+ private Preview theme_preview;
+ private Gtk.SpinButton fill_height_spinner;
+ private Gtk.SpinButton fill_prob_spinner;
+ private Gtk.CheckButton do_preview_toggle;
+ private Gtk.CheckButton difficult_blocks_toggle;
+ private Gtk.CheckButton rotate_counter_clock_wise_toggle;
+ private Gtk.CheckButton use_target_toggle;
+ private Gtk.CheckButton sound_toggle;
+
+ private const Gtk.ActionEntry actions[] =
+ {
+ { "GameMenu", null, N_("_Game") },
+ { "SettingsMenu", null, N_("_Settings") },
+ { "HelpMenu", null, N_("_Help") },
+ { "NewGame", GnomeGamesSupport.STOCK_NEW_GAME, null, null, null, new_game_cb },
+ { "Scores", GnomeGamesSupport.STOCK_SCORES, null, null, null, scores_cb },
+ { "Quit", Gtk.Stock.QUIT, null, null, null, quit_cb },
+ { "Preferences", Gtk.Stock.PREFERENCES, null, null, null, preferences_cb },
+ { "Contents", GnomeGamesSupport.STOCK_CONTENTS, null, null, null, help_cb },
+ { "About", Gtk.Stock.ABOUT, null, null, null, about_cb }
+ };
+
+ public Quadrapassel ()
+ {
+ var ui_description =
+ "<ui>" +
+ " <menubar name='MainMenu'>" +
+ " <menu action='GameMenu'>" +
+ " <menuitem action='NewGame'/>" +
+ " <menuitem action='_pause'/>" +
+ " <separator/>" +
+ " <menuitem action='Scores'/>" +
+ " <separator/>" +
+ " <menuitem action='Quit'/>" +
+ " </menu>" +
+ " <menu action='SettingsMenu'>" +
+ " <menuitem action='Preferences'/>" +
+ " </menu>" +
+ " <menu action='HelpMenu'>" +
+ " <menuitem action='Contents'/>" +
+ " <menuitem action='About'/>" +
+ " </menu>" +
+ " </menubar>" +
+ "</ui>";
+
+ settings = new Settings ("org.gnome.quadrapassel");
+
+ main_window = new Gtk.Window (Gtk.WindowType.TOPLEVEL);
+ main_window.set_title (_("Quadrapassel"));
+
+ main_window.delete_event.connect (window_delete_event_cb);
+
+ main_window.set_default_size (500, 550);
+ //games_conf_add_window (main_window, KEY_SAVED_GROUP);
+
+ view = new GameView ();
+ view.theme = settings.get_string ("theme");
+ view.mute = !settings.get_boolean ("sound");
+
+ preview = new Preview ();
+ preview.theme = settings.get_string ("theme");
+ preview.enabled = settings.get_boolean ("do-preview");
+
+ /* prepare menus */
+ GnomeGamesSupport.stock_init ();
+ var action_group = new Gtk.ActionGroup ("MenuActions");
+ action_group.set_translation_domain (GETTEXT_PACKAGE);
+ action_group.add_actions (actions, this);
+ var ui_manager = new Gtk.UIManager ();
+ ui_manager.insert_action_group (action_group, 0);
+ try
+ {
+ ui_manager.add_ui_from_string (ui_description, -1);
+ }
+ catch (Error e)
+ {
+ }
+ main_window.add_accel_group (ui_manager.get_accel_group ());
+
+ pause_action = new GnomeGamesSupport.PauseAction ("_pause");
+ pause_action.state_changed.connect (pause_cb);
+ action_group.add_action_with_accel (pause_action, null);
+
+ var menubar = ui_manager.get_widget ("/MainMenu");
+
+ var hb = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+
+ var vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
+ main_window.add (vbox);
+ vbox.pack_start (menubar, false, false, 0);
+ vbox.pack_start (hb, true, true, 0);
+
+ main_window.set_events (main_window.get_events () | Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.KEY_RELEASE_MASK);
+
+ var vb1 = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
+ vb1.set_border_width (10);
+ vb1.pack_start (view, true, true, 0);
+ hb.pack_start (vb1, true, true, 0);
+
+ main_window.key_press_event.connect (key_press_event_cb);
+ main_window.key_release_event.connect (key_release_event_cb);
+
+ var vb2 = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
+ vb2.set_border_width (10);
+ hb.pack_end (vb2, false, false, 0);
+
+ vb2.pack_start (preview, false, false, 0);
+
+ var score_grid = new Gtk.Grid ();
+
+ var label = new Gtk.Label (_("Score:"));
+ label.set_alignment (0.0f, 0.5f);
+ score_grid.attach (label, 0, 0, 1, 1);
+ score_label = new Gtk.Label ("0");
+ score_label.set_alignment (1.0f, 0.5f);
+ score_grid.attach (score_label, 1, 0, 1, 1);
+
+ label = new Gtk.Label (_("Lines:"));
+ label.set_alignment (0.0f, 0.5f);
+ score_grid.attach (label, 0, 1, 1, 1);
+ n_destroyed_label = new Gtk.Label ("0");
+ n_destroyed_label.set_alignment (1.0f, 0.5f);
+ score_grid.attach (n_destroyed_label, 1, 1, 1, 1);
+
+ label = new Gtk.Label (_("Level:"));
+ label.set_alignment (0.0f, 0.5f);
+ score_grid.attach (label, 0, 2, 1, 1);
+ level_label = new Gtk.Label ("0");
+ level_label.set_alignment (1.0f, 0.5f);
+ score_grid.attach (level_label, 1, 2, 1, 1);
+
+ vb2.pack_end (score_grid, true, false, 0);
+
+ high_scores = new GnomeGamesSupport.Scores ("quadrapassel",
+ new GnomeGamesSupport.ScoresCategory[0],
+ null,
+ null,
+ 0,
+ GnomeGamesSupport.ScoreStyle.PLAIN_DESCENDING);
+
+ pause_action.sensitive = false;
+ }
+
+ public void show ()
+ {
+ main_window.show_all ();
+ }
+
+ private void preferences_dialog_close_cb ()
+ {
+ preferences_dialog.destroy ();
+ preferences_dialog = null;
+ }
+
+ private void preferences_dialog_response_cb (int response_id)
+ {
+ preferences_dialog_close_cb ();
+ }
+
+ private void preferences_cb (Gtk.Action action)
+ {
+ if (preferences_dialog != null)
+ {
+ preferences_dialog.present ();
+ return;
+ }
+
+ preferences_dialog = new Gtk.Dialog.with_buttons (_("Quadrapassel Preferences"), main_window, (Gtk.DialogFlags)0, Gtk.Stock.CLOSE, Gtk.ResponseType.CLOSE, null);
+ preferences_dialog.set_border_width (5);
+ var vbox = (Gtk.Box) preferences_dialog.get_content_area ();
+ vbox.set_spacing (2);
+ preferences_dialog.close.connect (preferences_dialog_close_cb);
+ preferences_dialog.response.connect (preferences_dialog_response_cb);
+
+ var notebook = new Gtk.Notebook ();
+ notebook.set_border_width (5);
+ vbox.pack_start (notebook, true, true, 0);
+
+ vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 18);
+ vbox.set_border_width (12);
+ var label = new Gtk.Label (_("Game"));
+ notebook.append_page (vbox, label);
+
+ var frame = new GnomeGamesSupport.Frame (_("Setup"));
+ var grid = new Gtk.Grid ();
+ grid.set_row_spacing (6);
+ grid.set_column_spacing (12);
+
+ /* pre-filled rows */
+ label = new Gtk.Label.with_mnemonic (_("_Number of pre-filled rows:"));
+ label.set_alignment (0, 0.5f);
+ label.set_hexpand (true);
+ grid.attach (label, 0, 0, 1, 1);
+
+ var adj = new Gtk.Adjustment (settings.get_int ("line-fill-height"), 0, game.height - 1, 1, 5, 0);
+ fill_height_spinner = new Gtk.SpinButton (adj, 10, 0);
+ fill_height_spinner.set_update_policy (Gtk.SpinButtonUpdatePolicy.ALWAYS);
+ fill_height_spinner.set_snap_to_ticks (true);
+ fill_height_spinner.value_changed.connect (fill_height_spinner_value_changed_cb);
+ grid.attach (fill_height_spinner, 1, 0, 2, 1);
+ label.set_mnemonic_widget (fill_height_spinner);
+
+ /* pre-filled rows density */
+ label = new Gtk.Label.with_mnemonic (_("_Density of blocks in a pre-filled row:"));
+ label.set_alignment (0, 0.5f);
+ label.set_hexpand (true);
+ grid.attach (label, 0, 1, 1, 1);
+
+ adj = new Gtk.Adjustment (settings.get_int ("line-fill-probability"), 0, 10, 1, 5, 0);
+ fill_prob_spinner = new Gtk.SpinButton (adj, 10, 0);
+ fill_prob_spinner.set_update_policy (Gtk.SpinButtonUpdatePolicy.ALWAYS);
+ fill_prob_spinner.set_snap_to_ticks (true);
+ fill_prob_spinner.value_changed.connect (fill_prob_spinner_value_changed_cb);
+ grid.attach (fill_prob_spinner, 1, 1, 1, 1);
+ label.set_mnemonic_widget (fill_prob_spinner);
+
+ /* starting level */
+ label = new Gtk.Label.with_mnemonic (_("_Starting level:"));
+ label.set_alignment (0, 0.5f);
+ label.set_hexpand (true);
+ grid.attach (label, 0, 2, 1, 1);
+
+ adj = new Gtk.Adjustment (settings.get_int ("starting-level"), 1, 20, 1, 5, 0);
+ starting_level_spin = new Gtk.SpinButton (adj, 10.0, 0);
+ starting_level_spin.set_update_policy (Gtk.SpinButtonUpdatePolicy.ALWAYS);
+ starting_level_spin.set_snap_to_ticks (true);
+ starting_level_spin.value_changed.connect (starting_level_value_changed_cb);
+ grid.attach (starting_level_spin, 1, 2, 1, 1);
+ label.set_mnemonic_widget (starting_level_spin);
+
+ frame.add (grid);
+ vbox.pack_start (frame, false, false, 0);
+
+ frame = new GnomeGamesSupport.Frame (_("Operation"));
+ var fvbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 6);
+
+ sound_toggle = new Gtk.CheckButton.with_mnemonic (_("_Enable sounds"));
+ sound_toggle.set_active (settings.get_boolean ("sound"));
+ sound_toggle.toggled.connect (sound_toggle_toggled_cb);
+ fvbox.pack_start (sound_toggle, false, false, 0);
+
+ do_preview_toggle = new Gtk.CheckButton.with_mnemonic (_("_Preview next block"));
+ do_preview_toggle.set_active (settings.get_boolean ("do-preview"));
+ do_preview_toggle.toggled.connect (do_preview_toggle_toggled_cb);
+ fvbox.pack_start (do_preview_toggle, false, false, 0);
+
+ difficult_blocks_toggle = new Gtk.CheckButton.with_mnemonic (_("Choose difficult _blocks"));
+ difficult_blocks_toggle.set_active (settings.get_boolean ("pick-difficult-blocks"));
+ difficult_blocks_toggle.toggled.connect (difficult_blocks_toggled_cb);
+ fvbox.pack_start (difficult_blocks_toggle, false, false, 0);
+
+ /* rotate counter clock wise */
+ rotate_counter_clock_wise_toggle = new Gtk.CheckButton.with_mnemonic (_("_Rotate blocks counterclockwise"));
+ rotate_counter_clock_wise_toggle.set_active (settings.get_boolean ("rotate-counter-clock-wise"));
+ rotate_counter_clock_wise_toggle.toggled.connect (set_rotate_counter_clock_wise);
+ fvbox.pack_start (rotate_counter_clock_wise_toggle, false, false, 0);
+
+ use_target_toggle = new Gtk.CheckButton.with_mnemonic (_("Show _where the block will land"));
+ fvbox.pack_start (use_target_toggle, false, false, 0);
+
+ frame.add (fvbox);
+ vbox.pack_start (frame, false, false, 0);
+
+ frame = new GnomeGamesSupport.Frame (_("Theme"));
+ grid = new Gtk.Grid ();
+ grid.set_border_width (0);
+ grid.set_row_spacing (6);
+ grid.set_column_spacing (12);
+
+ /* controls page */
+ vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
+ vbox.set_border_width (12);
+ label = new Gtk.Label (_("Controls"));
+ notebook.append_page (vbox, label);
+
+ frame = new GnomeGamesSupport.Frame (_("Keyboard Controls"));
+ vbox.pack_start (frame, true, true, 0);
+
+ fvbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 6);
+ frame.add (fvbox);
+
+ var controls_list = new GnomeGamesSupport.ControlsList (settings);
+ controls_list.add_controls ("key-left", _("Move left"), 0,
+ "key-right", _("Move right"), 0,
+ "key-down", _("Move down"), 0,
+ "key-drop", _("Drop"), 0,
+ "key-rotate", _("Rotate"), 0,
+ "key-pause", _("_pause"), 0,
+ null);
+
+ fvbox.pack_start (controls_list, true, true, 0);
+
+ /* theme page */
+ vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
+ vbox.set_border_width (12);
+ label = new Gtk.Label (_("Theme"));
+ notebook.append_page (vbox, label);
+
+ frame = new GnomeGamesSupport.Frame (_("Block Style"));
+ vbox.pack_start (frame, true, true, 0);
+
+ fvbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 6);
+ frame.add (fvbox);
+
+ var theme_combo = new Gtk.ComboBox ();
+ var theme_store = new Gtk.ListStore (2, typeof (string), typeof (string));
+ theme_combo.model = theme_store;
+ var renderer = new Gtk.CellRendererText ();
+ theme_combo.pack_start (renderer, true);
+ theme_combo.add_attribute (renderer, "text", 0);
+
+ Gtk.TreeIter iter;
+
+ theme_store.append (out iter);
+ theme_store.set (iter, 0, _("Plain"), 1, "plain", -1);
+ if (settings.get_string ("theme") == "plain")
+ theme_combo.set_active_iter (iter);
+
+ theme_store.append (out iter);
+ theme_store.set (iter, 0, _("Tango Flat"), 1, "tangoflat", -1);
+ if (settings.get_string ("theme") == "tangoflat")
+ theme_combo.set_active_iter (iter);
+
+ theme_store.append (out iter);
+ theme_store.set (iter, 0, _("Tango Shaded"), 1, "tangoshaded", -1);
+ if (settings.get_string ("theme") == "tangoshaded")
+ theme_combo.set_active_iter (iter);
+
+ theme_store.append (out iter);
+ theme_store.set (iter, 0, _("Clean"), 1, "clean", -1);
+ if (settings.get_string ("theme") == "clean")
+ theme_combo.set_active_iter (iter);
+
+ theme_combo.changed.connect (theme_combo_changed_cb);
+ fvbox.pack_start (theme_combo, false, false, 0);
+
+ theme_preview = new Preview ();
+ theme_preview.game = new Game ();
+ theme_preview.theme = settings.get_string ("theme");
+ fvbox.pack_start (theme_preview, true, true, 0);
+
+ preferences_dialog.show_all ();
+ }
+
+ private void sound_toggle_toggled_cb ()
+ {
+ var play_sound = sound_toggle.get_active ();
+ settings.set_boolean ("sound", play_sound);
+ view.mute = !play_sound;
+ }
+
+ private void do_preview_toggle_toggled_cb ()
+ {
+ var do_preview = do_preview_toggle.get_active ();
+ settings.set_boolean ("do-preview", do_preview);
+ preview.enabled = do_preview;
+ }
+
+ private void difficult_blocks_toggled_cb ()
+ {
+ settings.set_boolean ("pick-difficult-blocks", difficult_blocks_toggle.get_active ());
+ }
+
+ private void set_rotate_counter_clock_wise ()
+ {
+ settings.set_boolean ("rotate-counter-clock-wise", rotate_counter_clock_wise_toggle.get_active ());
+ }
+
+ private void theme_combo_changed_cb (Gtk.ComboBox widget)
+ {
+ Gtk.TreeIter iter;
+ widget.get_active_iter (out iter);
+ string theme;
+ widget.model.get (iter, 1, out theme);
+ view.theme = theme;
+ preview.theme = theme;
+ if (theme_preview != null)
+ theme_preview.theme = theme;
+ settings.set_string ("theme", theme);
+ }
+
+ private void fill_height_spinner_value_changed_cb (Gtk.SpinButton spin)
+ {
+ int value = spin.get_value_as_int ();
+ settings.set_int ("line-fill-height", value);
+ }
+
+ private void fill_prob_spinner_value_changed_cb (Gtk.SpinButton spin)
+ {
+ int value = spin.get_value_as_int ();
+ settings.set_int ("line-fill-probability", value);
+ }
+
+ private void starting_level_value_changed_cb (Gtk.SpinButton spin)
+ {
+ int value = spin.get_value_as_int ();
+ settings.set_int ("starting-level", value);
+ }
+
+ private void pause_cb ()
+ {
+ if (game != null)
+ game.paused = pause_action.get_is_paused ();
+ }
+
+ private bool window_delete_event_cb (Gtk.Widget window, Gdk.EventAny event)
+ {
+ quit ();
+ return true;
+ }
+
+ private void quit_cb (Gtk.Action action)
+ {
+ quit ();
+ }
+
+ private void quit ()
+ {
+ /* Record the score if the game isn't over. */
+ if (game != null && game.score > 0)
+ high_scores.add_plain_score (game.score);
+
+ Gtk.main_quit ();
+ }
+
+ private bool key_press_event_cb (Gtk.Widget widget, Gdk.EventKey event)
+ {
+ var keyval = upper_key (event.keyval);
+
+ if (game == null)
+ return false;
+
+ if (keyval == upper_key (settings.get_int ("key-pause")))
+ {
+ pause_action.set_is_paused (!pause_action.get_is_paused ());
+ return true;
+ }
+
+ if (game.paused)
+ return false;
+
+ if (keyval == upper_key (settings.get_int ("key-left")))
+ {
+ game.move_left ();
+ return true;
+ }
+ else if (keyval == upper_key (settings.get_int ("key-right")))
+ {
+ game.move_right ();
+ return true;
+ }
+ else if (keyval == upper_key (settings.get_int ("key-rotate")))
+ {
+ if (settings.get_boolean ("rotate-counter-clock-wise"))
+ game.rotate_left ();
+ else
+ game.rotate_right ();
+ return true;
+ }
+ else if (keyval == upper_key (settings.get_int ("key-down")))
+ {
+ game.set_fast_forward (true);
+ return true;
+ }
+ else if (keyval == upper_key (settings.get_int ("key-drop")))
+ {
+ game.drop ();
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool key_release_event_cb (Gtk.Widget widget, Gdk.EventKey event)
+ {
+ var keyval = upper_key (event.keyval);
+
+ if (game == null)
+ return false;
+
+ if (keyval == upper_key (settings.get_int ("key-down")))
+ {
+ game.set_fast_forward (false);
+ return true;
+ }
+
+ return false;
+ }
+
+ private uint upper_key (uint keyval)
+ {
+ if (keyval > 255)
+ return keyval;
+ return ((char) keyval).toupper ();
+ }
+
+ private void new_game_cb (Gtk.Action action)
+ {
+ new_game ();
+ }
+
+ private void new_game ()
+ {
+ if (game != null)
+ {
+ game.stop ();
+ SignalHandler.disconnect_matched (game, SignalMatchType.DATA, 0, 0, null, null, this);
+ }
+
+ game = new Game (20, 14, settings.get_int ("starting-level"), settings.get_int ("line-fill-height"), settings.get_int ("line-fill-probability"), settings.get_boolean ("pick-difficult-blocks"));
+ game.shape_landed.connect (shape_landed_cb);
+ game.complete.connect (complete_cb);
+ preview.game = game;
+ view.game = game;
+
+ game.start ();
+
+ update_score ();
+ pause_action.sensitive = true;
+ }
+
+ private void shape_landed_cb (int[] lines, List<Block> line_blocks)
+ {
+ update_score ();
+ }
+
+ private void complete_cb ()
+ {
+ pause_action.sensitive = false;
+ if (game.score > 0)
+ {
+ var pos = high_scores.add_plain_score (game.score);
+ var dialog = new GnomeGamesSupport.ScoresDialog (main_window, high_scores, _("Quadrapassel Scores"));
+ var title = _("Puzzle solved!");
+ var message = _("You didn't make the top ten, better luck next time.");
+ if (pos == 1)
+ message = _("Your score is the best!");
+ else if (pos > 1)
+ message = _("Your score has made the top ten.");
+ dialog.set_message ("<b>%s</b>\n\n%s".printf (title, message));
+ dialog.set_buttons (GnomeGamesSupport.ScoresButtons.QUIT_BUTTON | GnomeGamesSupport.ScoresButtons.NEW_GAME_BUTTON);
+ if (pos > 0)
+ dialog.set_hilight (pos);
+
+ switch (dialog.run ())
+ {
+ case Gtk.ResponseType.REJECT:
+ Gtk.main_quit ();
+ break;
+ default:
+ new_game ();
+ break;
+ }
+ dialog.destroy ();
+ }
+ }
+
+ private void update_score ()
+ {
+ var score = 0;
+ var level = 0;
+ var n_lines_destroyed = 0;
+
+ if (game != null)
+ {
+ score = game.score;
+ level = game.level;
+ n_lines_destroyed = game.n_lines_destroyed;
+ }
+
+ score_label.set_text ("%d".printf (score));
+ level_label.set_text ("%d".printf (level));
+ n_destroyed_label.set_text ("%d".printf (n_lines_destroyed));
+ }
+
+ private void help_cb (Gtk.Action action)
+ {
+ try
+ {
+ Gtk.show_uri (main_window.get_screen (), "ghelp:quadrapassel", Gtk.get_current_event_time ());
+ }
+ catch (Error e)
+ {
+ warning ("Failed to show help: %s", e.message);
+ }
+ }
+
+ private void about_cb (Gtk.Action action)
+ {
+ string[] authors = { "Gnome Games Team", null };
+ string[] documenters = { "Angela Boyle", null };
+
+ Gtk.show_about_dialog (main_window,
+ "program-name", _("Quadrapassel"),
+ "version", VERSION,
+ "comments", _("A classic game of fitting falling blocks together.\n\nQuadrapassel is a part of GNOME Games."),
+ "copyright", "Copyright \xc2\xa9 1999 J. Marcin Gorycki, 2000-2009 Others",
+ "license", GnomeGamesSupport.get_license (_("Quadrapassel")),
+ "website-label", _("GNOME Games web site"),
+ "authors", authors,
+ "documenters", documenters,
+ "translator-credits", _("translator-credits"),
+ "logo-icon-name", "quadrapassel",
+ "website", "http://wwmain_window.gnome.org/projects/gnome-games/",
+ "wrap-license", true,
+ null);
+ }
+
+ private void scores_cb (Gtk.Action action)
+ {
+ var dialog = new GnomeGamesSupport.ScoresDialog (main_window, high_scores, _("Quadrapassel Scores"));
+ dialog.run ();
+ dialog.destroy ();
+ }
+
+ 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);
+
+ GnomeGamesSupport.scores_startup ();
+
+ var context = new OptionContext ("");
+
+ context.add_group (Gtk.get_option_group (true));
+ context.add_group (Clutter.get_option_group_without_init ());
+
+ try
+ {
+ context.parse (ref args);
+ }
+ catch (Error e)
+ {
+ stderr.printf ("%s\n", e.message);
+ return Posix.EXIT_FAILURE;
+ }
+
+ Environment.set_application_name (_("Quadrapassel"));
+
+ Gtk.Window.set_default_icon_name ("quadrapassel");
+
+ try
+ {
+ GtkClutter.init_with_args (ref args, "", new OptionEntry[0], null);
+ }
+ catch (Error e)
+ {
+ var dialog = new Gtk.MessageDialog (null, Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR, Gtk.ButtonsType.NONE, "Unable to initialize Clutter:\n%s", e.message);
+ dialog.set_title (Environment.get_application_name ());
+ dialog.run ();
+ dialog.destroy ();
+ return Posix.EXIT_FAILURE;
+ }
+
+ var app = new Quadrapassel ();
+ app.show ();
+
+ Gtk.main ();
+
+ return Posix.EXIT_SUCCESS;
+ }
+}
diff --git a/sounds/Makefile.am b/sounds/Makefile.am
index ca51827..39c5515 100644
--- a/sounds/Makefile.am
+++ b/sounds/Makefile.am
@@ -9,20 +9,15 @@ sound_DATA = \
die.ogg \
flip-piece.ogg \
gameover.ogg \
- gnometris.ogg \
gobble.ogg \
land.ogg \
laughter.ogg \
life.ogg \
- lines1.ogg \
- lines2.ogg \
- lines3.ogg \
pop.ogg \
reverse.ogg \
slide.ogg \
splat.ogg \
teleport.ogg \
- turn.ogg \
victory.ogg \
yahoo.ogg
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]