[quadrapassel: 1/2] Added major improvements to the controls and gameplay
- From: John Ward <jward src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [quadrapassel: 1/2] Added major improvements to the controls and gameplay
- Date: Mon, 13 Jun 2022 20:47:49 +0000 (UTC)
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]