[gnome-tetravex] Add CLI.
- From: Arnaud B. <arnaudb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-tetravex] Add CLI.
- Date: Fri, 8 Nov 2019 21:19:24 +0000 (UTC)
commit 652b50f6b07fabc9fdfbafb6728516939c713ed1
Author: Arnaud Bonatti <arnaud bonatti gmail com>
Date: Wed Oct 30 02:55:57 2019 +0100
Add CLI.
data/gnome-tetravex.6 | 3 +
src/cli.vala | 313 ++++++++++++++++++++++++++++++++++++++++++++++++
src/gnome-tetravex.vala | 130 +++++++++++++++-----
src/history.vala | 53 ++++++++
src/meson.build | 1 +
src/puzzle.vala | 145 ++++++++++++++++------
src/score-overlay.vala | 41 +++++++
7 files changed, 620 insertions(+), 66 deletions(-)
---
diff --git a/data/gnome-tetravex.6 b/data/gnome-tetravex.6
index dd994b1..b2c613e 100644
--- a/data/gnome-tetravex.6
+++ b/data/gnome-tetravex.6
@@ -27,6 +27,9 @@ that the same numbers are touching each other. Your game is timed,
these times are stored in a system-wide scoreboard.
.SH OPTIONS
.TP
+.B \-\-cli=COMMAND
+Play in the terminal (see “--cli=help”)
+.TP
.B \-c, \-\-colors=NUMBER
Set number of colors (2-10)
.TP
diff --git a/src/cli.vala b/src/cli.vala
new file mode 100644
index 0000000..a8dc928
--- /dev/null
+++ b/src/cli.vala
@@ -0,0 +1,313 @@
+/* -*- Mode: vala; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+
+ This file is part of GNOME Tetravex.
+
+ Copyright (C) 2019 Arnaud Bonatti
+
+ GNOME Tetravex 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 2 of the License, or
+ (at your option) any later version.
+
+ GNOME Tetravex 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 GNOME Tetravex. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+private const string KEY_GRID_SIZE = "grid-size";
+
+namespace CLI
+{
+ private static int play_cli (string cli, string schema_name, out GLib.Settings settings, out Variant
saved_game, out bool can_restore, out Puzzle puzzle, ref int colors, ref int game_size)
+ {
+ settings = new GLib.Settings (schema_name);
+
+ saved_game = settings.get_value ("saved-game");
+ can_restore = Puzzle.is_valid_saved_game (saved_game, /* restore finished game */ true);
+
+ uint8 size;
+ bool new_puzzle;
+ if (game_size != int.MIN)
+ {
+ settings.set_int (KEY_GRID_SIZE, game_size);
+ size = (uint8) game_size;
+ puzzle = new Puzzle (size, (uint8) colors);
+ new_puzzle = true;
+ }
+ else if (colors != 10 || (!) cli == "new")
+ {
+ size = (uint8) settings.get_int (KEY_GRID_SIZE);
+ puzzle = new Puzzle (size, (uint8) colors);
+ new_puzzle = true;
+ }
+ else if (can_restore)
+ {
+ size = (uint8) settings.get_int (KEY_GRID_SIZE);
+ puzzle = new Puzzle.restore ((!) saved_game);
+ new_puzzle = false;
+ }
+ else
+ {
+ size = (uint8) settings.get_int (KEY_GRID_SIZE);
+ puzzle = new Puzzle (size, 10);
+ new_puzzle = true;
+ }
+
+ switch ((!) cli) // TODO add translated commands? add translations?
+ {
+ case "help":
+ case "HELP":
+ assert_not_reached (); // should be handled by the caller
+
+ case "":
+ case "show":
+ case "status":
+ if (new_puzzle)
+ break;
+
+ print_board (puzzle, size);
+ return Posix.EXIT_SUCCESS;
+
+ case "new": // creation already handled, need saving
+ break;
+
+ case "up":
+ case "l-up":
+ if (!cli_move_tiles (puzzle, new_puzzle, /* left board */ true, Direction.UP, "Cannot move
up left-board tiles."))
+ return Posix.EXIT_FAILURE;
+ break;
+ case "down":
+ case "l-down":
+ if (!cli_move_tiles (puzzle, new_puzzle, /* left board */ true, Direction.DOWN, "Cannot move
down left-board tiles."))
+ return Posix.EXIT_FAILURE;
+ break;
+ case "left":
+ case "l-left":
+ if (!cli_move_tiles (puzzle, new_puzzle, /* left board */ true, Direction.LEFT, "Cannot move
left left-board tiles."))
+ return Posix.EXIT_FAILURE;
+ break;
+ case "right":
+ case "l-right":
+ if (!cli_move_tiles (puzzle, new_puzzle, /* left board */ true, Direction.RIGHT, "Cannot
move right left-board tiles."))
+ return Posix.EXIT_FAILURE;
+ break;
+
+ case "r-up":
+ if (!cli_move_tiles (puzzle, new_puzzle, /* left board */ false, Direction.UP, "Cannot move
up right-board tiles."))
+ return Posix.EXIT_FAILURE;
+ break;
+ case "r-down":
+ if (!cli_move_tiles (puzzle, new_puzzle, /* left board */ false, Direction.DOWN, "Cannot
move down right-board tiles."))
+ return Posix.EXIT_FAILURE;
+ break;
+ case "r-left":
+ if (!cli_move_tiles (puzzle, new_puzzle, /* left board */ false, Direction.LEFT, "Cannot
move left right-board tiles."))
+ return Posix.EXIT_FAILURE;
+ break;
+ case "r-right":
+ if (!cli_move_tiles (puzzle, new_puzzle, /* left board */ false, Direction.RIGHT, "Cannot
move right right-board tiles."))
+ return Posix.EXIT_FAILURE;
+ break;
+
+ case "end":
+ case "finish":
+ if (new_puzzle || puzzle.is_solved)
+ {
+ puzzle_is_solved_message (/* alternative message */ true);
+ return Posix.EXIT_FAILURE;
+ }
+
+ if (puzzle.is_solved_right)
+ puzzle.finish (/* duration */ 0);
+ else if (!puzzle.move_last_tile_if_possible ())
+ {
+ warning ("Cannot finish automatically. If you want to give up and view the solution, use
“solve”." + "\n");
+ return Posix.EXIT_FAILURE;
+ }
+ break;
+
+ case "solve":
+ if (new_puzzle || puzzle.is_solved)
+ {
+ puzzle_is_solved_message (/* alternative message */ true);
+ return Posix.EXIT_FAILURE;
+ }
+
+ puzzle.solve ();
+ break;
+
+ default:
+ if (new_puzzle || puzzle.is_solved)
+ {
+ puzzle_is_solved_message ();
+ return Posix.EXIT_FAILURE;
+ }
+
+ uint8 tile_1_x;
+ uint8 tile_1_y;
+ uint8 tile_2_x;
+ uint8 tile_2_y;
+ if (!parse_cli ((!) cli, size, out tile_1_x, out tile_1_y, out tile_2_x, out tile_2_y))
+ {
+ warning ("Cannot parse “--cli” command, aborting." + "\n");
+ return Posix.EXIT_FAILURE;
+ }
+ if (puzzle.get_tile (tile_1_x, tile_1_y) == null
+ && puzzle.get_tile (tile_2_x, tile_2_y) == null)
+ {
+ warning ("Both given tiles are empty, aborting." + "\n");
+ return Posix.EXIT_FAILURE;
+ }
+ if (!puzzle.can_switch (tile_1_x, tile_1_y, tile_2_x, tile_2_y))
+ {
+ warning ("Cannot swap the given tiles, aborting." + "\n");
+ print_board (puzzle, size);
+ return Posix.EXIT_FAILURE;
+ }
+ puzzle.switch_tiles (tile_1_x, tile_1_y, tile_2_x, tile_2_y);
+ break;
+ }
+
+ print_board (puzzle, size);
+
+ settings.set_value ("saved-game", puzzle.to_variant (/* save time */ false));
+
+ return Posix.EXIT_SUCCESS;
+ }
+
+ private static bool cli_move_tiles (Puzzle puzzle, bool new_puzzle, bool left_board, Direction
direction, string warning_string)
+ {
+ if (new_puzzle || puzzle.is_solved)
+ {
+ puzzle_is_solved_message ();
+ return false;
+ }
+ bool success;
+ switch (direction)
+ {
+ case Direction.UP: success = puzzle.move_up (left_board); break;
+ case Direction.DOWN: success = puzzle.move_down (left_board); break;
+ case Direction.LEFT: success = puzzle.move_left (left_board); break;
+ case Direction.RIGHT: success = puzzle.move_right (left_board); break;
+ default: assert_not_reached ();
+ }
+ if (!success)
+ {
+ warning (@"$warning_string\n");
+ return false;
+ }
+ return true;
+ }
+
+ private static void puzzle_is_solved_message (bool alternative_message = false)
+ {
+ if (alternative_message)
+ warning ("Puzzle is already solved! If you want to start a new one, use “new”." + "\n");
+ else
+ warning ("Puzzle is solved! If you want to start a new one, use “new”." + "\n");
+ }
+
+ private static void print_board (Puzzle puzzle, uint8 size)
+ {
+ stdout.printf ("\n");
+ for (uint8 y = 0; y < size; y++)
+ {
+ for (uint8 x = 0; x < 2 * size; x++)
+ {
+ Tile? tile = puzzle.get_tile (x, y);
+ if (tile == null)
+ stdout.printf (" ┌─ ─┐");
+ else
+ stdout.printf (@" ┌─$(((!) tile).north)─┐");
+ if (x == size - 1)
+ stdout.printf (" ");
+ }
+ stdout.printf ("\n");
+ for (uint8 x = 0; x < 2 * size; x++)
+ {
+ Tile? tile = puzzle.get_tile (x, y);
+ if (tile == null)
+ stdout.printf (" ");
+ else
+ stdout.printf (@" $(((!) tile).west) · $(((!) tile).east)");
+ if (x == size - 1)
+ stdout.printf (" ");
+ }
+ stdout.printf ("\n");
+ for (uint8 x = 0; x < 2 * size; x++)
+ {
+ Tile? tile = puzzle.get_tile (x, y);
+ if (tile == null)
+ stdout.printf (" └─ ─┘");
+ else
+ stdout.printf (@" └─$(((!) tile).south)─┘");
+ if (x == size - 1)
+ stdout.printf (" ");
+ }
+ stdout.printf ("\n");
+ }
+ stdout.printf ("\n");
+ if (puzzle.is_solved)
+ stdout.printf ("Puzzle is solved!\n\n");
+ }
+
+ private static bool parse_cli (string cli, uint8 size,
+ out uint8 tile_1_x, out uint8 tile_1_y,
+ out uint8 tile_2_x, out uint8 tile_2_y)
+ {
+ tile_1_x = uint8.MAX; //g
+ tile_1_y = uint8.MAX; // ar
+ tile_2_x = uint8.MAX; // ba
+ tile_2_y = uint8.MAX; // ge
+
+ if (cli.length != 4)
+ return false;
+
+ char column_char = cli [0];
+ if (!is_valid_column (column_char, size, out tile_1_x))
+ return false;
+
+ column_char = cli [2];
+ if (!is_valid_column (column_char, size, out tile_2_x))
+ return false;
+
+ uint64 test;
+ if (!uint64.try_parse (cli [1].to_string (), out test))
+ return false;
+ if (test <= 0 || test > size)
+ return false;
+ tile_1_y = (uint8) test - 1;
+
+ if (!uint64.try_parse (cli [3].to_string (), out test))
+ return false;
+ if (test <= 0 || test > size)
+ return false;
+ tile_2_y = (uint8) test - 1;
+
+ return true;
+ }
+
+ private static bool is_valid_column (char column_char, uint8 size, out uint8 column)
+ {
+ switch (column_char)
+ {
+ case 'a': column = 0; return true;
+ case 'b': column = 1; return true;
+ case 'c': column = 2; return size >= 3;
+ case 'd': column = 3; return size >= 4;
+ case 'e': column = 4; return size >= 5;
+ case 'f': column = 5; return size == 6;
+ case 'A': column = size; return true;
+ case 'B': column = size + 1; return true;
+ case 'C': column = size + 2; return size >= 3;
+ case 'D': column = size + 3; return size >= 4;
+ case 'E': column = size + 4; return size >= 5;
+ case 'F': column = size + 5; return size == 6;
+ default : column = uint8.MAX; return false;
+ }
+ }
+}
diff --git a/src/gnome-tetravex.vala b/src/gnome-tetravex.vala
index 054a1b2..104d078 100644
--- a/src/gnome-tetravex.vala
+++ b/src/gnome-tetravex.vala
@@ -26,18 +26,18 @@ private class Tetravex : Gtk.Application
/* Translators: name of the program, as seen in the headerbar, in GNOME Shell, or in the about dialog */
private const string PROGRAM_NAME = _("Tetravex");
- private const string KEY_GRID_SIZE = "grid-size";
-
private static bool start_paused = false;
private static bool restore_on_start = false;
private static int game_size = int.MIN;
private static int colors = 10;
+ private static string? cli = null;
private GLib.Settings settings;
private Puzzle puzzle;
private bool puzzle_init_done = false;
private Label clock_label;
+ private Box clock_box;
private History history;
private PuzzleView view;
@@ -67,6 +67,12 @@ private class Tetravex : Gtk.Application
private static string? [] remaining = new string? [1];
private const OptionEntry [] option_entries =
{
+ /* Translators: command-line option description, see 'gnome-tetravex --help' */
+ { "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 give a
command after '--cli' for playing in the terminal, see 'gnome-tetravex --help' */
+ N_("COMMAND") },
+
/* Translators: command-line option description, see 'gnome-tetravex --help' */
{ "colors", 'c', OptionFlags.NONE, OptionArg.INT, ref colors, N_("Set number of
colors (2-10)"),
@@ -92,6 +98,12 @@ private class Tetravex : Gtk.Application
{}
};
+ 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 =
{
{ "new-game", new_game_cb },
@@ -165,11 +177,43 @@ private class Tetravex : Gtk.Application
if (remaining [0] != null)
{
- /* Translators: command-line error message, displayed for an invalid CLI command; see
'gnome-tetravex cli' */
+ /* Translators: command-line error message, displayed for an invalid CLI command; see
'gnome-tetravex --cli new A1b2' */
stderr.printf (N_("Failed to parse command-line arguments.\n"));
return Posix.EXIT_FAILURE;
}
+ if (cli != null)
+ {
+ if ((!) cli == "help" || (!) cli == "HELP")
+ {
+ stdout.printf ("\n" + "To play GNOME Tetravex in command-line:");
+ stdout.printf ("\n --cli A1b2 " + "Invert two tiles, the one in A1, and the one in b2.");
+ stdout.printf ("\n " + "An uppercase targets a tile from the initial board.");
+ stdout.printf ("\n " + "A lowercase targets a tile in the left/final board.");
+ stdout.printf ("\n " + "Digits specify the rows of the two tiles to invert.");
+ stdout.printf ("\n");
+ stdout.printf ("\n --cli " + "Show the current puzzle. Alias: “status” or “show”.");
+ stdout.printf ("\n --cli new " + "Create a new puzzle; for changing size, use --size.");
+ stdout.printf ("\n --cli solve " + "Give up with current puzzle, and view the solution.");
+ stdout.printf ("\n");
+ stdout.printf ("\n --cli finish " + "Finish current puzzle, automatically. Alias: “end”.");
+ stdout.printf ("\n " + "Works for puzzles solved right or if one tile left.");
+ stdout.printf ("\n");
+ stdout.printf ("\n --cli up " + "Move all left-board tiles up by one.");
+ stdout.printf ("\n --cli down " + "Move all left-board tiles down by one.");
+ stdout.printf ("\n --cli left " + "Move all left-board tiles left by one.");
+ stdout.printf ("\n --cli right " + "Move all left-board tiles right by one.");
+ stdout.printf ("\n");
+ stdout.printf ("\n --cli r-up " + "Move all right-board tiles up by one.");
+ stdout.printf ("\n --cli r-down " + "Move all right-board tiles down by one.");
+ stdout.printf ("\n --cli r-left " + "Move all right-board tiles left by one.");
+ stdout.printf ("\n --cli r-right " + "Move all right-board tiles right by one.");
+ stdout.printf ("\n\n");
+ return Posix.EXIT_SUCCESS;
+ }
+ return CLI.play_cli ((!) cli, "org.gnome.Tetravex", out settings, out saved_game, out
can_restore, out puzzle, ref colors, ref game_size);
+ }
+
/* Activate */
return -1;
}
@@ -184,7 +228,7 @@ private class Tetravex : Gtk.Application
settings = new GLib.Settings ("org.gnome.Tetravex");
saved_game = settings.get_value ("saved-game");
- can_restore = Puzzle.is_valid_saved_game (saved_game);
+ can_restore = Puzzle.is_valid_saved_game (saved_game, /* restore finished game */ false);
add_action_entries (action_entries, this);
add_action (settings.create_action ("theme"));
@@ -373,19 +417,18 @@ private class Tetravex : Gtk.Application
new_game_solve_stack.show ();
grid.attach (new_game_solve_stack, 2, 1, 1, 1);
- Box box = new Box (Orientation.HORIZONTAL, /* spacing */ 8);
+ clock_box = new Box (Orientation.HORIZONTAL, /* spacing */ 8);
Image image = new Image.from_icon_name ("preferences-system-time-symbolic", IconSize.MENU);
image.show ();
- box.add (image);
+ clock_box.add (image);
clock_label = new Label ("");
clock_label.show ();
- box.add (clock_label);
- box.halign = Align.CENTER;
- box.valign = Align.BASELINE;
- box.set_margin_top (20);
- box.set_margin_bottom (20);
- box.show ();
- grid.attach (box, 1, 1, 1, 1);
+ clock_box.add (clock_label);
+ clock_box.halign = Align.CENTER;
+ clock_box.valign = Align.BASELINE;
+ clock_box.set_margin_top (20);
+ clock_box.set_margin_bottom (20);
+ grid.attach (clock_box, 1, 1, 1, 1);
undo_action = (SimpleAction) lookup_action ("undo");
redo_action = (SimpleAction) lookup_action ("redo");
@@ -465,8 +508,8 @@ private class Tetravex : Gtk.Application
settings.set_int ("window-width", window_width);
settings.set_int ("window-height", window_height);
settings.set_boolean ("window-is-maximized", is_maximized);
- if (puzzle.game_in_progress && !puzzle.is_solved)
- settings.set_value ("saved-game", puzzle.to_variant ());
+ if (puzzle.game_in_progress)
+ settings.set_value ("saved-game", puzzle.to_variant (/* save time */
!puzzle.tainted_by_command_line));
else if (!can_restore)
settings.@set ("saved-game", "m(yyda(yyyyyyyy)ua(yyyyu))", null);
settings.apply ();
@@ -520,14 +563,19 @@ private class Tetravex : Gtk.Application
else
was_paused = false;
- int size = settings.get_int (KEY_GRID_SIZE);
if (saved_game == null)
+ {
+ int size = settings.get_int (KEY_GRID_SIZE);
puzzle = new Puzzle ((uint8) size, (uint8) colors);
+ clock_box.show ();
+ }
else
{
puzzle = new Puzzle.restore ((!) saved_game);
if (puzzle.is_solved_right)
solved_right_cb ();
+ if (puzzle.tainted_by_command_line)
+ clock_box.hide ();
}
puzzle_init_done = true;
puzzle.tick.connect (tick_cb);
@@ -561,6 +609,9 @@ private class Tetravex : Gtk.Application
private void tick_cb ()
{
+ if (puzzle_init_done && puzzle.tainted_by_command_line)
+ return;
+
int elapsed = 0;
if (puzzle_init_done)
elapsed = (int) puzzle.elapsed; // felt better when + 0.5, but as the clock is still displayed
while the score-overlay displays the exact time, that is regularly feeling odd
@@ -603,21 +654,40 @@ private class Tetravex : Gtk.Application
private void show_end_game_cb (Puzzle puzzle)
{
- DateTime date = new DateTime.now_local ();
- last_history_entry = new HistoryEntry (date, puzzle.size, puzzle.elapsed, /* old history format */
false);
+ if (puzzle.tainted_by_command_line)
+ {
+ if (!puzzle_is_finished) // Ctrl-n has been hit before the animation finished
+ return;
- if (!puzzle_is_finished) // Ctrl-n has been hit before the animation finished
- return;
+ HistoryEntry? best_score;
+ HistoryEntry? second_score;
+ HistoryEntry? third_score;
+ HistoryEntry? worst_score;
+ history.get_fallback_scores (puzzle.size,
+ out best_score,
+ out second_score,
+ out third_score,
+ out worst_score);
+ score_overlay.display_fallback_scores (puzzle.size, best_score, second_score, third_score,
worst_score);
+ }
+ else
+ {
+ DateTime date = new DateTime.now_local ();
+ last_history_entry = new HistoryEntry (date, puzzle.size, puzzle.elapsed, /* old history format
*/ false);
- HistoryEntry? other_score_0;
- HistoryEntry? other_score_1;
- HistoryEntry? other_score_2;
- uint position = history.get_place ((!) last_history_entry,
- puzzle.size,
- out other_score_0,
- out other_score_1,
- out other_score_2);
- score_overlay.set_score (puzzle.size, position, (!) last_history_entry, other_score_0,
other_score_1, other_score_2);
+ if (!puzzle_is_finished) // Ctrl-n has been hit before the animation finished
+ return;
+
+ HistoryEntry? other_score_0;
+ HistoryEntry? other_score_1;
+ HistoryEntry? other_score_2;
+ uint position = history.get_place ((!) last_history_entry,
+ puzzle.size,
+ out other_score_0,
+ out other_score_1,
+ out other_score_2);
+ score_overlay.set_score (puzzle.size, position, (!) last_history_entry, other_score_0,
other_score_1, other_score_2);
+ }
new_game_solve_stack.set_visible_child_name ("new-game");
view.hide_right_sockets ();
@@ -696,7 +766,7 @@ private class Tetravex : Gtk.Application
private bool has_been_solved = false;
private void solve_cb ()
{
- if (puzzle.elapsed < 0.2) // security against multi-click on new-game button
+ if (!puzzle.tainted_by_command_line && puzzle.elapsed < 0.2) // security against multi-click on
new-game button
return;
if (puzzle.game_in_progress)
diff --git a/src/history.vala b/src/history.vala
index ca95b97..80a9de5 100644
--- a/src/history.vala
+++ b/src/history.vala
@@ -121,6 +121,59 @@ private class History : Object
return first_entry == null;
}
+ internal void get_fallback_scores (uint8 puzzle_size,
+ out HistoryEntry? fallback_score_0,
+ out HistoryEntry? fallback_score_1,
+ out HistoryEntry? fallback_score_2,
+ out HistoryEntry? fallback_score_3)
+ {
+ unowned List<HistoryEntry>? tmp_item = entries.first ();
+
+ while (tmp_item != null && ((!) tmp_item).data.size < puzzle_size)
+ { tmp_item = ((!) tmp_item).next; }
+
+ if (tmp_item == null || ((!) tmp_item).data.size != puzzle_size)
+ {
+ fallback_score_0 = null;
+ fallback_score_1 = null;
+ fallback_score_2 = null;
+ fallback_score_3 = null;
+ return;
+ }
+ fallback_score_0 = ((!) tmp_item).data;
+
+ tmp_item = ((!) tmp_item).next;
+ if (tmp_item == null || ((!) tmp_item).data.size != puzzle_size)
+ {
+ fallback_score_1 = null;
+ fallback_score_2 = null;
+ fallback_score_3 = null;
+ return;
+ }
+ fallback_score_1 = ((!) tmp_item).data;
+
+ tmp_item = ((!) tmp_item).next;
+ if (tmp_item == null || ((!) tmp_item).data.size != puzzle_size)
+ {
+ fallback_score_2 = null;
+ fallback_score_3 = null;
+ return;
+ }
+ fallback_score_2 = ((!) tmp_item).data;
+
+ tmp_item = ((!) tmp_item).next;
+ if (tmp_item == null || ((!) tmp_item).data.size != puzzle_size)
+ {
+ fallback_score_3 = null;
+ return;
+ }
+
+ unowned List<HistoryEntry> tmp_item_prev = (!) tmp_item; // garbage
+ do { tmp_item_prev = (!) tmp_item; tmp_item = ((!) tmp_item).next; }
+ while (tmp_item != null && ((!) tmp_item).data.size == puzzle_size);
+ fallback_score_3 = tmp_item_prev.data;
+ }
+
/*\
* * loading
\*/
diff --git a/src/meson.build b/src/meson.build
index 55c2674..390af54 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -3,6 +3,7 @@ resources = gnome.compile_resources ('resources', 'gnome-tetravex.gresource.xml'
c_name: 'resources')
sources = files (
+ 'cli.vala',
'config.vapi',
'gnome-tetravex.vala',
'history.vala',
diff --git a/src/puzzle.vala b/src/puzzle.vala
index 104664d..1666381 100644
--- a/src/puzzle.vala
+++ b/src/puzzle.vala
@@ -44,14 +44,17 @@ private class Puzzle : Object
private Tile? [,] board;
/* Game timer */
- private Timer? clock;
+ private Timer? clock = null; // TODO ask for Timer.do_not_start() constructor
private uint clock_timeout;
- public double initial_time { private get; protected construct; default = 0.0; }
+ [CCode (notify = false)] public double initial_time { private get; protected construct; default = 0.0; }
+ [CCode (notify = false)] public bool tainted_by_command_line { internal get; protected construct; }
[CCode (notify = false)] internal double elapsed
{
get
{
+ if (tainted_by_command_line)
+ assert_not_reached ();
if (clock == null)
return 0.0;
return initial_time + ((!) clock).elapsed ();
@@ -100,10 +103,10 @@ private class Puzzle : Object
return true;
}
- public bool restored { private get; protected construct; default = false; }
+ [CCode (notify = false)] public bool restored { private get; protected construct; default = false; }
internal Puzzle (uint8 size, uint8 colors)
{
- Object (size: size, colors: colors);
+ Object (size: size, colors: colors, tainted_by_command_line: false);
}
construct
@@ -326,16 +329,17 @@ private class Puzzle : Object
_switch_tiles (x0, y0, x1, y1, /* delay if finished */ 0, /* undoing or redoing */ false,
last_move_id);
}
- internal void move_up (bool left_board)
+ internal bool move_up (bool left_board)
{
if (!can_move_up (left_board) || last_move_id == uint.MAX)
- return;
+ return false;
last_move_id++;
uint8 base_x = left_board ? 0 : size;
for (uint8 y = 1; y < size; y++)
for (uint8 x = 0; x < size; x++)
switch_one_of_many_tiles (base_x + x, y, base_x + x, y - 1);
+ return true;
}
private bool can_move_up (bool left_board)
{
@@ -343,19 +347,26 @@ private class Puzzle : Object
for (uint8 x = 0; x < size; x++)
if (board [base_x + x, 0] != null)
return false;
- return true;
+
+ for (uint8 x = 0; x < size; x++)
+ for (uint8 y = 1; y < size; y++)
+ if (board [base_x + x, y] != null)
+ return true;
+
+ return false;
}
- internal void move_down (bool left_board)
+ internal bool move_down (bool left_board)
{
if (!can_move_down (left_board) || last_move_id == uint.MAX)
- return;
+ return false;
last_move_id++;
uint8 base_x = left_board ? 0 : size;
for (uint8 y = size - 1; y > 0; y--)
for (uint8 x = 0; x < size; x++)
switch_one_of_many_tiles (base_x + x, y - 1, base_x + x, y);
+ return true;
}
private bool can_move_down (bool left_board)
{
@@ -363,19 +374,26 @@ private class Puzzle : Object
for (uint8 x = 0; x < size; x++)
if (board [base_x + x, size - 1] != null)
return false;
- return true;
+
+ for (uint8 x = 0; x < size; x++)
+ for (uint8 y = 0; y < size - 1; y++)
+ if (board [base_x + x, y] != null)
+ return true;
+
+ return false;
}
- internal void move_left (bool left_board)
+ internal bool move_left (bool left_board)
{
if (!can_move_left (left_board) || last_move_id == uint.MAX)
- return;
+ return false;
last_move_id++;
uint8 base_x = left_board ? 0 : size;
for (uint8 x = 1; x < size; x++)
for (uint8 y = 0; y < size; y++)
switch_one_of_many_tiles (base_x + x, y, base_x + x - 1, y);
+ return true;
}
private bool can_move_left (bool left_board)
{
@@ -383,27 +401,41 @@ private class Puzzle : Object
for (uint8 y = 0; y < size; y++)
if (board [left_column, y] != null)
return false;
- return true;
+
+ for (uint8 x = 1; x < size; x++)
+ for (uint8 y = 0; y < size; y++)
+ if (board [left_column + x, y] != null)
+ return true;
+
+ return false;
}
- internal void move_right (bool left_board)
+ internal bool move_right (bool left_board)
{
if (!can_move_right (left_board) || last_move_id == uint.MAX)
- return;
+ return false;
last_move_id++;
uint8 base_x = left_board ? 0 : size;
for (uint8 x = size - 1; x > 0; x--)
for (uint8 y = 0; y < size; y++)
switch_one_of_many_tiles (base_x + x - 1, y, base_x + x, y);
+ return true;
}
private bool can_move_right (bool left_board)
{
- uint8 right_column = left_board ? size - 1 : 2 * size - 1;
+ uint8 left_column = left_board ? 0 : size;
+ uint8 right_column = left_column + size - 1;
for (uint8 y = 0; y < size; y++)
if (board [right_column, y] != null)
return false;
- return true;
+
+ for (uint8 x = 0; x < size - 1; x++)
+ for (uint8 y = 0; y < size; y++)
+ if (board [left_column + x, y] != null)
+ return true;
+
+ return false;
}
internal void try_move (uint8 x, uint8 y)
@@ -459,15 +491,6 @@ private class Puzzle : Object
return true;
}
- private enum Direction
- {
- NONE,
- UP,
- DOWN,
- LEFT,
- RIGHT;
- }
-
/*\
* * actions
\*/
@@ -504,6 +527,28 @@ private class Puzzle : Object
switch_tiles (x + size, y, x, y, duration);
}
+ internal bool move_last_tile_if_possible ()
+ {
+ uint8 empty_x;
+ uint8 empty_y;
+ if (!only_one_remaining_tile (out empty_x, out empty_y))
+ return false;
+
+ for (uint8 x = size; x < 2 * size; x++)
+ for (uint8 y = 0; y < size; y++)
+ if (get_tile (x, y) != null)
+ {
+ if (can_switch (x, y, empty_x, empty_y))
+ {
+ switch_tiles (x, y, empty_x, empty_y);
+ return true;
+ }
+ else
+ return false;
+ }
+ assert_not_reached ();
+ }
+
internal bool only_one_remaining_tile (out uint8 empty_x, out uint8 empty_y)
{
bool empty_found = false;
@@ -531,6 +576,8 @@ private class Puzzle : Object
private void start_clock ()
{
+ if (tainted_by_command_line)
+ return;
if (clock == null)
clock = new Timer ();
timeout_cb ();
@@ -538,6 +585,8 @@ private class Puzzle : Object
private void stop_clock ()
{
+ if (tainted_by_command_line)
+ return;
if (clock == null)
return;
if (clock_timeout != 0)
@@ -549,6 +598,8 @@ private class Puzzle : Object
private void continue_clock ()
{
+ if (tainted_by_command_line)
+ return;
if (clock == null)
clock = new Timer ();
else
@@ -558,6 +609,7 @@ private class Puzzle : Object
private bool timeout_cb ()
requires (clock != null)
+ requires (!tainted_by_command_line)
{
/* Notify on the next tick */
double elapsed = ((!) clock).elapsed ();
@@ -584,11 +636,11 @@ private class Puzzle : Object
private class Inversion : Object
{
- public uint8 x0 { internal get; protected construct; }
- public uint8 y0 { internal get; protected construct; }
- public uint8 x1 { internal get; protected construct; }
- public uint8 y1 { internal get; protected construct; }
- public uint id { internal get; protected construct; }
+ [CCode (notify = false)] public uint8 x0 { internal get; protected construct; }
+ [CCode (notify = false)] public uint8 y0 { internal get; protected construct; }
+ [CCode (notify = false)] public uint8 x1 { internal get; protected construct; }
+ [CCode (notify = false)] public uint8 y1 { internal get; protected construct; }
+ [CCode (notify = false)] public uint id { internal get; protected construct; }
internal Inversion (uint8 x0, uint8 y0, uint8 x1, uint8 y1, uint id)
{
@@ -697,7 +749,7 @@ private class Puzzle : Object
* * save and restore
\*/
- internal Variant to_variant ()
+ internal Variant to_variant (bool save_time)
{
VariantBuilder builder = new VariantBuilder (new VariantType ("m(yyda(yyyyyyyy)ua(yyyyu))"));
builder.open (new VariantType ("(yyda(yyyyyyyy)ua(yyyyu))"));
@@ -705,7 +757,10 @@ private class Puzzle : Object
// board
builder.add ("y", size);
builder.add ("y", colors);
- builder.add ("d", elapsed);
+ if (save_time)
+ builder.add ("d", elapsed);
+ else
+ builder.add ("d", double.MAX);
// tiles
builder.open (new VariantType ("a(yyyyyyyy)"));
@@ -753,7 +808,7 @@ private class Puzzle : Object
public uint8 initial_y;
}
- internal static bool is_valid_saved_game (Variant maybe_variant)
+ internal static bool is_valid_saved_game (Variant maybe_variant, bool restore_finished_game)
{
Variant? variant = maybe_variant.get_maybe ();
if (variant == null)
@@ -839,7 +894,15 @@ private class Puzzle : Object
// TODO validate history 1/2
- return true;
+ if (restore_finished_game)
+ return true;
+
+ // return false if the game is finished, true otherwise
+ for (uint8 x = board_size; x < board_size * 2; x++)
+ for (uint8 y = 0; y < board_size; y++)
+ if (current_board [x, y])
+ return true;
+ return false;
}
internal Puzzle.restore (Variant maybe_variant)
@@ -854,7 +917,7 @@ private class Puzzle : Object
((!) variant).get_child (0, "y", out _size);
((!) variant).get_child (1, "y", out _colors);
((!) variant).get_child (2, "d", out _elapsed);
- Object (size: _size, colors: _colors, restored: true, initial_time: _elapsed);
+ Object (size: _size, colors: _colors, restored: true, initial_time: _elapsed,
tainted_by_command_line: _elapsed == double.MAX);
Variant array_variant = ((!) variant).get_child_value (3);
board = new Tile? [size * 2, size];
@@ -887,7 +950,17 @@ private class Puzzle : Object
game_in_progress = true;
if (solved_on_right ())
is_solved_right = true;
+ check_if_solved ();
}
// TODO restore history 2/2
}
+
+private enum Direction
+{
+ NONE,
+ UP,
+ DOWN,
+ LEFT,
+ RIGHT;
+}
diff --git a/src/score-overlay.vala b/src/score-overlay.vala
index 94c0ef4..fbb55cc 100644
--- a/src/score-overlay.vala
+++ b/src/score-overlay.vala
@@ -138,6 +138,47 @@ private class ScoreOverlay : Grid
break;
}
}
+
+ internal void display_fallback_scores (uint8 puzzle_size,
+ HistoryEntry? best_score,
+ HistoryEntry? second_score,
+ HistoryEntry? third_score,
+ HistoryEntry? worst_score)
+ {
+ /* Translators: text of the score overlay, displayed after a puzzle is complete; introduces the game
best time (in a corner case) */
+ score_0.set_place_label (_("Best time:"));
+ if (best_score != null)
+ score_0.set_value_label (HistoryEntry.get_duration_string ((!) best_score));
+ else
+ score_0.set_value_label (null);
+
+ /* Translators: text of the score overlay, displayed after a puzzle is complete; introduces the
second best time (in a corner case) */
+ score_1.set_place_label (_("Second:"));
+ if (second_score != null)
+ score_1.set_value_label (HistoryEntry.get_duration_string ((!) second_score));
+ else
+ score_1.set_value_label (null);
+
+ /* Translators: text of the score overlay, displayed after a puzzle is complete; introduces the
third best time (in a corner case) */
+ score_2.set_place_label (_("Third:"));
+ if (third_score != null)
+ score_2.set_value_label (HistoryEntry.get_duration_string ((!) third_score));
+ else
+ score_2.set_value_label (null);
+
+ if (worst_score != null)
+ {
+ /* Translators: text of the score overlay, displayed after a puzzle is complete; introduces the
worst time (in a corner case) */
+ score_3.set_place_label (_("Worst time:"));
+ score_3.set_value_label (HistoryEntry.get_duration_string ((!) worst_score));
+ }
+ else
+ {
+ /* Translators: text of the score overlay, displayed after a puzzle is complete; introduces the
fourth time (in a corner case) */
+ score_3.set_place_label (_("Fourth:"));
+ score_3.set_value_label (null);
+ }
+ }
}
[GtkTemplate (ui = "/org/gnome/Tetravex/score-overlay-entry.ui")]
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]