[gnome-chess] Add support for Fischer and Bronstein clocks



commit d00db10c103617fa3d864366eb335624b05ac559
Author: Sahil Sareen <sahil sareen hotmail com>
Date:   Mon Feb 16 09:37:37 2015 +0530

    Add support for Fischer and Bronstein clocks
    
    * Preferences dialog: Added option for clock type
    * Clock Library: Support for these clocks
    * Gnome-Chess: Handle fischer/bronstein clocks
    
    https://bugzilla.gnome.org/show_bug.cgi?id=473395

 data/org.gnome.chess.gschema.xml |   26 ++++++
 data/preferences.ui              |  157 ++++++++++++++++++++++++++++++++-
 lib/chess-clock.vala             |   88 +++++++++++++++++++
 lib/chess-pgn.vala               |   10 ++
 src/gnome-chess.vala             |  179 +++++++++++++++++++++++++++++++++++++-
 5 files changed, 453 insertions(+), 7 deletions(-)
---
diff --git a/data/org.gnome.chess.gschema.xml b/data/org.gnome.chess.gschema.xml
index c5c4d60..8287e18 100644
--- a/data/org.gnome.chess.gschema.xml
+++ b/data/org.gnome.chess.gschema.xml
@@ -20,6 +20,12 @@
     <value value="2" nick="hard"/>
   </enum>
 
+  <enum id="org.gnome.chess.ClockType">
+    <value value="0" nick="simple"/>
+    <value value="1" nick="fischer"/>
+    <value value="2" nick="bronstein"/>
+  </enum>
+
   <schema id="org.gnome.chess" path="/org/gnome/chess/" gettext-domain="gnome-chess">
     <key name="width" type="i">
       <default>700</default>
@@ -76,6 +82,26 @@
       <summary>The duration of a game in seconds (0 for no limit)</summary>
       <description>The duration of a game in seconds (0 for no limit)</description>
     </key>
+    <key name="clock-type" enum="org.gnome.chess.ClockType">
+      <default>'simple'</default>
+      <summary>The type of clock (simple/fischer/bronstein)</summary>
+      <description>The type of clock (simple/fischer/bronstein)</description>
+    </key>
+    <key name="timer-increment" type="i">
+      <default>1</default>
+      <summary>The timer increment set corresponding to clock type (1 second minimum)</summary>
+      <description>The timer increment set corresponding to clock type (1 second minimum)</description>
+    </key>
+    <key name="white-last-move-seconds" type="i">
+      <default>0</default>
+      <summary>Seconds white has used until last move (0 for first move)</summary>
+      <description>Seconds white has used until last move (0 for first move)</description>
+    </key>
+    <key name="black-last-move-seconds" type="i">
+      <default>0</default>
+      <summary>Seconds black has used until last move (0 for first move)</summary>
+      <description>Seconds black has used until last move (0 for first move)</description>
+    </key>
     <key name="play-as-white" type="b">
       <default>true</default>
       <summary>true if the human player is playing white</summary>
diff --git a/data/preferences.ui b/data/preferences.ui
index d4d2733..783eff6 100644
--- a/data/preferences.ui
+++ b/data/preferences.ui
@@ -19,6 +19,46 @@
       </row>
     </data>
   </object>
+  <object class="GtkListStore" id="timer_increment_units_model">
+    <columns>
+      <!-- column-name label -->
+      <column type="gchararray"/>
+      <!-- column-name multiplier -->
+      <column type="gint"/>
+    </columns>
+    <data>
+      <row>
+        <col id="0">seconds</col>
+        <col id="1">1</col>
+      </row>
+      <row>
+        <col id="0">minutes</col>
+        <col id="1">60</col>
+      </row>
+    </data>
+  </object>
+  <object class="GtkListStore" id="clock_type_model">
+    <columns>
+      <!-- column-name label -->
+      <column type="gchararray"/>
+      <!-- column-name clock-type -->
+      <column type="gint"/>
+    </columns>
+    <data>
+      <row>
+        <col id="0">Simple</col>
+        <col id="1">0</col>
+      </row>
+      <row>
+        <col id="0">Fischer</col>
+        <col id="1">1</col>
+      </row>
+      <row>
+        <col id="0">Bronstein</col>
+        <col id="1">2</col>
+      </row>
+    </data>
+  </object>
   <object class="GtkListStore" id="difficulty_model">
     <columns>
       <!-- column-name label -->
