Subject: Gnometris is way TOO FRIENDLY From: Lubomir Rintel 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: gnometris/help/C/gnometris.xml =================================================================== --- gnometris/help/C/gnometris.xml (revision 8391) +++ gnometris/help/C/gnometris.xml (working copy) @@ -199,7 +199,7 @@ - Customizing the Blocks + Customizing the Gameplay From SettingsPreferences, you can customize a variety of features related to the blocks: previewing the next block, block colors, and block rotation. Enable sounds @@ -213,6 +213,10 @@ Use Random Block Colors 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. + + Bastard Mode + 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. + Rotate Blocks Counterclockwise Choose this option if you want the blocks to rotate counterclockwise or not. Index: gnometris/tetris.cpp =================================================================== --- gnometris/tetris.cpp (revision 8391) +++ gnometris/tetris.cpp (working copy) @@ -63,6 +63,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; @@ -448,6 +449,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); @@ -486,6 +489,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) { @@ -521,6 +525,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, @@ -694,7 +705,7 @@ g_signal_connect (t->sound_toggle, "clicked", G_CALLBACK (setSound), d); gtk_box_pack_start (GTK_BOX (fvbox), t->sound_toggle, 0, 0, 0); - + /* preview next block */ t->do_preview_toggle = gtk_check_button_new_with_mnemonic (_("_Preview next block")); @@ -715,6 +726,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: gnometris/blockops.cpp =================================================================== --- gnometris/blockops.cpp (revision 8391) +++ gnometris/blockops.cpp (working copy) @@ -38,12 +38,16 @@ color (0) { field = new Block*[COLUMNS]; + backfield = new Block*[COLUMNS]; posx = COLUMNS / 2; posy = 0; for (int i = 0; i < COLUMNS; ++i) + { field[i] = new Block[LINES]; + backfield[i] = new Block[LINES]; + } emptyField(); } @@ -294,20 +298,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; Index: gnometris/tetris.h =================================================================== --- gnometris/tetris.h (revision 8391) +++ gnometris/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; class Field; class Preview; @@ -139,6 +141,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); @@ -178,6 +181,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: gnometris/blockops.h =================================================================== --- gnometris/blockops.h (revision 8391) +++ gnometris/blockops.h (working copy) @@ -64,6 +64,9 @@ SlotType fill); bool blockOkHere (int x, int y, int b, int r); void eliminateLine (int l); + void bastardPick (); + void saveField (); + void restoreField (); bool useTarget; @@ -72,6 +75,7 @@ void generateTarget (); Block **field; + Block **backfield; int blocknr; int rot;