MartianZoo / solarnet

Solarnet, an open-source game engine for the board game Terraforming Mars. Pets, a specification language for game component behaviors in said game.
Apache License 2.0
15 stars 2 forks source link

How to handle transitioning Phases #32

Closed kevinb9n closed 1 year ago

kevinb9n commented 1 year ago

Once a Phase is created we want there to always be exactly one. We start with SetupPhase which changes to CorporationPhase, PreludePhase, etc.

Ideally, phase changes would always be handled as FooPhase FROM BarPhase, which works great, but we never want to have to actually write them that way (because then inserting something like VenusSolarPhase is too disruptive).

Assume that tasks carried out by ENGINE (not a player) will self-reify as long as there is only one valid way to do so. If necessary assume they will also self-reorder (i.e., move currently-impossible ones to the back of the queue and try again later).

Within ABSTRACT CLASS Phase { ... } there are various ways we could try to express this.

This:: -Phase.

The first phase created would remove itself.

Phase:: -This!

This would also trigger on itself and remove itself.

This:: -(Phase - This).

Requires not just a complement type, but a difference type. Some gnarly type theory. Also requires that invariants (HAS MAX 1 Phase) aren't checked until after all :: triggers finish, and I'm not sure yet whether we can get away with that.

PRE This:: -Phase.

Creates the notion of a "before" trigger. Also temporarily breaks a conceptual invariant, buuuut we can't actually enforce a min of 1 Phase anyway (bootstrap problem).

what about FROM

So the cleanest possible thing to happen in the game event log is for one phase to just transition directly to another, e.g. ActionPhase FROM ResearchPhase. (Again, as a manual instruction that does already work; we just don't want the initiator of a new phase to have to type that.)

To get that we'd need to create an entirely new kind of triggered effect! Again, this is inside ABSTRACT CLASS Phase { ... }:

This:: FROM Phase

This says that when a phase like ActionPhase is created, the engine should sorta "go back in time" and rewrite that creation as a FROM/transmutation instead. Of course, for the very first phase created (SetupPhase) it would have to see that there is no other phase and ignore this. (We could put the amap quantifier . in there somewhere, but that seems more like the whole operation might get skipped.)

Or this should be better?:

Phase:: FROM This.

When creating SetupPhase, it would start to trigger its own effect, but recognize that x FROM x is impossible and (again) ignore it. Then creating CorporationPhase would trigger nothing on itself, but would trigger this effect on SetupPhase. Then, again, the creation has to be rewritten as CorporationPhase FROM SetupPhase.

Of course, rather than "going back in time", we might just check for these special triggers before creating anything. I'm actually not positive which way is more gross.

This:: This FROM Phase

The advantage of this is that it parses normally according to existing rules. But the result is something that looks like you're ending up with one more instance than you started with.

I have a feeling there are several more ideas to lay out as well...

Wait, easy workaround?

Maybe we do just have to always write ActionPhase FROM Phase, and let that be auto-reified.

That seemed bad at first, but now that I see how messy the alternatives are above, I like it better.

Another issue can address auto-reification. This one is about doing one of the things above. And I'll mark it postponed.

kevinb9n commented 1 year ago

Wow, the workaround works just fine.