[gnome-games] iagno: Port from C to Vala



commit e9e8ae3e5b573cba9f535b5dceaf7bf98eabd126
Author: Robert Ancell <robert ancell canonical com>
Date:   Thu Dec 22 18:26:15 2011 +1100

    iagno: Port from C to Vala

 configure.in                                |    4 +-
 iagno/data/org.gnome.iagno.gschema.xml.in   |    7 -
 iagno/src/Makefile.am                       |   31 +-
 iagno/src/computer-player.vala              |  264 ++++++++++++
 iagno/src/config.vapi                       |    2 +
 iagno/src/game-view.vala                    |  239 +++++++++++
 iagno/src/game.vala                         |  282 +++++++++++++
 iagno/src/iagno.vala                        |  591 +++++++++++++++++++++++++++
 libgames-support/GnomeGamesSupport-1.0.vapi |   22 +
 po/POTFILES.in                              |    4 +-
 10 files changed, 1419 insertions(+), 27 deletions(-)
---
diff --git a/configure.in b/configure.in
index 7ea6874..8d0d9d3 100644
--- a/configure.in
+++ b/configure.in
@@ -125,7 +125,7 @@ for game in $gamelist; do
     *) ;;
   esac
   case $game in
-    glchess|gnomine|gnotravex|lightsoff|mahjongg) need_vala=yes ;;
+    glchess|gnomine|gnotravex|iagno|lightsoff|mahjongg) need_vala=yes ;;
     *) ;;
   esac
   case $game in
@@ -153,7 +153,7 @@ for game in $gamelist; do
     *) ;;
   esac
   case $game in
-    glines|gnobots2|gnotski|iagno) allow_smclient=yes ;;
+    glines|gnobots2|gnotski) allow_smclient=yes ;;
     *) ;;
   esac
   case $game in
diff --git a/iagno/data/org.gnome.iagno.gschema.xml.in b/iagno/data/org.gnome.iagno.gschema.xml.in
index 7ab3f2f..13a674b 100644
--- a/iagno/data/org.gnome.iagno.gschema.xml.in
+++ b/iagno/data/org.gnome.iagno.gschema.xml.in
@@ -14,13 +14,6 @@
     <key name="tileset" type="s">
       <default>'classic.png'</default>
     </key>
-    <key name="animate" type="i">
-      <default>2</default>
-      <range min="0" max="2" />
-    </key>
-    <key name="animate-stagger" type="b">
-      <default>false</default>
-    </key>
     <key name="show-grid" type="b">
       <default>false</default>
     </key>
diff --git a/iagno/src/Makefile.am b/iagno/src/Makefile.am
index 9473f10..95a3133 100644
--- a/iagno/src/Makefile.am
+++ b/iagno/src/Makefile.am
@@ -1,26 +1,27 @@
 bin_PROGRAMS = iagno
 
 iagno_SOURCES = \
-	gnothello.c \
-	gnothello.h \
-	othello.c \
-	othello.h \
-	properties.c \
-	properties.h \
-	$(NULL)
-
-iagno_CPPFLAGS = \
-	-I$(top_srcdir) \
-	$(AM_CPPFLAGS)
+	config.vapi \
+	computer-player.vala \
+	game.vala \
+	game-view.vala \
+	iagno.vala
 
 iagno_CFLAGS = \
-	$(GTK_CFLAGS) \
-	$(AM_CFLAGS)
+	-I$(top_srcdir)/libgames-support \
+	-DVERSION=\"$(VERSION)\" \
+	-DGETTEXT_PACKAGE=\"$(GETTEXT_PACKAGE)\" \
+	$(GTK_CFLAGS)
+
+iagno_VALAFLAGS = \
+	--pkg posix \
+	--pkg gtk+-3.0 \
+	--vapidir $(top_srcdir)/libgames-support \
+	--pkg GnomeGamesSupport-1.0
 
 iagno_LDADD = \
 	$(top_builddir)/libgames-support/libgames-support.la \
-	$(GTK_LIBS) \
-	$(INTLLIBS)
+	$(GTK_LIBS)
 
 if HAVE_GNOME
 iagno_CFLAGS += $(GNOME_CFLAGS)
