[gnome-2048] Add CLI.



commit 2523b2638e588e6934baa0cf3d8443c628db6ebf
Author: Arnaud Bonatti <arnaud bonatti gmail com>
Date:   Tue Oct 29 23:33:14 2019 +0100

    Add CLI.

 data/gnome-2048.6    |   3 +
 src/application.vala |  43 ++++++++-
 src/cli.vala         | 257 +++++++++++++++++++++++++++++++++++++++++++++++++++
 src/game.vala        |   8 +-
 src/grid.vala        |   4 +
 src/meson.build      |   1 +
 6 files changed, 307 insertions(+), 9 deletions(-)
---
diff --git a/data/gnome-2048.6 b/data/gnome-2048.6
index bd461a1..c5cb1ad 100644
--- a/data/gnome-2048.6
+++ b/data/gnome-2048.6
@@ -30,6 +30,9 @@ Use your keyboard’s arrow keys or gestures to slide all tiles in the desired d
 Gnome 2048 only adds tiles of value 2, as opposed to the original 2048 game. Also, it allows you to play on 
grids of various size.
 .SH OPTIONS
 .TP
+.B \-\-cli=<command>
+Play in the terminal. See “--cli=help” for documentation.
+.TP
 .B \-s, \-\-size=<size>
 Changes the size of the grid. Size must be between 2 and 9, or in the form 2x3.
 .TP
diff --git a/src/application.vala b/src/application.vala
index b91c1ab..f402e65 100644
--- a/src/application.vala
+++ b/src/application.vala
@@ -26,22 +26,35 @@ private class TwentyFortyEight : Gtk.Application
 
     private static bool show_version;
     private static string? size = null;
