Subject: Gnometris is way TOO FRIENDLY
From: Lubomir Rintel <lkundrak@v3.sk>

This adds inpopular [1] "Bastard" mode to gnometris. The idea is to
annoy the user with intentionally unsuitable pieces. It also annoys
the user by displaying the most suitable piece instead of the next
to come.

In order to make it a bit fun, it is not that evil. It more-or-less
picks from three worst pieces giving the highest chance to the worst
one.

The idea comes from "bastet" [2], which stands for "Bastard Tetris".
This shares the algorithm, but no code.
 
[1] http://games.slashdot.org/games/05/04/24/1259241.shtml?tid=208&tid=106
[2] http://fph.altervista.org/prog/bastet.shtml

Index: blockops.cpp
===================================================================
--- blockops.cpp	(revision 8828)
+++ blockops.cpp	(working copy)
@@ -1,6 +1,7 @@
 /* -*- mode:C++; tab-width:8; c-basic-offset:8; indent-tabs-mode:true -*- */
 /*
  * written by J. Marcin Gorycki <marcin.gorycki@intel.com>
+ * "bastard" mode by Lubomir Rintel <lkundrak@v3.sk>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -149,9 +150,12 @@
 	center_anchor_y(0)
 {
 	field = new Block*[COLUMNS];
+	backfield = new Block*[COLUMNS];
 
-	for (int i = 0; i < COLUMNS; ++i)
+	for (int i = 0; i < COLUMNS; ++i) {
 		field[i] = new Block[LINES];
+		backfield[i] = new Block[LINES];
+	}
 
 	w = gtk_clutter_embed_new ();
 
@@ -432,23 +436,138 @@
 	return numFullLines;
 }
 
+void
+BlockOps::saveField ()
+{
+	for (int y = 0; y < LINES; y++)
+		for (int x = 0; x < COLUMNS; x++)
+			backfield[x][y] = field[x][y];
+}
+
+void
+BlockOps::restoreField ()
+{
+	for (int y = 0; y < LINES; y++)
+		for (int x = 0; x < COLUMNS; x++)
+			field[x][y] = backfield[x][y];
+}
+
+/*
+ * An implementation of "Bastard" algorithm 
+ * it comes from Federico Poloni's "bastet"
+ */
+
+void
+BlockOps::bastardPick ()
+{
+	int scores[tableSize];
+	int blocks[tableSize];
+	int chance[tableSize];
+
+	/* This generates a priority for each block */
+	saveField ();
+	for (blocknr = 0; blocknr < tableSize; blocknr++)
+	{
+		scores[blocknr] = -32000;
+		for (rot = 0; rot < 4; rot++)
+		{
+			for (posx = 0; posx < COLUMNS; posx++)
+			{
+				int this_score = 0;
+				int x, y;
+
+				if (!blockOkHere(posx, posy = 0, blocknr, rot))
+					continue;
+
+				dropBlock();
+				fallingToLaying();
+
+				/* Count the completed lines */
+				this_score += 5000 * checkFullLines ();
+
+				/* Count heights of columns */
+				for (x = 0; x < COLUMNS; x++)
+				{
+					for (y = 0; y < LINES; y++)
+						if (field[x][y].what == LAYING)
+							break;
+					this_score -= 5 * (LINES - y);
+				}
+
+				restoreField ();
+				if (scores[blocknr] < this_score)
+					scores[blocknr] = this_score;
+			}
+		}
+	}
+
+	for (int i = 0; i < tableSize; i++) {
+		/* Initialize chances table */
+		chance[i] = 100;
+		/* Initialize block/priority table */
+		blocks[i] = i;
+		/* Perturb score (-2 to +2), to avoid stupid tie handling */
+		scores[i] += g_random_int_range(-2, 2);
+	}
+
+	/* Sorts blocks by priorities, worst (interesting to us) first*/
+	for (int i = 0; i < tableSize; i++)
+	{
+		for (int ii = 0; ii < tableSize - 1; ii++)
+		{
+			if (scores[blocks[ii]] > scores[blocks[ii+1]])
+			{
+				int t = blocks[ii];
+				blocks[ii] = blocks[ii+1];
+				blocks[ii+1] = t;
+			}
+		}
+	}
+
+	/* Lower the chances we're giving the worst one */
+	chance[0] = 75;
+	chance[1] = 92;
+	chance[2] = 98;
+
+	/* Actually choose a piece */
+	int rnd = g_random_int_range(0, 99);
+	for (int i = 0; i < tableSize; i++)
+	{
+		blocknr = blocks[i];
+		if (rnd < chance[i])
+			break;
+	}
+
+	/* This will almost certainly not given next */
+	blocknr_next = blocks[tableSize-1];
+}
+
 bool
 BlockOps::generateFallingBlock()
 {
+	if (bastard_mode)
+	{
+		abort ();
+		bastardPick();
+	}
+	else
+	{
+		blocknr = blocknr_next == -1 ? g_random_int_range(0, tableSize) :
+			blocknr_next;
+		blocknr_next = g_random_int_range(0, tableSize);
+	}
+
 	posx = COLUMNS / 2 + 1;
 	posy = 0;
 
 	posx_old = COLUMNS / 2 + 1;
 	posy_old = 0;
 
-	blocknr = blocknr_next == -1 ? g_random_int_range(0, tableSize) :
-		blocknr_next;
 	rot = rot_next == -1 ? g_random_int_range(0, 4) : rot_next;
 	int cn = random_block_colors ? g_random_int_range(0, NCOLOURS) :
 		blocknr % NCOLOURS;
 	color = color_next == -1 ? cn : color_next;
 
-	blocknr_next = g_random_int_range(0, tableSize);
 	rot_next = g_random_int_range(0, 4);
 	color_next = random_block_colors ? g_random_int_range(0, NCOLOURS) :
 		blocknr_next % NCOLOURS;
Index: tetris.h
===================================================================
--- tetris.h	(revision 8828)
+++ tetris.h	(working copy)
@@ -36,6 +36,7 @@
 #define KEY_DO_PREVIEW                "do_preview"
 #define KEY_LINE_FILL_HEIGHT          "line_fill_height"
 #define KEY_LINE_FILL_PROBABILITY     "line_fill_probability"
+#define KEY_BASTARD_MODE              "bastard_mode"
 #define KEY_RANDOM_BLOCK_COLORS       "random_block_colors"
 #define KEY_ROTATE_COUNTER_CLOCKWISE  "rotate_counter_clock_wise"
 #define KEY_SOUND                     "sound"
@@ -64,6 +65,7 @@
 extern int rot_next;
 
 extern bool random_block_colors;
+extern bool bastard_mode;
 
 #ifndef HAVE_CLUTTER
 class Field;
@@ -144,6 +146,7 @@
 	static void setSound (GtkWidget * widget, gpointer data);
 	static void setSelectionPreview (GtkWidget * widget, void *d);
 	static void setSelectionBlocks (GtkWidget * widget, void *d);
+	static void setBastardMode (GtkWidget * widget, void *d);
 	static void setRotateCounterClockWise (GtkWidget * widget, void *d);
 	static void setTarget (GtkWidget * widget, void *d);
 	static void setSelection (GtkWidget * widget, void *data);
@@ -183,6 +186,7 @@
 	GtkWidget *fill_prob_spinner;
 	GtkWidget *do_preview_toggle;
 	GtkWidget *random_block_colors_toggle;
+	GtkWidget *bastard_mode_toggle;
 	GtkWidget *rotate_counter_clock_wise_toggle;
 	GtkWidget *useTargetToggle;
 	GtkWidget *sound_toggle;
Index: blockops-noclutter.h
===================================================================
--- blockops-noclutter.h	(revision 8828)
+++ blockops-noclutter.h	(working copy)
@@ -48,6 +48,9 @@
 	int dropBlock ();
 	void fallingToLaying ();
 	int checkFullLines ();
+	void bastardPick ();
+	void saveField ();
+	void restoreField ();
 	bool generateFallingBlock ();
 	void emptyField (void);
 	void emptyField (int filled_lines, int fill_prob);
@@ -71,6 +74,7 @@
 	void generateTarget ();
 
 	Block **field;
+	Block **backfield;
 
 	int blocknr;
 	int rot;
Index: help/C/gnometris.xml
===================================================================
--- help/C/gnometris.xml	(revision 8828)
+++ help/C/gnometris.xml	(working copy)
@@ -199,7 +199,7 @@
      </sect3>
    </sect2>
    <sect2 id="s3-customblocks">
-    <title>Customizing the Blocks</title>
+    <title>Customizing the Gameplay</title>
     <para>From <menuchoice><guimenu>Settings</guimenu><guimenuitem>Preferences</guimenuitem></menuchoice>, you can customize a variety of features related to the blocks: previewing the next block, block colors, and block rotation.</para>
       <sect3 id="s3-sound">
 	<title>Enable sounds</title>
@@ -213,6 +213,10 @@
       <title>Use Random Block Colors</title>
        <para>Choose if you want the various block configurations to be color coded or randomly colored. If you use the colors to help you identify blocks, random colors will make the game more difficult.</para>
      </sect3>
+     <sect3 id="s3-bastard-mode">
+      <title>Bastard Mode</title>
+       <para>Choose this option if you want to select mode that is designed specifically to annoy the player. With this feature enabled, you never get the piece you want, and very seldom complete a row. Next block preview doesn't correspond to actual piece you get next, but to the one you probably wish to get.</para>
+     </sect3>
      <sect3 id="s3-block-rotation">
       <title>Rotate Blocks Counterclockwise</title>
        <para>Choose this option if you want the blocks to rotate counterclockwise or not.</para>
Index: tetris.cpp
===================================================================
--- tetris.cpp	(revision 8828)
+++ tetris.cpp	(working copy)
@@ -64,6 +64,7 @@
 int color_next = -1;
 
 bool random_block_colors = false;
+bool bastard_mode = false;
 bool do_preview = true;
 bool default_bgimage = false;
 bool rotateCounterClockWise = true;
@@ -458,6 +459,8 @@
 
 	random_block_colors = confGetBoolean (KEY_OPTIONS_GROUP, KEY_RANDOM_BLOCK_COLORS, TRUE);
 
+	bastard_mode = confGetBoolean (KEY_OPTIONS_GROUP, KEY_BASTARD_MODE, FALSE);
+
 	rotateCounterClockWise = confGetBoolean (KEY_OPTIONS_GROUP, KEY_ROTATE_COUNTER_CLOCKWISE, TRUE);
 
 	line_fill_height = confGetInt (KEY_OPTIONS_GROUP, KEY_LINE_FILL_HEIGHT, 0);
@@ -496,6 +499,7 @@
 		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sound_toggle), games_sound_is_enabled ());
 		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (do_preview_toggle), do_preview);
 		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (random_block_colors_toggle), random_block_colors);
