This is a tricky question, and I'm sure there's no definitive solution.
Lichess uses this definition (https://lichess.org/qa/102/how-are-the-mid-game-and-end-game-dividers-determined)
Opening is obvious, starts on move 1.
Mid-game is less obvious, and is still in testing. The long-and-short of the current heuristics are that the mid-game occurs when either:
- The position is adequately complex and enough pieces are developed.
- 2 sets of minor or major pieces have been taken off the board. (This is mainly useful for when the position is quite open)
End-game is slightly more straight forward, when there are less than 7 minor and major pieces on the board the end-game has begun (so, perhaps 2 rooks and a bishop Vs 2 bishops and a knight). This is not technically where everyone would draw the line, but it's a good indicator that either 1. The end-game has begun 2. The end-game will begin soon.
Stockfish uses interpolation between the middlegame and endgame. This is done by counting non-pawn materials (https://github.com/official-stockfish/Stockfish/blob/03a9b3bd8d79e1677f1c74cf7d8f8036795675c6/src/material.cpp)
// Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME]
e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit));
You will need to make a decision on what material counts you want to use.
- If your engine supports opening book, you might want to use that
- You might use the move counts (it's in the FEN string) for opening vs middlgame
- Endgame is generally defined as number of X pieces and N pawns on the board (you decide what X and N should be). N<=3 is almost always an endgame.
- Lichess's definitions have worked quite well practically.