Spot the bug - #164: Turn Combat Engine

This dungeon turn processor is skipping the wrong monster state.

function resolveTurn(entity, action, environment) {
  let actionLog = [];
  let statusEffect = entity.status ?? "normal";

  try {
    if (environment.hazard === "miasma" && !entity.immune) {
      throw new Error("suffocating");
    }

    switch (action.type) {
      case "attack":
        actionLog.push(`${entity.name} attacks for ${action.power} dmg`);
        break;
      case "cast":
        if (entity.mana < action.cost) {
          throw new Error("out of mana");
        }
        entity.mana -= action.cost;
        actionLog.push(`${entity.name} casts ${action.spell}`);
        break;
      case "defend":
        entity.guard = true;
        actionLog.push(`${entity.name} braces for impact`);
        break;
      default:
        throw new Error("invalid action");
    }
  } catch (err) {
    actionLog.push(`${entity.name} stumbled: ${err.message}`);
    return { success: false, log: actionLog, finalStatus: statusEffect };
  } finally {
    if (statusEffect === "poisoned") {
      entity.hp = Math.max(0, entity.hp - 5);
      actionLog.push(`${entity.name} takes poison damage`);
    }
    return { success: true, log: actionLog, finalStatus: statusEffect };
  }
}

Reply with what is broken and how you would fix it.

The finally block always returns, which means the try/catch block’s return is ignored. I’d move the poisoned check and its actionLog.push out of finally and before the return in the try/catch block.

Hmm, yes, the finally block overriding the try/catch return is a classic. It’s a subtle control flow issue that can be difficult to spot in a larger codebase.

You’re right, that finally block is definitely a tricky one. It’s always going to return, no matter what happens in the try or catch blocks.

To fix it, you’d want to move the finally block’s logic outside of the try/catch or ensure it doesn’t contain a return statement that overrides everything else. This tutorial on try...catch...finally might help clarify how those work: https://www.kirupa.com/html5/error_handling_js.htm

Spot the Bug answer: The function always returns from the finally block, overriding any error handling or specific return values from the try/catch blocks.

The fix:
Remove the return statement from the finally block.

Why:
The finally block is guaranteed to execute after try and catch blocks, regardless of whether an exception was thrown or caught. If a return statement is present in finally, it will always execute and return its value, effectively discarding any return statements or thrown errors from the try or catch blocks. This prevents the function from correctly reporting failures via ‘success: false’ or propagating errors.

First-answer leaderboard

  1. @emmawalter5 - 8 (firsts) :trophy:
  2. @kirupa - 8 (firsts) :trophy:
  3. @Apexcodes - 6 (firsts)
  4. @adnanahmed - 2 (firsts)

Okay so it’s like a hard bypass on the signal chain. everything gets routed through finally no matter what.