feat: refactor Nakama plugin into generic multi-game match engine

### Highlights
- Introduced generic match engine (`generic_match.go`) implementing dynamic GameRules-based runtime.
- Added modular structure under `/plugins`:
  - /plugins/game      → GameRules interface + TicTacToe + Battleship rule sets
  - /plugins/structs   → Board, Player, MatchState generic structs
  - /plugins/modules   → matchmaking + RPC handlers + match engine
- Migrated TicTacToe logic into reusable rule implementation.
- Added Battleship game support using same engine.
- Updated matchmaking to accept { game, mode } for multi-game routing.
- Updated UI contract: clients must send `game` (and optional `mode`) when joining matchmaking.
- Removed hardcoded TicTacToe match registration.
- Registered a single “generic” authoritative match with ruleset registry.
- Normalized imports under local dev module path.
- Ensured MatchState and Board are now generic and reusable across games.
- Added strict requirement for `game` metadata in match flow (error if missing).
- Cleaned initial state creation into MatchInit with flexible board dimensions.
- Improved MatchLeave for proper forfeit handling through GameRules.

### Result
The server now supports an unlimited number of turn-based board games
via swappable rulesets while keeping a single authoritative Nakama match loop.
This commit is contained in:
2025-12-01 15:28:54 +05:30
parent 70669fc856
commit eeb0a8175f
12 changed files with 1038 additions and 508 deletions

27
plugins/common/game.go Normal file
View File

@@ -0,0 +1,27 @@
package game
import (
"context"
"encoding/json"
"github.com/heroiclabs/nakama-common/runtime"
)
type MovePayload struct {
Data map[string]interface{} `json:"data"` // arbitrary structure per game
}
// GameRules defines game-specific mechanics.
// You implement this for TicTacToe, Chess, etc.
type GameRules interface {
MaxPlayers() int
// ApplyMove modifies state, returns (stateChanged, gameOver, winnerIndex)
ApplyMove(state *MatchState, playerIdx int, payload MovePayload) (bool, bool, int)
// Called when match starts and players are set.
AssignPlayerSymbols(players []*Player)
// Called when match ends via forfeit.
ForfeitWinner(state *MatchState, leaverIndex int) int
}