@@ -41,6 +81,13 @@
       </row>
     </data>
   </object>
+  <object class="GtkAdjustment" id="timer_increment_adjustment">
+    <property name="lower">1</property>
+    <property name="upper">59</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+    <signal name="value-changed" handler="timer_increment_units_changed_cb" swapped="no"/>
+  </object>
   <object class="GtkAdjustment" id="duration_adjustment">
     <property name="lower">1</property>
     <property name="upper">10</property>
@@ -197,7 +244,7 @@
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
                 <property name="border_width">10</property>
-                <property name="n_rows">6</property>
+                <property name="n_rows">8</property>
                 <property name="n_columns">2</property>
                 <property name="column_spacing">5</property>
                 <property name="row_spacing">5</property>
@@ -205,6 +252,59 @@
                   <placeholder/>
                 </child>
                 <child>
+                  <object class="GtkLabel" id="clock_type_label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes" comments="Preferences Dialog: Label before 
clock type (Fischer/Bronstein) combo box">_Clock type:</property>
+                    <property name="use_underline">True</property>
+                    <property name="mnemonic_widget">clock_type_combo</property>
+                  </object>
+                  <packing>
+                    <property name="top_attach">5</property>
+                    <property name="bottom_attach">6</property>
+                    <property name="x_options">GTK_FILL</property>
+                    <property name="y_options"/>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="timer_increment_label">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="label" translatable="yes" comments="Preferences Dialog: Label before 
timer increment combo box">Timer _increment:</property>
+                    <property name="use_underline">True</property>
+                    <property name="mnemonic_widget">timer_increment_spin</property>
+                  </object>
+                  <packing>
+                    <property name="top_attach">6</property>
+                    <property name="bottom_attach">7</property>
+                    <property name="x_options">GTK_FILL</property>
+                    <property name="y_options"/>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkComboBox" id="clock_type_combo">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="model">clock_type_model</property>
+                    <signal name="changed" handler="clock_type_changed_cb" swapped="no"/>
+                    <child>
+                      <object class="GtkCellRendererText" id="clock_type_cellrenderer"/>
+                      <attributes>
+                        <attribute name="text">0</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="top_attach">5</property>
+                    <property name="bottom_attach">6</property>
+                    <property name="y_options">GTK_FILL</property>
+                  </packing>
+                </child>
+                <child>
                   <object class="GtkLabel" id="side_label">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
@@ -230,6 +330,8 @@
                     <property name="mnemonic_widget">opponent_combo</property>
                   </object>
                   <packing>
+                    <property name="top_attach">0</property>
+                    <property name="bottom_attach">1</property>
                     <property name="x_options">GTK_FILL</property>
                     <property name="y_options"/>
                   </packing>
@@ -251,6 +353,55 @@
                   </packing>
                 </child>
                 <child>
+                  <object class="GtkBox" id="timer_increment_box">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <object class="GtkSpinButton" id="timer_increment_spin">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="invisible_char">•</property>
+                        <property name="adjustment">timer_increment_adjustment</property>
+                        <property name="climb_rate">1</property>
+                        <property name="numeric">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkComboBox" id="timer_increment_units_combo">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="model">timer_increment_units_model</property>
+                        <signal name="changed" handler="timer_increment_units_changed_cb" swapped="no"/>
+                        <child>
+                          <object class="GtkCellRendererText" id="timer_increment_units_cellrenderer"/>
+                          <attributes>
+                            <attribute name="text">0</attribute>
+                          </attributes>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="left_attach">1</property>
+                    <property name="right_attach">2</property>
+                    <property name="top_attach">6</property>
+                    <property name="bottom_attach">7</property>
+                    <property name="x_options">GTK_FILL</property>
+                    <property name="y_options">GTK_FILL</property>
+                  </packing>
+                </child>
+                <child>
                   <object class="GtkComboBox" id="side_combo">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
