diff --git a/plugins/games/battleship.go b/plugins/games/battleship.go index 8af2289..1c5c41b 100644 --- a/plugins/games/battleship.go +++ b/plugins/games/battleship.go @@ -81,22 +81,7 @@ func (b *BattleshipRules) InitBoards(players []*structs.Player, cfg GameConfigur // Assign player boards // ------------------------------ func (b *BattleshipRules) AssignPlayerSymbols(players []*structs.Player) { - // Battleship has no symbols like X/O, - // but we use this hook to initialize per-player boards. - - for _, p := range players { - // 10x10 boards - empty := make([][]string, 10) - for r := range empty { - empty[r] = make([]string, 10) - } - - // ship board → players place ships manually via a "setup" phase - p.Metadata["ship_board"] = encodeBoard(empty) - - // shot board → empty grid that tracks hits/misses - p.Metadata["shot_board"] = encodeBoard(empty) - } + // nothing needed for classic mode } // ------------------------------ @@ -105,72 +90,87 @@ func (b *BattleshipRules) AssignPlayerSymbols(players []*structs.Player) { // ------------------------------ func (b *BattleshipRules) ValidateMove(state *structs.MatchState, playerIdx int, payload MovePayload) bool { - rF, ok1 := payload.Data["row"].(float64) - cF, ok2 := payload.Data["col"].(float64) + rf, ok1 := payload.Data["row"].(float64) + cf, ok2 := payload.Data["col"].(float64) if !ok1 || !ok2 { return false } + r := int(rf) + c := int(cf) - r := int(rF) - c := int(cF) - - if r < 0 || r > 9 || c < 0 || c > 9 { + shotKey := fmt.Sprintf("p%d_shots", playerIdx) + shotBoard := state.Boards[shotKey] + if shotBoard == nil { return false } - // Check if this spot was already shot before - shotBoard := decodeBoard(state.Players[playerIdx].Metadata["shot_board"]) - return shotBoard[r][c] == "" + if !shotBoard.InBounds(r, c) { + return false + } + + // can't shoot same cell twice + if !shotBoard.IsEmpty(r, c) { + return false + } + + return true } -// ------------------------------ -// ApplyMove -// ------------------------------ +// ----------------------------- +// APPLY MOVE (MODE B — CLASSIC) +// ----------------------------- func (b *BattleshipRules) ApplyMove( state *structs.MatchState, playerIdx int, payload MovePayload, -) (bool, bool, int) { - attacker := state.Players[playerIdx] - defenderIdx := 1 - playerIdx - defender := state.Players[defenderIdx] +) (bool, bool, int, bool) { r := int(payload.Data["row"].(float64)) c := int(payload.Data["col"].(float64)) - shotBoard := decodeBoard(attacker.Metadata["shot_board"]) - shipBoard := decodeBoard(defender.Metadata["ship_board"]) + shotKey := fmt.Sprintf("p%d_shots", playerIdx) + shipKey := fmt.Sprintf("p%d_ships", 1-playerIdx) - if shipBoard[r][c] == "S" { - shotBoard[r][c] = "H" - shipBoard[r][c] = "X" + shots := state.Boards[shotKey] + ships := state.Boards[shipKey] + + hit := false + + if ships.Get(r, c) == "S" { + // hit + hit = true + shots.Set(r, c, "H") + ships.Set(r, c, "X") // mark destroyed section } else { - shotBoard[r][c] = "M" + shots.Set(r, c, "M") } - attacker.Metadata["shot_board"] = encodeBoard(shotBoard) - defender.Metadata["ship_board"] = encodeBoard(shipBoard) - + // check game over over, winner := b.CheckGameOver(state) - return true, over, winner + // keepTurn = hit (classic rule) + return true, over, winner, hit } // ------------------------------ // CheckGameOver // ------------------------------ func (b *BattleshipRules) CheckGameOver(state *structs.MatchState) (bool, int) { - - for i, p := range state.Players { - ships := decodeBoard(p.Metadata["ship_board"]) + for i := range state.Players { + shipKey := fmt.Sprintf("p%d_ships", i) + ships := state.Boards[shipKey] alive := false - for r := range ships { - for c := range ships[r] { - if ships[r][c] == "S" { + for r := 0; r < ships.Rows; r++ { + for c := 0; c < ships.Cols; c++ { + if ships.Get(r, c) == "S" { alive = true + break } } + if alive { + break + } } if !alive { diff --git a/plugins/games/rules.go b/plugins/games/rules.go index 7224eea..351d32a 100644 --- a/plugins/games/rules.go +++ b/plugins/games/rules.go @@ -22,7 +22,11 @@ type GameRules interface { // Apply a move. // Returns: (changed, gameOver, winnerIndex) - ApplyMove(state *structs.MatchState, playerIdx int, payload MovePayload) (bool, bool, int) + ApplyMove( + state *structs.MatchState, + playerIdx int, + payload MovePayload, + ) (changed bool, gameOver bool, winnerIdx int, keepTurn bool) // If a player leaves, who wins? // Return: diff --git a/plugins/games/tic_tac_toe.go b/plugins/games/tic_tac_toe.go index ca151df..7d99096 100644 --- a/plugins/games/tic_tac_toe.go +++ b/plugins/games/tic_tac_toe.go @@ -70,10 +70,10 @@ func (t *TicTacToeRules) ApplyMove( state *structs.MatchState, playerIdx int, payload MovePayload, -) (bool, bool, int) { +) (bool, bool, int, bool) { b := state.Boards["tictactoe"] if b == nil { - return false, false, -1 + return false, false, -1, false } symbol := state.Players[playerIdx].Metadata["symbol"] @@ -85,7 +85,7 @@ func (t *TicTacToeRules) ApplyMove( over, winner := t.CheckGameOver(state) - return true, over, winner + return true, over, winner, false } diff --git a/plugins/modules/match.go b/plugins/modules/match.go index eb859ff..f7be266 100644 --- a/plugins/modules/match.go +++ b/plugins/modules/match.go @@ -318,7 +318,7 @@ func (m *GenericMatch) MatchLoop( } // Delegate to rules.ApplyMove which returns (changed, gameOver, winnerIndex) - stateChanged, gameOver, winnerIdx := m.Rules.ApplyMove(s, playerIdx, payload) + stateChanged, gameOver, winnerIdx, keepTurn := m.Rules.ApplyMove(s, playerIdx, payload) if stateChanged { changed = true @@ -328,7 +328,7 @@ func (m *GenericMatch) MatchLoop( s.GameOver = true s.Winner = winnerIdx } else { - if len(s.Players) > 0 { + if !keepTurn && len(s.Players) > 0 { s.Turn = (s.Turn + 1) % len(s.Players) } }