[gnome-robots] Add declarative animation DSL



commit 435be43678bba7240bd1f843f983da14f9613e5e
Author: Andrey Kutejko <andy128k gmail com>
Date:   Sat Sep 12 21:29:01 2020 +0200

    Add declarative animation DSL

 src/animation.vala | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/game-area.vala |  78 ++++++++++++++++++++++
 src/game.vala      |  11 +---
 src/graphics.vala  | 122 ----------------------------------
 src/meson.build    |   1 +
 src/theme.vala     |   7 +-
 6 files changed, 273 insertions(+), 134 deletions(-)
---
diff --git a/src/animation.vala b/src/animation.vala
new file mode 100644
index 0000000..651e3b8
--- /dev/null
+++ b/src/animation.vala
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2020 Andrey Kutejko <andy128k gmail com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * For more details see the file COPYING.
+ */
+
+abstract class Animated {
+    protected abstract int do_tick ();
+    public abstract void reset ();
+
+    public int frame { get; private set; }
+
+    public int tick () {
+        frame = do_tick ();
+        return frame;
+    }
+
+    public static Animated sequence (int start, int step = 1) {
+        return new Sequence (start, step);
+    }
+
+    public Animated limit (int count) {
+        return new Limit (this, count);
+    }
+
+    public static Animated bounce (int start, int limit) {
+        return sequence (start).limit (limit)
+            .then (sequence (start + limit - 1, -1).limit (limit));
+    }
+
+    public Animated then (Animated another) {
+        return new Then (this, another);
+    }
+
+    public Animated repeat (int count) {
+        return new Repeat (this, count);
+    }
+
+    public Animated forever () {
+        return new Forever (this);
+    }
+}
+
+class Sequence : Animated {
+    private int start;
+    private int step;
+    private int current;
+
+    public Sequence (int start, int step = 1) {
+        this.start = start;
+        this.step = step;
+        this.current = start;
+    }
+
+    protected override int do_tick () {
+        var result = current;
+        current += step;
+        return result;
+    }
+
+    public override void reset () {
+        current = start;
+    }
+}
+
+class Then : Animated {
+    private Animated first;
+    private Animated second;
+    private bool is_second;
+
+    public Then (Animated first, Animated second) {
+        this.first = first;
+        this.second = second;
+        this.is_second = false;
+    }
+
+    protected override int do_tick () {
+        if (is_second) {
+            return second.tick();
+        } else {
+            var result = first.tick();
+            if (result >= 0) {
+                return result;
+            } else {
+                is_second = true;
+                return second.tick();
+            }
+        }
+    }
+
+    public override void reset () {
+        this.first.reset ();
+        this.second.reset ();
+        this.is_second = false;
+    }
+}
+
+class Repeat : Animated {
+    private Animated inner;
+    private int count;
+    private int current;
+
+    public Repeat (Animated inner, int count) {
+        this.inner = inner;
+        this.count = count;
+        this.current = 0;
+    }
+
+    protected override int do_tick () {
+        if (current >= count) {
+            return -1;
+        }
+        var result = inner.tick();
+        if (result >= 0) {
+            return result;
+        } else {
+            inner.reset ();
+            ++current;
+            return inner.tick();
+        }
+    }
+
+    public override void reset () {
+        this.inner.reset ();
+        this.current = 0;
+    }
+}
+
+class Forever : Animated {
+    private Animated inner;
+
+    public Forever (Animated inner) {
+        this.inner = inner;
+    }
+
+    protected override int do_tick () {
+        var result = inner.tick();
+        if (result >= 0) {
+            return result;
+        } else {
+            inner.reset ();
+            return inner.tick();
+        }
+    }
+
+    public override void reset () {
+        this.inner.reset ();
+    }
+}
+
+class Limit : Animated {
+    private Animated inner;
+    private int _limit;
+    private int count;
+
+    public Limit (Animated inner, int limit) {
+        this.inner = inner;
+        this._limit = limit;
+        this.count = 0;
+    }
+
+    protected override int do_tick () {
+        if (count >= _limit) {
+            return -1;
+        }
+        ++count;
+        return inner.tick();
+    }
+
+    public override void reset () {
+        inner.reset ();
+        count = 0;
+    }
+}
+
diff --git a/src/game-area.vala b/src/game-area.vala
index 3863be2..8bfef4c 100644
--- a/src/game-area.vala
+++ b/src/game-area.vala
@@ -18,6 +18,7 @@
  */
 
 using Gtk;