@@ -433,8 +584,8 @@
                   </object>
                   <packing>
                     <property name="right_attach">2</property>
-                    <property name="top_attach">5</property>
-                    <property name="bottom_attach">6</property>
+                    <property name="top_attach">7</property>
+                    <property name="bottom_attach">8</property>
                     <property name="y_options">GTK_FILL</property>
                   </packing>
                 </child>
diff --git a/lib/chess-clock.vala b/lib/chess-clock.vala
index 4e5c9f7..7e790b3 100644
--- a/lib/chess-clock.vala
+++ b/lib/chess-clock.vala
@@ -10,6 +10,43 @@
  * license.
  */
 
+public enum ClockType
+{
+    SIMPLE,
+    FISCHER,
+    BRONSTEIN;
+
+    public string to_string ()
+    {
+        switch (this)
+        {
+        case SIMPLE:
+            return "simple";
+        case FISCHER:
+            return "fischer";
+        case BRONSTEIN:
+            return "bronstein";
+        default:
+            assert_not_reached ();
+        }
+    }
+
+    public static ClockType string_to_enum (string s)
+    {
+        switch (s)
+        {
+        case "simple":
+            return SIMPLE;
+        case "fischer":
+            return FISCHER;
+        case "bronstein":
+            return BRONSTEIN;
+        default:
+            assert_not_reached ();
+        }
+    }
+}
+
 public class ChessClock : Object
 {
     public int white_initial_seconds { get; private set; }
@@ -20,7 +57,52 @@ public class ChessClock : Object
 
     public int black_seconds_used { get; private set; default = 0; }
 
+    public ClockType clock_type { get; set; default = ClockType.SIMPLE; }
+
+    public int white_prev_move_seconds { get; private set; default = 0; }
+
+    public int black_prev_move_seconds { get; private set; default = 0; }
+
+    public int white_extra_seconds { get; private set; default = 0; }
+
+    public int black_extra_seconds { get; private set; default = 0; }
+
     private Color _active_color = Color.WHITE;
+
+    public int extra_seconds { get; set; default = 0; }
+
+    public void update_prev_move_time ()
+    {
+        if (active_color == Color.WHITE)
+            black_prev_move_seconds = black_seconds_used;
+        else
+            white_prev_move_seconds = white_seconds_used;
+    }
+
+    public void update_extra_seconds ()
+    {
+        int white_move_used = 0, black_move_used = 0;
+        switch (clock_type)
+        {
+        case ClockType.SIMPLE:
+            break;
+        case ClockType.FISCHER:
+            if (active_color == Color.WHITE)
+                white_extra_seconds += extra_seconds;
+            else
+                black_extra_seconds += extra_seconds;
+            break;
+        case ClockType.BRONSTEIN:
+            white_move_used = white_seconds_used - white_prev_move_seconds;
+            black_move_used = black_seconds_used - black_prev_move_seconds;
+            if (active_color != Color.WHITE)
+                white_extra_seconds += int.min(extra_seconds, white_move_used);
+            else
+                black_extra_seconds += int.min(extra_seconds, black_move_used);
+            break;
+        }
+    }
+
     public Color active_color
     {
         get { return _active_color; }
@@ -31,6 +113,12 @@ public class ChessClock : Object
 
             stop ();
             _active_color = value;
+
+            // This is a move switch
+            // Update the clocks for Fischer and Bronstein mode
+            update_extra_seconds ();
+            update_prev_move_time ();
+
             start ();
         }
     }
