[quadrapassel: 1/2] Added major improvements to the controls and gameplay




commit 96e752c8a3a06d72d72edb108798185db786a6c8
Author: Charles Benca <zzazzbug gmail com>
Date:   Tue Feb 8 09:49:36 2022 -0500

    Added major improvements to the controls and gameplay
    
    Tetrominos are no longer distributed in a random orientation. Improved the random tetrominos generation 
algorithm, which now tries to give the player the tetrominos it has received less. Improved the tetrominos 
rotation array to be less annoying with tetrominos who have only 2 rotations. Tetrominos no longer hit the 
ceiling when rotated right as they spawn. Improved automatic moving of Tetrominos when the player press and 
hold a key. When rotated, the game will try to find a place where the tetromino doesn't hit anything, for 
example, the sides of the grid.

 src/game.vala | 193 +++++++++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 156 insertions(+), 37 deletions(-)
---
diff --git a/src/game.vala b/src/game.vala
index 04bd091..dac6653 100644
--- a/src/game.vala
+++ b/src/game.vala
@@ -11,20 +11,27 @@
 
 const int NCOLORS = 7;
 
+
+/* When the player holds either the left or right key, the game will begin automatically moving the 
tetrominos in that direction.
+ * To accomplish that in a way expected by the player, there is 2 important delay. The "activation" delay 
and the "interval" delay.(These are made up names to explain).
+ * 
+ * The "activation" delay is the amount of time it takes until de auto move starts.
+ * The "interval" delay is the speed of the auto move.
+ *
+ * The interval delay is naturally (i think) expected by the players to be faster than the activation 
period/delay.
+ * My subjective favorite values are 200 for activation and 40 for interval.
+ */
+const int AUTOMOVE_ACTIVATION_TIME = 200; // 200
+const int AUTOMOVE_INTERVAL = 40; // 40
+
+
+
+
 private const int block_table[448] =
 {
+
     /* *** */
     /* *   */
-    0, 0, 0, 0,
-    1, 1, 1, 0,
-    1, 0, 0, 0,
-    0, 0, 0, 0,
-
-    0, 1, 0, 0,
-    0, 1, 0, 0,
-    0, 1, 1, 0,
-    0, 0, 0, 0,
-
     0, 0, 1, 0,
     1, 1, 1, 0,
     0, 0, 0, 0,
@@ -35,18 +42,18 @@ private const int block_table[448] =
     0, 1, 0, 0,
     0, 0, 0, 0,
 
-    /* *** */
-    /*   * */
     0, 0, 0, 0,
     1, 1, 1, 0,
-    0, 0, 1, 0,
+    1, 0, 0, 0,
     0, 0, 0, 0,
 
-    0, 1, 1, 0,
     0, 1, 0, 0,
     0, 1, 0, 0,
+    0, 1, 1, 0,
     0, 0, 0, 0,
 
+    /* *** */
+    /*   * */
     1, 0, 0, 0,
     1, 1, 1, 0,
     0, 0, 0, 0,
@@ -57,18 +64,18 @@ private const int block_table[448] =
     1, 1, 0, 0,
     0, 0, 0, 0,
 
-    /* *** */
-    /*  *  */
     0, 0, 0, 0,
     1, 1, 1, 0,
-    0, 1, 0, 0,
+    0, 0, 1, 0,
     0, 0, 0, 0,
 
-    0, 1, 0, 0,
     0, 1, 1, 0,
     0, 1, 0, 0,
+    0, 1, 0, 0,
     0, 0, 0, 0,
 
+    /* *** */
+    /*  *  */
     0, 1, 0, 0,
     1, 1, 1, 0,
     0, 0, 0, 0,
@@ -79,6 +86,16 @@ private const int block_table[448] =
     0, 1, 0, 0,
     0, 0, 0, 0,
 
+    0, 0, 0, 0,
+    1, 1, 1, 0,
+    0, 1, 0, 0,
+    0, 0, 0, 0,
+
+    0, 1, 0, 0,
+    0, 1, 1, 0,
+    0, 1, 0, 0,
+    0, 0, 0, 0,
+
     /*  ** */
     /* **  */
 
@@ -92,14 +109,14 @@ private const int block_table[448] =
     0, 0, 1, 0,
     0, 0, 0, 0,
 
+    0, 0, 0, 0,
     0, 1, 1, 0,
     1, 1, 0, 0,
     0, 0, 0, 0,
-    0, 0, 0, 0,
 
-    1, 0, 0, 0,
-    1, 1, 0, 0,
     0, 1, 0, 0,
+    0, 1, 1, 0,
+    0, 0, 1, 0,
     0, 0, 0, 0,
 
     /* **  */
@@ -115,14 +132,14 @@ private const int block_table[448] =
     0, 1, 0, 0,
     0, 0, 0, 0,
 
+    0, 0, 0, 0,
     1, 1, 0, 0,
     0, 1, 1, 0,
     0, 0, 0, 0,
-    0, 0, 0, 0,
 
+    0, 0, 1, 0,
+    0, 1, 1, 0,
     0, 1, 0, 0,
-    1, 1, 0, 0,
-    1, 0, 0, 0,
     0, 0, 0, 0,
 
     /* **** */
@@ -395,21 +412,50 @@ public class Game : Object
 
     public bool rotate_left ()
     {
-        return move_shape (0, 0, 1);
+        return try_rotate(1);
     }
-
     public bool rotate_right ()
     {
-        return move_shape (0, 0, -1);
+        return try_rotate(-1);
+    }
+    //will rotate the tetromino and if it doesn't fit, will try to move it a little horizontaly so the 
rotation has the most chances of succeeding.
+    //Example use case: The tetromino is on the left of right side of the grid, because it will probably not 
fit after rotation, we move it a little so it still gets rotated if there is enough space around the 
tetromino.
+    private bool try_rotate (int r_step)
+    {
+        List<int> listHMoves = new List<int>();
+        listHMoves.append(0);
+        listHMoves.append(1);
+        listHMoves.append(-1);
+        listHMoves.append(2);
+        listHMoves.append(-2);
+
+        bool result = false;
+
+        foreach (int hmove in listHMoves)
+        {
+            //tries to move the shape
+            result = move_shape(hmove, 0, r_step);
+            //if rotation succeeded, we stop
+            if (result) {
+                break;
+            }
+        }
+
+        //we return if any of our tries succeeded
+        return result;
     }
 
     public void set_fast_forward (bool enable)
     {
+               //gamestate check
         if (fast_forward == enable || game_over)
             return;
+
+               //we move the shape down a little, according to parameters
         if (enable)
             if (!move_shape (0, 1, 0))
                 return;
+
         fast_forward = enable;
         setup_drop_timer ();
     }
@@ -443,16 +489,25 @@ public class Game : Object
         if (!move ())
             return false;
 
-        fast_move_timeout = Timeout.add (500, setup_fast_move_cb);
+        fast_move_timeout = Timeout.add (AUTOMOVE_ACTIVATION_TIME, setup_fast_move_cb);
 
         return true;
     }
 