+    private static string? cli = null;
     private uint8 cols = 0;
     private uint8 rows = 0;
 
     private const OptionEntry [] option_entries =
     {
         /* Translators: command-line option description, see 'gnome-2048 --help' */
-        { "size", 's',      OptionFlags.NONE, OptionArg.STRING, ref size,           N_("Start new game of 
given size"),
+        { "cli", 0,         OptionFlags.OPTIONAL_ARG, OptionArg.CALLBACK, (void*) _cli, N_("Play in the 
terminal (see “--cli=help”)"),
 
-        /* Translators: in the command-line options description, text to indicate the user should specify a 
size, see 'gnome-2048 --help' */
-                                                                                    N_("SIZE") },
+        /* Translators: in the command-line options description, text to indicate the user should give a 
command after '--cli' for playing in the terminal, see 'gnome-2048 --help' */
+                                                                                        N_("COMMAND") },
 
         /* Translators: command-line option description, see 'gnome-2048 --help' */
-        { "version", 'v',   OptionFlags.NONE, OptionArg.NONE,   ref show_version,   N_("Print release 
version and exit"), null },
+        { "size", 's',      OptionFlags.NONE, OptionArg.STRING, ref size,               N_("Start new game 
of given size"),
+
+        /* Translators: in the command-line options description, text to indicate the user should specify a 
size after '--size', see 'gnome-2048 --help' */
+                                                                                        N_("SIZE") },
+
+        /* Translators: command-line option description, see 'gnome-2048 --help' */
+        { "version", 'v',   OptionFlags.NONE, OptionArg.NONE,   ref show_version,       N_("Print release 
version and exit"), null },
         {}
     };
 
+    private bool _cli (string? option_name, string? val)
+    {
+        cli = option_name == null ? "" : (!) option_name;  // TODO report bug: should probably be val...
+        return true;
+    }
+
     private const GLib.ActionEntry [] action_entries =
     {
         { "quit", quit_cb }
@@ -94,7 +107,7 @@ private class TwentyFortyEight : Gtk.Application
         Object (application_id: "org.gnome.TwentyFortyEight", flags: ApplicationFlags.FLAGS_NONE);
     }
 
-    protected override int handle_local_options (GLib.VariantDict options)
+    protected override int handle_local_options (GLib.VariantDict options)  // options will be empty, we 
used a custom OptionContext
     {
         if (show_version)
         {
@@ -110,6 +123,26 @@ private class TwentyFortyEight : Gtk.Application
             return Posix.EXIT_FAILURE;
         }
 
+        if (cli != null)
+        {
+            if ((!) cli == "help" || (!) cli == "HELP")
+            {
+                string help_string = ""
+                    + "\n" + "To play GNOME 2048 in command-line:"
+                    + "\n" + "  --cli         " + "Display current game. Alias: “status” or “show”."
+                    + "\n" + "  --cli new     " + "Start a new game; for changing size, use --size."
+                    + "\n"
+                    + "\n" + "  --cli up      " + "Move tiles up.    Alias: “u”."
+                    + "\n" + "  --cli down    " + "Move tiles down.  Alias: “d”."
+                    + "\n" + "  --cli left    " + "Move tiles left.  Alias: “l”."
+                    + "\n" + "  --cli right   " + "Move tiles right. Alias: “r”."
+                    + "\n\n";
+                stdout.printf (help_string);
+                return Posix.EXIT_SUCCESS;
+            }
+            return CLI.play_cli ((!) cli, "org.gnome.TwentyFortyEight", ref cols, ref rows);
+        }
+
         /* Activate */
         return -1;
     }
diff --git a/src/cli.vala b/src/cli.vala
new file mode 100644
index 0000000..aedb527
--- /dev/null
+++ b/src/cli.vala
@@ -0,0 +1,257 @@
+/*
+   This file is part of GNOME 2048.
+
+   Copyright (C) 2019 Arnaud Bonatti <arnaud bonatti gmail com>
+
+   GNOME 2048 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.
+
+   GNOME 2048 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 GNOME 2048.  If not, see <https://www.gnu.org/licenses/>.
+*/
+
+namespace CLI
+{
+    private static int play_cli (string cli, string schema_name, ref uint8 cols, ref uint8 rows)
+    {
+        if ((cols != 0 || rows != 0) && cli != "new")
+        {
+            warning ("Size can only be given for new games." + "\n");
+            return Posix.EXIT_FAILURE;
+        }
+
+        string saved_path = Path.build_filename (Environment.get_user_data_dir (), "gnome-2048", "saved");
+
+        GLib.Settings settings = new GLib.Settings (schema_name);
+
+        bool new_game;
+        Grid grid;
+        if (cols != 0 || rows != 0)
+        {
+            if (cols == 0 || rows == 0)
+                assert_not_reached ();
+
+            settings.delay ();
+            settings.set_int ("cols", cols);
+            settings.set_int ("rows", rows);
+            settings.apply ();
+            GLib.Settings.sync ();
+
+            grid = new Grid (rows, cols);
+            new_game = true;
+        }
+        else
+        {
+            cols = (uint8) settings.get_int ("cols");  // schema ranges rows
+            rows = (uint8) settings.get_int ("rows"); // and cols from 1 to 9
+
+            grid = new Grid (rows, cols);
+            if (cli == "new")
+                new_game = true;
+            else if (!grid.restore_game (saved_path))
+                new_game = true;
+            else
+            {
+                new_game = false;
+                cols = grid.cols;
+                rows = grid.rows;
+            }
+        }
+        grid.target_value = (uint) settings.get_int ("target-value");
+
+        if (new_game)
+        {
+            Tile tile;
+            grid.new_tile (out tile);   // TODO clean that
+        }
+
+        switch (cli)
+        {
+            case "help":
+            case "HELP":
+                assert_not_reached ();  // should be handled by the caller
+
+            case "":
+            case "show":
+            case "status":
+                if (new_game)
+                    break;
+
+                print_board (cols, rows, grid, /* do congrat */ false, /* print score */ true);
+                return Posix.EXIT_SUCCESS;
+
+            case "new": // creation already handled, need saving
+                break;
+
+            case "r":
+            case "right":
+                if (!request_move (grid, MoveRequest.RIGHT))
+                    return Posix.EXIT_FAILURE;
+                break;
+
+            case "l":
+            case "left":
+                if (!request_move (grid, MoveRequest.LEFT))
+                    return Posix.EXIT_FAILURE;
+                break;
+
+            case "u":
+            case "up":
+                if (!request_move (grid, MoveRequest.UP))
+                    return Posix.EXIT_FAILURE;
+                break;
+
+            case "d":
+            case "down":
+                if (!request_move (grid, MoveRequest.DOWN))
+                    return Posix.EXIT_FAILURE;
+                break;
+
+            default:
+                warning ("Cannot parse “--cli” command, aborting." + "\n");
+                return Posix.EXIT_FAILURE;
+        }
+
+        Tile? new_tile = null;
+        if (!grid.is_finished ())
+        {
+            grid.new_tile (out new_tile);
+            if (cli == "new")
+                new_tile = null;
+        }
+
+        bool do_congrat = settings.get_boolean ("do-congrat");
+        if (do_congrat && grid.target_value_reached)
+            settings.set_boolean ("do-congrat", false);
+
+        print_board (cols, rows, grid, do_congrat, /* print score */ false, new_tile);
+
+        if (!grid.is_finished ()    // one more tile since previously
+         || grid.cols != grid.rows
+         || grid.cols < 3 || grid.cols > 5)
+            grid.save_game (saved_path);
+
+        return Posix.EXIT_SUCCESS;
+    }
+
+    /*\
+    * * move request
+    \*/
+
+    private static bool request_move (Grid grid, MoveRequest req)
+    {
+        if (!can_play (grid))
+            return false;
+
+        Gee.LinkedList<TileMovement?> to_move = new Gee.LinkedList<TileMovement?> ();
+        Gee.LinkedList<TileMovement?> to_hide = new Gee.LinkedList<TileMovement?> ();
+        Gee.LinkedList<Tile?>         to_show = new Gee.LinkedList<Tile?> ();
+
+        grid.move (req, ref to_move, ref to_hide, ref to_show); // TODO do not request so many unused things
+        if (!has_moves (ref to_move, ref to_hide))
+            return false;
+
+        return true;
+    }
+
+    private static inline bool can_play (Grid grid)
+    {
+        if (!grid.is_finished ())
+            return true;
+
+        warning ("Grid is finished, impossible to move." + "\n");
+        return false;
+    }
+
+    private static inline bool has_moves (ref Gee.LinkedList<TileMovement?> to_move,
+                                          ref Gee.LinkedList<TileMovement?> to_hide)
+    {
+        if (to_move.size != 0 || to_hide.size != 0)
+            return true;
+
+        warning ("Impossible to move in that direction." + "\n");
+        return false;
+    }
+
+    /*\
+    * * print board
+    \*/
+
+    private static void print_board (uint8 cols, uint8 rows, Grid grid, bool do_congrat, bool print_score, 
Tile? new_tile = null)
+    {
+        string board = "";
+
+        board += "\n ┏";
+        for (uint8 i = 0; i <= 7 * cols; i++)
+            board += "━";
+        board += "┓\n";
+
+        for (uint8 y = 0; y < rows; y++)
+        {
+            board += " ┃";
+            for (uint8 x = 0; x < cols; x++)
+            {
+                if (grid [y, x] == 0)               // FIXME inverted coordinates
+                    board += "       ";
+                else
+                    board += " ╭────╮";
+            }
+            board += " ┃\n ┃";
+            for (uint8 x = 0; x < cols; x++)
+            {
+                uint8 tile_value = grid [y, x];
+                if (tile_value == 0)
+                    board += "       ";
+                else
+                {
+                    string tile_value_string = tile_value.to_string ();
+                    if (tile_value == 1 && new_tile != null && ((!) new_tile).pos.col == x && ((!) 
new_tile).pos.row == y)
+                        board +=  " │ +1 │";
+                    else if (tile_value_string.length == 1)
+                        board += @" │  $tile_value_string │";
+                    else if (tile_value_string.length == 2)
+                        board += @" │ $tile_value_string │";
+                    else assert_not_reached ();
+                }
+            }
+            board += " ┃\n ┃";
+            for (uint8 x = 0; x < cols; x++)
+            {
+                if (grid [y, x] == 0)
+                    board += "       ";
+                else
+                    board += " ╰────╯";
+            }
+            board += " ┃\n";
+        }
+
+        board += " ┗";
+        for (uint8 i = 0; i <= 7 * cols; i++)
+            board += "━";
+        board += "┛\n\n";
+
+        if (do_congrat && grid.target_value_reached) // try to keep string as in game-window.vala
+            board += " " + "You have obtained the %u tile for the first time!".printf 
(grid.target_value_simple) + "\n\n";
+
+        if (grid.is_finished ())
+        {
+            if (print_score     // called from “--cli show”
+             || grid.cols != grid.rows
+             || grid.cols < 3 || grid.cols > 5)
+                board += @" Game is finished! Your score is $(grid.get_score ()).\n\n";
+            else                // game was just finished and score can be saved
+                board += @" Game is finished! Your score is $(grid.get_score ()). (If you want to save it, 
use GNOME 2048 graphical interface.)\n\n"; // TODO save score
+        }
+        else if (print_score)
+            board += @" Your score is $(grid.get_score ()).\n\n";
+
+        stdout.printf (board);
+    }
+}
diff --git a/src/game.vala b/src/game.vala
index 1150777..d2fca7a 100644
--- a/src/game.vala
+++ b/src/game.vala
@@ -68,8 +68,8 @@ private class Game : Object
 
     internal Game (ref GLib.Settings settings)
     {
-        uint8 rows = (uint8) settings.get_int ("rows");  // schema ranges rows
-        uint8 cols = (uint8) settings.get_int ("cols"); // and cols from 1 to 9
+        uint8 cols = (uint8) settings.get_int ("cols");  // schema ranges cols
+        uint8 rows = (uint8) settings.get_int ("rows"); // and rows from 1 to 9
         _init_grid (rows, cols, out _grid, ref settings);
     }
 
@@ -127,8 +127,8 @@ private class Game : Object
         _grid.clear ();
         _clear_history ();
 
-        uint8 rows = (uint8) settings.get_int ("rows");  // schema ranges rows
-        uint8 cols = (uint8) settings.get_int ("cols"); // and cols from 1 to 9
+        uint8 cols = (uint8) settings.get_int ("cols");  // schema ranges cols
+        uint8 rows = (uint8) settings.get_int ("rows"); // and rows from 1 to 9
 
         if ((rows != _grid.rows) || (cols != _grid.cols))
         {
diff --git a/src/grid.vala b/src/grid.vala
index 3e9356f..c726e6a 100644
--- a/src/grid.vala
+++ b/src/grid.vala
@@ -26,6 +26,7 @@ private class Grid : Object
     [CCode (notify = false)] public uint8 cols { internal get; protected construct; }
 
     [CCode (notify = false)] internal uint target_value          { internal get; internal set; default = 0; }
+    [CCode (notify = false)] internal uint target_value_simple   { internal get; private  set; default = 0; }
     [CCode (notify = false)] internal bool target_value_reached  { internal get; internal set; default = 
false; }
 
     construct
@@ -91,7 +92,10 @@ private class Grid : Object
                 _move_right ((int8) _cols, (int8) _rows, ref max_changed, ref _grid, ref to_move, ref 
to_hide, ref to_show); break;
         }
         if (Math.pow (2, max_changed) >= target_value)
+        {
+            target_value_simple = max_changed;
             target_value_reached = true;
+        }
     }
 
     private static void _move_down (int8 cols,
diff --git a/src/meson.build b/src/meson.build
index b362553..f77b198 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -23,6 +23,7 @@ resources = gnome.compile_resources(
 
 gnome_2048_sources = [
   'application.vala',
+  'cli.vala',
   'config.vapi',
   'game.vala',
   'game-headerbar.vala',


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