govariantsteam / govariants

A place to play Go variants
https://www.govariants.com
GNU Affero General Public License v3.0
5 stars 1 forks source link

Variant Abstraction #31

Closed JonKo314 closed 8 months ago

JonKo314 commented 2 years ago

As demonstrated in commit 01c399f982b3ac09000481708fc0b6f4d1b3dbfc variants can share some parts of code. Abstractions like this reduce code redundancy and should make it easier to add similar variants.

The commit introduced an examplary class AbstractAlternatingBlackWhiteOnGridVariant, which was used as superclass for the Baduk and TicTacToe variants. Other variants call for more considerations on how to achieve a better form of abstraction:

A couple of high level design thoughts:

  • Is this method of creating an intermediate subclass composable?
    • Example: HexGo might be described as an AlternatingBlackWhite variant, and Go Battle Royale might be described as a Grid variant. If we had two subclasses AlternatingBlackWhiteVariant and AbstractGridVariant, is it possible to compose the two into AbstractAlternatingBlackWhiteGridVariant?
  • As far as minimizing redudancy between Go and TicTacToe, it's definitely a useful exercise, but I don't think we want to go too deep into TicTacToe specifically as it was just a demonstration for how a game can be added.
  • Looking at the list of games we wanted to focus on in the forum thread, only Pyramid Go fits the AbstractAlternatingBlackWhiteGridVariant

Originally posted by @benjaminpjones in https://github.com/benjaminpjones/govariants/issues/27#issuecomment-1188069005

Also worth mentioning are thoughts on abstraction expressed in the OGS forum.

JonKo314 commented 2 years ago

HexGo might be described as an AlternatingBlackWhite variant, and Go Battle Royale might be described as a Grid variant. If we had two subclasses AlternatingBlackWhiteVariant and AbstractGridVariant, is it possible to compose the two into AbstractAlternatingBlackWhiteGridVariant?

Maybe we could achieve that with mix-ins? Or additional generic types? Or a library for utilities?

benjaminpjones commented 2 years ago

I think a library is going to be simplest, and seems like something we're going to need to do for some things anyway (I was already thinking map2d(), copy2d(), fill2d into a grid_utils.ts)

But I'd definitely like to see how mix-ins might work. Without knowing much about them, it sounds like it's possible to have something like this?

class TicTacToe extends GridMixin(AlternatingMixin(AbstractVariant)) {
    ...
}

class BattleRoyale extends GridMixin(PlayersCycleMixin(AbstractVariant)) {
    ...
}
JonKo314 commented 2 years ago

Yeah, I've never used them either, but that seems to be the idea.

I've thought about AbstractGame.playMove() again and I think it's a good idea to internally split up its behaviour. To the outside (public perspective) it says to use this method when someone wants to play/submit certain moves, on the inside (protected perspective) it establishes a structure how to handle this by default[^methodNames]:

This should work for most variants where players take turns in series, but for parallel variants (players make moves simultaneously) prepareForNextMoves() should probably be called outside playMove(). Technically it would work to indroduce a special (automated) player who submits a move to end a round, but it would be more intuitive to just change/override playMoves().

Can you think of any good changes to this structure?

[^methodNames]: I'm not sure about the current method names, maybe we can think of something better.

merowin commented 2 years ago

I believe it is possible to split the parts of a variant into pieces in such a way, that these pieces can be implemented differently but compatibly with each other. One way of doing so is by logically splitting the game state into pieces that are largely independent of each other, and then split the transition function in the same way, i.e. have a transition function for each individual piece. Say for example that for a 2-player variant, the game state consists of a grid board and one number that is either one or two, indicating who is on the play. We write two functions, both take possible moves along with the current game state as input. One maps to the new grid board position, one maps the other part, which defines who is next on the play. Of course it won't always be easy / possible to logically separate the rules in such a way.

As far as programming is concerned, interfaces might be an option to define a "blueprint" for an object that defines specific parts of the variant. Class inheritance or mix-ins might also be useful, I must admit that I don't have much experience with those.

JonKo314 commented 2 years ago

Good point, that should work at least in some cases. If it's hard for specific variants, we can always handle them separately by just extending AbstractGame.

I've revisited mix-ins and I don't anymore think that they are what we want to combine parts of variants like mentioned above. To me it seems they are used to add properties to a class, but what we want is to change the bevhaviour. Maybe generics are better suited for our purposes.

JonKo314 commented 2 years ago

Some insights in what functionalities certain variants would need:

benjaminpjones commented 8 months ago

Is there anything actionable in this task at this time? Or can we close it?

My thoughts are - it's definitely good to dedupe functionality where possible, but it's not a once-and-done kind of thing

JonKo314 commented 8 months ago

Is there anything actionable in this task at this time?

I don't think so.

it's not a once-and-done kind of thing

Good point!