+    // Following are the two callbacks who manages the auto moving of tetrominos. setup_fast_move_cb() and 
move().
+    // Why two? As explained in a more detailled manner above the declaration of the constants 
AUTOMOVE_INTERVAL and AUTOMOVE_ACTIVATION_TIME,
+    // the logic of auto moving is kinda separated in 2 phases : the "activating" and the "activated" phase, 
both of which have different delays.
+
     private bool setup_fast_move_cb ()
     {
         if (!move ())
-            return false;
-        fast_move_timeout = Timeout.add (40, move);
+        {
+            //it should not stop trying to move the block until the player releases the left or right key. 
it could happens that on the edge of the screen
+            //we rotate the block then it can move a little bit more.
+
+            //return false;
+        }
+        fast_move_timeout = Timeout.add (AUTOMOVE_INTERVAL, move);
 
         return false;
     }
@@ -461,10 +516,12 @@ public class Game : Object
     {
         if (!move_shape (fast_move_direction, 0, 0))
         {
-            fast_move_timeout = 0;
-            fast_move_direction = 0;
+            //it should not stop trying to move the block until the player releases the left or right key. 
it could happens that on the edge of the screen
+            //we rotate the block then it can move a little bit more.
 
-            return false;
+            //fast_move_timeout = 0;
+            //fast_move_direction = 0;
+            //return false;
         }
 
         return true;
@@ -542,9 +599,64 @@ public class Game : Object
         shape_added ();
     }
 
