Changelog - HearthSim/SabberStone GitHub Wiki

Changelog

Random Reworks 2019-07-29

Add Util.DeepCloneableRandom

  • Add Game.Random
    • Now each game has an independent random number generator.
    • Now user can provide a random seed for each game using GameConfig
    • The seed of Game.Random can be reset using Random.SetSeed(seed) method
  • Remove Util.Random
    • Now all Util's helper functions using Random requires a random number generator as its parameter.
  • Add Game.Clone(bool resetRandomSeed) parameter The last cloneable random commit accidentally make all cloned games have its prototype's Random, which is different with the default behaviour of Clone(). Instead, if you want to clone a game with the same random number generator(up to the moment), use Game.Clone(resetRandomSeed = false)

Miscellaneous

  • Add Cards.BasicTotems
  • Add an Exception debugger for tasks (WIP)

[2.1.0] 2019-01-16

SabberStoneCore 2.1 Release Note

Performance Improvement

Test environment:

  • Processor: AMD Ryzen 5 2600
  • Memory: DDR4 PC4-21300 16GB
  • OS: Windows 10
  • Framework: .NET Core 2.1
  • Compiler Switch Settings: /optimize+ /debug:pdbonly
  • GameConfig: History: false, Logging: false
  • Run 20000 games with Innkeeper Expert decks. Action are randomly chosen from all available actions. (10000 games for cloning.)
  • All measurements are recorded in seconds. (seconds per 20000 games (10000 per cloning))
  • The test process used only 1 thread.
2.0 Druid Hunter Mage Paladin Priest Rogue Shaman Warlock Warrior Average
Controller.Options() 11.99 6.57 10.85 7.84 16.15 8.55 10.78 6.93 9.5 9.91
Game.Process() 7.87 6.45 8.68 7.45 9.3 5.93 8.61 6.28 8.44 7.67
Game.Clone() 55.92 16.73 22.47 21.72 22.24 16.33 24.41 14.25 22.18 24.03
2.1
Controller.Options() 2.98 1.69 2.65 2.08 3.37 1.96 2.19 1.49 2.2 2.29
Game.Process() 3.48 3.04 3.93 3.38 3.77 2.59 3.35 2.78 3.67 3.33
Game.Clone() 39.6 12.64 16.83 16.12 16.45 11.95 15.82 10.42 15.7 17.28
Rate (%) (2.0 / 2.1) * 100
Controller.Options() 402.35 388.76 409.43 376.92 479.23 436.22 492.24 465.10 431.82 432.75
Game.Process() 226.15 212.17 220.87 220.41 246.68 228.96 257.01 225.90 229.98 230.33
Game.Clone() 141.21 132.36 133.51 134.74 135.20 136.65 154.30 136.76 141.27 139.06

Summary

  • 4.3x faster options generation.
  • 2.3x faster processing.
  • 1.4x faster cloning.

Card Implementations

  • 95% of Boomsday cards are implemented.

  • 90% of The Grant Tornament cards are implemented.

  • List of not yet implemented cards:

    The Boomsday Project

    • [BOT_299] Omega Assembly
    • [BOT_406] Supercollider
    • [BOT_436] Prismatic Lens
    • [BOT_453] Shooting Star
    • [BOT_912] Kangor's Endless Army
    • [BOT_914] Whizbang the Wonderful

    The Witchwood

    • [GIL_198] Azalina Soulthief
    • [GIL_655] Festeroot Hulk
    • [GIL_681] Nightmare Amalgam

    Rastakhan's Rumble

    • Most

    The Grand Tournament

    • [AT_003] Fallen Hero
    • [AT_004] Arcane Blast
    • [AT_005] Polymorph: Boar
    • [AT_027] Wilfred Fizzlebang
    • [AT_034] Poisoned Blade
    • [AT_056] Powershot
    • [AT_067] Magnataur Alpha
    • [AT_079] Mysterious Challenger
    • [AT_081] Eadric the Pure
    • [AT_086] Saboteur
    • [AT_088] Mogor's Champion
    • [AT_115] Fencing Coach
    • [AT_130] Sea Reaver

    Hall of Fame

    • [EX1_112] Gelbin Mekkatorque
    • [NEW1_016] Captain's Parrot
    • [PRO_001] Elite Tauren Chieftain

    Goblins vs Gnomes

    • [GVG_007] Flame Leviathan
    • [GVG_014] Vol'jin
    • [GVG_016] Fel Reaver
    • [GVG_017] Call Pet
    • [GVG_021] Mal'Ganis
    • [GVG_025] One-eyed Cheat
    • [GVG_046] King of Beasts
    • [GVG_049] Gahz'rilla
    • [GVG_050] Bouncing Blade
    • [GVG_054] Ogre Warmaul
    • [GVG_065] Ogre Brute
    • [GVG_066] Dunemaul Shaman
    • [GVG_074] Kezan Mystic
    • [GVG_077] Anima Golem
    • [GVG_087] Steamwheedle Sniper
    • [GVG_088] Ogre Ninja
    • [GVG_092] Gnomish Experimenter
    • [GVG_097] Lil' Exorcist
    • [GVG_107] Enhance-o Mechano
    • [GVG_108] Recombobulator
    • [GVG_111] Mimiron's Head
    • [GVG_112] Mogor the Ogre
    • [GVG_113] Foe Reaper 4000
    • [GVG_119] Blingtron 3000
    • [GVG_122] Wee Spellstopper

    The League of Explorers

    • [LOE_009] Obsidian Destroyer
    • [LOE_022] Fierce Monkey
    • [LOE_023] Dark Peddler
    • [LOE_029] Jeweled Scarab
    • [LOE_046] Huge Toad
    • [LOE_047] Tomb Spider
    • [LOE_051] Jungle Moonkin
    • [LOE_053] Djinni of Zephyrs
    • [LOE_061] Anubisath Sentinel
    • [LOE_073] Fossilized Devilsaur
    • [LOE_092] Arch-Thief Rafaam
    • [LOE_107] Eerie Statue
    • [LOE_118] Cursed Blade
    • [LOE_119] Animated Armor
    • [LOEA10_3] Murloc Tinyfin