diff --git a/lib/chess-pgn.vala b/lib/chess-pgn.vala
index f51dfb5..39733a6 100644
--- a/lib/chess-pgn.vala
+++ b/lib/chess-pgn.vala
@@ -125,6 +125,16 @@ public class PGNGame : Object
         get { return tags.lookup ("BlackTimeLeft"); }
         set { tags.insert ("BlackTimeLeft", value); }
     }
+    public string? clock_type
+    {
+        get { return tags.lookup ("X-GNOME-ClockType"); }
+        set { tags.insert ("X-GNOME-ClockType", value); }
+    }
+    public string? timer_increment
+    {
+        get { return tags.lookup ("X-GNOME-TimerIncrement"); }
+        set { tags.insert ("X-GNOME-TimerIncrement", value); }
+    }
     public bool set_up
     {
         get { string? v = tags.lookup ("SetUp"); return v != null && v == "1" ? true : false; }
diff --git a/src/gnome-chess.vala b/src/gnome-chess.vala
index af8853e..fc08d69 100644
--- a/src/gnome-chess.vala
+++ b/src/gnome-chess.vala
@@ -27,14 +27,19 @@ public class ChessApplication : Gtk.Application
     private Gtk.ComboBox history_combo;
     private Gtk.Widget white_time_label;
     private Gtk.Widget black_time_label;
+    private Gtk.Widget timer_increment_label;
     private Gtk.HeaderBar headerbar;
 
     private Gtk.Dialog? preferences_dialog = null;
     private Gtk.ComboBox side_combo;
     private Gtk.ComboBox difficulty_combo;
     private Gtk.ComboBox duration_combo;
+    private Gtk.ComboBox clock_type_combo;
     private Gtk.Adjustment duration_adjustment;
+    private Gtk.Adjustment timer_increment_adjustment;
     private Gtk.Box custom_duration_box;
+    private Gtk.Box timer_increment_box;
+    private Gtk.ComboBox timer_increment_units_combo;
     private Gtk.ComboBox custom_duration_units_combo;
     private uint save_duration_timeout = 0;
     private Gtk.FileChooserDialog? open_dialog = null;
@@ -570,6 +575,30 @@ public class ChessApplication : Gtk.Application
 
         game.start ();
 
+        int timer_increment_adj_value = 0;
+        if (pgn_game.timer_increment != null)
+            timer_increment_adj_value = int.parse (pgn_game.timer_increment);
+        else
+        {
+            timer_increment_adj_value = settings.get_int ("timer-increment");
+            pgn_game.timer_increment = timer_increment_adj_value.to_string ();
+        }
+
+        ClockType clock_type = ClockType.SIMPLE;
+        if (pgn_game.clock_type != null)
+            clock_type = ClockType.string_to_enum (pgn_game.clock_type);
+        else
+        {
+            clock_type = ClockType.string_to_enum (settings.get_string ("clock-type"));
+            pgn_game.clock_type = clock_type.to_string ();
+        }
+
+        if (game.clock != null)
+        {
+            game.clock.extra_seconds = (int) timer_increment_adj_value;
+            game.clock.clock_type = clock_type;
+        }
+
         // If loading a completed saved game
         if (pgn_game.result == PGNGame.RESULT_WHITE)
             game.result = ChessResult.WHITE_WON;
@@ -1535,9 +1564,9 @@ public class ChessApplication : Gtk.Application
 
         int time;
         if (color == Color.WHITE)
-            time = game.clock.white_initial_seconds - game.clock.white_seconds_used;
+            time = game.clock.white_initial_seconds - game.clock.white_seconds_used + 
game.clock.white_extra_seconds;
         else
-            time = game.clock.black_initial_seconds - game.clock.black_seconds_used;
+            time = game.clock.black_initial_seconds - game.clock.black_seconds_used + 
game.clock.black_extra_seconds;
 
         if (time >= 60)
             return "%d∶\xE2\x80\x8E%02d".printf (time / 60, time % 60);
@@ -1706,10 +1735,25 @@ public class ChessApplication : Gtk.Application
         set_combo (difficulty_combo, 1, settings.get_string ("difficulty"));
 
         duration_combo = (Gtk.ComboBox) preferences_builder.get_object ("duration_combo");
+        clock_type_combo = (Gtk.ComboBox) preferences_builder.get_object ("clock_type_combo");
         duration_adjustment = (Gtk.Adjustment) preferences_builder.get_object ("duration_adjustment");
+        timer_increment_adjustment = (Gtk.Adjustment) preferences_builder.get_object 
("timer_increment_adjustment");
         custom_duration_box = (Gtk.Box) preferences_builder.get_object ("custom_duration_box");
+        timer_increment_box = (Gtk.Box) preferences_builder.get_object ("timer_increment_box");
         custom_duration_units_combo = (Gtk.ComboBox) preferences_builder.get_object 
("custom_duration_units_combo");
         set_duration (settings.get_int ("duration"));
+        timer_increment_label = (Gtk.Widget) preferences_builder.get_object ("timer_increment_label");
+        timer_increment_units_combo = (Gtk.ComboBox) preferences_builder.get_object 
("timer_increment_units_combo");
+
+        if (pgn_game.clock_type != null)
+            set_clock_type (ClockType.string_to_enum (pgn_game.clock_type));
+        else
+            set_clock_type ((int) ClockType.string_to_enum (settings.get_string ("clock-type")));
+
+        if (pgn_game.timer_increment != null)
+            set_timer_increment (int.parse (pgn_game.timer_increment));
+        else
+            set_timer_increment (settings.get_int ("timer-increment"));
 
         var orientation_combo = (Gtk.ComboBox) preferences_builder.get_object ("orientation_combo");
         set_combo (orientation_combo, 1, settings.get_string ("board-side"));
@@ -1796,6 +1840,61 @@ public class ChessApplication : Gtk.Application
         settings.set_string ("difficulty", difficulty);
     }
 
