diff --git a/local.yml b/local.yml index 52cd663..4da3e63 100644 --- a/local.yml +++ b/local.yml @@ -7,3 +7,10 @@ session: socket: max_message_size_bytes: 4096 # reserved buffer max_request_size_bytes: 131072 +matchmaker: + max_tickets: 10000 + interval_ms: 100 + query: + properties: + string: + mode: true diff --git a/plugins/match.go b/plugins/match.go index d5d0378..b1ebe68 100644 --- a/plugins/match.go +++ b/plugins/match.go @@ -106,6 +106,21 @@ func (m *TicTacToeMatch) MatchJoin( } 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 } @@ -128,6 +143,18 @@ func (m *TicTacToeMatch) MatchLeave( s.GameOver = true s.Winner = "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 @@ -151,8 +178,11 @@ func (m *TicTacToeMatch) MatchLoop( return s } + changed := false + for _, msg := range messages { if msg.GetOpCode() != OpMove { + logger.Debug("Ignoring non-move opcode: %d", msg.GetOpCode()) continue } @@ -162,46 +192,72 @@ func (m *TicTacToeMatch) MatchLoop( } 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 } playerID := msg.GetUserId() 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 { - // not your turn + logger.Warn("Move rejected: not player's turn (playerIdx=%d turn=%d)", playerIdx, s.Turn) continue } 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 } if s.Board[move.Row][move.Col] != "" { + logger.Warn("Move rejected: cell already occupied (%d,%d)", move.Row, move.Col) continue } symbols := []string{"X", "O"} if playerIdx < 0 || playerIdx >= len(symbols) { + logger.Warn("Move rejected: invalid player index %d", playerIdx) 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 != "" { s.Winner = winner s.GameOver = true + logger.Info("Game over! Winner: %s", winner) } else if fullBoard(s.Board) { s.Winner = "draw" s.GameOver = true + logger.Info("Game over! Draw") } else { s.Turn = 1 - s.Turn + logger.Info("Turn advanced to %d", s.Turn) } } - // Broadcast updated state to everyone - stateJSON, _ := json.Marshal(s) - if err := dispatcher.BroadcastMessage(OpState, stateJSON, nil, nil, true); err != nil { - logger.Error("BroadcastMessage failed: %v", err) + // If anything changed (or periodically if you want), broadcast updated state to everyone + 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 { + logger.Error("BroadcastMessage failed: %v", err) + } else { + logger.Info("Broadcasted updated state to players") + } + } } return s