Skip to content

Commit

Permalink
Merge pull request #7751 from NBKelly/matryoshka-automate
Browse files Browse the repository at this point in the history
make matryoshka a cost
  • Loading branch information
NoahTheDuke authored Oct 10, 2024
2 parents 45124b8 + 61879cd commit d2adf16
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 30 deletions.
53 changes: 27 additions & 26 deletions src/clj/game/cards/programs.clj
Original file line number Diff line number Diff line change
Expand Up @@ -1983,10 +1983,14 @@

(defcard "Lobisomem"
(auto-icebreaker {:data {:counter {:power 1}}
:abilities [(break-sub 1 1 "Code Gate")
:abilities [(break-sub 1 1 "Code Gate" {:auto-break-sort 1})
{:label "Break X Barrier subroutines"
:cost [(->c :x-credits) (->c :power 1)]
:break-cost [(->c :x-credits) (->c :power 1)]
:auto-break-creds-per-sub 1
:break 0 ;; technically not correct, but it's enough for the engine to pick up for auto-breaking
:break-req (req (and (active-encounter? state)
(has-subtype? current-ice "Barrier")))
:req (req (and
(active-encounter? state)
(<= (get-strength current-ice) (get-strength card))))
Expand Down Expand Up @@ -2100,31 +2104,21 @@

(defcard "Matryoshka"
(let [break-abi {:label "Break X subroutines"
:cost [(->c :x-credits)]
:break-cost [(->c :x-credits)]
:req (req (let [hosted (:hosted (get-card state card))
valid (filter #(not (facedown? %)) hosted)
same-title (filter #(= (:title card) (:title %)) valid)]
(and (active-encounter? state)
current-ice card
(<= (get-strength current-ice) (get-strength card))
(not-empty same-title))))
; no break-req to not enable auto-pumping
:cost [(->c :x-credits)(->c :turn-hosted-matryoshka-facedown 1)]
:break-cost [(->c :x-credits)(->c :turn-hosted-matryoshka-facedown 1)]
:auto-break-creds-per-sub 1
:break 0 ;; technically not correct, but it's enough for the engine to pick up for auto-breaking
:break-req (req (active-encounter? state))
:req (req (and
(active-encounter? state)
(<= (get-strength current-ice) (get-strength card))))
:msg (msg "break " (quantify (cost-value eid :x-credits) "subroutine")
" on " (card-str state current-ice) " and turn 1 hosted copy of "
(:title card) " facedown")
:effect (req
(let [hosted (:hosted (get-card state card))
valid (filter #(not (facedown? %)) hosted)
same-title (filter #(= (:title card) (:title %)) valid)
first-copy (first same-title)]
(flip-facedown state side (get-card state first-copy))
(if (pos? (cost-value eid :x-credits))
(continue-ability
state side
(break-sub nil (cost-value eid :x-credits) "All")
card nil)
(effect-completed state side eid))))}
" on " (card-str state current-ice))
:effect (effect
(continue-ability
(when (pos? (cost-value eid :x-credits))
(break-sub nil (cost-value eid :x-credits) "All" {:repeatable false}))
card nil))}
host-abi {:action true
:label "Host 1 copy of Matryoshka"
:prompt (msg "Choose 1 copy of " (:title card) " in the grip")
Expand Down Expand Up @@ -3415,7 +3409,12 @@
:once :per-run
:req (req (and (break-req state side eid card targets)
(<= (get-strength current-ice) (get-strength card))))
; no break-req to not enable auto-pumping
:auto-break-creds-per-sub 1
:auto-break-sort 2
:break 0 ;; technically not correct, but it's enough for the engine to pick up for auto-breaking
:break-req (req (and (active-encounter? state)
(not-used-once? state {:once :per-run} card)
(has-subtype? current-ice "Code Gate")))
:msg (msg "break " (quantify (cost-value eid :x-credits) "subroutine")
" on " (card-str state current-ice))
:effect (effect
Expand All @@ -3424,6 +3423,8 @@
(break-sub nil (cost-value eid :x-credits) "Code Gate"))
card nil))}
(break-sub 1 1 "Code Gate" {:label "Break 1 Code Gate subroutine (Virtual restriction)"
;; this should be prioritized when available
:auto-break-sort 1
:req (req (<= 3 (count (filter #(has-subtype? % "Virtual")
(all-active-installed state :runner)))))})
(strength-pump 1 1)]})))
Expand Down
7 changes: 5 additions & 2 deletions src/clj/game/core/actions.clj
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
[game.core.cost-fns :refer [break-sub-ability-cost card-ability-cost card-ability-cost score-additional-cost-bonus]]
[game.core.effects :refer [any-effects is-disabled-reg?]]
[game.core.eid :refer [effect-completed make-eid]]
[game.core.engine :refer [ability-as-handler checkpoint register-pending-event pay queue-event resolve-ability trigger-event-simult]]
[game.core.engine :refer [ability-as-handler checkpoint register-once register-pending-event pay queue-event resolve-ability trigger-event-simult]]
[game.core.flags :refer [can-advance? can-score?]]
[game.core.ice :refer [break-subroutine! break-subs-event-context get-current-ice get-pump-strength get-strength pump resolve-subroutine! resolve-unbroken-subs!]]
[game.core.ice :refer [break-subroutine! break-subs-event-context get-current-ice get-pump-strength get-strength pump resolve-subroutine! resolve-unbroken-subs! substitute-x-credit-costs]]
[game.core.initializing :refer [card-init]]
[game.core.moving :refer [move trash]]
[game.core.payment :refer [build-spend-msg can-pay? merge-costs build-cost-string ->c]]
Expand Down Expand Up @@ -465,6 +465,7 @@
(sort-by #(-> % first :auto-break-sort))
(apply min-key #(let [costs (second %)]
(reduce (fnil + 0 0) 0 (mapv :cost/amount costs)))))
once-key (:once break-ability)
subs-broken-at-once (when break-ability
(:break break-ability 1))
unbroken-subs (when (:subroutines current-ice)
Expand All @@ -475,6 +476,7 @@
(if (pos? subs-broken-at-once)
(int (Math/ceil (/ unbroken-subs subs-broken-at-once)))
1))
break-cost (substitute-x-credit-costs break-cost unbroken-subs (:auto-break-creds-per-sub break-ability))
total-break-cost (when (and break-cost
times-break)
(repeat times-break break-cost))
Expand Down Expand Up @@ -504,6 +506,7 @@
"all ")
unbroken-subs " subroutines on "
(:title current-ice))))
(when once-key (register-once state side {:once once-key} card))
(continue state side nil))))))))