diff --git a/iagno/src/computer-player.vala b/iagno/src/computer-player.vala
new file mode 100644
index 0000000..4bfc024
--- /dev/null
+++ b/iagno/src/computer-player.vala
@@ -0,0 +1,264 @@
+private enum Strategy
+{
+    PERFECT,
+    VICTORY,
+    BEST
+}
+
+private struct PossibleMove
+{
+    int x;
+    int y;
+    int n_tiles;
+}
+
+public class ComputerPlayer
+{
+    /* Game being played */
+    private Game game;
+
+    /* Strength */
+    private int level;
+
+    /* Value of owning each location */
+    private const int[] heuristic =
+    {
+        65,  -3, 6, 4, 4, 6,  -3, 65,
+        -3, -29, 3, 1, 1, 3, -29, -3,
+         6,   3, 5, 3, 3, 5,   3,  6,
+         4,   1, 3, 1, 1, 3,   1,  4,
+         4,   1, 3, 1, 1, 3,   1,  4,
+         6,   3, 5, 3, 3, 5,   3,  6,
+        -3, -29, 3, 1, 1, 3, -29, -3,
+        65,  -3, 6, 4, 4, 6,  -3, 65
+    };
+
+    public ComputerPlayer (Game game, int level)
+    {
+        this.game = game;
+        this.level = level;
+    }
+
+    public void move ()
+    {
+        /* For the first two moves play randomly so the game is not always the same */
+        if (game.n_tiles < 8)
+        {
+            int x, y;
+            random_select (out x, out y);
+            game.place_tile (x, y);
+            return;
+        }
+
+        /* Choose a strategy based on how close to the end we are.
+         * At the end of the game try and maximise the number of tokens.
+         * Near the end try and push for a win.
+         * For the rest of the game try and maximise everything.
+         */
+        var depth = 64 - game.n_tiles;
+        var strategy = Strategy.BEST;
+        if (depth <= 17 - (3 - level) * 2)
+            strategy = Strategy.PERFECT;
+        else if (depth <= 19 - (3 - level) * 2)
+            strategy = Strategy.VICTORY;
+        else
+            depth = 7 - (3 - level) * 2;
+
+        /* Choose a location to place by building the tree of possible moves and
+         * using the minimax algorithm to pick the best branch with the chosen
+         * strategy. */
+        int x = 0, y = 0;
+        search (new Game.copy (game), strategy, depth, int.MIN, int.MAX, 1, ref x, ref y);
+        if (game.place_tile (x, y) == 0)
+            warning ("Computer chose an invalid move: %d,%d", x, y);
+    }
+
+    private int search (Game g, Strategy strategy, int depth, int a, int b, int p, ref int move_x, ref int move_y)
+    {
+        /* If the end of the search depth or end of the game calculate how good a result this is */
+        if (depth == 0 || g.is_complete)
+            return calculate_heuristic (g, strategy);
+
+        /* Find all possible moves and sort from most new tiles to least new tiles */
+        List<PossibleMove?> moves = null;
+        for (var x = 0; x < 8; x++)
+        {
+            for (var y = 0; y < 8; y++)
+            {
+                var n_tiles = g.place_tile (x, y);
+                if (n_tiles > 0)
+                {
+                    var move = PossibleMove ();
+                    move.x = x;
+                    move.y = y;
+                    //warning ("%d %d", x, y);
+                    move.n_tiles = n_tiles;
+                    moves.insert_sorted (move, compare_move);
+                    g.undo ();
+                }
+            }
+        }
+
+        /* If no moves then pass */
+        if (moves == null)
+        {
+            var move = PossibleMove ();
+            move.x = 0;
+            move.y = 0;
+            move.n_tiles = 0;
+            moves.append (move);
+        }
+
+        /* Try each move using alpha-beta pruning to optimise finding the best branch */
+        foreach (var move in moves)
+        {
+            if (move.n_tiles == 0)
+                g.pass ();
+            else if (g.place_tile (move.x, move.y) == 0)
+            {
+                warning ("Computer marked move (depth %d, %d,%d, %d flips) as valid, but is invalid when checking", depth, move.x, move.y, move.n_tiles);
+                continue;
+            }
+
+            /* If our move then maximise the result */
+            if (p > 0)
+            {
+                int next_x_move = 0, next_y_move = 0;
+                var a_new = search (g, strategy, depth - 1, a, b, -p, ref next_x_move, ref next_y_move);
+                if (a_new > a)
+                {
+                    a = a_new;
+                    move_x = move.x;
+                    move_y = move.y;
+                }
+            }
+            /* If enemy move then minimise the result */
+            else
+            {
+                int next_x_move = 0, next_y_move = 0;
+                var b_new = search (g, strategy, depth - 1, a, b, -p, ref next_x_move, ref next_y_move);
+                if (b_new < b)
+                {
+                    b = b_new;
+                    move_x = move.x;
+                    move_y = move.y;
+                }
+            }
+
+            g.undo ();
+
+            /* This branch has worse values, so ignore it */
+            if (b <= a)
+                break;
+        }
+
+        if (p > 0)
+            return a;
+        else
+            return b;
+    }
+
+    private static int compare_move (PossibleMove? a, PossibleMove? b)
+    {
+        return b.n_tiles - a.n_tiles;
+    }
+
+    private int calculate_heuristic (Game g, Strategy strategy)
+    {
+        var tile_difference = g.n_dark_tiles - g.n_light_tiles;
+        if (g.current_color == Player.DARK)
+            tile_difference = -tile_difference;
+
+        switch (strategy)
+        {
+        /* Maximise the number of tokens */
+        case Strategy.PERFECT:
+            return tile_difference;
+
+        /* Maximise a win over a loss */
+        case Strategy.VICTORY:
+            return tile_difference.clamp (-1, 1);
+
+        /* Try to maximise a number of values */
+        default:
+            return tile_difference + around () + eval_heuristic ();
+        }
+    }
+
+    private int eval_heuristic ()
+    {
+        var count = 0;
+        for (var x = 0; x < 8; x++)
+        {
+            for (var y = 0; y < 8; y++)
+            {
+                var h = heuristic[y * 8 + x];
+                if (game.get_owner (x, y) != game.current_color)
+                    h = -h;
+                count += h;
+            }
+        }
+
+        return count;
+    }
+
+    private int around ()
+    {
+        var count = 0;
+        for (var x = 0; x < 8; x++)
+        {
+            for (var y = 0; y < 8; y++)
+            {
+                var a = 0;
+                a += is_empty (x + 1, y);
+                a += is_empty (x + 1, y + 1);
+                a += is_empty (x, y + 1);
+                a += is_empty (x - 1, y + 1);
+                a += is_empty (x - 1, y);
+                a += is_empty (x - 1, y - 1);
+                a += is_empty (x, y - 1);
+                a += is_empty (x + 1, y - 1);
+
+                /* Two points for completely surrounded tiles */
+                if (a == 0)
+                    a = 2;
+
+                if (game.get_owner (x, y) != game.current_color)
+                    a = -a;
+                count += a;
+            }
+        }
+
+        return count;
+    }
+
+    private int is_empty (int x, int y)
+    {
+        if (x < 0 || x >= 8 || y < 0 || y >= 8 || game.get_owner (x, y) != Player.NONE)
+            return 0;
+
+        return 1;
+    }
+
+    private void random_select (out int move_x, out int move_y)
+    {
+        List<int> moves = null;
+        for (var x = 0; x < 8; x++)
+        {
+            for (var y = 0; y < 8; y++)
+            {
+                if (game.can_place (x, y))
+                    moves.append (x * 8 + y);
+            }
+        }
+        if (moves != null)
+        {
+            var i = Random.int_range (0, (int) moves.length ());
+            var xy = moves.nth_data (i);
+            move_x = xy / 8;
+            move_y = xy % 8;
+        }
+        else
+            move_x = move_y = 0;
+    }
+}
diff --git a/iagno/src/config.vapi b/iagno/src/config.vapi
new file mode 100644
index 0000000..6477226
--- /dev/null
+++ b/iagno/src/config.vapi
@@ -0,0 +1,2 @@
+public const string VERSION;
+public const string GETTEXT_PACKAGE;
\ No newline at end of file
diff --git a/iagno/src/game-view.vala b/iagno/src/game-view.vala
new file mode 100644
index 0000000..8be528d
--- /dev/null
+++ b/iagno/src/game-view.vala
@@ -0,0 +1,239 @@
+public class GameView : Gtk.DrawingArea
+{
+    private const int GRIDWIDTH = 1;
+    private const int PIXMAP_FLIP_DELAY = 20;
+
+    private uint tile_width = 80;
+    private uint tile_height = 80;
+    private uint board_width = 648;
+    private uint board_height = 648;
+    private double[] dash = {4.0};
+
+    private Cairo.Surface? tiles_surface = null;
+    private Cairo.Surface? background_surface = null;
+
+    private int[,] pixmaps;
+
+    private uint animate_timeout = 0;
+
+    public signal void move (int x, int y);
+
+    public GameView ()
+    {
+        set_events (Gdk.EventMask.EXPOSURE_MASK | Gdk.EventMask.BUTTON_PRESS_MASK);
+        pixmaps = new int[8,8];
+    }
+
+    private Game? _game = null;
+    public Game? game
+    {
+        get { return _game; }
+        set
+        {
+            if (_game != null)
+                SignalHandler.disconnect_by_func (_game, null, this);
+            _game = value;
+            if (_game != null)
+            {
+                _game.square_changed.connect (square_changed_cb);
+                for (var x = 0; x < 8; x++)
+                    for (var y = 0; y < 8; y++)
+                        pixmaps[x, y] = get_pixmap (_game.get_owner (x, y));
+            }
+            redraw ();
+        }
+    }
+
+    private string? _tile_set = null;
+    public string? tile_set
+    {
+        get { return _tile_set; }
+        set { _tile_set = value; tiles_surface = null; redraw (); }
+    }
+
+    private bool _show_grid;
+    public bool show_grid
+    {
+        get { return _show_grid; }
+        set { _show_grid = value; redraw (); }
+    }
+
+    public override bool draw (Cairo.Context cr)
+    {
+        if (game == null)
+            return false;
+
+        if (tiles_surface == null)
+            load_pixmaps ();
+
+        var p = new Cairo.Pattern.for_surface (background_surface);
+        p.set_extend (Cairo.Extend.REPEAT);
+        cr.set_source (p);
+        cr.move_to (0, 0);
+        cr.line_to (0, board_height);
+        cr.line_to (board_width, board_height);
+        cr.line_to (board_width, 0);
+        cr.line_to (0, 0);
+        cr.fill ();
+
+        for (var x = 0; x < 8; x++)
+        {
+            for (var y = 0; y < 8; y++)
+            {
+                var tile_surface_x = x * (int) (tile_width + GRIDWIDTH) - (pixmaps[x, y] % 8) * (int) tile_width;
+                var tile_surface_y = y * (int) (tile_height + GRIDWIDTH) - (pixmaps[x, y] / 8) * (int) tile_height;
+
+                cr.set_source_surface (tiles_surface, tile_surface_x, tile_surface_y);
+                cr.rectangle (x * (tile_width + GRIDWIDTH), y * (tile_height + GRIDWIDTH), tile_width, tile_height);
+                cr.fill ();
+            }
+        }
+
+        if (show_grid)
+        {
+            cr.set_source_rgb (1.0, 1.0, 1.0);
+            cr.set_operator (Cairo.Operator.DIFFERENCE);
+            cr.set_dash (dash, 2.5);
+            cr.set_line_width (GRIDWIDTH);
+            for (var i = 1; i < 8; i++)
+            {
+                cr.move_to (i * board_width / 8 - 0.5, 0);
+                cr.line_to (i * board_width / 8 - 0.5, board_height);
+
+                cr.move_to (0, i * board_height / 8 - 0.5);
+                cr.line_to (board_width, i * board_height / 8 - 0.5);
+            }
+
+            cr.stroke ();
+        }
+
+        return false;
+    }
+
+    private void load_pixmaps ()
+    {
+        var dname = GnomeGamesSupport.runtime_get_directory (GnomeGamesSupport.RuntimeDirectory.GAME_PIXMAP_DIRECTORY);
+        var fname = Path.build_filename (dname, tile_set);
+
+        /* fall back to default tileset "classic.png" if tile_set not found */
+        if (!FileUtils.test (fname, FileTest.EXISTS | FileTest.IS_REGULAR))
+            fname = Path.build_filename (dname, "classic.png");
+
+        if (!FileUtils.test (fname, FileTest.EXISTS | FileTest.IS_REGULAR))
+        {
+            stderr.printf (_("Could not find \'%s\' pixmap file\n"), fname);
+            Posix.exit (Posix.EXIT_FAILURE);
+        }
+
+        Gdk.Pixbuf image;
+        try
+        {
+            image = new Gdk.Pixbuf.from_file (fname);
+        }
+        catch (Error e)
+        {
+            warning ("gdk-pixbuf error %s\n", e.message);
+            return;
+        }
+
+        tile_width = image.get_width () / 8;
+        tile_height = image.get_height () / 4;
+
+        /* Make sure the dash width evenly subdivides the tile height, and is at least 4 pixels long.
+         * This makes the dash crossings always cross in the same place, which looks nicer. */
+        var dash_count = (tile_height + GRIDWIDTH) / 4;
+        if (dash_count % 2 != 0)
+            dash_count--;
+        dash[0] = ((double)(tile_height + GRIDWIDTH)) / dash_count;
+
+        board_width = (tile_width + GRIDWIDTH) * 8;
+        board_height = (tile_height + GRIDWIDTH) * 8;
+        set_size_request ((int) board_width, (int) board_height);
+
+        tiles_surface = get_window ().create_similar_surface (Cairo.Content.COLOR_ALPHA, image.get_width (), image.get_height ());
+        var cr = new Cairo.Context (tiles_surface);
+        Gdk.cairo_set_source_pixbuf (cr, image, 0, 0);
+        cr.paint ();
+
+        background_surface = get_window ().create_similar_surface (Cairo.Content.COLOR_ALPHA, 1, 1);
+        cr = new Cairo.Context (background_surface);
+        Gdk.cairo_set_source_pixbuf (cr, image, 0, 0);
+        cr.paint ();
+    }
+
+    public void redraw ()
+    {
+        queue_draw_area (0, 0, (int) board_width, (int) board_height);
+    }
+
+    private void square_changed_cb (int x, int y)
+    {
+        var target = get_pixmap (game.get_owner (x, y));
+
+        if (pixmaps[x, y] == target)
+            return;
+
+        if (target == 0 || pixmaps[x, y] == 0)
+            pixmaps[x, y] = target;
+        else
+        {
+            if (target > pixmaps[x, y])
+                pixmaps[x, y]++;
+            else
+                pixmaps[x, y]--;
+            if (animate_timeout == 0)
+                animate_timeout = Timeout.add (PIXMAP_FLIP_DELAY, animate_cb);
+        }
+        queue_draw_area (x * (int) (tile_width + GRIDWIDTH), y * (int) (tile_height + GRIDWIDTH), (int) tile_width, (int) tile_height);
+    }
+
+    private bool animate_cb ()
+    {
+        var animating = false;
+
+        for (var x = 0; x < 8; x++)
+        {
+            for (var y = 0; y < 8; y++)
+            {
+                var old = pixmaps[x, y];
+                square_changed_cb (x, y);
+                if (pixmaps[x, y] != old)
+                    animating = true;
+            }
+        }
+
+        if (!animating)
+        {
+            animate_timeout = 0;
+            return false;
+        }
+
+        return true;
+    }
+
+    private int get_pixmap (Player color)
+    {
+        switch (color)
+        {
+        default:
+        case Player.NONE:
+            return 0;
+        case Player.DARK:
+            return 1;
+        case Player.LIGHT:
+            return 31;
+        }
+    }
+
+    public override bool button_press_event (Gdk.EventButton event)
+    {
+        if (event.button == 1)
+        {
+            var x = (int) event.x / (int) (tile_width + GRIDWIDTH);
+            var y = (int) event.y / (int) (tile_height + GRIDWIDTH);
+            move (x, y);
+        }
+
+        return true;
+    }
+}
diff --git a/iagno/src/game.vala b/iagno/src/game.vala
new file mode 100644
index 0000000..b38a6e1
--- /dev/null
+++ b/iagno/src/game.vala
@@ -0,0 +1,282 @@
+public enum Player
+{
+    NONE,
+    LIGHT,
+    DARK
+}
+
+public class Game
+{
+    /* Tiles on the board */
+    public Player[,] tiles;
+
+    /* Undo stack.  This is a record of all the tile changes since the start of the game
+     * in the binary form ccxxxyyy where cc is the color (0-2), xxx is the x location (0-7)
+     * and yyy is the y location (0-7).  Each set of changes is followed by the number of changes
+     * preceeding.  This array is oversized, but big enough for the (impossible) worst case of
+     * each move flipping 20 tiles. */
+    private int undo_history[1344];
+    private int undo_index = 0;
+
+    /* Color to move next */
+    public Player current_color;
+
+    /* Indicate that a player should move */
+    public signal void move ();
+
+    /* Indicate a square has changed */
+    public signal void square_changed (int x, int y);
+
+    /* Indicate the game is complete */
+    public signal void complete ();
+
+    /* The number of tiles on the board */
+    public int n_tiles
+    {
+        get
+        {
+            var count = 0;
+            for (var x = 0; x < 8; x++)
+            {
+                for (var y = 0; y < 8; y++)
+                {
+                    if (tiles[x, y] != Player.NONE)
+                        count++;
+                }
+            }
+            return count;
+        }
+    }
+
+    public int n_light_tiles
+    {
+        get { return count_tiles (Player.LIGHT); }
+    }
+
+    public int n_dark_tiles
+    {
+        get { return count_tiles (Player.DARK); }
+    }
+
+    public bool can_move
+    {
+        get
+        {
+            for (var x = 0; x < 8; x++)
+                for (var y = 0; y < 8; y++)
+                    if (can_place (x, y))
+                        return true;
+            return false;
+        }
+    }
+
+    public bool is_complete
+    {
+        get { return n_tiles == 64 || n_light_tiles == 0 || n_dark_tiles == 0; }
+    }
+
+    public Game ()
+    {
+        /* Setup board with four tiles by default */
+        tiles = new Player[8, 8];
+        for (var x = 0; x < 8; x++)
+            for (var y = 0; y < 8; y++)
+                tiles[x, y] = Player.NONE;
+        set_tile (3, 3, Player.LIGHT, false);
+        set_tile (3, 4, Player.DARK, false);
+        set_tile (4, 3, Player.DARK, false);
+        set_tile (4, 4, Player.LIGHT, false);
+
+        /* Black plays first */
+        current_color = Player.DARK;
+    }
+
+    public void start ()
+    {
+        if (n_tiles != 4)
+            return;
+
+        move ();
+    }
+
+    public Game.copy (Game game)
+    {
+        tiles = new Player[8, 8];
+        for (var x = 0; x < 8; x++)
+            for (var y = 0; y < 8; y++)
+                tiles[x, y] = game.tiles[x, y];
+        for (var i = 0; i < game.undo_index; i++)
+            undo_history[i] = game.undo_history[i];
+        undo_index = game.undo_index;
+        current_color = game.current_color;
+    }
+
+    public Player get_owner (int x, int y)
+    {
+        if (is_valid_location (x, y))
+            return tiles[x, y];
+        else
+            return Player.NONE;
+    }
+
+    public bool can_place (int x, int y)
+    {
+        return place (x, y, current_color, false) > 0;
+    }
+
+    public int place_tile (int x, int y)
+    {
+        var n_tiles = place (x, y, current_color, true);
+        if (n_tiles == 0)
+            return 0;
+
+        /* Move to the next player */
+        if (current_color == Player.LIGHT)
+            current_color = Player.DARK;
+        else
+            current_color = Player.LIGHT;
+
+        if (is_complete)
+            complete ();
+        else
+            move ();
+
+        return n_tiles;
+    }
+
+    public void pass ()
+    {
+        undo_history[undo_index] = 0;
+        undo_index++;
+
+        if (current_color == Player.LIGHT)
+            current_color = Player.DARK;
+        else
+            current_color = Player.LIGHT;
+
+        move ();
+    }
+
+    private int place (int x, int y, Player color, bool apply)
+    {
+        /* Square needs to be empty */
+        if (!is_valid_location (x, y) || tiles[x, y] != Player.NONE)
+            return 0;
+
+        var n_flips = 0;
+        n_flips += flip_tiles (x, y, 1, 0, color, apply);
+        n_flips += flip_tiles (x, y, 1, 1, color, apply);
+        n_flips += flip_tiles (x, y, 0, 1, color, apply);
+        n_flips += flip_tiles (x, y, -1, 1, color, apply);
+        n_flips += flip_tiles (x, y, -1, 0, color, apply);
+        n_flips += flip_tiles (x, y, -1, -1, color, apply);
+        n_flips += flip_tiles (x, y, 0, -1, color, apply);
+        n_flips += flip_tiles (x, y, 1, -1, color, apply);
+
+        /* Store the number of entries in the undo history */
+        if (apply && n_flips > 0)
+        {
+            undo_history[undo_index] = n_flips + 1;
+            undo_index++;
+        }
+
+        return n_flips;
+    }
+
+    private int count_tiles (Player color)
+    {
+        var count = 0;
+        for (var x = 0; x < 8; x++)
+            for (var y = 0; y < 8; y++)
+                if (tiles[x, y] == color)
+                    count++;
+        return count;
+    }
+
+    private bool is_valid_location (int x, int y)
+    {
+        return x >= 0 && x < 8 && y >= 0 && y < 8;
+    }
+
+    private int flip_tiles (int x, int y, int x_step, int y_step, Player color, bool apply)
+    {
+        var enemy = Player.LIGHT;
+        if (color == Player.LIGHT)
+            enemy = Player.DARK;
+
+        /* Count number of enemy pieces we are beside */
+        var enemy_count = 0;
+        var xt = x + x_step;
+        var yt = y + y_step;
+        while (is_valid_location (xt, yt))
+        {
+            if (tiles[xt, yt] != enemy)
+                break;
+            enemy_count++;
+            xt += x_step;
+            yt += y_step;
+        }
+
+        /* Must be a line of enemy pieces then one of ours */
+        if (enemy_count == 0 || !is_valid_location (xt, yt) || tiles[xt, yt] != color)
+            return 0;
+
+        /* Place this tile and flip the adjacent ones */
+        if (apply)
+            for (var i = 0; i <= enemy_count; i++)
+                set_tile (x + i * x_step, y + i * y_step, color, true);
+
+        return enemy_count;
+    }
+
+    public bool can_undo
+    {
+        get { return undo_index > 0; }
+    }
+
+    public void undo (int count = 1)
+    {
+        if (count < 1)
+            return;
+
+        for (var i = 0; i < count; i++)
+        {
+            var n_changes = undo_history[undo_index - 1];
+            undo_index--;
+
+            /* Undo each tile change */
+            for (var j = 0; j < n_changes; j++)
+            {
+                var n = undo_history[undo_index - 1];
+                undo_index--;
+                var c = (Player) (n >> 6);
+                var xy = n & 0x3F;
+                set_tile (xy / 8, xy % 8, c, false);
+            }
+
+            /* Previous player to move again */
+            if (current_color == Player.LIGHT)
+                current_color = Player.DARK;
+            else
+                current_color = Player.LIGHT;
+        }
+
+        move ();
+    }
+
+    private void set_tile (int x, int y, Player color, bool update_history)
+    {
+        if (tiles[x, y] == color)
+            return;
+
+        /* Store the old color in the history */
+        if (update_history)
+        {
+            undo_history[undo_index] = ((int) tiles[x, y] << 6) | (x * 8 + y);
+            undo_index++;
+        }
+
+        tiles[x, y] = color;
+        square_changed (x, y);
+    }
+}
diff --git a/iagno/src/iagno.vala b/iagno/src/iagno.vala
new file mode 100644
index 0000000..44a8c82
--- /dev/null
+++ b/iagno/src/iagno.vala
@@ -0,0 +1,591 @@
+public class Iagno
+{
+    /* Application settings */
+    private Settings settings;
+
+    /* Widgets */
+    private Gtk.Window window;
+    private Gtk.Statusbar statusbar;
+    private uint statusbar_id;
+    private GameView view;
+    private Gtk.Label dark_score_label;
+    private Gtk.Label light_score_label;
+    private Gtk.Action new_game_action;
+    private Gtk.Action undo_action;
+
+    /* Light computer player (if there is one) */
+    private ComputerPlayer? light_computer = null;
+
+    /* Dark computer player (if there is one) */
+    private ComputerPlayer? dark_computer = null;
+
+    /* Timer to delay computer moves */
+    private uint computer_timer = 0;
+
+    /* The game being played */
+    private Game? game = null;
+    
+    /* true if the last move was a pass */
+    private bool was_pass = false;
+
+    /* Possible themes */
+    private GnomeGamesSupport.FileList? theme_file_list = null;
+
+    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},
+        {"UndoMove", GnomeGamesSupport.STOCK_UNDO_MOVE, null, null, null, undo_move_cb},
+        {"Quit", Gtk.Stock.QUIT, null, null, null, quit_game_cb},
+        {"Preferences", Gtk.Stock.PREFERENCES, null, null, null, properties_cb},
+        {"Contents", GnomeGamesSupport.STOCK_CONTENTS, null, null, null, help_cb},
+        {"About", Gtk.Stock.ABOUT, null, null, null, about_cb}
+    };
+
+    private string ui_description =
+        "<ui>" +
+        "  <menubar name='MainMenu'>" +
+        "    <menu action='GameMenu'>" +
+        "      <menuitem action='NewGame'/>" +
+        "      <separator/>" +
+        "      <menuitem action='UndoMove'/>" +
+        "      <separator/>" +
+        "      <menuitem action='Quit'/>" +
+        "    </menu>" +
+        "    <menu action='SettingsMenu'>" +
+        "      <menuitem action='Preferences'/>" +
+        "    </menu>" +
+        "    <menu action='HelpMenu'>" +
+        "      <menuitem action='Contents'/>" +
+        "      <menuitem action='About'/>" +
+        "    </menu>" +
+        "  </menubar>" +
+        "</ui>";
+
+    public Iagno ()
+    {
+        settings = new Settings ("org.gnome.iagno");
+
+        window = new Gtk.Window (Gtk.WindowType.TOPLEVEL);
+        window.set_title (_("Iagno"));
+
+        GnomeGamesSupport.settings_bind_window_state ("/org/gnome/iagno/", window);
+
+        var vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
+        vbox.show ();
+        window.add (vbox);
+
+        var ui_manager = new Gtk.UIManager ();
+        var action_group = new Gtk.ActionGroup ("group");
+
+        action_group.set_translation_domain (GETTEXT_PACKAGE);
+        action_group.add_actions (actions, this);
+
+        ui_manager.insert_action_group (action_group, 0);
+        try
+        {
+            ui_manager.add_ui_from_string (ui_description, -1);
+        }
+        catch (Error e)
+        {
+            warning ("Failed to load UI: %s", e.message);
+        }
+
+        window.add_accel_group (ui_manager.get_accel_group ());
+
+        new_game_action = action_group.get_action ("NewGame");
+        undo_action = action_group.get_action ("UndoMove");
+        undo_action.set_sensitive (false);
+        var menubar = (Gtk.MenuBar) ui_manager.get_widget ("/MainMenu");
+        vbox.pack_start (menubar, false, false, 0);
+
+        var notebook = new Gtk.Notebook ();
+        notebook.show ();
+        notebook.set_show_tabs (false);
+        notebook.set_show_border (false);
+
+        window.delete_event.connect (window_delete_event_cb);
+
+        view = new GameView ();
+        view.game = game;
+        view.move.connect (player_move_cb);
+        view.show_grid = settings.get_boolean ("show-grid");
+        view.tile_set = settings.get_string ("tileset");
+        view.show ();
+
+        notebook.append_page (view, null);
+        notebook.set_current_page (0);
+        vbox.pack_start (notebook, false, false, 0);
+
+        statusbar = new Gtk.Statusbar ();
+        statusbar.show ();
+        vbox.pack_start (statusbar, false, false, 0);
+
+        var grid = new Gtk.Grid ();
+        grid.set_column_spacing (6);
+        grid.show ();
+        statusbar.pack_start (grid, false, true, 0);
+
+        var label = new Gtk.Label (_("Dark:"));
+        label.show ();
+        grid.attach (label, 1, 0, 1, 1);
+
+        dark_score_label = new Gtk.Label ("00");
+        dark_score_label.show ();
+        grid.attach (dark_score_label, 2, 0, 1, 1);
+
+        label = new Gtk.Label (_("Light:"));
+        label.show ();
+        grid.attach (label, 4, 0, 1, 1);
+
+        light_score_label = new Gtk.Label ("00");
+        light_score_label.show ();
+        grid.attach (light_score_label, 5, 0, 1, 1);
+
+        window.set_resizable (false);
+
+        statusbar_id = statusbar.get_context_id ("iagno");
+
+        GnomeGamesSupport.sound_enable (settings.get_boolean ("sound"));
+
+        start_game ();
+    }
+
+    private void show ()
+    {
+        window.show ();
+    }
+
+    private void quit_game_cb ()
+    {
+        Gtk.main_quit ();
+    }
+
+    private bool window_delete_event_cb (Gtk.Widget widget, Gdk.EventAny event)
+    {
+        Gtk.main_quit ();
+        return true;
+    }
+
+    private void new_game_cb ()
+    {
+        start_game ();
+    }
+
+    private void start_game ()
+    {
+        /* Cancel any pending computer moves */
+        if (computer_timer != 0)
+        {
+            Source.remove (computer_timer);
+            computer_timer = 0;
+        }
+
+        if (game != null)
+            SignalHandler.disconnect_by_func (game, null, this);
+
+        game = new Game ();
+        game.move.connect (game_move_cb);
+        game.complete.connect (game_complete_cb);
+        view.game = game;
+        
+        var dark_level = settings.get_int ("black-level");
+        if (dark_level > 0)
+            dark_computer = new ComputerPlayer (game, dark_level);
+        else
+            dark_computer = null;
+        var light_level = settings.get_int ("white-level");
+        if (light_level > 0)
+            light_computer = new ComputerPlayer (game, light_level);
+        else
+            light_computer = null;
+
+        update_ui ();
+
+        game.start ();
+    }
+    
+    private void update_ui ()
+    {
+        /* Can't undo when running two computer players */
+        if (light_computer != null && dark_computer != null)
+            undo_action.set_sensitive (false);
+        else
+            undo_action.set_sensitive (game.can_undo);
+
+        dark_score_label.set_text (_("%.2d").printf (game.n_dark_tiles));
+        light_score_label.set_text (_("%.2d").printf (game.n_light_tiles));
+
+        if (was_pass)
+        {
+            if (game.current_color == Player.DARK)
+                show_message (_("Light must pass, Dark's move"));
+            else
+                show_message (_("Dark must pass, Light's move"));
+        }
+        else
+        {
+            if (game.current_color == Player.DARK)
+                show_message (_("Dark's move"));
+            else if (game.current_color == Player.LIGHT)
+                show_message (_("Light's move"));
+        }
+    }
+
+    private void undo_move_cb ()
+    {
+        /* Cancel any pending computer moves */
+        if (computer_timer != 0)
+        {
+            Source.remove (computer_timer);
+            computer_timer = 0;
+        }
+
+        /* Undo once if the human player just moved, otherwise undo both moves */
+        if ((game.current_color == Player.DARK && dark_computer != null) ||
+            (game.current_color == Player.LIGHT && light_computer != null))
+            game.undo (1);
+        else
+            game.undo (2);
+    }
+
+    private void about_cb (Gtk.Action action)
+    {
+        string[] authors = { "Ian Peters", "Robert Ancell", null };
+        string[] documenters = { "Eric Baudais", null };
+
+        Gtk.show_about_dialog (window,
+                               "name", _("Iagno"),
+                               "version", VERSION,
+                               "copyright",
+                               "Copyright \xc2\xa9 1998-2008 Ian Peters",
+                               "license", GnomeGamesSupport.get_license (_("Iagno")),
+                               "comments", _("A disk flipping game derived from Reversi.\n\nIagno is a part of GNOME Games."),
+                               "authors", authors,
+                               "documenters", documenters,
+                               "translator-credits", _("translator-credits"),
+                               "logo-icon-name", "gnome-iagno",
+                               "website-label", _("GNOME Games web site"),
+                               "website", "http://www.gnome.org/projects/gnome-games/";,
+                               "wrap-license", true,
+                               null);
+    }
+
+    private void properties_cb ()
+    {
+        show_properties_dialog ();
+    }
+
+    private void show_message (string message)
+    {
+        statusbar.pop (statusbar_id);
+        statusbar.push (statusbar_id, message);
+    }
+
+    private void help_cb (Gtk.Action action)
+    {
+        GnomeGamesSupport.help_display (window, "iagno", null);
+    }
+
+    private void game_move_cb ()
+    {
+        if (!game.can_move)
+        {
+            was_pass = true;
+            game.pass ();
+            return;
+        }
+
+        update_ui ();
+        was_pass = false;
+
+        /* Get the computer to move after a delay (so it looks like it's thinking) */
+        if ((game.current_color == Player.LIGHT && light_computer != null) ||
+            (game.current_color == Player.DARK && dark_computer != null))
+            computer_timer = Timeout.add (1000, computer_move_cb);
+    }
+
+    private bool computer_move_cb ()
+    {
+        if (game.current_color == Player.LIGHT)
+            light_computer.move ();
+        else
+            dark_computer.move ();
+        computer_timer = 0;
+        return false;
+    }
+
+    private void game_complete_cb ()
+    {
+        if (game.n_light_tiles > game.n_dark_tiles)
+            show_message (_("Light player wins!"));
+        if (game.n_dark_tiles > game.n_light_tiles)
+            show_message (_("Dark player wins!"));
+        if (game.n_light_tiles == game.n_dark_tiles)
+            show_message (_("The game was a draw."));
+
+        GnomeGamesSupport.sound_play ("gameover");
+    }
+
+    private void player_move_cb (int x, int y)
+    {
+        /* Ignore if we are waiting for the AI to move */
+        if (game.current_color == Player.LIGHT && settings.get_int ("white-level") > 0)
+            return;
+        if (game.current_color == Player.DARK && settings.get_int ("black-level") > 0)
+            return;
+
+        if (game.place_tile (x, y) == 0)
+            show_message (_("Invalid move."));
+    }
+
+    private void dark_human_cb (Gtk.ToggleButton widget)
+    {
+        if (widget.get_active ())
+            settings.set_int ("black-level", 0);
+    }
+
+    private void dark_level_one_cb (Gtk.ToggleButton widget)
+    {
+        if (widget.get_active ())
+            settings.set_int ("black-level", 1);
+    }
+
+    private void dark_level_two_cb (Gtk.ToggleButton widget)
+    {
+        if (widget.get_active ())
+            settings.set_int ("black-level", 2);
+    }
+
+    private void dark_level_three_cb (Gtk.ToggleButton widget)
+    {
+        if (widget.get_active ())
+            settings.set_int ("black-level", 3);
+    }
+
+    private void light_human_cb (Gtk.ToggleButton widget)
+    {
+        if (widget.get_active ())
+            settings.set_int ("white-level", 0);
+    }
+
+    private void light_level_one_cb (Gtk.ToggleButton widget)
+    {
+        if (widget.get_active ())
+            settings.set_int ("white-level", 1);
+    }
+
+    private void light_level_two_cb (Gtk.ToggleButton widget)
+    {
+        if (widget.get_active ())
+            settings.set_int ("white-level", 2);
+    }
+
+    private void light_level_three_cb (Gtk.ToggleButton widget)
+    {
+        if (widget.get_active ())
+            settings.set_int ("white-level", 3);
+    }
+
+    private void sound_select (Gtk.ToggleButton widget)
+    {
+        var play_sounds = widget.get_active ();
+        settings.set_boolean ("sound", play_sounds);
+        GnomeGamesSupport.sound_enable (play_sounds);
+    }
+
+    private void grid_select (Gtk.ToggleButton widget)
+    {
+        view.show_grid = widget.get_active ();
+        settings.set_boolean ("show-grid", view.show_grid);
+    }
+
+    private void propbox_response_cb (Gtk.Widget widget, int response_id)
+    {
+        widget.hide ();
+    }
+
+    private bool propbox_close_cb (Gtk.Widget widget, Gdk.EventAny event)
+    {
+        widget.hide ();
+        return true;
+    }
+
+    private void set_selection (Gtk.ComboBox widget)
+    {
+        view.tile_set = theme_file_list.get_nth (widget.get_active ());
+        settings.set_string ("tileset", view.tile_set);
+        view.redraw ();
+    }
+
+    private void show_properties_dialog ()
+    {
+        var propbox = new Gtk.Dialog.with_buttons (_("Iagno Preferences"),
+                                                   window,
+                                                   0,
+                                                   Gtk.Stock.CLOSE, Gtk.ResponseType.CLOSE,
+                                                   null);
+
+        propbox.set_border_width (5);
+        var box = (Gtk.Box) propbox.get_content_area ();
+        box.set_spacing (2);
+        propbox.resizable = false;
+        propbox.response.connect (propbox_response_cb);
+        propbox.delete_event.connect (propbox_close_cb);
+
+        var notebook = new Gtk.Notebook ();
+        notebook.set_border_width (5);
+        box.add (notebook);
+
+        var label = new Gtk.Label (_("Game"));
+
+        var vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 18);
+        vbox.set_border_width (12);
+        notebook.append_page (vbox, label);
+
+        var grid = new Gtk.Grid ();
+        grid.set_column_spacing (18);
+        vbox.pack_start (grid, false, false, 0);
+
+        var vbox2 = new Gtk.Box (Gtk.Orientation.VERTICAL, 6);
+        vbox.pack_start (vbox2, false, false, 0);
+
+        var enable_sounds_button = new Gtk.CheckButton.with_mnemonic (_("E_nable sounds"));
+        enable_sounds_button.set_active (settings.get_boolean ("sound"));
+        enable_sounds_button.toggled.connect (sound_select);
+        vbox2.pack_start (enable_sounds_button, false, false, 0);
+
+        var frame = new GnomeGamesSupport.Frame (_("Dark"));
+        grid.attach (frame, 0, 0, 1, 1);
+
+        vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 6);
+        frame.add (vbox);
+
+        var computer_button = new Gtk.RadioButton.with_label (null, _("Human"));
+        if (settings.get_int ("black-level") == 0)
+            computer_button.set_active (true);
+        computer_button.toggled.connect (dark_human_cb);
+        vbox.pack_start (computer_button, false, false, 0);
+
+        computer_button = new Gtk.RadioButton.with_label (computer_button.get_group (), _("Level one"));
+        if (settings.get_int ("black-level") == 1)
+            computer_button.set_active (true);
+        computer_button.toggled.connect (dark_level_one_cb);
+        vbox.pack_start (computer_button, false, false, 0);
+
+        computer_button = new Gtk.RadioButton.with_label (computer_button.get_group (), _("Level two"));
+        if (settings.get_int ("black-level") == 2)
+            computer_button.set_active (true);
+        computer_button.toggled.connect (dark_level_two_cb);
+        vbox.pack_start (computer_button, false, false, 0);
+
+        computer_button = new Gtk.RadioButton.with_label (computer_button.get_group (), _("Level three"));
+        if (settings.get_int ("black-level") == 3)
+            computer_button.set_active (true);
+        computer_button.toggled.connect (dark_level_three_cb);
+        vbox.pack_start (computer_button, false, false, 0);
+
+        frame = new GnomeGamesSupport.Frame (_("Light"));
+        grid.attach (frame, 1, 0, 1, 1);
+
+        vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 6);
+        frame.add (vbox);
+
+        computer_button = new Gtk.RadioButton.with_label (null, _("Human"));
+        if (settings.get_int ("white-level") == 0)
+            computer_button.set_active (true);
+        computer_button.toggled.connect (light_human_cb);
+        vbox.pack_start (computer_button, false, false, 0);
+
+        computer_button = new Gtk.RadioButton.with_label (computer_button.get_group (), _("Level one"));
+        if (settings.get_int ("white-level") == 1)
+            computer_button.set_active (true);
+        computer_button.toggled.connect (light_level_one_cb);
+        vbox.pack_start (computer_button, false, false, 0);
+
+        computer_button = new Gtk.RadioButton.with_label (computer_button.get_group (), _("Level two"));
+        if (settings.get_int ("white-level") == 2)
+            computer_button.set_active (true);
+        computer_button.toggled.connect (light_level_two_cb);
+        vbox.pack_start (computer_button, false, false, 0);
+
+        computer_button = new Gtk.RadioButton.with_label (computer_button.get_group (), _("Level three"));
+        if (settings.get_int ("white-level") == 3)
+            computer_button.set_active (true);
+        computer_button.toggled.connect (light_level_three_cb);
+        vbox.pack_start (computer_button, false, false, 0);
+
+        label = new Gtk.Label (_("Appearance"));
+
+        grid = new Gtk.Grid ();
+        grid.set_column_spacing (18);
+        grid.set_border_width (12);
+        notebook.append_page (grid, label);
+
+        frame = new GnomeGamesSupport.Frame (_("Options"));
+        grid.attach (frame, 0, 0, 1, 1);
+
+        vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 6);
+        frame.add (vbox);
+
+        var grid_button = new Gtk.CheckButton.with_mnemonic (_("S_how grid"));
+        grid_button.set_active (settings.get_boolean ("show-grid"));
+        grid_button.toggled.connect (grid_select);
+        vbox.pack_start (grid_button, false, false, 0);
+
+        var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 12);
+        vbox.pack_start (hbox, false, false, 0);
+
+        label = new Gtk.Label.with_mnemonic (_("_Tile set:"));
+        hbox.pack_start (label, false, false, 0);
+
+        var dir = GnomeGamesSupport.runtime_get_directory (GnomeGamesSupport.RuntimeDirectory.GAME_PIXMAP_DIRECTORY);
+        theme_file_list = new GnomeGamesSupport.FileList.images (dir, null);
+        theme_file_list.transform_basename ();
+        var option_menu = (Gtk.ComboBox) theme_file_list.create_widget (view.tile_set, GnomeGamesSupport.FILE_LIST_REMOVE_EXTENSION | GnomeGamesSupport.FILE_LIST_REPLACE_UNDERSCORES);
+
+        label.set_mnemonic_widget (option_menu);
+        option_menu.changed.connect (set_selection);
+        hbox.pack_start (option_menu, true, true, 0);
+
+
+        propbox.show_all ();
+    }
+
+    public static int main (string[] args)
+    {
+        if (!GnomeGamesSupport.runtime_init ("iagno"))
+            return Posix.EXIT_FAILURE;
+
+        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);
+            return Posix.EXIT_FAILURE;
+        }
+
+        Environment.set_application_name (_("Iagno"));
+
+        GnomeGamesSupport.stock_init ();
+
+        Gtk.Window.set_default_icon_name ("gnome-iagno");
+
+        var app = new Iagno ();
+        app.show ();
+
+        Gtk.main ();
+
+        GnomeGamesSupport.runtime_shutdown ();
+
+        return Posix.EXIT_SUCCESS;
+    }
+}
diff --git a/libgames-support/GnomeGamesSupport-1.0.vapi b/libgames-support/GnomeGamesSupport-1.0.vapi
index 7c5aa16..a5f71c4 100644
--- a/libgames-support/GnomeGamesSupport-1.0.vapi
+++ b/libgames-support/GnomeGamesSupport-1.0.vapi
@@ -92,6 +92,23 @@ namespace GnomeGamesSupport
     public static int runtime_get_gpl_version  ();
     [CCode (cheader_filename = "games-runtime.h")]
     public static bool runtime_is_system_prefix ();
