Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.Sign up
Rewriting core game loop to be tick based #329
Currently, every call to modify the game will immediately finish. This clashes with the way choices work in core Hearthstone.
When a choice card is played, the game loop is interrupted until the choice input resolves. This means that, for example, when playing a Discover card with a Knife Juggler on the board, the knife will trigger after the Battlecry completes, which includes the resolution of the choice. In fireplace, the battlecry will finish without waiting for the choice - but fireplace will prevent the user from performing further actions on the game.
A more powerful implementation of the Choice mechanic is blocked by this. Eye of Orsis (
A tick-based loop would also potentially simplify the Kettle server, defining clearly when to check for updates.
Finally, this has performance implications and is potentially more easily implemented in a copy-on-write design, so CC @smallnamespace
(note: my current understanding of HS gameloop/ticks is still blurry, so excuse any mistakes)
this looks like major surgery on the engine, being able to pause any "script", only to be continued later? you might get away with doing that for cards defined with your declarative DSL, but for general python methods? and rewriting every test to be async? good luck with all that.. ;)
i'm far from doing this, but my current plan is to use continuations (promises/generators/tasks). that way, i could easily just
alas, my understanding of high-level python is limited, so i'm unsure how easy this would be using python generators (or possible at all).
Are there other advantages to moving fully to a tick-based loop, besides being nice for kettle?
It seems to me that narrowly redefining the semantics of playing a choice card (preventing resolution and moving the game engine to an AWAITING_CHOICE state, where further updates are enqueued) would suffice for this.
OTOH, I am very partial to a copy-on-write design since it makes cloning and in particular tree search much nicer, but doing this nicely would require some major surgery.
For example, one can imagine defining game states as a base state + a stack of deltas, so that keeping history of game state is cheap in memory, but to really get mileage out of this you'd need to re-architect every object in the game. You would also need to have some sane rules for when to collapse or cache deltas, and that tradeoff really depends on the particular use case.
In short, my concern is that changing to a tick-based engine might be more up-front work for less immediate benefit, but would like to hear your thoughts.
I have some reservations specifically on lazy evaluators, which is an issue Blizzard hit when they came up against eg. Reno Jackson which had to reevaluate
Right now in fireplace,
There's some other bits and pieces like that, eg. secret exhaustion and so on. Initially, auras worked like that as well.. turns out that doesn't work too well because Hearthstone cares about the results of aura deltas and when they happen (cf. rivendare out of sneeds and similar edge case scenarios).
I also think pausing execution with
Regarding COW deltas, my main concern is when a lot of aura updates happen at once and the game runs long. You can see this in HS' game logs, they can easily reach megabytes for single games (of very verbose text, but still).
Been thinking about this for a few hours.
I think for the time being, I can add a
The problem is, action calls would still continue right now, so I'd have to modify all the current low level logic to check for the waiting state. How do we then resume that?
For mulligan, it's easy: we trigger things at the end of the MulliganChoice. But for Discover battlecries it's a lot harder because the Battlecry has to be paused. It's either that, or saving the python stack and resuming it later... nasty. Yeah I definitely think for Discover we have to switch to ticks.
I'll see if I can implement a fix for Mulligan since it's a lot more problematic.
added a commit
Apr 18, 2016
Well, couldn't you check the current state, and just no-op out if we're awaiting a choice? Or would that be the massive rework that you're alluding to?
It might not be that painful -- you could have a check function as a decorator and just decorate the entry points.
So for a tick-based system, where do you envision the tick boundaries would lie?
I could imagine one extreme, where each complete tick is initiated by a player action and runs until all queues are emptied. That would be pretty close to what we have now, except we don't call them 'ticks'.
The other extreme might be yielding at the completion of every action or phase.