(def dynamic-abilities
Expand Down
25 changes: 24 additions & 1 deletion src/clj/game/core/costs.clj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
[game.core.effects :refer [any-effects is-disabled-reg?]]
[game.core.flags :refer [is-scored?]]
[game.core.gaining :refer [deduct lose]]
[game.core.moving :refer [discard-from-hand forfeit mill move trash trash-cards]]
[game.core.moving :refer [discard-from-hand flip-facedown forfeit mill move trash trash-cards]]
[game.core.payment :refer [handler label payable? value stealth-value]]
[game.core.pick-counters :refer [pick-credit-providing-cards pick-credit-reducers pick-virus-counters-to-spend]]
[game.core.revealing :refer [reveal]]
Expand Down Expand Up @@ -941,6 +941,29 @@
:paid/targets cards})))}
nil nil)))

;; TurnHostedMatryoshkaFacedown
(defmethod value :turn-hosted-matryoshka-facedown [cost] (:cost/amount cost))
(defmethod label :turn-hosted-matryoshka-facedown [cost]
(str "turn " (quantify (value cost) "hosted cop" "y" "ies") " of Matryoshka facedown"))
(defmethod payable? :turn-hosted-matryoshka-facedown
[cost state side eid card]
(<= (value cost)
(count (filter #(and (not (facedown? %)) (= (:title %) "Matryoshka"))
(:hosted (get-card state card))))))
(defmethod handler :turn-hosted-matryoshka-facedown
[cost state side eid card]
(let [pred #(and (not (facedown? %)) (= (:title %) "Matryoshka"))
selected (take (value cost) (filter pred (:hosted (get-card state card))))]
(doseq [c selected]
(flip-facedown state side c))
(complete-with-result
state side eid
{:paid/msg (str "turns "(quantify (value cost) "hosted cop" "y" "ies")
" of Matryoshka facedown")
:paid/type :turn-hosted-matryoshka-facedown
:paid/value (value cost)
:paid/targets selected})))