+    private void set_clock_type (int clock_type)
+    {
+        var model = clock_type_combo.model;
+        Gtk.TreeIter iter, active_iter_clock_type = {};
+
+        /* Find the largest units that can be used for this value */
+        if (model.get_iter_first (out iter))
+        {
+            do
+            {
+                int type;
+                model.get (iter, 1, out type, -1);
+                if (type == clock_type)
+                {
+                    active_iter_clock_type = iter;
+                }
+            } while (model.iter_next (ref iter));
+        }
+
+        clock_type_combo.set_active_iter (active_iter_clock_type);
+        clock_type_changed_cb (clock_type_combo);
+    }
+
+    private void set_timer_increment (int timer_increment)
+    {
+        int timer_increment_multiplier = 1;
+
+        if (timer_increment >= 60)
+        {
+            timer_increment_adjustment.value = timer_increment / 60;
+            timer_increment_multiplier = 60;
+        } else
+            timer_increment_adjustment.value = timer_increment;
+
+        var model = timer_increment_units_combo.model;
+        Gtk.TreeIter iter, reqd_iter = {};
+
+        /* Find the largest units that can be used for this value */
+        if (model.get_iter_first (out iter))
+        {
+            do
+            {
+                int multiplier;
+                model.get (iter, 1, out multiplier, -1);
+                if (multiplier == timer_increment_multiplier)
+                {
+                    reqd_iter = iter;
+                }
+            } while (model.iter_next (ref iter));
+        }
+
+        timer_increment_units_combo.set_active_iter (reqd_iter);
+        timer_increment_units_changed_cb (timer_increment_units_combo);
+    }
+
     private void set_duration (int duration, bool simplify = true)
     {
         var model = custom_duration_units_combo.model;
@@ -1930,6 +2029,57 @@ public class ChessApplication : Gtk.Application
         save_duration ();
     }
 