Major Changes

Implemented 12.0 Game Mechanics Update

ReadOnlySpan<T> supports for Board, Hand and Deck zones.

You can get ReadOnlySpan<T> from these LimitedZone<T> with LimitedZone<T>.GetSpan(). The method is already used in some part of the simulator, but it should be exploited more.

Span<T> and Memory<T> is interesting new features from C# 7.2. There seems to be more fascinating application of these features in SabberStone. I have been designing new faster inner structure using these and that would be the one of the future performance improvement.

Rework on SimpleTasks

Previously each task needed to contain. instance-specific information. Which needed cloning every time when it is processed. To avoid the situation, now tasks do not contains any game-instance specific objects like IEntity Source. Instead now SimpleTask.Process() requires these objects as arguments.

With this change, it is even possible that all tasks can be a singleton instance. This scheme would be implemented in near future too and may reduce some memory overheads.

Rework on Aura and Ongoing Effects (IAura)

Slight performance and stability improvements.

Fields

Basically almost all attributes and properties of Entity is stored in EntityData as pairs of GameTag and int. This allows a lot of generality and readability within this simulator and makes us help how actual Hearthstone works. However, reading and writing these tags from dictionary tags from dictionary consume the most of times during the runtime.

To mitigate the problem, some of frequently used attributes like ATK and Health is now stored in fields internally.

Note

Please use properties for major attributes instead of direct tag queries for performance and stability. For example, it is still possible to get ATK attribute from Entity[GameTag.ATK] but Character.AttackDamage would be slightly faster and using tag indexers could be buggy in some rare situations.

Preprocessed Card Attributes

Previously, like how we get attributes from GameTags, all card attributes are stored as tags in Card.Tags Dictionary. To reduce runtime lookup for tags, these tags are stored in properties in a Card class. As Card instances are only created once and singletons, this can be regarded as preprocessing. Static memory usage is increased therefore.

Preprocessed Card Requirements

To know which entities in the board are valid targets for a playable, we need to see the card's PlayReqs underneath the playable. These are immutable and can be preprocessed and compressed to more compact data.

Options(bool skipPrePhase)

SabberStone checks the legality of each task when it is processed, but you can skip with PlayerTasks with SkipPrePhase property (This is not a new feature). Because options generated from Controller.Options() should be legal if you use the tasks for the same Game instance that creates the options (or in other controlled situations), so the double-checking would be redundant. Now you can generated all options with SkipPrePhase using the overload.

Minor Changes

Enchantment.CapturedCard

This new property is used for Carnivorous Cube's Enchantment and similar other cards and also for "Copying Deathrattle" cards. I think this can be useful when you want to extract state representation from SabberStone.

Game.StartGame()

Previously when you need a games with different starting hands and deck orders, you have to create new games each time. To mitigate this problem now you can call Game.Clone() before Game.StartGame() so that you can call clone.StartGame() to prepare new instances.

Future Updates

For users: we aware and understand well that lack of wikis and documentations discourages people from using and contributing to this project. We will prioritise documentation writing job.

There are still tons of things to be optimised or improved. Also new cards from new expansions would be updated soon!… (and not implemented old cards too).

To Contributors (and potential ones)

