feat(matchmaking): enable mode-based matchmaking and improve match initialization
- Added matchmaker configuration to local.yml with support for string property "mode" - Enabled faster matchmaking via interval_ms and large ticket limit - Broadcast initial match state when both players join - Added detailed validation and logging for move processing - Broadcast game-over and forfeit states immediately on player leave - Improved MatchLoop robustness with change tracking and clearer diagnostics
This commit is contained in:
@@ -7,3 +7,10 @@ session:
|
|||||||
socket:
|
socket:
|
||||||
max_message_size_bytes: 4096 # reserved buffer
|
max_message_size_bytes: 4096 # reserved buffer
|
||||||
max_request_size_bytes: 131072
|
max_request_size_bytes: 131072
|
||||||
|
matchmaker:
|
||||||
|
max_tickets: 10000
|
||||||
|
interval_ms: 100
|
||||||
|
query:
|
||||||
|
properties:
|
||||||
|
string:
|
||||||
|
mode: true
|
||||||
|
|||||||
@@ -106,6 +106,21 @@ func (m *TicTacToeMatch) MatchJoin(
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("MatchJoin: now %d players", len(s.Players))
|
logger.Info("MatchJoin: now %d players", len(s.Players))
|
||||||
|
|
||||||
|
// If we have enough players to start, broadcast initial state immediately
|
||||||
|
if len(s.Players) == 2 {
|
||||||
|
stateJSON, err := json.Marshal(s)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to marshal state on join: %v", err)
|
||||||
|
} else {
|
||||||
|
if err := dispatcher.BroadcastMessage(OpState, stateJSON, nil, nil, true); err != nil {
|
||||||
|
logger.Error("BroadcastMessage (initial state) failed: %v", err)
|
||||||
|
} else {
|
||||||
|
logger.Info("Broadcasted initial state to players")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,6 +143,18 @@ func (m *TicTacToeMatch) MatchLeave(
|
|||||||
s.GameOver = true
|
s.GameOver = true
|
||||||
s.Winner = "forfeit"
|
s.Winner = "forfeit"
|
||||||
logger.Info("MatchLeave: game ended by forfeit")
|
logger.Info("MatchLeave: game ended by forfeit")
|
||||||
|
|
||||||
|
// broadcast final state so clients see the forfeit
|
||||||
|
stateJSON, err := json.Marshal(s)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to marshal state on leave: %v", err)
|
||||||
|
} else {
|
||||||
|
if err := dispatcher.BroadcastMessage(OpState, stateJSON, nil, nil, true); err != nil {
|
||||||
|
logger.Error("BroadcastMessage (forfeit) failed: %v", err)
|
||||||
|
} else {
|
||||||
|
logger.Info("Broadcasted forfeit state to remaining players")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
@@ -151,8 +178,11 @@ func (m *TicTacToeMatch) MatchLoop(
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changed := false
|
||||||
|
|
||||||
for _, msg := range messages {
|
for _, msg := range messages {
|
||||||
if msg.GetOpCode() != OpMove {
|
if msg.GetOpCode() != OpMove {
|
||||||
|
logger.Debug("Ignoring non-move opcode: %d", msg.GetOpCode())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,46 +192,72 @@ func (m *TicTacToeMatch) MatchLoop(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(msg.GetData(), &move); err != nil {
|
if err := json.Unmarshal(msg.GetData(), &move); err != nil {
|
||||||
logger.Warn("Invalid move payload: %v", err)
|
logger.Warn("Invalid move payload from %s: %v", msg.GetUserId(), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
playerID := msg.GetUserId()
|
playerID := msg.GetUserId()
|
||||||
playerIdx := indexOf(s.Players, playerID)
|
playerIdx := indexOf(s.Players, playerID)
|
||||||
|
logger.Info("Received move from %s (playerIdx=%d): row=%d col=%d", playerID, playerIdx, move.Row, move.Col)
|
||||||
|
|
||||||
|
if playerIdx == -1 {
|
||||||
|
logger.Warn("Move rejected: player %s not in player list", playerID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if playerIdx != s.Turn {
|
if playerIdx != s.Turn {
|
||||||
// not your turn
|
logger.Warn("Move rejected: not player's turn (playerIdx=%d turn=%d)", playerIdx, s.Turn)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if move.Row < 0 || move.Row > 2 || move.Col < 0 || move.Col > 2 {
|
if move.Row < 0 || move.Row > 2 || move.Col < 0 || move.Col > 2 {
|
||||||
|
logger.Warn("Move rejected: out of bounds (%d,%d)", move.Row, move.Col)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Board[move.Row][move.Col] != "" {
|
if s.Board[move.Row][move.Col] != "" {
|
||||||
|
logger.Warn("Move rejected: cell already occupied (%d,%d)", move.Row, move.Col)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
symbols := []string{"X", "O"}
|
symbols := []string{"X", "O"}
|
||||||
if playerIdx < 0 || playerIdx >= len(symbols) {
|
if playerIdx < 0 || playerIdx >= len(symbols) {
|
||||||
|
logger.Warn("Move rejected: invalid player index %d", playerIdx)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
s.Board[move.Row][move.Col] = symbols[playerIdx]
|
|
||||||
|
|
||||||
|
// Apply move
|
||||||
|
s.Board[move.Row][move.Col] = symbols[playerIdx]
|
||||||
|
changed = true
|
||||||
|
logger.Info("Move applied for player %s -> %s at (%d,%d)", playerID, symbols[playerIdx], move.Row, move.Col)
|
||||||
|
|
||||||
|
// Check win/draw
|
||||||
if winner := checkWinner(s.Board); winner != "" {
|
if winner := checkWinner(s.Board); winner != "" {
|
||||||
s.Winner = winner
|
s.Winner = winner
|
||||||
s.GameOver = true
|
s.GameOver = true
|
||||||
|
logger.Info("Game over! Winner: %s", winner)
|
||||||
} else if fullBoard(s.Board) {
|
} else if fullBoard(s.Board) {
|
||||||
s.Winner = "draw"
|
s.Winner = "draw"
|
||||||
s.GameOver = true
|
s.GameOver = true
|
||||||
|
logger.Info("Game over! Draw")
|
||||||
} else {
|
} else {
|
||||||
s.Turn = 1 - s.Turn
|
s.Turn = 1 - s.Turn
|
||||||
|
logger.Info("Turn advanced to %d", s.Turn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broadcast updated state to everyone
|
// If anything changed (or periodically if you want), broadcast updated state to everyone
|
||||||
stateJSON, _ := json.Marshal(s)
|
if changed {
|
||||||
|
stateJSON, err := json.Marshal(s)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to marshal state: %v", err)
|
||||||
|
} else {
|
||||||
if err := dispatcher.BroadcastMessage(OpState, stateJSON, nil, nil, true); err != nil {
|
if err := dispatcher.BroadcastMessage(OpState, stateJSON, nil, nil, true); err != nil {
|
||||||
logger.Error("BroadcastMessage failed: %v", err)
|
logger.Error("BroadcastMessage failed: %v", err)
|
||||||
|
} else {
|
||||||
|
logger.Info("Broadcasted updated state to players")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
|||||||
Reference in New Issue
Block a user