# Demonstrates: a priority window, fast actions with costs, a stack (last in, first out),
# and cancelling an action on the stack.
game: ambush
label: Ambush
players: "2 players"
num_players: 2
mode: competitive
summary: >
  Both players have 3 life and 2 tokens. In the window, the player with priority may set a trap
  (1 token: the other player loses 1 life) or disarm one of the other player's traps (1 token).
  Everything goes on a stack and resolves from top to bottom once all players pass in succession.
  After the window, the player with more life left wins.

state:
  vars:
    life: {scope: player, type: int, init: 3}
    tokens: {scope: player, type: int, init: 2}
    done: {scope: global, type: bool, init: false}

priority:
  protocol:
    first: player 1
    first_x: "0"
    order: clockwise_from_first
    closes: all_pass_in_succession
    resolution: lifo_stack
  windows:
    ambush: {label: Ambush, allows: [fast]}

flow:
  id: game
  label: Game
  kind: sequence
  children:
    # The window opens after this step. A step with no effect is just an anchor point.
    - {id: setup, label: Setup, kind: step, do: set_up, window: ambush}
    - {id: outcome, label: Outcome, kind: step, do: finish}

actions:
  set_up: {effect: []}
  # speed: fast -> playable in windows that allow 'fast'. The cost is paid at once; the effect
  # happens only when the action resolves from the stack. In a window, `actor` is the player
  # with priority.
  trap:
    speed: fast
    pre: has a token left
    pre_x: "tokens[actor] >= 1"
    cost: [{op: add, var: "tokens[actor]", amount: -1}]
    effect: [{op: add, var: "life[1 - actor]", amount: -1}]
  disarm:
    speed: fast
    pre: has a token left, and the other player has a trap on the stack
    pre_x: "tokens[actor] >= 1 and any(not s['cancelled'] and s['actor'] != actor for s in stack)"
    cost:
      # The target is fixed when the action is played: the other player's topmost action that is
      # not yet cancelled.
      - {op: let, bind: target, value_x: "max(i for i, s in enumerate(stack) if not s['cancelled'] and s['actor'] != actor)"}
      - {op: add, var: "tokens[actor]", amount: -1}
    effect: [{op: cancel, target_x: target}]
  finish: {effect: [{op: set, var: done, value: true}]}

end:
  - id: result
    if: after the window; compare life left
    if_x: done
    result: score
    returns_x: "[1, -1] if life[0] > life[1] else [-1, 1] if life[1] > life[0] else [0, 0]"
