[gnome-robots] Rewrite game module in Vala



commit ddf59358591becc51a1df6cc23a298c767fff85c
Author: Andrey Kutejko <andy128k gmail com>
Date:   Sat Aug 29 14:04:27 2020 +0200

    Rewrite game module in Vala

 src/arena.vala      |   86 ++++
 src/game.c          | 1370 ---------------------------------------------------
 src/game.h          |   51 --
 src/game.vala       | 1135 ++++++++++++++++++++++++++++++++++++++++++
 src/gbdefs.h        |   54 --
 src/gnome-robots.c  |   78 ++-
 src/graphics.vala   |    9 -
 src/keyboard.c      |    3 +-
 src/main.vapi       |    9 +-
 src/meson.build     |    3 +-
 src/properties.vala |    4 +-
 11 files changed, 1301 insertions(+), 1501 deletions(-)
---
diff --git a/src/arena.vala b/src/arena.vala
new file mode 100644
index 0000000..de257b4
--- /dev/null
+++ b/src/arena.vala
@@ -0,0 +1,86 @@
+/* arenta.vala:
+
+   Copyright 2020 Andrey Kutejko
+
+   This library is free software; you can redistribute it and'or modify
+   it under the terms of the GNU Library General Public License as published
+   by the Free Software Foundation; either version 3, or (at your option)
+   any later version.
+
+   This library 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 Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public License
+   along with this library; if not, see <http://www.gnu.org/licenses/>.
+
+   Authors:   Andrey Kutejko <andy128k gmail com>
+*/
+
+public enum ObjectType {
+    PLAYER = 0,
+    HEAP = 1,
+    ROBOT1 = 2,
+    ROBOT2 = 3,
+    NONE = 99,
+}
+
+public delegate ObjectType ArenaMapper (ObjectType obj);
+
+public class Arena {
+
+    private int _width;
+    private int _height;
+    private ObjectType[] arena;
+
+    public int width {
+        get { return this._width; }
+    }
+
+    public int height {
+        get { return this._height; }
+    }
+
+    public Arena (int width, int height) {
+        this._width = width;
+        this._height = height;
+        this.arena = new ObjectType[width * height];
+        clear ();
+    }
+
+    public void clear () {
+        for (int i = 0; i < _width; ++i) {
+            for (int j = 0; j < _height; ++j) {
+                this.arena[i + j * _width] = ObjectType.NONE;
+            }
+        }
+    }
+
+    public ObjectType @get (int x, int y) {
+        if (x < 0 || y < 0 || x >= _width || y >= _height) {
+            return ObjectType.NONE;
+        }
+        return arena[x + y * _width];
+    }
+
+    public void @set (int x, int y, ObjectType obj) {
+        if (x < 0 || y < 0 || x >= _width || y >= _height) {
+            return;
+        }
+        arena[x + y * _width] = obj;
+    }
+
+    public Arena map (ArenaMapper mapper) {
+        var new_arena = new Arena (_width, _height);
+
+        for (int i = 0; i < _width; ++i) {
+            for (int j = 0; j < _height; ++j) {
+                new_arena.arena[i + j * _width] = mapper (this.arena[i + j * _width]);
+            }
+        }
+
+        return new_arena;
+    }
+}
+
diff --git a/src/game.vala b/src/game.vala
new file mode 100644
index 0000000..477aa3c
--- /dev/null
+++ b/src/game.vala
@@ -0,0 +1,1135 @@
+/*
+ * 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.
+*/
+
+using Games;
+
+public const int ANIMATION_DELAY = 100;
+public const int DEAD_DELAY = 30;
+public const int CHANGE_DELAY = 20;
+public const int WAITING_DELAY = 1;
+
+public Game game = null;
+
+public class Game {
+
+    public enum State {
+        PLAYING = 1,
+        WAITING,
+        COMPLETE,
+        DEAD,
+        ROBOT,
+        TYPE2,
+        WTYPE2,
+    }
+
+    public enum KeyboardControl {
+        NW = 0,
+        N,
+        NE,
+        W,
+        STAY,
+        E,
+        SW,
+        S,
+        SE,
+        TELE,
+        RTEL,
+        WAIT,
+    }
+
+    Rand rand;
+    public State state = State.PLAYING;
+    public Arena arena;
+
+    int num_robots1 = 0;
+    int num_robots2 = 0;
+    int endlev_counter = 0;
+    int current_level = 0;
+    int score = 0;
+    int kills = 0;
+    int score_step = 0;
+    int safe_teleports = 0;
+    int player_xpos = 0;
+    int player_ypos = 0;
+    int push_xpos = -1;
+    int push_ypos = -1;
+    uint game_timer_id = -1;
+    Arena temp_arena = null;
+
+    Scores.Category current_cat;
+
+    public Game () {
+        arena = new Arena (GAME_WIDTH, GAME_HEIGHT);
+        rand = new Rand ();
+    }
+
+    public State get_state () {
+        return state;
+    }
+
+    /**
+     * Displays the high-score table
+     **/
+    public void show_scores () {
+        highscores.run_dialog ();
+    }
+
+    /**
+     * Enters a score in the high-score table
+     **/
+    void log_score (int sc) {
+        var game_config = game_configs.get_current ();
+
+        string key;
+        if (properties_super_safe_moves ()) {
+            key = game_config.description + "-super-safe";
+        } else if (properties_safe_moves ()) {
+            key = game_config.description + "-safe";
+        } else {
+            key = game_config.description;
+        }
+
+        if (sc != 0) {
+            string name = category_name_from_key (key);
+            current_cat = new Scores.Category (key, name);
+            highscores.add_score.begin (sc, current_cat, null, (ctx, res) => {
+                try {
+                    highscores.add_score.end (res);
+                } catch (Error error) {
+                    warning ("Failed to add score: %s", error.message);
+                }
+            });
+        }
+    }
+
+    /**
+     * Ends the current game.
+     **/
+    void kill_player () {
+        state = State.DEAD;
+        play_sound (Sound.DIE);
+        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);
+    }
+
+    /**
+     * add_kill
+     * @type: robot type
+     *
+     * Description:
+     * registers a robot kill and updates the score
+     **/
+    void add_kill (ObjectType type) {
+        var game_config = game_configs.get_current ();
+
+        int si;
+        if ((state == State.WAITING) || (state == State.WTYPE2)) {
+            if (type == ObjectType.ROBOT1) {
+                si = game_config.score_type1_waiting;
+                kills += 1;
+            } else {
+                si = game_config.score_type2_waiting;
+                kills += 2;
+            }
+        } else {
+            if (type == ObjectType.ROBOT1) {
+                si = game_config.score_type1;
+            } else {
+                si = game_config.score_type2;
+            }
+        }
+
+        score += si;
+        score_step += si;
+
+        if (game_config.safe_score_boundary > 0) {
+            while (score_step >= game_config.safe_score_boundary) {
+                safe_teleports += 1;
+                score_step -= game_config.safe_score_boundary;
+            }
+        }
+
+        if (game_config.num_robots_per_safe > 0) {
+            while (kills >= game_config.num_robots_per_safe) {
+                safe_teleports += 1;
+                kills -= game_config.num_robots_per_safe;
+            }
+        }
+
+        if (safe_teleports > game_config.max_safe_teleports) {
+            safe_teleports = game_config.max_safe_teleports;
+        }
+
+        update_game_status (score, current_level + 1, safe_teleports);
+    }
+
+    /**
+     * clears the arena
+     **/
+    void clear_arena () {
+        arena.clear ();
+        num_robots1 = 0;
+        num_robots2 = 0;
+    }
+
+    /**
+     * Set up the temporary arena for processing speculative moves.
+     *
+     **/
+    void load_temp_arena () {
+        temp_arena = arena.map ((obj) => {
+            if (obj != ObjectType.PLAYER) {
+                return obj;
+            } else {
+                return ObjectType.NONE;
+            }
+        });
+    }
+
+    /**
+     * check_location
+     * @x: x position
+     * @y: y position
+     *
+     * Description:
+     * checks for an object at a given location
+     *
+     * Returns:
+     * type of object if present or ObjectType.NONE
+     **/
+    public int check_location (int x, int y) {
+        return arena.@get (x, y);
+    }
+
+    private int max_robots () {
+        return arena.width * arena.height / 2;
+    }
+
+    /**
+     * generate_level
+     *
+     * Description:
+     * Creates a new level and populates it with robots
+     **/
+    void generate_level () {
+        var game_config = game_configs.get_current ();
+
+        clear_arena ();
+
+        player_xpos = arena.width / 2;
+        player_ypos = arena.height / 2;
+        arena.@set(player_xpos, player_ypos, ObjectType.PLAYER);
+
+        num_robots1 = game_config.initial_type1 + game_config.increment_type1 * current_level;
+
+        if (num_robots1 > game_config.maximum_type1) {
+            num_robots1 = game_config.maximum_type1;
+        }
+        if (num_robots1 > max_robots ()) {
+            current_level = 0;
+            num_robots1 = game_config.initial_type1;
+            message_box (_("Congratulations, You Have Defeated the Robots!! \nBut Can You do it Again?"));
+            play_sound (Sound.VICTORY);
+        }
+
+        num_robots2 = game_config.initial_type2 + game_config.increment_type2 * current_level;
+
+        if (num_robots2 > game_config.maximum_type2) {
+            num_robots2 = game_config.maximum_type2;
+        }
+
+        if ((num_robots1 + num_robots2) > max_robots ()) {
+            current_level = 0;
+            num_robots1 = game_config.initial_type1;
+            num_robots2 = game_config.initial_type2;
+            message_box (_("Congratulations, You Have Defeated the Robots!! \nBut Can You do it Again?"));
+            play_sound (Sound.VICTORY);
+        }
+
+        safe_teleports += game_config.free_safe_teleports;
+
+        if (safe_teleports > game_config.max_safe_teleports) {
+            safe_teleports = game_config.max_safe_teleports;
+        }
+
+        update_game_status (score, current_level, safe_teleports);
+
+        for (int i = 0; i < num_robots1; ++i) {
+            while (true) {
+                int xp = rand.int_range (0, arena.width);
+                int yp = rand.int_range (0, arena.height);
+
+                if (check_location (xp, yp) == ObjectType.NONE) {
+                    arena[xp, yp] = ObjectType.ROBOT1;
+                    break;
+                }
+            }
+        }
+
+        for (int i = 0; i < num_robots2; ++i) {
+            while (true) {
+                int xp = rand.int_range (0, arena.width);
+                int yp = rand.int_range (0, arena.height);
+
+                if (check_location (xp, yp) == ObjectType.NONE) {
+                    arena[xp, yp] = ObjectType.ROBOT2;
+                    break;
+                }
+            }
+        }
+
+    }
+
+    /**
+     * update_arena
+     *
+     * Description:
+     * Copies the temporary arena into the main game arena
+     **/
+    void update_arena () {
+        var game_config = game_configs.get_current ();
+
+        num_robots1 = 0;
+        num_robots2 = 0;
+
+        for (int i = 0; i < GAME_WIDTH; ++i) {
+            for (int j = 0; j < GAME_HEIGHT; ++j) {
+                if (temp_arena[i, j] == ObjectType.HEAP &&
+                    push_xpos == i && push_ypos == j
+                ) {
+                    if (arena[i, j] == ObjectType.ROBOT1) {
+                        add_splat_bubble (i, j);
+                        play_sound (Sound.SPLAT);
+                        push_xpos = push_ypos = -1;
+                        score += game_config.score_type1_splatted;
+                    }
+                    if (arena[i, j] == ObjectType.ROBOT2) {
+                        add_splat_bubble (i, j);
+                        play_sound (Sound.SPLAT);
+                        push_xpos = push_ypos = -1;
+                        score += game_config.score_type2_splatted;
+                    }
+                }
+
+                arena[i, j] = temp_arena[i, j];
+                if (arena[i, j] == ObjectType.ROBOT1) {
+                    num_robots1 += 1;
+                } else if (arena[i, j] == ObjectType.ROBOT2) {
+                    num_robots2 += 1;
+                }
+            }
+        }
+
+        if (arena[player_xpos, player_ypos] != ObjectType.PLAYER) {
+            kill_player ();
+        } else {
+            /* This is in the else statement to catch the case where the last
+             * two robots collide on top of the human. Without the "else" this
+             * leads to the player being ressurected and winning. */
+            if ((num_robots1 + num_robots2) <= 0) {
+                state = State.COMPLETE;
+                play_sound (Sound.YAHOO);
+                endlev_counter = 0;
+                add_yahoo_bubble (player_xpos, player_ypos);
+                reset_player_animation ();
+                set_move_action_sensitivity (false);
+            }
+        }
+
+        update_game_status (score, current_level + 1, safe_teleports);
+    }
+
+
+    /**
+     * timeout_cb
+     * @data: callback data
+     *
+     * Description:
+     * Game timer callback function
+     **/
+    bool timeout_cb () {
+        animate_game_graphics ();
+
+        clear_game_area ();
+
+        if ((state == State.TYPE2) || (state == State.WTYPE2)) {
+            move_type2_robots ();
+            update_arena ();
+            if (state == State.TYPE2) {
+                state = State.PLAYING;
+            } else if (state == State.WTYPE2) {
+                state = State.WAITING;
+            }
+        } else if (state == State.WAITING) {
+            remove_splat_bubble ();
+            move_robots ();
+        } else if (state == State.COMPLETE) {
+            ++endlev_counter;
+            if (endlev_counter >= CHANGE_DELAY) {
+                ++current_level;
+                remove_bubble ();
+                reset_player_animation ();
+                clear_game_area ();
+                generate_level ();
+                state = State.PLAYING;
+                set_move_action_sensitivity (true);
+                update_game_status (score, current_level + 1, safe_teleports);
+            }
+        } else if (state == State.DEAD) {
+            ++endlev_counter;
+            if (endlev_counter >= DEAD_DELAY) {
+                if (score > 0) {
+                    log_score (score);
+                }
+                start_new_game ();
+            }
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Destroys the game timer
+     **/
+    void destroy_game_timer () {
+        if (game_timer_id != -1) {
+            Source.remove (game_timer_id);
+            game_timer_id = -1;
+        }
+    }
+
+
+    /**
+     * create_game_timer
+     *
+     * Description:
+     * Creates the game timer
+     **/
+    void create_game_timer () {
+        if (game_timer_id != -1) {
+            destroy_game_timer ();
+        }
+
+        game_timer_id = Timeout.add (ANIMATION_DELAY, timeout_cb);
+    }
+
+    /**
+     * Initialises everything when game first starts up
+     **/
+    public void init_game () {
+        create_game_timer ();
+        start_new_game ();
+    }
+
+    /**
+     * start_new_game
+     *
+     * Description:
+     * Initialises everything needed to start a new game
+     **/
+    public void start_new_game () {
+        current_level = 0;
+        score = 0;
+        kills = 0;
+        score_step = 0;
+
+        if (state == State.PLAYING)
+            log_score (score);
+
+        var game_config = game_configs.get_current ();
+        // g_return_if_fail (game_config != NULL);
+
+        safe_teleports = game_config.initial_safe_teleports;
+
+        remove_bubble ();
+        reset_player_animation ();
+        generate_level ();
+        clear_game_area ();
+
+        state = State.PLAYING;
+
+        update_game_status (score, current_level + 1, safe_teleports);
+        set_move_action_sensitivity (true);
+    }
+
+
+    /**
+     * move_all_robots
+     *
+     * Description:
+     * Moves all of the robots and checks for collisions
+     **/
+    void move_all_robots () {
+        temp_arena = arena.map ((obj) => {
+            if (obj == ObjectType.PLAYER || obj == ObjectType.HEAP) {
+                return obj;
+            } else {
+                return ObjectType.NONE;
+            }
+        });
+
+        int i, j;
+        int nx, ny;
+      for (i = 0; i < GAME_WIDTH; ++i) {
+        for (j = 0; j < GAME_HEIGHT; ++j) {
+          if ((arena.@get (i, j) == ObjectType.ROBOT1) || (arena.@get (i, j) == ObjectType.ROBOT2)) {
+            nx = i;
+            ny = j;
+            if (player_xpos < nx)
+              nx -= 1;
+            if (player_xpos > nx)
+              nx += 1;
+            if (player_ypos < ny)
+              ny -= 1;
+            if (player_ypos > ny)
+              ny += 1;
+
+            if (temp_arena.@get (nx, ny) == ObjectType.HEAP) {
+              add_kill (arena.@get (i, j));
+            } else if ((temp_arena.@get (nx, ny) == ObjectType.ROBOT1) ||
+                       (temp_arena.@get (nx, ny) == ObjectType.ROBOT2)) {
+              add_kill (arena.@get (i, j));
+              add_kill (temp_arena.@get (nx, ny));
+              temp_arena.@set (nx, ny, ObjectType.HEAP);
+            } else {
+              temp_arena.@set (nx, ny, arena.@get (i, j));
+            }
+          }
+        }
+      }
+
+    }
+
+
+    /**
+     * move_type2_robots
+     *
+     * Description:
+     * Makes the extra move for all of the type2 robots
+     **/
+    void move_type2_robots () {
+        int i, j;
+        int nx, ny;
+
+        temp_arena = arena.map ((obj) => {
+            if (obj == ObjectType.PLAYER || obj == ObjectType.ROBOT1 || obj == ObjectType.HEAP) {
+                return obj;
+            } else {
+                return ObjectType.NONE;
+            }
+        });
+
+      for (i = 0; i < arena.width; ++i) {
+        for (j = 0; j < arena.height; ++j) {
+          if (arena.@get (i, j) == ObjectType.ROBOT2) {
+                nx = i;
+                ny = j;
+                if (player_xpos < nx)
+                  nx -= 1;
+                if (player_xpos > nx)
+                  nx += 1;
+                if (player_ypos < ny)
+                  ny -= 1;
+                if (player_ypos > ny)
+                    ny += 1;
+
+            if (temp_arena.@get (nx, ny) == ObjectType.HEAP) {
+              add_kill (arena.@get (i, j));
+            } else if ((temp_arena.@get (nx, ny) == ObjectType.ROBOT1) ||
+                       (temp_arena.@get (nx, ny) == ObjectType.ROBOT2)) {
+              add_kill (arena.@get (i, j));
+              add_kill (temp_arena.@get (nx, ny));
+              temp_arena.@set (nx, ny, ObjectType.HEAP);
+            } else {
+              temp_arena.@set (nx, ny, arena.@get (i, j));
+            }
+          }
+        }
+      }
+    }
+
+    /**
+     * Starts the process of moving robots
+     **/
+    public void move_robots () {
+        move_all_robots ();
+
+        if (num_robots2 > 0) {
+            if (state == State.WAITING) {
+                state = State.WTYPE2;
+            } else if (state == State.PLAYING) {
+                state = State.TYPE2;
+            }
+        }
+
+        update_arena ();
+    }
+
+    private static Arena chase (Arena arena, Gee.Predicate<ObjectType> is_chaser, int x, int y) {
+        var new_arena = arena.map ((obj) => {
+            if (obj == ObjectType.PLAYER || obj == ObjectType.HEAP) {
+                return obj;
+            } else {
+                return ObjectType.NONE;
+            }
+        });
+
+        for (int i = 0; i < arena.width; ++i) {
+            for (int j = 0; j < arena.height; ++j) {
+                var who = arena.@get (i, j);
+                if (is_chaser (who)) {
+                    int nx = i;
+                    int ny = j;
+                    if (x < nx)
+                        nx -= 1;
+                    if (x > nx)
+                        nx += 1;
+                    if (y < ny)
+                        ny -= 1;
+                    if (y > ny)
+                        ny += 1;
+
+                    var destination = new_arena.@get (nx, ny);
+                    if (destination == ObjectType.ROBOT1 ||
+                        destination == ObjectType.ROBOT2 ||
+                        destination == ObjectType.HEAP
+                    ) {
+                        new_arena.@set (nx, ny, ObjectType.HEAP);
+                    } else {
+                        new_arena.@set (nx, ny, who);
+                    }
+                }
+            }
+        }
+
+        return new_arena;
+    }
+
+    /**
+     * check_safe
+     * @x: x position
+     * @y: y position
+     *
+     * Description:
+     * checks whether a given location is safe
+     *
+     * Returns:
+     * TRUE if location is safe, FALSE otherwise
+     **/
+    bool check_safe (int x, int y) {
+        if (temp_arena.@get (x, y) != ObjectType.NONE) {
+            return false;
+        }
+
+        var temp2_arena = temp_arena.map ((obj) => {
+            if (obj == ObjectType.PLAYER || obj == ObjectType.HEAP) {
+                return obj;
+            } else {
+                return ObjectType.NONE;
+            }
+        });
+
+        for (int i = 0; i < GAME_WIDTH; ++i) {
+            for (int j = 0; j < GAME_HEIGHT; ++j) {
+                if (temp_arena[i, j] == ObjectType.ROBOT1 ||
+                    temp_arena[i, j] == ObjectType.ROBOT2) {
+
+                    int nx = i;
+                    int ny = j;
+                    if (x < nx)
+                        nx -= 1;
+                    if (x > nx)
+                        nx += 1;
+                    if (y < ny)
+                        ny -= 1;
+                    if (y > ny)
+                        ny += 1;
+
+                    if (temp2_arena[nx, ny] == ObjectType.ROBOT1 ||
+                        temp2_arena[nx, ny] == ObjectType.ROBOT2 ||
+                        temp2_arena[nx, ny] == ObjectType.HEAP) {
+                        temp2_arena[nx, ny] = ObjectType.HEAP;
+                    } else {
+                        temp2_arena[nx, ny] = temp_arena[i, j];
+                    }
+                }
+            }
+        }
+
+        if (temp2_arena[x, y] != ObjectType.NONE) {
+            return false;
+        }
+
+        var temp3_arena = chase (temp2_arena, (obj) => obj == ObjectType.ROBOT2, x, y);
+
+        if (temp3_arena.@get (x, y) != ObjectType.NONE) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * push_heap
+     * @x: x position
+     * @y: y position
+     * @dx: x direction
+     * @dy: y direction
+     *
+     * Description:
+     * pushes a heap in a given direction
+     *
+     * Returns:
+     * TRUE if heap can be pushed, FALSE otherwise
+     **/
+    bool push_heap (int x, int y, int dx, int dy) {
+        int nx = x + dx;
+        int ny = y + dy;
+
+        if (temp_arena.@get (x, y) != ObjectType.HEAP)
+            return false;
+
+        if (nx < 0 || nx >= arena.width || ny < 0 || ny >= arena.height) {
+            return false;
+        }
+
+        if (temp_arena.@get (nx, ny) == ObjectType.HEAP)
+            return false;
+
+        push_xpos = nx;
+        push_ypos = ny;
+
+        temp_arena.@set (nx, ny, ObjectType.HEAP);
+        temp_arena.@set (x, y, ObjectType.NONE);
+
+        return true;
+    }
+
+    /**
+     * try_player_move
+     * @dx: x direction
+     * @dy: y direction
+     *
+     * Description:
+     * tries to move the player in a given direction
+     *
+     * Returns:
+     * TRUE if the player can move, FALSE otherwise
+     **/
+    bool try_player_move (int dx, int dy) {
+        var game_config = game_configs.get_current ();
+
+        int nx = player_xpos + dx;
+        int ny = player_ypos + dy;
+
+        if ((nx < 0) || (nx >= arena.width) || (ny < 0) || (ny >= arena.height)) {
+            return false;
+        }
+
+        load_temp_arena ();
+
+        if (temp_arena.@get (nx, ny) == ObjectType.HEAP) {
+            if (game_config.moveable_heaps) {
+                if (!push_heap (nx, ny, dx, dy)) {
+                    push_xpos = push_ypos = -1;
+                    return false;
+                }
+            } else {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * safe_move_available
+     *
+     * Description:
+     * checks to see if a safe move was available to the player
+     *
+     * Returns:
+     * TRUE if there is a possible safe move, FALSE otherwise
+     **/
+    bool safe_move_available () {
+        if (try_player_move (-1, -1)) {
+            if (check_safe (player_xpos - 1, player_ypos - 1)) {
+                return true;
+            }
+        }
+        if (try_player_move (0, -1)) {
+            if (check_safe (player_xpos, player_ypos - 1)) {
+                return true;
+            }
+        }
+        if (try_player_move (1, -1)) {
+            if (check_safe (player_xpos + 1, player_ypos - 1)) {
+                return true;
+            }
+        }
+        if (try_player_move (-1, 0)) {
+            if (check_safe (player_xpos - 1, player_ypos)) {
+                return true;
+            }
+        }
+        if (try_player_move (0, 0)) {
+            if (check_safe (player_xpos, player_ypos)) {
+                return true;
+            }
+        }
+        if (try_player_move (1, 0)) {
+            if (check_safe (player_xpos + 1, player_ypos)) {
+                return true;
+            }
+        }
+        if (try_player_move (-1, 1)) {
+            if (check_safe (player_xpos - 1, player_ypos + 1)) {
+                return true;
+            }
+        }
+        if (try_player_move (0, 1)) {
+            if (check_safe (player_xpos, player_ypos + 1)) {
+                return true;
+            }
+        }
+        if (try_player_move (1, 1)) {
+            if (check_safe (player_xpos + 1, player_ypos + 1)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * safe_teleport_available
+     *
+     * Description:
+     * Check for a safe teleport.
+     *
+     * Returns:
+     * TRUE is a safe teleport is possible, FALSE otherwise.
+     *
+     */
+    bool safe_teleport_available () {
+        load_temp_arena ();
+
+        for (int x = 0; x < GAME_WIDTH; x++) {
+            for (int y = 0; y < GAME_HEIGHT; y++) {
+                if (check_safe (x, y))
+                    return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * player_move
+     * @dx: x direction
+     * @dy: y direction
+     *
+     * Description:
+     * moves the player in a given direction
+     *
+     * Returns:
+     * TRUE if the player can move, FALSE otherwise
+     **/
+    public bool player_move (int dx, int dy) {
+
+        int nx = player_xpos + dx;
+        int ny = player_ypos + dy;
+
+        if (properties_safe_moves ()) {
+            if (!try_player_move (dx, dy)) {
+                play_sound (Sound.BAD);
+                return false;
+            } else {
+                if (!check_safe (nx, ny)) {
+                    if (properties_super_safe_moves () || safe_move_available ()) {
+                        play_sound (Sound.BAD);
+                        return false;
+                    }
+                }
+            }
+        } else {
+            if (!try_player_move (dx, dy)) {
+                play_sound (Sound.BAD);
+                return false;
+            }
+        }
+
+        player_xpos = nx;
+        player_ypos = ny;
+
+        if (temp_arena.@get (player_xpos, player_ypos) == ObjectType.NONE) {
+            temp_arena.@set (player_xpos, player_ypos, ObjectType.PLAYER);
+        }
+
+        reset_player_animation ();
+
+        remove_splat_bubble ();
+
+        update_arena ();
+
+        return true;
+    }
+
+
+    /**
+     * random_teleport
+     *
+     * Description:
+     * randomly teleports the player
+     *
+     * Returns:
+     * TRUE if the player can be teleported, FALSE otherwise
+     **/
+    bool random_teleport () {
+        temp_arena = arena.map ((obj) => {
+            if (obj != ObjectType.PLAYER) {
+                return obj;
+            } else {
+                return ObjectType.NONE;
+            }
+        });
+
+        int xp;
+        int yp;
+        if (random_position ((x, y) => temp_arena.@get(x, y) == ObjectType.NONE, out xp, out yp)) {
+            player_xpos = xp;
+            player_ypos = yp;
+            temp_arena.@set (player_xpos, player_ypos, ObjectType.PLAYER);
+
+            reset_player_animation ();
+            update_arena ();
+            remove_splat_bubble ();
+            play_sound (Sound.TELEPORT);
+
+            return true;
+        } else {
+            /* This should never happen. */
+            message_box (_("There are no teleport locations left!!"));
+            return false;
+        }
+    }
+
+
+    /**
+     * safe_teleport
+     *
+     * Description:
+     * teleports the player to safe location
+     *
+     * Returns:
+     * TRUE if player can be teleported, FALSE otherwise
+     **/
+    bool safe_teleport () {
+        if (!safe_teleport_available ()) {
+            message_box (_("There are no safe locations to teleport to!!"));
+            kill_player ();
+            return false;
+        }
+
+        if (safe_teleports <= 0)
+            return false;
+
+        temp_arena = arena.map ((obj) => {
+            if (obj != ObjectType.PLAYER) {
+                return obj;
+            } else {
+                return ObjectType.NONE;
+            }
+        });
+
+        int xp;
+        int yp;
+        if (random_position ((x, y) => temp_arena.@get(x, y) == ObjectType.NONE && check_safe (x, y), out 
xp, out yp)) {
+            player_xpos = xp;
+            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);
+
+            update_arena ();
+            remove_splat_bubble ();
+            play_sound (Sound.TELEPORT);
+
+            return true;
+        } else {
+            /* This should never happen. */
+            message_box (_("There are no teleport locations left!!"));
+            return false;
+        }
+    }
+
+    delegate bool PositionPredicate(int x, int y);
+
+    private bool random_position (PositionPredicate predicate, out int xp, out int yp) {
+        int ixp = rand.int_range (0, arena.width);
+        int iyp = rand.int_range (0, arena.height);
+
+        xp = ixp;
+        yp = iyp;
+        while (true) {
+            if (predicate(xp, yp)) {
+                return true;
+            }
+
+            ++xp;
+            if (xp >= arena.width) {
+                xp = 0;
+                ++yp;
+                if (yp >= arena.height) {
+                    yp = 0;
+                }
+            }
+
+            if (xp == ixp && yp == iyp) {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * handles keyboard commands
+     **/
+    public void keypress (KeyboardControl key) {
+        if (state != State.PLAYING)
+            return;
+
+        switch (key) {
+        case KeyboardControl.NW:
+            if (player_move (-1, -1)) {
+                move_robots ();
+            }
+            break;
+        case KeyboardControl.N:
+            if (player_move (0, -1)) {
+                move_robots ();
+            }
+            break;
+        case KeyboardControl.NE:
+            if (player_move (1, -1)) {
+                move_robots ();
+            }
+            break;
+        case KeyboardControl.W:
+            if (player_move (-1, 0)) {
+                move_robots ();
+            }
+            break;
+        case KeyboardControl.STAY:
+            if (player_move (0, 0)) {
+                move_robots ();
+            }
+            break;
+        case KeyboardControl.E:
+            if (player_move (1, 0)) {
+                move_robots ();
+            }
+            break;
+        case KeyboardControl.SW:
+            if (player_move (-1, 1)) {
+                move_robots ();
+            }
+            break;
+        case KeyboardControl.S:
+            if (player_move (0, 1)) {
+                move_robots ();
+            }
+            break;
+        case KeyboardControl.SE:
+            if (player_move (1, 1)) {
+                move_robots ();
+            }
+            break;
+        case KeyboardControl.TELE:
+            if (safe_teleport ()) {
+                move_robots ();
+            }
+            break;
+        case KeyboardControl.RTEL:
+            if (random_teleport ()) {
+                move_robots ();
+            }
+            break;
+        case KeyboardControl.WAIT:
+            state = State.WAITING;
+            break;
+        }
+    }
+
+    public void get_dir (int ix, int iy, out int odx, out int ody) {
+        const int[,] MOVE_TABLE = {
+            {-1, 0}, {-1, -1}, {0, -1}, {1, -1},
+            {1, 0}, {1, 1}, {0, 1}, {-1, 1}
+        };
+        int x = (ix / tile_width).clamp (0, arena.width);
+        int y = (iy / tile_height).clamp (0, arena.height);
+
+        /* If we click on our man then we assume we hold. */
+        if ((x == player_xpos) && (y == player_ypos)) {
+            odx = 0;
+            ody = 0;
+            return;
+        }
+
+        /* If the square clicked on is a valid move, go there. */
+        int idx = x - player_xpos;
+        int idy = y - player_ypos;
+        if (idx.abs () < 2 && idy.abs () < 2) {
+            odx = idx;
+            ody = idy;
+            return;
+        }
+
+        /* Otherwise go in the general direction of the mouse click. */
+        double dx = ix - (player_xpos + 0.5) * tile_width;
+        double dy = iy - (player_ypos + 0.5) * tile_height;
+
+        double angle = Math.atan2 (dy, dx);
+
+        /* Note the adjustment we have to make (+9, not +8) because atan2's idea
+         * of octants and the ones we want are shifted by PI/8. */
+        int octant = (((int) Math.floor (8.0 * angle / Math.PI) + 9) / 2) % 8;
+
+        odx = MOVE_TABLE[octant, 0];
+        ody = MOVE_TABLE[octant, 1];
+    }
+}
+
+/**
+ * Displays a modal dialog box with a given message
+ **/
+void message_box (string msg) {
+    var box = new Gtk.MessageDialog (window,
+                                     Gtk.DialogFlags.MODAL,
+                                     Gtk.MessageType.INFO,
+                                     Gtk.ButtonsType.OK,
+                                     "%s", msg);
+    box.run ();
+    box.destroy ();
+}
diff --git a/src/gnome-robots.c b/src/gnome-robots.c
index b220ad3..3644ae6 100644
--- a/src/gnome-robots.c
+++ b/src/gnome-robots.c
@@ -33,9 +33,8 @@
 #include <glib.h>
 #include <libgnome-games-support.h>
 
-#include "gbdefs.h"
 #include "riiv.h"
-#include "game.h"
+#include "keyboard.h"
 
 /* Minimum sizes. */
 #define MINIMUM_TILE_WIDTH   8
@@ -176,7 +175,7 @@ draw_cb (GtkWidget * w, cairo_t * cr, gpointer data)
 
   for (j = 0; j < GAME_HEIGHT; j++) {
     for (i = 0; i < GAME_WIDTH; i++) {
-      draw_object (i, j, arena[i][j], cr);
+      draw_object (i, j, game_check_location (game, i, j), cr);
     }
   }
 
@@ -207,7 +206,7 @@ preferences_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
 static void
 scores_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
 {
-  show_scores ();
+  game_show_scores (game);
 }
 
 static void
@@ -271,25 +270,25 @@ new_game_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
   gtk_widget_destroy (dialog);
 
   if (ret == GTK_RESPONSE_ACCEPT)
-    start_new_game ();
+    game_start_new_game (game);
 }
 
 static void
 random_teleport_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
 {
-  game_keypress (KBD_RTEL);
+  game_keypress (game, GAME_KEYBOARD_CONTROL_RTEL);
 }
 
 static void
 safe_teleport_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
 {
-  game_keypress (KBD_TELE);
+  game_keypress (game, GAME_KEYBOARD_CONTROL_TELE);
 }
 
 static void
 wait_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
 {
-  game_keypress (KBD_WAIT);
+  game_keypress (game, GAME_KEYBOARD_CONTROL_WAIT);
 }
 
 static gboolean
@@ -356,6 +355,61 @@ create_category_from_key (const char *key, gpointer user_data)
   return games_scores_category_new (key, name);
 }
 
+static void
+mouse_cb (GtkGestureMultiPress *gesture,
+          gint                  n_press,
+          gdouble               x,
+          gdouble               y,
+          gpointer              data)
+{
+  int dx, dy;
+
+  if (game_get_state(game) != GAME_STATE_PLAYING)
+    return;
+
+  game_get_dir (game, (int)x, (int)y, &dx, &dy);
+
+  if (game_player_move (game, dx, dy)) {
+    game_move_robots (game);
+  }
+
+  return;
+}
+
+static void
+move_cb (GtkEventControllerMotion *controller,
+         gdouble                   x,
+         gdouble                   y,
+         gpointer                  data)
+{
+  int dx, dy;
+  GdkWindow * window;
+
+  window = gtk_widget_get_window (game_area);
+  if (game_get_state (game) != GAME_STATE_PLAYING) {
+    set_cursor_default (window);
+  } else {
+    game_get_dir (game, (int)x, (int)y, &dx, &dy);
+    set_cursor_by_direction (window, dx, dy);
+  }
+
+  return;
+}
+
+/**
+ * Initialises the keyboard actions when the game first starts up
+ **/
+static void
+init_keyboard (void)
+{
+  GtkEventController *key_controller;
+
+  key_controller = gtk_event_controller_key_new (window);
+
+  g_signal_connect (G_OBJECT (key_controller), "key-pressed",
+                    G_CALLBACK (keyboard_cb), 0);
+}
+
 static void
 activate (GtkApplication *app, gpointer user_data)
 {
@@ -367,6 +421,8 @@ activate (GtkApplication *app, gpointer user_data)
   GtkGesture *click_controller;
   GtkEventController *motion_controller;
 
+  game = game_new ();
+
   if (window != NULL)
   {
     gtk_window_present_with_time (GTK_WINDOW (window), gtk_get_current_event_time ());
@@ -486,7 +542,8 @@ activate (GtkApplication *app, gpointer user_data)
     exit (1);
   }
 
-  load_properties ();
+  GError *load_properties_error = NULL;
+  load_properties (&load_properties_error);
 
   GError *load_game_graphics_error = NULL;
   load_game_graphics (&load_game_graphics_error);
@@ -504,7 +561,8 @@ activate (GtkApplication *app, gpointer user_data)
     exit (1);
   }
 
-  init_game ();
+  init_keyboard ();
+  game_init_game (game);
 
   g_settings_sync ();
 }
diff --git a/src/graphics.vala b/src/graphics.vala
index 8c2bacc..bedc22f 100644
--- a/src/graphics.vala
+++ b/src/graphics.vala
@@ -54,15 +54,6 @@ public const int NUM_PLAYER_ANIMATIONS = 4;
 public const int PLAYER_WAVE_WAIT      = 20;
 public const int PLAYER_NUM_WAVES      = 2;
 
-public enum ObjectType {
-    PLAYER = 0,
-    HEAP = 1,
-    ROBOT1 = 2,
-    ROBOT2 = 3,
-    NONE = 99,
-    FOO = 666,
-}
-
 public int tile_width = 0;
 public int tile_height = 0;
 
diff --git a/src/keyboard.c b/src/keyboard.c
index 6173ca1..e5f4408 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -27,7 +27,6 @@
 
 #include "keyboard.h"
 #include "riiv.h"
-#include "game.h"
 
 /**********************************************************************/
 /* File Static Variables                                              */
@@ -98,7 +97,7 @@ keyboard_cb (GtkEventControllerKey *controller,
 
   for (i = 0; i < 12; ++i) {
     if (keyval == toupper (control_keys[i])) {
-      game_keypress (i);
+      game_keypress (game, (GameKeyboardControl)i);
       return TRUE;
     }
   }
diff --git a/src/main.vapi b/src/main.vapi
index 2882a9f..63cbfb0 100644
--- a/src/main.vapi
+++ b/src/main.vapi
@@ -1,7 +1,5 @@
 public void keyboard_set (uint keys[9]);
 
-public void start_new_game ();
-
 [CCode (cheader_filename = "gnome-robots.h")]
 public Gtk.Window window;
 
@@ -11,3 +9,10 @@ public Gtk.Widget game_area;
 [CCode (cheader_filename = "gnome-robots.h")]
 public GLib.Settings settings;
 
+[CCode (cheader_filename = "gnome-robots.h")]
+public Games.Scores.Context highscores;
+
+public void update_game_status (int score, int level, int safe_teleports);
+public void set_move_action_sensitivity (bool state);
+public string category_name_from_key (string key);
+
diff --git a/src/meson.build b/src/meson.build
index 4fd8e94..e3b42bd 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -20,6 +20,8 @@ vala_sources = files(
     'cursors.vala',
     'properties.vala',
     'graphics.vala',
+    'arena.vala',
+    'game.vala',
 )
 
 vala_lib = static_library('riiv',
@@ -42,7 +44,6 @@ riiv_dependency = declare_dependency(
 )
 
 sources = files(
-    'game.c',
     'gnome-robots.c',
     'keyboard.c',
 )
diff --git a/src/properties.vala b/src/properties.vala
index b28e49d..2cd188f 100644
--- a/src/properties.vala
+++ b/src/properties.vala
@@ -112,7 +112,7 @@ void type_selection (int num) {
 
     game_configs.set_current_index ((uint)properties.selected_config);
 
-    start_new_game ();
+    game.start_new_game ();
 }
 
 /**
@@ -317,7 +317,7 @@ public void show_properties_dialog () {
 /**
  * loads the game properties from a file
  **/
-public void load_properties () {
+public void load_properties () throws Error {
     load_keys ();
 
     var bgcolour = settings.get_string (KEY_BACKGROUND_COLOR);



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