I'm having a very difficult time determining how to translate a game state for a specific turn in a game I'm developing into a limited sequence of moves that represents the moves taken for that turn. I'd appreciate advice on how I can do this.
NOTE: the game is now live! And if anyone wants to try it, it's at: https://mystikaze.com
(I ended up implementing an algorithm for storing game moves that may not be fully efficient but I think does guarantee that the move list for any given turn cannot be unbounded/infinitely long).
The rules for the game are relatively simple. There's a hex board, with hexes belonging to 2 players. On any given turn, pieces can already exist on the board, having been purchased on a previous turn, or they can be purchased onto the board (a yellow piece represents its being purchased onto the board this turn).
These pieces are "active", and can still be moved. Pieces can also be combined, and will still remain "active". They can be combined either by moving an existing piece onto another piece, or by purchasing a new piece onto an existing piece. When combined, an upgraded piece will exist on the target hex. Pieces can be of 3 strengths; X, Y, and Z. X combining with X gives Y, and X with Y gives Z.
Pieces can continue to be merged like this and remain "active". A piece can be moved to another hex in its own territory and remain "active". A piece stops being "active" when it is moved to capture the other player's hex. It cannot move after that, although it can still be combined with. Green below indicates an inactive piece.
A piece can also be summoned directly on top of another piece, resulting in an upgraded piece (if it was already active, it stays active; if it was inactive, it stays inactive):
Now, this is pretty easy to represent in game state; just update the state of the pieces and the board to reflect whatever's currently true. And it's quite easy to convert it into a sequence of moves as long as you theoretically allow for that sequence of moves to be unbounded; pieces could remain active and move to and fro ad infinitum. Of course, I want to keep the sequence of moves limited. This is where I'm having trouble. I have the following 2 moves:
- Move piece to location
- Summon piece to location
How can I convert the moves a player makes into a limited sequence of moves to represent what the player actually did, leading to the final state? I don't know if I'm missing something, but this seems to get almost impossibly complex to figure out. If you have pieces moving around within their own territory and remaining active, you might think you could just update the move in-place to the new coordinates instead of adding a new move to the new coordinates, but what if there is another move where a piece combines with that piece to form an upgraded piece, which relied upon the first piece moving to its first set of coordinates? Updating the move coordinates in-place now means that that second combination move becomes a regular move because it is now moving onto an empty hex, yet it should remain a combination move (the board state will in fact be the combined piece having moved to the new coordinates).
Conceptually, there should always be a limited sequence of moves that can represent any operation. However I am finding it extremely hard to figure out how to write an algorithm to do this automatically. I think an algorithm that would at least prevent the unbounded nature of the moves would be to say "a piece's most recent move is updated instead of adding the new move to the list if that most recent move is not a combine or capture operation". That should always result in the game state being correctly created by the move set, and prevent unlimited cycles. However that could still result in quite a lot of moves. For instance if you had 10 pieces in a territory, you could move all 1, capture with 1, move the remaining 9, combine one with another, move the remaining 8, etc. potentially resulting in over 60 moves from 10 pieces. It would be nice if there were an algorithm to get this down a bit, and I'm still not 100% sure that even this algorithm doesn't have some edge cases where it wouldn't work.
Am I missing a relatively straightforward way to solve this problem? The rules must stay the same but I'm open to suggestions about perhaps introducing new move types if that would help solve the problem, too.