;; AddRandomToBottom
(defmethod value :add-random-from-hand-to-bottom-of-deck [cost] (:cost/amount cost))
(defmethod label :add-random-from-hand-to-bottom-of-deck [cost]
Expand Down
10 changes: 10 additions & 0 deletions src/clj/game/core/ice.clj
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,15 @@
card)
duration))})))

(defn substitute-x-credit-costs
"Substitute out the 'x-credits' part of a cost when the credit count is known"
[cost x scale]
(let [pick-x (fn [c] (= (:cost/type c) :x-credits))
output-cost (when (and x scale) [(->c :credit (* x scale))])
adjusted (remove pick-x cost)]
(if (= (count adjusted) (count cost))
cost
(concat adjusted output-cost))))

(def breaker-auto-pump
"Updates an icebreaker's abilities with a pseudo-ability to trigger the
Expand Down Expand Up @@ -760,6 +769,7 @@
(if (pos? subs-broken-at-once)
(int (Math/ceil (/ unbroken-subs subs-broken-at-once)))
1))
break-cost (substitute-x-credit-costs break-cost unbroken-subs (:auto-break-creds-per-sub break-ability))
total-break-cost (when (and break-cost
times-break)
(repeat times-break break-cost))
Expand Down
34 changes: 34 additions & 0 deletions test/clj/game/cards/programs_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -8862,6 +8862,40 @@
(is (= (dec credits) (:credit (get-runner)))))
(is (= 3 (:credit (get-runner))) "Able to use ability now"))))

(deftest utae-auto-pump
(do-game
(new-game {:corp {:hand ["Enigma" "Hortum"] :credits 50}
:runner {:hand ["Utae" "Paladin Poemu" "Cookbook" "Ice Carver"] :credits 50}})
(play-from-hand state :corp "Enigma" "HQ")
(play-from-hand state :corp "Hortum" "HQ")
(take-credits state :corp)
(doseq [s (map :title (:hand (get-runner)))]
(play-from-hand state :runner s))
(take-credits state :runner)
(take-credits state :corp)
(rez state :corp (get-ice state :hq 0))
(rez state :corp (get-ice state :hq 1))
(run-on state :hq)
(let [utae (get-program state 0)]
(run-continue-until state :encounter-ice)
(is (changed? [(:credit (get-runner)) -4]
(auto-pump-and-break state (refresh utae)))
(str "spent 5 to break Hortum"))
(run-continue-until state :encounter-ice)
(is (changed? [(:credit (get-runner)) -2]
(auto-pump-and-break state (refresh utae)))
(str "spent 3 to break Enigma"))
(run-continue state)
(run-jack-out state)
(trash state :runner (get-resource state 0))
(run-on state :hq)
(run-continue-until state :encounter-ice)
(is (changed? [(:credit (get-runner)) -4]
(auto-pump-and-break state (refresh utae)))
(str "spent 5 to break Hortum"))
(run-continue-until state :encounter-ice)
(is (empty? (filter #(:dynamic %) (:abilities (refresh utae)))) "No option to break because of utae restriction"))))

(deftest vamadeva-swap-ability
;; Swap ability
(testing "Doesnt work if no Deva in hand"
Expand Down
6 changes: 5 additions & 1 deletion test/clj/game/core/ice_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -177,17 +177,21 @@
(testing "Auto pump available even with no active break ability"
(do-game
(new-game {:corp {:deck [(qty "Hedge Fund" 5)]
:hand ["DNA Tracker"]
:hand ["DNA Tracker" "Enigma"]
:credits 20}
:runner {:deck ["Utae"]
:credits 20}})
(play-from-hand state :corp "DNA Tracker" "HQ")
(play-from-hand state :corp "Enigma" "HQ")
(rez state :corp (get-ice state :hq 0))
(rez state :corp (get-ice state :hq 1))
(take-credits state :corp)
(play-from-hand state :runner "Utae")
(let [utae (get-program state 0)]
(run-on state :hq)
(run-continue state)
(auto-pump-and-break state (refresh utae))
(run-continue-until state :encounter-ice)
(is (not-empty (filter #(= :auto-pump (:dynamic %)) (:abilities (refresh utae)))) "Auto pump is active")
(is (empty? (filter #(= :auto-pump-and-break (:dynamic %)) (:abilities (refresh utae)))) "No auto break dynamic ability")))))

Expand Down

0 comments on commit d2adf16

Please sign in to comment.