+
+    //array to keep track of the amount of each shape we have created. it's used for a better random shape 
distribution algorithm
+    private int[] distshapecount = { 0, 0, 0, 0, 0, 0, 0 }; //there are 7 different shapes
+
+
     private Shape pick_random_shape ()
     {
-        return make_shape (Random.int_range (0, NCOLORS), Random.int_range (0, 4));
+        
+        int shapecount = 7; //number of existing shapes, at least in distshapecount
+
+        //find the smallest number in distshapecount
+        int lowerbound = distshapecount[0]; //pick the first. maybe it already the smallest, but we will 
look for smaller ones
+        for (int i = 0; i < shapecount; i++)
+        {
+            if (distshapecount[i] < lowerbound) {
+                lowerbound = distshapecount[i];
+            }
+        }
+
+        double sum = 0d;
+        //we compute the sum of the weigh of each shape. the less the shape has been given to the player, 
the bigger the weigh
+        for (int i = 0; i < shapecount; i++)
+        {
+            int rel = distshapecount[i] - lowerbound;
+            //we have to make sure it's not 0
+            if (rel < 1) { rel = 1; }
+
+            double weigh = 1d / (double)(rel * rel);
+
+            sum += weigh;
+        }
+
+        //we pick the random shape
+        double rndshape = sum * Random.next_double ();
+
+        //we figure out in which area the random number landed
+        double currentpos = 0d; //our current position
+        int newshape = 0; //increases by one every loop, until we find the area
+        for (int i = 0; i < shapecount; i++)
+        {
+            int rel = distshapecount[i] - lowerbound;
+            //we have to make sure it's not 0
+            if (rel < 1) { rel = 1; }
+
+            double weigh = 1d / (double)(rel * rel);
+
+            currentpos += weigh;
+
+            //if it's inside we break
+            if (rndshape < currentpos) {
+                break;
+            }
+
+            newshape++;
+        }
+
+        distshapecount[newshape]++;
+        return make_shape (newshape, 0);
     }
 
     private Shape pick_difficult_shapes ()
@@ -799,7 +911,7 @@ public class Game : Object
         {
             var x = shape.x + x_step + b.x;
             var y = shape.y + y_step + b.y;
-            if (x < 0 || x >= width || y >= height || blocks[x, y] != null)
+            if (x < 0 || x >= width || y >= height || (y >= 0 && blocks[x, y] != null))
             {
                 can_move = false;
                 break;
@@ -812,12 +924,19 @@ public class Game : Object
             shape.x += x_step;
             shape.y += y_step;
 
+            //Raises the appropriate signals. It is possible for multiple moves to be made at once, hence 
the reason why they are all separated. I have experienced refresh problems when not all concerned signals are 
called.
             if (x_step != 0)
+            {
                 shape_moved ();
-            else if (y_step > 0)
+            }
+            if (y_step != 0)
+            {
                 shape_dropped ();
-            else
+            }
+            if (r_step != 0)
+            {
                 shape_rotated ();
+            }
         }
         else
             rotate_shape (-r_step);


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