fixed unexpected behaviour with solving levels with Sokoban elements
authorHolger Schemel <info@artsoft.org>
Sun, 28 Oct 2018 23:26:22 +0000 (00:26 +0100)
committerHolger Schemel <info@artsoft.org>
Sun, 28 Oct 2018 23:26:22 +0000 (00:26 +0100)
This patch fixes problems with levels using a different number of
Sokoban objects and Sokoban fields, causing unexpected behaviour.

Before, levels with Sokoban objects, but without Sokoban fields, could
be solved by just pushing any of the Sokoban objects (which triggers a
check if there are any empty Sokoban fields left in the level, which
is not the case in this scenario). In addition, pushing a Sokoban
object to a Sokoban field would also solve a level with two Sokoban
objects, but only one Sokoban field, but not a level with two Sokoban
fields, but only one Sokoban object, which seems confusing or at least
unexpected. (Things get even more complicated and confusing if Sokoban
fields or objects are created during the game, for example as content
of yam-yams.)

Now, Sokoban fields and objects in a level are tracked independently
and the level is only solved if there are no Sokoban fields or objects
left on the playfield.

Also see the following forum post for a description of the problem:
https://www.artsoft.org/forum/viewtopic.php?f=7&t=2654

src/editor.c
src/game.c
src/game.h

index 7757fe51e4053a7beb27682fba4bcb42d84c4b7f..55e8104bb85263429867e28151aa4d53a86f513f 100644 (file)
@@ -3071,7 +3071,7 @@ static struct
     GADGET_ID_AUTO_EXIT_SOKOBAN,       GADGET_ID_NONE,
     &level.auto_exit_sokoban,
     NULL, NULL,