+
+    [CCode (cheader_filename = "games-sound.h")]
+    bool sound_is_available ();
+    [CCode (cheader_filename = "games-sound.h")]
+    void sound_init (Gdk.Screen screen);
+    [CCode (cheader_filename = "games-sound.h")]
+    void sound_play (string sound_name);
+    [CCode (cheader_filename = "games-sound.h")]
+    void sound_play_for_screen (string sound_name, Gdk.Screen screen);
+    [CCode (cheader_filename = "games-sound.h")]
+    void sound_play_for_event (string sound_name, Gdk.Event event);
+    [CCode (cheader_filename = "games-sound.h")]
+    void sound_play_for_widget (string sound_name, Gtk.Widget widget);
+    [CCode (cheader_filename = "games-sound.h")]
+    void sound_enable (bool enabled);
+    [CCode (cheader_filename = "games-sound.h")]
+    bool sound_is_enabled ();
     
     [CCode (cheader_filename = "games-help.h")]
     public static void help_display (Gtk.Widget window, string doc_module, string? section);
@@ -221,6 +238,11 @@ namespace GnomeGamesSupport
     }
 
     [CCode (cheader_filename = "games-files.h")]
+    public const int FILE_LIST_REMOVE_EXTENSION;
+    [CCode (cheader_filename = "games-files.h")]
+    public const int FILE_LIST_REPLACE_UNDERSCORES;
+
+    [CCode (cheader_filename = "games-files.h")]
     public class FileList : GLib.Object
     {
         public FileList (string glob, ...);
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e401801..c662d8a 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -94,9 +94,7 @@ gtali/src/setup.c
 gtali/src/yahtzee.c
 iagno/data/iagno.desktop.in.in
 iagno/data/org.gnome.iagno.gschema.xml.in
-iagno/src/gnothello.c
-iagno/src/othello.c
-iagno/src/properties.c
+iagno/src/iagno.vala
 libgames-support/eggdesktopfile.c
 libgames-support/eggsmclient.c
 libgames-support/eggsmclient-osx.c



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]