+    [CCode (cname = "G_MODULE_EXPORT timer_increment_units_changed_cb", instance_pos = -1)]
+    public void timer_increment_units_changed_cb (Gtk.Widget widget)
+    {
+        var model = (Gtk.ListStore) timer_increment_units_combo.model;
+        Gtk.TreeIter iter;
+        int multiplier = 0;
+        /* Set the unit labels to the correct plural form */
+        if (model.get_iter_first (out iter))
+        {
+            do
+            {
+                model.get (iter, 1, out multiplier, -1);
+                switch (multiplier)
+                {
+                case 1:
+                    model.set (iter, 0, ngettext (/* Preferences Dialog: Combo box entry for a custom clock 
type set in seconds */
+                                                  "second", "seconds", (ulong) 
timer_increment_adjustment.value), -1);
+                    break;
+                case 60:
+                    model.set (iter, 0, ngettext (/* Preferences Dialog: Combo box entry for a custom clock 
type set in minutes */
+                                                  "minute", "minutes", (ulong) 
timer_increment_adjustment.value), -1);
+                    break;
+                default:
+                    assert_not_reached ();
+                }
+            } while (model.iter_next (ref iter));
+        }
+
+        if (timer_increment_units_combo.get_active_iter (out iter))
+            timer_increment_units_combo.model.get (iter, 1, out multiplier, -1);
+
+        switch (multiplier)
+        {
+        case 1:
+            if (timer_increment_adjustment.get_upper () != 59)
+                timer_increment_adjustment.set_upper (59);
+            break;
+        case 60:
+            if (timer_increment_adjustment.get_upper () != 10)
+            {
+                timer_increment_adjustment.set_upper (10);
+                if (timer_increment_adjustment.value > 10)
+                    timer_increment_adjustment.value = 10;
+            }
+            break;
+        default:
+            assert_not_reached ();
+        }
+        settings.set_int ("timer-increment", (int) timer_increment_adjustment.value * multiplier);
+    }
+
     private void save_duration ()
     {
         /* Delay writing the value as it this event will be generated a lot spinning through the value */
@@ -1947,6 +2097,10 @@ public class ChessApplication : Gtk.Application
         int duration;
         combo.model.get (iter, 1, out duration, -1);
         custom_duration_box.visible = duration < 0;
+        clock_type_combo.sensitive = duration != 0;
+
+        if (duration == 0)
+            set_clock_type (ClockType.SIMPLE);
 
         if (duration >= 0)
             set_duration (duration, false);
@@ -1957,6 +2111,20 @@ public class ChessApplication : Gtk.Application
         save_duration ();
     }
 
+    [CCode (cname = "G_MODULE_EXPORT clock_type_changed_cb", instance_pos = -1)]
+    public void clock_type_changed_cb (Gtk.ComboBox combo)
+    {
+        Gtk.TreeIter iter;
+        if (!combo.get_active_iter (out iter))
+            return;
+        ClockType clock_type;
+        combo.model.get (iter, 1, out clock_type, -1);
+
+        timer_increment_box.visible = clock_type > 0;
+        timer_increment_label.visible = clock_type > 0;
+        settings.set_string ("clock-type", clock_type.to_string ());
+    }
+
     [CCode (cname = "G_MODULE_EXPORT preferences_response_cb", instance_pos = -1)]
     public void preferences_response_cb (Gtk.Widget widget, int response_id)
     {
@@ -2067,8 +2235,11 @@ public class ChessApplication : Gtk.Application
             uint white_used = game.clock.white_seconds_used;
             uint black_used = game.clock.black_seconds_used;
 
-            pgn_game.white_time_left = (white_initial_time - white_used).to_string ();
-            pgn_game.black_time_left = (black_initial_time - black_used).to_string ();
+            uint white_extra = game.clock.white_extra_seconds;
+            uint black_extra = game.clock.black_extra_seconds;
+
+            pgn_game.white_time_left = (white_initial_time - white_used + white_extra).to_string ();
+            pgn_game.black_time_left = (black_initial_time - black_used + black_extra).to_string ();
         }
     }
 


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