Files
tic-tac-toe/plugins/main.go
Vishesh 'ironeagle' Bangotra eeb0a8175f 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.
2025-12-01 15:28:54 +05:30

92 lines
2.6 KiB
Go

package main
import (
"context"
"database/sql"
"github.com/heroiclabs/nakama-common/runtime"
// Adjust these imports to match your project structure
"localrepo/plugins/modules"
"localrepo/plugins/games"
)
func InitModule(
ctx context.Context,
logger runtime.Logger,
db *sql.DB,
nk runtime.NakamaModule,
initializer runtime.Initializer,
) error {
//--------------------------------------------------------
// 1. Register RPCs
//--------------------------------------------------------
if err := initializer.RegisterRpc("hello_world", HelloWorld); err != nil {
logger.Error("Failed to register RPC hello_world: %v", err)
return err
}
if err := initializer.RegisterRpc("leave_matchmaking", modules.RpcLeaveMatchmaking); err != nil {
logger.Error("Failed to register RPC leave_matchmaking: %v", err)
return err
}
//--------------------------------------------------------
// 2. Register Matchmaker Handler
//--------------------------------------------------------
if err := initializer.RegisterMatchmakerMatched(modules.MatchmakerMatched); err != nil {
logger.Error("Failed to register MatchmakerMatched: %v", err)
return err
}
//--------------------------------------------------------
// 3. Register MATCHES for ALL games
//--------------------------------------------------------
// Build registry: game name → GameRules implementation
registry := map[string]game.GameRules{
"tictactoe": &game.TicTacToeRules{},
"battleship": &game.BattleshipRules{},
}
// Register a Generic Match Handler that can run ANY game from registry
if err := initializer.RegisterMatch("generic", modules.NewGenericMatch(registry)); err != nil {
logger.Error("Failed to register generic match: %v", err)
return err
}
//--------------------------------------------------------
// 4. Register Leaderboards dynamically (optional)
//--------------------------------------------------------
leaderboards := []string{
"tictactoe_classic",
"tictactoe_ranked",
"battleship_classic",
"battleship_ranked",
}
for _, lb := range leaderboards {
err := nk.LeaderboardCreate(
ctx,
lb, // leaderboard ID
true, // authoritative
"desc", // sort order
"incr", // operator
"", // reset schedule (none)
map[string]interface{}{}, // metadata
)
if err != nil && err.Error() != "Leaderboard ID already exists" {
logger.Error("Failed to create leaderboard %s: %v", lb, err)
return err
}
logger.Info("Leaderboard ready: %s", lb)
}
logger.Info("Go module loaded successfully!")
return nil
}