package games import ( "fmt" "encoding/json" "localrepo/plugins/structs" ) // // BATTLESHIP RULES IMPLEMENTATION // // NOTES: // - 2 players // - Each player has 2 boards: // 1. Their own ship board (state.Board is not reused here) // 2. Their "shots" board (hits/misses on opponent) // - We store boards in Player.Metadata as JSON strings // (simplest method without changing your structs). // // ShipBoard and ShotBoard are encoded inside Metadata: // // Metadata["ship_board"] = JSON string of [][]string // Metadata["shot_board"] = JSON string of [][]string // // ------------------------------ // Helpers: encode/decode // ------------------------------ func encodeBoard(b [][]string) string { out := "[" for i, row := range b { out += "[" for j, col := range row { out += fmt.Sprintf("%q", col) if j < len(row)-1 { out += "," } } out += "]" if i < len(b)-1 { out += "," } } out += "]" return out } func decodeBoard(s string) [][]string { var out [][]string // should never fail; safe fallback _ = json.Unmarshal([]byte(s), &out) return out } // ------------------------------ // BattleshipRules // ------------------------------ type BattleshipRules struct{} func (b *BattleshipRules) MaxPlayers() int { return 2 } func (b *BattleshipRules) InitBoards(players []*structs.Player, cfg GameConfiguration) map[string]*structs.Board { boards := make(map[string]*structs.Board) // One ships board and one shots board per player for _, p := range players { pid := fmt.Sprintf("p%d", p.Index) // Player's fleet board (ships placement) boards[pid+"_ships"] = structs.NewBoard(cfg.Board.Rows, cfg.Board.Cols) // Player's attack tracking board (shots fired at opponent) boards[pid+"_shots"] = structs.NewBoard(cfg.Board.Rows, cfg.Board.Cols) } return boards } // ------------------------------ // Assign player boards // ------------------------------ func (b *BattleshipRules) AssignPlayerSymbols(players []*structs.Player) { // nothing needed for classic mode } // ------------------------------ // ValidateMove // payload.data = { "row": int, "col": int } // ------------------------------ func (b *BattleshipRules) ValidateMove(state *structs.MatchState, playerIdx int, payload MovePayload) bool { rf, ok1 := payload.Data["row"].(float64) cf, ok2 := payload.Data["col"].(float64) if !ok1 || !ok2 { return false } r := int(rf) c := int(cf) shotKey := fmt.Sprintf("p%d_shots", playerIdx) shotBoard := state.Boards[shotKey] if shotBoard == nil { return false } if !shotBoard.InBounds(r, c) { return false } // can't shoot same cell twice if !shotBoard.IsEmpty(r, c) { return false } return true } // ----------------------------- // APPLY MOVE (MODE B — CLASSIC) // ----------------------------- func (b *BattleshipRules) ApplyMove( state *structs.MatchState, playerIdx int, payload MovePayload, ) (bool, bool, int, bool) { r := int(payload.Data["row"].(float64)) c := int(payload.Data["col"].(float64)) shotKey := fmt.Sprintf("p%d_shots", playerIdx) shipKey := fmt.Sprintf("p%d_ships", 1-playerIdx) 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 { shots.Set(r, c, "M") } // check game over over, winner := b.CheckGameOver(state) // keepTurn = hit (classic rule) return true, over, winner, hit } // ------------------------------ // CheckGameOver // ------------------------------ func (b *BattleshipRules) CheckGameOver(state *structs.MatchState) (bool, int) { for i := range state.Players { shipKey := fmt.Sprintf("p%d_ships", i) ships := state.Boards[shipKey] alive := false 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 { // this player has no ships left → opponent wins return true, 1 - i } } return false, -1 } // ------------------------------ // Forfeit Winner // ------------------------------ func (b *BattleshipRules) ForfeitWinner(state *structs.MatchState, leaverIndex int) int { // If player leaves, opponent automatically wins. if leaverIndex == 0 { return 1 } if leaverIndex == 1 { return 0 } return -1 }