From 30c356d39ec5dc856e6bac1e225f190eea5f3fb6 Mon Sep 17 00:00:00 2001 From: Andrew Chen Date: Mon, 14 Oct 2024 01:59:17 -0400 Subject: [PATCH] Big updates in the design coming. Sketches are prototyped in `DesignSketch.hs`. --- battlegrounds.cabal | 3 +- {src => docs}/Model.md | 4 +- {src => docs}/Thoughts.md | 30 +++++++-- src/DesignSketch.hs | 127 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+), 8 deletions(-) rename {src => docs}/Model.md (96%) rename {src => docs}/Thoughts.md (78%) create mode 100644 src/DesignSketch.hs diff --git a/battlegrounds.cabal b/battlegrounds.cabal index 2dbde9c..d1b6615 100644 --- a/battlegrounds.cabal +++ b/battlegrounds.cabal @@ -1,6 +1,6 @@ cabal-version: 2.2 --- This file has been generated from package.yaml by hpack version 0.36.0. +-- This file has been generated from package.yaml by hpack version 0.37.0. -- -- see: https://github.com/sol/hpack @@ -28,6 +28,7 @@ library Card Combat Controller + DesignSketch Logic Model Utils diff --git a/src/Model.md b/docs/Model.md similarity index 96% rename from src/Model.md rename to docs/Model.md index 93382ab..20cac50 100644 --- a/src/Model.md +++ b/docs/Model.md @@ -16,10 +16,12 @@ updateCard :: Card -> Card upgradeScope :: (Card -> Card) -> (GameState -> GameState) ``` +The `zoom` lens allows us to write this efficiently. + ### Guiding Questions #### Modelling `Malchezaar`, `Seafarer`, `Bazaar Dealer`, `Chimera`, `Octosari`, `Felboar`, `Elise`? -The named minions are have special effects that also have the notion of "counters" to them. +These minions are have special effects that also have the notion of "counters" to them. First, I write some algebraic laws: ```haskell diff --git a/src/Thoughts.md b/docs/Thoughts.md similarity index 78% rename from src/Thoughts.md rename to docs/Thoughts.md index cdf7bbf..e42b37a 100644 --- a/src/Thoughts.md +++ b/docs/Thoughts.md @@ -12,17 +12,17 @@ to issue commands to the server, etc. Note, server and client will be bidirectionally. Server needs to manage phase timers and ping the client when phase changes. So, the client needs to `forkIO` twice for a sending and receiving thread. The server can have one `fork` per client. -### Aug 1, 2024 +### Aug 1, 2024: Encapsulate custom effects in callbacks Callbacks can be used to implement both in-game logic and game rules. For example, omu's hero power can be implemented via a callback to `tierUp`. Conversely, maybe `tierUp` can expand the `randomShopSize` via a callback, as opposed to this being built-in. For now, `randomShop` is directly dependent on `TavernTier`, but maybe that can change. -### Aug 4, 2024 +### Aug 4, 2024: `simulateCombat` logic Taming `simulateCombat`. A few additions to make - Allow each call of `go` add multiple snapshots to the combat history. Each `go` handles all that transpires from one player's turn, which involves - capture initial state - on minion attack (capture state) - - minions trade (capture state, dead minions are snapped) + - minions trade (capture state, dead minions are snapped because this should show in the UI) - clear dead minions (capture state) - trigger deathrattles (capture state) @@ -38,7 +38,7 @@ Today's goal: I think since both Onyxia's whelp and Scallywag have the "attack immediately" keyword, it warrants a `AttackImmediately` keyword. When a minion with `AttackImmediately` is summoned, it is to perform an attack. -### Aug 5, 2024 +### Aug 5, 2024: How to write tests when randomness is invovled. How can I write tests? They need to fulfill two requirements: @@ -46,7 +46,7 @@ They need to fulfill two requirements: - If unspecified, choices fall back to random sourcing - Randomness are specified exactly where they are needed. -### Aug 6, 2024 +### Aug 6, 2024: Initial steps to handling randomness To answer 8/5 questions: Write a full DSL and interpreter for all of the card effects. During testing, override terms like `RandomIndex` to `SpecificIndex`. @@ -54,4 +54,22 @@ Refactoring: The API calls for many redesign opprotunities: 1. Since `FighterState` provides `PlayerState`, no need for `combatBoard`. 2. Need to seriously think about the pros and cons of the different CombatState designs. -Today's result: Combat.hs typechecks. Claude suggests to refactor `CombatState` into a map of `{ One: one's fighterstate, Two: two's fighterstate }` \ No newline at end of file +Today's result: Combat.hs typechecks. Claude suggests to refactor `CombatState` into a map of `{ One: one's fighterstate, Two: two's fighterstate }` + +### Oct 13, 2024: +Been reading Granin's FDD book. Main question I'm having: +1. What types can be "correct by construction"? + + +I wasn't able to get far from this question. But, I started a `Temp.hs` file in which I started mocking things in. This has proven to be such a great exercise — it's great seeing just how compositional some effects are. + + +**** + +Questions from 8/5/2024 still persist: +- How to determine everything random in tests? +- If unspecified, choices fall back to random sourcing + +Thought: + +Another question: diff --git a/src/DesignSketch.hs b/src/DesignSketch.hs new file mode 100644 index 0000000..027632f --- /dev/null +++ b/src/DesignSketch.hs @@ -0,0 +1,127 @@ +-- Temp file. Place for some all-in-one mocks + +module TempDesignSketch (module TempDesignSketch) where + +-- Sources of randomness: +-- 1. Random defender selection +-- 2. Random shop rolls +-- 3. Choose random minion in tavern (picky eater, enchanted lasso) +-- 4. Choose random minion in opponent's warband (cry foul) +-- 4. Choose random card under some additional criterion (chef's choice, shell whistler) + +type Multiplier = Int + +data RandomTarget + = HandRandom + | HandLeftmost + | HandRightmost + | BoardRandom + | ShopRandom + deriving (Eq) + +data RandomCriterion = RTribe Tribe | RTarget RandomTarget | RTier Int deriving (Eq) + +data CounterCriterion + = -- Upbeat Frontdrake + EndOfTurn + | -- Avenge mechanic + FriendlyDeaths + deriving (Eq) + +-- Unused yet. +data Target + = TShop Int + | THand Int + -- Them Apples. + | TEntireShop + +data TargetedEffect -- TODO: The idea of a target might be important. + +data StateEffect + = -- E.g., Glim Guardian + GainStats Int Int + | -- E.g., Cord Puller + Summon CardName + | -- E.g., Backstage Security + DamageHero Int + | -- E.g., Upbeat Frontdrake + GetRandom [RandomCriterion] + | -- E.g., Geomancer + Get CardName + | -- E.g., Picky Eater + Consume Multiplier + | -- E.g., Tavern Coin + GainGold Int + deriving (Eq) + +data Tribe = Murloc | Dragon | Demon deriving (Eq) + +-- TODO: It has became obvious what are "keywords" and what are "functionalities". Just observe who has StateEffect. +data Functionality + = -- phase=Combat + Taunt + | DivineShield + | Deathrattle [StateEffect] + | OnAttack [StateEffect] + | OnDamaged [StateEffect] + | OnKill [StateEffect] + | OnSummon [StateEffect] + | OnSell [StateEffect] + | StartOfCombat + | Reborn + | Windfury + | -- phase=Recruit + Battlecry StateEffect + | AfterPlay [StateEffect] + | Spellcraft Card + | -- Both + AfterSummon Tribe StateEffect + | -- Combinator + Counter CounterCriterion Int StateEffect + deriving (Eq) + +data Stats = Stats Int Int deriving (Eq) + +data CardName = GlimGuardian | HarmlessBonehead | CordPuller | Skeleton | Microbot | UpbeatFrontdrake | EnchantedLasso deriving (Eq) + +data Card = Card CardName Stats [Functionality] deriving (Eq) + +newtype CardInstance = CardInstance Card + +glimGuardian :: Card +glimGuardian = Card GlimGuardian (Stats 1 4) [OnAttack [GainStats 2 1]] + +skeleton :: Card +skeleton = Card Skeleton (Stats 1 1) [] + +harmlessBonehead :: Card +harmlessBonehead = Card HarmlessBonehead (Stats 1 1) [Deathrattle [Summon Skeleton, Summon Skeleton]] + +microbot :: Card +microbot = Card Microbot (Stats 1 1) [] + +cordPuller :: Card +cordPuller = Card CordPuller (Stats 1 1) [DivineShield, Deathrattle [Summon Microbot]] + +upbeatFrontdrake :: Card +upbeatFrontdrake = Card UpbeatFrontdrake (Stats 1 1) [Counter EndOfTurn 3 (GetRandom [RTribe Dragon])] + +-- Look mom! Tavern spells can be modeled as cards. +enchantedLasso :: Card +enchantedLasso = Card EnchantedLasso (Stats 0 0) [Battlecry (GetRandom [RTarget ShopRandom])] + +glimGuardianInst :: CardInstance +glimGuardianInst = CardInstance glimGuardian + +data GameState + = GameState + { p1 :: [Card], + p2 :: [Card], + onSummonCallbacks :: [GameState -> GameState], + onPlayCallbacks :: [GameState -> GameState] + } + +trade :: (CardInstance, CardInstance) -> (CardInstance, CardInstance) +trade (CardInstance (Card name1 stats1 fns1), CardInstance (Card name2 stats2 fns2)) = (_, _) + where + ds1 = DivineShield `elem` fns1 \ No newline at end of file