<commit_before>(in-ns 'game.core)
(declare corp-trace-prompt optional-ability
         check-optional check-psi check-trace
         can-trigger? do-choices do-ability do-effect
         print-msg register-end-turn register-once
         psi-game resolve-psi resolve-trace show-select)

; Functions for implementing card abilities and prompts

; Abilities
(defn resolve-ability
  "Resolves an ability defined by the given ability map. Checks :req functions; shows prompts, psi games,
  traces, etc. as needed; charges costs; invokes :effect functions. All card effects and most engine effects
  should run through this method.

  All keys that can be used in an ability map:
  COMMON KEYS
  :req -- a 4-argument function that must return true or false. If false, the ability will not be resolved.
  :cost -- a vector of costs to charge, for example [:credit 1 :click 1]. If the costs cannot be paid, the ability
           will not be resolved.
  :msg -- either a string or a 4-argument function that returns a string. Prints to the log when the ability is finished.
  :effect -- a 4-argument function that will be called if the ability will be resolved (if :req is true or not present,
             costs can be paid, and any prompts are resolved.)
  :player -- manually specifies which player the ability should affect, rather than leave it implicit.

  PROMPT KEYS
  :choices -- this key signals a prompt of some kind. Can be:
      * a 4-argument function returning a vector of cards or strings -- user chooses one option.
        Called a 'choice' prompt.
      * the keyword :credit -- user chooses an integer up to their current credit amount.
      * the keyword :counter -- user chooses an integer up to the :counter value of the given card.
      * a map containing the keyword :number with a value of a 4-argument function returning an integer -- user
        chooses an integer up to the value of the map.
      * a map containing the keyword :req with a value of a 1-argument function returning true or false. Triggers a
        'select' prompt with targeting cursor; only cards that cause the 1-argument function to return true will
        be allowed.
  :prompt -- a string or 4-argument function returning a string to display in the prompt menu.
  :priority -- a numeric value, or true (equivalent to 1). Prompts are inserted into the prompt queue and sorted base
               on priority, with higher priorities coming first. The sort is stable, so if two prompts have the same
               priority, the prompt that was inserted first will remain first after the sort. You should rarely need
               to use a priority larger than 1.
  :not-distinct -- true if the prompt should not collapse :choices entries of the same string to one button.
                   Defaults to false.
  :cancel-effect -- if the prompt uses a Cancel button, this 4-argument function will be called if the user
                    chooses Cancel.

  OTHER PROMPTS
  :psi -- a map that starts a psi game. Has keys :equal and :not-equal, whose values are 4-argument functions
          which are triggered when the game resolves.
  :trace -- a map that starts a trace. Has a :base key that is either an integer or a 4-argument function
            returning an integer for the base strength. The map is otherwise a normal ability map that can
            contain :req, :effect, or any other key in an ability; the :effect is only triggered if the trace
            succeeds. Can also have a key :kicker that is an ability map with key :min, whose effect triggers
            if the trace strength matches or exceeds the :min value. (Constellation ice.)

  OTHER KEYS
  :counter-cost / :advance-counter-cost -- number of counters to remove to resolve the ability
  :once -- its only value is :per-turn; signifies an effect that can only be triggered once per turn.
  :once-key -- by default, each :once is distinct per card. If multiple copies of a card can only resolve
               some ability once between all of them, then the card should specify a manual :once-key that can
               be any value, preferrably a unique keyword.
  :optional -- shows a 'Yes/No' prompt to let the user decide whether to resolve the ability.
  :end-turn -- if the ability is resolved, then this ability map will be resolved at the end of the turn."

  ; perhaps the most important function in the game logic
  [state side ability card targets]
  (when ability
    ; Is this an optional ability?
    (check-optional state side ability card targets)
    ; Is this a psi game?
    (check-psi state side ability card targets)
    ; Is this a trace?
    (check-trace state side ability card targets)
    ; Ensure this ability can be triggered more than once per turn,
    ; or has not been yet been triggered this turn.
    (when (can-trigger? state side ability card targets)
      (if (:choices ability)
        ; It's a prompt!
        (do-choices state side ability card targets)
        ; Not a prompt. Trigger the ability.
        (do-ability state side ability card targets)))))


; Checking functions for resolve-ability
(defn- check-req
  "Check if the requirement is fulfilled, or no requirement present"
  [state side card targets ability]
  (if-let [req (:req ability)]
    (req state side card targets)
    ; return true if no requirement present
    true))

(defn- check-optional
  "Checks if there is an optional ability to resolve"
  [state side {:keys [optional] :as ability} {:keys [cid] :as card} targets]
  (when (and optional
             (not (get-in @state [(:once optional) (or (:once-key optional) cid)]))
             (check-req state side card targets optional))
    (optional-ability state (or (:player optional) side) card
                      (:prompt optional) optional targets)))

(defn- check-psi
  "Checks if a psi-game is to be resolved"
  [state side {:keys [psi] :as ability} card targets]
  (when (and psi (check-req state side card targets psi))
    (psi-game state side card psi)))

(defn- check-trace
  "Checks if there is a trace to resolve"
  [state side {:keys [trace] :as ability} card targets]
  (when (and trace (check-req state side card targets trace))
    (corp-trace-prompt state card trace)))

(defn- can-trigger?
  "Checks if ability can trigger. Checks that once-per-turn is not violated."
  [state side {:keys [once once-key] :as ability} card targets]
  (let [cid (:cid card)]
    (and (not (get-in @state [once (or once-key cid)]))
         (check-req state side card targets ability))))

(defn- do-choices
  "Handle a choices ability"
  [state side {:keys [choices player priority cancel-effect not-distinct prompt] :as ability}
   card targets]
  (let [s (or player side)
        ab (dissoc ability :choices)
        args {:priority priority :cancel-effect cancel-effect}]
   (if (map? choices)
     ; Two types of choices use maps: select prompts, and :number prompts.
     (if (:req choices)
       ; a select prompt
       (show-select state s card ability {:priority priority})
       ; a :number prompt
       (let [n ((:number choices) state side card targets)]
         (prompt! state s card prompt {:number n} ab args)))
     ; Not a map; either :credit, :counter, or a vector of cards or strings.
     (let [cs (if-not (fn? choices)
                choices ; :credit or :counter
                (let [cards (choices state side card targets)] ; a vector of cards or strings
                  (if not-distinct cards (distinct-by :title cards))))]
       (prompt! state s card prompt cs ab args)))))

(defn- do-ability
  "Perform the ability, checking all costs can be paid etc."
  [state side {:keys [cost counter-cost advance-counter-cost] :as ability}
   {:keys [counter advance-counter] :as card} targets]
  ; Ensure counter costs can be paid
  (when (and (or (not counter-cost)
                 (<= counter-cost (or counter 0)))<commit_msg>Further cleans up resolve-ability function.<commit_after>(in-ns 'game.core)
(declare corp-trace-prompt optional-ability
         check-optional check-psi check-trace
         can-trigger? do-choices do-ability
         psi-game resolve-psi resolve-trace show-select)

; Functions for implementing card abilities and prompts

; Abilities
(defn resolve-ability
  "Resolves an ability defined by the given ability map. Checks :req functions; shows prompts, psi games,
  traces, etc. as needed; charges costs; invokes :effect functions. All card effects and most engine effects
  should run through this method.

  All keys that can be used in an ability map:
  COMMON KEYS
  :req -- a 4-argument function that must return true or false. If false, the ability will not be resolved.
  :cost -- a vector of costs to charge, for example [:credit 1 :click 1]. If the costs cannot be paid, the ability
           will not be resolved.
  :msg -- either a string or a 4-argument function that returns a string. Prints to the log when the ability is finished.
  :effect -- a 4-argument function that will be called if the ability will be resolved (if :req is true or not present,
             costs can be paid, and any prompts are resolved.)
  :player -- manually specifies which player the ability should affect, rather than leave it implicit.

  PROMPT KEYS
  :choices -- this key signals a prompt of some kind. Can be:
      * a 4-argument function returning a vector of cards or strings -- user chooses one option.
        Called a 'choice' prompt.
      * the keyword :credit -- user chooses an integer up to their current credit amount.
      * the keyword :counter -- user chooses an integer up to the :counter value of the given card.
      * a map containing the keyword :number with a value of a 4-argument function returning an integer -- user
        chooses an integer up to the value of the map.
      * a map containing the keyword :req with a value of a 1-argument function returning true or false. Triggers a
        'select' prompt with targeting cursor; only cards that cause the 1-argument function to return true will
        be allowed.
  :prompt -- a string or 4-argument function returning a string to display in the prompt menu.
  :priority -- a numeric value, or true (equivalent to 1). Prompts are inserted into the prompt queue and sorted base
               on priority, with higher priorities coming first. The sort is stable, so if two prompts have the same
               priority, the prompt that was inserted first will remain first after the sort. You should rarely need
               to use a priority larger than 1.
  :not-distinct -- true if the prompt should not collapse :choices entries of the same string to one button.
                   Defaults to false.
  :cancel-effect -- if the prompt uses a Cancel button, this 4-argument function will be called if the user
                    chooses Cancel.

  OTHER PROMPTS
  :psi -- a map that starts a psi game. Has keys :equal and :not-equal, whose values are 4-argument functions
          which are triggered when the game resolves.
  :trace -- a map that starts a trace. Has a :base key that is either an integer or a 4-argument function
            returning an integer for the base strength. The map is otherwise a normal ability map that can
            contain :req, :effect, or any other key in an ability; the :effect is only triggered if the trace
            succeeds. Can also have a key :kicker that is an ability map with key :min, whose effect triggers
            if the trace strength matches or exceeds the :min value. (Constellation ice.)

  OTHER KEYS
  :counter-cost / :advance-counter-cost -- number of counters to remove to resolve the ability
  :once -- its only value is :per-turn; signifies an effect that can only be triggered once per turn.
  :once-key -- by default, each :once is distinct per card. If multiple copies of a card can only resolve
               some ability once between all of them, then the card should specify a manual :once-key that can
               be any value, preferrably a unique keyword.
  :optional -- shows a 'Yes/No' prompt to let the user decide whether to resolve the ability.
  :end-turn -- if the ability is resolved, then this ability map will be resolved at the end of the turn."

  ; perhaps the most important function in the game logic
  [state side ability card targets]
  (when ability
    ; Is this an optional ability?
    (check-optional state side ability card targets)
    ; Is this a psi game?
    (check-psi state side ability card targets)
    ; Is this a trace?
    (check-trace state side ability card targets)
    ; Ensure this ability can be triggered more than once per turn,
    ; or has not been yet been triggered this turn.
    (when (can-trigger? state side ability card targets)
      (if (:choices ability)
        ; It's a prompt!
        (do-choices state side ability card targets)
        ; Not a prompt. Trigger the ability.
        (do-ability state side ability card targets)))))


; Checking functions for resolve-ability
(defn- check-req
  "Check if the requirement is fulfilled, or no requirement present"
  [state side card targets ability]
  (if-let [req (:req ability)]
    (req state side card targets)
    ; return true if no requirement present
    true))

(defn- check-optional
  "Checks if there is an optional ability to resolve"
  [state side ability card targets]
  (when-let [optional (:optional ability)]
    (when (and (not (get-in @state [(:once optional) (or (:once-key optional) (:cid card))]))
               (check-req state side card targets optional))
      (optional-ability state (or (:player optional) side) card (:prompt optional) optional targets))))

(defn- check-psi
  "Checks if a psi-game is to be resolved"
  [state side ability card targets]
  (when-let [psi (:psi ability)]
    (when (check-req state side card targets psi)
      (psi-game state side card psi))))

(defn- check-trace
  "Checks if there is a trace to resolve"
  [state side ability card targets]
  (when-let [trace (:trace ability)]
    (when (check-req state side card targets trace)
      (corp-trace-prompt state card trace))))

(defn- can-trigger?
  "Checks if ability can trigger. Checks that once-per-turn is not violated."
  [state side {:keys [once once-key] :as ability} card targets]
  (let [cid (:cid card)]
    (and (not (get-in @state [once (or once-key cid)]))
         (check-req state side card targets ability))))

(defn- do-choices
  "Handle a choices ability"
  [state side {:keys [choices player priority cancel-effect not-distinct prompt] :as ability}
   card targets]
  (let [s (or player side)
        ab (dissoc ability :choices)
        args {:priority priority :cancel-effect cancel-effect}]
   (if (map? choices)
     ; Two types of choices use maps: select prompts, and :number prompts.
     (if (:req choices)
       ; a select prompt
       (show-select state s card ability {:priority priority})
       ; a :number prompt
       (let [n ((:number choices) state side card targets)]
         (prompt! state s card prompt {:number n} ab args)))
     ; Not a map; either :credit, :counter, or a vector of cards or strings.
     (let [cs (if-not (fn? choices)
                choices ; :credit or :counter
                (let [cards (choices state side card targets)] ; a vector of cards or strings
                  (if not-distinct cards (distinct-by :title cards))))]
       (prompt! state s card prompt cs ab args)))))

(declare print-msg do-effect register-end-turn register-once)

(defn- do-ability
  "Perform the ability, checking all costs can be paid etc."
  [state side {:keys [cost counter-cost advance-counter-cost] :as ability}
   {:keys [counter advance-counter] :as card} targets]
  ; Ensure counter costs can be paid
  (when (and (or (not counter-cost)
                 (<= counter-cost (or counter 0)))
{'fraction_non_alphanumeric': 0.0753679954513867, 'fraction_numerical': 0.002021605913197296, 'mean_word_length': 2.8590931253047294, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '948737', 'n_tokens_mistral': 4242, 'n_tokens_neox': 4098, 'n_words': 2208}