The internal structure of some parts are extremely dirty and complicated, without comprehensive documentations. I understand that this is a serious problem and I will try to write full documentations for hard-coded parts and to relax complications. However, basic APIs like Process, Clone, and Option and properties are almost the same as before and card scripting is quite easy and not really changed from 2.0..

All feedbacks and questions are very welcomed! Discord: https://discord.gg/my9WTwK (Simulators channel) Email: [email protected]

[2.0.1] 2018-02-22

State of Implementations

  • Basic => 100% from 142 Cards
  • Classic => 100% from 239 Cards
  • Whispers of the Old Gods => 100% from 134 Cards
  • One Night in Karazhan => 100% from 45 Cards
  • Mean Streets of Gadgetzan => 99% from 132 Cards
  • Journey to Un'Goro => 97% from 135 Cards
  • Knights of the Frozen Throne => 98% from 135 Cards
  • Kobolds and Catacombs => 91% from 135 Cards
  • Total Standard => 98% from 1097 Cards

[2.0.0] 2018-02-12

Summary

  • Now Sabber uses the latest version of .NET Core
  • Refactoring of some fields (You should fix some of your own codes)
  • Entirely new Trigger / Aura / Enchantment system, which uses events and the actual ENCHANTMENT entities
  • Mass implementation of missing cards
  • Far more accurate gameplay simulation with new task system including the recent gameplay changes
  • Insanely optimised performance
  • Changed card implementation syntax

1. Refatored names:

Old Name Refactored Name
SabberStoneCore.Enchants.Enchantment SabberStoneCore.Enchants.Power
SabberStoneCore.Enchants.Enchantments SabberStoneCore.Enchants.Powers
SabberStoneCore.Model.Entities.Hero.Power SabberStoneCore.Model.Entities.Hero.HeroPower
SabberStoneCore.Enchants.Enchantment.SingleTask SabberStoneCore.Enchants.Power.PowerTask

2. Enchantment / Trigger Reworks

2.1. Triggers

Previously we checked every tag changes to trigger effects. The new trigger design uses events. This way we can reduce the number of tags that used for checking triggers and have no other uses (e.g. JUST_PLAYED, SUMMONED, ...), and can simulate the actual situation more precisely. Secondly, the new trigger system helps us to implement new cards with trigger more conveniently because the new implementation syntax doesn't require us to provide an exact tag or value increment. Finally I hope that this new system would help us to implement precise History Blocks

2.2 Enchantments and Auras

I decided to revive the dead Enchantment cards, to simulate all things accurately. From now on, most of cards that make changes of tags value of itself or other entities actually gives "Enchantment" entity to the target entities. (e.g. Abusive Sergeant, Preparation, Mana Wyrm, Potion of Madness, Crazed Alchemist, and so on) So we should implement both the enchantment giver and the enchantment card itself. I recommend you to check one of the CardSet file to comprehend how things are changed; I think they are really readable :p

3. Performance improvement

Personally, besides the other things I've done, I am really proud of myself that... I optimised Sabber more than 2X faster with this commit. Not only the changed mechanism makes the engine faster, but also I've introduced custom hash table for tags, which allows super faster cloning. I benchmarked with random games between players using the same mage expert deck (https://www.hearthpwn.com/decks/66047-mage-expert-ai-deck). I separately measured two cases; to see the performance of cloning, I cloned each time PlayerTask is processed and measured the duration of Game.Clone(). Secondly, I processed random decisions and measured the duration of Game.Process() I Ran 20000 Games to completion in each round and averaged results from 10 rounds.

for 20,000 full games Sabber before this commit Sabber with this commit
Game.Process() 13.4127 s 6.7383 s (199% faster)
Game.Clone() 56.827 s 26.2228 s (217% faster)

4. State of Implementations

Basic => 100% from 142 Cards

Classic => 100% from 239 Cards

Whispers of the Old Gods => 99% from 134 Cards

One Night in Karazhan => 100% from 45 Cards

Mean Streets of Gadgetzan => 99% from 132 Cards

Journey to Un'Goro => 95% from 135 Cards

Knights of the Frozen Throne => 92% from 135 Cards

Kobolds and Catacombs => 25% from 135 Cards

Total Standard => 89% from 1097 Cards

5. Known Issues

5.1 Hash

I have yet to write hash functions for new structure so It is not possible to check the integrity of the clone for now, but I tested stability of cloning with 100,000 random games with random cards.

5.2 Working with Kiln (Stove)

I have yet to test with any kind of servers ... but I expect the calibration would be easy than before. There are lot of things to implement to simulate precise history such as CHANGE_ENTITY or REVEAL_ENTITY, and METADATA.

5.3 Wild card implementations

I have yet to convert cardsets in wild format. so wild cards cannot be used for now.

5.4 Slow Controller.Options()

I've been developing a faster Options() algorithm and I will commit it soon