-    "exit level if all fields solved", "automatically finish Sokoban levels"
+    "exit level if all tasks solved",  "automatically finish Sokoban levels"
   },
   {
     ED_ELEMENT_SETTINGS_XPOS(0),       ED_ELEMENT_SETTINGS_YPOS(14),
index 479fa0aefda2d2279e9e5e08790a0f1eba85d36a..4a3d1eef4d4e63bbe088da3b3209f31ce03088ad 100644 (file)
@@ -1792,7 +1792,11 @@ static void InitField(int x, int y, boolean init_game)
       break;
 
     case EL_SOKOBAN_FIELD_EMPTY:
-      local_player->sokobanfields_still_needed++;
+      local_player->sokoban_fields_still_needed++;
+      break;
+
+    case EL_SOKOBAN_OBJECT:
+      local_player->sokoban_objects_still_needed++;
       break;
 
     case EL_STONEBLOCK:
@@ -2212,7 +2216,8 @@ static void UpdateGameControlValues(void)
                     game_mm.kettles_still_needed > 0 ||
                     game_mm.lights_still_needed > 0 :
                     local_player->gems_still_needed > 0 ||
-                    local_player->sokobanfields_still_needed > 0 ||
+                    local_player->sokoban_fields_still_needed > 0 ||
+                    local_player->sokoban_objects_still_needed > 0 ||
                     local_player->lights_still_needed > 0);
   int health = (local_player->LevelSolved ?
                local_player->LevelSolved_CountingHealth :
@@ -2381,9 +2386,9 @@ static void UpdateGameControlValues(void)
     local_player->friends_still_needed;
 
   game_panel_controls[GAME_PANEL_SOKOBAN_OBJECTS].value =
-    local_player->sokobanfields_still_needed;
+    local_player->sokoban_objects_still_needed;
   game_panel_controls[GAME_PANEL_SOKOBAN_FIELDS].value =
-    local_player->sokobanfields_still_needed;
+    local_player->sokoban_fields_still_needed;
 
   game_panel_controls[GAME_PANEL_ROBOT_WHEEL].value =
     (game.robot_wheel_active ? EL_ROBOT_WHEEL_ACTIVE : EL_ROBOT_WHEEL);
@@ -3389,7 +3394,8 @@ void InitGame(void)
     player->health_final = MAX_HEALTH;
 
     player->gems_still_needed = level.gems_needed;
-    player->sokobanfields_still_needed = 0;
+    player->sokoban_fields_still_needed = 0;
+    player->sokoban_objects_still_needed = 0;
     player->lights_still_needed = 0;
     player->players_still_needed = 0;
     player->friends_still_needed = 0;
@@ -9053,7 +9059,8 @@ static void ActivateMagicBall(int bx, int by)
 static void CheckExit(int x, int y)
 {
   if (local_player->gems_still_needed > 0 ||
-      local_player->sokobanfields_still_needed > 0 ||
+      local_player->sokoban_fields_still_needed > 0 ||
+      local_player->sokoban_objects_still_needed > 0 ||
       local_player->lights_still_needed > 0)
   {
     int element = Feld[x][y];
@@ -9076,7 +9083,8 @@ static void CheckExit(int x, int y)
 static void CheckExitEM(int x, int y)
 {
   if (local_player->gems_still_needed > 0 ||
-      local_player->sokobanfields_still_needed > 0 ||
+      local_player->sokoban_fields_still_needed > 0 ||
+      local_player->sokoban_objects_still_needed > 0 ||
       local_player->lights_still_needed > 0)
   {
     int element = Feld[x][y];
@@ -9099,7 +9107,8 @@ static void CheckExitEM(int x, int y)
 static void CheckExitSteel(int x, int y)
 {
   if (local_player->gems_still_needed > 0 ||
-      local_player->sokobanfields_still_needed > 0 ||
+      local_player->sokoban_fields_still_needed > 0 ||
+      local_player->sokoban_objects_still_needed > 0 ||
       local_player->lights_still_needed > 0)
   {
     int element = Feld[x][y];
@@ -9122,7 +9131,8 @@ static void CheckExitSteel(int x, int y)
 static void CheckExitSteelEM(int x, int y)
 {
   if (local_player->gems_still_needed > 0 ||
-      local_player->sokobanfields_still_needed > 0 ||
+      local_player->sokoban_fields_still_needed > 0 ||
+      local_player->sokoban_objects_still_needed > 0 ||
       local_player->lights_still_needed > 0)
   {
     int element = Feld[x][y];
@@ -13933,16 +13943,24 @@ static int DigField(struct PlayerInfo *player,
 
     if (IS_SB_ELEMENT(element))
     {
+      boolean sokoban_task_solved = FALSE;
+
       if (element == EL_SOKOBAN_FIELD_FULL)
       {
        Back[x][y] = EL_SOKOBAN_FIELD_EMPTY;
-       local_player->sokobanfields_still_needed++;
+       local_player->sokoban_fields_still_needed++;
+       local_player->sokoban_objects_still_needed++;
       }
 
       if (Feld[nextx][nexty] == EL_SOKOBAN_FIELD_EMPTY)
       {
        Back[nextx][nexty] = EL_SOKOBAN_FIELD_EMPTY;
-       local_player->sokobanfields_still_needed--;
+       local_player->sokoban_fields_still_needed--;
+       local_player->sokoban_objects_still_needed--;
+
+       // sokoban object was pushed from empty field to sokoban field
+       if (Back[x][y] == EL_EMPTY)
+         sokoban_task_solved = TRUE;
       }
 
       Feld[x][y] = EL_SOKOBAN_OBJECT;
@@ -13956,7 +13974,9 @@ static int DigField(struct PlayerInfo *player,
        PlayLevelSoundElementAction(nextx, nexty, EL_SOKOBAN_FIELD_EMPTY,
                                    ACTION_FILLING);
 
-      if (local_player->sokobanfields_still_needed == 0 &&
+      if (sokoban_task_solved &&
+         local_player->sokoban_fields_still_needed == 0 &&
+         local_player->sokoban_objects_still_needed == 0 &&
          (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
       {
        local_player->players_still_needed = 0;
index 553ec4b7498b516879a041fafb2f736e304189f7..5ef27e78085b46d12c8cad88fee58027b47a6b78 100644 (file)
@@ -347,7 +347,8 @@ struct PlayerInfo
   int health_final;
 
   int gems_still_needed;
-  int sokobanfields_still_needed;
+  int sokoban_fields_still_needed;
+  int sokoban_objects_still_needed;
   int lights_still_needed;
   int players_still_needed;
   int friends_still_needed;