+		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (bastard_mode_toggle), bastard_mode);
 		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (rotate_counter_clock_wise_toggle), rotateCounterClockWise);
 
 		if (theme_preview) {
@@ -533,6 +537,13 @@
 }
 
 void
+Tetris::setBastardMode(GtkWidget *widget, void *d)
+{
+	games_conf_set_boolean (KEY_OPTIONS_GROUP, KEY_BASTARD_MODE,
+				GTK_TOGGLE_BUTTON (widget)->active);
+}
+
+void
 Tetris::setRotateCounterClockWise(GtkWidget *widget, void *d)
 {
 	games_conf_set_boolean (KEY_OPTIONS_GROUP, KEY_ROTATE_COUNTER_CLOCKWISE,
@@ -729,6 +740,15 @@
 	gtk_box_pack_start (GTK_BOX (fvbox), t->random_block_colors_toggle,
 			    0, 0, 0);
 
+	/* bastard mode */
+	t->bastard_mode_toggle =
+		gtk_check_button_new_with_mnemonic (_("_Bastard mode"));
+	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (t->bastard_mode_toggle),
+				     bastard_mode);
+	g_signal_connect (t->bastard_mode_toggle, "clicked",
+			  G_CALLBACK (setBastardMode), d);
+	gtk_box_pack_start (GTK_BOX (fvbox), t->bastard_mode_toggle, 0, 0, 0);
+
 	/* rotate counter clock wise */
  	t->rotate_counter_clock_wise_toggle =
 		gtk_check_button_new_with_mnemonic (_("_Rotate blocks counterclockwise"));
Index: blockops.h
===================================================================
--- blockops.h	(revision 8828)
+++ blockops.h	(working copy)
@@ -100,6 +100,9 @@
 	bool blockOkHere (int x, int y, int b, int r);
 	void eliminateLine (int l);
 	bool checkFullLine(int l);
+	void bastardPick ();
+	void saveField ();
+	void restoreField ();
 
 	GtkWidget * w;
 
@@ -113,6 +116,7 @@
 	gint themeID;
 
 	Block **field;
+	Block **backfield;
 
 	int blocknr;
 	int rot;
Index: blockops-noclutter.cpp
===================================================================
--- blockops-noclutter.cpp	(revision 8828)
+++ blockops-noclutter.cpp	(working copy)
@@ -1,6 +1,7 @@
 /* -*- mode:C++; tab-width:8; c-basic-offset:8; indent-tabs-mode:true -*- */
 /*
  * written by J. Marcin Gorycki <marcin.gorycki@intel.com>
+ * "bastard" mode by Lubomir Rintel <lkundrak@v3.sk>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -30,12 +31,15 @@
 	color (0)
 {
 	field = new Block*[COLUMNS];
+	backfield = new Block*[COLUMNS];	
 
 	posx = COLUMNS / 2;
 	posy = 0;
 
-	for (int i = 0; i < COLUMNS; ++i)
+	for (int i = 0; i < COLUMNS; ++i) {
 		field[i] = new Block[LINES];
+		backfield[i] = new Block[LINES];
+	}
 
 	emptyField();
 }
@@ -286,20 +290,134 @@
 	return numFullLines;
 }
 
+void
+BlockOps::saveField ()
+{
+	for (int y = 0; y < LINES; y++)
+		for (int x = 0; x < COLUMNS; x++)
+			backfield[x][y] = field[x][y];
+}
+
+void
+BlockOps::restoreField ()
+{
+	for (int y = 0; y < LINES; y++)
+		for (int x = 0; x < COLUMNS; x++)
+			field[x][y] = backfield[x][y];
+}
+
+/*
+ * An implementation of "Bastard" algorithm 
+ * it comes from Federico Poloni's "bastet"
+ */
+
+void
+BlockOps::bastardPick ()
+{
+	int scores[tableSize];
+	int blocks[tableSize];
+	int chance[tableSize];
+
+	/* This generates a priority for each block */
+	saveField ();
+	for (blocknr = 0; blocknr < tableSize; blocknr++)
+	{
+		scores[blocknr] = -32000;
+		for (rot = 0; rot < 4; rot++)
+		{
+			for (posx = 0; posx < COLUMNS; posx++)
+			{
+				int this_score = 0;
+				int x, y;
+
+				if (!blockOkHere(posx, posy = 0, blocknr, rot))
+					continue;
+
+				dropBlock();
+				fallingToLaying();
+
+				/* Count the completed lines */
+				this_score += 5000 * checkFullLines ();
+
+				/* Count heights of columns */
+				for (x = 0; x < COLUMNS; x++)
+				{
+					for (y = 0; y < LINES; y++)
+						if (field[x][y].what == LAYING)
+							break;
+					this_score -= 5 * (LINES - y);
+				}
+
+				restoreField ();
+				if (scores[blocknr] < this_score)
+					scores[blocknr] = this_score;
+			}
+		}
+	}
+
+	for (int i = 0; i < tableSize; i++) {
+		/* Initialize chances table */
+		chance[i] = 100;
+		/* Initialize block/priority table */
+		blocks[i] = i;
+		/* Perturb score (-2 to +2), to avoid stupid tie handling */
+		scores[i] += g_random_int_range(-2, 2);
+	}
+
+	/* Sorts blocks by priorities, worst (interesting to us) first*/
+	for (int i = 0; i < tableSize; i++)
+	{
+		for (int ii = 0; ii < tableSize - 1; ii++)
+		{
+			if (scores[blocks[ii]] > scores[blocks[ii+1]])
+			{
+				int t = blocks[ii];
+				blocks[ii] = blocks[ii+1];
+				blocks[ii+1] = t;
+			}
+		}
+	}
+
+	/* Lower the chances we're giving the worst one */
+	chance[0] = 75;
+	chance[1] = 92;
+	chance[2] = 98;
+
+	/* Actually choose a piece */
+	int rnd = g_random_int_range(0, 99);
+	for (int i = 0; i < tableSize; i++)
+	{
+		blocknr = blocks[i];
+		if (rnd < chance[i])
+			break;
+	}
+
+	/* This will almost certainly not given next */
+	blocknr_next = blocks[tableSize-1];
+}
+
 bool
 BlockOps::generateFallingBlock()
 {
+	if (bastard_mode)
+	{
+		bastardPick();
+	}
+	else
+	{
+		blocknr = blocknr_next == -1 ? g_random_int_range(0, tableSize) :
+			blocknr_next;
+		blocknr_next = g_random_int_range(0, tableSize);
+	}
+
 	posx = COLUMNS / 2 + 1;
 	posy = 0;
 
-	blocknr = blocknr_next == -1 ? g_random_int_range(0, tableSize) :
-		blocknr_next;
 	rot = rot_next == -1 ? g_random_int_range(0, 4) : rot_next;
 	int cn = random_block_colors ? g_random_int_range(0, NCOLOURS) :
 		blocknr % NCOLOURS;
 	color = color_next == -1 ? cn : color_next;
 
-	blocknr_next = g_random_int_range(0, tableSize);
 	rot_next = g_random_int_range(0, 4);
 	color_next = random_block_colors ? g_random_int_range(0, NCOLOURS) :
 		blocknr_next % NCOLOURS;