+using Gdk;
 using Cairo;
 
 public class GameArea : DrawingArea {
@@ -29,6 +30,11 @@ public class GameArea : DrawingArea {
     private EventControllerMotion motion_controller;
     private Game game;
 
+    private Animated player_animation;
+    private Animated player_dead_animation;
+    private Animated robot1_animation;
+    private Animated robot2_animation;
+
     public GameArea (Game game) {
         this.game = game;
 
@@ -44,6 +50,30 @@ public class GameArea : DrawingArea {
 
         set_size_request (MINIMUM_TILE_WIDTH * game.arena.width,
                           MINIMUM_TILE_HEIGHT * game.arena.height);
+
+        player_animation = Animated
+            .sequence (Theme.Frames.PLAYER_START, 0)
+            .limit (20)
+            .then (
+                Animated
+                    .bounce (Theme.Frames.PLAYER_START, Theme.Frames.NUM_PLAYER_ANIMATIONS)
+                    .repeat (2)
+            )
+            .forever ();
+
+        player_dead_animation = Animated
+            .bounce (Theme.Frames.PLAYER_DEAD, Theme.Frames.NUM_PLAYER_DEAD_ANIMATIONS)
+            .forever ();
+
+        robot1_animation = Animated
+            .sequence (Theme.Frames.ROBOT1_START)
+            .limit (Theme.Frames.NUM_ROBOT1_ANIMATIONS)
+            .forever ();
+
+        robot2_animation = Animated
+            .sequence (Theme.Frames.ROBOT2_START)
+            .limit (Theme.Frames.NUM_ROBOT2_ANIMATIONS)
+            .forever ();
     }
 
     private bool resize_cb (Gdk.EventConfigure e) {
@@ -70,6 +100,54 @@ public class GameArea : DrawingArea {
         return true;
     }
 
+    private void draw_object (int x, int y, ObjectType type, Context cr) {
+        if ((x + y) % 2 != 0) {
+            cairo_set_source_rgba (cr, dark_background);
+        } else {
+            cairo_set_source_rgba (cr, light_background);
+        }
+
+        x *= tile_width;
+        y *= tile_height;
+
+        cr.rectangle (x, y, tile_width, tile_height);
+        cr.fill ();
+
+        int animation = 0;
+        switch (type) {
+        case ObjectType.PLAYER:
+            if (game.get_state () != Game.State.DEAD) {
+                animation = player_animation.frame;
+            } else {
+                animation = player_dead_animation.frame;
+            }
+            break;
+        case ObjectType.ROBOT1:
+            animation = robot1_animation.frame;
+            break;
+        case ObjectType.ROBOT2:
+            animation = robot2_animation.frame;
+            break;
+        case ObjectType.HEAP:
+            animation = 0;
+            break;
+        case ObjectType.NONE:
+            break;
+        }
+
+        cr.save ();
+        cr.translate (x, y);
+        theme.draw_object (type, animation, cr, tile_width, tile_height);
+        cr.restore ();
+    }
+
+    public void tick () {
+        player_animation.tick ();
+        player_dead_animation.tick ();
+        robot1_animation.tick ();
+        robot2_animation.tick ();
+    }
+
     private void mouse_cb (int n_press, double x, double y) {
         if (game.get_state () != Game.State.PLAYING) {
             return;
diff --git a/src/game.vala b/src/game.vala
index fc50cbf..709f70d 100644
--- a/src/game.vala
+++ b/src/game.vala
@@ -124,7 +124,6 @@ public class Game {
         arena[player_xpos, player_ypos] = ObjectType.PLAYER;
         endlev_counter = 0;
         add_aieee_bubble (player_xpos, player_ypos);
-        player_animation_dead ();
         set_move_action_sensitivity (false);
     }
 
@@ -341,7 +340,6 @@ public class Game {
                 play_sound (Sound.YAHOO);
                 endlev_counter = 0;
                 add_yahoo_bubble (player_xpos, player_ypos);
-                reset_player_animation ();
                 set_move_action_sensitivity (false);
             }
         }
@@ -358,7 +356,7 @@ public class Game {
      * Game timer callback function
      **/
     bool timeout_cb () {
-        animate_game_graphics ();
+        game_area.tick ();
 
         clear_game_area ();
 
@@ -378,7 +376,6 @@ public class Game {
             if (endlev_counter >= CHANGE_DELAY) {
                 ++current_level;
                 remove_bubble ();
-                reset_player_animation ();
                 clear_game_area ();
                 generate_level ();
                 state = State.PLAYING;
@@ -450,7 +447,6 @@ public class Game {
         safe_teleports = config.initial_safe_teleports;
 
         remove_bubble ();
-        reset_player_animation ();
         generate_level ();
         clear_game_area ();
 
@@ -869,8 +865,6 @@ public class Game {
             temp_arena.@set (player_xpos, player_ypos, ObjectType.PLAYER);
         }
 
-        reset_player_animation ();
-
         remove_splat_bubble ();
 
         update_arena ();
@@ -904,7 +898,6 @@ public class Game {
             player_ypos = yp;
             temp_arena.@set (player_xpos, player_ypos, ObjectType.PLAYER);
 
-            reset_player_animation ();
             update_arena ();
             remove_splat_bubble ();
             play_sound (Sound.TELEPORT);
@@ -952,8 +945,6 @@ public class Game {
             player_ypos = yp;
             temp_arena.@set (player_xpos, player_ypos, ObjectType.PLAYER);
 
-            reset_player_animation ();
-
             safe_teleports -= 1;
             update_game_status (score, current_level, safe_teleports);
 
diff --git a/src/graphics.vala b/src/graphics.vala
index bca4f9a..2aff851 100644
--- a/src/graphics.vala
+++ b/src/graphics.vala
@@ -39,9 +39,6 @@ public const int BUBBLE_HEIGHT = 34;
 public const int BUBBLE_XOFFSET = 8;
 public const int BUBBLE_YOFFSET = 4;
 
-public const int PLAYER_WAVE_WAIT      = 20;
-public const int PLAYER_NUM_WAVES      = 2;
-
 public int tile_width = 0;
 public int tile_height = 0;
 
@@ -54,12 +51,6 @@ Pixbuf aieee_pixbuf = null;
 Pixbuf yahoo_pixbuf = null;
 Pixbuf splat_pixbuf = null;
 
-int robot_animation = 0;
-int player_animation = 0;
-int player_num_waves = 0;
-int player_wave_wait = 0;
-int player_wave_dir = 1;
-
 int bubble_xpos = 0;
 int bubble_ypos = 0;
 int bubble_xo = 0;
@@ -132,57 +123,6 @@ public void set_background_color_from_name (string name) {
     set_background_color (color);
 }
 
-/**
- * draw_object
- * @x: x position
- * @y: y position
- * @type: object type
- * @cr: context to draw on
- *
- * Description:
- * Draws graphics for an object at specified location
- **/
-public void draw_object (int x, int y, ObjectType type, Context cr) {
-    if (game_area == null)
-        return;
-
-    if ((x + y) % 2 != 0) {
-        cairo_set_source_rgba (cr, dark_background);
-    } else {
-        cairo_set_source_rgba (cr, light_background);
-    }
-
-    x *= tile_width;
-    y *= tile_height;
-
-    cr.rectangle (x, y, tile_width, tile_height);
-    cr.fill ();
-
-    int animation = 0;
-    switch (type) {
-    case ObjectType.PLAYER:
-        animation = player_animation;
-        break;
-    case ObjectType.ROBOT1:
-        animation = robot_animation;
-        break;
-    case ObjectType.ROBOT2:
-        animation = robot_animation;
-        break;
-    case ObjectType.HEAP:
-        animation = 0;
-        break;
-    case ObjectType.NONE:
-        break;
-    }
-
-    cr.save ();
-    cr.translate (x, y);
-    theme.draw_object (type, animation, cr, tile_width, tile_height);
-    cr.restore ();
-}
-
-
 /**
  * clears the whole of the game area
  **/
@@ -193,68 +133,6 @@ public void clear_game_area () {
     game_area.queue_draw ();
 }
 
-
-/**
- * reset_player_animation
- *
- * Description:
- * resets player animation to standing position
- **/
-public void reset_player_animation () {
-    player_wave_wait = 0;
-    player_num_waves = 0;
-    player_wave_dir = 1;
-    player_animation = 0;
-}
-
-
-/**
- * player_animation_dead
- *
- * Description:
- * sets player animation to be dead
- **/
-public void player_animation_dead () {
-    player_wave_wait = 0;
-    player_num_waves = 0;
-    player_wave_dir = 1;
-    player_animation = Theme.Frames.NUM_PLAYER_ANIMATIONS;
-}
-
-
-/**
- * animate_game_graphics
- *
- * Description:
- * updates animation for object graphics
- **/
-public void animate_game_graphics () {
-  ++robot_animation;
-  if (robot_animation >= Theme.Frames.NUM_ROBOT1_ANIMATIONS) {
-    robot_animation = 0;
-  }
-
-  if (player_animation == Theme.Frames.NUM_PLAYER_ANIMATIONS) {
-    /* do nothing */
-  } else if (player_wave_wait < PLAYER_WAVE_WAIT) {
-    ++player_wave_wait;
-    player_animation = 0;
-  } else {
-    player_animation += player_wave_dir;
-    if (player_animation >= Theme.Frames.NUM_PLAYER_ANIMATIONS) {
-      player_wave_dir = -1;
-      player_animation -= 2;
-    } else if (player_animation < 0) {
-      player_wave_dir = 1;
-      player_animation = 1;
-      ++player_num_waves;
-      if (player_num_waves >= PLAYER_NUM_WAVES) {
-        reset_player_animation ();
-      }
-    }
-  }
-}
-
 /**
  * Draws a bubble if there is one
  **/
diff --git a/src/meson.build b/src/meson.build
index 75e660f..3cf36ee 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -15,6 +15,7 @@ sources = files(
     'themes.vala',
     'theme.vala',
     'preimage.vala',
+    'animation.vala',
     'controls.vala',
     'game-config.vala',
     'sound.vala',
diff --git a/src/theme.vala b/src/theme.vala
index 7a18891..ba4ceed 100644
--- a/src/theme.vala
+++ b/src/theme.vala
@@ -24,12 +24,15 @@ public class Theme {
 
     public enum Frames {
         PLAYER_START = 0,
+        PLAYER_DEAD = 4,
         ROBOT1_START = 5,
         ROBOT2_START = 9,
         HEAP_START = 13,
         COUNT = 14,
 
-        NUM_PLAYER_ANIMATIONS = ROBOT1_START - PLAYER_START,
+        NUM_PLAYER_ANIMATIONS = PLAYER_DEAD - PLAYER_START,
+        NUM_PLAYER_DEAD_ANIMATIONS = ROBOT1_START - PLAYER_DEAD,
+        NUM_PLAYER_TILES = ROBOT1_START - PLAYER_START,
         NUM_ROBOT1_ANIMATIONS = ROBOT2_START - ROBOT1_START,
         NUM_ROBOT2_ANIMATIONS = HEAP_START - ROBOT2_START,
         NUM_HEAP_ANIMATIONS = COUNT - HEAP_START,
@@ -54,7 +57,7 @@ public class Theme {
         int tile_no = -1;
         switch (type) {
         case ObjectType.PLAYER:
-            tile_no = Frames.PLAYER_START + frame_no % Frames.NUM_PLAYER_ANIMATIONS;
+            tile_no = Frames.PLAYER_START + frame_no % Frames.NUM_PLAYER_TILES;
             break;
         case ObjectType.ROBOT1:
             tile_no = Frames.ROBOT1_START + frame_no % Frames.NUM_ROBOT1_ANIMATIONS;


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